diff --git a/.eslintrc.js b/.eslintrc.js index 7925bceafd3d57..2e92b495691873 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -177,6 +177,8 @@ module.exports = { '@wordpress/dependency-group': 'error', '@wordpress/wp-global-usage': 'error', '@wordpress/react-no-unsafe-timeout': 'error', + '@wordpress/i18n-hyphenated-range': 'error', + '@wordpress/i18n-no-flanking-whitespace': 'error', '@wordpress/i18n-text-domain': [ 'error', { @@ -214,6 +216,12 @@ module.exports = { }, ], 'no-restricted-syntax': [ 'error', ...restrictedSyntax ], + 'jsdoc/check-tag-names': [ + 'error', + { + definedTags: [ 'jest-environment' ], + }, + ], }, overrides: [ { @@ -277,6 +285,84 @@ module.exports = { ], }, }, + { + files: [ 'packages/*/src/**/*.[tj]s?(x)' ], + excludedFiles: [ + 'packages/*/src/**/@(test|stories)/**', + '**/*.@(native|ios|android).js', + ], + rules: { + 'no-restricted-syntax': [ + 'error', + ...restrictedSyntax, + ...restrictedSyntaxComponents, + // Temporary rules until we're ready to officially deprecate the bottom margins. + ...[ + 'BaseControl', + 'CheckboxControl', + 'ComboboxControl', + 'DimensionControl', + 'FocalPointPicker', + 'RangeControl', + 'SearchControl', + 'SelectControl', + 'TextControl', + 'TextareaControl', + 'ToggleControl', + 'ToggleGroupControl', + 'TreeSelect', + ].map( ( componentName ) => ( { + selector: `JSXOpeningElement[name.name="${ componentName }"]:not(:has(JSXAttribute[name.name="__nextHasNoMarginBottom"]))`, + message: + componentName + + ' should have the `__nextHasNoMarginBottom` prop to opt-in to the new margin-free styles.', + } ) ), + // Temporary rules until we're ready to officially default to the new size. + ...[ + 'BorderBoxControl', + 'BorderControl', + 'BoxControl', + 'ComboboxControl', + 'CustomSelectControl', + 'DimensionControl', + 'FontAppearanceControl', + 'FontFamilyControl', + 'FontSizePicker', + 'FormTokenField', + 'InputControl', + 'LetterSpacingControl', + 'LineHeightControl', + 'NumberControl', + 'RangeControl', + 'SelectControl', + 'TextControl', + 'ToggleGroupControl', + 'UnitControl', + ].map( ( componentName ) => ( { + // Falsy `__next40pxDefaultSize` without a non-default `size` prop. + selector: `JSXOpeningElement[name.name="${ componentName }"]:not(:has(JSXAttribute[name.name="__next40pxDefaultSize"][value.expression.value!=false])):not(:has(JSXAttribute[name.name="size"][value.value!="default"]))`, + message: + componentName + + ' should have the `__next40pxDefaultSize` prop to opt-in to the new default size.', + } ) ), + { + // Falsy `__next40pxDefaultSize` without a `render` prop. + selector: + 'JSXOpeningElement[name.name="FormFileUpload"]:not(:has(JSXAttribute[name.name="__next40pxDefaultSize"][value.expression.value!=false])):not(:has(JSXAttribute[name.name="render"]))', + message: + 'FormFileUpload should have the `__next40pxDefaultSize` prop to opt-in to the new default size.', + }, + // Temporary rules until all existing components have the `__next40pxDefaultSize` prop. + ...[ 'Button' ].map( ( componentName ) => ( { + // Not strict. Allows pre-existing __next40pxDefaultSize={ false } usage until they are all manually updated. + selector: `JSXOpeningElement[name.name="${ componentName }"]:not(:has(JSXAttribute[name.name="__next40pxDefaultSize"])):not(:has(JSXAttribute[name.name="size"]))`, + message: + componentName + + ' should have the `__next40pxDefaultSize` prop to opt-in to the new default size.', + } ) ), + ], + }, + }, { files: [ // Components package. diff --git a/.gitattributes b/.gitattributes index 6c72e80a402976..1dc48620d8b67c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -20,3 +20,6 @@ changelog.txt linguist-language=Markdown # Flag docs directory as documentation for GitHub stats. docs/** linguist-documentation + +# TSConfig files use jsonc. +tsconfig*.json linguist-language=jsonc diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 88971069b3adf6..2ec03cba722c6b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,5 @@ # Documentation -/docs @ajitbohra @ryanwelcher @juanmaguitar @fabiankaegy @ndiego +/docs @ajitbohra @juanmaguitar @fabiankaegy @ndiego /packages/interactivity/docs @juanmaguitar # Schemas @@ -18,7 +18,7 @@ /packages/block-library/src/comment-template @michalczaplinski /packages/block-library/src/comments @michalczaplinski /packages/block-library/src/table-of-contents @ZebulanStanphill -/packages/block-library/src/image @artemiomorales @michalczaplinski +/packages/block-library/src/image @artemiomorales # Duotone /lib/block-supports/duotone.php @@ -119,9 +119,9 @@ /packages/plugins @gziolo @adamsilverstein # Rich Text -/packages/format-library @ellatrix @dcalhoun -/packages/rich-text @ellatrix @dcalhoun -/packages/block-editor/src/components/rich-text @ellatrix @dcalhoun +/packages/format-library @ellatrix +/packages/rich-text @ellatrix +/packages/block-editor/src/components/rich-text @ellatrix # Project Management /.github @desrosj diff --git a/.github/ISSUE_TEMPLATE/Bug_report.yml b/.github/ISSUE_TEMPLATE/Bug_report.yml index 1109056e7e5d56..5d7c876ccefca7 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.yml +++ b/.github/ISSUE_TEMPLATE/Bug_report.yml @@ -49,25 +49,19 @@ body: validations: required: false - - type: dropdown + - type: checkboxes id: existing attributes: label: Please confirm that you have searched existing issues in the repo. description: You can do this by searching https://github.com/WordPress/gutenberg/issues and making sure the bug is not related to another plugin. - multiple: true options: - - 'Yes' - - 'No' - validations: - required: true + - label: 'Yes' + required: true - - type: dropdown + - type: checkboxes id: plugins attributes: label: Please confirm that you have tested with all plugins deactivated except Gutenberg. - multiple: true options: - - 'Yes' - - 'No' - validations: - required: true + - label: 'Yes' + required: true diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml index f9fa22d324d777..ce830c04f651d8 100644 --- a/.github/workflows/build-plugin-zip.yml +++ b/.github/workflows/build-plugin-zip.yml @@ -3,7 +3,10 @@ name: Build Gutenberg Plugin Zip on: pull_request: push: - branches: [trunk] + branches: + - trunk + - 'release/**' + - 'wp/**' workflow_dispatch: inputs: version: @@ -171,7 +174,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Use desired version of Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' check-latest: true @@ -270,12 +273,12 @@ jobs: run: echo "version=$(echo $VERSION | cut -d / -f 3 | sed 's/-rc./ RC/' )" >> $GITHUB_OUTPUT - name: Download Plugin Zip Artifact - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: gutenberg-plugin - name: Download Release Notes Artifact - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: release-notes @@ -333,7 +336,7 @@ jobs: git config user.email gutenberg@wordpress.org - name: Setup Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: 'main/.nvmrc' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml index 507892aae1dd5f..fe2cc2edf6ca7f 100644 --- a/.github/workflows/bundle-size.yml +++ b/.github/workflows/bundle-size.yml @@ -94,7 +94,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Use desired version of Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' check-latest: true @@ -103,6 +103,6 @@ jobs: - uses: preactjs/compressed-size-action@f780fd104362cfce9e118f9198df2ee37d12946c # v2.6.0 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' - pattern: '{build/**/*.min.js,build/**/*.css}' + pattern: '{build/**/*.min.js,build/**/*.css,build-module/**/*.min.js}' clean-script: 'distclean' >>>>>>> upstream/trunk diff --git a/.github/workflows/check-backport-changelog.yml b/.github/workflows/check-backport-changelog.yml index 355acb37bd14d4..366bad9fdbc247 100644 --- a/.github/workflows/check-backport-changelog.yml +++ b/.github/workflows/check-backport-changelog.yml @@ -1,4 +1,4 @@ -name: Verify Core Backport Changlog +name: Verify Core Backport Changelog on: pull_request: @@ -18,22 +18,15 @@ on: - '!packages/e2e-tests/**' jobs: check: - name: Check CHANGELOG diff + name: Check for a Core backport changelog entry runs-on: ubuntu-latest + if: ${{ !contains(github.event.pull_request.labels.*.name, 'No Core Sync Required') && !contains(github.event.pull_request.labels.*.name, 'Backport from WordPress Core') }} 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@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: ref: ${{ github.event.pull_request.head.ref }} repository: ${{ github.event.pull_request.head.repo.full_name }} - fetch-depth: ${{ env.PR_COMMIT_COUNT }} - show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - name: 'Fetch relevant history from origin' - run: git fetch origin ${{ github.event.pull_request.base.ref }} - - name: Check CHANGELOG status - if: ${{ !contains(github.event.pull_request.labels.*.name, 'No Core Sync Required') && !contains(github.event.pull_request.labels.*.name, 'Backport from WordPress Core') }} + - name: Check the changelog folder env: PR_NUMBER: ${{ github.event.number }} run: | diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index 92b92be8e885e8..1474b48c0b94f3 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -225,7 +225,7 @@ jobs: ref: trunk show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - uses: actions/download-artifact@v4.1.7 + - uses: actions/download-artifact@v4.1.8 # Don't fail the job if there isn't any flaky tests report. continue-on-error: true with: diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 288bef1580038f..98615b93b8a176 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -69,13 +69,13 @@ jobs: - name: Compare performance with base branch if: github.event_name == 'push' # The base hash used here need to be a commit that is compatible with the current WP version - # The current one is 9725060a5b18904c6cc5fdbe4b06fbde7419e02c and it needs to be updated every WP major release. + # The current one is 5f4c9c853b15092ed885d5280edefb973c37d9e9 and it needs to be updated every WP major release. # It is used as a base comparison point to avoid fluctuation in the performance metrics. run: | 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 $GITHUB_SHA 9725060a5b18904c6cc5fdbe4b06fbde7419e02c --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" + ./bin/plugin/cli.js perf $GITHUB_SHA 5f4c9c853b15092ed885d5280edefb973c37d9e9 --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" - name: Compare performance with custom branches if: github.event_name == 'workflow_dispatch' @@ -101,7 +101,7 @@ jobs: CODEHEALTH_PROJECT_TOKEN: ${{ secrets.CODEHEALTH_PROJECT_TOKEN }} run: | COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI") - ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA 9725060a5b18904c6cc5fdbe4b06fbde7419e02c $COMMITTED_AT + ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA 5f4c9c853b15092ed885d5280edefb973c37d9e9 $COMMITTED_AT - name: Archive debug artifacts (screenshots, HTML snapshots) uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 diff --git a/.github/workflows/props-bot.yml b/.github/workflows/props-bot.yml index 0f21f47ef14f99..b2332aabb816c7 100644 --- a/.github/workflows/props-bot.yml +++ b/.github/workflows/props-bot.yml @@ -18,7 +18,7 @@ on: # You cannot filter this event for PR comments only. # However, the logic below does short-circuit the workflow for issues. issue_comment: - type: + types: - created # This event will run everytime a new PR review is initially submitted. pull_request_review: diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml index 31df5a1575fa84..a24e5012474025 100644 --- a/.github/workflows/publish-npm-packages.yml +++ b/.github/workflows/publish-npm-packages.yml @@ -67,7 +67,7 @@ jobs: - name: Setup Node.js if: ${{ github.event.inputs.release_type != 'wp' }} - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: 'cli/.nvmrc' registry-url: 'https://registry.npmjs.org' @@ -75,7 +75,7 @@ jobs: - name: Setup Node.js (for WP major version) if: ${{ github.event.inputs.release_type == 'wp' && github.event.inputs.wp_version }} - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: 'publish/.nvmrc' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/pull-request-automation.yml b/.github/workflows/pull-request-automation.yml index 7766893e54a970..26eb08d759a2a9 100644 --- a/.github/workflows/pull-request-automation.yml +++ b/.github/workflows/pull-request-automation.yml @@ -49,7 +49,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Use desired version of Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' check-latest: true diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index 4e032196e01509..75f4fab0b7c338 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -66,7 +66,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Use desired version of Java - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + uses: actions/setup-java@2dfa2011c5b2a0f1489bf9e433881c92c1631f88 # v4.3.0 with: distribution: 'corretto' java-version: '17' @@ -85,7 +85,7 @@ jobs: run: npm run native test:e2e:setup - name: Gradle cache - uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 + uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0 - name: AVD cache uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 @@ -98,7 +98,7 @@ jobs: - name: Create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@77986be26589807b8ebab3fde7bbf5c60dabec32 # v2.31.0 + uses: reactivecircus/android-emulator-runner@f0d1ed2dcad93c7479e8b2f2226c83af54494915 # v2.32.0 with: api-level: ${{ matrix.api-level }} force-avd-creation: false @@ -109,7 +109,7 @@ jobs: script: echo "Generated AVD snapshot for caching." - name: Run tests - uses: reactivecircus/android-emulator-runner@77986be26589807b8ebab3fde7bbf5c60dabec32 # v2.31.0 + uses: reactivecircus/android-emulator-runner@f0d1ed2dcad93c7479e8b2f2226c83af54494915 # v2.32.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 1849dc0c39aae7..8232d3703256ae 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -125,7 +125,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} >>>>>>> upstream/trunk - - uses: ruby/setup-ruby@3783f195e29b74ae398d7caca108814bbafde90e # v1.180.1 + - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 with: # `.ruby-version` file location working-directory: packages/react-native-editor/ios diff --git a/.github/workflows/static-checks.yml b/.github/workflows/static-checks.yml index 48402485561c04..ecea0f9d5c1c28 100644 --- a/.github/workflows/static-checks.yml +++ b/.github/workflows/static-checks.yml @@ -89,7 +89,7 @@ jobs: >>>>>>> upstream/trunk - name: Use desired version of Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' check-latest: true diff --git a/.github/workflows/sync-backport-changelog.yml b/.github/workflows/sync-backport-changelog.yml new file mode 100644 index 00000000000000..bbc5663cf715be --- /dev/null +++ b/.github/workflows/sync-backport-changelog.yml @@ -0,0 +1,91 @@ +name: Sync Core Backport Issue + +on: + push: + branches: + - trunk + issues: + types: [labeled] + +jobs: + sync-backport-changelog: + name: Sync Core Backport Issue + runs-on: ubuntu-latest + if: > + github.event_name == 'push' || + ( + github.event_name == 'issues' && + github.event.action == 'labeled' && + github.event.label.name == 'šŸ¤– Sync Backport Changelog' + ) + steps: + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + fetch-depth: 2 # Fetch the last two commits to compare changes + - name: Check for changes in backport-changelog + if: github.event_name == 'push' + run: | + if git diff --quiet HEAD^ HEAD -- backport-changelog; then + echo "skip_sync=true" >> "$GITHUB_ENV" + fi + - name: Sync Issue + if: ${{ ! env.skip_sync }} + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const labelName = 'šŸ¤– Sync Backport Changelog'; + const issues = await github.paginate(github.rest.issues.listForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + labels: [labelName], + state: 'open', + per_page: 1, + }); + + if (issues.length === 0) { + console.log(`No issues found with the "${labelName}" label.`); + return; + } + + const [latestIssue] = issues; + const versionMatch = latestIssue.title.match(/(\d+\.\d+)/); + if (!versionMatch) { + console.log('Could not find a version number in the latest issue title.'); + return; + } + + const version = versionMatch[1]; + console.log(`Latest version: ${version}`); + + const { execSync } = require('child_process'); + const processedChangelog = execSync(`awk '/./ {print ($0 ~ /^[-*]/ ? " " : "- ") $0}' backport-changelog/${version}/*.md`).toString().trim(); + + const startDelimiter = ''; + const endDelimiter = ''; + const autoGeneratedContent = `${startDelimiter}\n${processedChangelog}\n${endDelimiter}`; + + const existingBody = latestIssue.body ?? ''; + + let newBody; + + const regex = new RegExp(`${startDelimiter}[\\s\\S]*${endDelimiter}`); + if (regex.test(existingBody)) { + // If delimiters exist, replace the content between them + newBody = existingBody.replace(regex, autoGeneratedContent); + } else { + // If delimiters don't exist, append the new content at the end + newBody = `${existingBody}\n\n${autoGeneratedContent}`; + } + + if (newBody.trim() !== existingBody.trim()) { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: latestIssue.number, + body: newBody + }); + console.log('Issue description updated successfully.'); + } else { + console.log('Issue description is already up to date.'); + } diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index fd1c3e29484d6e..e7b1c7e18345ad 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -251,7 +251,9 @@ jobs: uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: build-assets - path: ./build/ + path: | + ./build/ + ./build-module/ test-php: name: PHP ${{ matrix.php }}${{ matrix.multisite && ' multisite' || '' }}${{ matrix.wordpress != '' && format( ' (WP {0}) ', matrix.wordpress ) || '' }} on ubuntu-latest @@ -301,7 +303,7 @@ jobs: # dependency versions are installed and cached. ## - name: Set up PHP - uses: shivammathur/setup-php@2e947f1f6932d141d076ca441d0e1e881775e95b # v2.31.0 + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 with: php-version: '${{ matrix.php }}' ini-file: development @@ -325,10 +327,9 @@ jobs: custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") - name: Download built JavaScript assets - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: build-assets - path: ./build - name: Docker debug information run: | @@ -403,7 +404,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up PHP - uses: shivammathur/setup-php@2e947f1f6932d141d076ca441d0e1e881775e95b # v2.31.0 + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 with: php-version: '7.4' coverage: none diff --git a/.github/workflows/upload-release-to-plugin-repo.yml b/.github/workflows/upload-release-to-plugin-repo.yml index 6f01a4f6ff12fd..81a9c4739ac19b 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -189,7 +189,7 @@ jobs: sed -i "s/$STABLE_TAG_PLACEHOLDER/Stable tag: $VERSION/g" ./trunk/readme.txt - name: Download Changelog Artifact - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: changelog trunk path: trunk @@ -247,7 +247,7 @@ jobs: sed -i "s/$STABLE_TAG_PLACEHOLDER/Stable tag: $VERSION/g" "$VERSION/readme.txt" - name: Download Changelog Artifact - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: changelog trunk path: ${{ github.event.release.name }} diff --git a/.stylelintrc.json b/.stylelintrc.json index df01978222e632..557376e02c4062 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,9 +1,17 @@ { - "extends": "@wordpress/stylelint-config/scss", + "extends": "@wordpress/stylelint-config/scss-stylistic", "rules": { "at-rule-empty-line-before": null, "at-rule-no-unknown": null, "comment-empty-line-before": null, + "declaration-property-value-allowed-list": [ + { + "flex-direction": "/^(?!(row|column)-reverse).*$/" + }, + { + "message": "Avoid the flex-direction reverse values. For accessibility reasons, visual, reading, and DOM order must match. Only use the reverse values when they do not affect reading order, meaning, and interaction." + } + ], "declaration-property-value-disallowed-list": [ { "/.*/": [ "/--wp-components-color-/" ] @@ -13,12 +21,12 @@ } ], "font-weight-notation": null, - "max-line-length": null, + "@stylistic/max-line-length": null, "no-descending-specificity": null, "property-disallowed-list": [ [ "order" ], { - "message": "Avoid the order property. For accessibility reasons, visual, reading, and DOM order must match. Only use the order property when it does not affect reading order, meaning, and interaction" + "message": "Avoid the order property. For accessibility reasons, visual, reading, and DOM order must match. Only use the order property when it does not affect reading order, meaning, and interaction." } ], "rule-empty-line-before": null, @@ -26,7 +34,7 @@ "value-keyword-case": null, "scss/operator-no-unspaced": null, "scss/selector-no-redundant-nesting-selector": null, - "scss/at-import-partial-extension": null, + "scss/load-partial-extension": null, "scss/no-global-function-names": null, "scss/comment-no-empty": null, "scss/at-extend-no-missing-placeholder": null, diff --git a/.wp-env.json b/.wp-env.json index 20d5597e54bbc9..05ea05b2809f9c 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,4 +1,5 @@ { + "$schema": "./schemas/json/wp-env.json", "core": "WordPress/WordPress", "plugins": [ "." ], "themes": [ "./test/emptytheme" ], diff --git a/backport-changelog/6.6/6987.md b/backport-changelog/6.6/6987.md new file mode 100644 index 00000000000000..c3bf36f8f9933d --- /dev/null +++ b/backport-changelog/6.6/6987.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6987 + +* https://github.com/WordPress/gutenberg/pull/63207 \ No newline at end of file diff --git a/backport-changelog/6.6/6989.md b/backport-changelog/6.6/6989.md new file mode 100644 index 00000000000000..3d236938ff74a5 --- /dev/null +++ b/backport-changelog/6.6/6989.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6989 + +* https://github.com/WordPress/gutenberg/pull/63172 diff --git a/backport-changelog/6.6/7012.md b/backport-changelog/6.6/7012.md new file mode 100644 index 00000000000000..265f3dad981e44 --- /dev/null +++ b/backport-changelog/6.6/7012.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7012 + +* https://github.com/WordPress/gutenberg/pull/63403 diff --git a/backport-changelog/6.6/7036.md b/backport-changelog/6.6/7036.md new file mode 100644 index 00000000000000..afc4d16bf011b7 --- /dev/null +++ b/backport-changelog/6.6/7036.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7036 + +* https://github.com/WordPress/gutenberg/pull/63436 diff --git a/backport-changelog/6.6/7061.md b/backport-changelog/6.6/7061.md new file mode 100644 index 00000000000000..307e6575cf38d8 --- /dev/null +++ b/backport-changelog/6.6/7061.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7061 + +* https://github.com/WordPress/gutenberg/pull/63726 diff --git a/backport-changelog/6.6/7088.md b/backport-changelog/6.6/7088.md new file mode 100644 index 00000000000000..46bd1147464846 --- /dev/null +++ b/backport-changelog/6.6/7088.md @@ -0,0 +1,4 @@ +https://github.com/WordPress/wordpress-develop/pull/7088 + +* https://github.com/WordPress/gutenberg/pull/63918 + diff --git a/backport-changelog/6.6/7097.md b/backport-changelog/6.6/7097.md new file mode 100644 index 00000000000000..e674d5ea76ba6f --- /dev/null +++ b/backport-changelog/6.6/7097.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7097 + +* https://github.com/WordPress/gutenberg/pull/63980 diff --git a/backport-changelog/6.6/7145.md b/backport-changelog/6.6/7145.md new file mode 100644 index 00000000000000..386f765cb22fa8 --- /dev/null +++ b/backport-changelog/6.6/7145.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7145 + +* https://github.com/WordPress/gutenberg/pull/64076 diff --git a/backport-changelog/6.7/6668.md b/backport-changelog/6.7/6668.md new file mode 100644 index 00000000000000..7653dd8d8294ee --- /dev/null +++ b/backport-changelog/6.7/6668.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6668 + +* https://github.com/WordPress/gutenberg/pull/62092 diff --git a/backport-changelog/6.7/6991.md b/backport-changelog/6.7/6991.md new file mode 100644 index 00000000000000..4d5f1f85ec7686 --- /dev/null +++ b/backport-changelog/6.7/6991.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6991 + +* https://github.com/WordPress/gutenberg/pull/61382 diff --git a/backport-changelog/6.7/7020.md b/backport-changelog/6.7/7020.md new file mode 100644 index 00000000000000..8eacb220d340e5 --- /dev/null +++ b/backport-changelog/6.7/7020.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7020 + +* https://github.com/WordPress/gutenberg/pull/63470 diff --git a/backport-changelog/6.7/7125.md b/backport-changelog/6.7/7125.md new file mode 100644 index 00000000000000..341e0415cc61a2 --- /dev/null +++ b/backport-changelog/6.7/7125.md @@ -0,0 +1,4 @@ +https://github.com/WordPress/wordpress-develop/pull/7125 + +* https://github.com/WordPress/gutenberg/pull/61577 +* https://github.com/WordPress/gutenberg/pull/64610 diff --git a/backport-changelog/6.7/7137.md b/backport-changelog/6.7/7137.md new file mode 100644 index 00000000000000..00771b8bc6c21d --- /dev/null +++ b/backport-changelog/6.7/7137.md @@ -0,0 +1,5 @@ +https://github.com/WordPress/wordpress-develop/pull/7137 + +* https://github.com/WordPress/gutenberg/pull/64128 +* https://github.com/WordPress/gutenberg/pull/64192 +* https://github.com/WordPress/gutenberg/pull/64328 diff --git a/backport-changelog/6.7/7139.md b/backport-changelog/6.7/7139.md new file mode 100644 index 00000000000000..b5b0090fbaa563 --- /dev/null +++ b/backport-changelog/6.7/7139.md @@ -0,0 +1,4 @@ +https://github.com/WordPress/wordpress-develop/pull/7139 + +* https://github.com/WordPress/gutenberg/pull/64504 +* https://github.com/WordPress/gutenberg/pull/65280 diff --git a/backport-changelog/6.7/7179.md b/backport-changelog/6.7/7179.md new file mode 100644 index 00000000000000..d777eace2cb05e --- /dev/null +++ b/backport-changelog/6.7/7179.md @@ -0,0 +1,5 @@ +https://github.com/WordPress/wordpress-develop/pull/7179 + +* https://github.com/WordPress/gutenberg/pull/64401 +* https://github.com/WordPress/gutenberg/pull/64459 +* https://github.com/WordPress/gutenberg/pull/64477 diff --git a/backport-changelog/6.7/7200.md b/backport-changelog/6.7/7200.md new file mode 100644 index 00000000000000..520b3d6054cc18 --- /dev/null +++ b/backport-changelog/6.7/7200.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7200 + +* https://github.com/WordPress/gutenberg/pull/64511 diff --git a/backport-changelog/6.7/7247.md b/backport-changelog/6.7/7247.md new file mode 100644 index 00000000000000..d0b1de25872344 --- /dev/null +++ b/backport-changelog/6.7/7247.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7247 + +* https://github.com/WordPress/gutenberg/pull/64790 diff --git a/backport-changelog/6.7/7258.md b/backport-changelog/6.7/7258.md new file mode 100644 index 00000000000000..6714b13b70b8d2 --- /dev/null +++ b/backport-changelog/6.7/7258.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7258 + +* https://github.com/WordPress/gutenberg/pull/64570 \ No newline at end of file diff --git a/backport-changelog/6.7/7270.md b/backport-changelog/6.7/7270.md new file mode 100644 index 00000000000000..358b0d7c9a9674 --- /dev/null +++ b/backport-changelog/6.7/7270.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7270 + +* https://github.com/WordPress/gutenberg/pull/64890 diff --git a/backport-changelog/6.7/7298.md b/backport-changelog/6.7/7298.md new file mode 100644 index 00000000000000..4c01ef5d4f46e6 --- /dev/null +++ b/backport-changelog/6.7/7298.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7298 + +* https://github.com/WordPress/gutenberg/pull/65099 \ No newline at end of file diff --git a/backport-changelog/6.7/7314.md b/backport-changelog/6.7/7314.md new file mode 100644 index 00000000000000..7d75cdff0f9075 --- /dev/null +++ b/backport-changelog/6.7/7314.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7314 + +* https://github.com/WordPress/gutenberg/pull/64167 diff --git a/backport-changelog/6.7/7336.md b/backport-changelog/6.7/7336.md new file mode 100644 index 00000000000000..7cb2e26d7eeb95 --- /dev/null +++ b/backport-changelog/6.7/7336.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7336 + +* https://github.com/WordPress/gutenberg/pull/65071 diff --git a/backport-changelog/6.7/7360.md b/backport-changelog/6.7/7360.md new file mode 100644 index 00000000000000..b2fb8efd624b93 --- /dev/null +++ b/backport-changelog/6.7/7360.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7360 + +* https://github.com/WordPress/gutenberg/pull/65460 diff --git a/backport-changelog/6.7/6910.md b/backport-changelog/6.8/6910.md similarity index 51% rename from backport-changelog/6.7/6910.md rename to backport-changelog/6.8/6910.md index 8e6be0dc8e7a5a..24b59bce87ee60 100644 --- a/backport-changelog/6.7/6910.md +++ b/backport-changelog/6.8/6910.md @@ -2,4 +2,6 @@ https://github.com/WordPress/wordpress-develop/pull/6910 * https://github.com/WordPress/gutenberg/pull/59483 * https://github.com/WordPress/gutenberg/pull/60652 -* https://github.com/WordPress/gutenberg/pull/62777 \ No newline at end of file +* https://github.com/WordPress/gutenberg/pull/62777 +* https://github.com/WordPress/gutenberg/pull/63108 +* https://github.com/WordPress/gutenberg/pull/63464 \ No newline at end of file diff --git a/backport-changelog/readme.md b/backport-changelog/readme.md index 200cb9db404865..8066cc6a6fca24 100644 --- a/backport-changelog/readme.md +++ b/backport-changelog/readme.md @@ -1,16 +1,63 @@ # Core Backport Changelog -Any PR that makes changes to be backported to [core](https://github.com/WordPress/wordpress-develop) should log a core PR here. It's possible to have multiple Gutenberg PRs link to a single core backport PR. The core backport PR can remain open as long as wanted/needed. The entries are sorted by core release (in folders), and each entry should be an md file with the core PR number as the file name, and the link to the Gutenberg PR in the file content. The file content should start with the core PR URL, followed by a Markdown list of Gutenberg PRs (see example). Files are used to avoid rebase conflicts. +If you've changed or added files to the Gutenberg plugin, you'll need to confirm whether the changes are to be backported to [WordPress Core](https://github.com/WordPress/wordpress-develop), and therefore featured in the next release of WordPress. -If you think a file path is wrongly flagged as needing a core backport PR, you can add it to the list of exceptions in `.github/workflows/check-backport-changelog.yml`. +On open Gutenberg PRs, changes to certain files are flagged as requiring backporting to WordPress Core, for example, PHP files in `/lib` and PHP unit tests. -## Example +These changes must have a corresponding Core PR before they can be merged to Gutenberg trunk. + +To create a Core PR, first create a [new Trac ticket](https://core.trac.wordpress.org/newticket) and submit a pull request to the [WordPress Core GitHub repository](https://github.com/WordPress/wordpress-develop). + +The Core PR can remain open as long as is required. + +For more information on how to create a Core PR, see the [WordPress Core Handbook](https://make.wordpress.org/core/handbook/contribute/git/github-pull-requests-for-code-review/). + +## How to add a Core Backport PR to the changelog + +After you create Core PR, you'll need to create a corresponding markdown file, and place it within the appropriate release subdirectory. + +The filename is the Core PR number. + +For example, if your Core PR number is `1234` and is slated to be part of the WordPress 6.9 release, the filename will be `1234.md`, and will be placed in the `/backport-changelog/6.9` directory. + +The content of the markdown file should be the Github URL of the Core PR, followed by a list of Gutenberg PR Github URLs whose changes are backported in the Core PR. + +A single Core PR may contain changes from one or multiple Gutenberg PRs. + +### Examples + +Let's say the next WordPress release is 6.9. You have two Gutenberg PRs ā€” `1111` and `2222` ā€” whose changes are backported in a single Core PR, number `1234`. + +First you would create a file named `1234.md` in the `/6.9` folder. + +If the `/6.9` folder doesn't exist, create it. + +Then you would add the following content to your new file: -Path: `{wp-release-number-x.x}/{core-pr-number}.md`, e.g. `6.6/1234.md`. -File content: ```md -https://github.com/WordPress/wordpress-develop/pull/{core-pr-number} +https://github.com/WordPress/wordpress-develop/pull/1234 -* https://github.com/WordPress/gutenberg/pull/{first-gb-pr-number} -* https://github.com/WordPress/gutenberg/pull/{second-gb-pr-number} +* https://github.com/WordPress/gutenberg/pull/1111 +* https://github.com/WordPress/gutenberg/pull/2222 ``` + +If `1234.md` already exists, you would add the Gutenberg PRs to the list in the existing file. + +## Why use individual files? + +For the backport changelog, Gutenberg uses individual files as opposed to a single changelog file to avoid rebase conflicts. + +## Exceptions + +Some Gutenberg PRs may be flagged as needing a core backport PR when they don't, for example when the PR contains minor comment changes, or the changes already exist in Core. + +For individual PRs, there are two Github labels that can be used to exclude a PR from the backport changelog CI check: + +- `Backport from WordPress Core` - Indicates that the PR is a backport from WordPress Core and doesn't need a Core PR. +- `No Core Sync Required` - Indicates that any changes do not need to be synced to WordPress Core. + +If there are specific file or directory changes that should **never** be flagged as requiring a Core backport PR, you can add it to the list of exceptions in [.github/workflows/check-backport-changelog.yml](https://github.com/WordPress/gutenberg/tree/trunk/.github/workflows/check-backport-changelog.yml). + +## Where to get help + +If you're unsure, you can always ask the Gutenberg Core team for help on the Gutenberg PR `@WordPress/gutenberg-core` or via the #core-editor channel in [WordPress Slack](https://make.wordpress.org/chat/). diff --git a/bin/api-docs/gen-theme-reference.js b/bin/api-docs/gen-theme-reference.js deleted file mode 100644 index 07a8c2fcc697d4..00000000000000 --- a/bin/api-docs/gen-theme-reference.js +++ /dev/null @@ -1,323 +0,0 @@ -/** - * Generates theme.json documentation using theme.json schema. - * Reads from : schemas/json/theme.json - * Publishes to: docs/reference-guides/theme-json-reference/theme-json-living.md - */ - -/** - * External dependencies - */ -const path = require( 'path' ); -const fs = require( 'fs' ); -/** - * Path to root project directory. - * - * @type {string} - */ -const ROOT_DIR = path.resolve( __dirname, '../..' ); - -/** - * Path to theme json schema file. - * - * @type {string} - */ -const THEME_JSON_SCHEMA_FILE = path.resolve( - ROOT_DIR, - path.join( 'schemas', 'json', 'theme.json' ) -); - -/** - * Path to docs file. - * - * @type {string} - */ -const THEME_JSON_REF_DOC = path.resolve( - ROOT_DIR, - 'docs/reference-guides/theme-json-reference/theme-json-living.md' -); - -/** - * Start token for matching string in doc file. - * - * @type {string} - */ -const START_TOKEN = ''; - -/** - * Start token for matching string in doc file. - * - * @type {string} - */ -const END_TOKEN = ''; - -/** - * Regular expression using tokens for matching in doc file. - * Note: `.` does not match new lines, so [^] is used. - * - * @type {RegExp} - */ -const TOKEN_PATTERN = new RegExp( START_TOKEN + '[^]*' + END_TOKEN ); - -const themejson = require( THEME_JSON_SCHEMA_FILE ); - -/** - * Convert object keys to an array. - * Gracefully handles non-object values. - * - * @param {*} maybeObject - * @return {Array} Object keys - */ -const keys = ( maybeObject ) => { - if ( typeof maybeObject !== 'object' ) { - return []; - } - return Object.keys( maybeObject ); -}; - -/** - * Get definition from ref. - * - * @param {string} ref - * @return {Object} definition - * @throws {Error} If the referenced definition is not found in 'themejson.definitions'. - * - * @example - * getDefinition( '#/definitions/typographyProperties/properties/fontFamily' ) - * // returns themejson.definitions.typographyProperties.properties.fontFamily - */ -const resolveDefinitionRef = ( ref ) => { - const refParts = ref.split( '/' ); - const definition = refParts[ refParts.length - 1 ]; - if ( ! themejson.definitions[ definition ] ) { - throw new Error( `Can't resolve '${ ref }'. Definition not found` ); - } - return themejson.definitions[ definition ]; -}; - -/** - * Get properties from an array. - * - * @param {Object} items - * @return {Object} properties - */ -const getPropertiesFromArray = ( items ) => { - // if its a $ref resolve it - if ( items.$ref ) { - return resolveDefinitionRef( items.$ref ).properties; - } - - // otherwise just return the properties - return items.properties; -}; - -/** - * Convert settings properties to markup. - * - * @param {Object} struct - * @return {string} markup - */ -const getSettingsPropertiesMarkup = ( struct ) => { - if ( ! ( 'properties' in struct ) ) { - return ''; - } - const props = struct.properties; - const ks = keys( props ); - if ( ks.length < 1 ) { - return ''; - } - - let markup = '| Property | Type | Default | Props |\n'; - markup += '| --- | --- | --- |--- |\n'; - ks.forEach( ( key ) => { - const def = 'default' in props[ key ] ? props[ key ].default : ''; - let type = props[ key ].type || ''; - let ps = - props[ key ].type === 'array' - ? keys( getPropertiesFromArray( props[ key ].items ) ) - .sort() - .join( ', ' ) - : ''; - - /* - * Handle`oneOf` type definitions - extract the type and properties. - * See: https://json-schema.org/understanding-json-schema/reference/combining#oneOf - */ - if ( props[ key ].oneOf && Array.isArray( props[ key ].oneOf ) ) { - if ( ! type ) { - type = props[ key ].oneOf - .map( ( item ) => item.type ) - .join( ', ' ); - } - - if ( ! ps ) { - ps = props[ key ].oneOf - .map( ( item ) => - item?.type === 'object' && item?.properties - ? '_{' + - keys( getPropertiesFromArray( item ) ) - .sort() - .join( ', ' ) + - '}_' - : '' - ) - .join( ' ' ); - } - } - - markup += `| ${ key } | ${ type } | ${ def } | ${ ps } |\n`; - } ); - - return markup; -}; - -/** - * Convert style properties to markup. - * - * @param {Object} struct - * @return {string} markup - */ -const getStylePropertiesMarkup = ( struct ) => { - if ( ! ( 'properties' in struct ) ) { - return ''; - } - const props = struct.properties; - const ks = keys( props ); - if ( ks.length < 1 ) { - return ''; - } - - let markup = '| Property | Type | Props |\n'; - markup += '| --- | --- |--- |\n'; - ks.forEach( ( key ) => { - const ps = - props[ key ].type === 'object' - ? keys( props[ key ].properties ).sort().join( ', ' ) - : ''; - const type = formatType( props[ key ] ); - markup += `| ${ key } | ${ type } | ${ ps } |\n`; - } ); - - return markup; -}; - -/** - * Parses a section for description and properties and - * returns a marked up version. - * - * @param {string} title - * @param {Object} data - * @param {string} type settings|style - * @return {string} markup - */ -const getSectionMarkup = ( title, data, type ) => { - const markupFn = - type === 'settings' - ? getSettingsPropertiesMarkup - : getStylePropertiesMarkup; - - return ` -### ${ title } - -${ data.description } - -${ markupFn( data ) } ---- -`; -}; - -let autogen = ''; - -/** - * Format list of types. - * - * @param {Object} prop - * @return {string} type - */ -const formatType = ( prop ) => { - let type = prop.type || ''; - - if ( prop.hasOwnProperty( 'anyOf' ) || prop.hasOwnProperty( 'oneOf' ) ) { - const propTypes = prop.anyOf || prop.oneOf; - const types = []; - - propTypes.forEach( ( item ) => { - if ( item.type ) { - types.push( item.type ); - } - // refComplete is always an object - if ( item.$ref && item.$ref === '#/definitions/refComplete' ) { - types.push( 'object' ); - } - } ); - - type = [ ...new Set( types ) ].join( ', ' ); - } - - return type; -}; - -// Settings -const settings = Object.entries( themejson.definitions ) - .filter( ( [ settingsKey ] ) => - /^settingsProperties(?!Complete)\w+$/.test( settingsKey ) - ) - .reduce( - ( settingsObj, [ , { properties } ] ) => - Object.assign( settingsObj, properties ), - {} - ); -const settingSections = keys( settings ); -autogen += '## Settings' + '\n\n'; -settingSections.forEach( ( section ) => { - autogen += getSectionMarkup( section, settings[ section ], 'settings' ); -} ); - -// Styles -const styles = themejson.definitions.stylesProperties.properties; -const styleSections = keys( styles ); -autogen += '## Styles' + '\n\n'; -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', - flag: 'r', -} ); - -// Replace auto generated part with new generated docs. -autogen = START_TOKEN + '\n' + autogen + '\n' + END_TOKEN; -docsContent = docsContent.replace( TOKEN_PATTERN, autogen ); - -// Write back out. -fs.writeFileSync( THEME_JSON_REF_DOC, docsContent, { encoding: 'utf8' } ); diff --git a/bin/api-docs/gen-theme-reference.mjs b/bin/api-docs/gen-theme-reference.mjs new file mode 100644 index 00000000000000..6dc7791e288b9e --- /dev/null +++ b/bin/api-docs/gen-theme-reference.mjs @@ -0,0 +1,296 @@ +/** + * Generates theme.json documentation using theme.json schema. + * Reads from : schemas/json/theme.json + * Publishes to: docs/reference-guides/theme-json-reference/theme-json-living.md + */ + +/** + * External dependencies + */ +import fs from 'node:fs/promises'; +import $RefParser from '@apidevtools/json-schema-ref-parser'; + +/** + * @typedef {import('@apidevtools/json-schema-ref-parser').JSONSchema} JSONSchema + */ + +/** + * Path to theme json schema file. + * + * @type {URL} + */ +const THEME_JSON_SCHEMA_URL = new URL( + '../../schemas/json/theme.json', + import.meta.url +); + +/** + * Path to docs file. + * + * @type {URL} + */ +const REFERENCE_DOC_URL = new URL( + '../../docs/reference-guides/theme-json-reference/theme-json-living.md', + import.meta.url +); + +/** + * Start token for matching string in doc file. + * + * @type {string} + */ +const START_TOKEN = ''; + +/** + * Start token for matching string in doc file. + * + * @type {string} + */ +const END_TOKEN = ''; + +/** + * @typedef {(schema: JSONSchema) => boolean} PredicateFunction + */ + +/** + * @typedef {(schema: JSONSchema) => string} SerializerFunction + */ + +/** + * Create a serializer function for a type. Supports merging one level of anyOf and oneOf subschemas. + * + * @see {@link https://json-schema.org/understanding-json-schema/reference/combining.html} + * + * @param {PredicateFunction} predicate Type predicate function to match a type. + * @param {SerializerFunction} serializer Serializer function to format a type. + * @return {SerializerFunction} Serializer function for the give type. + */ +function createSerializer( predicate, serializer ) { + return ( schema ) => { + const schemas = predicate( schema ) + ? [ schema ] + : schema.anyOf || schema.oneOf || []; + const formatted = schemas.filter( predicate ).map( serializer ); + return [ ...new Set( formatted ) ].join( ', ' ); + }; +} + +/** + * Serialize primitive types. + * + * @type {SerializerFunction} + */ +const serializePrimitiveTypes = createSerializer( + ( schema ) => + schema.type && ! [ 'object', 'array' ].includes( schema.type ), + ( schema ) => `\`${ schema.type }\`` +); + +/** + * Serialize object types. + * + * @type {SerializerFunction} + */ +const serializeObjectTypes = createSerializer( + ( schema ) => schema.properties, + ( schema ) => `\`{ ${ Object.keys( schema.properties ).join( ', ' ) } }\`` +); + +/** + * Serialize object array types. + * + * @type {SerializerFunction} + */ +const serializeObjectArrayTypes = createSerializer( + ( schema ) => schema.items && schema.items.properties, + ( schema ) => + `\`[ { ${ Object.keys( schema.items.properties ).join( ', ' ) } } ]\`` +); + +/** + * Serialize primitive array types. + * + * @type {SerializerFunction} + */ +const serializePrimitiveArrayTypes = createSerializer( + ( schema ) => + schema.items && + schema.items.type && + ! [ 'object', 'array' ].includes( schema.items.type ), + ( schema ) => `\`[ ${ schema.items.type } ]\`` +); + +/** + * Generate types from schema. + * + * @param {JSONSchema} schema JSON schema + * @return {string} serialized types + */ +function generateTypes( schema ) { + return [ + serializePrimitiveTypes( schema ), + serializeObjectTypes( schema ), + serializePrimitiveArrayTypes( schema ), + serializeObjectArrayTypes( schema ), + ] + .filter( Boolean ) + .join( ', ' ); +} + +/** + * Generate documentation from theme.json schema. + * + * @param {JSONSchema} themeJson theme.json JSON schema + * @return {string} generated documentation + */ +function generateDocs( themeJson ) { + /** Markdown content. */ + let md = ''; + + /* --------------- * + * Settings * + * --------------- */ + md += '## settings\n\n'; + md += `${ themeJson.properties.settings.description }\n\n`; + const settings = [ + // Top-level only properties. + ...Object.entries( themeJson.properties.settings.allOf[ 1 ].properties ) + .filter( ( [ property ] ) => property !== 'blocks' ) + .map( ( [ property, subschema ] ) => [ + property, + { + ...subschema, + description: `${ subschema.description }\n\n**Note:** Top-level only property. Not available in blocks.`, + }, + ] ), + // Top-level and blocks properties. + ...themeJson.properties.settings.allOf[ 0 ].allOf.flatMap( + ( subschema ) => Object.entries( subschema.properties ) + ), + ]; + for ( const [ section, schema ] of settings ) { + md += `### ${ section }\n\n`; + md += `${ schema.description }\n\n`; + if ( schema.properties ) { + md += '| Property | Description | Type | Default |\n'; + md += '| -------- | ----------- | ---- | ------- |\n'; + const properties = Object.entries( schema.properties ); + for ( const [ property, subschema ] of properties ) { + const description = + subschema.description?.split( '\n', 1 )[ 0 ] ?? ''; + const types = generateTypes( subschema ); + const defaultValue = + 'default' in subschema + ? `\`${ JSON.stringify( subschema.default ) }\`` + : ''; + md += `| ${ property } | ${ description } | ${ types } | ${ defaultValue } |\n`; + } + md += '\n'; + } + md += `---\n\n`; + } + + /* --------------- * + * Styles * + * --------------- */ + md += '## styles\n\n'; + md += `${ themeJson.properties.styles.description }\n\n`; + const styles = Object.entries( + themeJson.properties.styles.allOf[ 0 ].properties + ); + for ( const [ section, schema ] of styles ) { + md += `### ${ section }\n\n`; + md += `${ schema.description }\n\n`; + if ( schema.properties ) { + md += '| Property | Description | Type |\n'; + md += '| -------- | ----------- | ---- |\n'; + const properties = Object.entries( schema.properties ); + for ( const [ property, subschema ] of properties ) { + // Assuming that the first line of a description is a summary. + const description = + subschema.description?.split( '\n', 1 )[ 0 ] ?? ''; + const types = generateTypes( subschema ); + md += `| ${ property } | ${ description } | ${ types } |\n`; + } + md += '\n'; + } + md += `---\n\n`; + } + + /* --------------- * + * customTemplates * + * --------------- */ + md += '## customTemplates\n\n'; + md += `${ themeJson.properties.customTemplates.description }\n\n`; + md += '| Property | Description | Type |\n'; + md += '| -------- | ----------- | ---- |\n'; + const customTemplatesProperties = Object.entries( + themeJson.properties.customTemplates.items.properties + ); + for ( const [ property, subschema ] of customTemplatesProperties ) { + const { description } = subschema; + const types = generateTypes( subschema ); + md += `| ${ property } | ${ description } | ${ types } |\n`; + } + md += '\n'; + + /* --------------- * + * templateParts * + * --------------- */ + md += '## templateParts\n\n'; + md += `${ themeJson.properties.templateParts.description }\n\n`; + md += '| Property | Description | Type |\n'; + md += '| -------- | ----------- | ---- |\n'; + const templatePartsProperties = Object.entries( + themeJson.properties.templateParts.items.properties + ); + for ( const [ property, subschema ] of templatePartsProperties ) { + const { description } = subschema; + const types = generateTypes( subschema ); + md += `| ${ property } | ${ description } | ${ types } |\n`; + } + md += '\n'; + + /* --------------- * + * patterns * + * --------------- */ + md += '## patterns\n\n'; + md += themeJson.properties.patterns.description + '\n\n'; + md += `Type: ${ generateTypes( themeJson.properties.patterns ) }.\n`; + + return md; +} + +/** + * Main function. + */ +async function main() { + const themeJson = await $RefParser.dereference( + THEME_JSON_SCHEMA_URL.pathname, + { + parse: { binary: false, text: false, yaml: false }, + resolve: { external: false }, + } + ); + + const themeJsonReference = await fs.readFile( REFERENCE_DOC_URL, { + encoding: 'utf8', + flag: 'r', + } ); + + const generatedDocs = generateDocs( themeJson ); + const updatedThemeJsonReference = themeJsonReference.replace( + // `.` does not match new lines, but `[^]` will. + new RegExp( `${ START_TOKEN }[^]*${ END_TOKEN }` ), + `${ START_TOKEN }\n${ generatedDocs }\n${ END_TOKEN }` + ); + + await fs.writeFile( REFERENCE_DOC_URL, updatedThemeJsonReference, { + encoding: 'utf8', + } ); +} + +main().catch( ( error ) => { + console.error( error ); + process.exit( 1 ); +} ); diff --git a/bin/build-plugin-zip.sh b/bin/build-plugin-zip.sh index 4ba931c4a4aeb6..c823ca6a8017f1 100755 --- a/bin/build-plugin-zip.sh +++ b/bin/build-plugin-zip.sh @@ -78,26 +78,16 @@ npm run build php bin/generate-gutenberg-php.php > gutenberg.tmp.php mv gutenberg.tmp.php gutenberg.php -build_files=$( - ls build/*/*.{js,js.map,css,asset.php} \ - build/block-library/blocks/*.php \ - build/block-library/blocks/*/block.json \ - build/block-library/blocks/*/*.{js,js.map,css,asset.php} \ - build/edit-widgets/blocks/*/block.json \ - build/widgets/blocks/*.php \ - build/widgets/blocks/*/block.json \ - build/style-engine/*.php \ -) - - # Generate the plugin zip file. status "Creating archive... šŸŽ" -zip -r gutenberg.zip \ +zip --recurse-paths --no-dir-entries \ + gutenberg.zip \ gutenberg.php \ lib \ packages/block-serialization-default-parser/*.php \ post-content.php \ - $build_files \ + build \ + build-module \ readme.txt \ changelog.txt \ README.md diff --git a/bin/packages/get-babel-config.js b/bin/packages/get-babel-config.js index 91fe4b07d325e6..9427549cafaba6 100644 --- a/bin/packages/get-babel-config.js +++ b/bin/packages/get-babel-config.js @@ -11,6 +11,8 @@ module.exports = ( environment = '', file ) => { name: `WP_BUILD_${ environment.toUpperCase() }`, }, }; + // Add `/* wp:polyfill */` magic comment where needed. + callerOpts.caller.addPolyfillComments = true; switch ( environment ) { case 'main': // To be merged as a presetEnv option. diff --git a/changelog.txt b/changelog.txt index e0181a38091ca9..b04fa0e9bbf8e2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,2394 @@ == Changelog == += 19.3.0-rc.2 = + +## Changelog + +### Features + +#### Zoom Out +- Remove experimental flag. ([65404](https://github.com/WordPress/gutenberg/pull/65404)) + +### Enhancements + +- Create Block: Update the minimum required PHP version to 7.2. ([65166](https://github.com/WordPress/gutenberg/pull/65166)) +- DataViews: remove unused `.dataviews-view-table__cell-content-wrapper:Empty` style rule. ([65084](https://github.com/WordPress/gutenberg/pull/65084)) +- Media Utils: Add TypeScript support and export more utils. ([64784](https://github.com/WordPress/gutenberg/pull/64784)) +- Media placeholders: Add "drag" to the text. ([65149](https://github.com/WordPress/gutenberg/pull/65149)) +- Restore: Move to trash button in Document settings. ([65087](https://github.com/WordPress/gutenberg/pull/65087)) +- Inspector Controls: Use custom block name in inspector controls when available. ([65398](https://github.com/WordPress/gutenberg/pull/65398)) +- Icons: Adds bell and bell-unread icons. ([65324](https://github.com/WordPress/gutenberg/pull/65324)) +- Editor topbar: Reorder the actions on the right. ([65163](https://github.com/WordPress/gutenberg/pull/65163)) +- Patterns: Add opt out preference to the 'Choose a Pattern' modal when adding a page. ([65026](https://github.com/WordPress/gutenberg/pull/65026)) +- Locked Templates: Blocks with contentOnly locking should not be transformable. ([64917](https://github.com/WordPress/gutenberg/pull/64917)) +- Block Locking: Add border to Replace item in content only image toolbar. ([64849](https://github.com/WordPress/gutenberg/pull/64849)) + +#### Components +- Styling: Apply elevation scale in components package. ([65159](https://github.com/WordPress/gutenberg/pull/65159)) +- Tabs: Improve Tabs indicator animation and related utils. ([64926](https://github.com/WordPress/gutenberg/pull/64926)) +- Modal + - Add exit animation. ([65203](https://github.com/WordPress/gutenberg/pull/65203)) + - Decrease close button size. ([65131](https://github.com/WordPress/gutenberg/pull/65131)) +- Navigator Screen: Warn if path doesn't follow a URL-like scheme. ([65231](https://github.com/WordPress/gutenberg/pull/65231)) +- Card: Update Card radius. ([65053](https://github.com/WordPress/gutenberg/pull/65053)) +- Combobox Control: Add placeholder attribute. ([65254](https://github.com/WordPress/gutenberg/pull/65254)) + +#### Block Library +- Allow dropping multiple images to the image block. ([65030](https://github.com/WordPress/gutenberg/pull/65030)) +- Categories List block: Add dropdown for taxonomies. ([65272](https://github.com/WordPress/gutenberg/pull/65272)) +- Image: Adds the block controls for uploading image. ([64320](https://github.com/WordPress/gutenberg/pull/64320)) +- Remove colons from control labels. ([65205](https://github.com/WordPress/gutenberg/pull/65205)) +- Terms List block: Add Categories-specific variation. ([65434](https://github.com/WordPress/gutenberg/pull/65434)) + +#### Zoom Out +- Add Zoom Out toggle to editor header when experiment enabled. ([65183](https://github.com/WordPress/gutenberg/pull/65183)) +- Add prompt for drag and drop in Patterns tab in Zoom Out mode. ([65115](https://github.com/WordPress/gutenberg/pull/65115)) +- Close inserter on exiting Zoom Out to edit. ([65194](https://github.com/WordPress/gutenberg/pull/65194)) +- Show top level sections in List View. ([65202](https://github.com/WordPress/gutenberg/pull/65202)) +- Try vertical displacement when dragging a pattern between existing patterns/sections. ([63896](https://github.com/WordPress/gutenberg/pull/63896)) + +#### Block Editor +- Link Editing: Automatically add tel to phone number when linking URL. ([64865](https://github.com/WordPress/gutenberg/pull/64865)) +thub.com/WordPress/gutenberg/pull/65300)) +- Drag and Drop: When dragging a mix of video, audio, and image blocks, create individual blocks as appropriate. ([65144](https://github.com/WordPress/gutenberg/pull/65144)) +- URLInput: Replace input with InputControl. ([65158](https://github.com/WordPress/gutenberg/pull/65158)) +- Normalize block inspector controls spacing. ([64526](https://github.com/WordPress/gutenberg/pull/64526)) + +#### Post Editor +- Add new Media section to preferences modal. ([64846](https://github.com/WordPress/gutenberg/pull/64846)) +- DocumentBar: Replace icon with post type label. ([65170](https://github.com/WordPress/gutenberg/pull/65170)) +- Page editor: Double-click to edit template part. ([65024](https://github.com/WordPress/gutenberg/pull/65024)) +- Post publish upload media dialog: Handle more block types. ([65122](https://github.com/WordPress/gutenberg/pull/65122)) + +#### Block bindings +- Populate block context with inherited post type from template slug. ([65062](https://github.com/WordPress/gutenberg/pull/65062)) +- Try gap 0 on attribute items. ([65277](https://github.com/WordPress/gutenberg/pull/65277)) +- Use post meta label from `register_meta` in block bindings workflows. ([65099](https://github.com/WordPress/gutenberg/pull/65099)) + +#### Global Styles +- Refactor site background controls and move site global styles into Background group. ([65304](https://github.com/WordPress/gutenberg/pull/65304)) +- Spacing control: Replace sides dropdwon with link button. ([65193](https://github.com/WordPress/gutenberg/pull/65193)) + +#### Data Views +- DataViews Sidebar: Display item count on DataViews sidebar. ([65223](https://github.com/WordPress/gutenberg/pull/65223)) +- DataViews: Improve UX of bundled views for Pages. ([65295](https://github.com/WordPress/gutenberg/pull/65295)) + +#### Interactivity API +- Refactor context proxies. ([64713](https://github.com/WordPress/gutenberg/pull/64713)) +- Update: Rephrase "Force page reload" and move to Advanced. ([65081](https://github.com/WordPress/gutenberg/pull/65081)) + +#### REST API +- Global Styles: Allow read access to users with `edit_posts` capabilities. ([65071](https://github.com/WordPress/gutenberg/pull/65071)) +- Query loop / Post template: Enable post format filter. ([64167](https://github.com/WordPress/gutenberg/pull/64167)) + +### New APIs +- Add @wordpress/fields package. + - Introduce the package. ([65230](https://github.com/WordPress/gutenberg/pull/65230)) + - Make the package private. ([65269](https://github.com/WordPress/gutenberg/pull/65269)) +- Interactivity API: Add `getServerState()` and `getServerContext()`. ([65151](https://github.com/WordPress/gutenberg/pull/65151)) + +### Bug Fixes + +- Align popover alt variant styling with block toolbar. ([65263](https://github.com/WordPress/gutenberg/pull/65263)) +- Compose: Correctly call timer cleanup in 'useFocusOnMount'. ([65184](https://github.com/WordPress/gutenberg/pull/65184)) +- Fix some docblock types related to the Template Registration API. ([65187](https://github.com/WordPress/gutenberg/pull/65187)) +- Fix the issue where block spacing control not shown. ([65371](https://github.com/WordPress/gutenberg/pull/65371)) +- Fix unintentional block toolbar shadow. ([65182](https://github.com/WordPress/gutenberg/pull/65182)) +- Fix: Moving a page to the trash on the site editor does not goes back to the pages list. ([65119](https://github.com/WordPress/gutenberg/pull/65119)) +- Fix: Moving the last page item to the the trash causes a crash. ([65236](https://github.com/WordPress/gutenberg/pull/65236)) +- Preferences: Fix back button on mobile. ([65141](https://github.com/WordPress/gutenberg/pull/65141)) +- Post Summary Panel: Restore `height:Auto` for toggle buttons. ([65362](https://github.com/WordPress/gutenberg/pull/65362)) +- Fix Tabs styling in Font Library modal. ([65330](https://github.com/WordPress/gutenberg/pull/65330)) +- E2E: Change deprecated social icons for standard in end-to-end. ([65312](https://github.com/WordPress/gutenberg/pull/65312)) +- Typography: Make title blocks apply typographic styles consistently. ([65307](https://github.com/WordPress/gutenberg/pull/65307)) +- Target Hints REST API: Add missing param sanitization. ([65280](https://github.com/WordPress/gutenberg/pull/65280)) +- Interactivity API: Update iterable signals when `deepMerge()` adds new properties. ([65135](https://github.com/WordPress/gutenberg/pull/65135)) +- Navigation Menus: Typography styling support to the navigation submenu block. ([65060](https://github.com/WordPress/gutenberg/pull/65060)) +- Grid: In RTL languages, the resize handles point in the opposite direction. ([64995](https://github.com/WordPress/gutenberg/pull/64995)) +- Block Locking: Fix Content Only Toolbar icon focus style. ([64940](https://github.com/WordPress/gutenberg/pull/64940)) +- Image: Fix resizing to max width in classic themes. ([64819](https://github.com/WordPress/gutenberg/pull/64819)) +- Meta Boxes: Try split content view. ([64351](https://github.com/WordPress/gutenberg/pull/64351)) +- Distraction Free: Fix blurry edge along editor header. ([64277](https://github.com/WordPress/gutenberg/pull/64277)) + +#### Block Library +- Comments Pagination: Fix warning returned by comments pagination blocks. ([65435](https://github.com/WordPress/gutenberg/pull/65435)) +- Cover: Explicitly set isUserOverlayColor to false when media is updated. ([65105](https://github.com/WordPress/gutenberg/pull/65105)) +- Disallow setting grid block rows/columns to zero. ([65217](https://github.com/WordPress/gutenberg/pull/65217)) +- Fix image block crash. ([65222](https://github.com/WordPress/gutenberg/pull/65222)) +- Fix: Buttons block: Block spacing value does not apply to both vertical and horizontal alignment. ([64971](https://github.com/WordPress/gutenberg/pull/64971)) +- Fix: Embed blocks: Figcaption inserted via toolbar not nested within figure element - #64960. ([64970](https://github.com/WordPress/gutenberg/pull/64970)) +- Image cropping: Skip making an API request if there are no changes to apply. ([65384](https://github.com/WordPress/gutenberg/pull/65384)) +- Comments Pagination: Pass the comments query `paged` arg to functions `get_next_comments_link` and `get_previous_comments_link`. ([63698](https://github.com/WordPress/gutenberg/pull/63698)) +- Query Loop + - Default to querying posts when on singular content. ([65067](https://github.com/WordPress/gutenberg/pull/65067)) + - Remove is_singular() check and fix test. ([65483](https://github.com/WordPress/gutenberg/pull/65483)) + +#### Block Editor +- Inserter: Fix loading indicator for reusable blocks. ([64839](https://github.com/WordPress/gutenberg/pull/64839)) +- Normalize spacing in Layout hook controls. ([65132](https://github.com/WordPress/gutenberg/pull/65132)) +- Pattern Inserter: Fix pattern list overflow. ([65192](https://github.com/WordPress/gutenberg/pull/65192)) +- Remove reset styles RTL from the iframe. ([65150](https://github.com/WordPress/gutenberg/pull/65150)) +- Revert "Block Insertion: Clear the insertion point when selecting a dā€¦. ([65208](https://github.com/WordPress/gutenberg/pull/65208)) + +#### Components +- BoxControl: Unify input filed width whether linked or not. ([65348](https://github.com/WordPress/gutenberg/pull/65348)) +- ComboboxControl: Add more unit tests. ([65255](https://github.com/WordPress/gutenberg/pull/65255)) +- Fix: Button Replace remaining 40px default size violations [Edit widgets]. ([65367](https://github.com/WordPress/gutenberg/pull/65367)) +- Tabs: Fix vertical indicator. ([65385](https://github.com/WordPress/gutenberg/pull/65385)) + +#### Block bindings +- Fix empty strings placeholders in post meta bindings. ([65089](https://github.com/WordPress/gutenberg/pull/65089)) +- Remove key fallback in bindings get values and rely on source label. ([65517](https://github.com/WordPress/gutenberg/pull/65517)) + +#### Zoom Out +- Force device type to Desktop whenever zoom out is invoked. ([64476](https://github.com/WordPress/gutenberg/pull/64476)) +- Hide toolbar icon on smaller viewports. ([65437](https://github.com/WordPress/gutenberg/pull/65437)) +- Remove zoom out toggle when editor is not iframed. ([65452](https://github.com/WordPress/gutenberg/pull/65452)) + +### Accessibility + +- A11y: Add script-module. ([65101](https://github.com/WordPress/gutenberg/pull/65101)) +- Interactivity API: Use a11y Script Module in Gutenberg. ([65123](https://github.com/WordPress/gutenberg/pull/65123)) +- Script Modules API: Print script module live regions HTML in page HTML. ([65380](https://github.com/WordPress/gutenberg/pull/65380)) +- DatePicker: Better hover/focus styles. ([65117](https://github.com/WordPress/gutenberg/pull/65117)) +- Form Input: Don't use `flex-direction: Row-reverse` for checkbox field. ([64232](https://github.com/WordPress/gutenberg/pull/64232)) +- Navigation Menus: Remove Warning and add notice for Navigation. ([63921](https://github.com/WordPress/gutenberg/pull/63921)) +- Global Styles: Fix the shadows Range control accessibility and usability. ([63908](https://github.com/WordPress/gutenberg/pull/63908)) +- Block Editor: Fix accessibility of the hooked blocks toggles. ([63133](https://github.com/WordPress/gutenberg/pull/63133)) + + +#### Post Editor +- Support keyboard resizing of meta boxes pane. ([65325](https://github.com/WordPress/gutenberg/pull/65325)) +- Swap position of the Pre-publish checks buttons. ([65317](https://github.com/WordPress/gutenberg/pull/65317)) + + +### Performance + +- Core Data: Batch remaining actions in resolvers. ([65176](https://github.com/WordPress/gutenberg/pull/65176)) +- Block Editor: Use static access for selector in 'useZoomOutModeExit'. ([65337](https://github.com/WordPress/gutenberg/pull/65337)) +- Editor: Optimize global styles permission check. ([65177](https://github.com/WordPress/gutenberg/pull/65177)) + + +### Experiments + +- Block bindings REST API: Bring bindings UI in Site Editor. ([64072](https://github.com/WordPress/gutenberg/pull/64072)) + + +### Documentation + +- Add JSDoc block for getSectionRootClientId in block editor package. ([65219](https://github.com/WordPress/gutenberg/pull/65219)) +- ButtonGroup: Fix story to show what the component does. ([65336](https://github.com/WordPress/gutenberg/pull/65336)) +- DataViews storybook + - Better styles for combined fields story. ([65078](https://github.com/WordPress/gutenberg/pull/65078)) + - Enable all layouts for combined fields storybook. ([65082](https://github.com/WordPress/gutenberg/pull/65082)) +- Docs: Fix minor typos in Build your first block tutorial. ([64961](https://github.com/WordPress/gutenberg/pull/64961)) +- Docs: Update the content of the API version 3 section in the Block API Reference. ([65375](https://github.com/WordPress/gutenberg/pull/65375)) +- Fix typo in Slot Fills documentation. ([65275](https://github.com/WordPress/gutenberg/pull/65275)) + + +### Code Quality + +- Components: Transition to the new 40px default size. + - Button: + - Add __next40pxDefaultSize for files in editor 3. ([65139](https://github.com/WordPress/gutenberg/pull/65139)) + - Add __next40pxDefaultSize for files in editor 4. ([65140](https://github.com/WordPress/gutenberg/pull/65140)) + - Add props for buttons in editor 1. ([65068](https://github.com/WordPress/gutenberg/pull/65068)) + - Add props for buttons in editor 2. ([65083](https://github.com/WordPress/gutenberg/pull/65083)) + - Fix: Replace remaining 40px default size violations [Block Editor 4]. ([65257](https://github.com/WordPress/gutenberg/pull/65257)) + - Fix: Replace remaining 40px default size violation [Block library 3]. ([65110](https://github.com/WordPress/gutenberg/pull/65110)) + - Fix: Replace remaining 40px default size violation [Block library 4]. ([65143](https://github.com/WordPress/gutenberg/pull/65143)) + - Fix: Replace remaining 40px default size violation [Block library]. ([65075](https://github.com/WordPress/gutenberg/pull/65075)) + - Fix: Replace remaining 40px default size violation [Edit Site 2]. ([65258](https://github.com/WordPress/gutenberg/pull/65258)) + - Fix: Replace remaining 40px default size violations [Block library 1]. ([65033](https://github.com/WordPress/gutenberg/pull/65033)) + - Fix: Replace remaining 40px default size violations [Block Editor 1]. ([65034](https://github.com/WordPress/gutenberg/pull/65034)) + - BoxControl + - Add lint rule for 40px size prop usage. ([65341](https://github.com/WordPress/gutenberg/pull/65341)) + - DimensionsPanel: Apply 40px default size to UI when no spacing preset is available. ([65300](https://github.com/WordPress/gutenberg/pull/65300)) +- Add `useEvent` and revamped `useResizeObserver` to `@wordpress/compose`. ([64943](https://github.com/WordPress/gutenberg/pull/64943)) +- DataViews: Use Dropdown for views configuration dialog. ([65314](https://github.com/WordPress/gutenberg/pull/65314)) +- Platform docs: Upgrade dependencies. ([65445](https://github.com/WordPress/gutenberg/pull/65445)) +- Rename edit-post__fade-in-animation and unify keyframe definitions. ([65377](https://github.com/WordPress/gutenberg/pull/65377)) +- Update minimum required version in PHP. ([65301](https://github.com/WordPress/gutenberg/pull/65301)) +- Editor: Use hooks instead of HoC in `BlockManager`. ([65349](https://github.com/WordPress/gutenberg/pull/65349)) +- Data Views Fields: Migrate store and actions from editor package to fields package. ([65261](https://github.com/WordPress/gutenberg/pull/65261)) +- Plugin: Remove 'function_exists' checks for methods with 'gutenberg' prefix. ([65260](https://github.com/WordPress/gutenberg/pull/65260)) +- Global Styles: Update REST controller override method and backport changes from Core. ([65259](https://github.com/WordPress/gutenberg/pull/65259)) +- Patterns: Remove unused method returned from 'mapSelect'. ([65073](https://github.com/WordPress/gutenberg/pull/65073)) +- Embed: Convert EmbedPreview component to functional component. ([51325](https://github.com/WordPress/gutenberg/pull/51325)) + +#### Components +- BoxControl: Fix critical error when null value is passed. ([65287](https://github.com/WordPress/gutenberg/pull/65287)) +- Composite: + - Deprecate legacy, unstable version. ([63572](https://github.com/WordPress/gutenberg/pull/63572)) + - Remove store prop and useCompositeStore hook. ([64723](https://github.com/WordPress/gutenberg/pull/64723)) + - Stabilize APIs. ([63569](https://github.com/WordPress/gutenberg/pull/63569)) +- `@wordpress/components`: Add local copy of `use-lilius`. ([65097](https://github.com/WordPress/gutenberg/pull/65097)) + +#### Block bindings +- Always prioritize using context in post meta source logic. ([65449](https://github.com/WordPress/gutenberg/pull/65449)) +- Improve getRegisteredPostMeta resolver. ([65450](https://github.com/WordPress/gutenberg/pull/65450)) +- Remove extra filtering of empty sources. ([65447](https://github.com/WordPress/gutenberg/pull/65447)) + +#### Block Editor +- Remove the 'PrivateInserter' component. ([65111](https://github.com/WordPress/gutenberg/pull/65111)) +- Use the tooltip from a button in 'ButtonBlockAppender'. ([65113](https://github.com/WordPress/gutenberg/pull/65113)) +- Remove unused css selectors. ([65276](https://github.com/WordPress/gutenberg/pull/65276)) + +### Tools + +- Scripts: Update stylelint dependency and the default configuration. ([64828](https://github.com/WordPress/gutenberg/pull/64828)) +- Styleling config: Fix stylelint configuration missing files for npm. ([65313](https://github.com/WordPress/gutenberg/pull/65313)) + +#### Build Tooling +- Build Plugin: Simplify and improve zip contents. ([65232](https://github.com/WordPress/gutenberg/pull/65232)) +- Build zip artifact on release and wp production branches. ([65471](https://github.com/WordPress/gutenberg/pull/65471)) +- Build: Include Core blocks' `render` and `variations` files. ([63311](https://github.com/WordPress/gutenberg/pull/63311)) +- Script Modules + - Prepare build for more script modules. ([65064](https://github.com/WordPress/gutenberg/pull/65064)) + - Remove babel from script-modules build. ([65279](https://github.com/WordPress/gutenberg/pull/65279)) + - Remove es-module shims and importmap-polyfill. ([65210](https://github.com/WordPress/gutenberg/pull/65210)) +- Correctly generate PHP files for server-side rendering of blocks on Windows OS. ([65248](https://github.com/WordPress/gutenberg/pull/65248)) +- Packages: Only add polyfills where needed. ([65292](https://github.com/WordPress/gutenberg/pull/65292)) +- Switch from UglifyJS to Terser to build the polyfill script. ([65278](https://github.com/WordPress/gutenberg/pull/65278)) + +#### Testing +- Unit tests: Mock matchMedia to enforce prefers-reduce-motion. ([65438](https://github.com/WordPress/gutenberg/pull/65438)) +- Upgrade Playwright to v1.47. ([65156](https://github.com/WordPress/gutenberg/pull/65156)) + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @AKSHAT2802: Add __next40pxDefaultSize for files in editor 4. ([65140](https://github.com/WordPress/gutenberg/pull/65140)) +- @devansh016: Automatically add tel to phone number when linking URL. ([64865](https://github.com/WordPress/gutenberg/pull/64865)) +- @dhruvang21: Fix: Button Replace remaining 40px default size violations [Edit widgets]. ([65367](https://github.com/WordPress/gutenberg/pull/65367)) +- @farid-hadi: Docs: Fix minor typos in Build your first block tutorial. ([64961](https://github.com/WordPress/gutenberg/pull/64961)) +- @greenworld: Fix typo in Slot Fills documentation. ([65275](https://github.com/WordPress/gutenberg/pull/65275)) +- @louwie17: Convert EmbedPreview component to functional component. ([51325](https://github.com/WordPress/gutenberg/pull/51325)) +- @rahulharpal1603: URLInput: Replace input with InputControl. ([65158](https://github.com/WordPress/gutenberg/pull/65158)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @AKSHAT2802 @Aljullu @andrewserong @carolinan @cbravobernal @ciampo @colorful-tones @creativecoder @DaniGuardiola @DAreRodz @devansh016 @dhruvang21 @ellatrix @farid-hadi @getdave @gigitux @greenworld @gziolo @hbhalodia @jameskoster @jasmussen @javierarce @jeryj @jorgefilipecosta @jsnajdr @kevin940726 @louwie17 @madhusudhand @MaggieCabrera @Mamaduka @mikeybinns @mirka @ntsekouras @oandregal @ockham @peterwilsoncc @rahulharpal1603 @ramonjd @richtabor @rohitmathur-7 @SantosGuillamot @scruffian @sgomes @sirreal @stokesman @swissspidy @t-hamano @talldan @vipul0425 @zaguiini + + += 19.3.0-rc.1 = + +## Changelog + +### Features + +#### Zoom Out +- Remove experimental flag. ([65404](https://github.com/WordPress/gutenberg/pull/65404)) + +### Enhancements + +- Create Block: Update the minimum required PHP version to 7.2. ([65166](https://github.com/WordPress/gutenberg/pull/65166)) +- DataViews: remove unused `.dataviews-view-table__cell-content-wrapper:Empty` style rule. ([65084](https://github.com/WordPress/gutenberg/pull/65084)) +- Media Utils: Add TypeScript support and export more utils. ([64784](https://github.com/WordPress/gutenberg/pull/64784)) +- Media placeholders: Add "drag" to the text. ([65149](https://github.com/WordPress/gutenberg/pull/65149)) +- Restore: Move to trash button in Document settings. ([65087](https://github.com/WordPress/gutenberg/pull/65087)) +- Inspector Controls: Use custom block name in inspector controls when available. ([65398](https://github.com/WordPress/gutenberg/pull/65398)) +- Plugin: Don't force iframe editor when gutenberg plugin and block theme are enabled. ([65372](https://github.com/WordPress/gutenberg/pull/65372)) +- Icons: Adds bell and bell-unread icons. ([65324](https://github.com/WordPress/gutenberg/pull/65324)) +- Editor topbar: Reorder the actions on the right. ([65163](https://github.com/WordPress/gutenberg/pull/65163)) +- Patterns: Add opt out preference to the 'Choose a Pattern' modal when adding a page. ([65026](https://github.com/WordPress/gutenberg/pull/65026)) +- Locked Templates: Blocks with contentOnly locking should not be transformable. ([64917](https://github.com/WordPress/gutenberg/pull/64917)) +- Block Locking: Add border to Replace item in content only image toolbar. ([64849](https://github.com/WordPress/gutenberg/pull/64849)) + +#### Components +- Styling: Apply elevation scale in components package. ([65159](https://github.com/WordPress/gutenberg/pull/65159)) +- Tabs: Improve Tabs indicator animation and related utils. ([64926](https://github.com/WordPress/gutenberg/pull/64926)) +- Modal + - Add exit animation. ([65203](https://github.com/WordPress/gutenberg/pull/65203)) + - Decrease close button size. ([65131](https://github.com/WordPress/gutenberg/pull/65131)) +- Navigator Screen: Warn if path doesn't follow a URL-like scheme. ([65231](https://github.com/WordPress/gutenberg/pull/65231)) +- Card: Update Card radius. ([65053](https://github.com/WordPress/gutenberg/pull/65053)) +- Combobox Control: Add placeholder attribute. ([65254](https://github.com/WordPress/gutenberg/pull/65254)) + +#### Block Library +- Allow dropping multiple images to the image block. ([65030](https://github.com/WordPress/gutenberg/pull/65030)) +- Categories List block: Add dropdown for taxonomies. ([65272](https://github.com/WordPress/gutenberg/pull/65272)) +- Image: Adds the block controls for uploading image. ([64320](https://github.com/WordPress/gutenberg/pull/64320)) +- Remove colons from control labels. ([65205](https://github.com/WordPress/gutenberg/pull/65205)) +- Terms List block: Add Categories-specific variation. ([65434](https://github.com/WordPress/gutenberg/pull/65434)) + +#### Zoom Out +- Add Zoom Out toggle to editor header when experiment enabled. ([65183](https://github.com/WordPress/gutenberg/pull/65183)) +- Add prompt for drag and drop in Patterns tab in Zoom Out mode. ([65115](https://github.com/WordPress/gutenberg/pull/65115)) +- Close inserter on exiting Zoom Out to edit. ([65194](https://github.com/WordPress/gutenberg/pull/65194)) +- Show top level sections in List View. ([65202](https://github.com/WordPress/gutenberg/pull/65202)) +- Try vertical displacement when dragging a pattern between existing patterns/sections. ([63896](https://github.com/WordPress/gutenberg/pull/63896)) + +#### Block Editor +- Link Editing: Automatically add tel to phone number when linking URL. ([64865](https://github.com/WordPress/gutenberg/pull/64865)) +thub.com/WordPress/gutenberg/pull/65300)) +- Drag and Drop: When dragging a mix of video, audio, and image blocks, create individual blocks as appropriate. ([65144](https://github.com/WordPress/gutenberg/pull/65144)) +- URLInput: Replace input with InputControl. ([65158](https://github.com/WordPress/gutenberg/pull/65158)) +- Normalize block inspector controls spacing. ([64526](https://github.com/WordPress/gutenberg/pull/64526)) + +#### Post Editor +- Add new Media section to preferences modal. ([64846](https://github.com/WordPress/gutenberg/pull/64846)) +- DocumentBar: Replace icon with post type label. ([65170](https://github.com/WordPress/gutenberg/pull/65170)) +- Page editor: Double-click to edit template part. ([65024](https://github.com/WordPress/gutenberg/pull/65024)) +- Post publish upload media dialog: Handle more block types. ([65122](https://github.com/WordPress/gutenberg/pull/65122)) + +#### Block bindings +- Populate block context with inherited post type from template slug. ([65062](https://github.com/WordPress/gutenberg/pull/65062)) +- Try gap 0 on attribute items. ([65277](https://github.com/WordPress/gutenberg/pull/65277)) +- Use post meta label from `register_meta` in block bindings workflows. ([65099](https://github.com/WordPress/gutenberg/pull/65099)) + +#### Global Styles +- Refactor site background controls and move site global styles into Background group. ([65304](https://github.com/WordPress/gutenberg/pull/65304)) +- Spacing control: Replace sides dropdwon with link button. ([65193](https://github.com/WordPress/gutenberg/pull/65193)) + +#### Data Views +- DataViews Sidebar: Display item count on DataViews sidebar. ([65223](https://github.com/WordPress/gutenberg/pull/65223)) +- DataViews: Improve UX of bundled views for Pages. ([65295](https://github.com/WordPress/gutenberg/pull/65295)) + +#### Interactivity API +- Refactor context proxies. ([64713](https://github.com/WordPress/gutenberg/pull/64713)) +- Update: Rephrase "Force page reload" and move to Advanced. ([65081](https://github.com/WordPress/gutenberg/pull/65081)) + +#### REST API +- Global Styles: Allow read access to users with `edit_posts` capabilities. ([65071](https://github.com/WordPress/gutenberg/pull/65071)) +- Query loop / Post template: Enable post format filter. ([64167](https://github.com/WordPress/gutenberg/pull/64167)) + +### New APIs +- Add @wordpress/fields package. + - Introduce the package. ([65230](https://github.com/WordPress/gutenberg/pull/65230)) + - Make the package private. ([65269](https://github.com/WordPress/gutenberg/pull/65269)) + +### Bug Fixes + +- Align popover alt variant styling with block toolbar. ([65263](https://github.com/WordPress/gutenberg/pull/65263)) +- Compose: Correctly call timer cleanup in 'useFocusOnMount'. ([65184](https://github.com/WordPress/gutenberg/pull/65184)) +- Fix some docblock types related to the Template Registration API. ([65187](https://github.com/WordPress/gutenberg/pull/65187)) +- Fix the issue where block spacing control not shown. ([65371](https://github.com/WordPress/gutenberg/pull/65371)) +- Fix unintentional block toolbar shadow. ([65182](https://github.com/WordPress/gutenberg/pull/65182)) +- Fix: Moving a page to the trash on the site editor does not goes back to the pages list. ([65119](https://github.com/WordPress/gutenberg/pull/65119)) +- Fix: Moving the last page item to the the trash causes a crash. ([65236](https://github.com/WordPress/gutenberg/pull/65236)) +- Preferences: Fix back button on mobile. ([65141](https://github.com/WordPress/gutenberg/pull/65141)) +- Revert "Don't force iframe editor when gutenberg plugin and block the me are enabled (#65372)". ([65402](https://github.com/WordPress/gutenberg/pull/65402)) +- Post Summary Panel: Restore `height:Auto` for toggle buttons. ([65362](https://github.com/WordPress/gutenberg/pull/65362)) +- Fix Tabs styling in Font Library modal. ([65330](https://github.com/WordPress/gutenberg/pull/65330)) +- E2E: Change deprecated social icons for standard in end-to-end. ([65312](https://github.com/WordPress/gutenberg/pull/65312)) +- Typography: Make title blocks apply typographic styles consistently. ([65307](https://github.com/WordPress/gutenberg/pull/65307)) +- Target Hints REST API: Add missing param sanitization. ([65280](https://github.com/WordPress/gutenberg/pull/65280)) +- Interactivity API: Update iterable signals when `deepMerge()` adds new properties. ([65135](https://github.com/WordPress/gutenberg/pull/65135)) +- Navigation Menus: Typography styling support to the navigation submenu block. ([65060](https://github.com/WordPress/gutenberg/pull/65060)) +- Grid: In RTL languages, the resize handles point in the opposite direction. ([64995](https://github.com/WordPress/gutenberg/pull/64995)) +- Block Locking: Fix Content Only Toolbar icon focus style. ([64940](https://github.com/WordPress/gutenberg/pull/64940)) +- Image: Fix resizing to max width in classic themes. ([64819](https://github.com/WordPress/gutenberg/pull/64819)) +- Meta Boxes: Try split content view. ([64351](https://github.com/WordPress/gutenberg/pull/64351)) +- Distraction Free: Fix blurry edge along editor header. ([64277](https://github.com/WordPress/gutenberg/pull/64277)) + +#### Block Library +- Comments Pagination: Fix warning returned by comments pagination blocks. ([65435](https://github.com/WordPress/gutenberg/pull/65435)) +- Cover: Explicitly set isUserOverlayColor to false when media is updated. ([65105](https://github.com/WordPress/gutenberg/pull/65105)) +- Disallow setting grid block rows/columns to zero. ([65217](https://github.com/WordPress/gutenberg/pull/65217)) +- Fix image block crash. ([65222](https://github.com/WordPress/gutenberg/pull/65222)) +- Fix: Buttons block: Block spacing value does not apply to both vertical and horizontal alignment. ([64971](https://github.com/WordPress/gutenberg/pull/64971)) +- Fix: Embed blocks: Figcaption inserted via toolbar not nested within figure element - #64960. ([64970](https://github.com/WordPress/gutenberg/pull/64970)) +- Image cropping: Skip making an API request if there are no changes to apply. ([65384](https://github.com/WordPress/gutenberg/pull/65384)) +- Comments Pagination: Pass the comments query `paged` arg to functions `get_next_comments_link` and `get_previous_comments_link`. ([63698](https://github.com/WordPress/gutenberg/pull/63698)) +- Query Loop: Default to querying posts when on singular content. ([65067](https://github.com/WordPress/gutenberg/pull/65067)) + +#### Block Editor +- Inserter: Fix loading indicator for reusable blocks. ([64839](https://github.com/WordPress/gutenberg/pull/64839)) +- Normalize spacing in Layout hook controls. ([65132](https://github.com/WordPress/gutenberg/pull/65132)) +- Pattern Inserter: Fix pattern list overflow. ([65192](https://github.com/WordPress/gutenberg/pull/65192)) +- Remove reset styles RTL from the iframe. ([65150](https://github.com/WordPress/gutenberg/pull/65150)) +- Revert "Block Insertion: Clear the insertion point when selecting a dā€¦. ([65208](https://github.com/WordPress/gutenberg/pull/65208)) + +#### Components +- BoxControl: Unify input filed width whether linked or not. ([65348](https://github.com/WordPress/gutenberg/pull/65348)) +- ComboboxControl: Add more unit tests. ([65255](https://github.com/WordPress/gutenberg/pull/65255)) +- Fix: Button Replace remaining 40px default size violations [Edit widgets]. ([65367](https://github.com/WordPress/gutenberg/pull/65367)) +- Tabs: Fix vertical indicator. ([65385](https://github.com/WordPress/gutenberg/pull/65385)) + +#### Block bindings +- Fix empty strings placeholders in post meta bindings. ([65089](https://github.com/WordPress/gutenberg/pull/65089)) +- Prioritize existing `placeholder` over `bindingsPlaceholder`. ([65154](https://github.com/WordPress/gutenberg/pull/65154)) +- Revert "Block Bindings: Prioritize existing `placeholder` over `bindingsPlaceholder`". ([65190](https://github.com/WordPress/gutenberg/pull/65190)) + +#### Zoom Out +- Force device type to Desktop whenever zoom out is invoked. ([64476](https://github.com/WordPress/gutenberg/pull/64476)) +- Hide toolbar icon on smaller viewports. ([65437](https://github.com/WordPress/gutenberg/pull/65437)) +- Remove zoom out toggle when editor is not iframed. ([65452](https://github.com/WordPress/gutenberg/pull/65452)) + +### Accessibility + +- A11y: Add script-module. ([65101](https://github.com/WordPress/gutenberg/pull/65101)) +- Interactivity API: Use a11y Script Module in Gutenberg. ([65123](https://github.com/WordPress/gutenberg/pull/65123)) +- Script Modules API: Print script module live regions HTML in page HTML. ([65380](https://github.com/WordPress/gutenberg/pull/65380)) +- Post Editor: Support keyboard resizing of meta boxes pane. ([65325](https://github.com/WordPress/gutenberg/pull/65325)) +- DatePicker: Better hover/focus styles. ([65117](https://github.com/WordPress/gutenberg/pull/65117)) +- Form Input: Don't use `flex-direction: Row-reverse` for checkbox field. ([64232](https://github.com/WordPress/gutenberg/pull/64232)) +- Navigation Menus: Remove Warning and add notice for Navigation. ([63921](https://github.com/WordPress/gutenberg/pull/63921)) +- Global Styles: Fix the shadows Range control accessibility and usability. ([63908](https://github.com/WordPress/gutenberg/pull/63908)) +- Block Editor: Fix accessibility of the hooked blocks toggles. ([63133](https://github.com/WordPress/gutenberg/pull/63133)) + + +### Performance + +- Core Data: Batch remaining actions in resolvers. ([65176](https://github.com/WordPress/gutenberg/pull/65176)) +- Block Editor: Use static access for selector in 'useZoomOutModeExit'. ([65337](https://github.com/WordPress/gutenberg/pull/65337)) +- Editor: Optimize global styles permission check. ([65177](https://github.com/WordPress/gutenberg/pull/65177)) + + +### Experiments + +- Block bindings REST API: Bring bindings UI in Site Editor. ([64072](https://github.com/WordPress/gutenberg/pull/64072)) + + +### Documentation + +- Add JSDoc block for getSectionRootClientId in block editor package. ([65219](https://github.com/WordPress/gutenberg/pull/65219)) +- ButtonGroup: Fix story to show what the component does. ([65336](https://github.com/WordPress/gutenberg/pull/65336)) +- DataViews storybook + - Better styles for combined fields story. ([65078](https://github.com/WordPress/gutenberg/pull/65078)) + - Enable all layouts for combined fields storybook. ([65082](https://github.com/WordPress/gutenberg/pull/65082)) +- Docs: Fix minor typos in Build your first block tutorial. ([64961](https://github.com/WordPress/gutenberg/pull/64961)) +- Docs: Update the content of the API version 3 section in the Block API Reference. ([65375](https://github.com/WordPress/gutenberg/pull/65375)) +- Fix typo in Slot Fills documentation. ([65275](https://github.com/WordPress/gutenberg/pull/65275)) + + +### Code Quality + +- Components: Transition to the new 40px default size. + - Button: + - Add __next40pxDefaultSize for files in editor 3. ([65139](https://github.com/WordPress/gutenberg/pull/65139)) + - Add __next40pxDefaultSize for files in editor 4. ([65140](https://github.com/WordPress/gutenberg/pull/65140)) + - Add props for buttons in editor 1. ([65068](https://github.com/WordPress/gutenberg/pull/65068)) + - Add props for buttons in editor 2. ([65083](https://github.com/WordPress/gutenberg/pull/65083)) + - Fix: Replace remaining 40px default size violations [Block Editor 4]. ([65257](https://github.com/WordPress/gutenberg/pull/65257)) + - Fix: Replace remaining 40px default size violation [Block library 3]. ([65110](https://github.com/WordPress/gutenberg/pull/65110)) + - Fix: Replace remaining 40px default size violation [Block library 4]. ([65143](https://github.com/WordPress/gutenberg/pull/65143)) + - Fix: Replace remaining 40px default size violation [Block library]. ([65075](https://github.com/WordPress/gutenberg/pull/65075)) + - Fix: Replace remaining 40px default size violation [Edit Site 2]. ([65258](https://github.com/WordPress/gutenberg/pull/65258)) + - Fix: Replace remaining 40px default size violations [Block library 1]. ([65033](https://github.com/WordPress/gutenberg/pull/65033)) + - Fix: Replace remaining 40px default size violations [Block Editor 1]. ([65034](https://github.com/WordPress/gutenberg/pull/65034)) + - BoxControl + - Add lint rule for 40px size prop usage. ([65341](https://github.com/WordPress/gutenberg/pull/65341)) + - DimensionsPanel: Apply 40px default size to UI when no spacing preset is available. ([65300](https://gi +- Add `useEvent` and revamped `useResizeObserver` to `@wordpress/compose`. ([64943](https://github.com/WordPress/gutenberg/pull/64943)) +- DataViews: Use Dropdown for views configuration dialog. ([65314](https://github.com/WordPress/gutenberg/pull/65314)) +- Platform docs: Upgrade dependencies. ([65445](https://github.com/WordPress/gutenberg/pull/65445)) +- Rename edit-post__fade-in-animation and unify keyframe definitions. ([65377](https://github.com/WordPress/gutenberg/pull/65377)) +- Update minimum required version in PHP. ([65301](https://github.com/WordPress/gutenberg/pull/65301)) +- Editor: Use hooks instead of HoC in `BlockManager`. ([65349](https://github.com/WordPress/gutenberg/pull/65349)) +- Data Views Fields: Migrate store and actions from editor package to fields package. ([65261](https://github.com/WordPress/gutenberg/pull/65261)) +- Plugin: Remove 'function_exists' checks for methods with 'gutenberg' prefix. ([65260](https://github.com/WordPress/gutenberg/pull/65260)) +- Global Styles: Update REST controller override method and backport changes from Core. ([65259](https://github.com/WordPress/gutenberg/pull/65259)) +- Patterns: Remove unused method returned from 'mapSelect'. ([65073](https://github.com/WordPress/gutenberg/pull/65073)) +- Embed: Convert EmbedPreview component to functional component. ([51325](https://github.com/WordPress/gutenberg/pull/51325)) + +#### Components +- BoxControl: Fix critical error when null value is passed. ([65287](https://github.com/WordPress/gutenberg/pull/65287)) +- Composite: + - Deprecate legacy, unstable version. ([63572](https://github.com/WordPress/gutenberg/pull/63572)) + - Remove store prop and useCompositeStore hook. ([64723](https://github.com/WordPress/gutenberg/pull/64723)) + - Stabilize APIs. ([63569](https://github.com/WordPress/gutenberg/pull/63569)) +- `@wordpress/components`: Add local copy of `use-lilius`. ([65097](https://github.com/WordPress/gutenberg/pull/65097)) + +#### Block bindings +- Always prioritize using context in post meta source logic. ([65449](https://github.com/WordPress/gutenberg/pull/65449)) +- Improve getRegisteredPostMeta resolver. ([65450](https://github.com/WordPress/gutenberg/pull/65450)) +- Remove extra filtering of empty sources. ([65447](https://github.com/WordPress/gutenberg/pull/65447)) + +#### Block Editor +- Remove the 'PrivateInserter' component. ([65111](https://github.com/WordPress/gutenberg/pull/65111)) +- Use the tooltip from a button in 'ButtonBlockAppender'. ([65113](https://github.com/WordPress/gutenberg/pull/65113)) +- Remove unused css selectors. ([65276](https://github.com/WordPress/gutenberg/pull/65276)) + +### Tools + +- Scripts: Update stylelint dependency and the default configuration. ([64828](https://github.com/WordPress/gutenberg/pull/64828)) +- Styleling config: Fix stylelint configuration missing files for npm. ([65313](https://github.com/WordPress/gutenberg/pull/65313)) + +#### Build Tooling +- Build Plugin: Simplify and improve zip contents. ([65232](https://github.com/WordPress/gutenberg/pull/65232)) +- Build zip artifact on release and wp production branches. ([65471](https://github.com/WordPress/gutenberg/pull/65471)) +- Build: Include Core blocks' `render` and `variations` files. ([63311](https://github.com/WordPress/gutenberg/pull/63311)) +- Script Modules + - Prepare build for more script modules. ([65064](https://github.com/WordPress/gutenberg/pull/65064)) + - Remove babel from script-modules build. ([65279](https://github.com/WordPress/gutenberg/pull/65279)) + - Remove es-module shims and importmap-polyfill. ([65210](https://github.com/WordPress/gutenberg/pull/65210)) +- Correctly generate PHP files for server-side rendering of blocks on Windows OS. ([65248](https://github.com/WordPress/gutenberg/pull/65248)) +- Packages: Only add polyfills where needed. ([65292](https://github.com/WordPress/gutenberg/pull/65292)) +- Switch from UglifyJS to Terser to build the polyfill script. ([65278](https://github.com/WordPress/gutenberg/pull/65278)) + +#### Testing +- Unit tests: Mock matchMedia to enforce prefers-reduce-motion. ([65438](https://github.com/WordPress/gutenberg/pull/65438)) +- Upgrade Playwright to v1.47. ([65156](https://github.com/WordPress/gutenberg/pull/65156)) + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @AKSHAT2802: Add __next40pxDefaultSize for files in editor 4. ([65140](https://github.com/WordPress/gutenberg/pull/65140)) +- @devansh016: Automatically add tel to phone number when linking URL. ([64865](https://github.com/WordPress/gutenberg/pull/64865)) +- @dhruvang21: Fix: Button Replace remaining 40px default size violations [Edit widgets]. ([65367](https://github.com/WordPress/gutenberg/pull/65367)) +- @farid-hadi: Docs: Fix minor typos in Build your first block tutorial. ([64961](https://github.com/WordPress/gutenberg/pull/64961)) +- @greenworld: Fix typo in Slot Fills documentation. ([65275](https://github.com/WordPress/gutenberg/pull/65275)) +- @louwie17: Convert EmbedPreview component to functional component. ([51325](https://github.com/WordPress/gutenberg/pull/51325)) +- @rahulharpal1603: URLInput: Replace input with InputControl. ([65158](https://github.com/WordPress/gutenberg/pull/65158)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @AKSHAT2802 @Aljullu @andrewserong @carolinan @cbravobernal @ciampo @colorful-tones @creativecoder @DaniGuardiola @DAreRodz @devansh016 @dhruvang21 @ellatrix @farid-hadi @getdave @gigitux @greenworld @gziolo @hbhalodia @jameskoster @jasmussen @javierarce @jeryj @jorgefilipecosta @jsnajdr @kevin940726 @louwie17 @madhusudhand @MaggieCabrera @Mamaduka @mikeybinns @mirka @ntsekouras @oandregal @ockham @peterwilsoncc @rahulharpal1603 @ramonjd @richtabor @rohitmathur-7 @SantosGuillamot @scruffian @sgomes @sirreal @stokesman @swissspidy @t-hamano @talldan @vipul0425 @zaguiini + + += 19.2.0 = + +## Changelog + +### Enhancements + +- Add: Reorder control at the field level on the new view configuration UI. ([64381](https://github.com/WordPress/gutenberg/pull/64381)) +- Core Data Types: `recordId` can be a number. ([64796](https://github.com/WordPress/gutenberg/pull/64796)) +- Core Data: Derive collection totals for unbound queries. ([64772](https://github.com/WordPress/gutenberg/pull/64772)) +- Create Block: Set minimum supported WordPress version to 6.6. ([64920](https://github.com/WordPress/gutenberg/pull/64920)) +- Dataviews Filter search widget: Do not use Composite store. ([64985](https://github.com/WordPress/gutenberg/pull/64985)) +- Dataviews list view: Do not use Composite store. ([64987](https://github.com/WordPress/gutenberg/pull/64987)) +- Move bulk actions menu to the Footer, consolidate with floating toolbar and total items display. ([64268](https://github.com/WordPress/gutenberg/pull/64268)) +- Try: Update block warnings. ([64997](https://github.com/WordPress/gutenberg/pull/64997)) + +#### Components +- Add variants to InputControl prefix/suffix wrappers. ([64824](https://github.com/WordPress/gutenberg/pull/64824)) +- AlignmentMatrixControl: Do not use Composite store. ([64850](https://github.com/WordPress/gutenberg/pull/64850)) +- CircularOptionPicker: Stop using composite store. ([64833](https://github.com/WordPress/gutenberg/pull/64833)) +- Composite: Accept store props on top level component. ([64832](https://github.com/WordPress/gutenberg/pull/64832)) +- DataViews: Adds two new stories for edge cases. ([64975](https://github.com/WordPress/gutenberg/pull/64975)) +- Decrease standard padding to 12px. ([64708](https://github.com/WordPress/gutenberg/pull/64708)) +- DropdownMenuV2: Add GroupLabel subcomponent. ([64854](https://github.com/WordPress/gutenberg/pull/64854)) +- DropdownMenuV2: Update animation. ([64868](https://github.com/WordPress/gutenberg/pull/64868)) +- DropdownMenuV2: Use overloaded naming conventions. ([64654](https://github.com/WordPress/gutenberg/pull/64654)) +- InputControl: Tighten gap between input and prefix/suffix. ([64908](https://github.com/WordPress/gutenberg/pull/64908)) +- Navigator: Polish Storybook examples. ([64798](https://github.com/WordPress/gutenberg/pull/64798)) +- Navigator: Remove location history, simplify internal logic. ([64675](https://github.com/WordPress/gutenberg/pull/64675)) +- UnitControl: Update unit select styles. ([64712](https://github.com/WordPress/gutenberg/pull/64712)) +- Update hard-coded border-radius instances. ([64693](https://github.com/WordPress/gutenberg/pull/64693)) +- Update modal animation. ([64580](https://github.com/WordPress/gutenberg/pull/64580)) + +#### Block bindings +- Add warning in attributes connected to invalid sources. ([65002](https://github.com/WordPress/gutenberg/pull/65002)) +- Allow only admin users to create and modify bindings by default. ([64570](https://github.com/WordPress/gutenberg/pull/64570)) +- Lock editing in fields in editor if meta fields panel is opened. ([64738](https://github.com/WordPress/gutenberg/pull/64738)) +- Rely on `Text` component instead of `Truncate` in bindings panel. ([65007](https://github.com/WordPress/gutenberg/pull/65007)) +- Remove `getPlaceholder` API and rely on `key` argument or source label. ([64910](https://github.com/WordPress/gutenberg/pull/64910)) + +#### Data Views +- Add: Reorder control at the field level on the new view configuration UI. ([64381](https://github.com/WordPress/gutenberg/pull/64381)) +- Dataviews Filter search widget: Do not use Composite store. ([64985](https://github.com/WordPress/gutenberg/pull/64985)) +- Dataviews list view: Do not use Composite store. ([64987](https://github.com/WordPress/gutenberg/pull/64987)) +- Move bulk actions menu to the Footer, consolidate with floating toolbar and total items display. ([64268](https://github.com/WordPress/gutenberg/pull/64268)) + +#### Block Editor +- Add 'Reset' option to MediaReplaceFlow component. ([64826](https://github.com/WordPress/gutenberg/pull/64826)) +- Block Patterns List: Do not use Composite store. ([64983](https://github.com/WordPress/gutenberg/pull/64983)) +- Remove the Shuffle block toolbar button. ([64954](https://github.com/WordPress/gutenberg/pull/64954)) +- Show block icon in contentOnly toolbar. ([64694](https://github.com/WordPress/gutenberg/pull/64694)) + +#### Block Library +- Cover Block: Move Clear Media button from Inspector Controls to Block Controls. ([64630](https://github.com/WordPress/gutenberg/pull/64630)) +- Improve Social Icons setup and appending. ([64877](https://github.com/WordPress/gutenberg/pull/64877)) +- Pagination Block: Fix inconsistent margins between editor and frontend. ([64874](https://github.com/WordPress/gutenberg/pull/64874)) +- Tag Cloud: Improve state of block with no tags. ([63774](https://github.com/WordPress/gutenberg/pull/63774)) + +#### Block Locking +- ContentOnly: Add support for block styles on top-level contentOnly locked blocks. ([64872](https://github.com/WordPress/gutenberg/pull/64872)) +- Only show title in content only toolbar if has title value. ([64840](https://github.com/WordPress/gutenberg/pull/64840)) +- Remove ability to crop image if content only mode. ([64838](https://github.com/WordPress/gutenberg/pull/64838)) +- Rename Alt to Alternative Text in content only image toolbar. ([64841](https://github.com/WordPress/gutenberg/pull/64841)) + +#### Interactivity API +- Categories Block: Add iAPI directive for client-side routing. ([64907](https://github.com/WordPress/gutenberg/pull/64907)) +- Improve internal `deepMerge` function. ([64879](https://github.com/WordPress/gutenberg/pull/64879)) + +#### Global Styles +- Hide typeset button when there are no typesets available. ([64515](https://github.com/WordPress/gutenberg/pull/64515)) +- Use four color palette colors instead of five for useStylesPreviewColors. ([64700](https://github.com/WordPress/gutenberg/pull/64700)) + +#### Zoom Out +- Add "Edit" button to Zoom Out mode toolbar. ([64571](https://github.com/WordPress/gutenberg/pull/64571)) +- Double click block to exit zoom out mode. ([64573](https://github.com/WordPress/gutenberg/pull/64573)) + +#### Design Tools +- Comment Edit Link: Add Border Block Support. ([64239](https://github.com/WordPress/gutenberg/pull/64239)) +- Comment Reply Link: Add border support. ([64271](https://github.com/WordPress/gutenberg/pull/64271)) + +#### Icons +- Add thumbs up and down icons. ([65004](https://github.com/WordPress/gutenberg/pull/65004)) + +#### Site Editor +- Apply radius scale in the editor. ([64930](https://github.com/WordPress/gutenberg/pull/64930)) + +#### Post Editor +- Post publish upload media dialog: Handle upload errors. ([64823](https://github.com/WordPress/gutenberg/pull/64823)) + +#### Typography +- Fluid typography: Allow individual preset overrides. ([64790](https://github.com/WordPress/gutenberg/pull/64790)) + +#### Media +- Add experiment for client-side media processing. ([64650](https://github.com/WordPress/gutenberg/pull/64650)) + +#### REST API +- Core Data: Resolve entity collection user permissions. ([64504](https://github.com/WordPress/gutenberg/pull/64504)) + +#### Block Transforms +- Details block: Add transform from any block type. ([63422](https://github.com/WordPress/gutenberg/pull/63422)) + + +### New APIs + +#### Extensibility +- Editor: Add extensibility to PreviewOptions v2. ([64644](https://github.com/WordPress/gutenberg/pull/64644)) + + +### Bug Fixes + +- Add safeguard to `mediaUploadMiddleware`. ([64843](https://github.com/WordPress/gutenberg/pull/64843)) +- Allow multi-select on iOS Safari/touch devices. ([63671](https://github.com/WordPress/gutenberg/pull/63671)) +- Core Data: Fix the 'query._fields' property check inside 'getEntityRecord' resolver. ([65079](https://github.com/WordPress/gutenberg/pull/65079)) +- Fix Modify content-locked menu item not showing if the block is not selected. ([61605](https://github.com/WordPress/gutenberg/pull/61605)) +- Fix editor error in Safari due to availability of checkVisibility method. ([65069](https://github.com/WordPress/gutenberg/pull/65069)) +- Fix: Pagination arrows are pointing in the wrong direction in RTL languages. ([64962](https://github.com/WordPress/gutenberg/pull/64962)) +- Footnotes: Only replace attribute if footnotes were detected. ([63935](https://github.com/WordPress/gutenberg/pull/63935)) +- Paste: Fix image paste from Google Forms. ([64502](https://github.com/WordPress/gutenberg/pull/64502)) +- Revert Focus pattern inserter search when activating zoom out inserter. ([64748](https://github.com/WordPress/gutenberg/pull/64748)) +- Try: Update block warnings. ([64997](https://github.com/WordPress/gutenberg/pull/64997)) + +#### Block Library +- De-duplicate block toolbar icons for patterns. ([65054](https://github.com/WordPress/gutenberg/pull/65054)) +- Fix: Page list: Pages without a title has no link text. ([64297](https://github.com/WordPress/gutenberg/pull/64297)) +- Position BlockToolbar below all of the selected block's descendants. ([62711](https://github.com/WordPress/gutenberg/pull/62711)) +- Site Logo Block: Fix non-admin users seeing zero character. ([65010](https://github.com/WordPress/gutenberg/pull/65010)) +- Site Logo: Fix loader alignment issue. ([64919](https://github.com/WordPress/gutenberg/pull/64919)) +- Template Part: Hide Advanced panel for non-admin users. ([64721](https://github.com/WordPress/gutenberg/pull/64721)) +- Video Block: Fix layout issue. ([64834](https://github.com/WordPress/gutenberg/pull/64834)) + +#### Components +- ColorPalette utils: Do not normalize undefined color values. ([64969](https://github.com/WordPress/gutenberg/pull/64969)) +- DatePicker: Restore round radius for event dot. ([65031](https://github.com/WordPress/gutenberg/pull/65031)) +- DropdownMenuV2: Fix active and focus-visible item glitches. ([64942](https://github.com/WordPress/gutenberg/pull/64942)) +- DropdownMenuV2: Remove flashing styles when moving focus with keyboard. ([64873](https://github.com/WordPress/gutenberg/pull/64873)) +- Fixes "delete" action in DataViews' storybook. ([64901](https://github.com/WordPress/gutenberg/pull/64901)) +- Navigator: Fix isInitial, refine focusSelector logic. ([64786](https://github.com/WordPress/gutenberg/pull/64786)) +- Range control: Restore bottom margin rule. ([65035](https://github.com/WordPress/gutenberg/pull/65035)) + +#### Post Editor +- Add back editor-post-locked-modal to post lock component. ([64257](https://github.com/WordPress/gutenberg/pull/64257)) +- Add context to `View` string in post actions. ([65046](https://github.com/WordPress/gutenberg/pull/65046)) +- Apply space below content using a pseudo-element instead of padding-bottom. ([64639](https://github.com/WordPress/gutenberg/pull/64639)) +- Post Title: Fix pasting in Safari. ([64671](https://github.com/WordPress/gutenberg/pull/64671)) +- Post Title: Move selection at the end after pasting over the text. ([64665](https://github.com/WordPress/gutenberg/pull/64665)) +- Post publish upload media dialog: Fix silent failure. ([64741](https://github.com/WordPress/gutenberg/pull/64741)) + +#### Data Views +- DataViews: Fix field reordering and visibility logic. ([64999](https://github.com/WordPress/gutenberg/pull/64999)) +- Fix actions scrim in list layout. ([64696](https://github.com/WordPress/gutenberg/pull/64696)) +- Fix data views style inheritance. ([64933](https://github.com/WordPress/gutenberg/pull/64933)) +- Fix: Impossible to see pagination on viewports between small and medium. ([64844](https://github.com/WordPress/gutenberg/pull/64844)) +- List layout: Update broken styles. ([64837](https://github.com/WordPress/gutenberg/pull/64837)) + +#### Block Editor +- Add conditions when the Shuffle button can be displayed. ([64888](https://github.com/WordPress/gutenberg/pull/64888)) +- Inserter: Fix subtle media insertion error. ([65057](https://github.com/WordPress/gutenberg/pull/65057)) +- Post Editor: Fix click space after post content to append. ([64992](https://github.com/WordPress/gutenberg/pull/64992)) +- Writing flow: Fix triple click inside text blocks. ([64928](https://github.com/WordPress/gutenberg/pull/64928)) + +#### Global Styles +- Adjust spacing of background panel. ([64880](https://github.com/WordPress/gutenberg/pull/64880)) +- Cast globalFluid value to boolean. ([64882](https://github.com/WordPress/gutenberg/pull/64882)) +- Fix site editor broken when fontWeight is not defined or is an integer in theme.json or theme styles. ([64953](https://github.com/WordPress/gutenberg/pull/64953)) +- Fixes the default fluid value on the UI based on the global typography fluid value. ([64803](https://github.com/WordPress/gutenberg/pull/64803)) + +#### Block bindings +- Change placeholder when attribute is bound. ([64903](https://github.com/WordPress/gutenberg/pull/64903)) +- Fix empty custom fields not being editable in bindings. ([64881](https://github.com/WordPress/gutenberg/pull/64881)) + +#### CSS & Styling +- Featured Image Block: Reduce CSS specificity. ([64463](https://github.com/WordPress/gutenberg/pull/64463)) +- Retain the same specificity for non iframed selectors. ([64534](https://github.com/WordPress/gutenberg/pull/64534)) + +#### Patterns +- Pass 'blocks' as inner blocks value. ([65029](https://github.com/WordPress/gutenberg/pull/65029)) + +#### Synced Patterns +- Pattern: Don't render block controls when an entity is missing. ([65028](https://github.com/WordPress/gutenberg/pull/65028)) + +#### Site Editor +- DataViews: Fix pattern title direction in RTL languages. ([64967](https://github.com/WordPress/gutenberg/pull/64967)) + +#### Typography +- Site Title, Post Title: Fix typography for blocks with `a` children. ([64911](https://github.com/WordPress/gutenberg/pull/64911)) + +#### NUX +- Fix visibility of the template Welcome Guide in the Site Editor. ([64789](https://github.com/WordPress/gutenberg/pull/64789)) + +#### Document Settings +- Fix: Adjust Site URL Styles to Prevent Overflow in Pre-Publish Component. ([64745](https://github.com/WordPress/gutenberg/pull/64745)) + +#### Zoom Out +- Focus selected block in editor canvas when clicking edit button on zoom out mode toolbar. ([64725](https://github.com/WordPress/gutenberg/pull/64725)) + +#### Templates API +- Make plugin-registered templates overriden by themes to fall back to plugin-registered title and description. ([64610](https://github.com/WordPress/gutenberg/pull/64610)) + +#### Block Style Variations +- Block Styles: Ensure unique classname generation for variations. ([64511](https://github.com/WordPress/gutenberg/pull/64511)) + +#### Distraction Free +- Make Distraction Free not conditional on viewport width. ([63949](https://github.com/WordPress/gutenberg/pull/63949)) + +#### Media +- Limit the max width of image to its container size. ([63341](https://github.com/WordPress/gutenberg/pull/63341)) + + +### Accessibility + +#### Components +- AlignmentMatrixControl: Simplify styles and markup. ([64827](https://github.com/WordPress/gutenberg/pull/64827)) +- TimePicker: Use ToggleGroupControl for AM/PM toggle. ([64800](https://github.com/WordPress/gutenberg/pull/64800)) + +#### Block Editor +- Layout content and wide width controls: Remove confusing icon and clarify labels. ([64891](https://github.com/WordPress/gutenberg/pull/64891)) + +#### Font Library +- Font Library Modal: Group font variations as a list. ([64029](https://github.com/WordPress/gutenberg/pull/64029)) + +#### Post Editor +- Fix the post summary Status toggle button accessibility. ([63988](https://github.com/WordPress/gutenberg/pull/63988)) + + +### Performance + +- Core Data: Avoid loops in 'registry.batch' calls. ([64955](https://github.com/WordPress/gutenberg/pull/64955)) +- Core data: Performance: Fix receive user permissions. ([64894](https://github.com/WordPress/gutenberg/pull/64894)) +- Reusable blocks: Fix performance of __experimentalGetAllowedPatterns. ([64871](https://github.com/WordPress/gutenberg/pull/64871)) + +#### Site Editor +- Add 'OPTIONS /page' to preloaded paths. ([64890](https://github.com/WordPress/gutenberg/pull/64890)) +- Editor: Don't use selector shortcuts for the Site data. ([64884](https://github.com/WordPress/gutenberg/pull/64884)) + +#### Interactivity API +- Prevent calling `proxifyContext` with context proxies inside `wp-context`. ([65090](https://github.com/WordPress/gutenberg/pull/65090)) + +#### Block Library +- Media & Text: Don't use background-image. ([64981](https://github.com/WordPress/gutenberg/pull/64981)) + +#### Post Editor +- Editor: Remove create template permission check in 'VisualEditor'. ([64905](https://github.com/WordPress/gutenberg/pull/64905)) + +#### Block Editor +- Inserter: Use lighter grammar parse to check allowed status. ([64902](https://github.com/WordPress/gutenberg/pull/64902)) + +#### Patterns +- Shuffle: Don't call '__experimentalGetAllowedPatterns' for every block. ([64736](https://github.com/WordPress/gutenberg/pull/64736)) + + +### Experiments + +#### Zoom Out +- Add new zoom out experiment. ([65048](https://github.com/WordPress/gutenberg/pull/65048)) +- Remove the experiment that connects zoom out to the pattern inserter. ([65045](https://github.com/WordPress/gutenberg/pull/65045)) + + +### Documentation + +- Add a new section to the SlotFill reference to show how to conditionally render Fills. ([64807](https://github.com/WordPress/gutenberg/pull/64807)) +- Added Global Documentation in several php file. ([64956](https://github.com/WordPress/gutenberg/pull/64956)) +- Components: Move displayName assignment to top-level files. ([64793](https://github.com/WordPress/gutenberg/pull/64793)) +- Composite: Add context-forwarding with SlotFill example. ([65051](https://github.com/WordPress/gutenberg/pull/65051)) +- Composite: Fix Storybook docgen. ([64682](https://github.com/WordPress/gutenberg/pull/64682)) +- Corrected HTML Syntax for Closing Tags in api-reference.md file. ([64778](https://github.com/WordPress/gutenberg/pull/64778)) +- DataViews docs: Fix typo in `direction` values. ([64973](https://github.com/WordPress/gutenberg/pull/64973)) +- DataViews: Add story about combining fields. ([64984](https://github.com/WordPress/gutenberg/pull/64984)) +- DataViews: Document combined fields. ([64904](https://github.com/WordPress/gutenberg/pull/64904)) +- Dataviews docs: Layout properties checks and link. ([64918](https://github.com/WordPress/gutenberg/pull/64918)) +- Docs/iAPI: Fix wrong code snippets in API reference. ([64416](https://github.com/WordPress/gutenberg/pull/64416)) +- Docs: Update design resources to indicate edit isn't free. ([64792](https://github.com/WordPress/gutenberg/pull/64792)) +- PluginSidebarMoreMenuItem: Update example, screenshot and description. ([64761](https://github.com/WordPress/gutenberg/pull/64761)) +- Provide better examples and remove outdating site edit references for the MainDashboardButton SlotFill. ([64753](https://github.com/WordPress/gutenberg/pull/64753)) +- Removing ryanwelcher as a documentation codeowner because my inbox is dead. ([64762](https://github.com/WordPress/gutenberg/pull/64762)) +- Storybook: Hide deprecated `__next36pxDefaultSize` prop. ([64806](https://github.com/WordPress/gutenberg/pull/64806)) +- Update screenshot and description for PluginSidebar slot. ([64759](https://github.com/WordPress/gutenberg/pull/64759)) +- Update text to match code examples. ([64751](https://github.com/WordPress/gutenberg/pull/64751)) +- Update the import for PluginBlockSettingsMenuItem. ([64758](https://github.com/WordPress/gutenberg/pull/64758)) +- Updated Several Typos in Doc files. ([64787](https://github.com/WordPress/gutenberg/pull/64787)) +- [Docs]: Update Usage Example for block variation picker: Fix Import from Wrong Package. ([55555](https://github.com/WordPress/gutenberg/pull/55555)) + + +### Code Quality + +- Button: Add lint rule for 40px size prop usage. ([64835](https://github.com/WordPress/gutenberg/pull/64835)) +- Dataviews filter: Move resetValueOnSelect prop to combobox item. ([64852](https://github.com/WordPress/gutenberg/pull/64852)) +- Rename refs to fix tons of 'Mutating a value' errors in react-compiler. ([64718](https://github.com/WordPress/gutenberg/pull/64718)) +- Rich text: Add comment on placeholder approach. ([64945](https://github.com/WordPress/gutenberg/pull/64945)) +- SelectControl: Fix remaining 40px size violations. ([64831](https://github.com/WordPress/gutenberg/pull/64831)) +- Simplify useResizeObserver. ([64820](https://github.com/WordPress/gutenberg/pull/64820)) +- Typography: Backport comment changes only. ([64859](https://github.com/WordPress/gutenberg/pull/64859)) +- UnitControl: Add lint rule for 40px size prop usage. ([64520](https://github.com/WordPress/gutenberg/pull/64520)) +- UnitControl: Move to stricter lint rule for 40px size adherence. ([65017](https://github.com/WordPress/gutenberg/pull/65017)) +- Use rectIntersect instead of a custom argument to rectUnion. ([64855](https://github.com/WordPress/gutenberg/pull/64855)) + +#### Site Editor +- Add Custom Template modal: Do not use Composite store. ([65044](https://github.com/WordPress/gutenberg/pull/65044)) +- Add units to avoid console warning. ([64810](https://github.com/WordPress/gutenberg/pull/64810)) +- Edit Site Layout: Remove redundant fullResizer. ([64821](https://github.com/WordPress/gutenberg/pull/64821)) +- Remove unused 'useSiteEditorSettings' hook. ([64892](https://github.com/WordPress/gutenberg/pull/64892)) +- Style Book: Do not use Composite store. ([65047](https://github.com/WordPress/gutenberg/pull/65047)) + +#### Block Editor +- Block Inserter Listbox: Do not use Composite store. ([65042](https://github.com/WordPress/gutenberg/pull/65042)) +- Block Inserter Media List: Do not use Composite store. ([65043](https://github.com/WordPress/gutenberg/pull/65043)) +- Block Pattern Setup: Do not use Composite store. ([65039](https://github.com/WordPress/gutenberg/pull/65039)) +- Global Styles Shadow Panel: Do not use Composite store. ([65041](https://github.com/WordPress/gutenberg/pull/65041)) +- Pattern Transformations Menu: Do not use Composite store. ([65040](https://github.com/WordPress/gutenberg/pull/65040)) + +#### Zoom Out +- Add selector for getting section root clientId. ([65001](https://github.com/WordPress/gutenberg/pull/65001)) +- Don't pass 'rootClientId' to block lock selectors. ([64887](https://github.com/WordPress/gutenberg/pull/64887)) +- Fix error and improve privacy of sectionRootClientId setting. ([65000](https://github.com/WordPress/gutenberg/pull/65000)) + +#### Components +- AlignmentMatrixControl: Promote to stable. ([60913](https://github.com/WordPress/gutenberg/pull/60913)) +- Deprecate `DimensionControl`. ([64951](https://github.com/WordPress/gutenberg/pull/64951)) + +#### Block Library +- Block Bindings: Fix ESLint warnings. ([64684](https://github.com/WordPress/gutenberg/pull/64684)) +- Video Block: Remove custom CSS code for placeholder style. ([64861](https://github.com/WordPress/gutenberg/pull/64861)) + +#### Global Styles +- Allow referenced zero value and simplify getValueFromObjectPath calls. ([64836](https://github.com/WordPress/gutenberg/pull/64836)) +- Navigator: Replace deprecated NavigatorToParentButton with NavigatorBackButton. ([64775](https://github.com/WordPress/gutenberg/pull/64775)) + +#### Block Directory +- Downloadable Block List: Do not use composite store. ([65038](https://github.com/WordPress/gutenberg/pull/65038)) + +#### Design Tools +- Color panel hook: Rename to remove ambiguity. ([64993](https://github.com/WordPress/gutenberg/pull/64993)) + + +### Tools + +- Add remaining i18n rules to recommended ESLint ruleset. ([64710](https://github.com/WordPress/gutenberg/pull/64710)) +- Scripts: Added chunk filename in webpack configuration to avoid reading stale files. ([58176](https://github.com/WordPress/gutenberg/pull/58176)) +- Scripts: Import CSS files before optimization. ([61121](https://github.com/WordPress/gutenberg/pull/61121)) +- Scripts: Update `puppeteer-core` dependency. ([64597](https://github.com/WordPress/gutenberg/pull/64597)) + +#### Testing +- Flaky Test: Fix "Sorting" test in new-templates-list.spec.js. ([64776](https://github.com/WordPress/gutenberg/pull/64776)) +- Revert "Downgrade node 22(.5) unit tests to 22.4 (#63728)". ([63758](https://github.com/WordPress/gutenberg/pull/63758)) + + +### Various + +- Dataviews docs: Fixed property name for defaultLayouts settings. ([64897](https://github.com/WordPress/gutenberg/pull/64897)) +- task: Remove dcalhoun code owner. ([64886](https://github.com/WordPress/gutenberg/pull/64886)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @Imran92: Fix site editor broken when fontWeight is not defined or is an integer in theme.json or theme styles. ([64953](https://github.com/WordPress/gutenberg/pull/64953)) +- @jacobcassidy: Scripts: Update `puppeteer-core` dependency. ([64597](https://github.com/WordPress/gutenberg/pull/64597)) +- @jawadmalikdev: [Docs]: Update Usage Example for block variation picker: Fix Import from Wrong Package. ([55555](https://github.com/WordPress/gutenberg/pull/55555)) +- @lezama: Editor: Add extensibility to PreviewOptions v2. ([64644](https://github.com/WordPress/gutenberg/pull/64644)) +- @rithik56: Scripts: Added chunk filename in webpack configuration to avoid reading stale files. ([58176](https://github.com/WordPress/gutenberg/pull/58176)) +- @rohitmathur-7: Cover Block: Move Clear Media button from Inspector Controls to Block Controls. ([64630](https://github.com/WordPress/gutenberg/pull/64630)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @akasunil @Aljullu @andrewserong @atachibana @benoitchantre @carolinan @cbravobernal @ciampo @DAreRodz @dcalhoun @desrosj @dsas @ellatrix @fullofcaffeine @getdave @gziolo @Imran92 @imrraaj @jacobcassidy @jameskoster @jasmussen @jawadmalikdev @jeryj @jorgefilipecosta @jsnajdr @juanmaguitar @kevin940726 @lezama @Mamaduka @matiasbenedetto @mirka @noisysocks @ntsekouras @oandregal @ockham @rafaelgallani @ramonjd @richtabor @rithik56 @rohitmathur-7 @ryanwelcher @SantosGuillamot @scruffian @sgomes @shail-mehta @spacedmonkey @stokesman @swissspidy @t-hamano @talldan @tjcafferkey @tyxla + + += 19.1.0 = + +## Changelog + +### Enhancements + +#### Components +- Allow `style` prop on `Popover`. ([64489](https://github.com/WordPress/gutenberg/pull/64489)) +- Add elevation scale. ([64108](https://github.com/WordPress/gutenberg/pull/64108)) +- Apply elevation scale to: Modal, Popover, and Snackbar components. ([64655](https://github.com/WordPress/gutenberg/pull/64655)) +- Ariakit: Update to v0.4.10. ([64637](https://github.com/WordPress/gutenberg/pull/64637)) +- DimensionControl: Add flag to remove bottom margin. ([64346](https://github.com/WordPress/gutenberg/pull/64346)) +- DropdownMenu V2: Use themed color variables. ([64647](https://github.com/WordPress/gutenberg/pull/64647)) +- Placeholders: Update radius temporarily. ([64672](https://github.com/WordPress/gutenberg/pull/64672)) +- Reduce gap between steps in SpacingSizesControl, add animation, remove first/last marks. ([63803](https://github.com/WordPress/gutenberg/pull/63803)) +- Textarea Control: Update styles. ([64586](https://github.com/WordPress/gutenberg/pull/64586)) +- Tools Panel: Sets column-gap to 16px for grid. ([64497](https://github.com/WordPress/gutenberg/pull/64497)) +- Update DropdownMenuV2 elevation, remove unused configuration value. ([64432](https://github.com/WordPress/gutenberg/pull/64432)) +- Update components radius. ([64368](https://github.com/WordPress/gutenberg/pull/64368)) +- Use `useStoreState()` instead of `store.useState()`. ([64648](https://github.com/WordPress/gutenberg/pull/64648)) +- Composite: Use internal context to consume composite store. ([64493](https://github.com/WordPress/gutenberg/pull/64493)) +- Default to new 40px size in the following: + - FocalPointPicker: ([64456](https://github.com/WordPress/gutenberg/pull/64456)) + - QueryControls: ([64457](https://github.com/WordPress/gutenberg/pull/64457)) + +#### Data Views +- Do not display element descriptions in filters. ([64674](https://github.com/WordPress/gutenberg/pull/64674)) +- Apply minimal variant to pagination dropdown. ([63815](https://github.com/WordPress/gutenberg/pull/63815)) +- Update the style of the datetime fields to match the other types. ([64438](https://github.com/WordPress/gutenberg/pull/64438)) +- Use the fields array to define the order of the fields. ([64335](https://github.com/WordPress/gutenberg/pull/64335)) +- Make the move left/right controls in table header always available. ([64646](https://github.com/WordPress/gutenberg/pull/64646)) +- Support defining field headers/names as React elements. ([64642](https://github.com/WordPress/gutenberg/pull/64642)) +- Add marks to preview size control. ([64546](https://github.com/WordPress/gutenberg/pull/64546)) +- Move item size control to the new view configuration UI. ([64380](https://github.com/WordPress/gutenberg/pull/64380)) +- Update search appearance in narrow containers. ([64681](https://github.com/WordPress/gutenberg/pull/64681)) +- Quick edit additions: + - `comment_status` field. ([64370](https://github.com/WordPress/gutenberg/pull/64370)) + - `status` field. ([64398](https://github.com/WordPress/gutenberg/pull/64398)) + - 'Date' as field and `datetime` as field type. ([64267](https://github.com/WordPress/gutenberg/pull/64267)) +- Extensibility - allow unregistering of the following: + - Duplicate post action ([64441](https://github.com/WordPress/gutenberg/pull/64441)) + - Duplicate pattern action ([64373](https://github.com/WordPress/gutenberg/pull/64373)) + - Duplicate template part action ([64388](https://github.com/WordPress/gutenberg/pull/64388)) + - Rename post action ([64366](https://github.com/WordPress/gutenberg/pull/64366)) + - Reorder-page action ([64199](https://github.com/WordPress/gutenberg/pull/64199)) + - View post action ([64467](https://github.com/WordPress/gutenberg/pull/64467)) + - View post revisions action ([64464](https://github.com/WordPress/gutenberg/pull/64464)) +- Add missing styles and remove opinionated ones for generic usage. ([64711](https://github.com/WordPress/gutenberg/pull/64711)) + +#### Block Library +- Embed Block: Replace native input element with InputControl component. ([64668](https://github.com/WordPress/gutenberg/pull/64668)) +- Grid: Prevent highlight of cells when dragging a block if block type can't be dropped into grid. ([64290](https://github.com/WordPress/gutenberg/pull/64290)) +- Image block: Add reset button. ([64669](https://github.com/WordPress/gutenberg/pull/64669)) +- Overlay caption w. text-shadow. ([63471](https://github.com/WordPress/gutenberg/pull/63471)) + +#### Design Tools +- Background image: Add uploading state and restrict drag to one image. ([64565](https://github.com/WordPress/gutenberg/pull/64565)) +- Quote Block: Add align support. ([64188](https://github.com/WordPress/gutenberg/pull/64188)) +- Add border support to the following: + - Comment Author Name ([64550](https://github.com/WordPress/gutenberg/pull/64550)) + - Comment Content ([64230](https://github.com/WordPress/gutenberg/pull/64230)) + - Comment Date ([64210](https://github.com/WordPress/gutenberg/pull/64210)) + - Post Author Biography ([64615](https://github.com/WordPress/gutenberg/pull/64615)) + - Post Author Name ([64530](https://github.com/WordPress/gutenberg/pull/64530)) + - Post Author ([64599](https://github.com/WordPress/gutenberg/pull/64599)) + - Query Title ([64581](https://github.com/WordPress/gutenberg/pull/64581)) + - File: ([64509](https://github.com/WordPress/gutenberg/pull/64509)) + - List Item: ([63541](https://github.com/WordPress/gutenberg/pull/63541)) + - List: ([63540](https://github.com/WordPress/gutenberg/pull/63540)) + - Preformatted: ([64302](https://github.com/WordPress/gutenberg/pull/64302)) + - Tag Cloud: ([63579](https://github.com/WordPress/gutenberg/pull/63579)) + +#### Zoom Out +- Add private `isZoomOutMode` selector. ([64503](https://github.com/WordPress/gutenberg/pull/64503)) +- Block Insertion: Clear the insertion point when selecting a different block or clearing block selection. ([64048](https://github.com/WordPress/gutenberg/pull/64048)) +- Default the inserter to the patterns tab when in zoom out. ([64193](https://github.com/WordPress/gutenberg/pull/64193)) +- Focus pattern inserter search when activating zoom out inserter. ([64396](https://github.com/WordPress/gutenberg/pull/64396)) +- Stop unwanted drag and drop operations within section Patterns in Zoom Out mode. ([64331](https://github.com/WordPress/gutenberg/pull/64331)) + +#### Block Editor +- Button groups in Typography tools should use ToggleGroupControl. ([64529](https://github.com/WordPress/gutenberg/pull/64529)) +- Hyphenate long block names in the inserter. ([64667](https://github.com/WordPress/gutenberg/pull/64667)) + +#### Global Styles +- Additional CSS: Localize the link if it exists. ([64603](https://github.com/WordPress/gutenberg/pull/64603)) +- Background images: Add support for theme.json ref value resolution. ([64128](https://github.com/WordPress/gutenberg/pull/64128)) + + +### New APIs + +#### Components +- Composite + - Add Hover and Typeahead subcomponents. ([64399](https://github.com/WordPress/gutenberg/pull/64399)) + - Stabilize new ariakit implementation. ([63564](https://github.com/WordPress/gutenberg/pull/63564)) + - Export `useCompositeStore`, add more focus-related props. ([64450](https://github.com/WordPress/gutenberg/pull/64450)) + +#### Synced Patterns +- Block Bindings: Create utils to update or remove bindings. ([64102](https://github.com/WordPress/gutenberg/pull/64102)) + +#### Extensibility +- Add plugin template registration API. ([61577](https://github.com/WordPress/gutenberg/pull/61577)) + + +### Bug Fixes + +#### Components +- CustomSelectControl: Improve props type inferring. ([64412](https://github.com/WordPress/gutenberg/pull/64412)) +- ColorPalette: Partial support of `color-mix()` CSS colors. ([64224](https://github.com/WordPress/gutenberg/pull/64224)) +- RangeControl: Disable reset button consistently. ([64579](https://github.com/WordPress/gutenberg/pull/64579)) +- RangeControl: Tweak mark and label absolute positioning. ([64487](https://github.com/WordPress/gutenberg/pull/64487)) + +#### Data Views +- Load the filter toggle as open if there are primary filters. ([64651](https://github.com/WordPress/gutenberg/pull/64651)) +- Sort descending button may be wrongly pressed. ([64547](https://github.com/WordPress/gutenberg/pull/64547)) +- Filter icon is displayed even when no filter capabilities are given to any field. ([64640](https://github.com/WordPress/gutenberg/pull/64640)) +- Hide sort direction control if there are no sortable fields. ([64817](https://github.com/WordPress/gutenberg/pull/64817)) + +#### Zoom Out +- Disallow dropping outside section root in Zoom Out mode. ([64500](https://github.com/WordPress/gutenberg/pull/64500)) +- Don't hide the insertion point when hovering patterns. ([64392](https://github.com/WordPress/gutenberg/pull/64392)) +- Use previous device width for scale calculations. ([64478](https://github.com/WordPress/gutenberg/pull/64478)) + +#### Block Library +- Embed blocks: Adding captions via toolbar - #64385. ([64394](https://github.com/WordPress/gutenberg/pull/64394)) +- Paste: Fix blob uploading. ([64479](https://github.com/WordPress/gutenberg/pull/64479)) +- Table Block: Hide caption toolbar button on multiple selection. ([64462](https://github.com/WordPress/gutenberg/pull/64462)) + +#### Post Editor +- Fix user pattern preloading filter. ([64477](https://github.com/WordPress/gutenberg/pull/64477)) +- Fix preloaded REST API paths. ([64459](https://github.com/WordPress/gutenberg/pull/64459)) +- Force iframe editor when zoom-out mode. ([64316](https://github.com/WordPress/gutenberg/pull/64316)) + +#### Block Editor +- Don't hide the toolbar for an empty default block in HTML mode. ([64374](https://github.com/WordPress/gutenberg/pull/64374)) +- In-between Inserter: Show inserter when it doesn't conflict with block toolbar. ([64229](https://github.com/WordPress/gutenberg/pull/64229)) +- Slash Inserter: Restrict block list to allowed blocks only. ([64413](https://github.com/WordPress/gutenberg/pull/64413)) + +#### Site Editor +- Don't allow duplicating template parts in non-block-based themes. ([64379](https://github.com/WordPress/gutenberg/pull/64379)) +- Fix Template Parts post type preload path. ([64401](https://github.com/WordPress/gutenberg/pull/64401)) +- Cancel button in duplicate template part modal doesn't work. ([64377](https://github.com/WordPress/gutenberg/pull/64377)) +- Fix empty content sidebar panel. ([64569](https://github.com/WordPress/gutenberg/pull/64569)) + +#### Block bindings +- Fix long keys overflow in bindings panel. ([64465](https://github.com/WordPress/gutenberg/pull/64465)) +- Hide keys starting with underscore. ([64618](https://github.com/WordPress/gutenberg/pull/64618)) +- Refactor utils file. ([64740](https://github.com/WordPress/gutenberg/pull/64740)) + +#### CSS & Styling +- Remove inconsistent dark theme focus style on block selection. ([64549](https://github.com/WordPress/gutenberg/pull/64549)) +- Update postcss-prefixwrap dependency to 1.51.0 to fix prefixing in `:Where` selectors. ([64458](https://github.com/WordPress/gutenberg/pull/64458)) + +#### Interactivity API +- Fix context inheritance from namespaces different than the current one. ([64677](https://github.com/WordPress/gutenberg/pull/64677)) +- Fix computeds without scope in Firefox. ([64825](https://github.com/WordPress/gutenberg/pull/64825)) + +#### Document Settings +- Post Featured Image: Disable the media modal while uploading an image. ([64566](https://github.com/WordPress/gutenberg/pull/64566)) + +#### Patterns +- Changing sorting direction on patterns does nothing. ([64508](https://github.com/WordPress/gutenberg/pull/64508)) + +#### Design Tools +- Background image: Ensure consistency with defaults and fix reset/remove functionality. ([64328](https://github.com/WordPress/gutenberg/pull/64328)) + +#### Global Styles +- Fix bumped specificity for layout styles in non-iframed editor. ([64076](https://github.com/WordPress/gutenberg/pull/64076)) + + +### Accessibility + +- Site Editor: Always use auto-cursor style for editable text. ([64627](https://github.com/WordPress/gutenberg/pull/64627)) +- Post Editor: Update textControl to searchControl in taxonomy search. ([64605](https://github.com/WordPress/gutenberg/pull/64605)) +- RadioControl: Label radio group using fieldset and legend. ([64582](https://github.com/WordPress/gutenberg/pull/64582)) +- Fix labeling in Typography font size presets panel. ([64428](https://github.com/WordPress/gutenberg/pull/64428)) +- Latests Posts: Used ToggleGroupControl instead for Image alignment. ([64352](https://github.com/WordPress/gutenberg/pull/64352)) + + +### Performance + +- Fetch permissions for visible patterns only. ([64606](https://github.com/WordPress/gutenberg/pull/64606)) +- Background Image: Remove unnecessary 'block-editor' store subscription. ([64568](https://github.com/WordPress/gutenberg/pull/64568)) +- Edit Post: Avoid unnecessary post-template ID lookup. ([64431](https://github.com/WordPress/gutenberg/pull/64431)) +- GridVisualizer: Avoid over-selecting by using a new getBlockStyles private selector. ([64386](https://github.com/WordPress/gutenberg/pull/64386)) + + +### Experiments + +#### Data Views +- DataViews Quick Edit + - Add Post Card to the quick edit panel. ([64365](https://github.com/WordPress/gutenberg/pull/64365)) + - Add the PostActions dropdown menu. ([64393](https://github.com/WordPress/gutenberg/pull/64393)) + - Rely on the global save flow instead of a custom save button. ([64389](https://github.com/WordPress/gutenberg/pull/64389)) +- Update the copy of quick edit tooltip. ([64475](https://github.com/WordPress/gutenberg/pull/64475)) + +#### Components +- Composite v2: Undo stabilizing new version. ([64510](https://github.com/WordPress/gutenberg/pull/64510)) + + +### Documentation + +- Add clarification about importing css/scss files. ([61252](https://github.com/WordPress/gutenberg/pull/61252)) +- Components + - Add "Naming conventions" section. ([63714](https://github.com/WordPress/gutenberg/pull/63714)) + - Add 40px size prop to readmes. ([64592](https://github.com/WordPress/gutenberg/pull/64592)) +- Composite: Improve Storybook examples and clean up prop documentation. ([64397](https://github.com/WordPress/gutenberg/pull/64397)) +- Dataviews + - Added missing properties for actions object and link to storybook example. ([64442](https://github.com/WordPress/gutenberg/pull/64442)) + - Fixed tip link for block editor view. ([64469](https://github.com/WordPress/gutenberg/pull/64469)) + - Update README with missing properties and recent changes. ([64435](https://github.com/WordPress/gutenberg/pull/64435)) + - Better explanation of the "elements" property and its connection to the "filterBy" property. ([64633](https://github.com/WordPress/gutenberg/pull/64633)) +- Interactivity API + - The first three Core Concepts guides. ([63759](https://github.com/WordPress/gutenberg/pull/63759)) + - Fix internal links core-concepts. ([64609](https://github.com/WordPress/gutenberg/pull/64609)) + - Remove typed function from API reference. ([64429](https://github.com/WordPress/gutenberg/pull/64429)) + - Add code concepts to Navigating the Interactivity API documentation. ([64608](https://github.com/WordPress/gutenberg/pull/64608)) + - Interactivity API: Add wp_interactivity_state() clarification. ([64356](https://github.com/WordPress/gutenberg/pull/64356)) +- Fix typos in the Block Filters documentation.. ([64426](https://github.com/WordPress/gutenberg/pull/64426)) +- Fix example of useBlockProps hook. ([64363](https://github.com/WordPress/gutenberg/pull/64363)) +- Fix typo and link in static-dynamic-rendering.md. ([64449](https://github.com/WordPress/gutenberg/pull/64449)) +- Fix typo in block-filters.md. ([64452](https://github.com/WordPress/gutenberg/pull/64452)) +- Fix typo in block-wrapper.md. ([64447](https://github.com/WordPress/gutenberg/pull/64447)) +- Note about image sizes in MediaUpload::OnSelect. ([64616](https://github.com/WordPress/gutenberg/pull/64616)) +- Small typo correction in doc file. ([64596](https://github.com/WordPress/gutenberg/pull/64596)) +- TextDecorationControl, TextTransformControl: Remove size prop in Storybook. ([64583](https://github.com/WordPress/gutenberg/pull/64583)) +- Updated `@since` order in Inline document in client-assets.php file. ([64653](https://github.com/WordPress/gutenberg/pull/64653)) +- Updated small typo in compat.php file. ([64535](https://github.com/WordPress/gutenberg/pull/64535)) +- Updated small typo in modularity.md. ([64518](https://github.com/WordPress/gutenberg/pull/64518)) + + +### Code Quality + +- Add lint rule for 40px size prop usage in the following: + - BorderBoxControl, BorderControl, DimensionControl, FontSizePicker: ([64410](https://github.com/WordPress/gutenberg/pull/64410)) + - Block Editor typography components ([64591](https://github.com/WordPress/gutenberg/pull/64591)) + - FormFileUpload: ([64585](https://github.com/WordPress/gutenberg/pull/64585)) + - FormTokenField: ([64590](https://github.com/WordPress/gutenberg/pull/64590)) + - InputControl: ([64589](https://github.com/WordPress/gutenberg/pull/64589)) + - NumberControl: ([64561](https://github.com/WordPress/gutenberg/pull/64561)) + - RangeControl: ([64558](https://github.com/WordPress/gutenberg/pull/64558)) + - SelectControl: ([64486](https://github.com/WordPress/gutenberg/pull/64486)) + - TextControl: ([64455](https://github.com/WordPress/gutenberg/pull/64455)) + - ToggleGroupControl: ([64524](https://github.com/WordPress/gutenberg/pull/64524)) + - ComboboxControl: ([64560](https://github.com/WordPress/gutenberg/pull/64560)) + - CustomSelectControl: ([64559](https://github.com/WordPress/gutenberg/pull/64559)) +- Add margin-bottom lint rules for BaseControl. ([64355](https://github.com/WordPress/gutenberg/pull/64355)) +- Add missing changes to the changelog for the PR #62734. ([64507](https://github.com/WordPress/gutenberg/pull/64507)) +- Base Styles: Restore deprecated `$dark-theme-focus` variable. ([64563](https://github.com/WordPress/gutenberg/pull/64563)) +- ESLint: Enable and enforce remaining i18n rules for the plugin (e.g. no trailing spaces). ([60196](https://github.com/WordPress/gutenberg/pull/60196)) +- Remove unnecessary className. ([64403](https://github.com/WordPress/gutenberg/pull/64403)) +- Replace instances of deprecated elevation variables. ([64656](https://github.com/WordPress/gutenberg/pull/64656)) +- Style engine: Export util to compile CSS custom var from preset string. ([64490](https://github.com/WordPress/gutenberg/pull/64490)) +- Style engine: Update type for getCSSValueFromRawStyle. ([64528](https://github.com/WordPress/gutenberg/pull/64528)) +- TextControl: Fix remaining 40px size violations. ([64594](https://github.com/WordPress/gutenberg/pull/64594)) +- Border: 1px ā†’ $border-width. ([64680](https://github.com/WordPress/gutenberg/pull/64680)) + +#### Block Library +- Gallery: Remove 'withNotices' HoC. ([64384](https://github.com/WordPress/gutenberg/pull/64384)) +- Missing Block: Use hooks instead of HoC. ([64657](https://github.com/WordPress/gutenberg/pull/64657)) + +#### Block Editor +- Use hooks instead of HoC in: + - 'BlockModeToggle'. ([64460](https://github.com/WordPress/gutenberg/pull/64460)) + - 'MultiSelectionInspector'. ([64634](https://github.com/WordPress/gutenberg/pull/64634)) + +#### Components +- Deprecate bottom margin on BaseControl-based components. ([64408](https://github.com/WordPress/gutenberg/pull/64408)) +- Navigator: Simplify backwards navigation APIs. ([63317](https://github.com/WordPress/gutenberg/pull/63317)) + +#### Data Views +- Refactor the edit function to be based on discrete controls. ([64404](https://github.com/WordPress/gutenberg/pull/64404)) +- Update `renderFormElements` to make sure the value respects the type. ([64391](https://github.com/WordPress/gutenberg/pull/64391)) +- Abandon the ItemRecord type. ([64367](https://github.com/WordPress/gutenberg/pull/64367)) + +#### Block hooks +- Navigation Block: Remove now-obsolete function_exists guards. ([64673](https://github.com/WordPress/gutenberg/pull/64673)) + +#### Nested / Inner Blocks +- Block Editor: Refactor inner blocks appender components. ([64470](https://github.com/WordPress/gutenberg/pull/64470)) + +#### Plugin +- Script Modules: Move data passing to 6.7 compat file. ([64006](https://github.com/WordPress/gutenberg/pull/64006)) + + +### Tools + +- Make wp-env compatible with WordPress versions older than 5.4 by fixing wp-config anchors. ([55864](https://github.com/WordPress/gutenberg/pull/55864)) + +#### Testing +- Background block supports: Remove unused properties in unit tests. ([64564](https://github.com/WordPress/gutenberg/pull/64564)) +- Fix flaky block template registration end-to-end test. ([64541](https://github.com/WordPress/gutenberg/pull/64541)) +- Improve Image block end-to-end tests. ([64537](https://github.com/WordPress/gutenberg/pull/64537)) +- Upgrade Playwright to v1.46. ([64372](https://github.com/WordPress/gutenberg/pull/64372)) + +#### Build Tooling +- Fix gutenberg/gutenberg-coding-standards licensing issues. ([61913](https://github.com/WordPress/gutenberg/pull/61913)) +- Props Bot: Update to correct event type. ([64557](https://github.com/WordPress/gutenberg/pull/64557)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @cweiske: Note about image sizes in MediaUpload::OnSelect. ([64616](https://github.com/WordPress/gutenberg/pull/64616)) +- @imrraaj: Dataviews: Filter icon is displayed even when no filter capabilities are given to any field. ([64640](https://github.com/WordPress/gutenberg/pull/64640)) +- @janpfeil: Fix typo in block-filters.md. ([64452](https://github.com/WordPress/gutenberg/pull/64452)) +- @Rishit30G: `ColorPalette`: Partial support of `color-mix()` CSS colors. ([64224](https://github.com/WordPress/gutenberg/pull/64224)) +- @ssang: Slash Inserter: Restrict block list to allowed blocks only. ([64413](https://github.com/WordPress/gutenberg/pull/64413)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @akasunil @Aljullu @amitraj2203 @anton-vlasenko @arthur791004 @cbravobernal @ciampo @colorful-tones @cweiske @DAreRodz @ellatrix @felixarntz @getdave @hbhalodia @imrraaj @jameskoster @janpfeil @jasmussen @jeherve @jorgefilipecosta @jsnajdr @juanmaguitar @luisherranz @Mamaduka @meteorlxy @mirka @ndiego @noisysocks @ntsekouras @oandregal @ockham @ramonjd @richtabor @Rishit30G @SantosGuillamot @scruffian @shail-mehta @shreya0204 @sirreal @ssang @swissspidy @t-hamano @talldan @tyxla @vipul0425 @youknowriad + + += 19.0.0 = + +## Changelog + +### Enhancements + +- Add alt edit field to the inline image in the format library ([64124](https://github.com/WordPress/gutenberg/pull/64124)) +- Update copy from "No Title" to "No title" across multiple places on the editor. ([64184](https://github.com/WordPress/gutenberg/pull/64184)) +- Update column input to be 40px by default. ([64190](https://github.com/WordPress/gutenberg/pull/64190)) + +#### Block Library +- Add anchor block support to List Items. ([48758](https://github.com/WordPress/gutenberg/pull/48758)) +- Unset the rowStart and columnStart attributes when a block inside the Grid is removed from a manual layout. ([64186](https://github.com/WordPress/gutenberg/pull/64186)) +- Update Group block example. ([63114](https://github.com/WordPress/gutenberg/pull/63114)) +- Make SiteLogoReplaceFlow always available in the Site Logo block toolbar. ([63499](https://github.com/WordPress/gutenberg/pull/63499)) +- Make Query Loop settings more intuitive with a ToggleGroup and simplified help text. ([63739](https://github.com/WordPress/gutenberg/pull/63739)) +- Move gallery link controls to the block toolbar. ([62762](https://github.com/WordPress/gutenberg/pull/62762)) +- Hide loading when the overlay menu is selected. ([64262](https://github.com/WordPress/gutenberg/pull/64262)) +- Move the Site Logo tooltip to the middle right. ([64296](https://github.com/WordPress/gutenberg/pull/64296)) +- Prevent duplicate spacing on Tag Cloud block. ([63832](https://github.com/WordPress/gutenberg/pull/63832)) +- Fix 'can user edit' Template Part check. ([64137](https://github.com/WordPress/gutenberg/pull/64137)) +- Add clearfix in Post content. ([63690](https://github.com/WordPress/gutenberg/pull/63690)) +- Tweak Tag Cloud controls and description. ([64151](https://github.com/WordPress/gutenberg/pull/64151)) +- Tweak list block. ([64025](https://github.com/WordPress/gutenberg/pull/64025)) +- Update MediaUpload button for the site logo from "Add media" to "Choose logo". ([63498](https://github.com/WordPress/gutenberg/pull/63498)) +- Update help text for sticky control in Query loop. ([63999](https://github.com/WordPress/gutenberg/pull/63999)) +- Add border support to the following blocks: + - [Time To Read](https://github.com/WordPress/gutenberg/pull/63776) + - [Categories List](https://github.com/WordPress/gutenberg/pull/63950) + - [Post Date](https://github.com/WordPress/gutenberg/pull/64023) + - [Post Excerpt](https://github.com/WordPress/gutenberg/pull/64022) + - [Post Terms](https://github.com/WordPress/gutenberg/pull/64246) + - [Post Title](https://github.com/WordPress/gutenberg/pull/64024) + - [Site Tagline](https://github.com/WordPress/gutenberg/pull/63778) + - [Site Title](https://github.com/WordPress/gutenberg/pull/63631) + - [Table of contents](https://github.com/WordPress/gutenberg/pull/63578) + +#### Extensibility +- Add an async `__unstablePreSavePost` hook; resolving with false prevents saving. ([58022](https://github.com/WordPress/gutenberg/pull/58022)) +- Enable heading level curation. ([63535](https://github.com/WordPress/gutenberg/pull/63535)) +- Addition of `levelOptions` attribute to control available heading levels in [Post Title](https://github.com/WordPress/gutenberg/pull/64106), [Query Title](https://github.com/WordPress/gutenberg/pull/64107), [Site Tagline](https://github.com/WordPress/gutenberg/pull/64113), [Site Title](https://github.com/WordPress/gutenberg/pull/64111), and [Comments Title](https://github.com/WordPress/gutenberg/pull/64103). + +#### Data Views +- Be more clear with the copy of the "hide" action. ([63047](https://github.com/WordPress/gutenberg/pull/63047)) +- Graduate data view options out of a menu to allow more design expression. ([64175](https://github.com/WordPress/gutenberg/pull/64175)) +- Move filter UI into a toggle-able panel to improve experience on narrow viewports/containers. ([63203](https://github.com/WordPress/gutenberg/pull/63203)) +- Update field line-height across grid/list layouts. ([63945](https://github.com/WordPress/gutenberg/pull/63945)) +- Update template description in table layout. ([63942](https://github.com/WordPress/gutenberg/pull/63942)) +- De-emphasise bulk actions on Grid layout. ([64209](https://github.com/WordPress/gutenberg/pull/64209)) +- Update the copy of some of the strings on dataviews actions. ([64099](https://github.com/WordPress/gutenberg/pull/64099)) + +##### Dataviews Extensibility + +- Allow unregistering of the following post actions: [permanently delete](https://github.com/WordPress/gutenberg/pull/64088), [restore post](https://github.com/WordPress/gutenberg/pull/64134), and [trash post](https://github.com/WordPress/gutenberg/pull/64087). + +#### Dataform + +- Add author to quick edit page/post list. ([63983](https://github.com/WordPress/gutenberg/pull/63983)) +- If a field of type `text` declare `elements`, render it as a `SelectControl` in `edit`. ([64251](https://github.com/WordPress/gutenberg/pull/64251)) +- Migrate order action modal and introduce form validation. ([63895](https://github.com/WordPress/gutenberg/pull/63895)) + + + +#### Components +- Add radius scale. ([64007](https://github.com/WordPress/gutenberg/pull/64007)) +- Support generic props type on CustomSelectControl. ([63985](https://github.com/WordPress/gutenberg/pull/63985)) +- Guide: Add __next40pxDefaultSize to buttons. ([64181](https://github.com/WordPress/gutenberg/pull/64181)) +- Image: Make Placeholder white when there is a on top. ([63885](https://github.com/WordPress/gutenberg/pull/63885)) +- SelectControl: Infer `value` type from `options`. ([64069](https://github.com/WordPress/gutenberg/pull/64069)) +- SelectControl: Pass through `options` props. ([64211](https://github.com/WordPress/gutenberg/pull/64211)) +- TimeInput: Expose as subcomponent of TimePicker. ([63145](https://github.com/WordPress/gutenberg/pull/63145)) +- Update radius variables in components configuration. ([64133](https://github.com/WordPress/gutenberg/pull/64133)) +- `RadioControl`: Add support for option help text. ([63751](https://github.com/WordPress/gutenberg/pull/63751)) + +#### Block Editor +- Block Autocompleter: Force icon color to text color when item is selected. ([61376](https://github.com/WordPress/gutenberg/pull/61376)) +- Don't overlap canvas with inserter panel at large screens. ([64110](https://github.com/WordPress/gutenberg/pull/64110)) +- Format Library: Polish inline image format popover. ([64016](https://github.com/WordPress/gutenberg/pull/64016)) +- LineHeightControl: Hard deprecate bottom margin. ([64281](https://github.com/WordPress/gutenberg/pull/64281)) +- New useBlockElementRef hook for storing block element into a ref. ([63799](https://github.com/WordPress/gutenberg/pull/63799)) +- Improved tabbed sidebar styles. ([61974](https://github.com/WordPress/gutenberg/pull/61974)) +- URLInput: Hard deprecate bottom margin. ([64282](https://github.com/WordPress/gutenberg/pull/64282)) + +#### Global Styles +- Add a typesets section to Typography. ([62539](https://github.com/WordPress/gutenberg/pull/62539)) +- Add tooltips to the heading level selectors. ([64039](https://github.com/WordPress/gutenberg/pull/64039)) +- Background images: Ensure appropriate default values. ([64192](https://github.com/WordPress/gutenberg/pull/64192)) +- Create new public function to make it easier to expose style variations from other themes. ([63318](https://github.com/WordPress/gutenberg/pull/63318)) +- Style Book: Clearly denote heading levels. ([64038](https://github.com/WordPress/gutenberg/pull/64038)) + +#### Design Tools +- Column: Enable border radius support. ([63924](https://github.com/WordPress/gutenberg/pull/63924)) +- Comment Template: Add Border Block Support. ([64238](https://github.com/WordPress/gutenberg/pull/64238)) +- Post Comments Form: Add Border Block Support. ([64233](https://github.com/WordPress/gutenberg/pull/64233)) + +#### Zoom Out +- Add a control to enter and leave zoom out mode. ([63870](https://github.com/WordPress/gutenberg/pull/63870)) +- Improve zoom transition. ([64179](https://github.com/WordPress/gutenberg/pull/64179)) + +#### Site Editor +- Clarify that the site icon is a back button using an animation. ([63986](https://github.com/WordPress/gutenberg/pull/63986)) +- Site Icon: Add back filter effect to make it work for all kind of site icons. ([64172](https://github.com/WordPress/gutenberg/pull/64172)) + +#### Post Editor +- Tweak Create custom template modal. ([64255](https://github.com/WordPress/gutenberg/pull/64255)) + +#### Icons +- Add new "send" icon. ([64130](https://github.com/WordPress/gutenberg/pull/64130)) + +#### Plugin +- Bump minimum required WordPress version to 6.5. ([64126](https://github.com/WordPress/gutenberg/pull/64126)) + +#### Font Library +- Include a "Select All" options for google fonts. ([63893](https://github.com/WordPress/gutenberg/pull/63893)) + +#### Block bindings +- Allow bindings bootstrap after registration. ([63797](https://github.com/WordPress/gutenberg/pull/63797)) + +#### Interactivity API +- Refactor internal proxy and signals system. ([62734](https://github.com/WordPress/gutenberg/pull/62734)) + + +### New APIs + +- Make useStyleOverride public. ([63656](https://github.com/WordPress/gutenberg/pull/63656)) + + +### Bug Fixes + +- Core Data: Fix 'getEntityRecordPermissions' memoization. ([64091](https://github.com/WordPress/gutenberg/pull/64091)) +- Document bar: Fix long title with no spaces causing layout issue. ([64092](https://github.com/WordPress/gutenberg/pull/64092)) +- Fix density slider minus to be correct. ([64185](https://github.com/WordPress/gutenberg/pull/64185)) +- Fix: Deleting a pattern throws a notice saying undefined deleted. ([64301](https://github.com/WordPress/gutenberg/pull/64301)) +- Primitives: Add missing peer dependency. ([64218](https://github.com/WordPress/gutenberg/pull/64218)) +- Site Icon: Fix position in distraction free mode. ([64261](https://github.com/WordPress/gutenberg/pull/64261)) + +#### Data Views +- Add context to trash string. ([64249](https://github.com/WordPress/gutenberg/pull/64249)) +- Conditionally shows the description field in Template Grid layout. ([64043](https://github.com/WordPress/gutenberg/pull/64043)) +- Consider layout URL parameter when loading a default/custom view. ([64306](https://github.com/WordPress/gutenberg/pull/64306)) +- Display published date for pages/posts with published status. ([64049](https://github.com/WordPress/gutenberg/pull/64049)) +- Sort author by name + allow custom sort function. ([64064](https://github.com/WordPress/gutenberg/pull/64064)) +- Don't render action modal when there are no eligible items. ([64250](https://github.com/WordPress/gutenberg/pull/64250)) +- Pages: Update `useView` logic. ([63889](https://github.com/WordPress/gutenberg/pull/63889)) +- Update template preview dimensions in table layout. ([63938](https://github.com/WordPress/gutenberg/pull/63938)) +- Update template preview dimensions in table layout. ([63938](https://github.com/WordPress/gutenberg/pull/63938)) + +#### Dataform + +- Fix SelectControl size and spacing. ([64324](https://github.com/WordPress/gutenberg/pull/64324)) +- Provide a better default for render when field has elements. ([64338](https://github.com/WordPress/gutenberg/pull/64338)) + +#### Components +- Autocompleter UI: Fix text color when hovering selected item. ([64294](https://github.com/WordPress/gutenberg/pull/64294)) +- BaseControl: change label's display: Block. ([63911](https://github.com/WordPress/gutenberg/pull/63911)) +- Button: Fix tertiary destructive hover style. ([64152](https://github.com/WordPress/gutenberg/pull/64152)) +- ColorPalette: Remove extra bottom margin when `CircularOptionPicker` is unneeded. ([63961](https://github.com/WordPress/gutenberg/pull/63961)) +- DropdownMenuV2: Break menu item help text on multiple lines for better truncation. ([63916](https://github.com/WordPress/gutenberg/pull/63916)) +- Fix modal dismissers in development mode. ([64132](https://github.com/WordPress/gutenberg/pull/64132)) +- Fix toggle help indentation. ([63903](https://github.com/WordPress/gutenberg/pull/63903)) +- Update the TextControl padding to match the rest of the controls. ([64326](https://github.com/WordPress/gutenberg/pull/64326)) + +#### Global Styles +- Fix block custom CSS pseudo element selectors. ([63980](https://github.com/WordPress/gutenberg/pull/63980)) +- Fix block library and global styles stylesheet ordering when a block style variation is active. ([63918](https://github.com/WordPress/gutenberg/pull/63918)) +- Style Book: Fix critical error when heading block is not registered. ([64047](https://github.com/WordPress/gutenberg/pull/64047)) +- TypesetButton: Check if variations exist before running logic. ([64139](https://github.com/WordPress/gutenberg/pull/64139)) + +#### Site Editor +- Centrally align entity in focused edit mode. ([64143](https://github.com/WordPress/gutenberg/pull/64143)) +- Don't trigger template ID resolution for multi-selected posts. ([64254](https://github.com/WordPress/gutenberg/pull/64254)) +- Long slugs breaking summary panel UI. ([64053](https://github.com/WordPress/gutenberg/pull/64053)) + +#### Zoom Out +- Keep top-level block selection if entering zoom out mode. ([64178](https://github.com/WordPress/gutenberg/pull/64178)) +- Use the block editor for insertion point data. ([63934](https://github.com/WordPress/gutenberg/pull/63934)) + +#### Block Library +- Fix a typo in use-image-sizes.js. ([64100](https://github.com/WordPress/gutenberg/pull/64100)) +- Template Part: Fix capability checks for inner blocks. ([64159](https://github.com/WordPress/gutenberg/pull/64159)) +- Update useTaxonomies hook to check for taxonomies for passed post type. ([64145](https://github.com/WordPress/gutenberg/pull/64145)) + +#### Design Tools +- Quote: Prevent block theme styles overriding global border and padding. ([64045](https://github.com/WordPress/gutenberg/pull/64045)) +- Spacing controls: Using CustomSelectControlV2 for >= 8 spacing sizes. ([64284](https://github.com/WordPress/gutenberg/pull/64284)) + +#### Post Editor +- Avoid errors for post types without a 'menu_icon'. ([64015](https://github.com/WordPress/gutenberg/pull/64015)) +- Post: Add a max length to the post password protected field. ([64156](https://github.com/WordPress/gutenberg/pull/64156)) + +#### Grid layout +- Fix grid resizer drag over embed. ([64098](https://github.com/WordPress/gutenberg/pull/64098)) +- Move resizer popover slot to fix display on mobile. ([63920](https://github.com/WordPress/gutenberg/pull/63920)) + +#### Block Editor +- Fix unexpected drag & rrop row/gallery creation logic. ([64241](https://github.com/WordPress/gutenberg/pull/64241)) + +#### Icons +- Remove hardcoded color from sidesAxial and sidesBottom icons. ([64174](https://github.com/WordPress/gutenberg/pull/64174)) + +#### Document Settings +- Display empty option when post author is missing. ([64165](https://github.com/WordPress/gutenberg/pull/64165)) + +#### Patterns +- Enable cross-browser support for pattern uploading. ([64123](https://github.com/WordPress/gutenberg/pull/64123)) + +#### Commands +- Fix 'Preferences' and 'Shortcuts' commands in StrictMode. ([64019](https://github.com/WordPress/gutenberg/pull/64019)) + +#### Meta Boxes +- Prevent popover from being hidden by metaboxes. ([63939](https://github.com/WordPress/gutenberg/pull/63939)) + +#### Page Content Focus +- TemplateContentPanel: Don't show content blocks that are in a Query Loop. ([63732](https://github.com/WordPress/gutenberg/pull/63732)) + +#### Font Library +- Fix item font family item height in the sidebar. ([63125](https://github.com/WordPress/gutenberg/pull/63125)) + +#### Block API +- Block categories - ensure that categories are unique by slug. ([62954](https://github.com/WordPress/gutenberg/pull/62954)) + + +### Accessibility + +- Restore focus style in dataviews grid view. ([64298](https://github.com/WordPress/gutenberg/pull/64298)) +- A11y text for site editor. ([62648](https://github.com/WordPress/gutenberg/pull/62648)) +- Accessibility issue of device preview options. ([63958](https://github.com/WordPress/gutenberg/pull/63958)) + +#### Components +- Improve the aria-disabled focus style of the Button. ([62480](https://github.com/WordPress/gutenberg/pull/62480)) +- Restore `describedBy` functionality on CustomSelectControl. ([63957](https://github.com/WordPress/gutenberg/pull/63957)) + +#### Block Library +- Fix unlabeled Spacer block controls. ([63806](https://github.com/WordPress/gutenberg/pull/63806)) +- Move Posts Per Page, Offset, and Pages controls from the block toolbar into Inspector Controls. ([58207](https://github.com/WordPress/gutenberg/pull/58207)) + +#### Font Library +- Remove notice context and add message when fonts are updated. ([64030](https://github.com/WordPress/gutenberg/pull/64030)) + + +### Performance + +- Add User Timings for the Interactivity API. ([60522](https://github.com/WordPress/gutenberg/pull/60522)) + +#### Data Views +- Optimize the patterns dataviews by extracting the fields definition. ([63927](https://github.com/WordPress/gutenberg/pull/63927)) + +#### Layout +- Avoid iterating auto grid inner blocks unless mode specifically changed. ([64194](https://github.com/WordPress/gutenberg/pull/64194)) + +#### Block bindings +- Move logic to merge `usesContext` outside the reducer. ([63941](https://github.com/WordPress/gutenberg/pull/63941)) + + +### Experiments + +- Adds experimental blocks flag. ([64121](https://github.com/WordPress/gutenberg/pull/64121)) + +#### DataForm +- Support multiple layouts and introduce the panel layout. ([64299](https://github.com/WordPress/gutenberg/pull/64299)) + +#### DataViews Extensibility +- Add a hook to allow third-party scripts to register/unregister post type actions. ([64138](https://github.com/WordPress/gutenberg/pull/64138)) + +#### Grid Interactivity +- Fix block mover layout and styles. ([64021](https://github.com/WordPress/gutenberg/pull/64021)) + +#### Block bindings +- UI for connecting bindings. ([62880](https://github.com/WordPress/gutenberg/pull/62880)) + + +### Documentation + +- .wp-env.json schema: Fix schema and add unit tests. ([63281](https://github.com/WordPress/gutenberg/pull/63281)) +- Add WP Studio to list of tools in documentation. ([64327](https://github.com/WordPress/gutenberg/pull/64327)) +- Add documentation for some dynamically generated selectors in the core-data store. ([64269](https://github.com/WordPress/gutenberg/pull/64269)) +- Block Editor: Update 'getBlocksByName' JSDoc. ([63919](https://github.com/WordPress/gutenberg/pull/63919)) +- Components: Add missing `__nextHasNoMarginBottom` documentation. ([64313](https://github.com/WordPress/gutenberg/pull/64313)) +- Corrected @deprecated doc Order in Inline Documentation. ([64013](https://github.com/WordPress/gutenberg/pull/64013)) +- Add documentation for `render_block` and `register_block_type_args` to Block Filters. ([64118](https://github.com/WordPress/gutenberg/pull/64118)) +- Fix interactivity API documentation link. ([64060](https://github.com/WordPress/gutenberg/pull/64060)) +- Fix non working link to an interactivity API example block. ([64061](https://github.com/WordPress/gutenberg/pull/64061)) +- Fix WampServer links. ([64062](https://github.com/WordPress/gutenberg/pull/64062)) +- FormToggle, ToggleControl: Fix docgen in Storybook. ([64065](https://github.com/WordPress/gutenberg/pull/64065)) +- Provide a better example for the PluginSidebar slotfill. ([64206](https://github.com/WordPress/gutenberg/pull/64206)) +- Update data-core.md to use correct headings. ([64309](https://github.com/WordPress/gutenberg/pull/64309)) + + +### Code Quality + +- Add margin-bottom lint rules ([64212](https://github.com/WordPress/gutenberg/pull/64212)),([64213](https://github.com/WordPress/gutenberg/pull/64213)) and ([63960](https://github.com/WordPress/gutenberg/pull/63960)) +- Add new useEntityRecordsWithPermissions hook. ([63857](https://github.com/WordPress/gutenberg/pull/63857)) +- Fix deprecated sass usage. ([63990](https://github.com/WordPress/gutenberg/pull/63990)) +- Remove an unnecessary wrapper component. ([63989](https://github.com/WordPress/gutenberg/pull/63989)) +- Theme JSON: Update core theme json resolver class use to Gutenberg version. ([63981](https://github.com/WordPress/gutenberg/pull/63981)) +- Zoom out: Get store action outside the loop. ([63936](https://github.com/WordPress/gutenberg/pull/63936)) +- Remove Speak from device menu selection. ([64115](https://github.com/WordPress/gutenberg/pull/64115)) + +#### Block Editor +- BlockDraggable: Remove invalid aria-hidden attribute from button. ([64228](https://github.com/WordPress/gutenberg/pull/64228)) +- FontFamilyControl: Deprecate bottom margin. ([64280](https://github.com/WordPress/gutenberg/pull/64280)) +- Remove unnecessary/incorrect `unlock` call in `setEditorMode` action. ([64073](https://github.com/WordPress/gutenberg/pull/64073)) + +#### Data Views +- Formalize text field type definition. ([64168](https://github.com/WordPress/gutenberg/pull/64168)) +- Use items with permissions and avoid hooks to register actions. ([63923](https://github.com/WordPress/gutenberg/pull/63923)) + +#### DataForm +- Centralize edit logic in field type definitions. ([64171](https://github.com/WordPress/gutenberg/pull/64171)) +- Move validation logic to the field type definition. ([64164](https://github.com/WordPress/gutenberg/pull/64164)) + +#### Global Styles +- Background image: Remove toolspanel placeholder component. ([64242](https://github.com/WordPress/gutenberg/pull/64242)) +- Consolidate theme.json ref and URI resolution. ([64182](https://github.com/WordPress/gutenberg/pull/64182)) + +#### Plugin +- Remove compat layers for WP 6.4 and 6.5. ([64096](https://github.com/WordPress/gutenberg/pull/64096)) +- Remove leftover 'WP_Rest_Customizer_Nonces' controller. ([64221](https://github.com/WordPress/gutenberg/pull/64221)) + +#### Site Editor +- Use `structuredClone` for deep cloning. ([64203](https://github.com/WordPress/gutenberg/pull/64203)) + +#### Block Library +- Add stylelint rule to prevent usage of flex-direction reverse values. ([63081](https://github.com/WordPress/gutenberg/pull/63081)) +- Image Block Lightbox: Fix warning error when resizing. ([63995](https://github.com/WordPress/gutenberg/pull/63995)) + +#### Icons +- Fix invalid prop for `homeButton` icon. ([64191](https://github.com/WordPress/gutenberg/pull/64191)) + +#### Post Editor +- Remove resolvers hack for post actions. ([64094](https://github.com/WordPress/gutenberg/pull/64094)) + +#### Components +- Upgrade Ariakit. ([64066](https://github.com/WordPress/gutenberg/pull/64066)) + +#### Page Content Focus +- Fix the 'getBlocksByName' selector call. ([63922](https://github.com/WordPress/gutenberg/pull/63922)) + + +### Tools + +#### Testing +- Components: Cleanup flaky unit test `sleep()` hacks. ([64205](https://github.com/WordPress/gutenberg/pull/64205)) +- Fix flaky DataViews list layout end-to-end tests. ([64244](https://github.com/WordPress/gutenberg/pull/64244)) +- Fix typo in 'Verify Core Backport Changelog' job title. ([64058](https://github.com/WordPress/gutenberg/pull/64058)) +- Improve `Button` matrix in visual regression test. ([64120](https://github.com/WordPress/gutenberg/pull/64120)) +- Improve theme.json test failure messages by pretty printing css for a more accurate diff. ([64077](https://github.com/WordPress/gutenberg/pull/64077)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @Chrico: Block categories - ensure that categories are unique by slug. ([62954](https://github.com/WordPress/gutenberg/pull/62954)) +- @djcowan: Update api-reference.md. ([64325](https://github.com/WordPress/gutenberg/pull/64325)) +- @meteorlxy: CustomSelectControl: Support generic props type. ([63985](https://github.com/WordPress/gutenberg/pull/63985)) +- @Rishit30G: Add WP Studio to list of tools in documentation. ([64327](https://github.com/WordPress/gutenberg/pull/64327)) +- @wzieba: ([64044](https://github.com/WordPress/gutenberg/pull/64044)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @adamsilverstein @afercia @akasunil @Aljullu @amitraj2203 @andrewserong @carolinan @cbravobernal @Chrico @ciampo @creativecoder @DaniGuardiola @DAreRodz @djcowan @ellatrix @jameskoster @jasmussen @jeryj @jorgefilipecosta @jsnajdr @kebbet @kmanijak @Mamaduka @matiasbenedetto @meteorlxy @mikachan @mirka @mtias @ndiego @noisysocks @oandregal @ramonjd @richtabor @Rishit30G @ryanwelcher @SantosGuillamot @scruffian @shail-mehta @simison @stokesman @t-hamano @talldan @tomdevisser @tomjn @tyxla @up1512001 @wzieba @youknowriad + + + + += 18.9.0 = + +## Changelog + +### Enhancements + +#### Block Library +- Add Clear button for Overlay color option in Cover Block. ([63580](https://github.com/WordPress/gutenberg/pull/63580)) +- Embeds: Add 'Embed' to title for clarity. ([63371](https://github.com/WordPress/gutenberg/pull/63371)) +- Columns block: Fix block preview. ([63609](https://github.com/WordPress/gutenberg/pull/63609)) +- Gallery: Add border block support. ([63428](https://github.com/WordPress/gutenberg/pull/63428)) +- Image block: Show placeholder when uploading HEIC files. ([63643](https://github.com/WordPress/gutenberg/pull/63643)) +- Latest comments: Add color block support. ([63419](https://github.com/WordPress/gutenberg/pull/63419)) +- Media Text: Add border support. ([63542](https://github.com/WordPress/gutenberg/pull/63542)) +- Polish create template part modal. ([63617](https://github.com/WordPress/gutenberg/pull/63617)) +- Post Author blocks: Add example and preview. ([62978](https://github.com/WordPress/gutenberg/pull/62978)) +- Post date block: Add a block example. ([63368](https://github.com/WordPress/gutenberg/pull/63368)) +- Post featured image: Add example of the block. ([63011](https://github.com/WordPress/gutenberg/pull/63011)) +- Post terms block: Add an example. ([63369](https://github.com/WordPress/gutenberg/pull/63369)) +- Query Loop Block: Remove Posts List variation. ([63404](https://github.com/WordPress/gutenberg/pull/63404)) +- Query Loop block: Convert the post content type setting to a ToggleGroupControl if there are few items. ([63375](https://github.com/WordPress/gutenberg/pull/63375)) +- Query Loop: Change default query loop variations. ([63353](https://github.com/WordPress/gutenberg/pull/63353)) +- Set query loop to have the inherit value by default. ([63362](https://github.com/WordPress/gutenberg/pull/63362)) +- Social Links: Add border block support. ([63629](https://github.com/WordPress/gutenberg/pull/63629)) +- Social Links: Updated soundcloud icon for social link block. ([63504](https://github.com/WordPress/gutenberg/pull/63504)) +- Social Links: Update Facebook's color to match brand guidelines. ([60424](https://github.com/WordPress/gutenberg/pull/60424)) +- Term Description: Add border block support. ([63630](https://github.com/WordPress/gutenberg/pull/63630)) + +#### Design Tools +- Background Image: Make panel appear in a consistent location. ([63551](https://github.com/WordPress/gutenberg/pull/63551)) +- Buttons: Add border, color, and padding block supports. ([63538](https://github.com/WordPress/gutenberg/pull/63538)) +- Heading: Add border support. ([63539](https://github.com/WordPress/gutenberg/pull/63539)) +- Image: Adopt margin block support. ([63546](https://github.com/WordPress/gutenberg/pull/63546)) +- Paragraph: Add border support. ([63543](https://github.com/WordPress/gutenberg/pull/63543)) +- Quote: Add border support. ([63544](https://github.com/WordPress/gutenberg/pull/63544)) +- Quote: Add spacing supports. ([63545](https://github.com/WordPress/gutenberg/pull/63545)) +- Search: Add margin support. ([63547](https://github.com/WordPress/gutenberg/pull/63547)) + +#### Data Views +- DataViews: Allow column re-ordering. ([63416](https://github.com/WordPress/gutenberg/pull/63416)) +- DataViews: Update pagination icons. ([63594](https://github.com/WordPress/gutenberg/pull/63594)) +- DataViews: Rename the header property of fields to label. ([63843](https://github.com/WordPress/gutenberg/pull/63843)) +- DataViews: Support combined fields. ([63236](https://github.com/WordPress/gutenberg/pull/63236)) +- Dataviews List: Update item layout. ([63299](https://github.com/WordPress/gutenberg/pull/63299)) +- Increase column-gap between fields in List layout. ([63603](https://github.com/WordPress/gutenberg/pull/63603)) +- Update 'Front page' badge. ([63752](https://github.com/WordPress/gutenberg/pull/63752)) +- Update: Pages: Trash view should default to table layout try 2. ([63652](https://github.com/WordPress/gutenberg/pull/63652)) +- Update: Grid layout: Allow users to adjust grid density. ([63367](https://github.com/WordPress/gutenberg/pull/63367)) +- Update: Include avatars on list view. ([63309](https://github.com/WordPress/gutenberg/pull/63309)) +- Update: List / Table layout ā€“ selected item stroke should be tinted blue. ([63312](https://github.com/WordPress/gutenberg/pull/63312)) +- Update: Make changing order an action on the ellipsis menu. ([62189](https://github.com/WordPress/gutenberg/pull/62189)) + +#### Global Styles +- Add colors and typograpghy to the browse styles section. ([63173](https://github.com/WordPress/gutenberg/pull/63173)) +- Adding Font size presets UI. ([63057](https://github.com/WordPress/gutenberg/pull/63057)) +- Apply same styles to block previews on inserter and Global Styles. ([63177](https://github.com/WordPress/gutenberg/pull/63177)) +- Background: Add background attachment to top level styles. ([61382](https://github.com/WordPress/gutenberg/pull/61382)) +- Move background panel under color panel. ([63888](https://github.com/WordPress/gutenberg/pull/63888)) + +#### Block Editor +- Hide source filter in my patterns. ([63831](https://github.com/WordPress/gutenberg/pull/63831)) +- List View: Remove the sticky position icon tooltip. ([63850](https://github.com/WordPress/gutenberg/pull/63850)) +- Patterns: Render draggable only when enabled. ([63715](https://github.com/WordPress/gutenberg/pull/63715)) + +#### Post Editor +- Add post status icon in post summary. ([63658](https://github.com/WordPress/gutenberg/pull/63658)) +- Editor: Improve Header layout. ([62636](https://github.com/WordPress/gutenberg/pull/62636)) +- Post Actions: Use entity details for capability checks. ([63423](https://github.com/WordPress/gutenberg/pull/63423)) + +#### Font Library +- Group fonts by source. ([63211](https://github.com/WordPress/gutenberg/pull/63211)) +- Include a "Select All" options to activate/deactivate all fonts. ([63531](https://github.com/WordPress/gutenberg/pull/63531)) +- Reduce duplication of font library group headings. ([63532](https://github.com/WordPress/gutenberg/pull/63532)) + +#### Zoom Out +- Hide vertical toolbar when block is not full width. ([63650](https://github.com/WordPress/gutenberg/pull/63650)) +- Only show the inserters when a block is selected or hovered. ([63668](https://github.com/WordPress/gutenberg/pull/63668)) + +#### Block Locking +- Tweak Block Locking UI. ([63881](https://github.com/WordPress/gutenberg/pull/63881)) + +#### General UI +- Polish "Delete" modal. ([63392](https://github.com/WordPress/gutenberg/pull/63392)) +- Update close icon. ([63597](https://github.com/WordPress/gutenberg/pull/63597)) +- Site Editor: Reduce navigation sidebar width. ([63431](https://github.com/WordPress/gutenberg/pull/63431)) + +#### Block bindings +- Bootstrap sources defined in the server. ([63470](https://github.com/WordPress/gutenberg/pull/63470)) + +#### Patterns +- Limit pattern shuffling to theme and user patterns only. ([62677](https://github.com/WordPress/gutenberg/pull/62677)) + +#### Components +- CustomSelectControl V2 legacy adapter: Stabilize experimental props. ([63248](https://github.com/WordPress/gutenberg/pull/63248)) +- CustomSelectControl: Switch to ariakit-based implementation. ([63258](https://github.com/WordPress/gutenberg/pull/63258)) +- CustomSelectControlV2: Animate select popover appearance. ([63343](https://github.com/WordPress/gutenberg/pull/63343)) +- CustomSelectControlV2: Do not flip popover if legacy adapter. ([63357](https://github.com/WordPress/gutenberg/pull/63357)) +- DropdownMenuV2: Invert animation direction. ([63443](https://github.com/WordPress/gutenberg/pull/63443)) +- FontSizePicker: Tidy up internal logic. ([63553](https://github.com/WordPress/gutenberg/pull/63553)) +- FormTokenField: Deprecate bottom margin. ([63491](https://github.com/WordPress/gutenberg/pull/63491)) +- SelectControl: Add "minimal" variant. ([63265](https://github.com/WordPress/gutenberg/pull/63265)) +- Tabs: Hyphenate tab labels. ([63337](https://github.com/WordPress/gutenberg/pull/63337)) +- Tabs: Keep full opacity of focus ring on disabled tabs. ([63754](https://github.com/WordPress/gutenberg/pull/63754)) +- Update HeightControl component to label inputs. ([63761](https://github.com/WordPress/gutenberg/pull/63761)) + +#### Core Data +- Core Data: Mark 'canUser' related actions resolvers as resolved. ([63435](https://github.com/WordPress/gutenberg/pull/63435)) +- Core Data: Resolve user capabilities when fetching an entity. ([63430](https://github.com/WordPress/gutenberg/pull/63430)) +- Core Data: Support entities in the 'canUser' selector. ([63322](https://github.com/WordPress/gutenberg/pull/63322)) +- Core Data: Support entity queries in the 'useResourcePermissions' hook. ([63653](https://github.com/WordPress/gutenberg/pull/63653)) + +#### JSON Schemas +- Update JSON Schemas to Draft 7. ([63583](https://github.com/WordPress/gutenberg/pull/63583)) + +### New APIs + +#### Block bindings +- Unify `getValue`/`getValues` and `setValue`/`setValues` APIs. ([63185](https://github.com/WordPress/gutenberg/pull/63185)) + +### Bug Fixes + +#### Data Views +- DataViews: Do not render bulk actions Dropdown if no actions are available. ([63575](https://github.com/WordPress/gutenberg/pull/63575)) +- DataViews: Fix default layouts in the pages data views. ([63427](https://github.com/WordPress/gutenberg/pull/63427)) +- DataViews: Fix featured image height regression. ([63424](https://github.com/WordPress/gutenberg/pull/63424)) +- DataViews: Fix field rendering. ([63452](https://github.com/WordPress/gutenberg/pull/63452)) +- DataViews: Fix pattens list selection. ([63733](https://github.com/WordPress/gutenberg/pull/63733)) +- DataViews: Fix uncontrolled selection. ([63741](https://github.com/WordPress/gutenberg/pull/63741)) +- DataViews: Only show elligible actions in the bulk editing menu. ([63473](https://github.com/WordPress/gutenberg/pull/63473)) +- Fix patterns sorting by `title`. ([63710](https://github.com/WordPress/gutenberg/pull/63710)) +- Fix selected row styles in table layout. ([63811](https://github.com/WordPress/gutenberg/pull/63811)) +- Fix: DataViews: Layout resets for patterns each time a new pattern category is selected. ([63711](https://github.com/WordPress/gutenberg/pull/63711)) +- Fix: Inconsistent field spacing in Grid layout. ([63363](https://github.com/WordPress/gutenberg/pull/63363)) +- Templates DataViews: Set the right context for the preview field. ([63488](https://github.com/WordPress/gutenberg/pull/63488)) +- +#### Block Editor +- Fix user patterns disabling sync filter. ([63828](https://github.com/WordPress/gutenberg/pull/63828)) +- ImageURLInputUI: Make onSetLightbox and resetLightbox optional. ([63573](https://github.com/WordPress/gutenberg/pull/63573)) +- Pattern Inserter: Fix pagination layout when "Show button text labels" enabled. ([63466](https://github.com/WordPress/gutenberg/pull/63466)) +- Patterns inserter tabs: Temporary disable animated indicator. ([63352](https://github.com/WordPress/gutenberg/pull/63352)) +- Prevent empty void at the bottom of editor when block directory results are present. ([63397](https://github.com/WordPress/gutenberg/pull/63397)) +- Remove double shadow on Inserter category panel when zoomed out. ([63516](https://github.com/WordPress/gutenberg/pull/63516)) +- Tabs: Vertical Tabs should be 40px min height. ([63446](https://github.com/WordPress/gutenberg/pull/63446)) +- Fix mobile styles for inserter pattern and media tab navigation. ([63451](https://github.com/WordPress/gutenberg/pull/63451)) +- useBlockElement: Return null until ref callback has time to clean up the old element. ([63565](https://github.com/WordPress/gutenberg/pull/63565)) +- Remove hint in the Settings tab. ([63515](https://github.com/WordPress/gutenberg/pull/63515)) + +#### Block Library +- Avoid stripping attributes via group block migration when no layout is specified. ([63837](https://github.com/WordPress/gutenberg/pull/63837)) +- Fix default unit issue for tag cloud block. ([59122](https://github.com/WordPress/gutenberg/pull/59122)) +- Footnotes: Register format within the init function. ([63554](https://github.com/WordPress/gutenberg/pull/63554)) +- Image lightbox: Remove duplicate image when lightbox is opened. ([63381](https://github.com/WordPress/gutenberg/pull/63381)) +- Query Loop: Fix 'block' scoped variations to get the `query` defaults. ([63477](https://github.com/WordPress/gutenberg/pull/63477)) +- Query Loop: Fix passing of `namespace` when selecting from suggested patterns. ([63402](https://github.com/WordPress/gutenberg/pull/63402)) +- Template Part: Add check if create action should be allowed. ([63623](https://github.com/WordPress/gutenberg/pull/63623)) +- Update Inherited Query Loop value from Template Settings changes. ([63358](https://github.com/WordPress/gutenberg/pull/63358)) + +#### Site Editor +- Fix: Error while Calling edit-site getCurrentTemplateTemplateParts selector. ([63818](https://github.com/WordPress/gutenberg/pull/63818)) +- Fix error when duplicating a template part. ([63663](https://github.com/WordPress/gutenberg/pull/63663)) +- Fix: Add Template Modal layout in mobile view. ([63627](https://github.com/WordPress/gutenberg/pull/63627)) +- Make hover block outlines not present in Distraction Free. ([63819](https://github.com/WordPress/gutenberg/pull/63819)) +- Site Editor Navigation Commands: Add permission check. ([63798](https://github.com/WordPress/gutenberg/pull/63798)) +- fix: Wp icon focus issue. ([62675](https://github.com/WordPress/gutenberg/pull/62675)) + +#### Zoom Out +- Don't automatically show inserter when zoom out mode initiates. ([63859](https://github.com/WordPress/gutenberg/pull/63859)) +- Ensure that we only enter zoom out mode if the experiment is enabled. ([63417](https://github.com/WordPress/gutenberg/pull/63417)) +- Fix crash due to absence of selected block. ([63642](https://github.com/WordPress/gutenberg/pull/63642)) +- Fix vertical toolbar position. ([63745](https://github.com/WordPress/gutenberg/pull/63745)) +- Translate toolbar delete button. ([63476](https://github.com/WordPress/gutenberg/pull/63476)) + +#### Components +- Button: Never apply `aria-disabled` to anchor. ([63376](https://github.com/WordPress/gutenberg/pull/63376)) +- Revert "Update HeightControl component to label inputs". ([63839](https://github.com/WordPress/gutenberg/pull/63839)) +- SelectControl: Fix hover/focus color in wp-admin. ([63855](https://github.com/WordPress/gutenberg/pull/63855)) +- ToggleGroupControl: Support `disabled` options. ([63450](https://github.com/WordPress/gutenberg/pull/63450)) + +#### Global Styles +- Disable "Reset styles" button when there are no changes. ([63562](https://github.com/WordPress/gutenberg/pull/63562)) +- Disallow scrolling the block preview. ([63558](https://github.com/WordPress/gutenberg/pull/63558)) +- Ensure root selector (body) is not wrapped in :root :Where(). ([63726](https://github.com/WordPress/gutenberg/pull/63726)) +- Global styles block previews: Fix scaling. ([63596](https://github.com/WordPress/gutenberg/pull/63596)) +- Style variations: Don't display the default if its the only variation. ([63555](https://github.com/WordPress/gutenberg/pull/63555)) + +#### CSS & Styling +- Comments: Allow button element shadows from theme.json. ([63790](https://github.com/WordPress/gutenberg/pull/63790)) +- List: Prevent style bleed into non-List block lists. ([63537](https://github.com/WordPress/gutenberg/pull/63537)) +- Search: Prevent override of global button radii in editor. ([63789](https://github.com/WordPress/gutenberg/pull/63789)) + +#### Font Library +- Add 'No fonts installed' message on library tab when fonts aren't available. ([63740](https://github.com/WordPress/gutenberg/pull/63740)) +- Improve 'No fonts installed' state when fonts are installed but not activated. ([63533](https://github.com/WordPress/gutenberg/pull/63533)) + +#### Post Editor +- Allow editing of description only for custom templates. ([63664](https://github.com/WordPress/gutenberg/pull/63664)) + +#### Design Tools +- Background image block support: Fix dropzone size. ([63588](https://github.com/WordPress/gutenberg/pull/63588)) +- Background tool: Fix double border. ([63559](https://github.com/WordPress/gutenberg/pull/63559)) + +#### General interface +- Discussions panel: Distinguish between verb and adjective form of open for internationalization. ([63791](https://github.com/WordPress/gutenberg/pull/63791)) +- Fix canvas issues by removing VisualEditorā€™s height. ([63724](https://github.com/WordPress/gutenberg/pull/63724)) + +#### Block Transforms +- Block Switcher Preview: Adjust the position and enable pattern list preview in mobile viewport. ([63512](https://github.com/WordPress/gutenberg/pull/63512)) + +#### Block bindings +- Revert triggering multi-entity save panel in post with meta changes. ([63412](https://github.com/WordPress/gutenberg/pull/63412)) + +#### Block Directory +- Memoize store selectors. ([63346](https://github.com/WordPress/gutenberg/pull/63346)) + +#### Inner blocks +- InnerBlocks: Make sure blockType is set before trying to use it. ([63351](https://github.com/WordPress/gutenberg/pull/63351)) + +#### Widgets Editor +- Widgets: Memoize 'getWidgets' store selector. ([63338](https://github.com/WordPress/gutenberg/pull/63338)) + +#### Synced Patterns +- Pattern overrides: Ensure "Reset" button always shows as last item and with border. ([63291](https://github.com/WordPress/gutenberg/pull/63291)) + +#### Patterns +- Fix: Removed shuffle button when only 1 pattern is present. ([63093](https://github.com/WordPress/gutenberg/pull/63093)) + +#### Media +- Lock post saving during image uploads. ([41120](https://github.com/WordPress/gutenberg/pull/41120)) + +#### JSON Schemas +- Prepare JSON schemas for Draft 7 update. ([63582](https://github.com/WordPress/gutenberg/pull/63582)) + +#### Security +- Add: Permission checks to avoid 403 errors on non admin roles. ([63296](https://github.com/WordPress/gutenberg/pull/63296)) + +### Accessibility + +#### Components +- Align checkbox, radio, and toggle input design. ([63490](https://github.com/WordPress/gutenberg/pull/63490)) +- Fix ComboboxControl reset button when using the keyboard. ([63410](https://github.com/WordPress/gutenberg/pull/63410)) + +#### Post Editor +- Add missing aria-haspopup attribute to the buttons to set and replace the featured image. ([63360](https://github.com/WordPress/gutenberg/pull/63360)) + +#### Block Library +- Show visual label for Categories block in dropdown mode. ([56364](https://github.com/WordPress/gutenberg/pull/56364)) + + +### Performance + +#### Components +- Storybook: Improve TypeScript performance for slow stories. ([63388](https://github.com/WordPress/gutenberg/pull/63388)) + + +### Experiments + +#### Grid layout +- Disable in-between inserter in Manual grids. ([63391](https://github.com/WordPress/gutenberg/pull/63391)) +- Don't display default appender inside Manual grid. ([63395](https://github.com/WordPress/gutenberg/pull/63395)) +- Fix responsive behaviour so both column start and column span are taken into account. ([63464](https://github.com/WordPress/gutenberg/pull/63464)) +- Better looking block movers. ([63394](https://github.com/WordPress/gutenberg/pull/63394)) +- Place new block after currently selected block when using slash inserter and splitting text. ([63333](https://github.com/WordPress/gutenberg/pull/63333)) +- Move visualizer popover to slot under the canvas. ([63389](https://github.com/WordPress/gutenberg/pull/63389)) +- Don't remount the block when rendering grid tools. ([63557](https://github.com/WordPress/gutenberg/pull/63557)) + +#### Data Views +- Quick Edit: Support bulk selection. ([63841](https://github.com/WordPress/gutenberg/pull/63841)) +- DataViews: Bootstrap Quick Edit. ([63600](https://github.com/WordPress/gutenberg/pull/63600)) + + +### Documentation + +- Add to code requirements install and import Interactivity API. ([63439](https://github.com/WordPress/gutenberg/pull/63439)) +- Alpine vs Preact extra explanations. ([63593](https://github.com/WordPress/gutenberg/pull/63593)) +- Backport docs: Update and format. ([63830](https://github.com/WordPress/gutenberg/pull/63830)) +- Create-block - fix - update default folder name to proper default. ([63530](https://github.com/WordPress/gutenberg/pull/63530)) +- DataForm: Add a simple story for the DataForm component. ([63840](https://github.com/WordPress/gutenberg/pull/63840)) +- Fix Typo in Interactivity Api Reference. ([63775](https://github.com/WordPress/gutenberg/pull/63775)) +- Fix typo in Autocomplete component README.md. ([63496](https://github.com/WordPress/gutenberg/pull/63496)) +- FontSizePicker: Fix documentation for default `units`. ([63577](https://github.com/WordPress/gutenberg/pull/63577)) +- Improve the base control help prop documentation. ([63693](https://github.com/WordPress/gutenberg/pull/63693)) +- JSON Schema Docgen Rework. ([63868](https://github.com/WordPress/gutenberg/pull/63868)) +- Mark unstable__bootstrapServerSideBlockDefinitions with @ignore. ([63673](https://github.com/WordPress/gutenberg/pull/63673)) +- Move entity-provider.js exports into hooks/index.ts so they are added to the documentation. ([63528](https://github.com/WordPress/gutenberg/pull/63528)) +- Small Typo in Experiment Page. ([63773](https://github.com/WordPress/gutenberg/pull/63773)) +- Storybook: Remove popover-related height buffers. ([63480](https://github.com/WordPress/gutenberg/pull/63480)) +- Update "Versions in WordPress" page. ([63869](https://github.com/WordPress/gutenberg/pull/63869)) +- Update dataviews documentation. ([63860](https://github.com/WordPress/gutenberg/pull/63860)) +- Update getContext() usage examples with namespace argument. ([63411](https://github.com/WordPress/gutenberg/pull/63411)) +- Update react reference links in developer documentation. ([62818](https://github.com/WordPress/gutenberg/pull/62818)) +- Update react reference links in package's readme and doc blocks. ([62704](https://github.com/WordPress/gutenberg/pull/62704)) +- Updated Useeffect URL. ([63494](https://github.com/WordPress/gutenberg/pull/63494)) + + +### Code Quality + +- Add margin-bottom lint rules for CheckboxControl, ComboboxControl, SearchControl. ([63679](https://github.com/WordPress/gutenberg/pull/63679)) +- Add margin-bottom lint rules for FocalPointPicker, TextareaControl, TreeSelect. ([63633](https://github.com/WordPress/gutenberg/pull/63633)) +- Add margin-bottom lint rules for RangeControl. ([63821](https://github.com/WordPress/gutenberg/pull/63821)) +- Block editor settings: Add missing global styles links dependencies. ([63823](https://github.com/WordPress/gutenberg/pull/63823)) +- Core Data: Remove leftover 'todo' comment. ([63842](https://github.com/WordPress/gutenberg/pull/63842)) +- Core Data: Use meta-store actions for resolution status. ([63469](https://github.com/WordPress/gutenberg/pull/63469)) +- core-data: Fix `canUser` allowed methods handling. ([63615](https://github.com/WordPress/gutenberg/pull/63615)) +- DataViews: Move PostList component to its own folder. ([63334](https://github.com/WordPress/gutenberg/pull/63334)) +- JSON Schema Reorganization and Fixes. ([63591](https://github.com/WordPress/gutenberg/pull/63591)) +- Update: Simplify and do not pass renderingMode on editor SidebarContent. ([63814](https://github.com/WordPress/gutenberg/pull/63814)) +- Use Base Focus Styles for Region Focus. ([62881](https://github.com/WordPress/gutenberg/pull/62881)) +- Use static 'key' when filtering BlockEdit components. ([63590](https://github.com/WordPress/gutenberg/pull/63590)) +- Update: Simplify some permission checks. ([63812](https://github.com/WordPress/gutenberg/pull/63812)) +- Use entity details when calling 'canUser' selectors. ([63415](https://github.com/WordPress/gutenberg/pull/63415)) +- HTML API: Backport updates from Core. ([63723](https://github.com/WordPress/gutenberg/pull/63723)) + +#### Block Library +- Image block: Remove unnecessary variables on expand on click implementation. ([63290](https://github.com/WordPress/gutenberg/pull/63290)) +- Image lightbox: Move image data from context to state. ([63348](https://github.com/WordPress/gutenberg/pull/63348)) +- Navigation Submenu: Remove user permission checks. ([63720](https://github.com/WordPress/gutenberg/pull/63720)) +- Query Title block: Rely on the editor store to apply the right archive title placeholder. ([63478](https://github.com/WordPress/gutenberg/pull/63478)) +- Remove unused useSplit after #54543. ([63826](https://github.com/WordPress/gutenberg/pull/63826)) + +#### Data Views +- DataViews: Cleanup preview styles. ([63365](https://github.com/WordPress/gutenberg/pull/63365)) +- DataViews: Move the layouts into a dedicated folder. ([63409](https://github.com/WordPress/gutenberg/pull/63409)) +- DataViews: Refactor to prepare exposing the underlying UI pieces. ([63694](https://github.com/WordPress/gutenberg/pull/63694)) +- DataViews: Remove redundant setSelection prop. ([63648](https://github.com/WordPress/gutenberg/pull/63648)) +- DataViews: Rename `onSelectionChange` to `onChangeSelection`. ([63087](https://github.com/WordPress/gutenberg/pull/63087)) + +#### Components +- ColorPicker: Use `minimal` variant for SelectControl. ([63676](https://github.com/WordPress/gutenberg/pull/63676)) +- Rename Button describedBy prop to description and deprecate old name. ([63486](https://github.com/WordPress/gutenberg/pull/63486)) +- Tabs: Move animation-related utilities into separate utils file. ([62946](https://github.com/WordPress/gutenberg/pull/62946)) + +#### Block bindings +- Don't provide default `canUserEditValue` in reducer. ([63628](https://github.com/WordPress/gutenberg/pull/63628)) +- Improve how the context needed by sources is extended in the editor. ([63513](https://github.com/WordPress/gutenberg/pull/63513)) +- Improve the way block bindings sources are registered. ([63117](https://github.com/WordPress/gutenberg/pull/63117)) + +#### Post Editor +- Editor: Remove unused `setNestedValue` util. ([63620](https://github.com/WordPress/gutenberg/pull/63620)) +- Move useSelectNearestEditableBlock out of src/hooks. ([63730](https://github.com/WordPress/gutenberg/pull/63730)) + +#### Font Library +- Remove unused font library experiment. ([63890](https://github.com/WordPress/gutenberg/pull/63890)) + +#### Global Styles +- Remove unused global styles background screen. ([63887](https://github.com/WordPress/gutenberg/pull/63887)) + +#### Widgets Editor +- Widget Editor: Remove unused values returned from 'mapSelect'. ([63738](https://github.com/WordPress/gutenberg/pull/63738)) + +#### Block API +- Use `@wordpress/warning` during block registration instead of `console.error` and `console.warn`. ([63610](https://github.com/WordPress/gutenberg/pull/63610)) + +#### Synced Patterns +- Quality: Remove "reusable block name hint" code. ([63514](https://github.com/WordPress/gutenberg/pull/63514)) + +#### Commands +- Update cmdk. ([63465](https://github.com/WordPress/gutenberg/pull/63465)) + +#### Document Settings +- FlatTermSelector: Be more defensive about termIds. ([63461](https://github.com/WordPress/gutenberg/pull/63461)) + +#### Site Editor +- Deprecate 'getCanUserCreateMedia' selector. ([63413](https://github.com/WordPress/gutenberg/pull/63413)) + +#### Block Directory +- Remove 'edit-post' package dependency. ([63349](https://github.com/WordPress/gutenberg/pull/63349)) + +### Tools + +#### Project Management +- Issue template: Use checkboxes instead of dropdown. ([63523](https://github.com/WordPress/gutenberg/pull/63523)) +- Sync backport changelog action: Use outputs instead of env. ([63792](https://github.com/WordPress/gutenberg/pull/63792)) +- Run sync when issue is labeled with Sync Backport Changelog. ([63793](https://github.com/WordPress/gutenberg/pull/63793)) + +#### Testing +- Downgrade node 22(.5) unit tests to 22.4. ([63728](https://github.com/WordPress/gutenberg/pull/63728)) +- Font Library: Fix flaky end-to-end tests. ([63904](https://github.com/WordPress/gutenberg/pull/63904)) +- Upgrade Playwright to v1.45. ([61443](https://github.com/WordPress/gutenberg/pull/61443)) +- Bug: Eslint `recommended-with-formatting` allows for unnecessary spaces. ([63549](https://github.com/WordPress/gutenberg/pull/63549)) + +#### Build Tooling & Plugin +- Fix broken license check script. ([61868](https://github.com/WordPress/gutenberg/pull/61868)) +- React: Restore umd builds. ([63602](https://github.com/WordPress/gutenberg/pull/63602)) +- Upgrade TypeScript to 5.5. ([63012](https://github.com/WordPress/gutenberg/pull/63012)) +- Scripts: Remove now-obsolete `getRenderPropPaths()`. ([63661](https://github.com/WordPress/gutenberg/pull/63661)) +- Scripts: Include variations paths in build. ([63098](https://github.com/WordPress/gutenberg/pull/63098)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @hectorjarquin: Add to code requirements install and import Interactivity API. ([63439](https://github.com/WordPress/gutenberg/pull/63439)) +- @Sourav61: Fix: Removed shuffle button when only 1 pattern is present. ([63093](https://github.com/WordPress/gutenberg/pull/63093)) +- @tomllobet: Create-block - fix - update default folder name to proper default. ([63530](https://github.com/WordPress/gutenberg/pull/63530)) +- @troychaplin: change: Updated soundcloud icon for social link block. ([63504](https://github.com/WordPress/gutenberg/pull/63504)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @adamsilverstein @afercia @ajlende @akasunil @amitraj2203 @andrewserong @artemiomorales @barryceelen @carolinan @ciampo @DaniGuardiola @dhananjaykuber @dmsnell @dsas @ellatrix @geriux @hectorjarquin @jameskoster @jasmussen @jeherve @jeryj @jorgefilipecosta @jsnajdr @juanmaguitar @kevin940726 @luisherranz @madhusudhand @MaggieCabrera @Mamaduka @matiasbenedetto @mattsherman @mirka @noisysocks @ntsekouras @oandregal @ockham @priethor @ramonjd @richtabor @ryanwelcher @SantosGuillamot @scruffian @shail-mehta @sirreal @Sourav61 @stokesman @StyleShit @swissspidy @t-hamano @talldan @tellthemachines @tomllobet @troychaplin @tyxla @up1512001 @widoz @youknowriad + + += 18.8.0 = + +## Changelog + +### Features + +- DataForm: Implement first prototype using duplicate page action. ([63032](https://github.com/WordPress/gutenberg/pull/63032)) + + +### Enhancements + +#### Components +- BaseControl: Forward ref on VisualLabel. ([63169](https://github.com/WordPress/gutenberg/pull/63169)) +- CustomSelectControlV2: Allow wrapping item hint to new line. ([62848](https://github.com/WordPress/gutenberg/pull/62848)) +- CustomSelectControlV2: Expose legacy wrapper through private APIs. ([62936](https://github.com/WordPress/gutenberg/pull/62936)) +- CustomSelectControl V2: Keep legacy arrow down behavior only for legacy wrapper. ([62919](https://github.com/WordPress/gutenberg/pull/62919)) +- CustomSelectControlV2: Collapse checkmark space when unchecked. ([63229](https://github.com/WordPress/gutenberg/pull/63229)) +- CustomSelectControlV2: Keep item checkmark top aligned. ([63230](https://github.com/WordPress/gutenberg/pull/63230)) +- DateTime: Create TimeInput component and integrate into TimePicker. ([60613](https://github.com/WordPress/gutenberg/pull/60613)) +- FontSizePicker: Use CustomSelectControl V2 legacy adapter. ([63134](https://github.com/WordPress/gutenberg/pull/63134)) +- Tabs: Add vertical indicator animation. ([62879](https://github.com/WordPress/gutenberg/pull/62879)) +- TimeInput: Add `label` prop. ([63106](https://github.com/WordPress/gutenberg/pull/63106)) +- TimePicker: Add `dateOrder` prop to sort day, month, and year. ([62481](https://github.com/WordPress/gutenberg/pull/62481)) +- ToolbarButton: Deprecate `isDisabled` prop and merge with `disabled`. ([63101](https://github.com/WordPress/gutenberg/pull/63101)) +- Tooltip Component: Add custom class name support. ([63157](https://github.com/WordPress/gutenberg/pull/63157)) + +#### Data Views +- Add padding around selected values in author filter. ([63212](https://github.com/WordPress/gutenberg/pull/63212)) +- DataViews filterSortAndPaginate utility: Support sorting by number. ([63187](https://github.com/WordPress/gutenberg/pull/63187)) +- DataViews: Make `view.hiddenFields` optional. ([62876](https://github.com/WordPress/gutenberg/pull/62876)) +- DataViews: Remove Table Cells animation. ([63079](https://github.com/WordPress/gutenberg/pull/63079)) +- DataViews: Replace supportedLayouts prop with defaultLayouts prop instead. ([63287](https://github.com/WordPress/gutenberg/pull/63287)) +- Duplicate modal: Move to 40px components. ([63246](https://github.com/WordPress/gutenberg/pull/63246)) +- Pages: Include avatar in Author field. ([63142](https://github.com/WordPress/gutenberg/pull/63142)) +- Patterns Page: Hide preview column by default. ([63213](https://github.com/WordPress/gutenberg/pull/63213)) +- Posts list powered by DataViews. ([62705](https://github.com/WordPress/gutenberg/pull/62705)) +- Split layout / view options. Use active layout icon for the layout button. ([63205](https://github.com/WordPress/gutenberg/pull/63205)) +- Use status icons in field display. ([63289](https://github.com/WordPress/gutenberg/pull/63289)) + +#### Block Library +- Add example for query block and posts list. ([63286](https://github.com/WordPress/gutenberg/pull/63286)) +- Excerpt block: Add example of the block. ([63010](https://github.com/WordPress/gutenberg/pull/63010)) +- Group: Add block support for shadow. ([63295](https://github.com/WordPress/gutenberg/pull/63295)) +- Login/out block: Add example of the block. ([62937](https://github.com/WordPress/gutenberg/pull/62937)) +- Post content block: Add example of the block. ([62968](https://github.com/WordPress/gutenberg/pull/62968)) +- Post title: Add example of the block. ([62955](https://github.com/WordPress/gutenberg/pull/62955)) +- Table Block: Add toolbar button to add a caption. ([47984](https://github.com/WordPress/gutenberg/pull/47984)) + +#### Block Editor +- Block position controls: Use V2 legacy adapter instead of V1 `CustomSelectControl`. ([63139](https://github.com/WordPress/gutenberg/pull/63139)) +- DateFormatPicker: Use CustomSelectControl V2 legacy adapter. ([63171](https://github.com/WordPress/gutenberg/pull/63171)) +- Fix inspector inner shadow border. ([63245](https://github.com/WordPress/gutenberg/pull/63245)) +- FontAppearanceControl: Use CustomSelectControl V2 legacy adapter. ([63179](https://github.com/WordPress/gutenberg/pull/63179)) +- Inserter: Remove the dialog behaviour. ([63059](https://github.com/WordPress/gutenberg/pull/63059)) +- SpacingInputControl: Use CustomSelectControl V2 legacy adapter. ([63190](https://github.com/WordPress/gutenberg/pull/63190)) + +#### Global Styles +- Background image: Move controls into a popover. ([60151](https://github.com/WordPress/gutenberg/pull/60151)) +- Block background UI controls. ([60100](https://github.com/WordPress/gutenberg/pull/60100)) +- Tweak block background position preview height. ([63225](https://github.com/WordPress/gutenberg/pull/63225)) + +#### Extensibility +- DataViews: Register the deletePost action like any third-party action. ([62913](https://github.com/WordPress/gutenberg/pull/62913)) +- DataViews: Register the export pattern action like any third-party action. ([63046](https://github.com/WordPress/gutenberg/pull/63046)) +- DataViews: Register the reset template and template part action like any third-party action. ([63017](https://github.com/WordPress/gutenberg/pull/63017)) + +#### Design Tools +- Flex dimensions: Rename "Fill" to "Grow". ([62779](https://github.com/WordPress/gutenberg/pull/62779)) +- List Item: Add color support. ([59892](https://github.com/WordPress/gutenberg/pull/59892)) +- Uniform Focal point labels. ([62438](https://github.com/WordPress/gutenberg/pull/62438)) + +#### Font Library +- Font Library Modal: Enhance pagination appearance. ([63210](https://github.com/WordPress/gutenberg/pull/63210)) +- Font Library: Store font subdirectory in post meta. ([63000](https://github.com/WordPress/gutenberg/pull/63000)) +- Move font directory into uploads to match WP 6.5. ([60354](https://github.com/WordPress/gutenberg/pull/60354)) + +#### Layout +- Add justification to block toolbar in addition to sidebar. ([62924](https://github.com/WordPress/gutenberg/pull/62924)) + +#### Site Editor +- Align rename modals. ([62874](https://github.com/WordPress/gutenberg/pull/62874)) + +#### Block API +- block.json: Allow passing filename as `variations` field. ([62092](https://github.com/WordPress/gutenberg/pull/62092)) + +#### Template Editor +- Update: Move template areas into a panel. ([62033](https://github.com/WordPress/gutenberg/pull/62033)) + +#### Block Variations +- Automatically select group variation if there is only one available. ([61871](https://github.com/WordPress/gutenberg/pull/61871)) + +#### Zoom Out +- Add a vertical toolbar for zoom out mode. ([60123](https://github.com/WordPress/gutenberg/pull/60123)) + + +### New APIs + +#### Block API +- Introduce "local" attributes and use it for the image block. ([63076](https://github.com/WordPress/gutenberg/pull/63076)) + + +### Bug Fixes + +- Core Commands: Fix Pages command link. ([63235](https://github.com/WordPress/gutenberg/pull/63235)) +- Docgen: Fix function param for const function expression. ([63034](https://github.com/WordPress/gutenberg/pull/63034)) +- Enable `save draft` button for posts with custom post status. ([63293](https://github.com/WordPress/gutenberg/pull/63293)) +- Ensure device previews extra scrollbar only appears when needed. ([62952](https://github.com/WordPress/gutenberg/pull/62952)) +- Styles specificity: Allow comment form input overrides. ([62960](https://github.com/WordPress/gutenberg/pull/62960)) + +#### Components +- CustomSelectControl V2 legacy adapter: Fix trigger button font size. ([63131](https://github.com/WordPress/gutenberg/pull/63131)) +- CustomSelectControl V2: Fix labelling with a visually hidden label. ([63137](https://github.com/WordPress/gutenberg/pull/63137)) +- CustomSelectControl V2: Fix trigger text alignment in RTL languages. ([62869](https://github.com/WordPress/gutenberg/pull/62869)) +- CustomSelectControl V2: Prevent keyboard event propagation in legacy wrapper. ([62907](https://github.com/WordPress/gutenberg/pull/62907)) +- CustomSelectControlV2: Add root element wrapper. ([62803](https://github.com/WordPress/gutenberg/pull/62803)) +- CustomSelectControlV2: Fix item styles. ([62825](https://github.com/WordPress/gutenberg/pull/62825)) +- CustomSelectControlV2: Fix popover styles. ([62821](https://github.com/WordPress/gutenberg/pull/62821)) +- CustomSelectControlV2: Fix select popover content overflow. ([62844](https://github.com/WordPress/gutenberg/pull/62844)) +- CustomSelectControlV2: Tweak item inline padding based on size. ([62850](https://github.com/WordPress/gutenberg/pull/62850)) +- Editor: Fix duplicate save panels. ([62863](https://github.com/WordPress/gutenberg/pull/62863)) +- Fix UnitControl select disabled state colors. ([62970](https://github.com/WordPress/gutenberg/pull/62970)) +- Fix extra scrollbar when a popover extends past the viewport. ([62894](https://github.com/WordPress/gutenberg/pull/62894)) +- Fix the 'useUpdateEffect' hook in strict mode. ([62974](https://github.com/WordPress/gutenberg/pull/62974)) +- ProgressBar: Fix indeterminate RTL support. ([63129](https://github.com/WordPress/gutenberg/pull/63129)) +- RangeControl: Fix RTL support for custom marks. ([63198](https://github.com/WordPress/gutenberg/pull/63198)) +- SelectControl: Fix disabled styles. ([63266](https://github.com/WordPress/gutenberg/pull/63266)) +- Tabs: Fix "With tab icons" Storybook example. ([63297](https://github.com/WordPress/gutenberg/pull/63297)) +- Tabs: Fix text-align when text wraps in vertical mode. ([63272](https://github.com/WordPress/gutenberg/pull/63272)) +- TimePicker: Fix time zone overflow. ([63209](https://github.com/WordPress/gutenberg/pull/63209)) +- UnitControl: Fix an issue where keyboard shortcuts unintentionally shift focus on Windows OS. ([62988](https://github.com/WordPress/gutenberg/pull/62988)) + +#### Block Library +- Add Aspect ratio control on Image blocks in Grids. ([62891](https://github.com/WordPress/gutenberg/pull/62891)) +- Audio Block: Do not persist blob urls and fix undo. ([63257](https://github.com/WordPress/gutenberg/pull/63257)) +- File block: Do not persist blob urls and fix undo. ([63282](https://github.com/WordPress/gutenberg/pull/63282)) +- Fix Incorrect URL basename logic in EmbedPreview. ([63052](https://github.com/WordPress/gutenberg/pull/63052)) +- Fix: Update "Link Text" label to "Text" on Social Icons block #60966. ([61715](https://github.com/WordPress/gutenberg/pull/61715)) +- List: Maintain nested list on parent item removal. ([62949](https://github.com/WordPress/gutenberg/pull/62949)) +- Navigation: Allow themes to override block library text-decoration rule. ([63406](https://github.com/WordPress/gutenberg/pull/63406)) +- Patterns: Check for edited entity content property when exporting. ([63227](https://github.com/WordPress/gutenberg/pull/63227)) +- Reduce specificity of social link icon specific colors. ([63049](https://github.com/WordPress/gutenberg/pull/63049)) +- Refactor Post Date Relative Time Rendering for Future Dates. ([62979](https://github.com/WordPress/gutenberg/pull/62979)) +- Site Editor: Fix template parts 'Reset' action. ([62951](https://github.com/WordPress/gutenberg/pull/62951)) +- Video Block: Do not persist blob urls and fix undo. ([63238](https://github.com/WordPress/gutenberg/pull/63238)) + +#### Post Editor +- Editor: Do not truncate post excerpt if not editable. ([63314](https://github.com/WordPress/gutenberg/pull/63314)) +- Fix: Background height and padding in non-iframe editor canvas. ([63222](https://github.com/WordPress/gutenberg/pull/63222)) +- Fix: Crash when onActionPerformed is used with callback actions. ([63120](https://github.com/WordPress/gutenberg/pull/63120)) +- Fix: Permanently delete post action does not calls onActionPerformed. ([63121](https://github.com/WordPress/gutenberg/pull/63121)) +- Fix: Triple scrollbars in device previews. ([62940](https://github.com/WordPress/gutenberg/pull/62940)) +- Post editor: Increase specificity of bottom padding. ([63288](https://github.com/WordPress/gutenberg/pull/63288)) +- Actions: Translation should depend on number of items. ([62857](https://github.com/WordPress/gutenberg/pull/62857)) + +#### Data Views +- DataViews list layout: Fix action alignment. ([62971](https://github.com/WordPress/gutenberg/pull/62971)) +- DataViews: Restore preview focus outline in grid layout. ([62991](https://github.com/WordPress/gutenberg/pull/62991)) +- Fix buttonless table header alignment. ([62877](https://github.com/WordPress/gutenberg/pull/62877)) +- Fix typo in string for trashing posts. ([63119](https://github.com/WordPress/gutenberg/pull/63119)) +- Patterns: Avoid mapping template parts objects to patterns. ([62927](https://github.com/WordPress/gutenberg/pull/62927)) + +#### Block Style Variations +- Block supports: Ensure tools panel dropdown are visible on mobile. ([62896](https://github.com/WordPress/gutenberg/pull/62896)) +- Section Styles: Fix error when blocks are deregistered. ([63252](https://github.com/WordPress/gutenberg/pull/63252)) +- Section Styles: Prevent flash of variation styles in post editor. ([63071](https://github.com/WordPress/gutenberg/pull/63071)) +- Section Styles: Resolve ref values in variations data. ([63172](https://github.com/WordPress/gutenberg/pull/63172)) + +#### Layout +- Only hide drop indicator when grid has `isManualPlacement` set. ([63226](https://github.com/WordPress/gutenberg/pull/63226)) +- Remove dotted border from grid dropzone. ([63162](https://github.com/WordPress/gutenberg/pull/63162)) +- Resizing in Auto mode shouldn't add `columnStart` and `rowStart` values. ([63160](https://github.com/WordPress/gutenberg/pull/63160)) +- Fix invalid css for nested fullwidth layouts with zero padding applied. ([63436](https://github.com/WordPress/gutenberg/pull/63436)) + +#### Global Styles +- Elements: Avoid specificity bump for top-level element-only selectors. ([63403](https://github.com/WordPress/gutenberg/pull/63403)) +- Global styles revisions: Ensure that user-defined variation styles CSS is generated. ([62768](https://github.com/WordPress/gutenberg/pull/62768)) +- Root padding styles: Include alignwide in nested has-outer-padding logic. ([63207](https://github.com/WordPress/gutenberg/pull/63207)) +- Remove letter-spacing from typography element preview. ([60322](https://github.com/WordPress/gutenberg/pull/60322)) +- Only add customizer additional CSS to global styles in block themes. ([63331](https://github.com/WordPress/gutenberg/pull/63331)) + +#### Site Editor +- Make SiteHub available for Pages, Patterns, and Templates in mobile viewports. ([63118](https://github.com/WordPress/gutenberg/pull/63118)) +- Patterns and templates cannot be edited from sidebar mobile view. ([63002](https://github.com/WordPress/gutenberg/pull/63002)) +- Site Editor Sidebar: Hide horizontal scrollbar when navigating. ([63194](https://github.com/WordPress/gutenberg/pull/63194)) + +#### Synced Patterns +- Ensure disable overrides button is active for image blocks with captions or links. ([62948](https://github.com/WordPress/gutenberg/pull/62948)) +- Fix second scrollbar when editing patterns in the post editor. ([62909](https://github.com/WordPress/gutenberg/pull/62909)) +- Pattern overrides: Fix aspect ratio not working in image with overrides. ([62828](https://github.com/WordPress/gutenberg/pull/62828)) + +#### Block Editor +- Featured Image Panel: Align text and icons horizontally to avoid clipping. ([62842](https://github.com/WordPress/gutenberg/pull/62842)) +- Zoom Out: Move the hook to the inserter component. ([63315](https://github.com/WordPress/gutenberg/pull/63315)) +- Fix error when calling the PostActions `view-post` callback. ([63460](https://github.com/WordPress/gutenberg/pull/63460)) + +#### Block bindings +- Disable post meta editing in blocks inside a Query Loop. ([63237](https://github.com/WordPress/gutenberg/pull/63237)) +- Image block: Ensure extenders that rely on media ids in block html are supported by block bindings. ([63013](https://github.com/WordPress/gutenberg/pull/63013)) + +#### Patterns +- Fix: Restrict export pattern action to user patterns. ([63228](https://github.com/WordPress/gutenberg/pull/63228)) + +#### Posts/Tags/Categories Screen +- Constrain `is-fullscreen-mode` admin body class to posts list. ([63166](https://github.com/WordPress/gutenberg/pull/63166)) + +#### Inspector Controls +- Fix button wrapping in the document Inspector. ([63062](https://github.com/WordPress/gutenberg/pull/63062)) + +#### Design Tools +- Duotone: Fix code typo, to ensure Duotone updates correctly in Safari. ([62953](https://github.com/WordPress/gutenberg/pull/62953)) + +#### Commands +- Fix issue of HTML entities rendering in command menu. ([62606](https://github.com/WordPress/gutenberg/pull/62606)) + +#### Typography +- Use available font weights and styles in FontAppearanceControl. ([61915](https://github.com/WordPress/gutenberg/pull/61915)) +- Font Appearance Control: Refactor font appearance fallbacks. ([63215](https://github.com/WordPress/gutenberg/pull/63215)) + + +### Accessibility + +- Allow Escape key to move focus to editor region when in select mode. ([62196](https://github.com/WordPress/gutenberg/pull/62196)) +- Focus Editor Region from Template Footer Click. ([62595](https://github.com/WordPress/gutenberg/pull/62595)) + +#### Components +- Button: Stabilize `__experimentalIsFocusable` prop. ([62282](https://github.com/WordPress/gutenberg/pull/62282)) +- Fix inaccessible disabled `Button`s. ([62306](https://github.com/WordPress/gutenberg/pull/62306)) +- Make Tabs have a fluid height. ([62027](https://github.com/WordPress/gutenberg/pull/62027)) +- ToolbarButton: Always keep focusable when disabled. ([63102](https://github.com/WordPress/gutenberg/pull/63102)) + +#### Global Styles +- Fix unlabeled Remove shadow buttons. ([63197](https://github.com/WordPress/gutenberg/pull/63197)) + +#### Block Library +- Make usage of the settings icon more consistent. ([63020](https://github.com/WordPress/gutenberg/pull/63020)) + +#### Data Views +- Add translation context for 'view options' label. ([63031](https://github.com/WordPress/gutenberg/pull/63031)) +- Fix filter chip contrast. ([62865](https://github.com/WordPress/gutenberg/pull/62865)) + +#### Media +- Update URLPopover role and focus return. ([61313](https://github.com/WordPress/gutenberg/pull/61313)) + + +### Performance + +- Core data: Batch receiveUserPermission. ([63201](https://github.com/WordPress/gutenberg/pull/63201)) +- Perf tests: Make pages test compatible with base branch. ([63204](https://github.com/WordPress/gutenberg/pull/63204)) + +#### Block hooks +- Optimize selectors in the control component. ([63141](https://github.com/WordPress/gutenberg/pull/63141)) + + +### Experiments + +#### Layout +- Allow inserting blocks directly in empty grid cells. ([63108](https://github.com/WordPress/gutenberg/pull/63108)) +- Use `manualPlacement` attribute to set manual grid mode and allow responsive behaviour in both modes. ([62777](https://github.com/WordPress/gutenberg/pull/62777)) + + +### Documentation + +- Add note about postcss-urlrebase package patch. ([63015](https://github.com/WordPress/gutenberg/pull/63015)) +- Add RichText formatting example to the Editor curation documentation. ([63065](https://github.com/WordPress/gutenberg/pull/63065)) +- Block supports: Add documentation for 'splitting'. ([63016](https://github.com/WordPress/gutenberg/pull/63016)) +- Fix typo to be preposition, not verb, in some package comments and documentations. ([62945](https://github.com/WordPress/gutenberg/pull/62945)) +- Fix urls to developer documentation. ([63104](https://github.com/WordPress/gutenberg/pull/63104)) +- Interactivity API: Fix minor typos in code snippets. ([62890](https://github.com/WordPress/gutenberg/pull/62890)), ([63234](https://github.com/WordPress/gutenberg/pull/63234)) +- Interactivity API: Fix variable name in color directive example. ([62912](https://github.com/WordPress/gutenberg/pull/62912)) +- Interactivity API: Include references to more examples from the documentation. ([63025](https://github.com/WordPress/gutenberg/pull/63025)) +- Interactivity API: Recommend kebab-case in data-wp-class. ([62817](https://github.com/WordPress/gutenberg/pull/62817)) +- Remove link to polyfill.io. ([62883](https://github.com/WordPress/gutenberg/pull/62883)) +- Storybook: Fix links for block editor examples. ([63132](https://github.com/WordPress/gutenberg/pull/63132)) +- ToolbarButton: Fix documentation for `accessibleWhenDisabled`. ([63140](https://github.com/WordPress/gutenberg/pull/63140)) +- Update React dev docs rule hook URL. ([62995](https://github.com/WordPress/gutenberg/pull/62995)) + + +### Code Quality + +- Add linguist-documentation attribute to docs/ directory. ([62651](https://github.com/WordPress/gutenberg/pull/62651)) +- Conditionally call focus with getEditorRegion. ([62980](https://github.com/WordPress/gutenberg/pull/62980)) +- Core Data: Remove entity configuration '__experimentalNoFetch' flag checks. ([63303](https://github.com/WordPress/gutenberg/pull/63303)) +- Dependencies: Upgrades and deduplication. ([62657](https://github.com/WordPress/gutenberg/pull/62657)) +- Format Library: Clean up 'Highlight' format components. ([62965](https://github.com/WordPress/gutenberg/pull/62965)) +- Remove postcss-local-keyframes from dependencies. ([63224](https://github.com/WordPress/gutenberg/pull/63224)) +- Upgrade postcss-urlrebase package. ([63075](https://github.com/WordPress/gutenberg/pull/63075)) + +#### Data Views +- DataViews: Fix double check in `isTemplateRemovable`. ([63021](https://github.com/WordPress/gutenberg/pull/63021)) +- DataViews: Remove the AnyItem type. ([62856](https://github.com/WordPress/gutenberg/pull/62856)) +- DataViews: Removing mapping of user patterns to temporary object. ([63042](https://github.com/WordPress/gutenberg/pull/63042)) +- DataViews: Replace hiddenFields configuration with fields property instead. ([63127](https://github.com/WordPress/gutenberg/pull/63127)) +- DataViews: Simplify selection setting. ([62846](https://github.com/WordPress/gutenberg/pull/62846)) + +#### Block Editor +- Remove CSS hack for Internet Explorer 11. ([63220](https://github.com/WordPress/gutenberg/pull/63220)) +- Remove duplicate translator comment. ([62860](https://github.com/WordPress/gutenberg/pull/62860)) + +#### Components +- Allow ariakit and framer motion imports in the components package. ([63123](https://github.com/WordPress/gutenberg/pull/63123)) +- Normalize focusable disabled ToolbarButton usage. ([63130](https://github.com/WordPress/gutenberg/pull/63130)) +- Sidebar: Add a shared component for the inserter and list view. ([62343](https://github.com/WordPress/gutenberg/pull/62343)) +- Tabs: Split animation logic into multiple separate composable utilities. ([62942](https://github.com/WordPress/gutenberg/pull/62942)) + +#### Global Styles +- Global Styles: Allow variations to be filtered by multiple properties. ([62847](https://github.com/WordPress/gutenberg/pull/62847)) +- Global Styles: Simplify code to fetch color and typography variation. ([62827](https://github.com/WordPress/gutenberg/pull/62827)) +- Make a shared component for typography and color preview. ([62829](https://github.com/WordPress/gutenberg/pull/62829)) +- Section Styles: Clean up block style variation filters. ([62858](https://github.com/WordPress/gutenberg/pull/62858)) + +#### Block Library +- Gallery Block: Clean up v1 code. ([63285](https://github.com/WordPress/gutenberg/pull/63285)) + +#### Site Editor +- Clean up unused Table component. ([63283](https://github.com/WordPress/gutenberg/pull/63283)) + +#### Rich Text +- Raw handling: Remove IE11 fallback code. ([63219](https://github.com/WordPress/gutenberg/pull/63219)) + +#### Zoom Out +- Replace deprecated selector. ([63144](https://github.com/WordPress/gutenberg/pull/63144)) + +#### Block bindings +- Add comment about `useSelect` usage in withBlockBindingSupport. ([63005](https://github.com/WordPress/gutenberg/pull/63005)) + +#### HTML API +- Compat: Update HTML API with changes from 6.6. ([63089](https://github.com/WordPress/gutenberg/pull/63089)) + + +### Tools + +- Dependency extraction: Map to `regenerator-runtime` instead of `wp-polyfill`. ([63091](https://github.com/WordPress/gutenberg/pull/63091)) +- Env: Remove version field from docker-compose configuration. ([63099](https://github.com/WordPress/gutenberg/pull/63099)) +- Eslint-plugin: Add method-signature-style TypeScript lint rule. ([62718](https://github.com/WordPress/gutenberg/pull/62718)) +- Scripts: Ensure that typescript-eslint checks for unused vars. ([62925](https://github.com/WordPress/gutenberg/pull/62925)) +- Update new release issue template to remove core editor chat item. ([62864](https://github.com/WordPress/gutenberg/pull/62864)) + +#### Testing +- Automatically sync backport changelog to issue. ([62973](https://github.com/WordPress/gutenberg/pull/62973)) +- Block styles variations E2E: Wait for Save button before editing global styles. ([62915](https://github.com/WordPress/gutenberg/pull/62915)) +- Cherry pick automation: Fix for forks. ([62900](https://github.com/WordPress/gutenberg/pull/62900)) +- Cherry pick workflow: Improve message after conflict. ([62826](https://github.com/WordPress/gutenberg/pull/62826)) +- DataViews: Add performance test for pages. ([63170](https://github.com/WordPress/gutenberg/pull/63170)) +- Fix typo in column block fixture file. ([63007](https://github.com/WordPress/gutenberg/pull/63007)) +- Performance tests: Fix for 6.5. ([62871](https://github.com/WordPress/gutenberg/pull/62871)) +- Performance tests: Restore 6.5-compatible locator. ([63041](https://github.com/WordPress/gutenberg/pull/63041)) +- Re-enable image block cropping test (#62781). ([62854](https://github.com/WordPress/gutenberg/pull/62854)) +- Update method for changing the content in 'editor-modes' end-to-end test. ([62957](https://github.com/WordPress/gutenberg/pull/62957)) +- Update the `project-management-automation`action to use Node.js 20. ([62851](https://github.com/WordPress/gutenberg/pull/62851)) +- Upgrade web-vitals package. ([63019](https://github.com/WordPress/gutenberg/pull/63019)) + +#### Plugin +- Add local version of wp-env schema to .wp-env.json. ([63253](https://github.com/WordPress/gutenberg/pull/63253)) + +#### Build Tooling +- Build: Enable TypeScript skipDefaultLibCheck. ([63056](https://github.com/WordPress/gutenberg/pull/63056)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @airman5573: Fix variable name in color directive example for Interactivity API. ([62912](https://github.com/WordPress/gutenberg/pull/62912)) +- @aliaghdam: Tooltip Component: Add custom class name support. ([63157](https://github.com/WordPress/gutenberg/pull/63157)) +- @bogiii: DateTime: Create TimeInput component and integrate into TimePicker. ([60613](https://github.com/WordPress/gutenberg/pull/60613)) +- @Chrico: Scripts: Ensure that typescript-eslint checks for unused vars. ([62925](https://github.com/WordPress/gutenberg/pull/62925)) +- @dhananjaykuber: Fix Incorrect URL basename logic in EmbedPreview. ([63052](https://github.com/WordPress/gutenberg/pull/63052)) +- @iamibrahimriaz: Update iapi-about.md. ([63234](https://github.com/WordPress/gutenberg/pull/63234)) +- @iworks: Translation should depend on number of items. ([62857](https://github.com/WordPress/gutenberg/pull/62857)) +- @roygbyte: Fix typo to be preposition, not verb, in some package comments and documentations. ([62945](https://github.com/WordPress/gutenberg/pull/62945)) +- @shreya0204: Add justification to block toolbar in addition to sidebar. ([62924](https://github.com/WordPress/gutenberg/pull/62924)) +- @sejas: Fix: Error when calling the PostActions `view-post` callback. ([63460](https://github.com/WordPress/gutenberg/pull/63460)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @airman5573 @akasunil @aliaghdam @amitraj2203 @bogiii @carolinan @Chrico @ciampo @costasovo @creativecoder @DaniGuardiola @desrosj @dhananjaykuber @dmsnell @ellatrix @fluiddot @geriux @hbhalodia @iamibrahimriaz @iworks @jameskoster @jasmussen @jeryj @jffng @jorgefilipecosta @jsnajdr @juanmaguitar @kevin940726 @luisherranz @MaggieCabrera @Mamaduka @matiasbenedetto @michalczaplinski @mikachan @mirka @ndiego @ntsekouras @oandregal @ockham @peterwilsoncc @ramonjd @richtabor @roygbyte @SantosGuillamot @scruffian @shail-mehta @shreya0204 @sirreal @stokesman @swissspidy @t-hamano @talldan @tellthemachines @tyxla @vipul0425 @westonruter @youknowriad + + + + = 18.7.1 = diff --git a/docs/README.md b/docs/README.md index 7ead7bbef5aa02..10cac14714276b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -178,6 +178,22 @@ The Block Editor Handbook is designed for those looking to create and develop fo ### äø»ćŖå¤‰ę›“ +2024/9/29 +- [ć‚¹ć‚æć‚¤ćƒ«ćØć‚¹ć‚æć‚¤ćƒ«ć‚·ćƒ¼ćƒˆć®åˆ©ē”Ø](https://ja.wordpress.org/team/handbook/block-editor/how-to-guides/block-tutorial/applying-styles-with-stylesheets/) - css/scss ć®ć‚¤ćƒ³ćƒćƒ¼ćƒˆć®ę³Øčؘ [#61252](https://github.com/WordPress/gutenberg/pull/61252) +- [API ćƒćƒ¼ć‚øćƒ§ćƒ³](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/block-api/block-api-versions/) - iframe ć®ę”ä»¶ [#65375](https://github.com/WordPress/gutenberg/pull/65375) +- [block.json 恮惔ć‚æćƒ‡ćƒ¼ć‚æ](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/block-api/block-metadata/) - PHPć«ć‚ˆć‚‹ćƒćƒŖć‚Øćƒ¼ć‚·ćƒ§ćƒ³ćƒŖć‚¹ćƒˆ [#62092](https://github.com/WordPress/gutenberg/pull/62092) +[ć‚³ć‚¢ćƒ–ćƒ­ćƒƒć‚ÆćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/core-blocks/) - ē”»åƒ blob [#63076](https://github.com/WordPress/gutenberg/pull/63076)态margin [#63546](https://github.com/WordPress/gutenberg/pull/63546)ć€å‹•ē”» blob [#63238](https://github.com/WordPress/gutenberg/pull/63238)ć€éŸ³å£° blob [#63257](https://github.com/WordPress/gutenberg/pull/63257)ć€ćƒ•ć‚”ć‚¤ćƒ« blob [#63282](https://github.com/WordPress/gutenberg/pull/63282)ć€ć‚°ćƒ«ćƒ¼ćƒ— shadow [#63295](https://github.com/WordPress/gutenberg/pull/63295)态ć‚æćƒ¼ćƒ äø€č¦§ label, showLabel [#56364](https://github.com/WordPress/gutenberg/pull/56364)ć€å¼•ē”Ø margin, padding [#63545](https://github.com/WordPress/gutenberg/pull/63545)态align [#64188](https://github.com/WordPress/gutenberg/pull/64188)态惜ć‚æćƒ³ color, padding [#63538](https://github.com/WordPress/gutenberg/pull/63538)态ꤜē“¢ margin [#63547](https://github.com/WordPress/gutenberg/pull/63547)ć€ęœ€ę–°ć®ć‚³ćƒ”ćƒ³ćƒˆ color [#63419](https://github.com/WordPress/gutenberg/pull/63419)态見å‡ŗ恗 levelOptions [#63535](https://github.com/WordPress/gutenberg/pull/63535)ć€ćƒ­ć‚°ć‚¤ćƒ³ / ćƒ­ć‚°ć‚¢ć‚¦ćƒˆ color [#63550](https://github.com/WordPress/gutenberg/pull/63550)ć€ć‚µć‚¤ćƒˆć®ć‚­ćƒ£ćƒƒćƒćƒ•ćƒ¬ćƒ¼ć‚ŗ levelOptions [#64113](https://github.com/WordPress/gutenberg/pull/64113)ć€ć‚µć‚¤ćƒˆć®ć‚æć‚¤ćƒˆćƒ« levelOptions [#64111](https://github.com/WordPress/gutenberg/pull/64111)态ć‚Æć‚ØćƒŖćƒ¼ć‚æć‚¤ćƒˆćƒ« levelOptions [#64107](https://github.com/WordPress/gutenberg/pull/64107)ć€ć‚³ćƒ”ćƒ³ćƒˆć‚æć‚¤ćƒˆćƒ« levelOptions [#64103](https://github.com/WordPress/gutenberg/pull/64103)态ć‚æć‚¤ćƒˆćƒ« levelOptions [#64106](https://github.com/WordPress/gutenberg/pull/64106)态ćƒŖć‚¹ćƒˆ čŖ¬ę˜Ž [#64025](https://github.com/WordPress/gutenberg/pull/64025)态ćƒŖć‚¹ćƒˆé …ē›® čŖ¬ę˜Ž [#64025](https://github.com/WordPress/gutenberg/pull/64025)态anchor [#48758](https://github.com/WordPress/gutenberg/pull/48758)态ć‚æ悰ć‚Æćƒ©ć‚¦ćƒ‰ čŖ¬ę˜Ž [#64151](https://github.com/WordPress/gutenberg/pull/64151)态 ć‚µćƒ–ćƒ”ćƒ‹ćƒ„ćƒ¼ typography [#65060](https://github.com/WordPress/gutenberg/pull/65060)ć€ć‚«ćƒ†ć‚“ćƒŖćƒ¼äø€č¦§ -> ć‚æćƒ¼ćƒ äø€č¦§ [#65272](https://github.com/WordPress/gutenberg/pull/65272) +- [Interactivity API ćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/) - interactivity 恮čæ½åŠ  [#64061](https://github.com/WordPress/gutenberg/pull/64061) +- [Core Concepts](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/core-concepts/) - ę–°č¦ +- [Server-side rendering: Processing directives on the server](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/core-concepts/server-side-rendering/) - ę–°č¦ +- [The Reactive and Declarative mindset](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/core-concepts/the-reactive-and-declarative-mindset/) - ę–°č¦ +- [Understanding global state, local context and derived state](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/core-concepts/undestanding-global-state-local-context-and-derived-state/) - ę–°č¦ +- [Using TypeScript](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/core-concepts/using-typescript/) - ę–°č¦ +- [Interactivity API FAQ](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/iapi-faq/) - Alpine vs Prect [#63593](https://github.com/WordPress/gutenberg/pull/63593) +- [API ćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/api-reference/) - getContext ć®åå‰ē©ŗé–“å¼•ę•° [#63411](https://github.com/WordPress/gutenberg/pull/63411)态wp_interactivity_state [#64356](https://github.com/WordPress/gutenberg/pull/64356) +- [SlotFill ćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/slotfills/) - ę”ä»¶ä»˜ćć®ćƒ¬ćƒ³ćƒ€ćƒ¼ [#64807](https://github.com/WordPress/gutenberg/pull/64807) +- [theme.json ćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/theme-json-reference/theme-json-living/) - background backgroundAttachment [#61382](https://github.com/WordPress/gutenberg/pull/61382)ć€ę§‹ęˆå¤‰ę›“ [#63591](https://github.com/WordPress/gutenberg/pull/63591)态[#63868](https://github.com/WordPress/gutenberg/pull/63868) + 2024/7/7 - [ć‚Øćƒ‡ć‚£ć‚æćƒ¼ę©Ÿčƒ½ć®ē„”効化](https://ja.wordpress.org/team/handbook/block-editor/how-to-guides/curating-the-editor-experience/disable-editor-functionality/) - RichText ć®ä¾‹ [#63065](https://github.com/WordPress/gutenberg/pull/63065) - [theme.json](https://ja.wordpress.org/team/handbook/block-editor/how-to-guides/themes/global-settings-and-styles/) - spacing 恮čŖ¬ę˜Žć®å‰Šé™¤ [#61842](https://github.com/WordPress/gutenberg/pull/61842)态editor-spacing-sizes [#62252](https://github.com/WordPress/gutenberg/pull/62252) diff --git a/docs/assets/plugin-sidebar-closed-state.png b/docs/assets/plugin-sidebar-closed-state.png deleted file mode 100644 index 025da900ffcdd5..00000000000000 Binary files a/docs/assets/plugin-sidebar-closed-state.png and /dev/null differ diff --git a/docs/assets/plugin-sidebar-open-state.png b/docs/assets/plugin-sidebar-open-state.png index f1c3781a500f00..a114f7119020ab 100644 Binary files a/docs/assets/plugin-sidebar-open-state.png and b/docs/assets/plugin-sidebar-open-state.png differ diff --git a/docs/contributors/code/coding-guidelines.md b/docs/contributors/code/coding-guidelines.md index a876cb3f3ef483..4779a2ce96e787 100644 --- a/docs/contributors/code/coding-guidelines.md +++ b/docs/contributors/code/coding-guidelines.md @@ -65,7 +65,7 @@ Examples of styles that appear in both the theme and the editor include gallery ## JavaScript -JavaScript in Gutenberg uses modern language features of the [ECMAScript language specification](https://www.ecma-international.org/ecma-262/) as well as the [JSX language syntax extension](https://reactjs.org/docs/introducing-jsx.html). These are enabled through a combination of preset configurations, notably [`@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default) which is used as a preset in the project's [Babel](https://babeljs.io/) configuration. +JavaScript in Gutenberg uses modern language features of the [ECMAScript language specification](https://www.ecma-international.org/ecma-262/) as well as the [JSX language syntax extension](https://react.dev/learn/writing-markup-with-jsx). These are enabled through a combination of preset configurations, notably [`@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default) which is used as a preset in the project's [Babel](https://babeljs.io/) configuration. While the [staged process](https://tc39.es/process-document/) for introducing a new JavaScript language feature offers an opportunity to use new features before they are considered complete, **the Gutenberg project and the `@wordpress/babel-preset-default` configuration will only target support for proposals which have reached Stage 4 ("Finished")**. @@ -531,7 +531,7 @@ alert( `My name is ${ name }.` ); ### React components -It is preferred to implement all components as [function components](https://reactjs.org/docs/components-and-props.html), using [hooks](https://reactjs.org/docs/hooks-reference.html) to manage component state and lifecycle. With the exception of [error boundaries](https://reactjs.org/docs/error-boundaries.html), you should never encounter a situation where you must use a class component. Note that the [WordPress guidance on Code Refactoring](https://make.wordpress.org/core/handbook/contribute/code-refactoring/) applies here: There needn't be a concentrated effort to update class components in bulk. Instead, consider it as a good refactoring opportunity in combination with some other change. +It is preferred to implement all components as [function components](https://react.dev/learn/your-first-component), using [hooks](https://react.dev/reference/react/hooks) to manage component state and lifecycle. With the exception of [error boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary), you should never encounter a situation where you must use a class component. Note that the [WordPress guidance on Code Refactoring](https://make.wordpress.org/core/handbook/contribute/code-refactoring/) applies here: There needn't be a concentrated effort to update class components in bulk. Instead, consider it as a good refactoring opportunity in combination with some other change. ## JavaScript documentation using JSDoc @@ -761,7 +761,7 @@ When documenting an example, use the markdown \`\`\` code block to ### Documenting React components -When possible, all components should be implemented as [function components](https://reactjs.org/docs/components-and-props.html#function-and-class-components), using [hooks](https://react.dev/reference/react/hooks) for managing component lifecycle and state. +When possible, all components should be implemented as [function components](https://react.dev/learn/your-first-component), using [hooks](https://react.dev/reference/react/hooks) for managing component lifecycle and state. Documenting a function component should be treated the same as any other function. The primary caveat in documenting a component is being aware that the function typically accepts only a single argument (the "props"), which may include many property members. Use the [dot syntax for parameter properties](https://jsdoc.app/tags-param.html#parameters-with-properties) to document individual prop types. diff --git a/docs/contributors/code/deprecations.md b/docs/contributors/code/deprecations.md index 51d4b0b293ff0a..415600617b73a7 100644 --- a/docs/contributors/code/deprecations.md +++ b/docs/contributors/code/deprecations.md @@ -183,7 +183,7 @@ For features included in the Gutenberg plugin, the deprecation policy is intende ## 3.8.0 -- `wp.components.withContext` has been removed. Please use `wp.element.createContext` instead. See: https://reactjs.org/docs/context.html. +- `wp.components.withContext` has been removed. Please use `wp.element.createContext` instead. See: https://react.dev/reference/react/createContext. - `wp.coreBlocks.registerCoreBlocks` has been removed. Please use `wp.blockLibrary.registerCoreBlocks` instead. - `wp.editor.DocumentTitle` component has been removed. - `getDocumentTitle` selector (`core/editor`) has been removed. diff --git a/docs/contributors/code/getting-started-with-code-contribution.md b/docs/contributors/code/getting-started-with-code-contribution.md index eb482a6dca2416..f9dd42ccb1fe58 100644 --- a/docs/contributors/code/getting-started-with-code-contribution.md +++ b/docs/contributors/code/getting-started-with-code-contribution.md @@ -44,9 +44,9 @@ We recommend using the [Node Version Manager](https://github.com/nvm-sh/nvm) (nv **ę³Øꄏ**: Windows 10 Home Edition 恫 Docker ć‚’ć‚¤ćƒ³ć‚¹ćƒˆćƒ¼ćƒ«ć™ć‚‹ć«ćÆ态[install instructions from Docker for Windows with WSL2](https://docs.docker.com/docker-for-windows/wsl/) ć«å¾“ć£ć¦ćć ć•ć„ć€‚ -Docker ć‚»ćƒƒćƒˆć‚¢ćƒƒćƒ—ć®ä»£ę›æćØ恗恦ćÆ态[Local](https://localwp.com/)态[WampServer](http://www.wampserver.com/en/)态[MAMP](https://www.mamp.info/)ć‚’åˆ©ē”Øć§ćć¾ć™ć€‚ć¾ćŸć€ćƒŖćƒ¢ćƒ¼ćƒˆć‚µćƒ¼ćƒćƒ¼ć§ć‚‚ę§‹ć„ć¾ć›ć‚“ć€‚ +Docker ć‚»ćƒƒćƒˆć‚¢ćƒƒćƒ—ć®ä»£ę›æćØ恗恦ćÆ态[Local](https://localwp.com/)态[WampServer](https://wampserver.aviatechno.net/)态[MAMP](https://www.mamp.info/)ć‚’åˆ©ē”Øć§ćć¾ć™ć€‚ć¾ćŸćÆćƒŖćƒ¢ćƒ¼ćƒˆć‚µćƒ¼ćƒćƒ¼ć§ć‚‚ę§‹ć„ć¾ć›ć‚“ć€‚ -Docker ćØ `wp-env` ć®ä»£ć‚ć‚Šć«ć€[Local](https://localwp.com/)态[WampServer](http://www.wampserver.com/en/)ć€ć¾ćŸćÆ [MAMP](https://www.mamp.info/) 悒ä½æē”Øć—ć¦ć€ćƒ­ćƒ¼ć‚«ćƒ«ć® WordPress ē’°å¢ƒć‚’å®Ÿč”Œć§ćć¾ć™ć€‚ćć‚Œć«ćÆ态Gutenberg ć®ćƒ‡ć‚£ćƒ¬ć‚Æ惈ćƒŖć«ć‚·ćƒ³ćƒœćƒŖ惃ć‚ÆćƒŖćƒ³ć‚Æć‚’ä½œęˆć™ć‚‹ć‹ć€`wp-content/plugins` ćƒ‡ć‚£ćƒ¬ć‚Æ惈ćƒŖć«ć‚³ćƒ”ćƒ¼ć—ć¦ć€ 通åøøć®ćƒ—ćƒ©ć‚°ć‚¤ćƒ³ćØć—ć¦ć‚¤ćƒ³ć‚¹ćƒˆćƒ¼ćƒ«ć—ć¦ćć ć•ć„ć€‚ +Docker ćØ `wp-env` ć®ä»£ć‚ć‚Šć«ć€[Local](https://localwp.com/)态[WampServer](https://wampserver.aviatechno.net/)ć€ć¾ćŸćÆ [MAMP](https://www.mamp.info/) 悒ä½æē”Øć—ć¦ć€ćƒ­ćƒ¼ć‚«ćƒ«ć® WordPress ē’°å¢ƒć‚’å®Ÿč”Œć§ćć¾ć™ć€‚ćć‚Œć«ćÆ適切ćŖ `wp-content/plugins` 恫 Gutenberg ćƒ‡ć‚£ćƒ¬ć‚Æ惈ćƒŖć®ć‚·ćƒ³ćƒœćƒŖ惃ć‚ÆćƒŖćƒ³ć‚Æć‚’ä½œęˆć™ć‚‹ć‹ć‚³ćƒ”ćƒ¼ć—ć¦ć€é€šåøøć®ćƒ—ćƒ©ć‚°ć‚¤ćƒ³ćØć—ć¦ć‚¤ćƒ³ć‚¹ćƒˆćƒ¼ćƒ«ć—ć¦ćć ć•ć„ć€‚ diff --git a/docs/explanations/user-interface/design-resources.md b/docs/explanations/user-interface/design-resources.md index 9b3dab895e5fe3..33d062d75a80ed 100644 --- a/docs/explanations/user-interface/design-resources.md +++ b/docs/explanations/user-interface/design-resources.md @@ -9,9 +9,9 @@ ## Figma -[WordPress ćƒ‡ć‚¶ć‚¤ćƒ³ćƒćƒ¼ćƒ ](https://make.wordpress.org/design/) ćÆć‚³ćƒ©ćƒœćƒ¬ćƒ¼ć‚·ćƒ§ćƒ³ćØćƒÆćƒ¼ć‚Æć®å…±ęœ‰ć« [Figma](https://www.figma.com/) 悒ä½æē”Øć—ć¾ć™ć€‚ć‚³ćƒ³ćƒˆćƒŖćƒ“ćƒ„ćƒ¼ć‚·ćƒ§ćƒ³ć‚’č€ƒćˆć¦ć„ć‚‹å “åˆćÆ态[Slack](https://make.wordpress.org/chat/) 恮 [#design ćƒćƒ£ćƒ³ćƒćƒ«](https://app.slack.com/client/T024MFP4J/C02S78ZAL) ć«å‚åŠ ć—ć€Figma ć®ćƒ•ćƒŖćƒ¼ć‚¢ć‚«ć‚¦ćƒ³ćƒˆć®ć‚»ćƒƒćƒˆć‚¢ćƒƒćƒ—ć‚’ä¾é ¼ć—ć¦ćć ć•ć„ć€‚WordPress 恧ä½æē”Øć•ć‚Œć‚‹ć‚³ćƒ³ćƒćƒ¼ćƒćƒ³ćƒˆć®ęœ‰ē”ØćŖćƒ©ć‚¤ćƒ–ćƒ©ćƒŖć«ć‚¢ć‚Æć‚»ć‚¹ć§ćć‚‹ć‚ˆć†ć«ćŖć‚Šć¾ć™ć€‚ćƒ©ć‚¤ćƒ–ćƒ©ćƒŖćÆå®‰å®šć—ć¦ćŠć‚Šć€å®Œå…Øć«ć‚µćƒćƒ¼ćƒˆć•ć‚Œć€ęœ€ę–°ē‰ˆć§ć€ćƒ‡ć‚¶ć‚¤ćƒ³ć‚„ćƒ—ćƒ­ćƒˆć‚æć‚¤ćƒ—ć§ć™ćć«ä½æćˆć¾ć™ć€‚ +[WordPress ćƒ‡ć‚¶ć‚¤ćƒ³ćƒćƒ¼ćƒ ](https://make.wordpress.org/design/)ćÆć‚³ćƒ©ćƒœćƒ¬ćƒ¼ć‚·ćƒ§ćƒ³ćØćƒÆćƒ¼ć‚Æć®å…±ęœ‰ć« [Figma](https://www.figma.com/) 悒ä½æē”Øć—ć¾ć™ć€‚ć‚³ćƒ³ćƒˆćƒŖćƒ“ćƒ„ćƒ¼ć‚·ćƒ§ćƒ³ć‚’č€ƒćˆć¦ć„ć‚‹å “åˆćÆ态[WordPress Figma ćƒ‡ć‚¶ć‚¤ćƒ³ćƒ©ć‚¤ćƒ–ćƒ©ćƒŖ](https://make.wordpress.org/design/handbook/get-involved/tools-figma/)悒ä½æē”Øć—ć¦ćƒ¢ćƒƒć‚Æć‚¢ćƒƒćƒ—ć‚’ä½œęˆć§ćć¾ć™ć€‚ć¾ćŸć€[Slack](https://make.wordpress.org/chat/) 恮 [#design ćƒćƒ£ćƒ³ćƒćƒ«](https://app.slack.com/client/T024MFP4J/C02S78ZAL)ć«å‚åŠ ć—ć¦ć€ć‚¢ćƒ‰ćƒć‚¤ć‚¹ćŖć©ć‚’ę±‚ć‚ć‚‰ć‚Œć¾ć™ć€‚Figma ć®ć‚¢ć‚«ć‚¦ćƒ³ćƒˆćÆē„”ę–™ć§ć€å…±ęœ‰ćƒ©ć‚¤ćƒ–ćƒ©ćƒŖć‹ć‚‰ć‚³ćƒ³ćƒćƒ¼ćƒćƒ³ćƒˆć‚’ä½æē”Ø恧恍态ē·Ø集恌åæ…要ćŖ堓合ćÆćƒ•ć‚”ć‚¤ćƒ«ć‚’ćƒ‰ćƒ©ćƒ•ćƒˆć«č¤‡č£½ć§ćć¾ć™ć€‚WordPress ćƒ©ć‚¤ćƒ–ćƒ©ćƒŖćøć®å®Œå…ØćŖē·Øé›†ć‚¢ć‚Æć‚»ć‚¹ćÆę”Æꉕ恄ęøˆćæć§ć€ćƒ‡ć‚¶ć‚¤ćƒ³ćƒćƒ¼ćƒ ć®ćŸć‚ć«äŗˆē“„ć•ć‚Œć¦ć„ć¾ć™ć€‚ + -惀悤惊惟惃ć‚Æ惖惭惃ć‚Æ恧ćÆć€ćƒ•ć‚©ćƒ³ćƒˆć‚Øćƒ³ćƒ‰ć®ćƒžćƒ¼ć‚Æć‚¢ćƒƒćƒ—ćÆć‚µćƒ¼ćƒćƒ¼ć‚µć‚¤ćƒ‰ć§ćƒ¬ćƒ³ćƒ€ćƒ¼ć•ć‚Œć¾ć™ć€‚`save` é–¢ę•°ć§ `useBlockProps.save()` 悒ä½æē”Øć—ćŸć®ćØåŒć˜ć‚ˆć†ć«ć€[`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/) é–¢ę•°ć‚’åˆ©ē”Ø恗恦态åæ…要ćŖć‚Æćƒ©ć‚¹ćØå±žę€§ć‚’å‡ŗåŠ›ć§ćć¾ć™ć€‚[例](https://github.com/WordPress/block-development-examples/blob/f68640f42d993f0866d1879f67c73910285ca114/plugins/block-dynamic-rendering-64756b/src/render.php#L11)ć‚’å‚ē…§ć—ć¦ćć ć•ć„ć€‚ +惀悤惊惟惃ć‚Æ惖惭惃ć‚Æ恧ćÆć€ćƒ•ćƒ­ćƒ³ćƒˆć‚Øćƒ³ćƒ‰ć®ćƒžćƒ¼ć‚Æć‚¢ćƒƒćƒ—ćÆć‚µćƒ¼ćƒćƒ¼ć‚µć‚¤ćƒ‰ć§ćƒ¬ćƒ³ćƒ€ćƒ¼ć•ć‚Œć¾ć™ć€‚`save` é–¢ę•°ć§ `useBlockProps.save()` 悒ä½æē”Øć—ćŸć®ćØåŒć˜ć‚ˆć†ć«ć€[`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/) é–¢ę•°ć‚’åˆ©ē”Ø恗恦态åæ…要ćŖć‚Æćƒ©ć‚¹ćØå±žę€§ć‚’å‡ŗåŠ›ć§ćć¾ć™ć€‚[例](https://github.com/WordPress/block-development-examples/blob/f68640f42d993f0866d1879f67c73910285ca114/plugins/block-dynamic-rendering-64756b/src/render.php#L11)ć‚’å‚ē…§ć—ć¦ćć ć•ć„ć€‚ ```php

> diff --git a/docs/getting-started/fundamentals/static-dynamic-rendering.md b/docs/getting-started/fundamentals/static-dynamic-rendering.md index 8d199f66cccd2a..dfb6a7123b44b3 100644 --- a/docs/getting-started/fundamentals/static-dynamic-rendering.md +++ b/docs/getting-started/fundamentals/static-dynamic-rendering.md @@ -61,7 +61,7 @@ Dynamic blocks, which we'll explore in the following section, can specify an ini For a practical demonstration of how this works, refer to the [Building your first block](/docs/getting-started/tutorial.md) tutorial. Specifically, the [Adding static rendering](/docs/getting-started/tutorial.md#adding-static-rendering) section illustrates how a block can have both a saved HTML structure and dynamic rendering capabilities.

-WordPress provides mechanisms like the render_block are the $render_callback function to alter the saved HTML of a block before it appears on the front end. These tools offer developers the capability to customize block output dynamically, catering to complex and interactive user experiences. +WordPress provides mechanisms like the render_block and the render_callback function to alter the saved HTML of a block before it appears on the front end. These tools offer developers the capability to customize block output dynamically, catering to complex and interactive user experiences.
Additional examples of WordPress blocks that use static rendering, meaning their output is fixed at the time of saving and doesn't rely on server-side processing, include: diff --git a/docs/getting-started/tutorial.md b/docs/getting-started/tutorial.md index 22f5a3223726e7..ff8f350e54acec 100644 --- a/docs/getting-started/tutorial.md +++ b/docs/getting-started/tutorial.md @@ -64,10 +64,10 @@ If you don't have one or more of these items, the [Block Development Environment -> ć“ć®ćƒćƒ„ćƒ¼ćƒˆćƒŖć‚¢ćƒ«ć§ćÆ态[`wp-env`](https://ja.wordpress.org/team/handbook/block-editor/getting-started/devenv/get-started-with-wp-env/) 悒ä½æē”Ø恗恦 WordPress ć®ćƒ­ćƒ¼ć‚«ćƒ«é–‹ē™ŗē’°å¢ƒć‚’ä½œęˆć—ć¾ć™ć€‚ć™ć§ć«å„½ćæć®ćƒ­ćƒ¼ć‚«ćƒ«é–‹ē™ŗćƒ„ćƒ¼ćƒ«ćŒć‚ć‚Œć°ć€č‡Ŗē”±ć«ćć®ćƒ„ćƒ¼ćƒ«ć‚’ä½æē”Øć—ć¦ćć ć•ć„ć€‚ +> ć“ć®ćƒćƒ„ćƒ¼ćƒˆćƒŖć‚¢ćƒ«ć§ćÆ态[`wp-env`](https://ja.wordpress.org/team/handbook/block-editor/getting-started/devenv/get-started-with-wp-env/) 悒ä½æē”Ø恗恦 WordPress ć®ćƒ­ćƒ¼ć‚«ćƒ«é–‹ē™ŗē’°å¢ƒć‚’ä½œęˆć—ć¾ć™ć€‚ćŸć ć—ć€äøŠčæ°ć®å‰ęę”件悒ęŗ€ćŸć™ä»»ę„ć®é–‹ē™ŗē’°å¢ƒć‚’ä½æē”Øć—ć¦ć‚‚ę§‹ć„ć¾ć›ć‚“ć€‚ [`edit.js`](https://ja.wordpress.org/team/handbook/block-editor/getting-started/fundamentals/file-structure-of-a-block/#editjs) ćƒ•ć‚”ć‚¤ćƒ«ćÆć€ć“ć®ćƒ–ćƒ­ćƒƒć‚ÆćŒć©ć®ć‚ˆć†ć«ę©Ÿčƒ½ć—ć€ć©ć®ć‚ˆć†ć«ć‚Øćƒ‡ć‚£ć‚æćƒ¼ć«č”Øē¤ŗć•ć‚Œć‚‹ć‹ć‚’åˆ¶å¾”ć—ć¾ć™ć€‚ē¾åœØć€ćƒ¦ćƒ¼ć‚¶ćƒ¼ć«ćÆćƒ”ćƒƒć‚»ćƒ¼ć‚ø怌Copyright Date Block - hello from the editor!怍恌č”Øē¤ŗć•ć‚Œć¾ć™ć€‚ć“ć‚Œć‚’å¤‰ę›“ć—ć¾ć—ć‚‡ć†ć€‚ @@ -1095,13 +1095,13 @@ Start by adding a variable called `$display_date` and replicate what you did in ć¾ćšć€å¤‰ę•° `$display_date` 悒čæ½åŠ ć—恦态äøŠć® `Edit()` é–¢ę•°ć§ć®å®Ÿč£…ć‚’ē¹°ć‚Ščæ”ć—ć¾ć™ć€‚ ć“ć®å¤‰ę•°ć«ćÆ `startingYear` å±žę€§ć®å€¤ćØ `$current_year` å¤‰ę•°ć‚’ em ćƒ€ćƒƒć‚·ćƒ„ć§åŒŗåˆ‡ć£ć¦č”Øē¤ŗć™ć‚‹ć‹ć€ć¾ćŸćÆ `showStartingYear` å±žę€§ćŒ `false` 恮ćØ恍 `$current_year` ć®å€¤ć ć‘ć‚’č”Øē¤ŗć—ć¾ć™ć€‚ block.jsonć§ęŒ‡å®šć•ć‚ŒćŸćƒ•ć‚”ć‚¤ćƒ«ćÆ态č‡Ŗ動ēš„恫ć‚Øćƒ³ć‚­ćƒ„ćƒ¼ć•ć‚Œć¾ć™ć€‚ + +> `wordpress/scripts` 悒ä½æē”Øć—ć¦ć„ć‚‹å “åˆć€`@wordpress/scripts` ćŒć‚¹ć‚æć‚¤ćƒ«ć‚·ćƒ¼ćƒˆć‚’å‡¦ē†ć™ć‚‹ćŸć‚ć«ć€åƾåæœć™ć‚‹ JavaScript ćƒ•ć‚”ć‚¤ćƒ«å†…ć«ć‚¹ć‚æć‚¤ćƒ«ć‚·ćƒ¼ćƒˆć‚’ć‚¤ćƒ³ćƒćƒ¼ćƒˆć™ć‚‹åæ…č¦ćŒć‚ć‚Šć¾ć™ć€‚ +> +> 例: +> - `edit.js` å†…ć«ćÆ `import './editor.scss';` ć‚’čµ·ćć¾ć™ć€‚ +> - `index.js` å†…ć«ćÆ `import './style.scss';` ć‚’čµ·ćć¾ć™ć€‚ +> - `view.js` å†…ć«ćÆ `import './view.scss';` ć‚’čµ·ćć¾ć™ (ć‚¤ćƒ³ć‚æ惩ć‚Æćƒ†ć‚£ćƒ–ćƒ–ćƒ­ćƒƒć‚Æćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆ)怂 + -**ę³Øꄏ:** ć‚¤ćƒ³ć‚Æćƒ«ćƒ¼ćƒ‰ć™ć‚‹ćƒ•ć‚”ć‚¤ćƒ«ćŒč¤‡ę•°ć‚ć‚‹å “åˆćÆć€ä»–ć®ćƒ—ćƒ©ć‚°ć‚¤ćƒ³ć‚„ćƒ†ćƒ¼ćƒžćØåŒę§˜ć«ć€ęؙęŗ–恮 `wp_enqueue_style` é–¢ę•°ć‚’ä½æē”Øć§ćć¾ć™ć€‚ćƒ–ćƒ­ćƒƒć‚Æć‚Øćƒ‡ć‚£ć‚æ恧ćÆ态仄äø‹ć®ćƒ•ćƒƒć‚Æ悒ä½æē”Øć§ćć¾ć™ć€‚ +**ę³Øꄏ:** ć‚¤ćƒ³ć‚Æćƒ«ćƒ¼ćƒ‰ć™ć‚‹ćƒ•ć‚”ć‚¤ćƒ«ćŒč¤‡ę•°ć‚ć‚‹å “åˆćÆć€ä»–ć®ćƒ—ćƒ©ć‚°ć‚¤ćƒ³ć‚„ćƒ†ćƒ¼ćƒžćØåŒę§˜ć«ć€ęؙęŗ–恮 `wp_enqueue_style` é–¢ę•°ć‚’ä½æē”Øć§ćć¾ć™ć€‚ćƒ–ćƒ­ćƒƒć‚Æć‚Øćƒ‡ć‚£ć‚æćƒ¼ć§ćÆ态仄äø‹ć®ćƒ•ćƒƒć‚Æ悒ä½æē”Øć§ćć¾ć™ć€‚ -ćƒ•ć‚£ćƒ¼ćƒ«ćƒ‰ćÆē©ŗć§å§‹ć¾ć‚Šć€å†…å®¹ćÆ `searchTerm` ć‚¹ćƒ†ćƒ¼ćƒˆå€¤ć«ę ¼ē“ć•ć‚Œć¾ć™ć€‚[useState](https://reactjs.org/docs/hooks-state.html) 惕惃ć‚ÆćŒć‚ˆćåˆ†ć‹ć‚‰ćŖć„å “åˆćÆ态[Reactć®ćƒ‰ć‚­ćƒ„ćƒ”ćƒ³ćƒˆ](https://reactjs.org/docs/hooks-state.html)ć‚’å‚ē…§ć—ć¦ćć ć•ć„ć€‚ +ćƒ•ć‚£ćƒ¼ćƒ«ćƒ‰ćÆē©ŗć§å§‹ć¾ć‚Šć€å†…å®¹ćÆ `searchTerm` ć‚¹ćƒ†ćƒ¼ćƒˆå€¤ć«ę ¼ē“ć•ć‚Œć¾ć™ć€‚[useState](https://react.dev/reference/react/useState) 惕惃ć‚ÆćŒć‚ˆćåˆ†ć‹ć‚‰ćŖć„å “åˆćÆ态[Reactć®ćƒ‰ć‚­ćƒ„ćƒ”ćƒ³ćƒˆ](https://react.dev/reference/react/useState)ć‚’å‚ē…§ć—ć¦ćć ć•ć„ć€‚ -ć“ć®ćƒ—ćƒ­ć‚»ć‚¹ć§ćÆ态ć‚Øćƒ‡ć‚£ć‚æćƒ¼ć®ćƒ¬ć‚¤ć‚¢ć‚¦ćƒˆć®ę øćØćŖ悋éƒØåˆ†ć‚’č‡Ŗ動ē”Ÿęˆć—ć¾ć™ć€‚åŒę™‚ć«ć„ćć¤ć‹ć®ē‰¹ę®ŠćŖ[ć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆćƒ—ćƒ­ćƒć‚¤ćƒ€ćƒ¼](https://reactjs.org/docs/context.html#contextprovider)悂å‡ŗåŠ›ć—ć€ć‚³ćƒ³ćƒćƒ¼ćƒćƒ³ćƒˆć®éšŽå±¤å…Øä½“ć§åˆ©ē”ØåÆčƒ½ćŖē‰¹å®šć®ę©Ÿčƒ½ć‚’ä½œęˆć—ć¾ć™ć€‚ +ć“ć®ćƒ—ćƒ­ć‚»ć‚¹ć§ćÆ态ć‚Øćƒ‡ć‚£ć‚æćƒ¼ć®ćƒ¬ć‚¤ć‚¢ć‚¦ćƒˆć®ę øćØćŖ悋éƒØåˆ†ć‚’č‡Ŗ動ē”Ÿęˆć—ć¾ć™ć€‚åŒę™‚ć«ć„ćć¤ć‹ć®ē‰¹ę®ŠćŖ[ć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆćƒ—ćƒ­ćƒć‚¤ćƒ€ćƒ¼](https://react.dev/reference/react/createContext#provider)悂å‡ŗåŠ›ć—ć€ć‚³ćƒ³ćƒćƒ¼ćƒćƒ³ćƒˆć®éšŽå±¤å…Øä½“ć§åˆ©ē”ØåÆčƒ½ćŖē‰¹å®šć®ę©Ÿčƒ½ć‚’ä½œęˆć—ć¾ć™ć€‚ č©³ć—ćč¦‹ć¦ć„ćć¾ć™ć€‚ diff --git a/docs/japanese-changelog.md b/docs/japanese-changelog.md index e04b51e692cea0..3c4f2a5140797b 100644 --- a/docs/japanese-changelog.md +++ b/docs/japanese-changelog.md @@ -2,6 +2,23 @@ ēæ»čØ³ć®é€²ę—ć‚„ć€ęœ€ę–°ć®č‹±čŖžē‰ˆć§åŒęœŸć—ćŸéš›ć«ę°—ć„恄恟ē®‡ę‰€ć‚’ćƒ”ćƒ¢ć—ć¦ć„ć¾ć™ć€‚ +2024/9/29 + +- [ć‚¹ć‚æć‚¤ćƒ«ćØć‚¹ć‚æć‚¤ćƒ«ć‚·ćƒ¼ćƒˆć®åˆ©ē”Ø](https://ja.wordpress.org/team/handbook/block-editor/how-to-guides/block-tutorial/applying-styles-with-stylesheets/) - css/scss ć®ć‚¤ćƒ³ćƒćƒ¼ćƒˆć®ę³Øčؘ [#61252](https://github.com/WordPress/gutenberg/pull/61252) +- [API ćƒćƒ¼ć‚øćƒ§ćƒ³](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/block-api/block-api-versions/) - iframe ć®ę”ä»¶ [#65375](https://github.com/WordPress/gutenberg/pull/65375) +- [block.json 恮惔ć‚æćƒ‡ćƒ¼ć‚æ](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/block-api/block-metadata/) - PHPć«ć‚ˆć‚‹ćƒćƒŖć‚Øćƒ¼ć‚·ćƒ§ćƒ³ćƒŖć‚¹ćƒˆ [#62092](https://github.com/WordPress/gutenberg/pull/62092) +[ć‚³ć‚¢ćƒ–ćƒ­ćƒƒć‚ÆćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/core-blocks/) - ē”»åƒ blob [#63076](https://github.com/WordPress/gutenberg/pull/63076)态margin [#63546](https://github.com/WordPress/gutenberg/pull/63546)ć€å‹•ē”» blob [#63238](https://github.com/WordPress/gutenberg/pull/63238)ć€éŸ³å£° blob [#63257](https://github.com/WordPress/gutenberg/pull/63257)ć€ćƒ•ć‚”ć‚¤ćƒ« blob [#63282](https://github.com/WordPress/gutenberg/pull/63282)ć€ć‚°ćƒ«ćƒ¼ćƒ— shadow [#63295](https://github.com/WordPress/gutenberg/pull/63295)态ć‚æćƒ¼ćƒ äø€č¦§ label, showLabel [#56364](https://github.com/WordPress/gutenberg/pull/56364)ć€å¼•ē”Ø margin, padding [#63545](https://github.com/WordPress/gutenberg/pull/63545)态align [#64188](https://github.com/WordPress/gutenberg/pull/64188)态惜ć‚æćƒ³ color, padding [#63538](https://github.com/WordPress/gutenberg/pull/63538)态ꤜē“¢ margin [#63547](https://github.com/WordPress/gutenberg/pull/63547)ć€ęœ€ę–°ć®ć‚³ćƒ”ćƒ³ćƒˆ color [#63419](https://github.com/WordPress/gutenberg/pull/63419)态見å‡ŗ恗 levelOptions [#63535](https://github.com/WordPress/gutenberg/pull/63535)ć€ćƒ­ć‚°ć‚¤ćƒ³ / ćƒ­ć‚°ć‚¢ć‚¦ćƒˆ color [#63550](https://github.com/WordPress/gutenberg/pull/63550)ć€ć‚µć‚¤ćƒˆć®ć‚­ćƒ£ćƒƒćƒćƒ•ćƒ¬ćƒ¼ć‚ŗ levelOptions [#64113](https://github.com/WordPress/gutenberg/pull/64113)ć€ć‚µć‚¤ćƒˆć®ć‚æć‚¤ćƒˆćƒ« levelOptions [#64111](https://github.com/WordPress/gutenberg/pull/64111)态ć‚Æć‚ØćƒŖćƒ¼ć‚æć‚¤ćƒˆćƒ« levelOptions [#64107](https://github.com/WordPress/gutenberg/pull/64107)ć€ć‚³ćƒ”ćƒ³ćƒˆć‚æć‚¤ćƒˆćƒ« levelOptions [#64103](https://github.com/WordPress/gutenberg/pull/64103)态ć‚æć‚¤ćƒˆćƒ« levelOptions [#64106](https://github.com/WordPress/gutenberg/pull/64106)态ćƒŖć‚¹ćƒˆ čŖ¬ę˜Ž [#64025](https://github.com/WordPress/gutenberg/pull/64025)态ćƒŖć‚¹ćƒˆé …ē›® čŖ¬ę˜Ž [#64025](https://github.com/WordPress/gutenberg/pull/64025)态anchor [#48758](https://github.com/WordPress/gutenberg/pull/48758)态ć‚æ悰ć‚Æćƒ©ć‚¦ćƒ‰ čŖ¬ę˜Ž [#64151](https://github.com/WordPress/gutenberg/pull/64151)态 ć‚µćƒ–ćƒ”ćƒ‹ćƒ„ćƒ¼ typography [#65060](https://github.com/WordPress/gutenberg/pull/65060)ć€ć‚«ćƒ†ć‚“ćƒŖćƒ¼äø€č¦§ -> ć‚æćƒ¼ćƒ äø€č¦§ [#65272](https://github.com/WordPress/gutenberg/pull/65272) +- [Interactivity API ćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/) - interactivity 恮čæ½åŠ  [#64061](https://github.com/WordPress/gutenberg/pull/64061) +- [Core Concepts](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/core-concepts/) - ę–°č¦ +- [Server-side rendering: Processing directives on the server](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/core-concepts/server-side-rendering/) - ę–°č¦ +- [The Reactive and Declarative mindset](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/core-concepts/the-reactive-and-declarative-mindset/) - ę–°č¦ +- [Understanding global state, local context and derived state](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/core-concepts/undestanding-global-state-local-context-and-derived-state/) - ę–°č¦ +- [Using TypeScript](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/core-concepts/using-typescript/) - ę–°č¦ +- [Interactivity API FAQ](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/iapi-faq/) - Alpine vs Prect [#63593](https://github.com/WordPress/gutenberg/pull/63593) +- [API ćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/api-reference/) - getContext ć®åå‰ē©ŗé–“å¼•ę•° [#63411](https://github.com/WordPress/gutenberg/pull/63411)态wp_interactivity_state [#64356](https://github.com/WordPress/gutenberg/pull/64356) +- [SlotFill ćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/slotfills/) - ę”ä»¶ä»˜ćć®ćƒ¬ćƒ³ćƒ€ćƒ¼ [#64807](https://github.com/WordPress/gutenberg/pull/64807) +- [theme.json ćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/theme-json-reference/theme-json-living/) - background backgroundAttachment [#61382](https://github.com/WordPress/gutenberg/pull/61382)ć€ę§‹ęˆå¤‰ę›“ [#63591](https://github.com/WordPress/gutenberg/pull/63591)态[#63868](https://github.com/WordPress/gutenberg/pull/63868) + 2024/7/7 - [ć‚Øćƒ‡ć‚£ć‚æćƒ¼ę©Ÿčƒ½ć®ē„”効化](https://ja.wordpress.org/team/handbook/block-editor/how-to-guides/curating-the-editor-experience/disable-editor-functionality/) - RichText ć®ä¾‹ [#63065](https://github.com/WordPress/gutenberg/pull/63065) diff --git a/docs/manifest.json b/docs/manifest.json index 1704e6d711510f..d76717fbdedfc1 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -497,6 +497,36 @@ "markdown_source": "../docs/reference-guides/interactivity-api/README.md", "parent": "reference-guides" }, + { + "title": "Core Concepts", + "slug": "core-concepts", + "markdown_source": "../docs/reference-guides/interactivity-api/core-concepts/README.md", + "parent": "interactivity-api" + }, + { + "title": "The Reactive and Declarative mindset", + "slug": "the-reactive-and-declarative-mindset", + "markdown_source": "../docs/reference-guides/interactivity-api/core-concepts/the-reactive-and-declarative-mindset.md", + "parent": "core-concepts" + }, + { + "title": "Understanding global state, local context and derived state", + "slug": "undestanding-global-state-local-context-and-derived-state", + "markdown_source": "../docs/reference-guides/interactivity-api/core-concepts/undestanding-global-state-local-context-and-derived-state.md", + "parent": "core-concepts" + }, + { + "title": "Server-side rendering: Processing directives on the server", + "slug": "server-side-rendering", + "markdown_source": "../docs/reference-guides/interactivity-api/core-concepts/server-side-rendering.md", + "parent": "core-concepts" + }, + { + "title": "Using TypeScript", + "slug": "using-typescript", + "markdown_source": "../docs/reference-guides/interactivity-api/core-concepts/using-typescript.md", + "parent": "core-concepts" + }, { "title": "Quick start guide", "slug": "iapi-quick-start-guide", @@ -767,6 +797,12 @@ "markdown_source": "../packages/components/src/combobox-control/README.md", "parent": "components" }, + { + "title": "Composite", + "slug": "composite", + "markdown_source": "../packages/components/src/composite/README.md", + "parent": "components" + }, { "title": "ConfirmDialog", "slug": "confirm-dialog", @@ -1667,6 +1703,12 @@ "markdown_source": "../packages/eslint-plugin/README.md", "parent": "packages" }, + { + "title": "@wordpress/fields", + "slug": "packages-fields", + "markdown_source": "../packages/fields/README.md", + "parent": "packages" + }, { "title": "@wordpress/format-library", "slug": "packages-format-library", diff --git a/docs/reference-guides/block-api/block-api-versions.md b/docs/reference-guides/block-api/block-api-versions.md index 4e20b293646ce1..2a9ca0856be2f0 100644 --- a/docs/reference-guides/block-api/block-api-versions.md +++ b/docs/reference-guides/block-api/block-api-versions.md @@ -11,9 +11,9 @@ This document lists the changes made between the different API versions. ## Version 3 (>= WordPress 6.3) -- ē™»éŒ²ć•ć‚Œć¦ć„ć‚‹ć™ć¹ć¦ć®ćƒ–ćƒ­ćƒƒć‚Æ恌惖惭惃ć‚Æ API ćƒćƒ¼ć‚øćƒ§ćƒ³3仄äøŠć‚’ęŒć”态ć‚Øćƒ‡ć‚£ć‚æćƒ¼ćŒćƒ–ćƒ­ćƒƒć‚Æ恮äø‹ć«ć‚Æćƒ©ć‚·ćƒƒć‚ÆćŖ惔ć‚æ惜惃ć‚Æć‚¹ć‚’ęŒćŸćŖ恑悌恰态ꊕēØæć‚Øćƒ‡ć‚£ć‚æćƒ¼ćÆ iframe åŒ–ć•ć‚Œć¾ć™ć€‚ćƒćƒ¼ć‚øćƒ§ćƒ³3ć‚µćƒćƒ¼ćƒˆć®čæ½åŠ ćÆ态惖惭惃ć‚Æ恌 iframe å†…ć§å‹•ä½œć™ć‚‹ć“ćØć‚’ę„å‘³ć—ć¾ć™ćŒć€ć™ć¹ć¦ć®ćƒ–ćƒ­ćƒƒć‚ÆćŒćƒćƒ¼ć‚øćƒ§ćƒ³3恫åƾåæœć—恦恄ćŖ恑悌恰态惖惭惃ć‚ÆćÆ iframe å¤–ć§ćƒ¬ćƒ³ćƒ€ćƒ¼ć•ć‚Œć‚‹åÆčƒ½ę€§ćŒć‚ć‚Šć¾ć™ć€‚ +- ē™»éŒ²ć•ć‚Œć¦ć„ć‚‹ć™ć¹ć¦ć®ćƒ–ćƒ­ćƒƒć‚Æ恌惖惭惃ć‚Æ API ćƒćƒ¼ć‚øćƒ§ćƒ³3仄äøŠć§ć‚ć‚Œć°ć€ęŠ•ēØæć‚Øćƒ‡ć‚£ć‚æćƒ¼ćÆ iframe åŒ–ć•ć‚Œć¾ć™ć€‚ćƒćƒ¼ć‚øćƒ§ćƒ³3ć‚µćƒćƒ¼ćƒˆć®čæ½åŠ ćÆ态惖惭惃ć‚Æ恌 iframe å†…ć§å‹•ä½œć™ć‚‹ć“ćØć‚’ę„å‘³ć—ć¾ć™ćŒć€ć™ć¹ć¦ć®ćƒ–ćƒ­ćƒƒć‚ÆćŒćƒćƒ¼ć‚øćƒ§ćƒ³3恫åƾåæœć—恦恄ćŖ恑悌恰态惖惭惃ć‚ÆćÆ iframe å¤–ć§ćƒ¬ćƒ³ćƒ€ćƒ¼ć•ć‚Œć‚‹åÆčƒ½ę€§ćŒć‚ć‚Šć¾ć™ć€‚ ## Version 2 (>= WordPress 5.6) diff --git a/docs/reference-guides/block-api/block-attributes.md b/docs/reference-guides/block-api/block-attributes.md index 72fc6a4fb74985..59d50e2095067f 100644 --- a/docs/reference-guides/block-api/block-attributes.md +++ b/docs/reference-guides/block-api/block-attributes.md @@ -4,7 +4,7 @@ # å±žę€§ -[React ć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆ](https://reactjs.org/docs/context.html) 悒ēŸ„ć£ć¦ć„ć‚Œć°ć€ćƒ–ćƒ­ćƒƒć‚Æć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆćÆå¤šćć§åŒć˜ć‚¢ć‚¤ćƒ‡ć‚¢ć‚’ęŽ”ē”Øć—ć¦ć„ć¾ć™ć€‚å®Ÿéš›ć€ćƒ–ćƒ­ćƒƒć‚Æć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆć®ć‚Æćƒ©ć‚¤ć‚¢ćƒ³ćƒˆå“ćƒ–ćƒ­ćƒƒć‚Æć‚Øćƒ‡ć‚£ć‚æćƒ¼ć®å®Ÿč£…ćÆ非åøø恫ē°”単ćŖ React ć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆć®ć‚¢ćƒ—ćƒŖć‚±ćƒ¼ć‚·ćƒ§ćƒ³ć§ć™ć€‚ćƒ–ćƒ­ćƒƒć‚Æć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆćÆ仄äø‹ć®ä¾‹ć§č¦‹ć‚‹ć‚ˆć†ć«ć‚µćƒ¼ćƒćƒ¼å“ `render_callback` å®Ÿč£…ć§ć‚‚ć‚µćƒćƒ¼ćƒˆć•ć‚Œć¦ć„ć¾ć™ć€‚ +[React ć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆ](https://react.dev/learn/passing-data-deeply-with-context) 悒ēŸ„ć£ć¦ć„ć‚Œć°ć€ćƒ–ćƒ­ćƒƒć‚Æć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆćÆå¤šćć§åŒć˜ć‚¢ć‚¤ćƒ‡ć‚¢ć‚’ęŽ”ē”Øć—ć¦ć„ć¾ć™ć€‚å®Ÿéš›ć€ćƒ–ćƒ­ćƒƒć‚Æć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆć®ć‚Æćƒ©ć‚¤ć‚¢ćƒ³ćƒˆå“ćƒ–ćƒ­ćƒƒć‚Æć‚Øćƒ‡ć‚£ć‚æćƒ¼ć®å®Ÿč£…ćÆ非åøø恫ē°”単ćŖ React ć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆć®ć‚¢ćƒ—ćƒŖć‚±ćƒ¼ć‚·ćƒ§ćƒ³ć§ć™ć€‚ćƒ–ćƒ­ćƒƒć‚Æć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆćÆ仄äø‹ć®ä¾‹ć§č¦‹ć‚‹ć‚ˆć†ć«ć‚µćƒ¼ćƒćƒ¼å“ `render_callback` å®Ÿč£…ć§ć‚‚ć‚µćƒćƒ¼ćƒˆć•ć‚Œć¦ć„ć¾ć™ć€‚ - 型: `object[]` +- 型: `object[]|WPDefinedPath` ([č©³ē“°](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/block-api/block-metadata/#wpdefinedpath)) - ć‚Ŗćƒ—ć‚·ćƒ§ćƒ³ - ćƒ­ćƒ¼ć‚«ćƒ©ć‚¤ć‚ŗ: åÆ (`title`, `description`, `keywords` ćć‚Œćžć‚Œć®ćƒćƒŖć‚Øćƒ¼ć‚·ćƒ§ćƒ³ć®ćæ) - ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£: `variations` @@ -780,13 +781,65 @@ See the [Example documentation](/docs/reference-guides/block-api/block-registrat Block Variations is the API that allows a block to have similar versions of it, but all these versions share some common functionality. Each block variation is differentiated from the others by setting some initial attributes or inner blocks. Then at the time when a block is inserted these attributes and/or inner blocks are applied. _Note: In JavaScript you can provide a function for the `isActive` property, and a React element for the `icon`. In the `block.json` file both only support strings_ - -See [the variations documentation](/docs/reference-guides/block-api/block-variations.md) for more details. --> 惖惭惃ć‚Æ惐ćƒŖć‚Øćƒ¼ć‚·ćƒ§ćƒ³ćÆ态恂悋惖惭惃ć‚Æć«é”žä¼¼ć®ćƒćƒ¼ć‚øćƒ§ćƒ³ć‚’ęŒćŸć›ć‚‰ć‚Œć‚‹ API ć§ć™ćŒć€ć“ć‚Œć‚‰ć®ćƒćƒ¼ć‚øćƒ§ćƒ³ćÆć™ć¹ć¦ć€å…±é€šć®ę©Ÿčƒ½ć‚’å…±ęœ‰ć—ć¾ć™ć€‚å„ćƒ–ćƒ­ćƒƒć‚Æ惐ćƒŖć‚Øćƒ¼ć‚·ćƒ§ćƒ³ćÆć€ć„ćć¤ć‹ć®åˆęœŸå±žę€§ć‚„ć‚¤ćƒ³ćƒŠćƒ¼ćƒ–ćƒ­ćƒƒć‚Æ恮čØ­å®šć«ć‚ˆć‚Šć€ä»–ć®ćƒ–ćƒ­ćƒƒć‚ÆćØåŒŗåˆ„ć•ć‚Œć¾ć™ć€‚ćƒ–ćƒ­ćƒƒć‚Æ悒ęŒæå…„ć™ć‚‹ćØć€ć“ć‚Œć‚‰ć®å±žę€§ć‚„ć‚¤ćƒ³ćƒŠćƒ¼ćƒ–ćƒ­ćƒƒć‚ÆćŒé©ē”Øć•ć‚Œć¾ć™ć€‚ _ę³Ø: JavaScript恧ćÆ态`isActive`ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć«é–¢ę•°ć‚’ć€`icon` 恫 React 要ē“ ć‚’ęŒ‡å®šć§ćć¾ć™ć€‚`block.json` ćƒ•ć‚”ć‚¤ćƒ«ć§ćÆć€ć©ć”ć‚‰ć‚‚ę–‡å­—åˆ—ć®ćæć‚’ć‚µćƒćƒ¼ćƒˆć—ć¾ć™ć€‚_ + +Version 6.7恋悉ćÆ `block.json` å†…ć§ PHP ćƒ•ć‚”ć‚¤ćƒ«ć‚’ęŒ‡å®šć—ć¦ć€ć‚µćƒ¼ćƒćƒ¼å“ć§ćƒ–ćƒ­ćƒƒć‚Æ惐ćƒŖć‚Øćƒ¼ć‚·ćƒ§ćƒ³ć®ćƒŖć‚¹ćƒˆć‚’ē”Ÿęˆć§ćć‚‹ć‚ˆć†ć«ćŖć‚Šć¾ć—ćŸć€‚ + +```json +{ "variations": "file:./variations.php" } +``` + + +恓恮 PHP ćƒ•ć‚”ć‚¤ćƒ«ćÆ惖惭惃ć‚Æ惐ćƒŖć‚Øćƒ¼ć‚·ćƒ§ćƒ³ć‚’å«ć‚€é…åˆ—ć‚’ `return` 恙悋恓ćØć‚’ęœŸå¾…ć•ć‚Œć¦ć„ć¾ć™ć€‚PHP ćƒ•ć‚”ć‚¤ćƒ«ć‹ć‚‰čæ”ć‚‹ćƒćƒŖć‚Øćƒ¼ć‚·ćƒ§ćƒ³å†…ć®ę–‡å­—åˆ—ćÆ态č‡Ŗ動ēš„ć«ćƒ­ćƒ¼ć‚«ćƒ©ć‚¤ć‚ŗć•ć‚Œć¾ć›ć‚“ć€‚ä»£ć‚ć‚Šć«ć„ć¤ć‚‚ć® `__()` é–¢ę•°ć‚’ä½æē”Øć—ć¦ćć ć•ć„ć€‚ + + +例: + +```php + true, + 'name' => 'wordpress', + 'title' => 'WordPress', + 'icon' => 'wordpress', + 'attributes' => array( + 'service' => 'wordpress', + ), + 'isActive' => array( 'service' ) + ), + array( + 'name' => 'mail', + 'title' => __( 'Mail' ), + 'keywords' => array( + __( 'email' ), + __( 'e-mail' ) + ), + 'icon' => 'mail', + 'attributes' => array( + 'service' => 'mail', + ), + 'isActive' => array( 'mail' ) + ), +); + +``` + + č©³ē“°ćÆ[ćƒ‰ć‚­ćƒ„ćƒ”ćƒ³ćƒˆć®ćƒćƒŖć‚Øćƒ¼ć‚·ćƒ§ćƒ³](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/block-api/block-variations/) ć‚’å‚ē…§ć—ć¦ćć ć•ć„ć€‚ ### Block Hooks diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 9a8bb5c4a77f56..406f269b987004 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -55,7 +55,7 @@ Embed a simple audio player. ([Source](https://github.com/WordPress/gutenberg/tr - **Name:** core/audio - **Category:** media - **Supports:** align, anchor, interactivity (clientNavigation), spacing (margin, padding) -- **Attributes:** autoplay, caption, id, loop, preload, src +- **Attributes:** autoplay, blob, caption, id, loop, preload, src + + + + +## Terms List / ć‚æćƒ¼ćƒ äø€č¦§ + + +ęŒ‡å®šć•ć‚ŒćŸć‚æć‚Æć‚½ćƒŽćƒŸćƒ¼ć®ć™ć¹ć¦ć®ć‚æćƒ¼ćƒ ć®ćƒŖć‚¹ćƒˆć‚’č”Øē¤ŗć—ć¾ć™ć€‚ ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/categories)) + - **Name:** core/categories - **Category:** widgets - **Supports:** align, interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** displayAsDropdown, showEmpty, showHierarchy, showOnlyTopLevel, showPostCounts +- **Attributes:** displayAsDropdown, label, showEmpty, showHierarchy, showLabel, showOnlyTopLevel, showPostCounts, taxonomy -ē•Ŗ号ćŖć—ć€ć¾ćŸćÆē•Ŗå·ä»˜ćć®ćƒŖć‚¹ćƒˆć‚’ä½œęˆć—ć¾ć™ć€‚([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/list)) +ē‰¹å®šć®é †åŗć§č”Øē¤ŗć•ć‚Œć‚‹ć€ę•“ē†ć•ć‚ŒćŸé …ē›®ć®ć‚³ćƒ¬ć‚Æć‚·ćƒ§ćƒ³ć€‚([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/list)) - **Name:** core/list - **Category:** text @@ -746,14 +760,15 @@ Create a bulleted or numbered list. ([Source](https://github.com/WordPress/guten --> ## List item / ćƒŖć‚¹ćƒˆé …ē›® -ćƒŖć‚¹ćƒˆé …ē›®ć‚’ä½œęˆć—ć¾ć™ć€‚([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/list-item)) +ćƒŖć‚¹ćƒˆå†…ć®å€‹ć€…ć®é …ē›®ć€‚([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/list-item)) + - **Name:** core/list-item - **Category:** text - **Parent:** core/list - **Allowed Blocks:** core/list -- **Supports:** color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), splitting, typography (fontSize, lineHeight), ~~className~~ +- **Supports:** anchor, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), splitting, typography (fontSize, lineHeight), ~~className~~ - **Attributes:** content, placeholder -悈恏ä½æē”Øć•ć‚Œć¦ć„ć‚‹ć‚æ悰恮ć‚Æćƒ©ć‚¦ćƒ‰ć€‚([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/tag-cloud)) +å‡ŗē¾é »åŗ¦ć«ć‚ˆć£ć¦ć‚µć‚¤ć‚ŗ恮ē•°ćŖ悋态äŗŗę°—ć‚­ćƒ¼ćƒÆćƒ¼ćƒ‰ć®ć‚Æćƒ©ć‚¦ćƒ‰ć€‚([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/tag-cloud)) - **Name:** core/tag-cloud - **Category:** widgets @@ -1628,7 +1643,7 @@ Embed a video from your media library or upload a new one. ([Source](https://git - **Name:** core/video - **Category:** media - **Supports:** align, anchor, interactivity (clientNavigation), spacing (margin, padding) -- **Attributes:** autoplay, caption, controls, id, loop, muted, playsInline, poster, preload, src, tracks +- **Attributes:** autoplay, blob, caption, controls, id, loop, muted, playsInline, poster, preload, src, tracks diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index 4b66ad9eb6cb40..4b3ca78f74d299 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -84,7 +84,7 @@ _Parameters_ _Returns_ -- `boolean | undefined`: Whether the given block is allowed to be moved. +- `boolean`: Whether the given block is allowed to be moved. ### canMoveBlocks @@ -412,7 +412,7 @@ Returns all blocks that match a blockName. Results include nested blocks. _Parameters_ - _state_ `Object`: Global application state. -- _blockName_ `?string`: Optional block name, if not specified, returns an empty array. +- _blockName_ `string[]`: Block name(s) for which clientIds are to be returned. _Returns_ @@ -562,6 +562,18 @@ _Returns_ - `number`: Number of blocks in the post, or number of blocks with name equal to blockName. +### getHoveredBlockClientId + +Returns the currently hovered block. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `Object`: Client Id of the hovered block. + ### 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). @@ -845,15 +857,9 @@ _Returns_ ### hasBlockMovingClientId -Returns whether block moving mode is enabled. - -_Parameters_ - -- _state_ `Object`: Editor state. - -_Returns_ +> **Deprecated** -- `string`: Client Id of moving block. +Returns whether block moving mode is enabled. ### hasDraggedInnerBlock @@ -1257,6 +1263,18 @@ _Parameters_ Action that hides the insertion point. +### hoverBlock + +Returns an action object used in signalling that the block with the specified client ID has been hovered. + +_Parameters_ + +- _clientId_ `string`: Block client ID. + +_Returns_ + +- `Object`: Action object. + ### insertAfterBlock Action that inserts a default block after a given block. @@ -1637,11 +1655,13 @@ _Returns_ ### setBlockMovingClientId -Action that enables or disables the block moving mode. +> **Deprecated** + +Set the block moving client ID. -_Parameters_ +_Returns_ -- _hasBlockMovingClientId_ `string|null`: Enable/Disable block moving mode. +- `Object`: Action object. ### setBlockVisibility diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index b22fd2238f3031..775dd66a821ef0 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -24,6 +24,8 @@ _Returns_ ### getCurrentTemplateTemplateParts +> **Deprecated** + Returns the template parts and their blocks for the current edited template. _Parameters_ diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index ba77f065584cfe..474207aa20460f 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -2,7 +2,106 @@ Namespace: `core`. -## Selectors +## Dynamically generated selectors + +There are a number of user-friendly selectors that are wrappers of the more generic `getEntityRecord` and `getEntityRecords` that can be used to retrieve information for the various entities. + +### getPostType + +Returns the information for a given post type. + +_Usage_ + + import { useSelect } from '@wordpress/data'; + import { store as coreDataStore } from '@wordpress/core-data'; + + const postType = useSelect( + ( select ) => select( coreDataStore ).getPostType( 'post' ) + + // Equivalent to: select( coreDataStore ).getEntityRecord( 'root', 'postType', 'post' ) + ); + +_Parameters_ + +- postType `string` + +_Returns_ + +- `EntityRecord | undefined`: Record. + +### getPostTypes + +Returns the information for post types. + +_Usage_ + + import { useSelect } from '@wordpress/data'; + import { store as coreDataStore } from '@wordpress/core-data'; + + const postTypes = useSelect( ( select ) => { + return select( coreDataStore ).getPostTypes( { per_page: 4 } ); + + // Equivalent to: + // select( coreDataStore ).getEntityRecords( 'root', 'postType', { per_page: 4 } ); + } ); + +_Parameters_ + +- _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_ + +- `EntityRecord[] | null`: Records. + +### getTaxonomy + +Returns information for a given taxonomy. + +_Usage_ + + import { useSelect } from '@wordpress/data'; + import { store as coreDataStore } from '@wordpress/core-data'; + + const taxonomy = useSelect( ( select ) => { + return select( coreDataStore ).getTaxonomy( 'category' ); + + // Equivalent to: + // select( coreDataStore ).getEntityRecord( 'root', 'taxonomy', 'category' ); + } ); + +_Parameters_ + +- taxonomy `string` + +_Returns_ + +- `EntityRecord | undefined`: Record. + +### getTaxonomies + +Returns information for taxonomies. + +_Usage_ + + import { useSelect } from '@wordpress/data'; + import { store as coreDataStore } from '@wordpress/core-data'; + + const taxonomies = useSelect( ( select ) => { + return select( coreDataStore ).getTaxonomies( { type: 'post' } ); + + // Equivalent to: + // select( coreDataStore ).getEntityRecords( 'root', 'taxonomy', { type: 'post' } ); + } ); + +_Parameters_ + +- _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_ + +- `EntityRecord[] | null`: Records. + +## Other Selectors @@ -18,7 +117,7 @@ _Parameters_ - _state_ `State`: Data state. - _action_ `string`: Action to check. One of: 'create', 'read', 'update', 'delete'. -- _resource_ `string`: REST resource to check, e.g. 'media' or 'posts'. +- _resource_ `string | EntityResource`: Entity resource to check. Accepts entity object `{ kind: 'root', name: 'media', id: 1 }` or REST base as a string - `media`. - _id_ `EntityRecordKey`: Optional ID of the rest resource to check. _Returns_ @@ -673,7 +772,7 @@ _Parameters_ - _kind_ `string`: Kind of the deleted entity. - _name_ `string`: Name of the deleted entity. -- _recordId_ `string`: Record ID of the deleted entity. +- _recordId_ `number|string`: Record ID of the deleted entity. - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. @@ -755,7 +854,7 @@ _Parameters_ ### receiveThemeSupports -> **Deprecated** since WP 5.9, this is not useful anymore, use the selector direclty. +> **Deprecated** since WP 5.9, this is not useful anymore, use the selector directly. Returns an action object used in signalling that the index has been received. diff --git a/docs/reference-guides/filters/block-filters.md b/docs/reference-guides/filters/block-filters.md index 5a2a1fad6fd737..3346fe3601993d 100644 --- a/docs/reference-guides/filters/block-filters.md +++ b/docs/reference-guides/filters/block-filters.md @@ -4,15 +4,15 @@ WordPress exposes several APIs that allow you to modify the behavior of existing ## Registration -The following filters are available to extend block settings during their registration. +Blocks in WordPress are typically registered on both the server and client side using `block.json`` metadata. You can use the following filters to modify or extend block settings during their registration on the server with PHP and on the client with JavaScript. To learn more, refer to the [block registration](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/) guide. ### `block_type_metadata` Filters the raw metadata loaded from the `block.json` file when registering a block type on the server with PHP. It allows modifications to be applied before the metadata gets processed. -The filter takes one parameter: +The callback function for this filter receives one parameter: -- `$metadata` (`array`) ā€“ metadata loaded from `block.json` for registering a block type. +- `$metadata` (`array`): Metadata loaded from `block.json` for registering a block type. The following example sets the `apiVersion` of all blocks to `2`. @@ -28,33 +28,33 @@ Here's a more robust example that disables background color and gradient support ```php function example_disable_heading_background_color_and_gradients( $metadata ) { - - // Only apply the filter to Heading blocks. - if ( ! isset( $metadata['name'] ) || 'core/heading' !== $metadata['name'] ) { - return $metadata; - } - - // Check if 'supports' key exists. - if ( isset( $metadata['supports'] ) && isset( $metadata['supports']['color'] ) ) { - - // Remove Background color and Gradients support. - $metadata['supports']['color']['background'] = false; - $metadata['supports']['color']['gradients'] = false; - } - - return $metadata; + + // Only apply the filter to Heading blocks. + if ( ! isset( $metadata['name'] ) || 'core/heading' !== $metadata['name'] ) { + return $metadata; + } + + // Check if 'supports' key exists. + if ( isset( $metadata['supports'] ) && isset( $metadata['supports']['color'] ) ) { + + // Remove Background color and Gradients support. + $metadata['supports']['color']['background'] = false; + $metadata['supports']['color']['gradients'] = false; + } + + return $metadata; } add_filter( 'block_type_metadata', 'example_disable_heading_background_color_and_gradients' ); ``` ### `block_type_metadata_settings` -Filters the settings determined from the processed block type metadata. It makes it possible to apply custom modifications using the block metadata that isnā€™t handled by default. +Filters the settings determined from the processed block type metadata. It makes it possible to apply custom modifications using the block metadata that isn't handled by default. -The filter takes two parameters: +The callback function for this filter receives two parameters: -- `$settings` (`array`) ā€“ Array of determined settings for registering a block type. -- `$metadata` (`array`) ā€“ Metadata loaded from the `block.json` file. +- `$settings` (`array`): Array of determined settings for registering a block type. +- `$metadata` (`array`): Metadata loaded from the `block.json` file. The following example increases the `apiVersion` for all blocks by `1`. @@ -66,6 +66,45 @@ function example_filter_metadata_registration( $settings, $metadata ) { add_filter( 'block_type_metadata_settings', 'example_filter_metadata_registration', 10, 2 ); ``` +### `register_block_type_args` + +Filters a block's arguments array (`$args`) right before the block type is officially registered on the server. + +The callback function for this filter receives two parameters: + +- `$args` (`array`): Array of arguments for registering a block type. +- `$block_type` (`string`): Block type name including namespace. + +`register_block_type_args` is the most low-level PHP filter available, and it will work for every block registered on the server. All settings defined on the server are propagated to the client with higher priority than those set in the client. + +The following code will disable the color controls for Paragraph, Heading, List, and List Item blocks. + +```php +function example_disable_color_for_specific_blocks( $args, $block_type ) { + + // List of block types to modify. + $block_types_to_modify = [ + 'core/paragraph', + 'core/heading', + 'core/list', + 'core/list-item' + ]; + + // Check if the current block type is in the list. + if ( in_array( $block_type, $block_types_to_modify, true ) ) { + // Disable color controls. + $args['supports']['color'] = array( + 'text' => false, + 'background' => false, + 'link' => false, + ); + } + + return $args; +} +add_filter( 'register_block_type_args', 'example_disable_color_for_specific_blocks', 10, 2 ); +``` + ### `blocks.registerBlockType` Used to filter the block settings when registering the block on the client with JavaScript. It receives the block settings, the name of the registered block, and either null or the deprecated block settings (when applied to a registered deprecation) as arguments. This filter is also applied to each of a block's deprecated settings. @@ -94,31 +133,100 @@ wp.hooks.addFilter( ); ``` -## Block Editor +## Front end + +The following PHP filters are available to change the output of a block on the front end. + +### `render_block` + +Filters the front-end content of any block. This filter has no impact on the behavior of blocks in the Editor. + +The callback function for this filter receives three parameters: + +- `$block_content` (`string`): The block content. +- `$block` (`array`): The full block, including name and attributes. +- `$instance` (`WP_Block`): The block instance. + +In the following example, the class `example-class` is added to all Paragraph blocks on the front end. Here the [HTML API](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/) is used to easily add the class instead of relying on regex. + +```php +function example_add_custom_class_to_paragraph_block( $block_content, $block ) { + + // Check if the block is a Paragraph block. + if ( 'core/paragraph' === $block['blockName'] ) { + + // Add the custom class to the block content using the HTML API. + $processor = new WP_HTML_Tag_Processor( $block_content ); + + if ( $processor->next_tag( 'p' ) ) { + $processor->add_class( 'example-class' ); + } + + return $processor->get_updated_html(); + } + + return $block_content; +} +add_filter( 'render_block', 'example_add_custom_class_to_paragraph_block', 10, 2 ); +``` + +### `render_block_{namespace/block}` + +Filters the front-end content of the defined block. This is just a simpler form of `render_block` when you only need to modify a specific block type. + +The callback function for this filter receives three parameters: + +- `$block_content` (`string`): The block content. +- `$block` (`array`): The full block, including name and attributes. +- `$instance` (`WP_Block`): The block instance. + +In the following example, the class `example-class` is added to all Paragraph blocks on the front end. Notice that compared to the `render_block` example above, you no longer need to check the block type before modifying the content. Again, the [HTML API](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/) is used instead of regex. + +```php +function example_add_custom_class_to_paragraph_block( $block_content, $block ) { + + // Add the custom class to the block content using the HTML API. + $processor = new WP_HTML_Tag_Processor( $block_content ); + + if ( $processor->next_tag( 'p' ) ) { + $processor->add_class( 'example-class' ); + } + + return $processor->get_updated_html(); +} +add_filter( 'render_block_core/paragraph', 'example_add_custom_class_to_paragraph_block', 10, 2 ); +``` + +## Editor -The following filters are available to change the behavior of blocks while editing in the block editor. +The following JavaScript filters are available to change the behavior of blocks while editing in the Editor. ### `blocks.getSaveElement` -A filter that applies to the result of a block's `save` function. This filter is used to replace or extend the element, for example using `React.cloneElement` to modify the element's props or replace its children, or returning an entirely new element. +A filter that applies to the result of a block's `save` function. This filter is used to replace or extend the element, for example using `React.cloneElement` to modify the element's props, replace its children, or return an entirely new element. -The filter's callback receives an element, a block-type definition object, and the block attributes as arguments. It should return an element. +The callback function for this filter receives three parameters: -The following example wraps a Cover block in an outer container div. +- `element` (`Object`): The element to be modified and returned. +- `blockType` (`Object`): A block-type definition object. +- `attributes` (`Object`): The block's attributes. + +The following example wraps a Cover block in an outer container `div`. ```js function wrapCoverBlockInContainer( element, blockType, attributes ) { - // skip if element is undefined + + // Skip if element is undefined. if ( ! element ) { return; } - // only apply to cover blocks + // Only apply to Cover blocks. if ( blockType.name !== 'core/cover' ) { return element; } - // return the element wrapped in a div + // Return the element wrapped in a div. return
{ element }
; } @@ -131,9 +239,13 @@ wp.hooks.addFilter( ### `blocks.getSaveContent.extraProps` -A filter that applies to all blocks returning a WP Element in the `save` function. This filter is used to add extra props to the root element of the `save` function. For example: to add a className, an id, or any valid prop for this element. +A filter that applies to all blocks returning a WP Element in the `save` function. This filter is used to add extra props to the root element of the `save` function. For example, you could add a className, an id, or any valid prop for this element. + +The callback function for this filter receives three parameters: -The filter receives the current `save` element's props, a block type, and the block attributes as arguments. It should return a props object. +- `props` (`Object`): The current `save` element's props to be modified and returned. +- `blockType` (`Object`): A block-type definition object. +- `attributes` (`Object`): The block's attributes. The following example adds a red background by default to all blocks. @@ -152,7 +264,7 @@ wp.hooks.addFilter( ); ``` -_Note:_ A [block validation](/docs/reference-guides/block-api/block-edit-save.md#validation) error will occur if this filter modifies existing content the next time the post is edited. The editor verifies that the content stored in the post matches the content output by the `save()` function. +_Note:_ A [block validation](/docs/reference-guides/block-api/block-edit-save.md#validation) error will occur if this filter modifies existing content the next time the post is edited. The Editor verifies that the content stored in the post matches the content output by the `save()` function. To avoid this validation error, use `render_block` server-side to modify existing post content instead of this filter. See [render_block documentation](https://developer.wordpress.org/reference/hooks/render_block/). --> @@ -164,15 +276,13 @@ _ę³Øꄏ:_ ć“ć®ćƒ•ć‚£ćƒ«ć‚æćƒ¼ćŒę—¢å­˜ć®ć‚³ćƒ³ćƒ†ćƒ³ćƒ„ć‚’å¤‰ę›“ć™ć‚‹ćØ态 Generated HTML classes for blocks follow the `wp-block-{name}` nomenclature. This filter allows to provide an alternative class name. -_Example:_ - ```js -// Our filter function +// Our filter function. function setBlockCustomClassName( className, blockName ) { return blockName === 'core/code' ? 'my-plugin-code' : className; } -// Adding the filter +// Adding the filter. wp.hooks.addFilter( 'blocks.getBlockDefaultClassName', 'my-plugin/set-block-custom-class-name', @@ -192,8 +302,7 @@ Called immediately after the default parsing of a block's attributes and before Used to modify the block's `edit` component. It receives the original block `BlockEdit` component and returns a new wrapped component. -_Example:_ - +The following example adds a new Inspector panel for all blocks. ```js const { createHigherOrderComponent } = wp.compose; @@ -220,13 +329,14 @@ wp.hooks.addFilter( ); ``` - Note that as this hook is run for _all blocks_, consuming it has the potential for performance regressions, particularly around block selection metrics. To mitigate this, consider whether any work you perform can be altered to run only under certain conditions. For example, suppose you are adding components that only need to render when the block is _selected_. In that case, you can use the block's "selected" state (`props.isSelected`) to conditionalize your rendering. +The following example adds a new Inspector panel for all blocks, but only when a block is selected. + ```js const withMyPluginControls = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { @@ -248,7 +358,7 @@ const withMyPluginControls = createHigherOrderComponent( ( BlockEdit ) => { Used to modify the block's wrapper component containing the block's `edit` component and all toolbars. It receives the original `BlockListBlock` component and returns a new wrapped component. -The following example adds a unique class name. +The following example adds a unique class name to all blocks. ```js const { createHigherOrderComponent } = wp.compose; @@ -452,7 +562,7 @@ You can also display an icon with your block category by setting an `icon` attri You can also set a custom icon in SVG format. To do so, the icon should be rendered and set on the frontend, so it can make use of WordPress SVG, allowing mobile compatibility and making the icon more accessible. -To set an SVG icon for the category shown in the previous example, add the following example JavaScript code to the editor calling `wp.blocks.updateCategory` e.g: +To set an SVG icon for the category shown in the previous example, add the following example JavaScript code to the Editor calling `wp.blocks.updateCategory` e.g: ```js ( function () { diff --git a/docs/reference-guides/filters/editor-filters.md b/docs/reference-guides/filters/editor-filters.md index a27a989dbc7884..08d1c5ddb8a33c 100644 --- a/docs/reference-guides/filters/editor-filters.md +++ b/docs/reference-guides/filters/editor-filters.md @@ -226,9 +226,9 @@ function example_filter_block_editor_rest_api_preload_paths_when_post_provided( ## Logging errors -A JavaScript error in a part of the UI shouldn't break the whole app. To solve this problem for users, React library uses the concept of an ["error boundary"](https://reactjs.org/docs/error-boundaries.html). Error boundaries are React components that catch JavaScript errors anywhere in their child component tree and display a fallback UI instead of the component tree that crashed. +A JavaScript error in a part of the UI shouldn't break the whole app. To solve this problem for users, React library uses the concept of an ["error boundary"](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary). Error boundaries are React components that catch JavaScript errors anywhere in their child component tree and display a fallback UI instead of the component tree that crashed. -The `editor.ErrorBoundary.errorLogged` action allows you to hook into the [Error Boundaries](https://reactjs.org/docs/error-boundaries.html) and gives you access to the error object. +The `editor.ErrorBoundary.errorLogged` action allows you to hook into the [Error Boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) and gives you access to the error object. You can use this action to get hold of the error object handled by the boundaries. For example, you may want to send them to an external error-tracking tool. Here's an example: diff --git a/docs/reference-guides/interactivity-api/README.md b/docs/reference-guides/interactivity-api/README.md index 742bf0d2f62806..2269d8d802db25 100644 --- a/docs/reference-guides/interactivity-api/README.md +++ b/docs/reference-guides/interactivity-api/README.md @@ -41,15 +41,17 @@ Use the following links to locate the topic you're interested in. If you have ne 仄äø‹ć®ćƒŖćƒ³ć‚Æ悒ä½æē”Øć—ć¦ć€čˆˆå‘³ć®ć‚ć‚‹ćƒˆćƒ”ćƒƒć‚Æć‚’ęŽ¢ć—ć¦ćæć¦ćć ć•ć„ć€‚ć“ć‚Œć¾ć§ć« Interactivity API 悒ä½æē”Ø恗恟恓ćØ恌ćŖć‘ć‚Œć°ć€ä»„äø‹ć®ćƒŖć‚½ćƒ¼ć‚¹ć‚’é †ē•Ŗ恫čŖ­ć‚“ć§ćć ć•ć„ć€‚ - **要件:** Interactivity API 悒ä½æē”Øć—ć¦ć‚¤ćƒ³ć‚æ惩ć‚Æćƒ†ć‚£ćƒ–ćŖ惖惭惃ć‚Æć®ä½œęˆć‚’å§‹ć‚ć‚‹å‰ć«ć€ć“ć®ć‚»ć‚Æć‚·ćƒ§ćƒ³ć‚’ē¢ŗčŖć—ć¦ćć ć•ć„ (後čæ°)怂 - **[ć‚Æ悤惃ć‚Æć‚¹ć‚æćƒ¼ćƒˆć‚¬ć‚¤ćƒ‰](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/iapi-quick-start-guide/):** Interactivity API 悒ä½æē”Øć™ć‚‹ć‚«ć‚¹ć‚æ惠惖惭惃ć‚Æ悒1åˆ†ć§ä½œęˆć—ć€å®Ÿč”Œć—ć¾ć™ć€‚ - **[ćƒćƒ„ćƒ¼ćƒˆćƒŖć‚¢ćƒ«: ćÆć˜ć‚ć¦ć® Interactivity API](https://developer.wordpress.org/news/2024/04/11/a-first-look-at-the-interactivity-api/):** [WordPress Developer Blog](https://developer.wordpress.org/news/)恮恓恮čؘäŗ‹ćÆ态Interactivity API 恮ē“ ę™“悉恗恄ē“¹ä»‹čؘäŗ‹ć§ć™ć€‚ +- **[ć‚³ć‚¢ć‚³ćƒ³ć‚»ćƒ—ćƒˆ](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/core-concepts/)** 恓恮悻ć‚Æć‚·ćƒ§ćƒ³ć§ćÆ Interactive API 開ē™ŗć«é–¢ć™ć‚‹ć‚³ćƒ³ć‚»ćƒ—ćƒˆćØćƒ”ćƒ³ć‚æćƒ«ćƒ¢ćƒ‡ćƒ«ć«ć¤ć„ć¦ē†č§£ć‚’ę·±ć‚ć‚‰ć‚Œć¾ć™ć€‚ - **[API ćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/api-reference/):** API ćŒå†…éƒØēš„ć«ć©ć®ć‚ˆć†ć«å‹•ä½œć™ć‚‹ć®ć‹ć€ćƒ‡ć‚£ćƒ¬ć‚Æćƒ†ć‚£ćƒ–ć®ćƒŖć‚¹ćƒˆć€ć‚¹ćƒˆć‚¢ćŒć©ć®ć‚ˆć†ć«å‹•ä½œć™ć‚‹ć®ć‹ć‚’ę·±ćęŽ˜ć‚Šäø‹ć’ć¾ć™ć€‚ - **ćƒ‰ć‚­ćƒ„ćƒ”ćƒ³ćƒˆćØć‚µćƒ³ćƒ—ćƒ«ćƒ—ćƒ­ć‚°ćƒ©ćƒ :** Interactivity API ć«ć¤ć„ć¦ć‚‚ć£ćØå­¦ēæ’ć™ć‚‹ćŸć‚ć®čæ½åŠ ć®ęƒ…å ± (後čæ°)怂 @@ -59,10 +61,10 @@ To get a deeper understanding of what the Interactivity API is or find answers t Interactivity API ć‚’ć‚ˆć‚Šę·±ćē†č§£ć—ćŸć‚Šć€ć“ć®ęؙęŗ–ć«é–¢ć™ć‚‹ē–‘問ćø恮ē­”ćˆć‚’č¦‹ć¤ć‘ć‚‹ć«ćÆ仄äø‹ć®ćƒŖć‚½ćƒ¼ć‚¹ć‚’ćƒć‚§ćƒƒć‚Æć—ć¦ćć ć•ć„ć€‚ -- **[Interactivity API 恫恤恄恦](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/iapi-about/):** API ć®ć‚“ćƒ¼ćƒ«ćØ态惖惭惃ć‚Æćøć®ć‚¤ćƒ³ć‚æ惩ć‚Æćƒ†ć‚£ćƒ“ćƒ†ć‚£ć®čæ½åŠ ć«ć€ęؙęŗ–ć‚’ä½æē”Ø恙悋ē†ē”±ć«ć¤ć„ć¦ć€č©³ē“°ć‚’å­¦ć³ć¾ć™ć€‚ +- **[Interactivity API 恫恤恄恦](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/iapi-about/):** API ć®ć‚“ćƒ¼ćƒ«ćØ态惖惭惃ć‚Æćøć®ć‚¤ćƒ³ć‚æ惩ć‚Æćƒ†ć‚£ćƒ“ćƒ†ć‚£ć®čæ½åŠ ć«ęؙęŗ–ć‚’ä½æē”Ø恙悋ē†ē”±ć«ć¤ć„ć¦ć€č©³ē“°ć‚’å­¦ć³ć¾ć™ć€‚ - **[悈恏恂悋č³Ŗ問](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/iapi-faq/):** čƒŒå¾Œć«ć‚ć‚‹ęŠ€č”“ćØ代ę›æć«ć¤ć„ć¦ć€ć‚ˆćć‚ć‚‹č³Ŗå•ć«åÆ¾ć™ć‚‹å›žē­”ćŒć‚ć‚Šć¾ć™ć€‚ - [ć‚³ćƒ¼ćƒ‰ć‚Øćƒ‡ć‚£ć‚æćƒ¼](https://ja.wordpress.org/team/handbook/block-editor/getting-started/devenv/#code-editor) - [Node.js 開ē™ŗćƒ„ćƒ¼ćƒ«](https://ja.wordpress.org/team/handbook/block-editor/getting-started/devenv/#node-js-development-tools) @@ -99,6 +101,28 @@ You can start creating interactions once you set up a block development environm --> ### ć‚³ćƒ¼ćƒ‰ć®č¦ä»¶ + +#### 惗惭ć‚ø悧ć‚Æ惈ćø恮 `interactivity` 恮čæ½åŠ  + + +仄äø‹ć®ć‚³ćƒžćƒ³ćƒ‰ć§ćƒ—ćƒ­ć‚ø悧ć‚Æ惈恫 Interactivity API ć‚’ć‚¤ćƒ³ć‚¹ćƒˆćƒ¼ćƒ«ć—ć¾ć™ć€‚ + +```bash +npm install @wordpress/interactivity --save +``` + +ć‚¹ćƒˆć‚¢ć‚’ `view.js` å†…ć«ć‚¤ćƒ³ćƒćƒ¼ćƒˆć—ć¾ć™ć€‚č©³ē“°ć«ć¤ć„恦ćÆ[ć‚¹ćƒˆć‚¢ć®ćƒ‰ć‚­ćƒ„ćƒ”ćƒ³ćƒˆ](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/interactivity-api/api-reference/#the-store)ć‚’å‚ē…§ć—ć¦ćć ć•ć„ć€‚ + +```js +import { store } from '@wordpress/interactivity'; +``` + @@ -165,7 +189,7 @@ DOM 要ē“  (ćŠć‚ˆć³ćć®å­č¦ē“ ) 恧 Interactivity API ć‚’ć€Œęœ‰åŠ¹åŒ–ć€ ```html
- +
``` @@ -185,15 +209,15 @@ Here you have some more resources to learn/read more about the Interactivity API Interactivity API ć«ć¤ć„ć¦ć‚ˆć‚Šč©³ē“°ć‚’ēŸ„ć‚ŠćŸć„ę–¹ćÆ态仄äø‹ć®ćƒŖć‚½ćƒ¼ć‚¹ć‚’å‚ē…§ć—ć¦ćć ć•ć„ć€‚ - [WordPress 6.5 Dev Note](https://make.wordpress.org/core/2024/03/04/interactivity-api-dev-note/) - [Merge ć‚¢ćƒŠć‚¦ćƒ³ć‚¹ćƒ”ćƒ³ćƒˆ](https://make.wordpress.org/core/2024/02/19/merge-announcement-interactivity-api/) @@ -201,10 +225,10 @@ Interactivity API ć«ć¤ć„ć¦ć‚ˆć‚Šč©³ē“°ć‚’ēŸ„ć‚ŠćŸć„ę–¹ćÆ态仄äø‹ć®ćƒŖ - [Interactivity API ćƒ‡ć‚£ć‚¹ć‚«ćƒƒć‚·ćƒ§ćƒ³](https://github.com/WordPress/gutenberg/discussions/52882)态ē‰¹ć« [showcase](https://github.com/WordPress/gutenberg/discussions/55642#discussioncomment-9667164) ćƒ‡ć‚£ć‚¹ć‚«ćƒƒć‚·ćƒ§ćƒ³ - [wpmovies.dev](http://wpmovies.dev/) ćƒ‡ćƒ¢ćØćć® [wp-movies-demo](https://github.com/WordPress/wp-movies-demo) ćƒŖ惝ć‚ø惈ćƒŖ - [block-development-examples](https://github.com/WordPress/block-development-examples) 恮 Interactivity API 悒ä½æē”Øć—ćŸć‚µćƒ³ćƒ—ćƒ«ćƒ—ćƒ­ć‚°ćƒ©ćƒ  - - [`interactivity-api-block-833d15`](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/833d15) + - [`interactivity-api-block-833d15`](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/interactivity-api-block-833d15) - [`interactivity-api-countdown-3cd73e`](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/interactivity-api-countdown-3cd73e) - [`interactivity-api-quiz-1835fa`](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/interactivity-api-quiz-1835fa) - + Interactivity API ćƒ‰ć‚­ćƒ„ćƒ”ćƒ³ćƒˆć«é–¢é€£ć™ć‚‹ä½œę„­ć®čŖæę•“ć‚’ē°”ē“ åŒ–ć®ćŸć‚ć€Tracking Issue 恌ć‚Ŗćƒ¼ćƒ—ćƒ³ć•ć‚Œć¾ć—ćŸļ¼šDocumentation for the Interactivity API - Tracking Issue #53296 恧恙怂 -[åŽŸę–‡](https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/interactivity-api/README.md) \ No newline at end of file +[åŽŸę–‡](https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/interactivity-api/README.md) diff --git a/docs/reference-guides/interactivity-api/api-reference.md b/docs/reference-guides/interactivity-api/api-reference.md index 1b331b30d961eb..6cb23f2b0fad5f 100644 --- a/docs/reference-guides/interactivity-api/api-reference.md +++ b/docs/reference-guides/interactivity-api/api-reference.md @@ -517,8 +517,7 @@ The callback passed as the reference receives [the event](https://developer.mozi ### `wp-on-async` ć“ć®ćƒ‡ć‚£ćƒ¬ć‚Æćƒ†ć‚£ćƒ–ćÆ `wp-on` ć‚ˆć‚Šć‚‚ćƒ‘ćƒ•ć‚©ćƒ¼ćƒžćƒ³ć‚¹ć«å„Ŗć‚ŒćŸć‚¢ćƒ—ćƒ­ćƒ¼ćƒć§ć™ć€‚ē›“ć”ć«ćƒ”ć‚¤ćƒ³ć‚¹ćƒ¬ćƒƒćƒ‰ć« yield 恗恦态Ꙃ間恮恋恋悋ć‚æć‚¹ć‚Æć®å¾…ć”ć‚’å›žéæć—ć€ćƒ”ć‚¤ćƒ³ć‚¹ćƒ¬ćƒƒćƒ‰ć§å¾…ę©Ÿć—ć¦ć„ć‚‹ä»–ć®å‡¦ē†ćŒć‚ˆć‚Šę—©ćå®Ÿč”Œć§ćć‚‹ć‚ˆć†ć«ć—ć¾ć™ć€‚`event` ć‚Ŗ惖ć‚ø悧ć‚Æćƒˆć«åŒęœŸēš„ć«ć‚¢ć‚Æć‚»ć‚¹ć™ć‚‹åæ…č¦ćŒćŖć„å “åˆć€ē‰¹ć« `event.preventDefault()`态`event.stopPropagation()`态`event.stopImmediatePropagation()` ćƒ”ć‚½ćƒƒćƒ‰ć§ćÆć€ć“ć®éžåŒęœŸćƒćƒ¼ć‚øćƒ§ćƒ³ć‚’ä½æē”Øć—ć¦ćć ć•ć„ć€‚ @@ -542,10 +541,9 @@ This directive allows you to attach global window events like `resize`, `copy`, ćƒŖćƒ³ć‚Æ: [ć‚µćƒćƒ¼ćƒˆć™ć‚‹ć‚¦ć‚£ćƒ³ćƒ‰ć‚¦ć‚¤ćƒ™ćƒ³ćƒˆć®ćƒŖć‚¹ćƒˆ](https://developer.mozilla.org/en-US/docs/Web/API/Window#events) -ć“ć®ćƒ‡ć‚£ćƒ¬ć‚Æćƒ†ć‚£ćƒ–ć®ę§‹ę–‡ćÆ `data-wp-on-window--[window-event]` 恧恙 (例: `data-wp-on-window--resize`态`data-wp-on-window--languagechange`)怂 +ć“ć®ćƒ‡ć‚£ćƒ¬ć‚Æćƒ†ć‚£ćƒ–ć®ę§‹ę–‡ćÆ `data-wp-on-window--[window-event]` 恧恙 (`data-wp-on-window--resize` or `data-wp-on-window--languagechange`)怂 ```php
@@ -600,8 +598,7 @@ This directive allows you to attach global document events like `scroll`, `mouse ćƒŖćƒ³ć‚Æ: [ć‚µćƒćƒ¼ćƒˆć™ć‚‹ćƒ‰ć‚­ćƒ„ćƒ”ćƒ³ćƒˆć‚¤ćƒ™ćƒ³ćƒˆć®ćƒŖć‚¹ćƒˆ](https://developer.mozilla.org/en-US/docs/Web/API/Document#events) ć“ć®ćƒ‡ć‚£ćƒ¬ć‚Æćƒ†ć‚£ćƒ–ć®ę§‹ę–‡ćÆ `data-wp-on-document--[document-event]` 恧恙 (例: `data-wp-on-document--keydown`态`data-wp-on-document--selectionchange`) @@ -1138,7 +1135,7 @@ Actions are just regular JavaScript functions. Usually triggered by the `data-wp // TypeScript const { state, actions } = store("myPlugin", { actions: { - selectItem: (id?: number) => { + selectItem: ( id ) => { const context = getContext(); // 恓恓恧 `id` ćÆć‚Ŗćƒ—ć‚·ćƒ§ćƒ³ć§ć™ć€‚ć—ćŸćŒć£ć¦ć“ć®ć‚¢ć‚Æć‚·ćƒ§ćƒ³ćÆćƒ‡ć‚£ćƒ¬ć‚Æćƒ†ć‚£ćƒ–ć§ć‚‚ä½æē”Øć§ćć¾ć™ć€‚ state.selected = id || context.id; @@ -1237,9 +1234,9 @@ const { state } = store("myPlugin", { ``` -`wp-on`态`wp-on-window`态`wp-on-document` ć§ć‚‚č§¦ć‚ŒćŸć‚ˆć†ć«ć€ć‚¢ć‚Æć‚·ćƒ§ćƒ³ćŒ `event` ć‚Ŗ惖ć‚ø悧ć‚Æ惈ćøć®åŒęœŸć‚¢ć‚Æć‚»ć‚¹ć‚’åæ…要ćØć™ć‚‹ćŸć‚ć«ć“ć‚Œć‚‰ć®ćƒ‡ć‚£ćƒ¬ć‚Æćƒ†ć‚£ćƒ–ć® `async` ćƒćƒ¼ć‚øćƒ§ćƒ³ć‚’ä½æē”Ø恧恍ćŖć„å “åˆćÆ态åøøć«éžåŒęœŸć‚¢ć‚Æć‚·ćƒ§ćƒ³ć‚’ä½æē”Ø恙悋åæ…č¦ćŒć‚ć‚Šć¾ć™ć€‚ć‚¢ć‚Æć‚·ćƒ§ćƒ³ćŒ `event.preventDefault()`态`event.stopPropagation()`态`event.stopImmediatePropagation()` ć‚’å‘¼ć³å‡ŗ恙åæ…č¦ćŒć‚ć‚‹å “合ćÆ态åøøć«åŒęœŸć‚¢ć‚Æć‚»ć‚¹ćŒåæ…č¦ć§ć™ć€‚ć‚¢ć‚Æć‚·ćƒ§ćƒ³ć®ć‚³ćƒ¼ćƒ‰ćŒę™‚é–“ć®ć‹ć‹ć‚‹ć‚æć‚¹ć‚Æ恫åƄäøŽć—ćŖć„ć‚ˆć†ć€åŒęœŸć‚¤ćƒ™ćƒ³ćƒˆ API ć®å‘¼ć³å‡ŗć—å¾Œć«ć€ćƒ”ć‚¤ćƒ³ć‚¹ćƒ¬ćƒƒćƒ‰ć«ę‰‹å‹•ć§ yield ć§ćć¾ć™ć€‚ä¾‹ćˆć° +äøŠć® `wp-on`态`wp-on-window`态`wp-on-document` ć§ć‚‚č§¦ć‚ŒćŸć‚ˆć†ć«ć€ć‚¢ć‚Æć‚·ćƒ§ćƒ³ćŒ `event` ć‚Ŗ惖ć‚ø悧ć‚Æ惈ćøć®åŒęœŸć‚¢ć‚Æć‚»ć‚¹ć‚’åæ…要ćØć™ć‚‹ćŸć‚ć«ć“ć‚Œć‚‰ć®ćƒ‡ć‚£ćƒ¬ć‚Æćƒ†ć‚£ćƒ–ć® `async` ćƒćƒ¼ć‚øćƒ§ćƒ³ć‚’ä½æē”Ø恧恍ćŖć„å “åˆćÆ态åøøć«éžåŒęœŸć‚¢ć‚Æć‚·ćƒ§ćƒ³ć‚’ä½æē”Ø恙悋åæ…č¦ćŒć‚ć‚Šć¾ć™ć€‚ć‚¢ć‚Æć‚·ćƒ§ćƒ³ćŒ `event.preventDefault()`态`event.stopPropagation()`态`event.stopImmediatePropagation()` ć‚’å‘¼ć³å‡ŗ恙åæ…č¦ćŒć‚ć‚‹å “合ćÆ态åøøć«åŒęœŸć‚¢ć‚Æć‚»ć‚¹ćŒåæ…č¦ć§ć™ć€‚ć‚¢ć‚Æć‚·ćƒ§ćƒ³ć®ć‚³ćƒ¼ćƒ‰ćŒę™‚é–“ć®ć‹ć‹ć‚‹ć‚æć‚¹ć‚Æ恫åƄäøŽć—ćŖć„ć‚ˆć†ć€åŒęœŸć‚¤ćƒ™ćƒ³ćƒˆ API ć®å‘¼ć³å‡ŗć—å¾Œć«ć€ćƒ”ć‚¤ćƒ³ć‚¹ćƒ¬ćƒƒćƒ‰ć«ę‰‹å‹•ć§ yield ć§ćć¾ć™ć€‚ä¾‹ćˆć° ```js // ę³Øꄏ: WordPress 6.6恧ćÆ态恓恮 splitTask é–¢ę•°ćÆ @wordpress/interactivity 恧ć‚Øć‚Æć‚¹ćƒćƒ¼ćƒˆć•ć‚Œć¾ć™ć€‚ @@ -1413,7 +1410,7 @@ This approach enables some functionalities that make directives flexible and pow *å„ćƒ–ćƒ­ćƒƒć‚Æ恮 `view.js` ćƒ•ć‚”ć‚¤ćƒ«ć®äø­* 恧态開ē™ŗ者ćÆć‚¹ćƒ†ćƒ¼ćƒˆćØć€ć‚¹ćƒˆć‚¢ć®č¦ē“ ć®äø”ę–¹ć‚’å®šē¾©ć§ćć¾ć™ć€‚ć‚¹ćƒˆć‚¢ć®č¦ē“ ćÆć‚¢ć‚Æć‚·ćƒ§ćƒ³ć€å‰Æ作ē”Øć€ę“¾ē”Ÿć‚¹ćƒ†ćƒ¼ćƒˆćŖć©ć®é–¢ę•°ć‚’å‚ē…§ć—ć¾ć™ć€‚ JavaScript ć§ć‚¹ćƒˆć‚¢ć‚’čØ­å®šć™ć‚‹ `store` ćƒ”ć‚½ćƒƒćƒ‰ćÆ态`@wordpress/interactivity` ć‹ć‚‰ć‚¤ćƒ³ćƒćƒ¼ćƒˆć§ćć¾ć™ć€‚ @@ -1538,9 +1535,17 @@ store é–¢ę•°ä»„å¤–ć«ć€é–‹ē™ŗ者恌 store é–¢ę•°ć®ćƒ‡ćƒ¼ć‚æć«ć‚¢ć‚Æć‚»ć‚¹ć™ #### getContext() -é–¢ę•°ć‚’č©•ä¾”ć™ć‚‹č¦ē“ ćŒē¶™ę‰æć—ćŸć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆć‚’ć‚¹ćƒˆć‚¢ć‹ć‚‰å–å¾—ć—ć¾ć™ć€‚ęˆ»ć‚Šå€¤ćÆ要ē“ ćØ `getContext()` ć‚’å‘¼ć³å‡ŗć—ćŸé–¢ę•°ćŒå­˜åœØć™ć‚‹åå‰ē©ŗé–“ć«ä¾å­˜ć—ć¾ć™ć€‚ +é–¢ę•°ć‚’č©•ä¾”ć™ć‚‹č¦ē“ ćŒē¶™ę‰æć—ćŸć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆć‚’ć‚¹ćƒˆć‚¢ć‹ć‚‰å–å¾—ć—ć¾ć™ć€‚ęˆ»ć‚Šå€¤ćÆ要ē“ ćØ `getContext()` ć‚’å‘¼ć³å‡ŗć—ćŸé–¢ę•°ćŒå­˜åœØć™ć‚‹åå‰ē©ŗé–“ć«ä¾å­˜ć—ć¾ć™ć€‚ć‚Ŗćƒ—ć‚·ćƒ§ćƒ³ć§åå‰ē©ŗé–“ć‚’å¼•ę•°ć«å–ć‚Šć€ē‰¹å®šć® interactive é ˜åŸŸć®ć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆć‚’å–å¾—ć§ćć¾ć™ć€‚ + +```js +const context = getContext('namespace'); +``` + +- `namespace` (ć‚Ŗćƒ—ć‚·ćƒ§ćƒ³): interactive é ˜åŸŸć®åå‰ē©ŗ間ćØåˆč‡“ć™ć‚‹ę–‡å­—åˆ—ć€‚ęŒ‡å®šćŒćŖ恑悌恰态ē¾č”Œć® interactive é ˜åŸŸć®ć‚³ćƒ³ćƒ†ć‚­ć‚¹ćƒˆć‚’å–å¾—ć—ć¾ć™ć€‚ ```php // render.php @@ -1559,6 +1564,11 @@ store( "myPlugin", { const context = getContext(); // "false" 悒惭悰 console.log('context => ', context.isOpen) + + // With namespace argument. + const myPluginContext = getContext("myPlugin"); + // Logs "false" + console.log('myPlugin isOpen => ', myPluginContext.isOpen); }, }, }); @@ -1600,7 +1610,7 @@ store( "myPlugin", { actions: { log: () => { const element = getElement(); - // attributes 悒惭悰 + // Logs attributes console.log('element attributes => ', element.attributes) }, }, @@ -1657,7 +1667,13 @@ store('mySliderPlugin', { + + +Interactivity API 恫ćÆć€ć‚µćƒ¼ćƒćƒ¼äøŠć®ę§‹ęˆć‚Ŗćƒ—ć‚·ćƒ§ćƒ³ć‚’åˆęœŸåŒ–ć€å‚ē…§ć™ć‚‹ćŸć‚ć®ä¾æ利ćŖé–¢ę•°ćŒć‚ć‚Šć¾ć™ć€‚ć“ć‚ŒćÆ Server Directive Processing ć«åˆęœŸćƒ‡ćƒ¼ć‚æ悒äøŽćˆć‚‹ćŸć‚ć«åæ…č¦ć§ć€ć“ć®åˆęœŸćƒ‡ćƒ¼ć‚æćÆ态Server Directive Processing ćŒćƒ–ćƒ©ć‚¦ć‚¶ć« HTML ćƒžćƒ¼ć‚Æć‚¢ćƒƒćƒ—ć‚’é€äæ”ć™ć‚‹å‰ć«ć€äæ®ę­£ć™ć‚‹ćŸć‚ć«ä½æē”Øć•ć‚Œć¾ć™ć€‚ć¾ćŸć€nonce态AJAX态ēæ»čسćŖ恩恮 WordPress ć®å¤šćć® API ć‚’ę“»ē”Ø恙悋ē“ ę™“ć‚‰ć—ć„ę‰‹ę®µćŒęä¾›ć•ć‚Œć¾ć™ć€‚ ### wp_interactivity_config @@ -1702,6 +1718,65 @@ This config can be retrieved on the client: const { showLikeButton } = getConfig(); ``` +### wp_interactivity_state + + +`wp_interactivity_state` 悒ä½æē”Ø恙悋ćØć‚µćƒ¼ćƒćƒ¼äøŠć§ć‚°ćƒ­ćƒ¼ćƒćƒ«ć‚¹ćƒ†ćƒ¼ćƒˆć‚’åˆęœŸåŒ–ć§ćć¾ć™ć€‚ć“ć®ć‚°ćƒ­ćƒ¼ćƒćƒ«ć‚¹ćƒ†ćƒ¼ćƒˆćÆć‚µćƒ¼ćƒćƒ¼äøŠć®ćƒ‡ć‚£ćƒ¬ć‚Æćƒ†ć‚£ćƒ–ć®å‡¦ē†ć«ä½æē”Ø恕悌态ć‚Æćƒ©ć‚¤ć‚¢ćƒ³ćƒˆć§å®šē¾©ć•ć‚ŒćŸć‚°ćƒ­ćƒ¼ćƒćƒ«ć‚¹ćƒ†ćƒ¼ćƒˆćØćƒžćƒ¼ć‚øć•ć‚Œć¾ć™ć€‚ + + +ć¾ćŸć€ć‚µćƒ¼ćƒćƒ¼äøŠć§ć‚°ćƒ­ćƒ¼ćƒćƒ«ć‚¹ćƒ†ćƒ¼ćƒˆć‚’åˆęœŸåŒ–ć™ć‚‹ć“ćØ恧态[AJAX](https://developer.wordpress.org/plugins/javascript/ajax/) 悄 [nonces](https://developer.wordpress.org/plugins/javascript/enqueuing/#nonce) ćŖć©å¤šćć®é‡č¦ćŖ WordPress API 悒ä½æē”Øć§ćć¾ć™ć€‚ + + +é–¢ę•° `wp_interactivity_state` ćÆ2ć¤ć®å¼•ę•°ć‚’å–ć‚Šć¾ć™ć€‚1恤ćÆ参ē…§ćØ恗恦ä½æē”Øć•ć‚Œć‚‹åå‰ē©ŗé–“ć®ę–‡å­—åˆ—ć§ć€ć‚‚ć†1恤ćÆå€¤ć‚’å«ć‚€é€£ęƒ³é…åˆ—ć§ć™ć€‚ + + +仄äø‹ćÆ态nonce ćØå…±ć« WP Admin AJAX ć‚Øćƒ³ćƒ‰ćƒć‚¤ćƒ³ćƒˆć‚’ęø”ć™ä¾‹ć§ć™ć€‚ + +```php +// render.php + +wp_interactivity_state( + 'myPlugin', + array( + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'myPlugin_nonce' ), + ), +); +``` + +```js +// view.js + +const { state } = store( 'myPlugin', { + actions: { + *doSomething() { + try { + const formData = new FormData(); + formData.append( 'action', 'do_something' ); + formData.append( '_ajax_nonce', state.nonce ); + + const data = yield fetch( state.ajaxUrl, { + method: 'POST', + body: formData, + } ).then( ( response ) => response.json() ); + console.log( 'Server data!', data ); + } catch ( e ) { + // Something went wrong! + } + }, + }, + } +); +``` + ### wp_interactivity_process_directives + + ``` + + You can also initialize the local context on the server using the `wp_interactivity_data_wp_context` PHP helper, which ensures proper escaping and formatting of the stringified values: + + ```php + 0 ); + ?> + +
> + +
+ ``` + +- **Accessing the local context** + + In the HTML markup, you can access the local context values directly by referencing `context` in the directive values: + + ```html +
+ +
+ ``` + + In JavaScript, you can access the local context values using the `getContext` function: + + ```js + store( 'myPlugin', { + actions: { + sendAnalyticsEvent() { + const { counter } = getContext(); + myAnalyticsLibrary.sendEvent( 'updated counter', counter ); + }, + }, + callbacks: { + logCounter() { + const { counter } = getContext(); + console.log( `Current counter: ${ counter }` ); + }, + }, + } ); + ``` + + The `getContext` function returns the local context of the element that triggered the action/callback execution. + +- **Updating the local context** + + To update the local context values in JavaScript, you can modify the object returned by `getContext`: + + ```js + store( 'myPlugin', { + actions: { + increment() { + const context = getContext(); + context.counter += 1; + }, + updateName( event ) { + const context = getContext(); + context.name = event.target.value; + }, + }, + } ); + ``` + + Changes to the local context will automatically trigger updates in any directives that depend on the modified values. + + _Please, visit [The Reactive and Declarative mindset](/docs/reference-guides/interactivity-api/core-concepts/the-reactive-and-declarative-mindset.md) guide to learn more about how reactivity works in the Interactivity API._ + +- **Nesting local contexts** + + Local contexts can be nested, with child contexts inheriting and potentially overriding values from parent contexts: + + ```html +
+

Theme:

+

Counter:

+ +
+

Theme:

+

Counter:

+
+
+ ``` + + In this example, the inner `div` will have a `theme` value of `"dark"`, but will inherit the `counter` value `0` from its parent context. + +### Example: One interactive block using local context to have independent state + +In this example, there is a single interactive block that shows a counter and can increment it. By using local context, each instance of this block will have its own independent counter, even if multiple blocks are added to the page. + +```php +
+ data-wp-context='{ "counter": 0 }' +> +

Counter:

+ +
+``` + +```js +store( 'myCounterPlugin', { + actions: { + increment() { + const context = getContext(); + context.counter += 1; + }, + }, +} ); +``` + +In this example: + +1. A local context with an initial `counter` value of `0` is defined using the `data-wp-context` directive. +2. The counter is displayed using `data-wp-text="context.counter"`, which reads from the local context. +3. The increment button uses `data-wp-on-async--click="actions.increment"` to trigger the increment action. +4. In JavaScript, the `getContext` function is used to access and modify the local context for each block instance. + +A user will be able to add multiple instances of this block to a page, and each will maintain its own independent counter. Clicking the "Increment" button on one block will only affect that specific block's counter and not the others. + +## Derived state + +**Derived state** in the Interactivity API refers to a value that is computed from other parts of the global state or local context. It's calculated on demand rather than stored. It ensures consistency, reduces redundancies, and enhances the declarative nature of your code. + +Derived state is a fundamental concept in modern state management, not unique to the Interactivity API. It's also used in other popular state management systems like Redux, where it's called `selectors`, or Preact Signals, where it's known as `computed` values. + +Derived state offers several key benefits that make it an essential part of a well-designed application state, including: + +1. **Single source of truth:** Derived state encourages you to store only the essential, raw data in your state. Any values that can be calculated from this core data become derived state. This approach reduces the risk of inconsistencies in your interactive blocks. + +2. **Automatic updates:** When you use derived state, values are recalculated automatically whenever the underlying data changes. This ensures that all parts of your interactive blocks always have access to the most up-to-date information without manual intervention. + +3. **Simplified state management:** By computing values on-demand rather than storing and updating them manually, you reduce the complexity of your state management logic. This leads to cleaner, more maintainable code. + +4. **Improved performance:** In many cases, derived state can be optimized to recalculate only when necessary, potentially improving your interactive blocks' performance. + +5. **Easier debugging:** With derived state, it's clearer where data originates and how it's transformed. This can make it easier to track down issues in your interactive blocks. + +In essence, derived state allows you to express relationships between different pieces of data in your interactive blocks declaratively, instead of imperatively updating related values whenever something changes. + +_Please, visit [The Reactive and Declarative mindset](/docs/reference-guides/interactivity-api/core-concepts/the-reactive-and-declarative-mindset.md) guide to learn more about how to leverage declarative coding in the Interactivity API._ + +You should use derived state: + +- When a part of your global state or local context can be computed from other state values. +- To avoid redundant data that needs to be manually kept in sync. +- To ensure consistency across your interactive blocks by automatically updating derived values. +- To simplify your actions by removing the need to update multiple related state properties. + +### Working with derived state + +- **Initializing the derived state** + + Typically, the derived state should be initialized on the server using the `wp_interactivity_state` function in the exact same way as the global state. + + - When the initial value is known and static, it can be defined directly: + + ```php + wp_interactivity_state( 'myCounterPlugin', array( + 'counter' => 1, // This is global state. + 'double' => 2, // This is derived state. + )); + ``` + + - Or it can be defined by doing the necessary computations: + + ```php + $counter = 1; + $double = $counter * 2; + + wp_interactivity_state( 'myCounterPlugin', array( + 'counter' => $counter, // This is global state. + 'double' => $double, // This is derived state. + )); + ``` + + Regardless of the approach, the initial derived state values will be used during the rendering of the page in PHP, and the HTML can be populated with the correct values. + + _Please, visit [the Server-side Rendering guide](/docs/reference-guides/interactivity-api/core-concepts/server-side-rendering.md) to learn more about how directives are processed on the server._ + + The same mechanism applies even when the derived state property depends on the local context. + + ```php + $counter ); + + wp_interactivity_state( 'myCounterPlugin', array( + 'double' => $counter * 2, // This is derived state. + )); + ?> + +
+ > +
+ Counter: +
+
+ Double: +
+
+ ``` + + In JavaScript, the derived state is defined using getters: + + ```js + const { state } = store( 'myCounterPlugin', { + state: { + get double() { + return state.counter * 2; + }, + }, + } ); + ``` + + Derived state can depend on local context, or local context and global state at the same time. + + ```js + const { state } = store( 'myCounterPlugin', { + state: { + get double() { + const { counter } = getContext(); + // Depends on local context. + return counter * 2; + }, + get product() { + const { counter } = getContext(); + // Depends on local context and global state. + return counter * state.factor; + }, + }, + } ); + ``` + + In some cases, when the derived state depends on the local context and the local context can change dynamically in the server, instead of the initial derived state, you can use a function (Closure) that calculates it dynamically. + + ```php + array( 1, 2, 3 ), + 'factor' => 3, + 'product' => function() { + $state = wp_interactivity_state(); + $context = wp_interactivity_get_context(); + return $context['item'] * $state['factor']; + } + )); + ?> + + + ``` + + This `data-wp-each` template will render this HTML (directives omitted): + + ```html + 3 + 6 + 9 + ``` + +- **Accessing the derived state** + + In the HTML markup, the syntax for the derived state is the same as the one for the global state, just by referencing `state` in the directive attribute values. + + ```html + + ``` + + The same happens in JavaScript. Both global state and derived state can be consumed through the `state` property of the store: + + ```js + const { state } = store( 'myCounterPlugin', { + // ... + actions: { + readValues() { + state.counter; // Regular state, returns 1. + state.double; // Derived state, returns 2. + }, + }, + } ); + ``` + + This lack of distinction is intentional, allowing developers to consume both derived and global state uniformly, and making them interchangeable in practice. + + You can also access the derived state from another derived state and, thus, create multiple levels of computed values. + + ```js + const { state } = store( 'myPlugin', { + state: { + get double() { + return state.counter * 2; + }, + get doublePlusOne() { + return state.double + 1; + }, + }, + } ); + ``` + +- **Updating the derived state** + + The derived state cannot be updated directly. To update its values, you need to update the global state or local context on which that derived state depends. + + ```js + const { state } = store( 'myCounterPlugin', { + // ... + actions: { + updateValues() { + state.counter; // Regular state, returns 1. + state.double; // Derived state, returns 2. + + state.counter = 2; + + state.counter; // Regular state, returns 2. + state.double; // Derived state, returns 4. + }, + }, + } ); + ``` + +### Example: Not using derived state vs using derived state + +Let's consider a scenario where there is a counter and the double value needs to be displayed, and let's compare two approaches: one without derived state and one with derived state. + +- **Not using derived state** + + ```js + const { state } = store( 'myCounterPlugin', { + state: { + counter: 1, + double: 2, + }, + actions: { + increment() { + state.counter += 1; + state.double = state.counter * 2; + }, + }, + } ); + ``` + + In this approach, both the `state.counter` and `state.double` values are manually updated in the `increment` action. While this works, it has several drawbacks: + + - It's less declarative. + - It can lead to bugs if `state.counter` is updated from multiple places and developers forget to keep `state.double` in sync. + - It requires more cognitive load to remember to update related values. + +- **Using derived state** + + ```js + const { state } = store( 'myCounterPlugin', { + state: { + counter: 1, + get double() { + return state.counter * 2; + }, + }, + actions: { + increment() { + state.counter += 1; + }, + }, + } ); + ``` + + In this improved version: + + - `state.double` is defined as a getter, automatically deriving its value from `state.counter`. + - The `increment` action only needs to update `state.counter`. + - `state.double` is always guaranteed to have the correct value, regardless of how or where `state.counter` is updated. + +### Example: Using derived state with local context + +Let's now consider a scenario where there is a local context that initializes a counter. + +```js +store( 'myCounterPlugin', { + state: { + get double() { + const { counter } = getContext(); + return counter * 2; + }, + }, + actions: { + increment() { + const context = getContext(); + context.counter += 1; + }, + }, +} ); +``` + +```html +
+ +
+ Double: + + + +
+ + +
+ Double: + + + +
+
+``` + +In this example, the derived state `state.double` reads from the local context present in each element and returns the correct value for each instance where it is used. + +### Example: Using derived state with both local context and global state + +Let's now consider a scenario where there are a global tax rate and local product prices and calculate the final price, including tax. + +```html +
+

Product Price: $

+

Tax Rate:

+

Price (inc. tax): $

+
+``` + +```js +const { state } = store( 'myProductPlugin', { + state: { + taxRate: 0.21, + get taxRatePercentage() { + return `${ state.taxRate * 100 }%`; + }, + get priceWithTax() { + const { priceWithoutTax } = getContext(); + return price * ( 1 + state.taxRate ); + }, + }, + actions: { + updateTaxRate( event ) { + // Updates the global tax rate. + state.taxRate = event.target.value; + }, + updatePrice( event ) { + // Updates the local product price. + const context = getContext(); + context.priceWithoutTax = event.target.value; + }, + }, +} ); +``` + +In this example, `priceWithTax` is derived from both the global `taxRate` and the local `priceWithoutTax`. Every time you update the global state or local context through the `updateTaxRate` or `updatePrice` actions, the Interactivity API recomputes the derived state and updates the necessary parts of the DOM. + +By using derived state, you create a more maintainable and less error-prone codebase. It ensures that related state values are always in sync, reduces the complexity of your actions, and makes your code more declarative and easier to reason about. + +## Conclusion + +Remember, the key to effective state management is to keep your state minimal and avoid redundancy. Use derived state to compute values dynamically, and choose between global state and local context based on the scope and requirements of your data. This will lead to a cleaner, more robust architecture that is easier to debug and maintain. diff --git a/docs/reference-guides/interactivity-api/core-concepts/using-typescript.md b/docs/reference-guides/interactivity-api/core-concepts/using-typescript.md new file mode 100644 index 00000000000000..ed0bdd88211d11 --- /dev/null +++ b/docs/reference-guides/interactivity-api/core-concepts/using-typescript.md @@ -0,0 +1,746 @@ +# Using TypeScript + +The Interactivity API provides robust support for TypeScript, enabling developers to build type-safe stores to enhance the development experience with static type checking, improved code completion, and simplified refactoring. This guide will walk you through the process of using TypeScript with Interactivity API stores, covering everything from basic type definitions to advanced techniques for handling complex store structures. + +These are the core principles of TypeScript's interaction with the Interactivity API: + +- **Inferred client types**: When you create a store using the `store` function, TypeScript automatically infers the types of the store's properties (`state`, `actions`, etc.). This means that you can often get away with just writing plain JavaScript objects, and TypeScript will figure out the types for you. +- **Explicit server types**: When dealing with data defined on the server, like local context or the initial values of the global state, you can explicitly define its types to ensure that everything is correctly typed. +- **Mutiple store parts**: Even if your store is split into multiple parts, you can define or infer the types of each part of the store and then merge them into a single type that represents the entire store. +- **Typed external stores**: You can import typed stores from external namespaces, allowing you to use other plugins' functionality with type safety. + +## Installing `@wordpress/interactivity` locally + +If you haven't done so already, you need to install the package `@wordpress/interactivity` locally so TypeScript can use its types in your IDE. You can do this using the following command: + +`npm install @wordpress/interactivity` + +It is also a good practice to keep that package updated. + +## Scaffolding a new typed interactive block + +If you want to explore an example of an interactive block using TypeScript in your local environment, you can use the `@wordpress/create-block-interactive-template`. + +Start by ensuring you have Node.js and `npm` installed on your computer. Review the [Node.js development environment](https://developer.wordpress.org/block-editor/getting-started/devenv/nodejs-development-environment/) guide if not. + +Next, use the [`@wordpress/create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) package and the [`@wordpress/create-block-interactive-template`](https://www.npmjs.com/package/@wordpress/create-block-interactive-template) template to scaffold the block. + +Choose the folder where you want to create the plugin, execute the following command in the terminal from within that folder, and choose the `typescript` variant when asked. + +``` +npx @wordpress/create-block@latest --template @wordpress/create-block-interactive-template +``` + +**Important**: Do not provide a slug in the terminal. Otherwise, `create-block` will not ask you which variant you want to choose and it will select the default non-TypeScript variant by default. + +Finally, you can keep following the instructions in the [Getting Started Guide](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-quick-start-guide/) as the rest of the instructions remain the same. + +## Typing the store + +Depending on the structure of your store and your preference, there are three options you can choose from to generate your store's types: + +1. Infer the types from your client store definition. +2. Manually type the server state, but infer the rest from your client store definition. +3. Manually write all the types. + +### 1. Infer the types from your client store definition + +When you create a store using the `store` function, TypeScript automatically infers the types of the store's properties (`state`, `actions`, `callbacks`, etc.). This means that you can often get away with just writing plain JavaScript objects, and TypeScript will figure out the correct types for you. + +Let's start with a basic example of a counter block. We will define the store in the `view.ts` file of the block, which contains the initial global state, an action and a callback. + +```ts +// view.ts +const myStore = store( 'myCounterPlugin', { + state: { + counter: 0, + }, + actions: { + increment() { + myStore.state.counter += 1; + }, + }, + callbacks: { + log() { + console.log( `counter: ${ myStore.state.counter }` ); + }, + }, +} ); +``` + +If you inspect the types of `myStore` using TypeScript, you will see that TypeScript has been able to infer the types correctly. + +```ts +const myStore: { + state: { + counter: number; + }; + actions: { + increment(): void; + }; + callbacks: { + log(): void; + }; +}; +``` + +You can also destructure the `state`, `actions` and `callbacks` properties, and the types will still work correctly. + +```ts +const { state } = store( 'myCounterPlugin', { + state: { + counter: 0, + }, + actions: { + increment() { + state.counter += 1; + }, + }, + callbacks: { + log() { + console.log( `counter: ${ state.counter }` ); + }, + }, +} ); +``` + +In conclusion, inferring the types is useful when you have a simple store defined in a single call to the `store` function and you do not need to type any state that has been initialized on the server. + +### 2. Manually type the server state, but infer the rest from your client store definition + +The global state that is initialized on the server with the `wp_interactivity_state` function doesn't exist on your client store definition and, therefore, needs to be manually typed. But if you don't want to define all the types of your store, you can infer the types of your client store definition and merge them with the types of your server initialized state. + +_Please, visit [the Server-side Rendering guide](/docs/reference-guides/interactivity-api/core-concepts/server-side-rendering.md) to learn more about `wp_interactivity_state` and how directives are processed on the server._ + +Following our previous example, let's move our `counter` state initialization to the server. + +```php +wp_interactivity_state( 'myCounterPlugin', array( + 'counter' => 1, +)); +``` + +Now, let's define the server state types and merge it with the types inferred from the client store definition. + +```ts +// Types the server state. +type ServerState = { + state: { + counter: number; + }; +}; + +// Defines the store in a variable to be able to extract its type later. +const storeDef = { + actions: { + increment() { + state.counter += 1; + }, + }, + callbacks: { + log() { + console.log( `counter: ${ state.counter }` ); + }, + }, +}; + +// Merges the types of the server state and the client store definition. +type Store = ServerState & typeof storeDef; + +// Injects the final types when calling the `store` function. +const { state } = store< Store >( 'myCounterPlugin', storeDef ); +``` + +Alternatively, if you don't mind typing the entire state including both the values defined on the server and the values defined on the client, you can cast the `state` property and let TypeScript infer the rest of the store. + +Let's imagine you have an additional property in the client global state called `product`. + +```ts +type State = { + counter: number; // The server state. + product: number; // The client state. +}; + +const { state } = store( 'myCounterPlugin', { + state: { + product: 2, + } as State, // Casts the entire state manually. + actions: { + increment() { + state.counter * state.product; + }, + }, +} ); +``` + +That's it. Now, TypeScript will infer the types of the `actions` and `callbacks` properties from the store definition, but it will use the type `State` for the `state` property so it contains the correct types from both the client and server definitions. + +In conclusion, this approach is useful when you have a server state that needs to be manually typed, but you still want to infer the types of the rest of the store. + +### 3. Manually write all the types + +If you prefer to define all the types of the store manually instead of letting TypeScript infer them from your client store definition, you can do that too. You simply need to pass them to the `store` function. + +```ts +// Defines the store types. +interface Store { + state: { + counter: number; // Initial server state + }; + actions: { + increment(): void; + }; + callbacks: { + log(): void; + }; +} + +// Pass the types when calling the `store` function. +const { state } = store< Store >( 'myCounterPlugin', { + actions: { + increment() { + state.counter += 1; + }, + }, + callbacks: { + log() { + console.log( `counter: ${ state.counter }` ); + }, + }, +} ); +``` + +That's it! In conclusion, this approach is useful when you want to control all the types of your store and you don't mind writing them by hand. + +## Typing the local context + +The initial local context is defined on the server using the `data-wp-context` directive. + +```html +
...
+``` + +For that reason, you need to define its type manually and pass it to the `getContext` function to ensure the returned properties are correctly typed. + +```ts +// Defines the types of your context. +type MyContext = { + counter: number; +}; + +store( 'myCounterPlugin', { + actions: { + increment() { + // Passes it to the getContext function. + const context = getContext< MyContext >(); + // Now `context` is properly typed. + context.counter += 1; + }, + }, +} ); +``` + +To avoid having to pass the context types over and over, you can also define a typed function and use that function instead of `getContext`. + +```ts +// Defines the types of your context. +type MyContext = { + counter: number; +}; + +// Defines a typed function. You only have to do this once. +const getMyContext = getContext< MyContext >; + +store( 'myCounterPlugin', { + actions: { + increment() { + // Use your typed function. + const context = getMyContext(); + // Now `context` is properly typed. + context.counter += 1; + }, + }, +} ); +``` + +That's it! Now you can access the context properties with the correct types. + +## Typing the derived state + +The derived state is data that is calculated based on the global state or local context. In the client store definition, it is defined using a getter in the `state` object. + +_Please, visit the [Understanding global state, local context and derived state](./undestanding-global-state-local-context-and-derived-state.md) guide to learn more about how derived state works in the Interactivity API._ + +Following our previous example, let's create a derived state that is the double of our counter. + +```ts +type MyContext = { + counter: number; +}; + +const myStore = store( 'myCounterPlugin', { + state: { + get double() { + const { counter } = getContext< MyContext >(); + return counter * 2; + }, + }, + actions: { + increment() { + state.counter += 1; // This type is number. + }, + }, +} ); +``` + +Normally, when the derived state depends on the local context, TypeScript will be able to infer the correct types: + +```ts +const myStore: { + state: { + readonly double: number; + }; + actions: { + increment(): void; + }; +}; +``` + +But when the return value of the derived state depends directly on some part of the global state, TypeScript will not be able to infer the types because it will claim that it has a circular reference. + +For example, in this case, TypeScript cannot infer the type of `state.double` because it depends on `state.counter`, and the type of `state` is not completed until the type of `state.double` is defined, creating a circular reference. + +```ts +const { state } = store( 'myCounterPlugin', { + state: { + counter: 0, + get double() { + // TypeScript can't infer this return type because it depends on `state`. + return state.counter * 2; + }, + }, + actions: { + increment() { + state.counter += 1; // This type is now unknown. + }, + }, +} ); +``` + +In this case, depending on your TypeScript configuration, TypeScript will either warn you about a circular reference or simply add the `any` type to the `state` property. + +However, solving this problem is easy; we simply need to manually provide TypeScript with the return type of that getter. Once we do that, the circular reference disappears, and TypeScript can once again infer all the `state` types. + +```ts +const { state } = store( 'myCounterPlugin', { + state: { + counter: 1, + get double(): number { + return state.counter * 2; + }, + }, + actions: { + increment() { + state.counter += 1; // Correctly inferred! + }, + }, +} ); +``` + +These are now the correct inferred types for the previous store. + +```ts +const myStore: { + state: { + counter: number; + readonly double: number; + }; + actions: { + increment(): void; + }; +}; +``` + +When using `wp_interactivity_state` in the server, remember that you also need to define the initial value of your derived state, like this: + +```php +wp_interactivity_state( 'myCounterPlugin', array( + 'counter' => 1, + 'double' => 2, +)); +``` + +But if you are inferring the types, you don't need to manually define the type of the derived state because it already exists in your client's store definition. + +```ts +// You don't need to type `state.double` here. +type ServerState = { + state: { + counter: number; + }; +}; + +// The `state.double` type is inferred from here. +const storeDef = { + state: { + get double(): number { + return state.counter * 2; + }, + }, + actions: { + increment() { + state.counter += 1; + }, + }, +}; + +// Merges the types of the server state and the client store definition. +type Store = ServerState & typeof storeDef; + +// Injects the final types when calling the `store` function. +const { state } = store< Store >( 'myCounterPlugin', storeDef ); +``` + +That's it! Now you can access the derived state properties with the correct types. + +## Typing asynchronous actions + +Another thing to keep in mind when using TypeScript with the Interactivity API is that asynchronous actions must be defined with generators instead of async functions. + +The reason for using generators in the Interactivity API's asynchronous actions is to be able to restore the scope from the initially triggered action once the asynchronous action continues its execution after yielding. But this is a syntax change only, otherwise, **these functions operate just like regular async functions**, and the inferred types from the `store` function reflect this. + +Following our previous example, let's add an asynchronous action to the store. + +```ts +const { state } = store( 'myCounterPlugin', { + state: { + counter: 0, + get double(): number { + return state.counter * 2; + }, + }, + actions: { + increment() { + state.counter += 1; + }, + *delayedIncrement() { + yield new Promise( ( r ) => setTimeout( r, 1000 ) ); + state.counter += 1; + }, + }, +} ); +``` + +The inferred types for this store are: + +```ts +const myStore: { + state: { + counter: number; + readonly double: number; + }; + actions: { + increment(): void; + // This behaves like a regular async function. + delayedIncrement(): Promise< void >; + }; +}; +``` + +This also means that you can use your async actions in external functions, and TypeScript will correctly use the async function types. + +```ts +const someAsyncFunction = async () => { + // This works fine and it's correctly typed. + await actions.delayedIncrement( 2000 ); +}; +``` + +When you are not inferring types but manually writing the types for your entire store, you can use async function types for your async actions. + +```ts +type Store = { + state: { + counter: number; + readonly double: number; + }; + actions: { + increment(): void; + delayedIncrement(): Promise< void >; // You can use async functions here. + }; +}; +``` + +There's something to keep in mind when when using asynchronous actions. Just like with the derived state, if the asynchronous action needs to return a value and this value directly depends on some part of the global state, TypeScript will not be able to infer the type due to a circular reference. + + ```ts + const { state, actions } = store( 'myCounterPlugin', { + state: { + counter: 0, + }, + actions: { + *delayedReturn() { + yield new Promise( ( r ) => setTimeout( r, 1000 ) ); + return state.counter; // TypeScript can't infer this return type. + }, + }, + } ); + ``` + + In this case, just as we did with the derived state, we must manually type the return value of the generator. + + ```ts + const { state, actions } = store( 'myCounterPlugin', { + state: { + counter: 0, + }, + actions: { + *delayedReturn(): Generator< uknown, number, uknown > { + yield new Promise( ( r ) => setTimeout( r, 1000 ) ); + return state.counter; // Now this is correctly inferred. + }, + }, + } ); + ``` + + That's it! Remember that the return type of a Generator is the second generic argument: `Generator< unknown, ReturnType, unknown >`. + +## Typing stores that are divided into multiple parts + +Sometimes, stores can be divided into different files. This can happen when different blocks share the same namespace, with each block loading the part of the store it needs. + +Let's look at an example of two blocks: + +- `todo-list`: A block that displays a list of todos. +- `add-post-to-todo`: A block that shows a button to add a new todo item to the list with the text "Read {$post_title}". + +First, let's initialize the global and derived state of the `todo-list` block on the server. + +```php + $todos, + 'filter' => 'all', + 'filteredTodos' => $todos, +)); +?> + + +``` + +Now, let's type the server state and add the client store definition. Remember, `filteredTodos` is derived state, so you don't need to type it manually. + +```ts +// todo-list-block/view.ts +type ServerState = { + state: { + todos: string[]; + filter: 'all' | 'completed'; + }; +}; + +const todoList = { + state: { + get filteredTodos(): string[] { + return state.filter === 'completed' + ? state.todos.filter( ( todo ) => todo.includes( 'āœ…' ) ) + : state.todos; + }, + }, + actions: { + addTodo( todo: string ) { + state.todos.push( todo ); + }, + }, +}; + +// Merges the inferred types with the server state types. +export type TodoList = ServerState & typeof todoList; + +// Injects the final types when calling the `store` function. +const { state } = store< TodoList >( 'myTodoPlugin', todoList ); +``` + +So far, so good. Now let's create our `add-post-to-todo` block. + +First, let's add the current post title to the server state. + +```php + get_the_title(), +)); +?> + + +``` + +Now, let's type that server state and add the client store definition. + +```ts +// add-post-to-todo-block/view.ts +type ServerState = { + state: { + postTitle: string; + }; +}; + +const addPostToTodo = { + actions: { + addPostToTodo() { + const todo = `Read: ${ state.postTitle }`.trim(); + if ( ! state.todos.includes( todo ) ) { + actions.addTodo( todo ); + } + }, + }, +}; + +// Merges the inferred types with the server state types. +type Store = ServerState & typeof addPostToTodo; + +// Injects the final types when calling the `store` function. +const { state, actions } = store< Store >( 'myTodoPlugin', addPostToTodo ); +``` + +This works fine in the browser, but TypeScript will complain that, in this block, `state` and `actions` do not include `state.todos` and `actions.addtodo`. + +To fix this, we need to import the `TodoList` type from the `todo-list` block and merge it with the other types. + +```ts +import type { TodoList } from '../todo-list-block/view'; + +// ... + +// Merges the inferred types inferred the server state types. +type Store = TodoList & ServerState & typeof addPostToTodo; +``` + +That's it! Now TypeScript will know that `state.todos` and `actions.addTodo` are available in the `add-post-to-todo` block. + +This approach allows the `add-post-to-todo` block to interact with the existing todo list while maintaining type safety and adding its own functionality to the shared store. + +If you need to use the `add-post-to-todo` types in the `todo-list` block, you simply have to export its types and import them in the other `view.ts` file. + +Finally, if you prefer to define all types manually instead of inferring them, you can define them in a separate file and import that definition into each of your store parts. Here's how you could do that for our todo list example: + +```ts +// types.ts +interface Store { + state: { + todos: string[]; + filter: 'all' | 'completed'; + filtered: string[]; + postTitle: string; + }; + actions: { + addTodo( todo: string ): void; + addPostToTodo(): void; + }; +} + +export default Store; +``` + +```ts +// todo-list-block/view.ts +import type Store from '../types'; + +const { state } = store< Store >( 'myTodoPlugin', { + // Everything is correctly typed here +} ); +``` + +```ts +// add-post-to-todo-block/view.ts +import type Store from '../types'; + +const { state, actions } = store< Store >( 'myTodoPlugin', { + // Everything is correctly typed here +} ); +``` + +This approach allows you to have full control over your types and ensures consistency across all parts of your store. It's particularly useful when you have a complex store structure or when you want to enforce a specific interface across multiple blocks or components. + +## Importing and exporting typed stores + +In the Interactivity API, stores from other namespaces can be accessed using the `store` function. + +Let's go back to our `todo-list` block example, but this time, let's imagine that the `add-post-to-todo` block belongs to a different plugin and therefore will use a different namespace. + +```ts +// Import the store of the `todo-list` block. +const myTodoPlugin = store( 'myTodoPlugin' ); + +store( 'myAddPostToTodoPlugin', { + actions: { + addPostToTodo() { + const todo = `Read: ${ state.postTitle }`.trim(); + if ( ! myTodoPlugin.state.todos.includes( todo ) ) { + myTodoPlugin.actions.addTodo( todo ); + } + }, + }, +} ); +``` + +This works fine in the browser, but TypeScript will complain that `myTodoPlugin.state` and `myTodoPlugin.actions` are not typed. + +To fix that, the `myTodoPlugin` plugin can export the result of calling the `store` function with the correct types, and make that available using a script module. + +```ts +// Export the already typed state and actions. +export const { state, actions } = store< TodoList >( 'myTodoPlugin', { + // ... +} ); +``` + +Now, the `add-post-to-todo` block can import the typed store from the `myTodoPlugin` script module, and it not only ensures that the store will be loaded, but that it also contains the correct types. + +```ts +import { store } from '@wordpress/interactivity'; +import { + state as todoState, + actions as todoActions, +} from 'my-todo-plugin-module'; + +store( 'myAddPostToTodoPlugin', { + actions: { + addPostToTodo() { + const todo = `Read: ${ state.postTitle }`.trim(); + if ( ! todoState.todos.includes( todo ) ) { + todoActions.addTodo( todo ); + } + }, + }, +} ); +``` + +Remember that you will need to declare the `my-todo-plugin-module` script module as a dependency. + +If the other store is optional and you don't want to load it eagerly, a dynamic import can be used instead of a static import. + +```ts +import { store } from '@wordpress/interactivity'; + +store( 'myAddPostToTodoPlugin', { + actions: { + *addPostToTodo() { + const todoPlugin = yield import( 'my-todo-plugin-module' ); + const todo = `Read: ${ state.postTitle }`.trim(); + if ( ! todoPlugin.state.todos.includes( todo ) ) { + todoPlugin.actions.addTodo( todo ); + } + }, + }, +} ); +``` + +## Conclusion + +In this guide, we explored different approaches to typing the Interactivity API stores, from inferring types automatically to manually defining them. We also covered how to handle server-initialized state, local context, and derived state, as well as how to type asynchronous actions. + +Remember that the choice between inferring types and manually defining them depends on your specific needs and the complexity of your store. Whichever approach you choose, TypeScript will help you build better and more reliable interactive blocks. diff --git a/docs/reference-guides/interactivity-api/iapi-about.md b/docs/reference-guides/interactivity-api/iapi-about.md index 887a284be298fb..08689be03aa383 100644 --- a/docs/reference-guides/interactivity-api/iapi-about.md +++ b/docs/reference-guides/interactivity-api/iapi-about.md @@ -130,7 +130,7 @@ store( 'wpmovies', { > + + + + ); +}; + +registerPlugin( 'plugin-sidebar-more-menu-item-example', { render: PluginSidebarMoreMenuItemTest, } ); ``` ## Location -![Interaction](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/plugin-sidebar-more-menu-item.gif?raw=true) +![Interaction](https://developer.wordpress.org/files/2024/08/pluginsidebar-more-menu-item-1.gif) diff --git a/docs/reference-guides/slotfills/plugin-sidebar.md b/docs/reference-guides/slotfills/plugin-sidebar.md index 8fe274414905b7..9bf911b3bb13f2 100644 --- a/docs/reference-guides/slotfills/plugin-sidebar.md +++ b/docs/reference-guides/slotfills/plugin-sidebar.md @@ -1,30 +1,67 @@ # PluginSidebar -This slot allows for adding items into the Gutenberg Toolbar. -Using this slot will add an icon to the bar that, when clicked, will open a sidebar with the content of the items wrapped in the `` component. +This slot allows adding items to the tool bar of either the Post or Site editor screens. +Using this slot will add an icon to the toolbar that, when clicked, opens a panel with containing the items wrapped in the `` component. +Additionally, it will also create a `` that will allow opening the panel from Options panel when clicked. ## Example -```js -import { registerPlugin } from '@wordpress/plugins'; +```jsx +import { __ } from '@wordpress/i18n'; import { PluginSidebar } from '@wordpress/editor'; -import { image } from '@wordpress/icons'; +import { + PanelBody, + Button, + TextControl, + SelectControl, +} from '@wordpress/components'; +import { registerPlugin } from '@wordpress/plugins'; +import { useState } from '@wordpress/element'; + +const PluginSidebarExample = () => { + const [ text, setText ] = useState( '' ); + const [ select, setSelect ] = useState( 'a' ); -const PluginSidebarTest = () => ( - -

Plugin Sidebar

-
-); + return ( + + +

+ { __( 'This is a heading for the PluginSidebar example.' ) } +

+

+ { __( + 'This is some example text for the PluginSidebar example.' + ) } +

+ setText( newText ) } + /> + setSelect( newSelect ) } + /> + +
+
+ ); +}; -registerPlugin( 'plugin-sidebar-test', { render: PluginSidebarTest } ); +// Register the plugin. +registerPlugin( 'plugin-sidebar-example', { render: PluginSidebarExample } ); ``` ## Location -### Closed State - -![Closed State](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/plugin-sidebar-closed-state.png?raw=true) - -### Open State - -![Open State](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/plugin-sidebar-open-state.png?raw=true) +![PluginSidebar example expanded](https://developer.wordpress.org/files/2024/08/plugin-sidebar-example.png) 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 97241598d4c9fa..8d110aeb1809d3 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -13,7 +13,7 @@ --> > 恓恮ꖇę›øćÆ `theme.json` **ćƒćƒ¼ć‚øćƒ§ćƒ³3**恮ē¾åœØć®ä»•ę§˜ć§ć™ć€‚ć“ć®ćƒćƒ¼ć‚øćƒ§ćƒ³ćÆ态WordPress 6.6仄降ćØęœ€ę–°ć® Gutenberg ćƒ—ćƒ©ć‚°ć‚¤ćƒ³ć§å‹•ä½œć—ć¾ć™ć€‚ > -> é–¢é€£ć™ć‚‹čˆˆå‘³ę·±ć„ćƒ‰ć‚­ćƒ„ćƒ”ćƒ³ćƒˆćØ恗恦仄äø‹ćŒć‚ć‚Šć¾ć™ć€‚ +> é–¢é€£ćƒ‰ć‚­ćƒ„ćƒ”ćƒ³ćƒˆćØ恗恦仄äø‹ćŒć‚ć‚Šć¾ć™ć€‚ > - [theme.json v1](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/theme-json-reference/theme-json-v1/) ä»•ę§˜ > - [theme.json v2](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/theme-json-reference/theme-json-v1/) ä»•ę§˜ > - [å¤ć„ćƒćƒ¼ć‚øćƒ§ćƒ³ć® theme.json 恋悉恮ē§»č”ŒćƒŖćƒ•ć‚”ćƒ¬ćƒ³ć‚¹](https://ja.wordpress.org/team/handbook/block-editor/reference-guides/theme-json-reference/theme-json-migrations/) @@ -27,18 +27,22 @@ This reference guide lists the settings and style properties defined in the `the + + + @@ -67,11 +71,40 @@ See [Developing with theme.json](/docs/how-to-guides/themes/global-settings-and- - ## settings + +惖惭惃ć‚Æć‚Øćƒ‡ć‚£ć‚æćƒ¼ćØå€‹ć€…ć®ćƒ–ćƒ­ćƒƒć‚Æ恮čØ­å®šć€‚ä»„äø‹ćŒå«ć¾ć‚Œć¾ć™ć€‚ +- ćƒ¦ćƒ¼ć‚¶ćƒ¼ćŒåˆ©ē”Øć§ćć‚‹ć‚«ć‚¹ć‚æ惞悤ć‚ŗć‚Ŗćƒ—ć‚·ćƒ§ćƒ³ć€‚ +- ćƒ¦ćƒ¼ć‚¶ćƒ¼ćŒåˆ©ē”Øć§ćć‚‹ćƒ‡ćƒ•ć‚©ćƒ«ćƒˆć®č‰²ć€ćƒ•ć‚©ćƒ³ćƒˆć‚µć‚¤ć‚ŗ ...怂 +- ć‚¹ć‚æć‚¤ćƒ«ć§ä½æē”Ø恕悌悋 CSS ć‚«ć‚¹ć‚æćƒ ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ćØć‚Æćƒ©ć‚¹åć€‚ +- ć‚Øćƒ‡ć‚£ć‚æćƒ¼ć®ćƒ‡ćƒ•ć‚©ćƒ«ćƒˆćƒ¬ć‚¤ć‚¢ć‚¦ćƒˆ (幅ćØ利ē”ØåÆčƒ½ćŖ配ē½®)怂 + +### useRootPaddingAwareAlignments + + +ćƒ«ćƒ¼ćƒˆćƒ–ćƒ­ćƒƒć‚Æć®ä»£ć‚ć‚Šć«å…Øå¹…ćƒ–ćƒ­ćƒƒć‚Æć®ć‚³ćƒ³ćƒ†ćƒ³ćƒ„ć«ć€ćƒ«ćƒ¼ćƒˆćƒ‘ćƒ‡ć‚£ćƒ³ć‚° (`styles.spacing.padding` ć®å€¤) 悒適ē”Ø恙悋恓ćØ悒åÆčƒ½ć«ć—ć¾ć™ć€‚ + + +ę³Øꄏ: 恓恮čØ­å®šć‚’ä½æē”Øć™ć‚‹å “åˆćÆ `styles.spacing.padding`ćÆåøø恫 `top`态`right`态`bottom`态`left` ć®å€¤ć‚’å€‹åˆ„ć«å®£čØ€ć—ćŸć‚Ŗ惖ć‚ø悧ć‚Æ惈ćØ恗恦čØ­å®šć—ć¦ćć ć•ć„ć€‚ + + +**ę³Øꄏ:** ꜀äøŠä½ćƒ¬ćƒ™ćƒ«ć®ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć€‚ćƒ–ćƒ­ćƒƒć‚Æ恧ćÆ利ē”ØäøåÆ怂 + +--- + ### appearanceTools -_**ę³Øꄏ:** WordPress 6.1仄降_ +### background -ćƒ«ćƒ¼ćƒˆćƒ–ćƒ­ćƒƒć‚Æć®ä»£ć‚ć‚Šć«å…Øå¹…ćƒ–ćƒ­ćƒƒć‚Æć®å†…å®¹ć«ć€ćƒ«ćƒ¼ćƒˆćƒ‘ćƒ‡ć‚£ćƒ³ć‚° (`styles.spacing.padding` ć®å€¤) 悒適ē”Ø恙悋恓ćØ悒åÆčƒ½ć«ć—ć¾ć™ć€‚ +背ę™Æé–¢é€£ć®čØ­å®šć€‚ -ę³Øꄏ: 恓恮čØ­å®šć‚’ä½æē”Øć™ć‚‹å “åˆć€`styles.spacing.padding` ćÆåøø恫ć‚Ŗ惖ć‚ø悧ć‚Æ惈ćØ恗恦čØ­å®šć—ć€`top`, `right`, `bottom`, `left`ć®å€¤ćÆåˆ„ć€…ć«å®£čØ€ć™ć‚‹åæ…č¦ćŒć‚ć‚Šć¾ć™ć€‚ +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| backgroundImage | 背ę™Æē”»åƒć®čح定 | `boolean` | `false` | +| backgroundSize | 背ę™Æē”»åƒć®ć‚µć‚¤ć‚ŗć«é–¢ć™ć‚‹å€¤ć€ć‚µć‚¤ć‚ŗć€ä½ē½®ć€ćƒŖćƒ”ćƒ¼ćƒˆć‚³ćƒ³ćƒˆćƒ­ćƒ¼ćƒ«ćŖ恩恮čح定 | `boolean` | `false` | --- @@ -114,25 +143,12 @@ Settings related to borders. --> ćƒœćƒ¼ćƒ€ćƒ¼é–¢é€£ć®čØ­å®šć€‚ -| Property | Type | Default | Props | -| --- | --- | --- |--- | -| color | boolean | false | | -| radius | boolean | false | | -| style | boolean | false | | -| width | boolean | false | | - ---- - -### shadow - -ć‚·ćƒ£ćƒ‰ćƒ¼é–¢é€£ć®čØ­å®šć€‚ - -| Property | Type | Default | Props | -| --- | --- | --- |--- | -| defaultPresets | boolean | true | | -| presets | array | | name, shadow, slug | +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| color | ć‚«ć‚¹ć‚æćƒ ćƒœćƒ¼ćƒ€ćƒ¼č‰²ć®čح定 | `boolean` | `false` | +| radius | ć‚«ć‚¹ć‚æćƒ ćƒœćƒ¼ćƒ€ćƒ¼č§’äøøåŠå¾„ć®čح定 | `boolean` | `false` | +| style | ć‚«ć‚¹ć‚æćƒ ćƒœćƒ¼ćƒ€ćƒ¼ć‚¹ć‚æć‚¤ćƒ«ć®čح定 | `boolean` | `false` | +| width | ć‚«ć‚¹ć‚æćƒ ćƒœćƒ¼ćƒ€ćƒ¼å¹…ć®čح定 | `boolean` | `false` | --- @@ -143,37 +159,23 @@ Settings related to colors. --> č‰²é–¢é€£ć®čØ­å®šć€‚ -| Property | Type | Default | Props | -| --- | --- | --- |--- | -| background | boolean | true | | -| custom | boolean | true | | -| customDuotone | boolean | true | | -| customGradient | boolean | true | | -| defaultDuotone | boolean | true | | -| defaultGradients | boolean | true | | -| defaultPalette | boolean | true | | -| duotone | array | | colors, name, slug | -| gradients | array | | gradient, name, slug | -| link | boolean | false | | -| palette | array | | color, name, slug | -| text | boolean | true | | -| heading | boolean | true | | -| button | boolean | true | | -| caption | boolean | true | | - ---- - -### background - - -背ę™Æé–¢é€£ć®čØ­å®šć€‚ - -| Property | Type | Default | Props | -| --- | --- | --- |--- | -| backgroundImage | boolean | false | | -| backgroundSize | boolean | false | | +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| background | 背ę™Æč‰²ć®čح定 | `boolean` | `true` | +| custom | ć‚«ć‚¹ć‚æćƒ č‰²ć®éøꊞ | `boolean` | `true` | +| customDuotone | ć‚«ć‚¹ć‚æćƒ ćƒ‡ćƒ„ć‚Ŗćƒˆćƒ¼ćƒ³ćƒ•ć‚£ćƒ«ć‚æćƒ¼ć®ä½œęˆ | `boolean` | `true` | +| customGradient | ć‚«ć‚¹ć‚æćƒ ć‚°ćƒ©ćƒ‡ćƒ¼ć‚·ćƒ§ćƒ³ć®ä½œęˆ | `boolean` | `true` | +| defaultDuotone | ćƒ‡ćƒ•ć‚©ćƒ«ćƒˆćƒ‡ćƒ„ć‚Ŗćƒˆćƒ¼ćƒ³ćƒ•ć‚£ćƒ«ć‚æćƒ¼ćƒ—ćƒŖć‚»ćƒƒćƒˆć‹ć‚‰ćƒ•ć‚£ćƒ«ć‚æćƒ¼ć‚’éøꊞ | `boolean` | `true` | +| defaultGradients | ćƒ‡ćƒ•ć‚©ćƒ«ćƒˆć‚°ćƒ©ćƒ‡ćƒ¼ć‚·ćƒ§ćƒ³ć‹ć‚‰č‰²ć‚’éøꊞ | `boolean` | `true` | +| defaultPalette | ćƒ‡ćƒ•ć‚©ćƒ«ćƒˆćƒ‘ćƒ¬ćƒƒćƒˆć‹ć‚‰č‰²ć‚’éøꊞ | `boolean` | `true` | +| duotone | ćƒ‡ćƒ„ć‚Ŗćƒˆćƒ¼ćƒ³ćƒ”ćƒƒć‚«ćƒ¼ć®ćƒ‡ćƒ„ć‚Ŗćƒˆćƒ¼ćƒ³ćƒ—ćƒŖć‚»ćƒƒćƒˆ | `[ { name, slug, colors } ]` | | +| gradients | ć‚°ćƒ©ćƒ‡ćƒ¼ć‚·ćƒ§ćƒ³ćƒ”ćƒƒć‚«ćƒ¼ć®ć‚°ćƒ©ćƒ‡ćƒ¼ć‚·ćƒ§ćƒ³ćƒ—ćƒŖć‚»ćƒƒćƒˆ | `[ { name, slug, gradient } ]` | | +| link | 惖惭惃ć‚Æå†…ć®ćƒŖćƒ³ć‚Æč‰²ć®čح定 | `boolean` | `false` | +| palette | ć‚«ćƒ©ćƒ¼ćƒ”ćƒƒć‚«ćƒ¼ć®ć‚«ćƒ©ćƒ¼ćƒ‘ćƒ¬ćƒƒćƒˆćƒ—ćƒŖć‚»ćƒƒćƒˆ | `[ { name, slug, color } ]` | | +| text | 惖惭惃ć‚Æå†…ć®ćƒ†ć‚­ć‚¹ćƒˆč‰²ć®čح定 | `boolean` | `true` | +| heading | 惖惭惃ć‚Æå†…ć®č¦‹å‡ŗć—č‰²ć®čح定 | `boolean` | `true` | +| button | 惖惭惃ć‚Æå†…ć®ćƒœć‚æćƒ³č‰²ć®čح定 | `boolean` | `true` | +| caption | 惖惭惃ć‚Æå†…ć®ć‚­ćƒ£ćƒ—ć‚·ćƒ§ćƒ³č‰²ć®čح定 | `boolean` | `true` | --- @@ -183,12 +185,12 @@ Settings related to dimensions. --> åÆøę³•é–¢é€£ć®čØ­å®šć€‚ -| Property | Type | Default | Props | -| --- | --- | --- |--- | -| aspectRatio | boolean | false | | -| defaultAspectRatios | boolean | true | | -| aspectRatios | array | | name, ratio, slug | -| minHeight | boolean | false | | +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| aspectRatio | ć‚¢ć‚¹ćƒšć‚Æ惈ęÆ”ć®čح定 | `boolean` | `false` | +| defaultAspectRatios | ć‚¢ć‚¹ćƒšć‚Æ惈ęÆ”ć®ćƒ‡ćƒ•ć‚©ćƒ«ćƒˆć‚»ćƒƒćƒˆć‹ć‚‰ć‚¢ć‚¹ćƒšć‚Æ惈ęÆ”ć‚’éøꊞ | `boolean` | `true` | +| aspectRatios | ć„ćć¤ć‹ć®ćƒ–ćƒ­ćƒƒć‚Æć§ć‚¢ć‚¹ćƒšć‚Æ惈ęÆ”ć‚’å®šē¾© | `[ { name, slug, ratio } ]` | | +| minHeight | ć‚«ć‚¹ć‚æćƒ ęœ€å°é«˜ć‚’čح定 | `boolean` | `false` | --- @@ -199,12 +201,12 @@ Settings related to layout. --> ćƒ¬ć‚¤ć‚¢ć‚¦ćƒˆé–¢é€£ć®čØ­å®šć€‚ -| Property | Type | Default | Props | -| --- | --- | --- |--- | -| contentSize | string | | | -| wideSize | string | | | -| allowEditing | boolean | true | | -| allowCustomContentAndWideSize | boolean | true | | +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| contentSize | ć‚³ćƒ³ćƒ†ćƒ³ćƒ„ć® max-width 悒čح定 | `string` | | +| wideSize | 幅åŗƒ (`.alignwide`) ć‚³ćƒ³ćƒ†ćƒ³ćƒ„ć® max-width 恮čØ­å®šć€‚ćƒ•ćƒ«ćƒ¼ćƒ‰ćƒ•ć‚©ćƒ³ćƒˆć‚µć‚¤ć‚ŗč؈ē®—Ꙃ恮 maximum viewport ćØ恗恦悂ä½æē”Ø恕悌悋 | `string` | | +| allowEditing | ćƒ¬ć‚¤ć‚¢ć‚¦ćƒˆ UI ć‚³ćƒ³ćƒˆćƒ­ćƒ¼ćƒ«ć®ē„”効化 | `boolean` | `true` | +| allowCustomContentAndWideSize | ć‚«ć‚¹ć‚æćƒ ć‚³ćƒ³ćƒ†ćƒ³ćƒˆćØ悵悤ć‚ŗć‚³ćƒ³ćƒˆćƒ­ćƒ¼ćƒ«ć®ęœ‰åŠ¹åŒ–ć€ē„”効化 | `boolean` | `true` | --- @@ -215,10 +217,10 @@ Settings related to the lightbox. --> lightbox é–¢é€£ć®čØ­å®šć€‚ -| Property | Type | Default | Props | -| --- | --- | --- |--- | -| enabled | boolean | | | -| allowEditing | boolean | | | +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| enabled | lightbox ćŒęœ‰åŠ¹åŒ–ć•ć‚Œć¦ć„ć‚‹ć‹ć©ć†ć‹ć®å®šē¾© | `boolean` | | +| allowEditing | 惖惭惃ć‚Æć‚Øćƒ‡ć‚£ć‚æćƒ¼å†…ć§ Lightbox UI 悒č”Øē¤ŗć™ć‚‹ć‹ć©ć†ć‹ć®å®šē¾©ć€‚`false` 恫čØ­å®šć™ć‚‹ćØć€ćƒ¦ćƒ¼ć‚¶ćƒ¼ćÆ惖惭惃ć‚Æć‚Øćƒ‡ć‚£ć‚æćƒ¼å†…ć§ lightbox 恮čØ­å®šć‚’å¤‰ę›“ć§ććŖ恄 | `boolean` | | --- @@ -229,9 +231,23 @@ Settings related to position. --> 位ē½®é–¢é€£ć®čØ­å®šć€‚ -| Property | Type | Default | Props | -| --- | --- | --- |--- | -| sticky | boolean | false | | +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| sticky | 位ē½®å›ŗå®šć®čح定 | `boolean` | `false` | + +--- + +### shadow + + +ć‚·ćƒ£ćƒ‰ćƒ¼é–¢é€£ć®čح定 + +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| defaultPresets | ćƒ‡ćƒ•ć‚©ćƒ«ćƒˆć‚·ćƒ£ćƒ‰ćƒ¼ćƒ—ćƒŖć‚»ćƒƒćƒˆć‹ć‚‰ć‚·ćƒ£ćƒ‰ćƒ¼ć‚’éøꊞ | `boolean` | `true` | +| presets | ć‚·ćƒ£ćƒ‰ćƒ¼ćƒ”ćƒƒć‚«ćƒ¼ć®ć‚·ćƒ£ćƒ‰ćƒ¼ćƒ—ćƒŖć‚»ćƒƒćƒˆ | `[ { name, slug, shadow } ]` | | --- @@ -242,16 +258,16 @@ Settings related to spacing. --> ć‚¹ćƒšćƒ¼ć‚¹é–¢é€£ć®čØ­å®šć€‚ -| Property | Type | Default | Props | -| --- | --- | --- |--- | -| blockGap | boolean, null | null | | -| margin | boolean | false | | -| padding | boolean | false | | -| units | array | px,em,rem,vh,vw,% | | -| customSpacingSize | boolean | true | | -| defaultSpacingSizes | boolean | true | | -| spacingSizes | array | | name, size, slug | -| spacingScale | object | | | +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| blockGap | styles.spacing.blockGap 恋悉 `--wp--style--block-gap` 恮ē”Ÿęˆć‚’ęœ‰åŠ¹åŒ– | `boolean`, `null` | `null` | +| margin | ć‚«ć‚¹ć‚æćƒ ćƒžćƒ¼ć‚øćƒ³ć®čح定 | `boolean` | `false` | +| padding | ć‚«ć‚¹ć‚æćƒ ćƒ‘ćƒ‡ć‚£ćƒ³ć‚°ć®čح定 | `boolean` | `false` | +| units | ć‚¹ćƒšćƒ¼ć‚¹å€¤ć«åˆ©ē”ØåÆčƒ½ćŖå˜ä½ć®ćƒŖć‚¹ćƒˆ | `[ string ]` | `["px","em","rem","vh","vw","%"]` | +| customSpacingSize | ć‚«ć‚¹ć‚æćƒ ć‚¹ćƒšćƒ¼ć‚¹ć‚µć‚¤ć‚ŗ恮čح定 | `boolean` | `true` | +| defaultSpacingSizes | ćƒ‡ćƒ•ć‚©ćƒ«ćƒˆć‚¹ćƒšćƒ¼ć‚¹ć‚µć‚¤ć‚ŗ惗ćƒŖć‚»ćƒƒćƒˆć‹ć‚‰ć‚¹ćƒšćƒ¼ć‚¹ć‚µć‚¤ć‚ŗ悒éøꊞ | `boolean` | `true` | +| spacingSizes | ć‚¹ćƒšćƒ¼ć‚¹ć‚µć‚¤ć‚ŗć‚»ćƒ¬ć‚Æć‚æć®ć‚¹ćƒšćƒ¼ć‚¹ć‚µć‚¤ć‚ŗ惗ćƒŖć‚»ćƒƒćƒˆ | `[ { name, slug, size } ]` | | +| spacingScale | ć‚¹ćƒšćƒ¼ć‚¹ć‚µć‚¤ć‚ŗć‚»ćƒ¬ć‚Æć‚æ恮č‡Ŗ動ē”Ÿęˆć‚¹ćƒšćƒ¼ć‚¹ć‚µć‚¤ć‚ŗ惗ćƒŖć‚»ćƒƒćƒˆć®čح定 | `{ operator, increment, steps, mediumStep, unit }` | | --- @@ -262,23 +278,23 @@ Settings related to typography. --> ć‚æć‚¤ćƒć‚°ćƒ©ćƒ•ć‚£é–¢é€£ć®čØ­å®šć€‚ -| Property | Type | Default | Props | -| --- | --- | --- |--- | -| defaultFontSizes | boolean | true | | -| customFontSize | boolean | true | | -| fontStyle | boolean | true | | -| fontWeight | boolean | true | | -| fluid | object, boolean | false | _{maxViewportWidth, minFontSize, minViewportWidth}_ | -| letterSpacing | boolean | true | | -| lineHeight | boolean | false | | -| textAlign | boolean | true | | -| textColumns | boolean | false | | -| textDecoration | boolean | true | | -| writingMode | boolean | false | | -| textTransform | boolean | true | | -| dropCap | boolean | true | | -| fontSizes | array | | fluid, name, size, slug | -| fontFamilies | array | | fontFace, fontFamily, name, slug | +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| defaultFontSizes | ćƒ‡ćƒ•ć‚©ćƒ«ćƒˆć®ćƒ•ć‚©ćƒ³ćƒˆć‚µć‚¤ć‚ŗ惗ćƒŖć‚»ćƒƒćƒˆć‹ć‚‰ćƒ•ć‚©ćƒ³ćƒˆć‚µć‚¤ć‚ŗ悒éøꊞ | `boolean` | `true` | +| customFontSize | ć‚«ć‚¹ć‚æćƒ ćƒ•ć‚©ćƒ³ćƒˆć‚µć‚¤ć‚ŗ悒čح定 | `boolean` | `true` | +| fontStyle | ć‚«ć‚¹ć‚æćƒ ćƒ•ć‚©ćƒ³ćƒˆć‚¹ć‚æć‚¤ćƒ«ć‚’čح定 | `boolean` | `true` | +| fontWeight | ć‚«ć‚¹ć‚æćƒ ćƒ•ć‚©ćƒ³ćƒˆć‚¦ć‚Ø悤惈悒čح定 | `boolean` | `true` | +| fluid | ćƒ•ćƒ«ćƒ¼ćƒ‰ć‚æć‚¤ćƒć‚°ćƒ©ćƒ•ć‚£ć®ęœ‰åŠ¹åŒ–ćØć‚°ćƒ­ćƒ¼ćƒćƒ«ćƒ•ćƒ«ćƒ¼ćƒ‰ć‚æć‚¤ćƒć‚°ćƒ©ćƒ•ć‚£ćƒ‘ćƒ©ćƒ”ćƒ¼ć‚æ恮čح定 | `boolean`, `{ minFontSize, maxViewportWidth, minViewportWidth }` | `false` | +| letterSpacing | ć‚«ć‚¹ć‚æćƒ ę–‡å­—ć‚¹ćƒšćƒ¼ć‚¹ć®čح定 | `boolean` | `true` | +| lineHeight | ć‚«ć‚¹ć‚æćƒ č”Œé«˜ć®čح定 | `boolean` | `false` | +| textAlign | ćƒ†ć‚­ć‚¹ćƒˆé…ē½®ć®čح定 | `boolean` | `true` | +| textColumns | ćƒ†ć‚­ć‚¹ćƒˆć‚«ćƒ©ćƒ ę•°ć®čح定 | `boolean` | `false` | +| textDecoration | ć‚«ć‚¹ć‚æćƒ ćƒ†ć‚­ć‚¹ćƒˆč£…é£¾ć®čح定 | `boolean` | `true` | +| writingMode | 執ē­†ćƒ¢ćƒ¼ćƒ‰ć®čح定 | `boolean` | `false` | +| textTransform | ć‚«ć‚¹ć‚æćƒ ćƒ†ć‚­ć‚¹ćƒˆå¤‰ę›ć®čح定 | `boolean` | `true` | +| dropCap | ćƒ‰ćƒ­ćƒƒćƒ—ć‚­ćƒ£ćƒƒćƒ—ć®ęœ‰åŠ¹åŒ– | `boolean` | `true` | +| fontSizes | ćƒ•ć‚©ćƒ³ćƒˆć‚µć‚¤ć‚ŗć‚»ćƒ¬ć‚Æć‚æć®ćƒ•ć‚©ćƒ³ćƒˆć‚µć‚¤ć‚ŗ惗ćƒŖć‚»ćƒƒćƒˆ | `[ { name, slug, size, fluid } ]` | | +| fontFamilies | ćƒ•ć‚©ćƒ³ćƒˆćƒ•ć‚”ćƒŸćƒŖćƒ¼ć‚»ćƒ¬ć‚Æć‚æć®ćƒ•ć‚©ćƒ³ćƒˆćƒ•ć‚”ćƒŸćƒŖćƒ¼ćƒ—ćƒŖć‚»ćƒƒćƒˆ | `[ { name, slug, fontFamily, fontFace } ]` | | --- @@ -291,26 +307,12 @@ Generate custom CSS custom properties of the form `--wp--custom--{key}--{nested- --- - - - - ## styles + +CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć‚’čØ­å®šć™ć‚‹ę•“ē†ć•ć‚ŒćŸę–¹ę³•ć€‚ćƒˆćƒƒćƒ—ćƒ¬ćƒ™ćƒ«ć®ć‚¹ć‚æć‚¤ćƒ«ćÆ `body` ć‚»ćƒ¬ć‚Æć‚æ恫čæ½åŠ ć•ć‚Œć¾ć™ć€‚ ### background 背ę™Æć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ -| Property | Type | Props | -| --- | --- |--- | -| backgroundImage | string, object | | -| backgroundPosition | string, object | | -| backgroundRepeat | string, object | | -| backgroundSize | string, object | | +| Property | Description | Type | +| -------- | ----------- | ---- | +| backgroundImage | `background-image` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }`, `{ url }` | +| backgroundPosition | `background-position` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| backgroundRepeat | `background-repeat` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| backgroundSize | `background-size` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| backgroundAttachment | `background-attachment` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | --- @@ -334,16 +337,16 @@ Border styles. --> ćƒœćƒ¼ćƒ€ćƒ¼ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ -| Property | Type | Props | -| --- | --- |--- | -| color | string, object | | -| radius | string, object | | -| style | string, object | | -| width | string, object | | -| top | object | color, style, width | -| right | object | color, style, width | -| bottom | object | color, style, width | -| left | object | color, style, width | +| Property | Description | Type | +| -------- | ----------- | ---- | +| color | `border-color` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| radius | `border-radius` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }`, `{ topLeft, topRight, bottomLeft, bottomRight }` | +| style | `border-style` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| width | `border-width` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| top | | `{ color, style, width }` | +| right | | `{ color, style, width }` | +| bottom | | `{ color, style, width }` | +| left | | `{ color, style, width }` | --- @@ -354,76 +357,62 @@ Color styles. --> č‰²ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ -| Property | Type | Props | -| --- | --- |--- | -| background | string, object | | -| gradient | string, object | | -| text | string, object | | +| Property | Description | Type | +| -------- | ----------- | ---- | +| background | `background-color` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| gradient | `background` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| text | `color` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | --- -### dimensions - +### css -åÆøę³•ć®ć‚¹ć‚æć‚¤ćƒ« - -| Property | Type | Props | -| --- | --- |--- | -| aspectRatio | string, object | | -| minHeight | string, object | | +ć‚«ć‚¹ć‚æ惠 CSS 悒čØ­å®šć—ć¾ć™ć€‚ä»–ć® theme.json ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć§ćÆć‚«ćƒćƒ¼ć•ć‚ŒćŖć„ć‚¹ć‚æć‚¤ćƒ«ć‚’é©ē”Øć§ćć¾ć™ć€‚ --- -### spacing +### dimensions - -ć‚¹ćƒšćƒ¼ć‚¹ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ + +åÆøę³•ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ -| Property | Type | Props | -| --- | --- |--- | -| blockGap | string, object | | -| margin | object | bottom, left, right, top | -| padding | object | bottom, left, right, top | +| Property | Description | Type | +| -------- | ----------- | ---- | +| aspectRatio | `aspect-ratio` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| minHeight | `min-height` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | --- -### typography +### filter - -ć‚æć‚¤ćƒć‚°ćƒ©ćƒ•ć‚£ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ + +CSS ćØ SVG ćƒ•ć‚£ćƒ«ć‚æćƒ¼ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ -| Property | Type | Props | -| --- | --- |--- | -| fontFamily | string, object | | -| fontSize | string, object | | -| fontStyle | string, object | | -| fontWeight | string, object | | -| letterSpacing | string, object | | -| lineHeight | string, object | | -| textAlign | string | | -| textColumns | string | | -| textDecoration | string, object | | -| writingMode | string, object | | -| textTransform | string, object | | +| Property | Description | Type | +| -------- | ----------- | ---- | +| duotone | ćƒ‡ćƒ„ć‚Ŗćƒˆćƒ¼ćƒ³ćƒ•ć‚£ćƒ«ć‚æćƒ¼ć®čح定 | `string`, `{ ref }` | --- -### filter +### outline -CSS ćØ SVG ćƒ•ć‚£ćƒ«ć‚æćƒ¼ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ +ć‚¢ć‚¦ćƒˆćƒ©ć‚¤ćƒ³ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ -| Property | Type | Props | -| --- | --- |--- | -| duotone | string, object | | +| Property | Description | Type | +| -------- | ----------- | ---- | +| color | `outline-color` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| offset | `outline-offset` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| style | `outline-style` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| width | `outline-width` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | --- @@ -434,89 +423,90 @@ Box shadow styles. --> 惜惃ć‚Æć‚¹ć‚·ćƒ£ćƒ‰ćƒ¼ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ - --- -### outline +### spacing -ć‚¢ć‚¦ćƒˆćƒ©ć‚¤ćƒ³ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ +ć‚¹ćƒšćƒ¼ć‚¹ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ -| Property | Type | Props | -| --- | --- |--- | -| color | string, object | | -| offset | string, object | | -| style | string, object | | -| width | string, object | | + +| Property | Description | Type | +| -------- | ----------- | ---- | +| blockGap | settings.spacing.blockGap 恌 true 恮ćØćć® `--wp--style--block-gap` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| margin | ćƒžćƒ¼ć‚øćƒ³ć‚¹ć‚æć‚¤ćƒ« | `{ top, right, bottom, left }` | +| padding | ćƒ‘ćƒ‡ć‚£ćƒ³ć‚°ć‚¹ć‚æć‚¤ćƒ« | `{ top, right, bottom, left }` | --- -### css +### typography + -ć‚«ć‚¹ć‚æ惠 CSS 悒čØ­å®šć—ć¾ć™ć€‚ä»–ć® theme.json ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć§ćÆć‚«ćƒćƒ¼ć•ć‚ŒćŖć„ć‚¹ć‚æć‚¤ćƒ«ć‚’é©ē”Øć§ćć¾ć™ć€‚ +ć‚æć‚¤ćƒć‚°ćƒ©ćƒ•ć‚£ć®ć‚¹ć‚æć‚¤ćƒ«ć€‚ + +| Property | Description | Type | +| -------- | ----------- | ---- | +| fontFamily | `font-family` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| fontSize | `font-size` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| fontStyle | `font-style` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| fontWeight | `font-weight` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| letterSpacing | `letter-spacing` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| lineHeight | `line-height` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| textAlign | `text-align` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| textColumns | `column-count` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| textDecoration | `text-decoration` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| writingMode | `writing-mode` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | +| textTransform | `text-transform` CSS ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć®čح定 | `string`, `{ ref }` | --- + ## customTemplates ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆćƒ•ć‚©ćƒ«ćƒ€ć§å®šē¾©ć•ć‚Œć‚‹ć‚«ć‚¹ć‚æćƒ ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆć®čæ½åŠ ćƒ”ć‚æćƒ‡ćƒ¼ć‚æ -ć‚æ悤惗: `object` - +| Property | Description | Type | +| -------- | ----------- | ---- | +| name | ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆćƒ•ć‚©ćƒ«ćƒ€å†…ć®ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆć®ćƒ•ć‚”ć‚¤ćƒ«åć€‚ę‹”å¼µå­ćŖ恗 | `string` | +| title | ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆć®ć‚æć‚¤ćƒˆćƒ«ć€‚ēæ»čسåÆ | `string` | +| postTypes | ć“ć®ć‚«ć‚¹ć‚æćƒ ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆć‚’ä½æ恈悋ꊕēØæć‚æć‚¤ćƒ—ć®ćƒŖć‚¹ćƒˆ | `[ string ]` | -| Property | čŖ¬ę˜Ž | Type | -| --- | --- | --- | -| name | ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆćƒ•ć‚©ćƒ«ćƒ€å†…ć®ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆć®ćƒ•ć‚”ć‚¤ćƒ«åć€‚ę‹”å¼µå­ćŖ恗怂 | string | -| title | ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆć®ć‚æć‚¤ćƒˆćƒ«ć€‚ēæ»čسåÆ怂 | string | -| postTypes | ć“ć®ć‚«ć‚¹ć‚æćƒ ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆć‚’ä½æ恈悋ꊕēØæć‚æć‚¤ćƒ—ć®ćƒŖć‚¹ćƒˆć€‚ | array | ## templateParts -ćƒ‘ćƒ¼ćƒ„ćƒ•ć‚©ćƒ«ćƒ€ć§å®šē¾©ć•ć‚Œć‚‹ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆćƒ‘ćƒ¼ćƒ„ć®čæ½åŠ ćƒ”ć‚æćƒ‡ćƒ¼ć‚æ怂 - -ć‚æ悤惗: `object` +ćƒ‘ćƒ¼ćƒ„ćƒ•ć‚©ćƒ«ćƒ€ć§å®šē¾©ć•ć‚Œć‚‹ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆćƒ‘ćƒ¼ćƒ„ć®čæ½åŠ ćƒ”ć‚æćƒ‡ćƒ¼ć‚æ - -| Property | čŖ¬ę˜Ž | Type | -| --- | --- | --- | -| name | ćƒ‘ćƒ¼ćƒ„ćƒ•ć‚©ćƒ«ćƒ€å†…ć®ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆć®ćƒ•ć‚”ć‚¤ćƒ«åć€‚ę‹”å¼µå­ćŖ恗怂 | string | -| title | ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆć®ć‚æć‚¤ćƒˆćƒ«ć€‚ēæ»čسåÆ怂 | string | -| area | ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆćƒ‘ćƒ¼ćƒ„ćŒä½æē”Ø恕悌悋ć‚ØćƒŖć‚¢ć€‚`header` 値ćØ `footer` å€¤ć®ćƒ–ćƒ­ćƒƒć‚Æ惐ćƒŖć‚Øćƒ¼ć‚·ćƒ§ćƒ³ćŒå­˜åœØ恗态ć‚ØćƒŖć‚¢ćŒćć®ć©ć”ć‚‰ć‹ćØčØ­å®šć•ć‚ŒćŸéš›ć«ä½æē”Ø恕悌悋怂 | string | +| -------- | ----------- | ---- | +| name | ćƒ‘ćƒ¼ćƒ„ćƒ•ć‚©ćƒ«ćƒ€å†…ć®ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆć®ćƒ•ć‚”ć‚¤ćƒ«åć€‚ę‹”å¼µå­ćŖ恗 | `string` | +| title | ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆć®ć‚æć‚¤ćƒˆćƒ«ć€‚ēæ»čسåÆ | `string` | +| area | ćƒ†ćƒ³ćƒ—ćƒ¬ćƒ¼ćƒˆćƒ‘ćƒ¼ćƒ„ćŒä½æē”Ø恕悌悋ć‚ØćƒŖć‚¢ć€‚`header` 値ćØ `footer` å€¤ć®ćƒ–ćƒ­ćƒƒć‚Æ惐ćƒŖć‚Øćƒ¼ć‚·ćƒ§ćƒ³ćŒå­˜åœØ恗态ć‚ØćƒŖć‚¢ćŒćć®ć©ć”ć‚‰ć‹ćØčØ­å®šć•ć‚ŒćŸéš›ć«ä½æē”Ø恕悌悋 | `string` | + +## patterns -## Patterns 惑ć‚æćƒ¼ćƒ³ćƒ‡ć‚£ćƒ¬ć‚Æ惈ćƒŖ恋悉ē™»éŒ²ć•ć‚Œć‚‹ćƒ‘ć‚æćƒ¼ćƒ³ć®ć‚¹ćƒ©ćƒƒć‚°ć®é…åˆ—ć€‚ -ć‚æ悤惗: `array` + +型: `[ string ]`怂 diff --git a/docs/toc.json b/docs/toc.json index fa80ee6c4f4404..0d4689811b26ec 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -204,6 +204,22 @@ }, { "docs/reference-guides/interactivity-api/README.md": [ + { + "docs/reference-guides/interactivity-api/core-concepts/README.md": [ + { + "docs/reference-guides/interactivity-api/core-concepts/the-reactive-and-declarative-mindset.md": [] + }, + { + "docs/reference-guides/interactivity-api/core-concepts/undestanding-global-state-local-context-and-derived-state.md": [] + }, + { + "docs/reference-guides/interactivity-api/core-concepts/server-side-rendering.md": [] + }, + { + "docs/reference-guides/interactivity-api/core-concepts/using-typescript.md": [] + } + ] + }, { "docs/reference-guides/interactivity-api/iapi-quick-start-guide.md": [] }, diff --git a/gutenberg.php b/gutenberg.php index 703d64f48059ed..e15cb113ea65a1 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.4 + * Requires at least: 6.5 * Requires PHP: 7.2 - * Version: 18.7.1 + * Version: 19.3.0-rc.2 * Author: Gutenberg Team * Text Domain: gutenberg * @@ -15,6 +15,8 @@ ### BEGIN AUTO-GENERATED DEFINES defined( 'GUTENBERG_DEVELOPMENT_MODE' ) or define( 'GUTENBERG_DEVELOPMENT_MODE', true ); ### END AUTO-GENERATED DEFINES +defined( 'GUTENBERG_MINIMUM_WP_VERSION' ) or define( 'GUTENBERG_MINIMUM_WP_VERSION', '6.5' ); + gutenberg_pre_init(); @@ -26,7 +28,7 @@ function gutenberg_wordpress_version_notice() { echo '

'; /* translators: %s: Minimum required version */ - printf( __( 'Gutenberg requires WordPress %s or later to function properly. Please upgrade WordPress before activating Gutenberg.', 'gutenberg' ), '5.9' ); + printf( __( 'Gutenberg requires WordPress %s or later to function properly. Please upgrade WordPress before activating Gutenberg.', 'gutenberg' ), GUTENBERG_MINIMUM_WP_VERSION ); echo '

'; deactivate_plugins( array( 'gutenberg/gutenberg.php' ) ); @@ -67,7 +69,7 @@ function gutenberg_pre_init() { // Compare against major release versions (X.Y) rather than minor (X.Y.Z) // unless a minor release is the actual minimum requirement. WordPress reports // X.Y for its major releases. - if ( version_compare( $version, '5.9', '<' ) ) { + if ( version_compare( $version, GUTENBERG_MINIMUM_WP_VERSION, '<' ) ) { add_action( 'admin_notices', 'gutenberg_wordpress_version_notice' ); return; } diff --git a/lib/README.md b/lib/README.md index 8e22f676a153d2..9ee05efccffe98 100644 --- a/lib/README.md +++ b/lib/README.md @@ -192,12 +192,12 @@ Existing comments in `lib/load.php` should act as a guide. ## When to sync changes to Gutenberg PHP with Core and vice versa -If you've changed or added PHP files to the Gutenberg plugin, you'll need to confirm whether the changes are to be synced to WordPress Core, and therefore featured in the next release of WordPress. +On open Gutenberg PRs, changes to certain files are flagged as requiring syncing (also called "backporting") to WordPress Core, for example, PHP files in `/lib` and PHP unit tests. -The Gutenberg GitHub pull request in question should be labeled with the `Needs PHP backport` label if the changes are to be synced to Core. +The CI checks will indicate whether you need to create a Core PR. If you do, you'll need to create a corresponding markdown file and place it within the appropriate release subdirectory in the [Core backport changelog](https://github.com/WordPress/gutenberg/tree/trunk/backport-changelog/). -If so, it is recommended to create a [new Trac ticket](https://core.trac.wordpress.org/newticket) and submit a pull request to the [WordPress Core GitHub repository](https://github.com/WordPress/wordpress-develop) soon after your pull request is merged. +For more information, please refer to the [Core backport changelog documentation](https://github.com/WordPress/gutenberg/tree/trunk/backport-changelog/readme.md). -So too, if you've made changes in WordPress Core to code that also lives in the Gutenberg plugin, these changes will need to be synced (often called "backporting") to Gutenberg. The relevant Gutenberg GitHub pull request should be labeled with the `Backport from WordPress Core` label. +So too, if you've made changes in WordPress Core to code that also lives in the Gutenberg plugin, these changes will need to be synced to Gutenberg. The relevant Gutenberg GitHub pull request should be labeled with the `Backport from WordPress Core` label. If you're unsure, you can always ask for help in the #core-editor channel in [WordPress Slack](https://make.wordpress.org/chat/). diff --git a/lib/block-supports/background.php b/lib/block-supports/background.php index 57b8d75f03d358..a1d99133c1fc09 100644 --- a/lib/block-supports/background.php +++ b/lib/block-supports/background.php @@ -52,17 +52,17 @@ function gutenberg_render_background_support( $block_content, $block ) { return $block_content; } - $background_styles = array(); - $background_styles['backgroundImage'] = $block_attributes['style']['background']['backgroundImage'] ?? null; - $background_styles['backgroundSize'] = $block_attributes['style']['background']['backgroundSize'] ?? null; - $background_styles['backgroundPosition'] = $block_attributes['style']['background']['backgroundPosition'] ?? null; - $background_styles['backgroundRepeat'] = $block_attributes['style']['background']['backgroundRepeat'] ?? null; - + $background_styles = array(); + $background_styles['backgroundImage'] = $block_attributes['style']['background']['backgroundImage'] ?? null; + $background_styles['backgroundSize'] = $block_attributes['style']['background']['backgroundSize'] ?? null; + $background_styles['backgroundPosition'] = $block_attributes['style']['background']['backgroundPosition'] ?? null; + $background_styles['backgroundRepeat'] = $block_attributes['style']['background']['backgroundRepeat'] ?? null; + $background_styles['backgroundAttachment'] = $block_attributes['style']['background']['backgroundAttachment'] ?? null; if ( ! empty( $background_styles['backgroundImage'] ) ) { $background_styles['backgroundSize'] = $background_styles['backgroundSize'] ?? 'cover'; // If the background size is set to `contain` and no position is set, set the position to `center`. if ( 'contain' === $background_styles['backgroundSize'] && ! $background_styles['backgroundPosition'] ) { - $background_styles['backgroundPosition'] = 'center'; + $background_styles['backgroundPosition'] = '50% 50%'; } } diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php index 1c049f4a0fee58..3942fed24b98a8 100644 --- a/lib/block-supports/block-style-variations.php +++ b/lib/block-supports/block-style-variations.php @@ -11,12 +11,15 @@ * * @since 6.6.0 * + * @deprecated 6.7.0 + * * @param array $block Block object. * @param string $variation Slug for the block style variation. * * @return string The unique variation name. */ function gutenberg_create_block_style_variation_instance_name( $block, $variation ) { + _deprecated_function( __FUNCTION__, '6.7.0' ); return $variation . '--' . md5( serialize( $block ) ); } @@ -38,6 +41,42 @@ function gutenberg_get_block_style_variation_name_from_class( $class_string ) { return $matches[1] ?? null; } +/** + * Recursively resolves any `ref` values within a block style variation's data. + * + * @since 6.6.0 + * + * @param array $variation_data Reference to the variation data being processed. + * @param array $theme_json Theme.json data to retrieve referenced values from. + */ +function gutenberg_resolve_block_style_variation_ref_values( &$variation_data, $theme_json ) { + foreach ( $variation_data as $key => &$value ) { + // Only need to potentially process arrays. + if ( is_array( $value ) ) { + // If ref value is set, attempt to find its matching value and update it. + if ( array_key_exists( 'ref', $value ) ) { + // Clean up any invalid ref value. + if ( empty( $value['ref'] ) || ! is_string( $value['ref'] ) ) { + unset( $variation_data[ $key ] ); + } + + $value_path = explode( '.', $value['ref'] ?? '' ); + $ref_value = _wp_array_get( $theme_json, $value_path ); + + // Only update the current value if the referenced path matched a value. + if ( null === $ref_value ) { + unset( $variation_data[ $key ] ); + } else { + $value = $ref_value; + } + } else { + // Recursively look for ref instances. + gutenberg_resolve_block_style_variation_ref_values( $value, $theme_json ); + } + } + } +} + /** * Render the block style variation's styles. * @@ -79,7 +118,11 @@ function gutenberg_render_block_style_variation_support_styles( $parsed_block ) return $parsed_block; } - $variation_instance = gutenberg_create_block_style_variation_instance_name( $parsed_block, $variation ); + // Recursively resolve any ref values with the appropriate value within the + // theme_json data. + gutenberg_resolve_block_style_variation_ref_values( $variation_data, $theme_json ); + + $variation_instance = wp_unique_id( $variation . '--' ); $class_name = "is-style-$variation_instance"; $updated_class_name = $parsed_block['attrs']['className'] . " $class_name"; @@ -151,7 +194,7 @@ function gutenberg_render_block_style_variation_support_styles( $parsed_block ) return $parsed_block; } - wp_register_style( 'block-style-variation-styles', false, array( 'global-styles', 'wp-block-library' ) ); + wp_register_style( 'block-style-variation-styles', false, array( 'wp-block-library', 'global-styles' ) ); wp_add_inline_style( 'block-style-variation-styles', $variation_styles ); /* @@ -184,11 +227,9 @@ function gutenberg_render_block_style_variation_class_name( $block_content, $blo /* * Matches a class prefixed by `is-style`, followed by the - * variation slug, then `--`, and finally a hash. - * - * See `gutenberg_create_block_style_variation_instance_name` for class generation. + * variation slug, then `--`, and finally an instance number. */ - preg_match( '/\bis-style-(\S+?--\w+)\b/', $block['attrs']['className'], $matches ); + preg_match( '/\bis-style-(\S+?--\d+)\b/', $block['attrs']['className'], $matches ); if ( empty( $matches ) ) { return $block_content; diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index b5e2fe37faecdc..ddbd1917c30547 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -306,14 +306,22 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support * They're added separately because padding might only be set on one side. */ if ( isset( $block_spacing_values['declarations']['padding-right'] ) ) { - $padding_right = $block_spacing_values['declarations']['padding-right']; + $padding_right = $block_spacing_values['declarations']['padding-right']; + // Add unit if 0. + if ( '0' === $padding_right ) { + $padding_right = '0px'; + } $layout_styles[] = array( 'selector' => "$selector > .alignfull", 'declarations' => array( 'margin-right' => "calc($padding_right * -1)" ), ); } if ( isset( $block_spacing_values['declarations']['padding-left'] ) ) { - $padding_left = $block_spacing_values['declarations']['padding-left']; + $padding_left = $block_spacing_values['declarations']['padding-left']; + // Add unit if 0. + if ( '0' === $padding_left ) { + $padding_left = '0px'; + } $layout_styles[] = array( 'selector' => "$selector > .alignfull", 'declarations' => array( 'margin-left' => "calc($padding_left * -1)" ), @@ -501,7 +509,7 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support if ( ! empty( $layout['rowCount'] ) ) { $layout_styles[] = array( 'selector' => $selector, - 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(8px, auto))' ), + 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(1rem, auto))' ), ); } } elseif ( ! empty( $layout['columnCount'] ) ) { @@ -512,7 +520,7 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support if ( ! empty( $layout['rowCount'] ) ) { $layout_styles[] = array( 'selector' => $selector, - 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(8px, auto))' ), + 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(1rem, auto))' ), ); } } else { @@ -646,11 +654,19 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { if ( ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) { $column_span_number = floatval( $column_span ); $column_start_number = floatval( $column_start ); - $highest_number = max( $column_span_number, $column_start_number ); $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem'; $parent_column_value = floatval( $parent_column_width ); $parent_column_unit = explode( $parent_column_value, $parent_column_width ); + $num_cols_to_break_at = 2; + if ( $column_span_number && $column_start_number ) { + $num_cols_to_break_at = $column_start_number + $column_span_number - 1; + } elseif ( $column_span_number ) { + $num_cols_to_break_at = $column_span_number; + } else { + $num_cols_to_break_at = $column_start_number; + } + /* * If there is no unit, the width has somehow been mangled so we reset both unit and value * to defaults. @@ -672,7 +688,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { * viable to use in the computation of the container query value. */ $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5; - $container_query_value = $highest_number * $parent_column_value + ( $highest_number - 1 ) * $default_gap_value; + $container_query_value = $num_cols_to_break_at * $parent_column_value + ( $num_cols_to_break_at - 1 ) * $default_gap_value; $minimum_container_query_value = $parent_column_value * 2 + $default_gap_value - 1; $container_query_value = max( $container_query_value, $minimum_container_query_value ) . $parent_column_unit; // If a span is set we want to preserve it as long as possible, otherwise we just reset the value. @@ -1003,27 +1019,10 @@ function gutenberg_restore_group_inner_container( $block_content, $block ) { $processor = new WP_HTML_Tag_Processor( $block_content ); if ( $processor->next_tag( array( 'class_name' => 'wp-block-group' ) ) ) { - if ( method_exists( $processor, 'class_list' ) ) { - foreach ( $processor->class_list() as $class_name ) { - if ( str_contains( $class_name, 'layout' ) ) { - array_push( $layout_classes, $class_name ); - $processor->remove_class( $class_name ); - } - } - } else { - /* - * The class_list method was only added in 6.4 so this needs a temporary fallback. - * This fallback should be removed when the minimum supported version is 6.4. - */ - $classes = $processor->get_attribute( 'class' ); - if ( $classes ) { - $classes = explode( ' ', $classes ); - foreach ( $classes as $class_name ) { - if ( str_contains( $class_name, 'is-layout-' ) ) { - array_push( $layout_classes, $class_name ); - $processor->remove_class( $class_name ); - } - } + foreach ( $processor->class_list() as $class_name ) { + if ( str_contains( $class_name, 'layout' ) ) { + array_push( $layout_classes, $class_name ); + $processor->remove_class( $class_name ); } } } diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index fa2a7b81e94e21..a4719b7bdd4099 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -449,6 +449,7 @@ function gutenberg_get_computed_fluid_typography_value( $args = array() ) { * @since 6.3.0 Using layout.wideSize as max viewport width, and logarithmic scale factor to calculate minimum font scale. * @since 6.4.0 Added configurable min and max viewport width values to the typography.fluid theme.json schema. * @since 6.6.0 Deprecated bool argument $should_use_fluid_typography. + * @since 6.7.0 Font size presets can enable fluid typography individually, even if itā€™s disabled globally. * * @param array $preset { * Required. fontSizes preset value as seen in theme.json. @@ -468,10 +469,11 @@ function gutenberg_get_typography_font_size_value( $preset, $settings = array() } /* - * Catches empty values and 0/'0'. - * Fluid calculations cannot be performed on 0. + * Catch falsy values and 0/'0'. Fluid calculations cannot be performed on `0`. + * Also return early when a preset font size explicitly disables fluid typography with `false`. */ - if ( empty( $preset['size'] ) ) { + $fluid_font_size_settings = $preset['fluid'] ?? null; + if ( false === $fluid_font_size_settings || empty( $preset['size'] ) ) { return $preset['size']; } @@ -489,20 +491,25 @@ function gutenberg_get_typography_font_size_value( $preset, $settings = array() } // Fallback to global settings as default. - $global_settings = gutenberg_get_global_settings(); - $settings = wp_parse_args( + $global_settings = gutenberg_get_global_settings(); + $settings = wp_parse_args( $settings, $global_settings ); - $typography_settings = isset( $settings['typography'] ) ? $settings['typography'] : array(); - $should_use_fluid_typography = ! empty( $typography_settings['fluid'] ); + $typography_settings = $settings['typography'] ?? array(); - if ( ! $should_use_fluid_typography ) { + /* + * Return early when fluid typography is disabled in the settings, and there + * are no local settings to enable it for the individual preset. + * + * If this condition isn't met, either the settings or individual preset settings + * have enabled fluid typography. + */ + if ( empty( $typography_settings['fluid'] ) && empty( $fluid_font_size_settings ) ) { return $preset['size']; } - // $typography_settings['fluid'] can be a bool or an array. Normalize to array. - $fluid_settings = is_array( $typography_settings['fluid'] ) ? $typography_settings['fluid'] : array(); + $fluid_settings = isset( $typography_settings['fluid'] ) ? $typography_settings['fluid'] : array(); $layout_settings = isset( $settings['layout'] ) ? $settings['layout'] : array(); // Defaults. @@ -522,14 +529,6 @@ function gutenberg_get_typography_font_size_value( $preset, $settings = array() $has_min_font_size = isset( $fluid_settings['minFontSize'] ) && ! empty( gutenberg_get_typography_value_and_unit( $fluid_settings['minFontSize'] ) ); $minimum_font_size_limit = $has_min_font_size ? $fluid_settings['minFontSize'] : $default_minimum_font_size_limit; - // Font sizes. - $fluid_font_size_settings = isset( $preset['fluid'] ) ? $preset['fluid'] : null; - - // A font size has explicitly bypassed fluid calculations. - if ( false === $fluid_font_size_settings ) { - return $preset['size']; - } - // Try to grab explicit min and max fluid font sizes. $minimum_font_size_raw = isset( $fluid_font_size_settings['min'] ) ? $fluid_font_size_settings['min'] : null; $maximum_font_size_raw = isset( $fluid_font_size_settings['max'] ) ? $fluid_font_size_settings['max'] : null; diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index 421408e6f20b4a..1f6543fa184283 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -13,25 +13,30 @@ /** * Base Global Styles REST API Controller. */ -class WP_REST_Global_Styles_Controller_Gutenberg extends WP_REST_Controller { +class WP_REST_Global_Styles_Controller_Gutenberg extends WP_REST_Posts_Controller { /** - * Post type. + * Whether the controller supports batching. * - * @since 5.9.0 - * @var string + * @since 6.6.0 + * @var array */ - protected $post_type; + protected $allow_batch = array( 'v1' => false ); /** * Constructor. * * @since 5.9.0 */ - public function __construct() { - $this->namespace = 'wp/v2'; - $this->rest_base = 'global-styles'; - $this->post_type = 'wp_global_styles'; + /** + * Constructor. + * + * @since 6.6.0 + * + * @param string $post_type Post type. + */ + public function __construct( $post_type = 'wp_global_styles' ) { + parent::__construct( $post_type ); } /** @@ -54,8 +59,14 @@ public function register_routes() { 'type' => 'string', ), ), + 'allow_batch' => $this->allow_batch, ), - ) + ), + /* + * $override is set to true to avoid conflicts with the core endpoint. + * Do not sync to WordPress core. + */ + true ); // List themes global styles. @@ -65,8 +76,10 @@ public function register_routes() { sprintf( '/%s/themes/(?P%s)', $this->rest_base, - // Matches theme's directory: `/themes///` or `/themes//`. - // Excludes invalid directory name characters: `/:<>*?"|`. + /* + * Matches theme's directory: `/themes///` or `/themes//`. + * Excludes invalid directory name characters: `/:<>*?"|`. + */ '[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?' ), array( @@ -81,8 +94,14 @@ public function register_routes() { 'sanitize_callback' => array( $this, '_sanitize_global_styles_callback' ), ), ), + 'allow_batch' => $this->allow_batch, ), - ) + ), + /* + * $override is set to true to avoid conflicts with the core endpoint. + * Do not sync to WordPress core. + */ + true ); // Lists/updates a single global style variation based on the given id. @@ -108,8 +127,14 @@ public function register_routes() { 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) + 'schema' => array( $this, 'get_public_item_schema' ), + 'allow_batch' => $this->allow_batch, + ), + /* + * $override is set to true to avoid conflicts with the core endpoint. + * Do not sync to WordPress core. + */ + true ); } @@ -196,28 +221,10 @@ public function get_item_permissions_check( $request ) { * @param WP_Post $post Post object. * @return bool Whether the post can be read. */ - protected function check_read_permission( $post ) { + public function check_read_permission( $post ) { return current_user_can( 'read_post', $post->ID ); } - /** - * Returns the given global styles config. - * - * @since 5.9.0 - * - * @param WP_REST_Request $request The request instance. - * - * @return WP_REST_Response|WP_Error - */ - public function get_item( $request ) { - $post = $this->get_post( $request['id'] ); - if ( is_wp_error( $post ) ) { - return $post; - } - - return $this->prepare_item_for_response( $post, $request ); - } - /** * Checks if a given request has access to write a single global styles config. * @@ -243,61 +250,12 @@ public function update_item_permissions_check( $request ) { return true; } - /** - * Checks if a global style can be edited. - * - * @since 5.9.0 - * - * @param WP_Post $post Post object. - * @return bool Whether the post can be edited. - */ - protected function check_update_permission( $post ) { - return current_user_can( 'edit_post', $post->ID ); - } - - /** - * Updates a single global style config. - * - * @since 5.9.0 - * @since 6.2.0 Added validation of styles.css property. - * - * @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 ) { - $post_before = $this->get_post( $request['id'] ); - if ( is_wp_error( $post_before ) ) { - return $post_before; - } - - $changes = $this->prepare_item_for_database( $request ); - if ( is_wp_error( $changes ) ) { - return $changes; - } - - $result = wp_update_post( wp_slash( (array) $changes ), true, false ); - if ( is_wp_error( $result ) ) { - return $result; - } - - $post = get_post( $request['id'] ); - $fields_update = $this->update_additional_fields_for_object( $post, $request ); - if ( is_wp_error( $fields_update ) ) { - return $fields_update; - } - - wp_after_insert_post( $post, true, $post_before ); - - $response = $this->prepare_item_for_response( $post, $request ); - - return rest_ensure_response( $response ); - } - /** * Prepares a single global styles config for update. * * @since 5.9.0 * @since 6.2.0 Added validation of styles.css property. + * @since 6.6.0 Added registration of block style variations from theme.json sources (theme.json, user theme.json, partials). * * @param WP_REST_Request $request Request object. * @return stdClass|WP_Error Prepared item on success. WP_Error on when the custom CSS is not valid. @@ -394,10 +352,12 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V } if ( rest_is_field_included( 'title.rendered', $fields ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); $data['title']['rendered'] = get_the_title( $post->ID ); remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); } if ( rest_is_field_included( 'settings', $fields ) ) { @@ -426,7 +386,7 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V } $response->add_links( $links ); if ( ! empty( $links['self']['href'] ) ) { - $actions = $this->get_available_actions(); + $actions = $this->get_available_actions( $post, $request ); $self = $links['self']['href']; foreach ( $actions as $rel ) { $response->add_link( $rel, $self ); @@ -450,9 +410,12 @@ protected function prepare_links( $id ) { $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); $links = array( - 'self' => array( + 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $id ), ), + 'about' => array( + 'href' => rest_url( 'wp/v2/types/' . $this->post_type ), + ), ); if ( post_type_supports( $this->post_type, 'revisions' ) ) { @@ -473,13 +436,16 @@ protected function prepare_links( $id ) { * * @since 5.9.0 * @since 6.2.0 Added 'edit-css' action. + * @since 6.6.0 Added $post and $request parameters. * + * @param WP_Post $post Post object. + * @param WP_REST_Request $request Request object. * @return array List of link relations. */ - protected function get_available_actions() { + protected function get_available_actions( $post, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $rels = array(); - $post_type = get_post_type_object( $this->post_type ); + $post_type = get_post_type_object( $post->post_type ); if ( current_user_can( $post_type->cap->publish_posts ) ) { $rels[] = 'https://api.w.org/action-publish'; } @@ -491,21 +457,6 @@ protected function get_available_actions() { return $rels; } - /** - * Overwrites the default protected title format. - * - * By default, WordPress will show password protected posts with a title of - * "Protected: %s", as the REST API communicates the protected status of a post - * in a machine readable format, we remove the "Protected: " prefix. - * - * @since 5.9.0 - * - * @return string Protected title format. - */ - public function protected_title_format() { - return '%s'; - } - /** * Retrieves the query params for the global styles collection. * @@ -581,27 +532,39 @@ public function get_item_schema() { * Checks if a given request has access to read a single theme global styles config. * * @since 5.9.0 + * @since 6.7.0 Allow users with edit post capabilities to view theme global styles. * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_theme_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + /* + * Verify if the current user has edit_posts capability. + */ + if ( current_user_can( 'edit_posts' ) ) { + return true; + } + + foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { + if ( current_user_can( $post_type->cap->edit_posts ) ) { + return true; + } + } /* * Verify if the current user has edit_theme_options capability. - * This capability is required to edit/view/delete templates. */ - if ( ! current_user_can( 'edit_theme_options' ) ) { - return new WP_Error( - 'rest_cannot_manage_global_styles', - __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); + if ( current_user_can( 'edit_theme_options' ) ) { + return true; } - return true; + return new WP_Error( + 'rest_cannot_read_global_styles', + __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); } /** @@ -623,8 +586,8 @@ public function get_theme_item( $request ) { } $theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( 'theme' ); - $data = array(); $fields = $this->get_fields_for_response( $request ); + $data = array(); if ( rest_is_field_included( 'settings', $fields ) ) { $data['settings'] = $theme->get_settings(); @@ -665,23 +628,8 @@ public function get_theme_item( $request ) { * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ - public function get_theme_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - - /* - * Verify if the current user has edit_theme_options capability. - * This capability is required to edit/view/delete templates. - */ - if ( ! current_user_can( 'edit_theme_options' ) ) { - return new WP_Error( - 'rest_cannot_manage_global_styles', - __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); - } - - return true; + public function get_theme_items_permissions_check( $request ) { + return $this->get_theme_item_permissions_check( $request ); } /** diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index bd74852c52074a..756ef06c80aa87 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -234,6 +234,7 @@ class WP_Theme_JSON_Gutenberg { 'background-position' => array( 'background', 'backgroundPosition' ), 'background-repeat' => array( 'background', 'backgroundRepeat' ), 'background-size' => array( 'background', 'backgroundSize' ), + 'background-attachment' => array( 'background', 'backgroundAttachment' ), 'border-radius' => array( 'border', 'radius' ), 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ), 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ), @@ -503,10 +504,11 @@ class WP_Theme_JSON_Gutenberg { */ const VALID_STYLES = array( 'background' => array( - 'backgroundImage' => null, - 'backgroundPosition' => null, - 'backgroundRepeat' => null, - 'backgroundSize' => null, + 'backgroundImage' => null, + 'backgroundAttachment' => null, + 'backgroundPosition' => null, + 'backgroundRepeat' => null, + 'backgroundSize' => null, ), 'border' => array( 'color' => null, @@ -1441,9 +1443,16 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' protected function process_blocks_custom_css( $css, $selector ) { $processed_css = ''; + if ( empty( $css ) ) { + return $processed_css; + } + // Split CSS nested rules. $parts = explode( '&', $css ); foreach ( $parts as $part ) { + if ( empty( $part ) ) { + continue; + } $is_root_css = ( ! str_contains( $part, '{' ) ); if ( $is_root_css ) { // If the part doesn't contain braces, it applies to the root level. @@ -1456,11 +1465,24 @@ protected function process_blocks_custom_css( $css, $selector ) { } $nested_selector = $part[0]; $css_value = $part[1]; - $part_selector = str_starts_with( $nested_selector, ' ' ) + + /* + * Handle pseudo elements such as ::before, ::after etc. Regex will also + * capture any leading combinator such as >, +, or ~, as well as spaces. + * This allows pseudo elements as descendants e.g. `.parent ::before`. + */ + $matches = array(); + $has_pseudo_element = preg_match( '/([>+~\s]*::[a-zA-Z-]+)/', $nested_selector, $matches ); + $pseudo_part = $has_pseudo_element ? $matches[1] : ''; + $nested_selector = $has_pseudo_element ? str_replace( $pseudo_part, '', $nested_selector ) : $nested_selector; + + // Finalize selector and re-append pseudo element if required. + $part_selector = str_starts_with( $nested_selector, ' ' ) ? static::scope_selector( $selector, $nested_selector ) : static::append_to_selector( $selector, $nested_selector ); - $final_selector = ":root :where($part_selector)"; - $processed_css .= $final_selector . '{' . trim( $css_value ) . '}'; + $final_selector = ":root :where($part_selector)$pseudo_part"; + + $processed_css .= $final_selector . '{' . trim( $css_value ) . '}'; } } return $processed_css; @@ -1722,7 +1744,7 @@ protected function get_layout_styles( $block_metadata, $types = array() ) { $spacing_rule['selector'] ); } else { - $format = static::ROOT_BLOCK_SELECTOR === $selector ? '.%2$s %3$s' : '%1$s-%2$s %3$s'; + $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':root :where(.%2$s)%3$s' : ':root :where(%1$s-%2$s)%3$s'; $layout_selector = sprintf( $format, $selector, @@ -2307,7 +2329,7 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) { * ```php * array( * 'name' => 'property_name', - * 'value' => 'property_value, + * 'value' => 'property_value', * ) * ``` * @@ -2316,6 +2338,7 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) { * @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters. * @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set. * @since 6.6.0 Passing current theme JSON settings to wp_get_typography_font_size_value(). Using style engine to correctly fetch background CSS values. + * @since 6.7.0 Allow ref resolution of background properties. * * @param array $styles Styles to process. * @param array $settings Theme settings. @@ -2359,10 +2382,28 @@ protected static function compute_style_properties( $styles, $settings = array() $root_variable_duplicates[] = substr( $css_property, $root_style_length ); } - // Processes background styles. - if ( 'background' === $value_path[0] && isset( $styles['background'] ) ) { - $background_styles = gutenberg_style_engine_get_styles( array( 'background' => $styles['background'] ) ); - $value = $background_styles['declarations'][ $css_property ] ?? $value; + /* + * Processes background image styles. + * If the value is a URL, it will be converted to a CSS `url()` value. + * For an uploaded image (images with a database ID), apply size and position + * defaults equal to those applied in block supports in lib/background.php. + */ + if ( 'background-image' === $css_property && ! empty( $value ) ) { + $background_styles = gutenberg_style_engine_get_styles( + array( 'background' => array( 'backgroundImage' => $value ) ) + ); + + $value = $background_styles['declarations'][ $css_property ]; + } + if ( empty( $value ) && static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) { + if ( 'background-size' === $css_property ) { + $value = 'cover'; + } + // If the background size is set to `contain` and no position is set, set the position to `center`. + if ( 'background-position' === $css_property ) { + $background_size = $styles['background']['backgroundSize'] ?? null; + $value = 'contain' === $background_size ? '50% 50%' : null; + } } // Skip if empty and not "0" or value represents array of longhand values. @@ -2430,6 +2471,7 @@ protected static function compute_style_properties( $styles, $settings = array() * @since 5.8.0 * @since 5.9.0 Added support for values of array type, which are returned as is. * @since 6.1.0 Added the `$theme_json` parameter. + * @since 6.7.0 Added support for background image refs * * @param array $styles Styles subtree. * @param array $path Which property to process. @@ -2446,15 +2488,17 @@ protected static function get_property_value( $styles, $path, $theme_json = null } /* - * This converts references to a path to the value at that path - * where the values is an array with a "ref" key, pointing to a path. + * Where the current value is an array with a 'ref' key pointing + * to a path, this converts that path into the value at that path. * For example: { "ref": "style.color.background" } => "#fff". */ if ( is_array( $value ) && isset( $value['ref'] ) ) { $value_path = explode( '.', $value['ref'] ); - $ref_value = _wp_array_get( $theme_json, $value_path ); + $ref_value = _wp_array_get( $theme_json, $value_path, null ); + // Background Image refs can refer to a string or an array containing a URL string. + $ref_value_url = $ref_value['url'] ?? null; // Only use the ref value if we find anything. - if ( ! empty( $ref_value ) && is_string( $ref_value ) ) { + if ( ! empty( $ref_value ) && ( is_string( $ref_value ) || is_string( $ref_value_url ) ) ) { $value = $ref_value; } @@ -2918,8 +2962,28 @@ static function ( $pseudo_selector ) use ( $selector ) { $declarations = static::update_separator_declarations( $declarations ); } + /* + * Root selector (body) styles should not be wrapped in `:root where()` to keep + * specificity at (0,0,1) and maintain backwards compatibility. + * + * Top-level element styles using element-only specificity selectors should + * not get wrapped in `:root :where()` to maintain backwards compatibility. + * + * Pseudo classes, e.g. :hover, :focus etc., are a class-level selector so + * still need to be wrapped in `:root :where` to cap specificity for nested + * variations etc. Pseudo selectors won't match the ELEMENTS selector exactly. + */ + $element_only_selector = $is_root_selector || ( + $current_element && + isset( static::ELEMENTS[ $current_element ] ) && + // buttons, captions etc. still need `:root :where()` as they are class based selectors. + ! isset( static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $current_element ] ) && + static::ELEMENTS[ $current_element ] === $selector + ); + // 2. Generate and append the rules that use the general selector. - $block_rules .= static::to_ruleset( ":root :where($selector)", $declarations ); + $general_selector = $element_only_selector ? $selector : ":root :where($selector)"; + $block_rules .= static::to_ruleset( $general_selector, $declarations ); // 3. Generate and append the rules that use the duotone selector. if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { @@ -3000,10 +3064,10 @@ public function get_root_layout_rules( $selector, $block_metadata ) { $css .= '.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; // Alignfull children of the container with left and right padding have negative margins so they can still be full width. $css .= '.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }'; - // Nested children of the container with left and right padding that are not wide or full aligned do not get padding, unless they are direct children of an alignfull flow container. - $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) { padding-right: 0; padding-left: 0; }'; + // Nested children of the container with left and right padding that are not full aligned do not get padding, unless they are direct children of an alignfull flow container. + $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; }'; // Alignfull direct children of the containers that are targeted by the rule above do not need negative margins. - $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) > .alignfull { margin-left: 0; margin-right: 0; }'; + $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0; }'; } $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; @@ -3194,6 +3258,25 @@ public function merge( $incoming ) { } } } + + /* + * Style values are merged at the leaf level, however + * some values provide exceptions, namely style values that are + * objects and represent unique definitions for the style. + */ + $style_nodes = static::get_styles_block_nodes(); + foreach ( $style_nodes as $style_node ) { + $path = $style_node['path']; + /* + * Background image styles should be replaced, not merged, + * as they themselves are specific object definitions for the style. + */ + $background_image_path = array_merge( $path, static::PROPERTIES_METADATA['background-image'] ); + $content = _wp_array_get( $incoming_data, $background_image_path, null ); + if ( isset( $content ) ) { + _wp_array_set( $this->theme_json, $background_image_path, $content ); + } + } } /** diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index a2153e639db3ba..2231cb0f11538f 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -764,8 +764,18 @@ private static function style_variation_has_scope( $variation, $scope ) { * @return array */ public static function get_style_variations( $scope = 'theme' ) { + return static::get_style_variations_from_directory( get_stylesheet_directory(), $scope ); + } + + /** + * Returns the style variation files defined by the theme (parent and child). + * + * @since 6.7.0 + * + * @return array An array of style variation files. + */ + protected static function get_style_variation_files_from_current_theme() { $variation_files = array(); - $variations = array(); $base_directory = get_stylesheet_directory() . '/styles'; $template_directory = get_template_directory() . '/styles'; if ( is_dir( $base_directory ) ) { @@ -783,6 +793,29 @@ public static function get_style_variations( $scope = 'theme' ) { } $variation_files = array_merge( $variation_files, $variation_files_parent ); } + + return $variation_files; + } + + /** + * Returns the style variations in the given directory. + * + * @since 6.7.0 + * + * @param string $directory The directory to get the style variations from. + * @param string $scope The scope or type of style variation to retrieve e.g. theme, block etc. + * @return array + */ + public static function get_style_variations_from_directory( $directory, $scope = 'theme' ) { + $variation_files = array(); + $variations = array(); + if ( is_dir( $directory ) ) { + if ( get_stylesheet_directory() === $directory ) { + $variation_files = static::get_style_variation_files_from_current_theme(); + } else { + $variation_files = static::recursively_iterate_json( $directory ); + } + } ksort( $variation_files ); foreach ( $variation_files as $path => $file ) { $decoded_file = self::read_json_file( $path ); diff --git a/lib/client-assets.php b/lib/client-assets.php index a159bc53e6a591..2343530e5595a7 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -487,7 +487,9 @@ function gutenberg_register_packages_styles( $styles ) { * This hook also exists, and should be backported to Core in future versions. * However, it is envisaged that Gutenberg will continue to use the Style Engine's `gutenberg_*` functions and `_Gutenberg` classes to aid continuous development. * - * See: https://developer.wordpress.org/block-editor/reference-guides/packages/packages-style-engine/ + * @since 6.1 + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-style-engine/ * * @param array $options { * Optional. An array of options to pass to gutenberg_style_engine_get_stylesheet_from_context(). Default empty array. @@ -496,8 +498,6 @@ function gutenberg_register_packages_styles( $styles ) { * @type bool $prettify Whether to add new lines and indents to output. Default is the test of whether the global constant `SCRIPT_DEBUG` is defined. * } * - * @since 6.1 - * * @return void */ function gutenberg_enqueue_stored_styles( $options = array() ) { @@ -601,6 +601,56 @@ function gutenberg_register_vendor_scripts( $scripts ) { } add_action( 'wp_default_scripts', 'gutenberg_register_vendor_scripts' ); +/** + * Registers or re-registers Gutenberg Script Modules. + * + * Script modules that are registered by Core will be re-registered by Gutenberg. + * + * @since 19.3.0 + */ +function gutenberg_default_script_modules() { + /* + * Expects multidimensional array like: + * + * 'interactivity/index.min.js' => array('dependencies' => array(ā€¦), 'version' => 'ā€¦'), + * 'interactivity/debug.min.js' => array('dependencies' => array(ā€¦), 'version' => 'ā€¦'), + * 'interactivity-router/index.min.js' => ā€¦ + */ + $assets = include gutenberg_dir_path() . '/build-module/assets.php'; + + foreach ( $assets as $file_name => $script_module_data ) { + /* + * Build the WordPress Script Module ID from the file name. + * Prepend `@wordpress/` and remove extensions and `/index` if present: + * - interactivity/index.min.js => @wordpress/interactivity + * - interactivity/debug.min.js => @wordpress/interactivity/debug + * - block-library/query/view.js => @wordpress/block-library/query/view + */ + $script_module_id = '@wordpress/' . preg_replace( '~(?:/index)?\.min\.js$~D', '', $file_name, 1 ); + switch ( $script_module_id ) { + /* + * Interactivity exposes two entrypoints, "/index" and "/debug". + * "/debug" should replalce "/index" in devlopment. + */ + case '@wordpress/interactivity/debug': + if ( ! SCRIPT_DEBUG ) { + continue 2; + } + $script_module_id = '@wordpress/interactivity'; + break; + case '@wordpress/interactivity': + if ( SCRIPT_DEBUG ) { + continue 2; + } + break; + } + + $path = gutenberg_url( "build-module/{$file_name}" ); + wp_register_script_module( $script_module_id, $path, $script_module_data['dependencies'], $script_module_data['version'] ); + } +} +remove_action( 'wp_default_scripts', 'wp_default_script_modules' ); +add_action( 'wp_default_scripts', 'gutenberg_default_script_modules' ); /* * Always remove the Core action hook while gutenberg_enqueue_stored_styles() exists to avoid styles being printed twice. diff --git a/lib/compat/plugin/fonts.php b/lib/compat/plugin/fonts.php new file mode 100644 index 00000000000000..f427f6110f610a --- /dev/null +++ b/lib/compat/plugin/fonts.php @@ -0,0 +1,43 @@ +post_type ) { + return; + } + + $font_files = get_post_meta( $post_id, '_wp_font_face_file', false ); + + if ( empty( $font_files ) ) { + return; + } + + $site_path = ''; + if ( is_multisite() && ! ( is_main_network() && is_main_site() ) ) { + $site_path = '/sites/' . get_current_blog_id(); + } + + $font_dir = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; + + foreach ( $font_files as $font_file ) { + $font_path = $font_dir . '/' . $font_file; + + if ( file_exists( $font_path ) ) { + wp_delete_file( $font_path ); + } + } +} +add_action( 'before_delete_post', 'gutenberg_before_delete_font_face', 10, 2 ); diff --git a/lib/compat/plugin/footnotes.php b/lib/compat/plugin/footnotes.php deleted file mode 100644 index a3d89aba0ae96a..00000000000000 --- a/lib/compat/plugin/footnotes.php +++ /dev/null @@ -1,250 +0,0 @@ -post_parent; - - // Just making sure we're updating the right revision. - if ( $post->ID === $post_id ) { - $footnotes = get_post_meta( $post_id, 'footnotes', true ); - - if ( $footnotes ) { - // Can't use update_post_meta() because it doesn't allow revisions. - update_metadata( 'post', $wp_temporary_footnote_revision_id, 'footnotes', wp_slash( $footnotes ) ); - } - } - } - } - - if ( ! function_exists( 'wp_post_revision_meta_keys' ) ) { - add_action( 'rest_after_insert_post', 'wp_add_footnotes_revisions_to_post_meta' ); - add_action( 'rest_after_insert_page', 'wp_add_footnotes_revisions_to_post_meta' ); - } - } - - if ( ! function_exists( 'wp_restore_footnotes_from_revision' ) ) { - - /** - * Restores the footnotes meta value from the revision. - * - * @since 6.3.0 - * @since 6.4.0 Core added post meta revisions, so this is no longer needed. - * - * @param int $post_id The post ID. - * @param int $revision_id The revision ID. - */ - function wp_restore_footnotes_from_revision( $post_id, $revision_id ) { - $footnotes = get_post_meta( $revision_id, 'footnotes', true ); - - if ( $footnotes ) { - update_post_meta( $post_id, 'footnotes', wp_slash( $footnotes ) ); - } else { - delete_post_meta( $post_id, 'footnotes' ); - } - } - if ( ! function_exists( 'wp_post_revision_meta_keys' ) ) { - add_action( 'wp_restore_post_revision', 'wp_restore_footnotes_from_revision', 10, 2 ); - } - } - - if ( ! function_exists( '_wp_rest_api_autosave_meta' ) ) { - - /** - * The REST API autosave endpoint doesn't save meta, so we can use the - * `wp_creating_autosave` when it updates an exiting autosave, and - * `_wp_put_post_revision` when it creates a new autosave. - * - * @since 6.3.0 - * @since 6.4.0 Core added post meta revisions, so this is no longer needed. - * - * @param int|array $autosave The autosave ID or array. - */ - function _wp_rest_api_autosave_meta( $autosave ) { - // Ensure it's a REST API request. - if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) { - return; - } - - $body = rest_get_server()->get_raw_data(); - $body = json_decode( $body, true ); - - if ( ! isset( $body['meta']['footnotes'] ) ) { - return; - } - - // `wp_creating_autosave` passes the array, - // `_wp_put_post_revision` passes the ID. - $id = is_int( $autosave ) ? $autosave : $autosave['ID']; - - if ( ! $id ) { - return; - } - - // Can't use update_post_meta() because it doesn't allow revisions. - update_metadata( 'post', $id, 'footnotes', wp_slash( $body['meta']['footnotes'] ) ); - } - - if ( ! function_exists( 'wp_post_revision_meta_keys' ) ) { - // See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L391C1-L391C1. - add_action( 'wp_creating_autosave', '_wp_rest_api_autosave_meta' ); - // See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L398. - // Then https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/revision.php#L367. - add_action( '_wp_put_post_revision', '_wp_rest_api_autosave_meta' ); - } - } - - if ( ! function_exists( '_wp_rest_api_force_autosave_difference' ) ) { - - /** - * This is a workaround for the autosave endpoint returning early if the - * revision field are equal. The problem is that "footnotes" is not real - * revision post field, so there's nothing to compare against. - * - * This trick sets the "footnotes" field (value doesn't matter), which will - * cause the autosave endpoint to always update the latest revision. That should - * be fine, it should be ok to update the revision even if nothing changed. Of - * course, this is temporary fix. - * - * @since 6.3.0 - * @since 6.4.0 Core added post meta revisions, so this is no longer needed. - * - * @param WP_Post $prepared_post The prepared post object. - * @param WP_REST_Request $request The request object. - * - * See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L365-L384. - * See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L219. - */ - function _wp_rest_api_force_autosave_difference( $prepared_post, $request ) { - // We only want to be altering POST requests. - if ( $request->get_method() !== 'POST' ) { - return $prepared_post; - } - - // Only alter requests for the '/autosaves' route. - if ( substr( $request->get_route(), -strlen( '/autosaves' ) ) !== '/autosaves' ) { - return $prepared_post; - } - - $prepared_post->footnotes = '[]'; - return $prepared_post; - } - if ( ! function_exists( 'wp_post_revision_meta_keys' ) ) { - add_filter( 'rest_pre_insert_post', '_wp_rest_api_force_autosave_difference', 10, 2 ); - } - } -} diff --git a/lib/compat/wordpress-6.4/block-hooks.php b/lib/compat/wordpress-6.4/block-hooks.php deleted file mode 100644 index f77582caf13454..00000000000000 --- a/lib/compat/wordpress-6.4/block-hooks.php +++ /dev/null @@ -1,377 +0,0 @@ - 'before', - 'after' => 'after', - 'firstChild' => 'first_child', - 'lastChild' => 'last_child', - ); - - $inserted_block_name = $metadata['name']; - foreach ( $block_hooks as $anchor_block_name => $position ) { - // Avoid infinite recursion (hooking to itself). - if ( $inserted_block_name === $anchor_block_name ) { - _doing_it_wrong( - __METHOD__, - __( 'Cannot hook block to itself.', 'gutenberg' ), - '6.4.0' - ); - continue; - } - - if ( ! isset( $property_mappings[ $position ] ) ) { - continue; - } - - $mapped_position = $property_mappings[ $position ]; - - gutenberg_add_hooked_block( $inserted_block_name, $mapped_position, $anchor_block_name ); - - $settings['block_hooks'][ $anchor_block_name ] = $mapped_position; - } - - // Copied from `get_block_editor_server_block_settings()`. - $fields_to_pick = array( - 'api_version' => 'apiVersion', - 'title' => 'title', - 'description' => 'description', - 'icon' => 'icon', - 'attributes' => 'attributes', - 'provides_context' => 'providesContext', - 'uses_context' => 'usesContext', - 'selectors' => 'selectors', - 'supports' => 'supports', - 'category' => 'category', - 'styles' => 'styles', - 'textdomain' => 'textdomain', - 'parent' => 'parent', - 'ancestor' => 'ancestor', - 'keywords' => 'keywords', - 'example' => 'example', - 'variations' => 'variations', - 'allowed_blocks' => 'allowedBlocks', - ); - // Add `block_hooks` to the list of fields to pick. - $fields_to_pick['block_hooks'] = 'blockHooks'; - - $exposed_settings = array_intersect_key( $settings, $fields_to_pick ); - - // TODO: Make work for blocks registered via direct call to gutenberg_add_hooked_block(). - wp_add_inline_script( - 'wp-blocks', - 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( array( $inserted_block_name => $exposed_settings ) ) . ');' - ); - - return $settings; -} - -/** - * Register a hooked block for automatic insertion into a given block hook. - * - * A block hook is specified by a block type and a relative position. The hooked block - * will be automatically inserted in the given position next to the "anchor" block - * whenever the latter is encountered. This applies both to the frontend and to the markup - * returned by the templates and patterns REST API endpoints. - * - * This is currently done by filtering parsed blocks as obtained from a block template, - * template part, or pattern, and injecting the hooked block where applicable. - * - * @todo In the long run, we'd likely want some sort of registry for hooked blocks. - * - * @param string $hooked_block The name of the block to insert. - * @param string $position The desired position of the hooked block, relative to its anchor block. - * Can be 'before', 'after', 'first_child', or 'last_child'. - * @param string $anchor_block The name of the block to insert the hooked block next to. - * @return void - */ -function gutenberg_add_hooked_block( $hooked_block, $position, $anchor_block ) { - $hooked_block_array = array( - 'blockName' => $hooked_block, - 'attrs' => array(), - 'innerHTML' => '', - 'innerContent' => array(), - 'innerBlocks' => array(), - ); - - $inserter = gutenberg_insert_hooked_block( $hooked_block_array, $position, $anchor_block ); - add_filter( 'gutenberg_serialize_block', $inserter, 10, 1 ); - - /* - * The block-types REST API controller uses objects of the `WP_Block_Type` class, which are - * in turn created upon block type registration. However, that class does not contain - * a `block_hooks` property (and is not easily extensible), so we have to use a different - * mechanism to communicate to the controller which hooked blocks have been registered for - * automatic insertion. We're doing so here (i.e. upon block registration), by adding a filter to - * the controller's response. - */ - $controller_extender = gutenberg_add_block_hooks_field_to_block_type_controller( $hooked_block, $position, $anchor_block ); - add_filter( 'rest_prepare_block_type', $controller_extender, 10, 2 ); -} - -/** - * Return a function that auto-inserts a block next to a given "anchor" block. - * - * This is a helper function used in the implementation of block hooks. - * It is not meant for public use. - * - * The auto-inserted block can be inserted before or after the anchor block, - * or as the first or last child of the anchor block. - * - * Note that the returned function mutates the automatically inserted block's - * designated parent block by inserting into the parent's `innerBlocks` array, - * and by updating the parent's `innerContent` array accordingly. - * - * @param array $inserted_block The block to insert. - * @param string $relative_position The position relative to the given block. - * Can be 'before', 'after', 'first_child', or 'last_child'. - * @param string $anchor_block_type The automatically inserted block will be inserted next to instances of this block type. - * @return callable A function that accepts a block's content and returns the content with the inserted block. - */ -function gutenberg_insert_hooked_block( $inserted_block, $relative_position, $anchor_block_type ) { - return function ( $block ) use ( $inserted_block, $relative_position, $anchor_block_type ) { - if ( $anchor_block_type === $block['blockName'] ) { - if ( 'first_child' === $relative_position ) { - array_unshift( $block['innerBlocks'], $inserted_block ); - // Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`) - // when rendering blocks, we also need to prepend a value (`null`, to mark a block - // location) to that array after HTML content for the inner blocks wrapper. - $chunk_index = 0; - for ( $index = $chunk_index; $index < count( $block['innerContent'] ); $index++ ) { - if ( is_null( $block['innerContent'][ $index ] ) ) { - $chunk_index = $index; - break; - } - } - array_splice( $block['innerContent'], $chunk_index, 0, array( null ) ); - } elseif ( 'last_child' === $relative_position ) { - array_push( $block['innerBlocks'], $inserted_block ); - // Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`) - // when rendering blocks, we also need to correctly append a value (`null`, to mark a block - // location) to that array before the remaining HTML content for the inner blocks wrapper. - $chunk_index = count( $block['innerContent'] ); - for ( $index = count( $block['innerContent'] ); $index > 0; $index-- ) { - if ( is_null( $block['innerContent'][ $index - 1 ] ) ) { - $chunk_index = $index; - break; - } - } - array_splice( $block['innerContent'], $chunk_index, 0, array( null ) ); - } - return $block; - } - - $anchor_block_index = array_search( $anchor_block_type, array_column( $block['innerBlocks'], 'blockName' ), true ); - if ( false !== $anchor_block_index && ( 'after' === $relative_position || 'before' === $relative_position ) ) { - if ( 'after' === $relative_position ) { - ++$anchor_block_index; - } - array_splice( $block['innerBlocks'], $anchor_block_index, 0, array( $inserted_block ) ); - - // Find matching `innerContent` chunk index. - $chunk_index = 0; - while ( $anchor_block_index > 0 ) { - if ( ! is_string( $block['innerContent'][ $chunk_index ] ) ) { - --$anchor_block_index; - } - ++$chunk_index; - } - // Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`) - // when rendering blocks, we also need to insert a value (`null`, to mark a block - // location) into that array. - array_splice( $block['innerContent'], $chunk_index, 0, array( null ) ); - } - return $block; - }; -} - -/** - * Add block hooks information to a block type's controller. - * - * @param array $inserted_block_type The type of block to insert. - * @param string $position The position relative to the anchor block. - * Can be 'before', 'after', 'first_child', or 'last_child'. - * @param string $anchor_block_type The hooked block will be inserted next to instances of this block type. - * @return callable A filter for the `rest_prepare_block_type` hook that adds a `block_hooks` field to the network response. - */ -function gutenberg_add_block_hooks_field_to_block_type_controller( $inserted_block_type, $position, $anchor_block_type ) { - return function ( $response, $block_type ) use ( $inserted_block_type, $position, $anchor_block_type ) { - if ( $block_type->name !== $inserted_block_type ) { - return $response; - } - - $data = $response->get_data(); - if ( ! isset( $data['block_hooks'] ) ) { - $data['block_hooks'] = array(); - } - $data['block_hooks'][ $anchor_block_type ] = $position; - $response->set_data( $data ); - return $response; - }; -} - -/** - * Parse and reserialize block templates to allow running filters. - * - * By parsing a block template's content and then reserializing it - * via `gutenberg_serialize_blocks()`, we are able to run filters - * on the parsed blocks. This allows us to modify (parsed) blocks during - * depth-first traversal already provided by the serialization process, - * rather than having to do so in a separate pass. - * - * @param WP_Block_Template[] $query_result Array of found block templates. - * @return WP_Block_Template[] Updated array of found block templates. - */ -function gutenberg_parse_and_serialize_block_templates( $query_result ) { - foreach ( $query_result as $block_template ) { - if ( empty( $block_template->content ) || 'custom' === $block_template->source ) { - continue; - } - $blocks = parse_blocks( $block_template->content ); - $block_template->content = gutenberg_serialize_blocks( $blocks ); - } - - return $query_result; -} - -/** - * Filters the block template object after it has been (potentially) fetched from the theme file. - * - * By parsing a block template's content and then reserializing it - * via `gutenberg_serialize_blocks()`, we are able to run filters - * on the parsed blocks. This allows us to modify (parsed) blocks during - * depth-first traversal already provided by the serialization process, - * rather than having to do so in a separate pass. - * - * @param WP_Block_Template|null $block_template The found block template, or null if there is none. - */ -function gutenberg_parse_and_serialize_blocks( $block_template ) { - if ( empty( $block_template->content ) ) { - return $block_template; - } - - $blocks = parse_blocks( $block_template->content ); - $block_template->content = gutenberg_serialize_blocks( $blocks ); - - return $block_template; -} - -/** - * Register the `block_hooks` field for the block-types REST API controller. - * - * @return void - */ -function gutenberg_register_block_hooks_rest_field() { - register_rest_field( - 'block-type', - 'block_hooks', - array( - 'schema' => array( - 'description' => __( 'This block is automatically inserted near any occurrence of the block types used as keys of this map, into a relative position given by the corresponding value.', 'gutenberg' ), - 'type' => 'object', - 'patternProperties' => array( - '^[a-zA-Z0-9-]+/[a-zA-Z0-9-]+$' => array( - 'type' => 'string', - 'enum' => array( 'before', 'after', 'first_child', 'last_child' ), - ), - ), - ), - ) - ); -} - -// Install the polyfill for Block Hooks only if it isn't already handled in WordPress core. -if ( ! function_exists( 'traverse_and_serialize_blocks' ) ) { - add_filter( 'block_type_metadata_settings', 'gutenberg_add_hooked_blocks', 10, 2 ); - add_filter( 'get_block_templates', 'gutenberg_parse_and_serialize_block_templates', 10, 1 ); - add_filter( 'get_block_file_template', 'gutenberg_parse_and_serialize_blocks', 10, 1 ); - add_action( 'rest_api_init', 'gutenberg_register_block_hooks_rest_field' ); -} - -// Helper functions. -// ----------------- -// The sole purpose of the following two functions (`gutenberg_serialize_block` -// and `gutenberg_serialize_blocks`), which are otherwise copies of their unprefixed -// counterparts (`serialize_block` and `serialize_blocks`) is to apply a filter -// (also called `gutenberg_serialize_block`) as an entry point for modifications -// to the parsed blocks. - -/** - * Filterable version of `serialize_block()`. - * - * This function is identical to `serialize_block()`, except that it applies - * the `gutenberg_serialize_block` filter to each block before it is serialized. - * - * @param array $block The block to be serialized. - * @return string The serialized block. - * - * @see serialize_block() - */ -function gutenberg_serialize_block( $block ) { - $block_content = ''; - - /** - * Filters a parsed block before it is serialized. - * - * @param array $block The block to be serialized. - */ - $block = apply_filters( 'gutenberg_serialize_block', $block ); - - $index = 0; - foreach ( $block['innerContent'] as $chunk ) { - if ( is_string( $chunk ) ) { - $block_content .= $chunk; - } else { // Compare to WP_Block::render(). - $inner_block = $block['innerBlocks'][ $index++ ]; - $block_content .= gutenberg_serialize_block( $inner_block ); - } - } - - if ( ! is_array( $block['attrs'] ) ) { - $block['attrs'] = array(); - } - - return get_comment_delimited_block_content( - $block['blockName'], - $block['attrs'], - $block_content - ); -} - -/** - * Filterable version of `serialize_blocks()`. - * - * This function is identical to `serialize_blocks()`, except that it applies - * the `gutenberg_serialize_block` filter to each block before it is serialized. - * - * @param array $blocks The blocks to be serialized. - * @return string[] The serialized blocks. - * - * @see serialize_blocks() - */ -function gutenberg_serialize_blocks( $blocks ) { - return implode( '', array_map( 'gutenberg_serialize_block', $blocks ) ); -} diff --git a/lib/compat/wordpress-6.4/blocks.php b/lib/compat/wordpress-6.4/blocks.php deleted file mode 100644 index 74fa9253e45d50..00000000000000 --- a/lib/compat/wordpress-6.4/blocks.php +++ /dev/null @@ -1,23 +0,0 @@ -get_data(); - - if ( empty( $data['content'] ) ) { - return $response; - } - - $blocks = parse_blocks( $data['content'] ); - $data['content'] = gutenberg_serialize_blocks( $blocks ); // Serialize or render? - - return rest_ensure_response( $data ); - } -} diff --git a/lib/compat/wordpress-6.4/class-gutenberg-rest-global-styles-revisions-controller-6-4.php b/lib/compat/wordpress-6.4/class-gutenberg-rest-global-styles-revisions-controller-6-4.php deleted file mode 100644 index 4c7df97c33e57c..00000000000000 --- a/lib/compat/wordpress-6.4/class-gutenberg-rest-global-styles-revisions-controller-6-4.php +++ /dev/null @@ -1,164 +0,0 @@ -get_parent( $request['parent'] ); - $global_styles_config = $this->get_decoded_global_styles_json( $post->post_content ); - - if ( is_wp_error( $global_styles_config ) ) { - return $global_styles_config; - } - - $fields = $this->get_fields_for_response( $request ); - $data = array(); - - if ( ! empty( $global_styles_config['styles'] ) || ! empty( $global_styles_config['settings'] ) ) { - $global_styles_config = ( new WP_Theme_JSON_Gutenberg( $global_styles_config, 'custom' ) )->get_raw_data(); - if ( rest_is_field_included( 'settings', $fields ) ) { - $data['settings'] = ! empty( $global_styles_config['settings'] ) ? $global_styles_config['settings'] : new stdClass(); - } - if ( rest_is_field_included( 'styles', $fields ) ) { - $data['styles'] = ! empty( $global_styles_config['styles'] ) ? $global_styles_config['styles'] : new stdClass(); - } - } - - if ( rest_is_field_included( 'author', $fields ) ) { - $data['author'] = (int) $post->post_author; - } - - if ( rest_is_field_included( 'date', $fields ) ) { - $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); - } - - if ( rest_is_field_included( 'date_gmt', $fields ) ) { - $data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt ); - } - - if ( rest_is_field_included( 'id', $fields ) ) { - $data['id'] = (int) $post->ID; - } - - if ( rest_is_field_included( 'modified', $fields ) ) { - $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); - } - - if ( rest_is_field_included( 'modified_gmt', $fields ) ) { - $data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt ); - } - - if ( rest_is_field_included( 'parent', $fields ) ) { - $data['parent'] = (int) $parent->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 ); - - 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 ); - } -} diff --git a/lib/compat/wordpress-6.4/class-gutenberg-rest-templates-controller-6-4.php b/lib/compat/wordpress-6.4/class-gutenberg-rest-templates-controller-6-4.php deleted file mode 100644 index ec969519f9ac4f..00000000000000 --- a/lib/compat/wordpress-6.4/class-gutenberg-rest-templates-controller-6-4.php +++ /dev/null @@ -1,75 +0,0 @@ -get_fields_for_response( $request ); - - $response = parent::prepare_item_for_response( $item, $request ); - - if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { - $links = $this->prepare_revision_links( $template ); - $response->add_links( $links ); - if ( ! empty( $links['self']['href'] ) ) { - $actions = $this->get_available_actions(); - $self = $links['self']['href']; - foreach ( $actions as $rel ) { - $response->add_link( $rel, $self ); - } - } - } - - return $response; - } - - /** - * Adds revisions to links. - * - * @param WP_Block_Template $template Template instance. - * @return array Links for the given post. - */ - protected function prepare_revision_links( $template ) { - $links = array(); - - if ( post_type_supports( $this->post_type, 'revisions' ) && (int) $template->wp_id ) { - $revisions = wp_get_latest_revision_id_and_total_count( (int) $template->wp_id ); - $revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0; - $revisions_base = sprintf( '/%s/%s/%s/revisions', $this->namespace, $this->rest_base, $template->id ); - - $links['version-history'] = array( - 'href' => rest_url( $revisions_base ), - 'count' => $revisions_count, - ); - - if ( $revisions_count > 0 ) { - $links['predecessor-version'] = array( - 'href' => rest_url( $revisions_base . '/' . $revisions['latest_id'] ), - 'id' => $revisions['latest_id'], - ); - } - } - - return $links; - } -} diff --git a/lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face-resolver.php b/lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face-resolver.php deleted file mode 100644 index 556663b8813665..00000000000000 --- a/lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face-resolver.php +++ /dev/null @@ -1,183 +0,0 @@ - $src_url ) { - // Skip if the src doesn't start with the placeholder, as there's nothing to replace. - if ( ! str_starts_with( $src_url, $placeholder ) ) { - continue; - } - - $src_file = str_replace( $placeholder, '', $src_url ); - $src[ $src_key ] = get_theme_file_uri( $src_file ); - } - - return $src; - } - - /** - * Converts all first dimension keys into kebab-case. - * - * @since 6.4.0 - * - * @param array $data The array to process. - * @return array Data with first dimension keys converted into kebab-case. - */ - private static function to_kebab_case( array $data ) { - foreach ( $data as $key => $value ) { - $kebab_case = _wp_to_kebab_case( $key ); - $data[ $kebab_case ] = $value; - if ( $kebab_case !== $key ) { - unset( $data[ $key ] ); - } - } - - return $data; - } - } -} diff --git a/lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face.php b/lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face.php deleted file mode 100644 index 6bea6eb86cc714..00000000000000 --- a/lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face.php +++ /dev/null @@ -1,435 +0,0 @@ - '', - 'font-style' => 'normal', - 'font-weight' => '400', - 'font-display' => 'fallback', - ); - - /** - * Valid font-face property names. - * - * @since 6.4.0 - * - * @var string[] - */ - private $valid_font_face_properties = array( - 'ascent-override', - 'descent-override', - 'font-display', - 'font-family', - 'font-stretch', - 'font-style', - 'font-weight', - 'font-variant', - 'font-feature-settings', - 'font-variation-settings', - 'line-gap-override', - 'size-adjust', - 'src', - 'unicode-range', - ); - - /** - * Valid font-display values. - * - * @since 6.4.0 - * - * @var string[] - */ - private $valid_font_display = array( 'auto', 'block', 'fallback', 'swap', 'optional' ); - - /** - * Array of font-face style tag's attribute(s) - * where the key is the attribute name and the - * value is its value. - * - * @since 6.4.0 - * - * @var string[] - */ - private $style_tag_attrs = array(); - - /** - * Creates and initializes an instance of WP_Font_Face. - * - * @since 6.4.0 - */ - public function __construct() { - if ( - function_exists( 'is_admin' ) && ! is_admin() - && - function_exists( 'current_theme_supports' ) && ! current_theme_supports( 'html5', 'style' ) - ) { - $this->style_tag_attrs = array( 'type' => 'text/css' ); - } - } - - /** - * Generates and prints the `@font-face` styles for the given fonts. - * - * @since 6.4.0 - * - * @param array[][] $fonts Optional. The font-families and their font variations. - * See {@see wp_print_font_faces()} for the supported fields. - * Default empty array. - */ - public function generate_and_print( array $fonts ) { - $fonts = $this->validate_fonts( $fonts ); - - // Bail out if there are no fonts are given to process. - if ( empty( $fonts ) ) { - return; - } - - $css = $this->get_css( $fonts ); - - /* - * The font-face CSS is contained within and open a - * - * In an HTML document this would print "…" to the console, - * but in an XHTML document it would print "ā€¦" to the console. - * - * - * - * In an HTML document this would print "An image is in HTML", - * but it's an invalid XHTML document because it interprets the `` - * as an empty tag missing its closing `/`. - * - * @see https://www.w3.org/TR/xhtml1/#h-4.8 - */ - if ( - ! $is_html5 && - ( - ! isset( $attributes['type'] ) || - 'module' === $attributes['type'] || - str_contains( $attributes['type'], 'javascript' ) || - str_contains( $attributes['type'], 'ecmascript' ) || - str_contains( $attributes['type'], 'jscript' ) || - str_contains( $attributes['type'], 'livescript' ) - ) - ) { - /* - * If the string `]]>` exists within the JavaScript it would break - * out of any wrapping CDATA section added here, so to start, it's - * necessary to escape that sequence which requires splitting the - * content into two CDATA sections wherever it's found. - * - * Note: it's only necessary to escape the closing `]]>` because - * an additional `', ']]]]>', $data ); - - // Wrap the entire escaped script inside a CDATA section. - $data = sprintf( "/* */", $data ); - } - - $data = "\n" . trim( $data, "\n\r " ) . "\n"; - - /** - * Filters attributes to be added to a script tag. - * - * @since 5.7.0 - * - * @param array $attributes Key-value pairs representing `\n", wp_sanitize_script_attributes( $attributes ), $data ); -} diff --git a/lib/compat/wordpress-6.5/scripts-modules.php b/lib/compat/wordpress-6.5/scripts-modules.php deleted file mode 100644 index 110ef858eb8ce8..00000000000000 --- a/lib/compat/wordpress-6.5/scripts-modules.php +++ /dev/null @@ -1,224 +0,0 @@ -add_hooks(); - - /** - * Add module fields from block metadata to WP_Block_Type settings. - * - * This filter allows us to register modules from block metadata and attach additional fields to - * WP_Block_Type instances. - * - * @param array $settings Array of determined settings for registering a block type. - * @param array $metadata Metadata provided for registering a block type. - */ - function gutenberg_filter_block_type_metadata_settings_register_modules( $settings, $metadata = null ) { - $module_fields = array( - 'viewScriptModule' => 'view_script_module_ids', - ); - foreach ( $module_fields as $metadata_field_name => $settings_field_name ) { - if ( ! empty( $settings[ $metadata_field_name ] ) ) { - $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ]; - } - if ( ! empty( $metadata[ $metadata_field_name ] ) ) { - $modules = $metadata[ $metadata_field_name ]; - $processed_modules = array(); - if ( is_array( $modules ) ) { - for ( $index = 0; $index < count( $modules ); $index++ ) { - $processed_modules[] = gutenberg_register_block_module_id( - $metadata, - $metadata_field_name, - $index - ); - } - } else { - $processed_modules[] = gutenberg_register_block_module_id( - $metadata, - $metadata_field_name - ); - } - $settings[ $settings_field_name ] = $processed_modules; - } - } - - return $settings; - } - - add_filter( 'block_type_metadata_settings', 'gutenberg_filter_block_type_metadata_settings_register_modules', 10, 2 ); - - /** - * Enqueue modules associated with the block. - * - * @param string $block_content The block content. - * @param array $parsed_block The full block, including name and attributes. - * @param WP_Block $block_instance The block instance. - */ - function gutenberg_filter_render_block_enqueue_view_script_modules( $block_content, $parsed_block, $block_instance ) { - $block_type = $block_instance->block_type; - - if ( ! empty( $block_type->view_script_module_ids ) ) { - foreach ( $block_type->view_script_module_ids as $module_id ) { - wp_enqueue_script_module( $module_id ); - } - } - - return $block_content; - } - - add_filter( 'render_block', 'gutenberg_filter_render_block_enqueue_view_script_modules', 10, 3 ); - - /** - * Registers a REST field for block types to provide view script module IDs. - * - * Adds the `view_script_module_ids` and `view_module_ids` (deprecated) field to block type objects in the REST API, which - * lists the script module IDs for any script modules associated with the - * block's viewScriptModule key. - */ - function gutenberg_register_view_script_module_ids_rest_field() { - register_rest_field( - 'block-type', - 'view_script_module_ids', - array( - 'get_callback' => function ( $item ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $item['name'] ); - if ( isset( $block_type->view_script_module_ids ) ) { - return $block_type->view_script_module_ids; - } - return array(); - }, - ) - ); - } - - add_action( 'rest_api_init', 'gutenberg_register_view_script_module_ids_rest_field' ); -} - -if ( ! function_exists( 'wp_register_script_module' ) ) { - /** - * Registers the script module if no script module with that script module - * identifier has already been registered. - * - * @since 6.5.0 - * - * @param string $id The identifier of the script module. Should be unique. It will be used in the - * final import map. - * @param string $src Optional. Full URL of the script module, or path of the script module relative - * to the WordPress root directory. If it is provided and the script module has - * not been registered yet, it will be registered. - * @param array $deps { - * Optional. List of dependencies. - * - * @type string|array $0... { - * An array of script module identifiers of the dependencies of this script - * module. The dependencies can be strings or arrays. If they are arrays, - * they need an `id` key with the script module identifier, and can contain - * an `import` key with either `static` or `dynamic`. By default, - * dependencies that don't contain an `import` key are considered static. - * - * @type string $id The script module identifier. - * @type string $import Optional. Import type. May be either `static` or - * `dynamic`. Defaults to `static`. - * } - * } - * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false. - * It is added to the URL as a query string for cache busting purposes. If $version - * is set to false, the version number is the currently installed WordPress version. - * If $version is set to null, no version is added. - */ - function wp_register_script_module( string $id, string $src, array $deps = array(), $version = false ) { - wp_script_modules()->register( $id, $src, $deps, $version ); - } -} - -if ( ! function_exists( 'wp_enqueue_script_module' ) ) { - /** - * Marks the script module to be enqueued in the page. - * - * If a src is provided and the script module has not been registered yet, it - * will be registered. - * - * @since 6.5.0 - * - * @param string $id The identifier of the script module. Should be unique. It will be used in the - * final import map. - * @param string $src Optional. Full URL of the script module, or path of the script module relative - * to the WordPress root directory. If it is provided and the script module has - * not been registered yet, it will be registered. - * @param array $deps { - * Optional. List of dependencies. - * - * @type string|array $0... { - * An array of script module identifiers of the dependencies of this script - * module. The dependencies can be strings or arrays. If they are arrays, - * they need an `id` key with the script module identifier, and can contain - * an `import` key with either `static` or `dynamic`. By default, - * dependencies that don't contain an `import` key are considered static. - * - * @type string $id The script module identifier. - * @type string $import Optional. Import type. May be either `static` or - * `dynamic`. Defaults to `static`. - * } - * } - * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false. - * It is added to the URL as a query string for cache busting purposes. If $version - * is set to false, the version number is the currently installed WordPress version. - * If $version is set to null, no version is added. - */ - function wp_enqueue_script_module( string $id, string $src = '', array $deps = array(), $version = false ) { - wp_script_modules()->enqueue( $id, $src, $deps, $version ); - } -} - -if ( ! function_exists( 'wp_dequeue_script_module' ) ) { - /** - * Unmarks the script module so it is no longer enqueued in the page. - * - * @since 6.5.0 - * - * @param string $id The identifier of the script module. - */ - function wp_dequeue_script_module( string $id ) { - wp_script_modules()->dequeue( $id ); - } -} - -if ( ! function_exists( 'wp_deregister_script_module' ) ) { - /** - * Deregisters the script module. - * - * @since 6.5.0 - * - * @param string $id The identifier of the script module. - */ - function wp_deregister_script_module( string $id ) { - wp_script_modules()->deregister( $id ); - } -} diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php index f725366c33cfb8..3e5d4cdd68454a 100644 --- a/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php @@ -15,7 +15,7 @@ * * @see WP_REST_Controller */ -class Gutenberg_REST_Global_Styles_Revisions_Controller_6_6 extends Gutenberg_REST_Global_Styles_Revisions_Controller_6_5 { +class Gutenberg_REST_Global_Styles_Revisions_Controller_6_6 extends WP_REST_Global_Styles_Revisions_Controller { /** * Prepares the revision for the REST response. * diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php index e670afacea5b33..034187ca9a70ae 100644 --- a/lib/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php @@ -12,7 +12,7 @@ * `edit_theme_options` capability. In order to allow other roles to also view the templates, * we need to override the permissions check for the REST API endpoints. */ -class Gutenberg_REST_Templates_Controller_6_6 extends Gutenberg_REST_Templates_Controller_6_4 { +class Gutenberg_REST_Templates_Controller_6_6 extends WP_REST_Templates_Controller { /** * Checks if a given request has access to read templates. diff --git a/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-decoder-6-6.php b/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-decoder-6-6.php index 2999921be52259..d0b9f18bf5b29b 100644 --- a/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-decoder-6-6.php +++ b/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-decoder-6-6.php @@ -196,6 +196,8 @@ public static function decode( $context, $text ) { * * @since 6.6.0 * + * @global WP_Token_Map $html5_named_character_references + * * @param string $context `attribute` for decoding attribute values, `data` otherwise. * @param string $text Text document containing span of text to decode. * @param int $at Optional. Byte offset into text where span begins, defaults to the beginning (0). diff --git a/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-processor-6-6.php b/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-processor-6-6.php index 634aaab01707cd..77801535ff3683 100644 --- a/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-processor-6-6.php +++ b/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-processor-6-6.php @@ -293,8 +293,8 @@ public static function create_fragment( $html, $context = '', $encoding = $processor->state->insertion_mode = Gutenberg_HTML_Processor_State_6_6::INSERTION_MODE_IN_BODY; // @todo Create "fake" bookmarks for non-existent but implied nodes. - $processor->bookmarks['root-node'] = new Gutenberg_HTML_Span_6_5( 0, 0 ); - $processor->bookmarks['context-node'] = new Gutenberg_HTML_Span_6_5( 0, 0 ); + $processor->bookmarks['root-node'] = new WP_HTML_Span( 0, 0 ); + $processor->bookmarks['context-node'] = new WP_HTML_Span( 0, 0 ); $processor->state->stack_of_open_elements->push( new WP_HTML_Token( diff --git a/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-tag-processor-6-6.php b/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-tag-processor-6-6.php index eb873e7a063401..ecbc6d0d7b6af4 100644 --- a/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-tag-processor-6-6.php +++ b/lib/compat/wordpress-6.6/html-api/class-gutenberg-html-tag-processor-6-6.php @@ -1227,7 +1227,7 @@ public function set_bookmark( $name ) { return false; } - $this->bookmarks[ $name ] = new Gutenberg_HTML_Span_6_5( $this->token_starts_at, $this->token_length ); + $this->bookmarks[ $name ] = new WP_HTML_Span( $this->token_starts_at, $this->token_length ); return true; } @@ -2035,7 +2035,7 @@ private function parse_next_attribute() { * an array when encountering duplicates avoids needless allocations in the * normative case of parsing tags with no duplicate attributes. */ - $duplicate_span = new Gutenberg_HTML_Span_6_5( $attribute_start, $attribute_end - $attribute_start ); + $duplicate_span = new WP_HTML_Span( $attribute_start, $attribute_end - $attribute_start ); if ( null === $this->duplicate_attributes ) { $this->duplicate_attributes = array( $comparable_name => array( $duplicate_span ) ); } elseif ( ! array_key_exists( $comparable_name, $this->duplicate_attributes ) ) { @@ -3007,7 +3007,7 @@ public function set_attribute( $name, $value ) { * Result:
*/ $existing_attribute = $this->attributes[ $comparable_name ]; - $this->lexical_updates[ $comparable_name ] = new Gutenberg_HTML_Text_Replacement_6_5( + $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( $existing_attribute->start, $existing_attribute->length, $updated_attribute @@ -3025,7 +3025,7 @@ public function set_attribute( $name, $value ) { * * Result:
*/ - $this->lexical_updates[ $comparable_name ] = new Gutenberg_HTML_Text_Replacement_6_5( + $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( $this->tag_name_starts_at + $this->tag_name_length, 0, ' ' . $updated_attribute @@ -3103,7 +3103,7 @@ public function remove_attribute( $name ) { * * Result:
*/ - $this->lexical_updates[ $name ] = new Gutenberg_HTML_Text_Replacement_6_5( + $this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement( $this->attributes[ $name ]->start, $this->attributes[ $name ]->length, '' @@ -3112,7 +3112,7 @@ public function remove_attribute( $name ) { // Removes any duplicated attributes if they were also present. if ( null !== $this->duplicate_attributes && array_key_exists( $name, $this->duplicate_attributes ) ) { foreach ( $this->duplicate_attributes[ $name ] as $attribute_token ) { - $this->lexical_updates[] = new Gutenberg_HTML_Text_Replacement_6_5( + $this->lexical_updates[] = new WP_HTML_Text_Replacement( $attribute_token->start, $attribute_token->length, '' diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php index 54eaaf28de82df..eadd3b1d376a72 100644 --- a/lib/compat/wordpress-6.6/rest-api.php +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -33,7 +33,7 @@ function wp_api_template_access_controller( $args, $post_type ) { /** * Adds the post classes to the REST API response. * - * @param array $post The response object data. + * @param array $post The response object data. * * @return array */ @@ -88,76 +88,116 @@ function gutenberg_register_global_styles_revisions_endpoints() { add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' ); -if ( ! function_exists( 'gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field' ) ) { - /** - * Adds `stylesheet_uri` fields to WP_REST_Themes_Controller class. - */ - function gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field() { - register_rest_field( - 'theme', - 'stylesheet_uri', - array( - 'get_callback' => function ( $item ) { - if ( ! empty( $item['stylesheet'] ) ) { - $theme = wp_get_theme( $item['stylesheet'] ); - $current_theme = wp_get_theme(); - if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) { - return get_stylesheet_directory_uri(); - } else { - return $theme->get_stylesheet_directory_uri(); - } +/** + * Adds `stylesheet_uri` fields to WP_REST_Themes_Controller class. + */ +function gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field() { + register_rest_field( + 'theme', + 'stylesheet_uri', + array( + 'get_callback' => function ( $item ) { + if ( ! empty( $item['stylesheet'] ) ) { + $theme = wp_get_theme( $item['stylesheet'] ); + $current_theme = wp_get_theme(); + if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) { + return get_stylesheet_directory_uri(); + } else { + return $theme->get_stylesheet_directory_uri(); } + } - return null; - }, - 'schema' => array( - 'type' => 'string', - 'description' => __( 'The uri for the theme\'s stylesheet directory.', 'gutenberg' ), - 'format' => 'uri', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ) - ); - } + return null; + }, + 'schema' => array( + 'type' => 'string', + 'description' => __( 'The uri for the theme\'s stylesheet directory.', 'gutenberg' ), + 'format' => 'uri', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + ) + ); } add_action( 'rest_api_init', 'gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field' ); -if ( ! function_exists( 'gutenberg_register_wp_rest_themes_template_directory_uri_field' ) ) { - /** - * Adds `template_uri` fields to WP_REST_Themes_Controller class. - */ - function gutenberg_register_wp_rest_themes_template_directory_uri_field() { - register_rest_field( - 'theme', - 'template_uri', - array( - 'get_callback' => function ( $item ) { - if ( ! empty( $item['stylesheet'] ) ) { - $theme = wp_get_theme( $item['stylesheet'] ); - $current_theme = wp_get_theme(); - if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) { - return get_template_directory_uri(); - } else { - return $theme->get_template_directory_uri(); - } +/** + * Adds `template_uri` fields to WP_REST_Themes_Controller class. + */ +function gutenberg_register_wp_rest_themes_template_directory_uri_field() { + register_rest_field( + 'theme', + 'template_uri', + array( + 'get_callback' => function ( $item ) { + if ( ! empty( $item['stylesheet'] ) ) { + $theme = wp_get_theme( $item['stylesheet'] ); + $current_theme = wp_get_theme(); + if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) { + return get_template_directory_uri(); + } else { + return $theme->get_template_directory_uri(); } + } - return null; - }, - 'schema' => array( - 'type' => 'string', - 'description' => __( 'The uri for the theme\'s template directory. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet directory.', 'gutenberg' ), - 'format' => 'uri', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ) - ); - } + return null; + }, + 'schema' => array( + 'type' => 'string', + 'description' => __( 'The uri for the theme\'s template directory. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet directory.', 'gutenberg' ), + 'format' => 'uri', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + ) + ); } add_action( 'rest_api_init', 'gutenberg_register_wp_rest_themes_template_directory_uri_field' ); +/** + * Adds `template` and `template_lock` fields to WP_REST_Post_Types_Controller class. + */ +function gutenberg_register_wp_rest_post_types_controller_fields() { + register_rest_field( + 'type', + 'template', + array( + 'get_callback' => function ( $item ) { + $post_type = get_post_type_object( $item['slug'] ); + if ( ! empty( $post_type ) ) { + return $post_type->template ?? array(); + } + }, + 'schema' => array( + 'type' => 'array', + 'description' => __( 'The block template associated with the post type.', 'gutenberg' ), + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + ) + ); + register_rest_field( + 'type', + 'template_lock', + array( + 'get_callback' => function ( $item ) { + $post_type = get_post_type_object( $item['slug'] ); + if ( ! empty( $post_type ) ) { + return ! empty( $post_type->template_lock ) ? $post_type->template_lock : false; + } + }, + 'schema' => array( + 'type' => array( 'string', 'boolean' ), + 'enum' => array( 'all', 'insert', 'contentOnly', false ), + 'description' => __( 'The template_lock associated with the post type, or false if none.', 'gutenberg' ), + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + ) + ); +} +add_action( 'rest_api_init', 'gutenberg_register_wp_rest_post_types_controller_fields' ); + /** * Preload theme and global styles paths to avoid flash of variation styles in post editor. * @@ -169,7 +209,7 @@ function gutenberg_block_editor_preload_paths_6_6( $paths, $context ) { if ( 'core/edit-post' === $context->name ) { $paths[] = '/wp/v2/global-styles/themes/' . get_stylesheet(); $paths[] = '/wp/v2/themes?context=edit&status=active'; - $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver::get_user_global_styles_post_id() . '?context=edit'; + $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id() . '?context=edit'; } return $paths; } diff --git a/lib/compat/wordpress-6.7/block-bindings.php b/lib/compat/wordpress-6.7/block-bindings.php new file mode 100644 index 00000000000000..a8f68c0f0f04ea --- /dev/null +++ b/lib/compat/wordpress-6.7/block-bindings.php @@ -0,0 +1,87 @@ + $source_properties ) { + // Add source with the label to editor settings. + $editor_settings['blockBindingsSources'][ $source_name ] = array( + 'label' => $source_properties->label, + ); + // Add `usesContext` property if exists. + if ( ! empty( $source_properties->uses_context ) ) { + $editor_settings['blockBindingsSources'][ $source_name ]['usesContext'] = $source_properties->uses_context; + } + } + } + return $editor_settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_add_server_block_bindings_sources_to_editor_settings', 10 ); + +/** + * Initialize `canUpdateBlockBindings` editor setting if it doesn't exist. By default, it is `true` only for admin users. + * + * @param array $settings The block editor settings from the `block_editor_settings_all` filter. + * @return array The editor settings including `canUpdateBlockBindings`. + */ +function gutenberg_add_can_update_block_bindings_editor_setting( $editor_settings ) { + if ( empty( $editor_settings['canUpdateBlockBindings'] ) ) { + $editor_settings['canUpdateBlockBindings'] = current_user_can( 'manage_options' ); + } + return $editor_settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_add_can_update_block_bindings_editor_setting', 10 ); + +/** + * Add `label` to `register_meta`. + * + * @param array $args Array of arguments for registering meta. + * @return array Modified arguments array including `label`. + */ +function gutenberg_update_meta_args_with_label( $args ) { + // Don't update schema when label isn't provided. + if ( ! isset( $args['label'] ) ) { + return $args; + } + + $schema = array( 'title' => $args['label'] ); + if ( ! is_array( $args['show_in_rest'] ) ) { + $args['show_in_rest'] = array( + 'schema' => $schema, + ); + return $args; + } + + if ( ! empty( $args['show_in_rest']['schema'] ) ) { + $args['show_in_rest']['schema'] = array_merge( $args['show_in_rest']['schema'], $schema ); + } else { + $args['show_in_rest']['schema'] = $schema; + } + + return $args; +} + +// Priority must be lower than 10 to ensure the label is not removed. +add_filter( 'register_meta_args', 'gutenberg_update_meta_args_with_label', 5, 1 ); diff --git a/lib/compat/wordpress-6.7/block-templates.php b/lib/compat/wordpress-6.7/block-templates.php new file mode 100644 index 00000000000000..d1f2859070b8b4 --- /dev/null +++ b/lib/compat/wordpress-6.7/block-templates.php @@ -0,0 +1,41 @@ +register( $template_name, $args ); + } +} + +if ( ! function_exists( 'wp_unregister_block_template' ) ) { + /** + * Unregister a template. + * + * @param string $template_name Template name in the form of `plugin_uri//template_name`. + * @return WP_Block_Template|WP_Error The unregistered template object on success, WP_Error object on failure or if + * the template doesn't exist. + */ + function wp_unregister_block_template( $template_name ) { + return WP_Block_Templates_Registry::get_instance()->unregister( $template_name ); + } +} diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php new file mode 100644 index 00000000000000..6b9526f8056fd3 --- /dev/null +++ b/lib/compat/wordpress-6.7/blocks.php @@ -0,0 +1,105 @@ +context['query']['format'] ) || ! is_array( $block->context['query']['format'] ) ) { + return $query; + } + + $formats = $block->context['query']['format']; + $tax_query = array( 'relation' => 'OR' ); + + // The default post format, 'standard', is not stored in the database. + // If 'standard' is part of the request, the query needs to exclude all post items that + // have a format assigned. + if ( in_array( 'standard', $formats, true ) ) { + $tax_query[] = array( + 'taxonomy' => 'post_format', + 'field' => 'slug', + 'terms' => array(), + 'operator' => 'NOT EXISTS', + ); + // Remove the standard format, since it cannot be queried. + unset( $formats[ array_search( 'standard', $formats, true ) ] ); + } + + // Add any remaining formats to the tax query. + if ( ! empty( $formats ) ) { + // Add the post-format- prefix. + $terms = array_map( + static function ( $format ) { + return 'post-format-' . $format; + }, + $formats + ); + + $tax_query[] = array( + 'taxonomy' => 'post_format', + 'field' => 'slug', + 'terms' => $terms, + 'operator' => 'IN', + ); + } + + // This condition is intended to prevent $tax_query from being added to $query + // if it only contains the relation. + if ( count( $tax_query ) > 1 ) { + $query['tax_query'][] = $tax_query; + } + + return $query; +} +add_filter( 'query_loop_block_query_vars', 'gutenberg_add_format_query_vars_to_query_loop_block', 10, 2 ); diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php new file mode 100644 index 00000000000000..c7de4371c94f56 --- /dev/null +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -0,0 +1,698 @@ + 400 ) + ); + } + + // Ensure an include parameter is set in case the orderby is set to 'include'. + if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) { + return new WP_Error( + 'rest_orderby_include_missing_include', + __( 'You need to define an include parameter to order by include.' ), + array( 'status' => 400 ) + ); + } + + // Retrieve the list of registered collection query parameters. + $registered = $this->get_collection_params(); + $args = array(); + + /* + * This array defines mappings between public API query parameters whose + * values are accepted as-passed, and their internal WP_Query parameter + * name equivalents (some are the same). Only values which are also + * present in $registered will be set. + */ + $parameter_mappings = array( + 'author' => 'author__in', + 'author_exclude' => 'author__not_in', + 'exclude' => 'post__not_in', + 'include' => 'post__in', + 'menu_order' => 'menu_order', + 'offset' => 'offset', + 'order' => 'order', + 'orderby' => 'orderby', + 'page' => 'paged', + 'parent' => 'post_parent__in', + 'parent_exclude' => 'post_parent__not_in', + 'search' => 's', + 'search_columns' => 'search_columns', + 'slug' => 'post_name__in', + 'status' => 'post_status', + ); + + /* + * For each known parameter which is both registered and present in the request, + * set the parameter's value on the query $args. + */ + foreach ( $parameter_mappings as $api_param => $wp_param ) { + if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { + $args[ $wp_param ] = $request[ $api_param ]; + } + } + + // Check for & assign any parameters which require special handling or setting. + $args['date_query'] = array(); + + if ( isset( $registered['before'], $request['before'] ) ) { + $args['date_query'][] = array( + 'before' => $request['before'], + 'column' => 'post_date', + ); + } + + if ( isset( $registered['modified_before'], $request['modified_before'] ) ) { + $args['date_query'][] = array( + 'before' => $request['modified_before'], + 'column' => 'post_modified', + ); + } + + if ( isset( $registered['after'], $request['after'] ) ) { + $args['date_query'][] = array( + 'after' => $request['after'], + 'column' => 'post_date', + ); + } + + if ( isset( $registered['modified_after'], $request['modified_after'] ) ) { + $args['date_query'][] = array( + 'after' => $request['modified_after'], + 'column' => 'post_modified', + ); + } + + // Ensure our per_page parameter overrides any provided posts_per_page filter. + if ( isset( $registered['per_page'] ) ) { + $args['posts_per_page'] = $request['per_page']; + } + + if ( isset( $registered['sticky'], $request['sticky'] ) ) { + $sticky_posts = get_option( 'sticky_posts', array() ); + if ( ! is_array( $sticky_posts ) ) { + $sticky_posts = array(); + } + if ( $request['sticky'] ) { + /* + * As post__in will be used to only get sticky posts, + * we have to support the case where post__in was already + * specified. + */ + $args['post__in'] = $args['post__in'] ? array_intersect( $sticky_posts, $args['post__in'] ) : $sticky_posts; + + /* + * If we intersected, but there are no post IDs in common, + * WP_Query won't return "no posts" for post__in = array() + * so we have to fake it a bit. + */ + if ( ! $args['post__in'] ) { + $args['post__in'] = array( 0 ); + } + } elseif ( $sticky_posts ) { + /* + * As post___not_in will be used to only get posts that + * are not sticky, we have to support the case where post__not_in + * was already specified. + */ + $args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts ); + } + } + + $args = $this->prepare_tax_query( $args, $request ); + + if ( ! empty( $request['format'] ) ) { + $formats = $request['format']; + $tax_query = array( 'relation' => 'OR' ); + + // The default post format, 'standard', is not stored in the database. + // If 'standard' is part of the request, the query needs to exclude all post items that + // have a format assigned. + if ( in_array( 'standard', $formats, true ) ) { + $tax_query[] = array( + 'taxonomy' => 'post_format', + 'field' => 'slug', + 'terms' => array(), + 'operator' => 'NOT EXISTS', + ); + // Remove the standard format, since it cannot be queried. + unset( $formats[ array_search( 'standard', $formats, true ) ] ); + } + + // Add any remaining formats to the tax query. + if ( ! empty( $formats ) ) { + // Add the post-format- prefix. + $terms = array_map( + static function ( $format ) { + return 'post-format-' . $format; + }, + $formats + ); + + $tax_query[] = array( + 'taxonomy' => 'post_format', + 'field' => 'slug', + 'terms' => $terms, + 'operator' => 'IN', + ); + } + + // Enable filtering by both post formats and other taxonomies by combining them with AND. + if ( isset( $args['tax_query'] ) ) { + $args['tax_query'][] = array( + 'relation' => 'AND', + $tax_query, + ); + } else { + $args['tax_query'] = $tax_query; + } + } + + // Force the post_type argument, since it's not a user input variable. + $args['post_type'] = $this->post_type; + + /** + * Filters WP_Query arguments when querying posts via the REST API. + * + * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. + * + * Possible hook names include: + * + * - `rest_post_query` + * - `rest_page_query` + * - `rest_attachment_query` + * + * Enables adding extra arguments or setting defaults for a post collection request. + * + * @since 4.7.0 + * @since 5.7.0 Moved after the `tax_query` query arg is generated. + * + * @link https://developer.wordpress.org/reference/classes/wp_query/ + * + * @param array $args Array of arguments for WP_Query. + * @param WP_REST_Request $request The REST API request. + */ + $args = apply_filters( "rest_{$this->post_type}_query", $args, $request ); + $query_args = $this->prepare_items_query( $args, $request ); + + $posts_query = new WP_Query(); + $query_result = $posts_query->query( $query_args ); + + // Allow access to all password protected posts if the context is edit. + if ( 'edit' === $request['context'] ) { + add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 ); + } + + $posts = array(); + + update_post_author_caches( $query_result ); + update_post_parent_caches( $query_result ); + + if ( post_type_supports( $this->post_type, 'thumbnail' ) ) { + update_post_thumbnail_cache( $posts_query ); + } + + foreach ( $query_result as $post ) { + if ( ! $this->check_read_permission( $post ) ) { + continue; + } + + $data = $this->prepare_item_for_response( $post, $request ); + $posts[] = $this->prepare_response_for_collection( $data ); + } + + // Reset filter. + if ( 'edit' === $request['context'] ) { + remove_filter( 'post_password_required', array( $this, 'check_password_required' ) ); + } + + $page = (int) $query_args['paged']; + $total_posts = $posts_query->found_posts; + + if ( $total_posts < 1 && $page > 1 ) { + // Out-of-bounds, run the query again without LIMIT for total count. + unset( $query_args['paged'] ); + + $count_query = new WP_Query(); + $count_query->query( $query_args ); + $total_posts = $count_query->found_posts; + } + + $max_pages = (int) ceil( $total_posts / (int) $posts_query->query_vars['posts_per_page'] ); + + if ( $page > $max_pages && $total_posts > 0 ) { + return new WP_Error( + 'rest_post_invalid_page_number', + __( 'The page number requested is larger than the number of pages available.' ), + array( 'status' => 400 ) + ); + } + + $response = rest_ensure_response( $posts ); + + $response->header( 'X-WP-Total', (int) $total_posts ); + $response->header( 'X-WP-TotalPages', (int) $max_pages ); + + $request_params = $request->get_query_params(); + $collection_url = rest_url( rest_get_route_for_post_type_items( $this->post_type ) ); + $base = add_query_arg( urlencode_deep( $request_params ), $collection_url ); + + if ( $page > 1 ) { + $prev_page = $page - 1; + + if ( $prev_page > $max_pages ) { + $prev_page = $max_pages; + } + + $prev_link = add_query_arg( 'page', $prev_page, $base ); + $response->link_header( 'prev', $prev_link ); + } + if ( $max_pages > $page ) { + $next_page = $page + 1; + $next_link = add_query_arg( 'page', $next_page, $base ); + + $response->link_header( 'next', $next_link ); + } + + return $response; + } + + /** + * Retrieves the query params for the posts collection. + * + * @since 4.7.0 + * @since 5.4.0 The `tax_relation` query parameter was added. + * @since 5.7.0 The `modified_after` and `modified_before` query parameters were added. + * @since 6.7.0 The `format` query parameter was added. + * + * @return array Collection parameters. + */ + public function get_collection_params() { + $query_params = parent::get_collection_params(); + + $query_params['context']['default'] = 'view'; + + $query_params['after'] = array( + 'description' => __( 'Limit response to posts published after a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + $query_params['modified_after'] = array( + 'description' => __( 'Limit response to posts modified after a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + if ( post_type_supports( $this->post_type, 'author' ) ) { + $query_params['author'] = array( + 'description' => __( 'Limit result set to posts assigned to specific authors.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + $query_params['author_exclude'] = array( + 'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + } + + $query_params['before'] = array( + 'description' => __( 'Limit response to posts published before a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + $query_params['modified_before'] = array( + 'description' => __( 'Limit response to posts modified before a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + $query_params['exclude'] = array( + 'description' => __( 'Ensure result set excludes specific IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + + $query_params['include'] = array( + 'description' => __( 'Limit result set to specific IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + + if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { + $query_params['menu_order'] = array( + 'description' => __( 'Limit result set to posts with a specific menu_order value.' ), + 'type' => 'integer', + ); + } + + $query_params['offset'] = array( + 'description' => __( 'Offset the result set by a specific number of items.' ), + 'type' => 'integer', + ); + + $query_params['order'] = array( + 'description' => __( 'Order sort attribute ascending or descending.' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + ); + + $query_params['orderby'] = array( + 'description' => __( 'Sort collection by post attribute.' ), + 'type' => 'string', + 'default' => 'date', + 'enum' => array( + 'author', + 'date', + 'id', + 'include', + 'modified', + 'parent', + 'relevance', + 'slug', + 'include_slugs', + 'title', + ), + ); + + if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { + $query_params['orderby']['enum'][] = 'menu_order'; + } + + $post_type = get_post_type_object( $this->post_type ); + + if ( $post_type->hierarchical || 'attachment' === $this->post_type ) { + $query_params['parent'] = array( + 'description' => __( 'Limit result set to items with particular parent IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + $query_params['parent_exclude'] = array( + 'description' => __( 'Limit result set to all items except those of a particular parent ID.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + } + + $query_params['search_columns'] = array( + 'default' => array(), + 'description' => __( 'Array of column names to be searched.' ), + 'type' => 'array', + 'items' => array( + 'enum' => array( 'post_title', 'post_content', 'post_excerpt' ), + 'type' => 'string', + ), + ); + + $query_params['slug'] = array( + 'description' => __( 'Limit result set to posts with one or more specific slugs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ); + + $query_params['status'] = array( + 'default' => 'publish', + 'description' => __( 'Limit result set to posts assigned one or more statuses.' ), + 'type' => 'array', + 'items' => array( + 'enum' => array_merge( array_keys( get_post_stati() ), array( 'any' ) ), + 'type' => 'string', + ), + 'sanitize_callback' => array( $this, 'sanitize_post_statuses' ), + ); + + $query_params = $this->prepare_taxonomy_limit_schema( $query_params ); + + if ( 'post' === $this->post_type ) { + $query_params['sticky'] = array( + 'description' => __( 'Limit result set to items that are sticky.' ), + 'type' => 'boolean', + ); + } + + if ( post_type_supports( $this->post_type, 'post-formats' ) ) { + $query_params['format'] = array( + 'description' => __( 'Limit result set to items assigned one or more given formats.' ), + 'type' => 'array', + 'uniqueItems' => true, + 'items' => array( + 'enum' => array_values( get_post_format_slugs() ), + 'type' => 'string', + ), + ); + } + + /** + * Filters collection parameters for the posts controller. + * + * The dynamic part of the filter `$this->post_type` refers to the post + * type slug for the controller. + * + * This filter registers the collection parameter, but does not map the + * collection parameter to an internal WP_Query parameter. Use the + * `rest_{$this->post_type}_query` filter to set WP_Query parameters. + * + * @since 4.7.0 + * + * @param array $query_params JSON Schema-formatted collection parameters. + * @param WP_Post_Type $post_type Post type object. + */ + return apply_filters( "rest_{$this->post_type}_collection_params", $query_params, $post_type ); + } + + /** + * Prepares the 'tax_query' for a collection of posts. + * + * @since 5.7.0 + * + * @param array $args WP_Query arguments. + * @param WP_REST_Request $request Full details about the request. + * @return array Updated query arguments. + */ + private function prepare_tax_query( array $args, WP_REST_Request $request ) { + $relation = $request['tax_relation']; + + if ( $relation ) { + $args['tax_query'] = array( 'relation' => $relation ); + } + + $taxonomies = wp_list_filter( + get_object_taxonomies( $this->post_type, 'objects' ), + array( 'show_in_rest' => true ) + ); + + foreach ( $taxonomies as $taxonomy ) { + $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; + + $tax_include = $request[ $base ]; + $tax_exclude = $request[ $base . '_exclude' ]; + + if ( $tax_include ) { + $terms = array(); + $include_children = false; + $operator = 'IN'; + + if ( rest_is_array( $tax_include ) ) { + $terms = $tax_include; + } elseif ( rest_is_object( $tax_include ) ) { + $terms = empty( $tax_include['terms'] ) ? array() : $tax_include['terms']; + $include_children = ! empty( $tax_include['include_children'] ); + + if ( isset( $tax_include['operator'] ) && 'AND' === $tax_include['operator'] ) { + $operator = 'AND'; + } + } + + if ( $terms ) { + $args['tax_query'][] = array( + 'taxonomy' => $taxonomy->name, + 'field' => 'term_id', + 'terms' => $terms, + 'include_children' => $include_children, + 'operator' => $operator, + ); + } + } + + if ( $tax_exclude ) { + $terms = array(); + $include_children = false; + + if ( rest_is_array( $tax_exclude ) ) { + $terms = $tax_exclude; + } elseif ( rest_is_object( $tax_exclude ) ) { + $terms = empty( $tax_exclude['terms'] ) ? array() : $tax_exclude['terms']; + $include_children = ! empty( $tax_exclude['include_children'] ); + } + + if ( $terms ) { + $args['tax_query'][] = array( + 'taxonomy' => $taxonomy->name, + 'field' => 'term_id', + 'terms' => $terms, + 'include_children' => $include_children, + 'operator' => 'NOT IN', + ); + } + } + } + + return $args; + } + + /** + * Prepares the collection schema for including and excluding items by terms. + * + * @since 5.7.0 + * + * @param array $query_params Collection schema. + * @return array Updated schema. + */ + private function prepare_taxonomy_limit_schema( array $query_params ) { + $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); + + if ( ! $taxonomies ) { + return $query_params; + } + + $query_params['tax_relation'] = array( + 'description' => __( 'Limit result set based on relationship between multiple taxonomies.' ), + 'type' => 'string', + 'enum' => array( 'AND', 'OR' ), + ); + + $limit_schema = array( + 'type' => array( 'object', 'array' ), + 'oneOf' => array( + array( + 'title' => __( 'Term ID List' ), + 'description' => __( 'Match terms with the listed IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + ), + array( + 'title' => __( 'Term ID Taxonomy Query' ), + 'description' => __( 'Perform an advanced term query.' ), + 'type' => 'object', + 'properties' => array( + 'terms' => array( + 'description' => __( 'Term IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ), + 'include_children' => array( + 'description' => __( 'Whether to include child terms in the terms limiting the result set.' ), + 'type' => 'boolean', + 'default' => false, + ), + ), + 'additionalProperties' => false, + ), + ), + ); + + $include_schema = array_merge( + array( + /* translators: %s: Taxonomy name. */ + 'description' => __( 'Limit result set to items with specific terms assigned in the %s taxonomy.' ), + ), + $limit_schema + ); + // 'operator' is supported only for 'include' queries. + $include_schema['oneOf'][1]['properties']['operator'] = array( + 'description' => __( 'Whether items must be assigned all or any of the specified terms.' ), + 'type' => 'string', + 'enum' => array( 'AND', 'OR' ), + 'default' => 'OR', + ); + + $exclude_schema = array_merge( + array( + /* translators: %s: Taxonomy name. */ + 'description' => __( 'Limit result set to items except those with specific terms assigned in the %s taxonomy.' ), + ), + $limit_schema + ); + + foreach ( $taxonomies as $taxonomy ) { + $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; + $base_exclude = $base . '_exclude'; + + $query_params[ $base ] = $include_schema; + $query_params[ $base ]['description'] = sprintf( $query_params[ $base ]['description'], $base ); + + $query_params[ $base_exclude ] = $exclude_schema; + $query_params[ $base_exclude ]['description'] = sprintf( $query_params[ $base_exclude ]['description'], $base ); + + if ( ! $taxonomy->hierarchical ) { + unset( $query_params[ $base ]['oneOf'][1]['properties']['include_children'] ); + unset( $query_params[ $base_exclude ]['oneOf'][1]['properties']['include_children'] ); + } + } + + return $query_params; + } +} diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-server.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-server.php new file mode 100644 index 00000000000000..efdc41289dfac2 --- /dev/null +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-server.php @@ -0,0 +1,185 @@ +get_data(); + $links = static::get_compact_response_links( $response ); + + if ( ! empty( $links ) ) { + // Convert links to part of the data. + $data['_links'] = $links; + } + + if ( $embed ) { + $this->embed_cache = array(); + // Determine if this is a numeric array. + if ( wp_is_numeric_array( $data ) ) { + foreach ( $data as $key => $item ) { + $data[ $key ] = $this->embed_links( $item, $embed ); + } + } else { + $data = $this->embed_links( $data, $embed ); + } + $this->embed_cache = array(); + } + + return $data; + } + + /** + * Retrieves links from a response. + * + * Extracts the links from a response into a structured hash, suitable for + * direct output. + * + * @since 4.4.0 + * @since 6.7.0 The `targetHints` property to the `self` link object was added. + * + * @param WP_REST_Response $response Response to extract links from. + * @return array Map of link relation to list of link hashes. + */ + public static function get_response_links( $response ) { + $links = $response->get_links(); + + if ( empty( $links ) ) { + return array(); + } + + $server = rest_get_server(); + + // Convert links to part of the data. + $data = array(); + foreach ( $links as $rel => $items ) { + $data[ $rel ] = array(); + + foreach ( $items as $item ) { + $attributes = $item['attributes']; + $attributes['href'] = $item['href']; + + if ( 'self' !== $rel ) { + $data[ $rel ][] = $attributes; + continue; + } + + // Prefer targetHints that were specifically designated by the developer. + if ( isset( $attributes['targetHints']['allow'] ) ) { + $data[ $rel ][] = $attributes; + continue; + } + + $request = WP_REST_Request::from_url( $item['href'] ); + if ( ! $request ) { + $data[ $rel ][] = $attributes; + continue; + } + + $matched = $server->match_request_to_handler( $request ); + + if ( is_wp_error( $matched ) ) { + $data[ $rel ][] = $attributes; + continue; + } + + if ( is_wp_error( $request->has_valid_params() ) ) { + $data[ $rel ][] = $attributes; + continue; + } + + if ( is_wp_error( $request->sanitize_params() ) ) { + $data[ $rel ][] = $attributes; + continue; + } + + list( $route, $handler ) = $matched; + + $response = new WP_REST_Response(); + $response->set_matched_route( $route ); + $response->set_matched_handler( $handler ); + $headers = rest_send_allow_header( $response, $server, $request )->get_headers(); + + foreach ( $headers as $name => $value ) { + $name = WP_REST_Request::canonicalize_header_name( $name ); + $attributes['targetHints'][ $name ] = array_map( 'trim', explode( ',', $value ) ); + } + + $data[ $rel ][] = $attributes; + } + } + + return $data; + } + + /** + * Retrieves the CURIEs (compact URIs) used for relations. + * + * Extracts the links from a response into a structured hash, suitable for + * direct output. + * + * @since 4.5.0 + * + * @param WP_REST_Response $response Response to extract links from. + * @return array Map of link relation to list of link hashes. + */ + // @core-merge: Do not merge. The method is copied here to fix the inheritance issue. + public static function get_compact_response_links( $response ) { + $links = static::get_response_links( $response ); + + if ( empty( $links ) ) { + return array(); + } + + $curies = $response->get_curies(); + $used_curies = array(); + + foreach ( $links as $rel => $items ) { + + // Convert $rel URIs to their compact versions if they exist. + foreach ( $curies as $curie ) { + $href_prefix = substr( $curie['href'], 0, strpos( $curie['href'], '{rel}' ) ); + if ( ! str_starts_with( $rel, $href_prefix ) ) { + continue; + } + + // Relation now changes from '$uri' to '$curie:$relation'. + $rel_regex = str_replace( '\{rel\}', '(.+)', preg_quote( $curie['href'], '!' ) ); + preg_match( '!' . $rel_regex . '!', $rel, $matches ); + if ( $matches ) { + $new_rel = $curie['name'] . ':' . $matches[1]; + $used_curies[ $curie['name'] ] = $curie; + $links[ $new_rel ] = $items; + unset( $links[ $rel ] ); + break; + } + } + } + + // Push the curies onto the start of the links array. + if ( $used_curies ) { + $links['curies'] = array_values( $used_curies ); + } + + return $links; + } +} diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php new file mode 100644 index 00000000000000..ed67dded75ecb1 --- /dev/null +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -0,0 +1,203 @@ +post_type ); + } else { + $template = get_block_template( $request['id'], $this->post_type ); + } + + if ( ! $template ) { + return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); + } + + return $this->prepare_item_for_response( $template, $request ); + } + + /** + * Prepare a single template output for response + * + * @param WP_Block_Template $item Template instance. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + */ + // @core-merge: Fix wrong author in plugin templates. + public function prepare_item_for_response( $item, $request ) { + $template = $item; + + $fields = $this->get_fields_for_response( $request ); + + if ( 'plugin' !== $item->origin ) { + return parent::prepare_item_for_response( $item, $request ); + } + $cloned_item = clone $item; + // Set the origin as theme when calling the previous `prepare_item_for_response()` to prevent warnings when generating the author text. + $cloned_item->origin = 'theme'; + $response = parent::prepare_item_for_response( $cloned_item, $request ); + $data = $response->data; + + if ( rest_is_field_included( 'origin', $fields ) ) { + $data['origin'] = 'plugin'; + } + + if ( rest_is_field_included( 'plugin', $fields ) ) { + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $cloned_item->slug ); + if ( $registered_template ) { + $data['plugin'] = $registered_template->plugin; + } + } + + if ( rest_is_field_included( 'author_text', $fields ) ) { + $data['author_text'] = $this->get_wp_templates_author_text_field( $template ); + } + + if ( rest_is_field_included( 'original_source', $fields ) ) { + $data['original_source'] = $this->get_wp_templates_original_source_field( $template ); + } + + $response = rest_ensure_response( $data ); + + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { + $links = $this->prepare_links( $template->id ); + $response->add_links( $links ); + if ( ! empty( $links['self']['href'] ) ) { + $actions = $this->get_available_actions(); + $self = $links['self']['href']; + foreach ( $actions as $rel ) { + $response->add_link( $rel, $self ); + } + } + } + + return $response; + } + + /** + * Returns the source from where the template originally comes from. + * + * @param WP_Block_Template $template_object Template instance. + * @return string Original source of the template one of theme, plugin, site, or user. + */ + // @core-merge: Changed the comments format (from inline to multi-line) in the entire function. + private static function get_wp_templates_original_source_field( $template_object ) { + if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { + /* + * Added by theme. + * Template originally provided by a theme, but customized by a user. + * Templates originally didn't have the 'origin' field so identify + * older customized templates by checking for no origin and a 'theme' + * or 'custom' source. + */ + if ( $template_object->has_theme_file && + ( 'theme' === $template_object->origin || ( + empty( $template_object->origin ) && in_array( + $template_object->source, + array( + 'theme', + 'custom', + ), + true + ) ) + ) + ) { + return 'theme'; + } + + // Added by plugin. + // @core-merge: Removed `$template_object->has_theme_file` check from this if clause. + if ( 'plugin' === $template_object->origin ) { + return 'plugin'; + } + + /* + * Added by site. + * Template was created from scratch, but has no author. Author support + * was only added to templates in WordPress 5.9. Fallback to showing the + * site logo and title. + */ + if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) { + return 'site'; + } + } + + // Added by user. + return 'user'; + } + + /** + * Returns a human readable text for the author of the template. + * + * @param WP_Block_Template $template_object Template instance. + * @return string Human readable text for the author. + */ + private static function get_wp_templates_author_text_field( $template_object ) { + $original_source = self::get_wp_templates_original_source_field( $template_object ); + switch ( $original_source ) { + case 'theme': + $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' ); + return empty( $theme_name ) ? $template_object->theme : $theme_name; + case 'plugin': + // @core-merge: Prioritize plugin name instead of theme name for plugin-registered templates. + if ( ! function_exists( 'get_plugins' ) || ! function_exists( 'get_plugin_data' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + if ( isset( $template_object->plugin ) ) { + $plugins = wp_get_active_and_valid_plugins(); + + foreach ( $plugins as $plugin_file ) { + $plugin_basename = plugin_basename( $plugin_file ); + // Split basename by '/' to get the plugin slug. + list( $plugin_slug, ) = explode( '/', $plugin_basename ); + + if ( $plugin_slug === $template_object->plugin ) { + $plugin_data = get_plugin_data( $plugin_file ); + + if ( ! empty( $plugin_data['Name'] ) ) { + return $plugin_data['Name']; + } + + break; + } + } + } + + /* + * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards + * compatibility with templates that were registered before the plugin attribute was added. + */ + $plugins = get_plugins(); + $plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ); + if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) { + return $plugins[ $plugin_basename ]['Name']; + } + return isset( $template_object->plugin ) ? + $template_object->plugin : + $template_object->theme; + // @core-merge: End of changes to merge in core. + case 'site': + return get_bloginfo( 'name' ); + case 'user': + $author = get_user_by( 'id', $template_object->author ); + if ( ! $author ) { + return __( 'Unknown author' ); + } + return $author->get( 'display_name' ); + } + } +} diff --git a/lib/compat/wordpress-6.7/class-gutenberg-token-map-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-token-map-6-7.php new file mode 100644 index 00000000000000..a2142171ddc14f --- /dev/null +++ b/lib/compat/wordpress-6.7/class-gutenberg-token-map-6-7.php @@ -0,0 +1,820 @@ + 'šŸ˜Æ', + * ':(' => 'šŸ™', + * ':)' => 'šŸ™‚', + * ':?' => 'šŸ˜•', + * ) ); + * + * true === $smilies->contains( ':)' ); + * false === $smilies->contains( 'simile' ); + * + * 'šŸ˜•' === $smilies->read_token( 'Not sure :?.', 9, $length_of_smily_syntax ); + * 2 === $length_of_smily_syntax; + * + * ## Precomputing the Token Map. + * + * Creating the class involves some work sorting and organizing the tokens and their + * replacement values. In order to skip this, it's possible for the class to export + * its state and be used as actual PHP source code. + * + * Example: + * + * // Export with four spaces as the indent, only for the sake of this docblock. + * // The default indent is a tab character. + * $indent = ' '; + * echo $smilies->precomputed_php_source_table( $indent ); + * + * // Output, to be pasted into a PHP source file: + * WP_Token_Map::from_precomputed_table( + * array( + * "storage_version" => "6.6.0", + * "key_length" => 2, + * "groups" => "", + * "long_words" => array(), + * "small_words" => "8O\x00:)\x00:(\x00:?\x00", + * "small_mappings" => array( "šŸ˜Æ", "šŸ™‚", "šŸ™", "šŸ˜•" ) + * ) + * ); + * + * ## Large vs. small words. + * + * This class uses a short prefix called the "key" to optimize lookup of its tokens. + * This means that some tokens may be shorter than or equal in length to that key. + * Those words that are longer than the key are called "large" while those shorter + * than or equal to the key length are called "small." + * + * This separation of large and small words is incidental to the way this class + * optimizes lookup, and should be considered an internal implementation detail + * of the class. It may still be important to be aware of it, however. + * + * ## Determining Key Length. + * + * The choice of the size of the key length should be based on the data being stored in + * the token map. It should divide the data as evenly as possible, but should not create + * so many groups that a large fraction of the groups only contain a single token. + * + * For the HTML5 named character references, a key length of 2 was found to provide a + * sufficient spread and should be a good default for relatively large sets of tokens. + * + * However, for some data sets this might be too long. For example, a list of smilies + * may be too small for a key length of 2. Perhaps 1 would be more appropriate. It's + * best to experiment and determine empirically which values are appropriate. + * + * ## Generate Pre-Computed Source Code. + * + * Since the `WP_Token_Map` is designed for relatively static lookups, it can be + * advantageous to precompute the values and instantiate a table that has already + * sorted and grouped the tokens and built the lookup strings. + * + * This can be done with `WP_Token_Map::precomputed_php_source_table()`. + * + * Note that if there is a leading character that all tokens need, such as `&` for + * HTML named character references, it can be beneficial to exclude this from the + * token map. Instead, find occurrences of the leading character and then use the + * token map to see if the following characters complete the token. + * + * Example: + * + * $map = WP_Token_Map::from_array( array( 'simple_smile:' => 'šŸ™‚', 'sob:' => 'šŸ˜­', 'soba:' => 'šŸœ' ) ); + * echo $map->precomputed_php_source_table(); + * // Output + * WP_Token_Map::from_precomputed_table( + * array( + * "storage_version" => "6.6.0", + * "key_length" => 2, + * "groups" => "si\x00so\x00", + * "long_words" => array( + * // simple_smile:[šŸ™‚]. + * "\x0bmple_smile:\x04šŸ™‚", + * // soba:[šŸœ] sob:[šŸ˜­]. + * "\x03ba:\x04šŸœ\x02b:\x04šŸ˜­", + * ), + * "short_words" => "", + * "short_mappings" => array() + * } + * ); + * + * This precomputed value can be stored directly in source code and will skip the + * startup cost of generating the lookup strings. See `$html5_named_character_entities`. + * + * Note that any updates to the precomputed format should update the storage version + * constant. It would also be best to provide an update function to take older known + * versions and upgrade them in place when loading into `from_precomputed_table()`. + * + * ## Future Direction. + * + * It may be viable to dynamically increase the length limits such that there's no need to impose them. + * The limit appears because of the packing structure, which indicates how many bytes each segment of + * text in the lookup tables spans. If, however, care were taken to track the longest word length, then + * the packing structure could change its representation to allow for that. Each additional byte storing + * length, however, increases the memory overhead and lookup runtime. + * + * An alternative approach could be to borrow the UTF-8 variable-length encoding and store lengths of less + * than 127 as a single byte with the high bit unset, storing longer lengths as the combination of + * continuation bytes. + * + * Since it has not been shown during the development of this class that longer strings are required, this + * update is deferred until such a need is clear. + * + * @since 6.6.0 + */ +class Gutenberg_Token_Map_6_7 { + /** + * Denotes the version of the code which produces pre-computed source tables. + * + * This version will be used not only to verify pre-computed data, but also + * to upgrade pre-computed data from older versions. Choosing a name that + * corresponds to the WordPress release will help people identify where an + * old copy of data came from. + */ + const STORAGE_VERSION = '6.6.0-trunk'; + + /** + * Maximum length for each key and each transformed value in the table (in bytes). + * + * @since 6.6.0 + */ + const MAX_LENGTH = 256; + + /** + * How many bytes of each key are used to form a group key for lookup. + * This also determines whether a word is considered short or long. + * + * @since 6.6.0 + * + * @var int + */ + private $key_length = 2; + + /** + * Stores an optimized form of the word set, where words are grouped + * by a prefix of the `$key_length` and then collapsed into a string. + * + * In each group, the keys and lookups form a packed data structure. + * The keys in the string are stripped of their "group key," which is + * the prefix of length `$this->key_length` shared by all of the items + * in the group. Each word in the string is prefixed by a single byte + * whose raw unsigned integer value represents how many bytes follow. + * + * ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” + * ā”‚ Length of rest ā”‚ Rest of key ā”‚ Length of value ā”‚ Value ā”‚ + * ā”‚ of key (bytes) ā”‚ ā”‚ (bytes) ā”‚ ā”‚ + * ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ + * ā”‚ 0x08 ā”‚ nterDot; ā”‚ 0x02 ā”‚ Ā· ā”‚ + * ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + * + * In this example, the key `CenterDot;` has a group key `Ce`, leaving + * eight bytes for the rest of the key, `nterDot;`, and two bytes for + * the transformed value `Ā·` (or U+B7 or "\xC2\xB7"). + * + * Example: + * + * // Stores array( 'CenterDot;' => 'Ā·', 'Cedilla;' => 'Āø' ). + * $groups = "Ce\x00"; + * $large_words = array( "\x08nterDot;\x02Ā·\x06dilla;\x02Āø" ) + * + * The prefixes appear in the `$groups` string, each followed by a null + * byte. This makes for quick lookup of where in the group string the key + * is found, and then a simple division converts that offset into the index + * in the `$large_words` array where the group string is to be found. + * + * This lookup data structure is designed to optimize cache locality and + * minimize indirect memory reads when matching strings in the set. + * + * @since 6.6.0 + * + * @var array + */ + private $large_words = array(); + + /** + * Stores the group keys for sequential string lookup. + * + * The offset into this string where the group key appears corresponds with the index + * into the group array where the rest of the group string appears. This is an optimization + * to improve cache locality while searching and minimize indirect memory accesses. + * + * @since 6.6.0 + * + * @var string + */ + private $groups = ''; + + /** + * Stores an optimized row of small words, where every entry is + * `$this->key_size + 1` bytes long and zero-extended. + * + * This packing allows for direct lookup of a short word followed + * by the null byte, if extended to `$this->key_size + 1`. + * + * Example: + * + * // Stores array( 'GT', 'LT', 'gt', 'lt' ). + * "GT\x00LT\x00gt\x00lt\x00" + * + * @since 6.6.0 + * + * @var string + */ + private $small_words = ''; + + /** + * Replacements for the small words, in the same order they appear. + * + * With the position of a small word it's possible to index the translation + * directly, as its position in the `$small_words` string corresponds to + * the index of the replacement in the `$small_mapping` array. + * + * Example: + * + * array( '>', '<', '>', '<' ) + * + * @since 6.6.0 + * + * @var string[] + */ + private $small_mappings = array(); + + /** + * Create a token map using an associative array of key/value pairs as the input. + * + * Example: + * + * $smilies = WP_Token_Map::from_array( array( + * '8O' => 'šŸ˜Æ', + * ':(' => 'šŸ™', + * ':)' => 'šŸ™‚', + * ':?' => 'šŸ˜•', + * ) ); + * + * @since 6.6.0 + * + * @param array $mappings The keys transform into the values, both are strings. + * @param int $key_length Determines the group key length. Leave at the default value + * of 2 unless there's an empirical reason to change it. + * + * @return WP_Token_Map|null Token map, unless unable to create it. + */ + public static function from_array( array $mappings, int $key_length = 2 ): ?WP_Token_Map { + $map = new WP_Token_Map(); + $map->key_length = $key_length; + + // Start by grouping words. + + $groups = array(); + $shorts = array(); + foreach ( $mappings as $word => $mapping ) { + if ( + self::MAX_LENGTH <= strlen( $word ) || + self::MAX_LENGTH <= strlen( $mapping ) + ) { + _doing_it_wrong( + __METHOD__, + sprintf( + /* translators: 1: maximum byte length (a count) */ + __( 'Token Map tokens and substitutions must all be shorter than %1$d bytes.' ), + self::MAX_LENGTH + ), + '6.6.0' + ); + return null; + } + + $length = strlen( $word ); + + if ( $key_length >= $length ) { + $shorts[] = $word; + } else { + $group = substr( $word, 0, $key_length ); + + if ( ! isset( $groups[ $group ] ) ) { + $groups[ $group ] = array(); + } + + $groups[ $group ][] = array( substr( $word, $key_length ), $mapping ); + } + } + + /* + * Sort the words to ensure that no smaller substring of a match masks the full match. + * For example, `Cap` should not match before `CapitalDifferentialD`. + */ + usort( $shorts, 'WP_Token_Map::longest_first_then_alphabetical' ); + foreach ( $groups as $group_key => $group ) { + usort( + $groups[ $group_key ], + static function ( array $a, array $b ): int { + return self::longest_first_then_alphabetical( $a[0], $b[0] ); + } + ); + } + + // Finally construct the optimized lookups. + + foreach ( $shorts as $word ) { + $map->small_words .= str_pad( $word, $key_length + 1, "\x00", STR_PAD_RIGHT ); + $map->small_mappings[] = $mappings[ $word ]; + } + + $group_keys = array_keys( $groups ); + sort( $group_keys ); + + foreach ( $group_keys as $group ) { + $map->groups .= "{$group}\x00"; + + $group_string = ''; + + foreach ( $groups[ $group ] as $group_word ) { + list( $word, $mapping ) = $group_word; + + $word_length = pack( 'C', strlen( $word ) ); + $mapping_length = pack( 'C', strlen( $mapping ) ); + $group_string .= "{$word_length}{$word}{$mapping_length}{$mapping}"; + } + + $map->large_words[] = $group_string; + } + + return $map; + } + + /** + * Creates a token map from a pre-computed table. + * This skips the initialization cost of generating the table. + * + * This function should only be used to load data created with + * WP_Token_Map::precomputed_php_source_tag(). + * + * @since 6.6.0 + * + * @param array $state { + * Stores pre-computed state for directly loading into a Token Map. + * + * @type string $storage_version Which version of the code produced this state. + * @type int $key_length Group key length. + * @type string $groups Group lookup index. + * @type array $large_words Large word groups and packed strings. + * @type string $small_words Small words packed string. + * @type array $small_mappings Small word mappings. + * } + * + * @return WP_Token_Map Map with precomputed data loaded. + */ + public static function from_precomputed_table( $state ): ?WP_Token_Map { + $has_necessary_state = isset( + $state['storage_version'], + $state['key_length'], + $state['groups'], + $state['large_words'], + $state['small_words'], + $state['small_mappings'] + ); + + if ( ! $has_necessary_state ) { + _doing_it_wrong( + __METHOD__, + __( 'Missing required inputs to pre-computed WP_Token_Map.' ), + '6.6.0' + ); + return null; + } + + if ( self::STORAGE_VERSION !== $state['storage_version'] ) { + _doing_it_wrong( + __METHOD__, + /* translators: 1: version string, 2: version string. */ + sprintf( __( 'Loaded version \'%1$s\' incompatible with expected version \'%2$s\'.' ), $state['storage_version'], self::STORAGE_VERSION ), + '6.6.0' + ); + return null; + } + + $map = new WP_Token_Map(); + + $map->key_length = $state['key_length']; + $map->groups = $state['groups']; + $map->large_words = $state['large_words']; + $map->small_words = $state['small_words']; + $map->small_mappings = $state['small_mappings']; + + return $map; + } + + /** + * Indicates if a given word is a lookup key in the map. + * + * Example: + * + * true === $smilies->contains( ':)' ); + * false === $smilies->contains( 'simile' ); + * + * @since 6.6.0 + * + * @param string $word Determine if this word is a lookup key in the map. + * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. + * @return bool Whether there's an entry for the given word in the map. + */ + public function contains( string $word, string $case_sensitivity = 'case-sensitive' ): bool { + $ignore_case = 'ascii-case-insensitive' === $case_sensitivity; + + if ( $this->key_length >= strlen( $word ) ) { + if ( 0 === strlen( $this->small_words ) ) { + return false; + } + + $term = str_pad( $word, $this->key_length + 1, "\x00", STR_PAD_RIGHT ); + $word_at = $ignore_case ? stripos( $this->small_words, $term ) : strpos( $this->small_words, $term ); + if ( false === $word_at ) { + return false; + } + + return true; + } + + $group_key = substr( $word, 0, $this->key_length ); + $group_at = $ignore_case ? stripos( $this->groups, $group_key ) : strpos( $this->groups, $group_key ); + if ( false === $group_at ) { + return false; + } + $group = $this->large_words[ $group_at / ( $this->key_length + 1 ) ]; + $group_length = strlen( $group ); + $slug = substr( $word, $this->key_length ); + $length = strlen( $slug ); + $at = 0; + + while ( $at < $group_length ) { + $token_length = unpack( 'C', $group[ $at++ ] )[1]; + $token_at = $at; + $at += $token_length; + $mapping_length = unpack( 'C', $group[ $at++ ] )[1]; + $mapping_at = $at; + + if ( $token_length === $length && 0 === substr_compare( $group, $slug, $token_at, $token_length, $ignore_case ) ) { + return true; + } + + $at = $mapping_at + $mapping_length; + } + + return false; + } + + /** + * If the text starting at a given offset is a lookup key in the map, + * return the corresponding transformation from the map, else `false`. + * + * This function returns the translated string, but accepts an optional + * parameter `$matched_token_byte_length`, which communicates how many + * bytes long the lookup key was, if it found one. This can be used to + * advance a cursor in calling code if a lookup key was found. + * + * Example: + * + * false === $smilies->read_token( 'Not sure :?.', 0, $token_byte_length ); + * 'šŸ˜•' === $smilies->read_token( 'Not sure :?.', 9, $token_byte_length ); + * 2 === $token_byte_length; + * + * Example: + * + * while ( $at < strlen( $input ) ) { + * $next_at = strpos( $input, ':', $at ); + * if ( false === $next_at ) { + * break; + * } + * + * $smily = $smilies->read_token( $input, $next_at, $token_byte_length ); + * if ( false === $next_at ) { + * ++$at; + * continue; + * } + * + * $prefix = substr( $input, $at, $next_at - $at ); + * $at += $token_byte_length; + * $output .= "{$prefix}{$smily}"; + * } + * + * @since 6.6.0 + * + * @param string $text String in which to search for a lookup key. + * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. + * @param int|null &$matched_token_byte_length Optional. Holds byte-length of found token matched, otherwise not set. Default null. + * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. + * + * @return string|null Mapped value of lookup key if found, otherwise `null`. + */ + public function read_token( string $text, int $offset = 0, &$matched_token_byte_length = null, $case_sensitivity = 'case-sensitive' ): ?string { + $ignore_case = 'ascii-case-insensitive' === $case_sensitivity; + $text_length = strlen( $text ); + + // Search for a long word first, if the text is long enough, and if that fails, a short one. + if ( $text_length > $this->key_length ) { + $group_key = substr( $text, $offset, $this->key_length ); + + $group_at = $ignore_case ? stripos( $this->groups, $group_key ) : strpos( $this->groups, $group_key ); + if ( false === $group_at ) { + // Perhaps a short word then. + return strlen( $this->small_words ) > 0 + ? $this->read_small_token( $text, $offset, $matched_token_byte_length, $case_sensitivity ) + : null; + } + + $group = $this->large_words[ $group_at / ( $this->key_length + 1 ) ]; + $group_length = strlen( $group ); + $at = 0; + while ( $at < $group_length ) { + $token_length = unpack( 'C', $group[ $at++ ] )[1]; + $token = substr( $group, $at, $token_length ); + $at += $token_length; + $mapping_length = unpack( 'C', $group[ $at++ ] )[1]; + $mapping_at = $at; + + if ( 0 === substr_compare( $text, $token, $offset + $this->key_length, $token_length, $ignore_case ) ) { + $matched_token_byte_length = $this->key_length + $token_length; + return substr( $group, $mapping_at, $mapping_length ); + } + + $at = $mapping_at + $mapping_length; + } + } + + // Perhaps a short word then. + return strlen( $this->small_words ) > 0 + ? $this->read_small_token( $text, $offset, $matched_token_byte_length, $case_sensitivity ) + : null; + } + + /** + * Finds a match for a short word at the index. + * + * @since 6.6.0 + * + * @param string $text String in which to search for a lookup key. + * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. + * @param int|null &$matched_token_byte_length Optional. Holds byte-length of found lookup key if matched, otherwise not set. Default null. + * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. + * + * @return string|null Mapped value of lookup key if found, otherwise `null`. + */ + private function read_small_token( string $text, int $offset = 0, &$matched_token_byte_length = null, $case_sensitivity = 'case-sensitive' ): ?string { + $ignore_case = 'ascii-case-insensitive' === $case_sensitivity; + $small_length = strlen( $this->small_words ); + $search_text = substr( $text, $offset, $this->key_length ); + if ( $ignore_case ) { + $search_text = strtoupper( $search_text ); + } + $starting_char = $search_text[0]; + + $at = 0; + while ( $at < $small_length ) { + if ( + $starting_char !== $this->small_words[ $at ] && + ( ! $ignore_case || strtoupper( $this->small_words[ $at ] ) !== $starting_char ) + ) { + $at += $this->key_length + 1; + continue; + } + + for ( $adjust = 1; $adjust < $this->key_length; $adjust++ ) { + if ( "\x00" === $this->small_words[ $at + $adjust ] ) { + $matched_token_byte_length = $adjust; + return $this->small_mappings[ $at / ( $this->key_length + 1 ) ]; + } + + if ( + $search_text[ $adjust ] !== $this->small_words[ $at + $adjust ] && + ( ! $ignore_case || strtoupper( $this->small_words[ $at + $adjust ] !== $search_text[ $adjust ] ) ) + ) { + $at += $this->key_length + 1; + continue 2; + } + } + + $matched_token_byte_length = $adjust; + return $this->small_mappings[ $at / ( $this->key_length + 1 ) ]; + } + + return null; + } + + /** + * Exports the token map into an associate array of key/value pairs. + * + * Example: + * + * $smilies->to_array() === array( + * '8O' => 'šŸ˜Æ', + * ':(' => 'šŸ™', + * ':)' => 'šŸ™‚', + * ':?' => 'šŸ˜•', + * ); + * + * @return array The lookup key/substitution values as an associate array. + */ + public function to_array(): array { + $tokens = array(); + + $at = 0; + $small_mapping = 0; + $small_length = strlen( $this->small_words ); + while ( $at < $small_length ) { + $key = rtrim( substr( $this->small_words, $at, $this->key_length + 1 ), "\x00" ); + $value = $this->small_mappings[ $small_mapping++ ]; + $tokens[ $key ] = $value; + + $at += $this->key_length + 1; + } + + foreach ( $this->large_words as $index => $group ) { + $prefix = substr( $this->groups, $index * ( $this->key_length + 1 ), 2 ); + $group_length = strlen( $group ); + $at = 0; + while ( $at < $group_length ) { + $length = unpack( 'C', $group[ $at++ ] )[1]; + $key = $prefix . substr( $group, $at, $length ); + + $at += $length; + $length = unpack( 'C', $group[ $at++ ] )[1]; + $value = substr( $group, $at, $length ); + + $tokens[ $key ] = $value; + $at += $length; + } + } + + return $tokens; + } + + /** + * Export the token map for quick loading in PHP source code. + * + * This function has a specific purpose, to make loading of static token maps fast. + * It's used to ensure that the HTML character reference lookups add a minimal cost + * to initializing the PHP process. + * + * Example: + * + * echo $smilies->precomputed_php_source_table(); + * + * // Output. + * WP_Token_Map::from_precomputed_table( + * array( + * "storage_version" => "6.6.0", + * "key_length" => 2, + * "groups" => "", + * "long_words" => array(), + * "small_words" => "8O\x00:)\x00:(\x00:?\x00", + * "small_mappings" => array( "šŸ˜Æ", "šŸ™‚", "šŸ™", "šŸ˜•" ) + * ) + * ); + * + * @since 6.6.0 + * + * @param string $indent Optional. Use this string for indentation, or rely on the default horizontal tab character. Default "\t". + * @return string Value which can be pasted into a PHP source file for quick loading of table. + */ + public function precomputed_php_source_table( string $indent = "\t" ): string { + $i1 = $indent; + $i2 = $i1 . $indent; + $i3 = $i2 . $indent; + + $class_version = self::STORAGE_VERSION; + + $output = self::class . "::from_precomputed_table(\n"; + $output .= "{$i1}array(\n"; + $output .= "{$i2}\"storage_version\" => \"{$class_version}\",\n"; + $output .= "{$i2}\"key_length\" => {$this->key_length},\n"; + + $group_line = str_replace( "\x00", "\\x00", $this->groups ); + $output .= "{$i2}\"groups\" => \"{$group_line}\",\n"; + + $output .= "{$i2}\"large_words\" => array(\n"; + + $prefixes = explode( "\x00", $this->groups ); + foreach ( $prefixes as $index => $prefix ) { + if ( '' === $prefix ) { + break; + } + $group = $this->large_words[ $index ]; + $group_length = strlen( $group ); + $comment_line = "{$i3}//"; + $data_line = "{$i3}\""; + $at = 0; + while ( $at < $group_length ) { + $token_length = unpack( 'C', $group[ $at++ ] )[1]; + $token = substr( $group, $at, $token_length ); + $at += $token_length; + $mapping_length = unpack( 'C', $group[ $at++ ] )[1]; + $mapping = substr( $group, $at, $mapping_length ); + $at += $mapping_length; + + $token_digits = str_pad( dechex( $token_length ), 2, '0', STR_PAD_LEFT ); + $mapping_digits = str_pad( dechex( $mapping_length ), 2, '0', STR_PAD_LEFT ); + + $mapping = preg_replace_callback( + "~[\\x00-\\x1f\\x22\\x5c]~", + static function ( $match_result ) { + switch ( $match_result[0] ) { + case '"': + return '\\"'; + + case '\\': + return '\\\\'; + + default: + $hex = dechex( ord( $match_result[0] ) ); + return "\\x{$hex}"; + } + }, + $mapping + ); + + $comment_line .= " {$prefix}{$token}[{$mapping}]"; + $data_line .= "\\x{$token_digits}{$token}\\x{$mapping_digits}{$mapping}"; + } + $comment_line .= ".\n"; + $data_line .= "\",\n"; + + $output .= $comment_line; + $output .= $data_line; + } + + $output .= "{$i2}),\n"; + + $small_words = array(); + $small_length = strlen( $this->small_words ); + $at = 0; + while ( $at < $small_length ) { + $small_words[] = substr( $this->small_words, $at, $this->key_length + 1 ); + $at += $this->key_length + 1; + } + + $small_text = str_replace( "\x00", '\x00', implode( '', $small_words ) ); + $output .= "{$i2}\"small_words\" => \"{$small_text}\",\n"; + + $output .= "{$i2}\"small_mappings\" => array(\n"; + foreach ( $this->small_mappings as $mapping ) { + $output .= "{$i3}\"{$mapping}\",\n"; + } + $output .= "{$i2})\n"; + $output .= "{$i1})\n"; + $output .= ')'; + + return $output; + } + + /** + * Compares two strings, returning the longest, or whichever + * is first alphabetically if they are the same length. + * + * This is an important sort when building the token map because + * it should not form a match on a substring of a longer potential + * match. For example, it should not detect `Cap` when matching + * against the string `CapitalDifferentialD`. + * + * @since 6.6.0 + * + * @param string $a First string to compare. + * @param string $b Second string to compare. + * @return int -1 or lower if `$a` is less than `$b`; 1 or greater if `$a` is greater than `$b`, and 0 if they are equal. + */ + private static function longest_first_then_alphabetical( string $a, string $b ): int { + if ( $a === $b ) { + return 0; + } + + $length_a = strlen( $a ); + $length_b = strlen( $b ); + + // Longer strings are less-than for comparison's sake. + if ( $length_a !== $length_b ) { + return $length_b - $length_a; + } + + return strcmp( $a, $b ); + } +} diff --git a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php new file mode 100644 index 00000000000000..92673c0bf50f90 --- /dev/null +++ b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php @@ -0,0 +1,256 @@ + $instance` pairs. + * + * @since 6.7.0 + * @var WP_Block_Template[] $registered_block_templates Registered templates. + */ + private $registered_templates = array(); + + /** + * Container for the main instance of the class. + * + * @since 6.7.0 + * @var WP_Block_Templates_Registry|null + */ + private static $instance = null; + + /** + * Registers a template. + * + * @since 6.7.0 + * + * @param string $template_name Template name including namespace. + * @param array $args Optional. Array of template arguments. + * @return WP_Block_Template|WP_Error The registered template on success, or WP_Error on failure. + */ + public function register( $template_name, $args = array() ) { + + $template = null; + + $error_message = ''; + $error_code = ''; + if ( ! is_string( $template_name ) ) { + $error_message = __( 'Template names must be strings.', 'gutenberg' ); + $error_code = 'template_name_no_string'; + } elseif ( preg_match( '/[A-Z]+/', $template_name ) ) { + $error_message = __( 'Template names must not contain uppercase characters.', 'gutenberg' ); + $error_code = 'template_name_no_uppercase'; + } elseif ( ! preg_match( '/^[a-z0-9-]+\/\/[a-z0-9-]+$/', $template_name ) ) { + $error_message = __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ); + $error_code = 'template_no_prefix'; + } elseif ( $this->is_registered( $template_name ) ) { + /* translators: %s: Template name. */ + $error_message = sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name ); + $error_code = 'template_already_registered'; + } + + if ( $error_message ) { + _doing_it_wrong( + __METHOD__, + $error_message, + '6.7.0' + ); + return new WP_Error( $error_code, $error_message ); + } + + if ( ! $template ) { + $theme_name = get_stylesheet(); + list( $plugin, $slug ) = explode( '//', $template_name ); + $default_template_types = get_default_block_template_types(); + + $template = new WP_Block_Template(); + $template->id = $theme_name . '//' . $slug; + $template->theme = $theme_name; + $template->plugin = $plugin; + $template->author = null; + $template->content = isset( $args['content'] ) ? $args['content'] : ''; + $template->source = 'plugin'; + $template->slug = $slug; + $template->type = 'wp_template'; + $template->title = isset( $args['title'] ) ? $args['title'] : $template_name; + $template->description = isset( $args['description'] ) ? $args['description'] : ''; + $template->status = 'publish'; + $template->origin = 'plugin'; + $template->is_custom = ! isset( $default_template_types[ $template_name ] ); + $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : array(); + } + + $this->registered_templates[ $template_name ] = $template; + + return $template; + } + + /** + * Retrieves all registered templates. + * + * @since 6.7.0 + * + * @return WP_Block_Template[] Associative array of `$template_name => $template` pairs. + */ + public function get_all_registered() { + return $this->registered_templates; + } + + /** + * Retrieves a registered template by its name. + * + * @since 6.7.0 + * + * @param string $template_name Template name including namespace. + * @return WP_Block_Template|null The registered template, or null if it is not registered. + */ + public function get_registered( $template_name ) { + if ( ! $this->is_registered( $template_name ) ) { + return null; + } + + return $this->registered_templates[ $template_name ]; + } + + /** + * Retrieves a registered template by its slug. + * + * @since 6.7.0 + * + * @param string $template_slug Slug of the template. + * @return WP_Block_Template|null The registered template, or null if it is not registered. + */ + public function get_by_slug( $template_slug ) { + $all_templates = $this->get_all_registered(); + + if ( ! $all_templates ) { + return null; + } + + foreach ( $all_templates as $template ) { + if ( $template->slug === $template_slug ) { + return $template; + } + } + + return null; + } + + /** + * Retrieves registered templates matching a query. + * + * @since 6.7.0 + * + * @param array $query { + * Arguments to retrieve templates. Optional, empty by default. + * + * @type string[] $slug__in List of slugs to include. + * @type string[] $slug__not_in List of slugs to skip. + * @type string $post_type Post type to get the templates for. + * } + */ + public function get_by_query( $query = array() ) { + $all_templates = $this->get_all_registered(); + + if ( ! $all_templates ) { + return array(); + } + + $query = wp_parse_args( + $query, + array( + 'slug__in' => array(), + 'slug__not_in' => array(), + 'post_type' => '', + ) + ); + $slugs_to_include = $query['slug__in']; + $slugs_to_skip = $query['slug__not_in']; + $post_type = $query['post_type']; + + $matching_templates = array(); + foreach ( $all_templates as $template_name => $template ) { + if ( $slugs_to_include && ! in_array( $template->slug, $slugs_to_include, true ) ) { + continue; + } + + if ( $slugs_to_skip && in_array( $template->slug, $slugs_to_skip, true ) ) { + continue; + } + + if ( $post_type && ! in_array( $post_type, $template->post_types, true ) ) { + continue; + } + + $matching_templates[ $template_name ] = $template; + } + + return $matching_templates; + } + + /** + * Checks if a template is registered. + * + * @since 6.7.0 + * + * @param string $template_name Template name. + * @return bool True if the template is registered, false otherwise. + */ + public function is_registered( $template_name ) { + return isset( $this->registered_templates[ $template_name ] ); + } + + /** + * Unregisters a template. + * + * @since 6.7.0 + * + * @param string $template_name Template name including namespace. + * @return WP_Block_Template|WP_Error The unregistered template on success, or WP_Error on failure. + */ + public function unregister( $template_name ) { + if ( ! $this->is_registered( $template_name ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Template name. */ + sprintf( __( 'Template "%s" is not registered.', 'gutenberg' ), $template_name ), + '6.7.0' + ); + /* translators: %s: Template name. */ + return new WP_Error( 'template_not_registered', __( 'Template "%s" is not registered.', 'gutenberg' ) ); + } + + $unregistered_template = $this->registered_templates[ $template_name ]; + unset( $this->registered_templates[ $template_name ] ); + + return $unregistered_template; + } + + /** + * Utility method to retrieve the main instance of the class. + * + * The instance will be created if it does not exist yet. + * + * @since 6.7.0 + * + * @return WP_Block_Templates_Registry The main instance. + */ + public static function get_instance() { + if ( null === self::$instance ) { + self::$instance = new self(); + } + + return self::$instance; + } + } +} diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php new file mode 100644 index 00000000000000..cd533a42cc528e --- /dev/null +++ b/lib/compat/wordpress-6.7/compat.php @@ -0,0 +1,120 @@ + $value ) { + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $query_result[ $key ]->slug ); + if ( $registered_template ) { + $query_result[ $key ]->plugin = $registered_template->plugin; + $query_result[ $key ]->origin = + 'theme' !== $query_result[ $key ]->origin && 'theme' !== $query_result[ $key ]->source ? + 'plugin' : + $query_result[ $key ]->origin; + $query_result[ $key ]->title = + empty( $query_result[ $key ]->title ) || $query_result[ $key ]->title === $query_result[ $key ]->slug ? + $registered_template->title : $query_result[ $key ]->title; + $query_result[ $key ]->description = empty( $query_result[ $key ]->description ) ? $registered_template->description : $query_result[ $key ]->description; + } + } + + if ( ! isset( $query['wp_id'] ) ) { + $template_files = _gutenberg_get_block_templates_files( $template_type, $query ); + + /* + * Add templates registered in the template registry. Filtering out the ones which have a theme file. + */ + $registered_templates = WP_Block_Templates_Registry::get_instance()->get_by_query( $query ); + $matching_registered_templates = array_filter( + $registered_templates, + function ( $registered_template ) use ( $template_files ) { + foreach ( $template_files as $template_file ) { + if ( $template_file['slug'] === $registered_template->slug ) { + return false; + } + } + return true; + } + ); + $query_result = array_merge( $query_result, $matching_registered_templates ); + } + + return $query_result; +} +add_filter( 'get_block_templates', '_gutenberg_add_block_templates_from_registry', 10, 3 ); + +/** + * Hooks into `get_block_template` to add the `plugin` property when necessary. + * + * @param [WP_Block_Template|null] $block_template The found block template, or null if there isnā€™t one. + * @return [WP_Block_Template|null] The block template that was already found with the plugin property defined if it was registered by a plugin. + */ +function _gutenberg_add_block_template_plugin_attribute( $block_template ) { + if ( $block_template ) { + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); + if ( $registered_template ) { + $block_template->plugin = $registered_template->plugin; + $block_template->origin = + 'theme' !== $block_template->origin && 'theme' !== $block_template->source ? + 'plugin' : + $block_template->origin; + $block_template->title = empty( $block_template->title ) || $block_template->title === $block_template->slug ? $registered_template->title : $block_template->title; + $block_template->description = empty( $block_template->description ) ? $registered_template->description : $block_template->description; + } + } + + return $block_template; +} +add_filter( 'get_block_template', '_gutenberg_add_block_template_plugin_attribute', 10, 1 ); + +/** + * Hooks into `get_block_file_template` so templates from the registry are also returned. + * + * @param WP_Block_Template|null $block_template The found block template, or null if there is none. + * @param string $id Template unique identifier (example: 'theme_slug//template_slug'). + * @return WP_Block_Template|null The block template that was already found or from the registry. In case the template was already found, add the necessary details from the registry. + */ +function _gutenberg_add_block_file_templates_from_registry( $block_template, $id ) { + if ( $block_template ) { + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); + if ( $registered_template ) { + $block_template->plugin = $registered_template->plugin; + $block_template->origin = + 'theme' !== $block_template->origin && 'theme' !== $block_template->source ? + 'plugin' : + $block_template->origin; + } + return $block_template; + } + + $parts = explode( '//', $id, 2 ); + + if ( count( $parts ) < 2 ) { + return $block_template; + } + + list( , $slug ) = $parts; + return WP_Block_Templates_Registry::get_instance()->get_by_slug( $slug ); +} +add_filter( 'get_block_file_template', '_gutenberg_add_block_file_templates_from_registry', 10, 2 ); diff --git a/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-active-formatting-elements-6-7.php b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-active-formatting-elements-6-7.php new file mode 100644 index 00000000000000..10f53fe82ce4e0 --- /dev/null +++ b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-active-formatting-elements-6-7.php @@ -0,0 +1,187 @@ + Initially, the list of active formatting elements is empty. + * > It is used to handle mis-nested formatting element tags. + * > + * > The list contains elements in the formatting category, and markers. + * > The markers are inserted when entering applet, object, marquee, + * > template, td, th, and caption elements, and are used to prevent + * > formatting from "leaking" into applet, object, marquee, template, + * > td, th, and caption elements. + * > + * > In addition, each element in the list of active formatting elements + * > is associated with the token for which it was created, so that + * > further elements can be created for that token if necessary. + * + * @since 6.4.0 + * + * @access private + * + * @see https://html.spec.whatwg.org/#list-of-active-formatting-elements + * @see WP_HTML_Processor + */ +class Gutenberg_HTML_Active_Formatting_Elements_6_7 { + /** + * Holds the stack of active formatting element references. + * + * @since 6.4.0 + * + * @var WP_HTML_Token[] + */ + private $stack = array(); + + /** + * Reports if a specific node is in the stack of active formatting elements. + * + * @since 6.4.0 + * + * @param WP_HTML_Token $token Look for this node in the stack. + * @return bool Whether the referenced node is in the stack of active formatting elements. + */ + public function contains_node( Gutenberg_HTML_Token_6_7 $token ) { + foreach ( $this->walk_up() as $item ) { + if ( $token->bookmark_name === $item->bookmark_name ) { + return true; + } + } + + return false; + } + + /** + * Returns how many nodes are currently in the stack of active formatting elements. + * + * @since 6.4.0 + * + * @return int How many node are in the stack of active formatting elements. + */ + public function count() { + return count( $this->stack ); + } + + /** + * Returns the node at the end of the stack of active formatting elements, + * if one exists. If the stack is empty, returns null. + * + * @since 6.4.0 + * + * @return WP_HTML_Token|null Last node in the stack of active formatting elements, if one exists, otherwise null. + */ + public function current_node() { + $current_node = end( $this->stack ); + + return $current_node ? $current_node : null; + } + + /** + * Pushes a node onto the stack of active formatting elements. + * + * @since 6.4.0 + * + * @see https://html.spec.whatwg.org/#push-onto-the-list-of-active-formatting-elements + * + * @param WP_HTML_Token $token Push this node onto the stack. + */ + public function push( Gutenberg_HTML_Token_6_7 $token ) { + /* + * > If there are already three elements in the list of active formatting elements after the last marker, + * > if any, or anywhere in the list if there are no markers, that have the same tag name, namespace, and + * > attributes as element, then remove the earliest such element from the list of active formatting + * > elements. For these purposes, the attributes must be compared as they were when the elements were + * > created by the parser; two elements have the same attributes if all their parsed attributes can be + * > paired such that the two attributes in each pair have identical names, namespaces, and values + * > (the order of the attributes does not matter). + * + * @todo Implement the "Noah's Ark clause" to only add up to three of any given kind of formatting elements to the stack. + */ + // > Add element to the list of active formatting elements. + $this->stack[] = $token; + } + + /** + * Removes a node from the stack of active formatting elements. + * + * @since 6.4.0 + * + * @param WP_HTML_Token $token Remove this node from the stack, if it's there already. + * @return bool Whether the node was found and removed from the stack of active formatting elements. + */ + public function remove_node( Gutenberg_HTML_Token_6_7 $token ) { + foreach ( $this->walk_up() as $position_from_end => $item ) { + if ( $token->bookmark_name !== $item->bookmark_name ) { + continue; + } + + $position_from_start = $this->count() - $position_from_end - 1; + array_splice( $this->stack, $position_from_start, 1 ); + return true; + } + + return false; + } + + /** + * Steps through the stack of active formatting elements, starting with the + * top element (added first) and walking downwards to the one added last. + * + * This generator function is designed to be used inside a "foreach" loop. + * + * Example: + * + * $html = 'We are here'; + * foreach ( $stack->walk_down() as $node ) { + * echo "{$node->node_name} -> "; + * } + * > EM -> STRONG -> A -> + * + * To start with the most-recently added element and walk towards the top, + * see WP_HTML_Active_Formatting_Elements::walk_up(). + * + * @since 6.4.0 + */ + public function walk_down() { + $count = count( $this->stack ); + + for ( $i = 0; $i < $count; $i++ ) { + yield $this->stack[ $i ]; + } + } + + /** + * Steps through the stack of active formatting elements, starting with the + * bottom element (added last) and walking upwards to the one added first. + * + * This generator function is designed to be used inside a "foreach" loop. + * + * Example: + * + * $html = 'We are here'; + * foreach ( $stack->walk_up() as $node ) { + * echo "{$node->node_name} -> "; + * } + * > A -> STRONG -> EM -> + * + * To start with the first added element and walk towards the bottom, + * see WP_HTML_Active_Formatting_Elements::walk_down(). + * + * @since 6.4.0 + */ + public function walk_up() { + for ( $i = count( $this->stack ) - 1; $i >= 0; $i-- ) { + yield $this->stack[ $i ]; + } + } +} diff --git a/lib/compat/wordpress-6.5/html-api/class-gutenberg-html-attribute-token-6-5.php b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-attribute-token-6-7.php similarity index 98% rename from lib/compat/wordpress-6.5/html-api/class-gutenberg-html-attribute-token-6-5.php rename to lib/compat/wordpress-6.7/html-api/class-gutenberg-html-attribute-token-6-7.php index 70359ea339d669..4ee369b795c84c 100644 --- a/lib/compat/wordpress-6.5/html-api/class-gutenberg-html-attribute-token-6-5.php +++ b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-attribute-token-6-7.php @@ -19,7 +19,7 @@ * * @see WP_HTML_Tag_Processor */ -class Gutenberg_HTML_Attribute_Token_6_5 { +class Gutenberg_HTML_Attribute_Token_6_7 { /** * Attribute name. * diff --git a/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-decoder-6-7.php b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-decoder-6-7.php new file mode 100644 index 00000000000000..c745904865b1f8 --- /dev/null +++ b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-decoder-6-7.php @@ -0,0 +1,463 @@ += $length ) { + return null; + } + + if ( '&' !== $text[ $at ] ) { + return null; + } + + /* + * Numeric character references. + * + * When truncated, these will encode the code point found by parsing the + * digits that are available. For example, when `🅰` is truncated + * to `DZ` it will encode `Ē±`. It does not: + * - know how to parse the original `šŸ…°`. + * - fail to parse and return plaintext `DZ`. + * - fail to parse and return the replacement character `ļæ½` + */ + if ( '#' === $text[ $at + 1 ] ) { + if ( $at + 2 >= $length ) { + return null; + } + + /** Tracks inner parsing within the numeric character reference. */ + $digits_at = $at + 2; + + if ( 'x' === $text[ $digits_at ] || 'X' === $text[ $digits_at ] ) { + $numeric_base = 16; + $numeric_digits = '0123456789abcdefABCDEF'; + $max_digits = 6; // 􏿿 + ++$digits_at; + } else { + $numeric_base = 10; + $numeric_digits = '0123456789'; + $max_digits = 7; // 􏿿 + } + + // Cannot encode invalid Unicode code points. Max is to U+10FFFF. + $zero_count = strspn( $text, '0', $digits_at ); + $digit_count = strspn( $text, $numeric_digits, $digits_at + $zero_count ); + $after_digits = $digits_at + $zero_count + $digit_count; + $has_semicolon = $after_digits < $length && ';' === $text[ $after_digits ]; + $end_of_span = $has_semicolon ? $after_digits + 1 : $after_digits; + + // `&#` or `&#x` without digits returns into plaintext. + if ( 0 === $digit_count && 0 === $zero_count ) { + return null; + } + + // Whereas `&#` and only zeros is invalid. + if ( 0 === $digit_count ) { + $match_byte_length = $end_of_span - $at; + return 'ļæ½'; + } + + // If there are too many digits then it's not worth parsing. It's invalid. + if ( $digit_count > $max_digits ) { + $match_byte_length = $end_of_span - $at; + return 'ļæ½'; + } + + $digits = substr( $text, $digits_at + $zero_count, $digit_count ); + $code_point = intval( $digits, $numeric_base ); + + /* + * Noncharacters, 0x0D, and non-ASCII-whitespace control characters. + * + * > A noncharacter is a code point that is in the range U+FDD0 to U+FDEF, + * > inclusive, or U+FFFE, U+FFFF, U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF, + * > U+3FFFE, U+3FFFF, U+4FFFE, U+4FFFF, U+5FFFE, U+5FFFF, U+6FFFE, + * > U+6FFFF, U+7FFFE, U+7FFFF, U+8FFFE, U+8FFFF, U+9FFFE, U+9FFFF, + * > U+AFFFE, U+AFFFF, U+BFFFE, U+BFFFF, U+CFFFE, U+CFFFF, U+DFFFE, + * > U+DFFFF, U+EFFFE, U+EFFFF, U+FFFFE, U+FFFFF, U+10FFFE, or U+10FFFF. + * + * A C0 control is a code point that is in the range of U+00 to U+1F, + * but ASCII whitespace includes U+09, U+0A, U+0C, and U+0D. + * + * These characters are invalid but still decode as any valid character. + * This comment is here to note and explain why there's no check to + * remove these characters or replace them. + * + * @see https://infra.spec.whatwg.org/#noncharacter + */ + + /* + * Code points in the C1 controls area need to be remapped as if they + * were stored in Windows-1252. Note! This transformation only happens + * for numeric character references. The raw code points in the byte + * stream are not translated. + * + * > If the number is one of the numbers in the first column of + * > the following table, then find the row with that number in + * > the first column, and set the character reference code to + * > the number in the second column of that row. + */ + if ( $code_point >= 0x80 && $code_point <= 0x9F ) { + $windows_1252_mapping = array( + 0x20AC, // 0x80 -> EURO SIGN (ā‚¬). + 0x81, // 0x81 -> (no change). + 0x201A, // 0x82 -> SINGLE LOW-9 QUOTATION MARK (ā€š). + 0x0192, // 0x83 -> LATIN SMALL LETTER F WITH HOOK (ʒ). + 0x201E, // 0x84 -> DOUBLE LOW-9 QUOTATION MARK (ā€ž). + 0x2026, // 0x85 -> HORIZONTAL ELLIPSIS (ā€¦). + 0x2020, // 0x86 -> DAGGER (ā€ ). + 0x2021, // 0x87 -> DOUBLE DAGGER (ā€”). + 0x02C6, // 0x88 -> MODIFIER LETTER CIRCUMFLEX ACCENT (Ė†). + 0x2030, // 0x89 -> PER MILLE SIGN (ā€°). + 0x0160, // 0x8A -> LATIN CAPITAL LETTER S WITH CARON (Å ). + 0x2039, // 0x8B -> SINGLE LEFT-POINTING ANGLE QUOTATION MARK (ā€¹). + 0x0152, // 0x8C -> LATIN CAPITAL LIGATURE OE (Œ). + 0x8D, // 0x8D -> (no change). + 0x017D, // 0x8E -> LATIN CAPITAL LETTER Z WITH CARON (Ž). + 0x8F, // 0x8F -> (no change). + 0x90, // 0x90 -> (no change). + 0x2018, // 0x91 -> LEFT SINGLE QUOTATION MARK (ā€˜). + 0x2019, // 0x92 -> RIGHT SINGLE QUOTATION MARK (ā€™). + 0x201C, // 0x93 -> LEFT DOUBLE QUOTATION MARK (ā€œ). + 0x201D, // 0x94 -> RIGHT DOUBLE QUOTATION MARK (ā€). + 0x2022, // 0x95 -> BULLET (ā€¢). + 0x2013, // 0x96 -> EN DASH (ā€“). + 0x2014, // 0x97 -> EM DASH (ā€”). + 0x02DC, // 0x98 -> SMALL TILDE (Ėœ). + 0x2122, // 0x99 -> TRADE MARK SIGN (ā„¢). + 0x0161, // 0x9A -> LATIN SMALL LETTER S WITH CARON (Å”). + 0x203A, // 0x9B -> SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (ā€ŗ). + 0x0153, // 0x9C -> LATIN SMALL LIGATURE OE (œ). + 0x9D, // 0x9D -> (no change). + 0x017E, // 0x9E -> LATIN SMALL LETTER Z WITH CARON (ž). + 0x0178, // 0x9F -> LATIN CAPITAL LETTER Y WITH DIAERESIS (Åø). + ); + + $code_point = $windows_1252_mapping[ $code_point - 0x80 ]; + } + + $match_byte_length = $end_of_span - $at; + return self::code_point_to_utf8_bytes( $code_point ); + } + + /** Tracks inner parsing within the named character reference. */ + $name_at = $at + 1; + // Minimum named character reference is two characters. E.g. `GT`. + if ( $name_at + 2 > $length ) { + return null; + } + + $name_length = 0; + $replacement = $html5_named_character_references->read_token( $text, $name_at, $name_length ); + if ( false === $replacement ) { + return null; + } + + $after_name = $name_at + $name_length; + + // If the match ended with a semicolon then it should always be decoded. + if ( ';' === $text[ $name_at + $name_length - 1 ] ) { + $match_byte_length = $after_name - $at; + return $replacement; + } + + /* + * At this point though there's a match for an entry in the named + * character reference table but the match doesn't end in `;`. + * It may be allowed if it's followed by something unambiguous. + */ + $ambiguous_follower = ( + $after_name < $length && + $name_at < $length && + ( + ctype_alnum( $text[ $after_name ] ) || + '=' === $text[ $after_name ] + ) + ); + + // It's non-ambiguous, safe to leave it in. + if ( ! $ambiguous_follower ) { + $match_byte_length = $after_name - $at; + return $replacement; + } + + // It's ambiguous, which isn't allowed inside attributes. + if ( 'attribute' === $context ) { + return null; + } + + $match_byte_length = $after_name - $at; + return $replacement; + } + + /** + * Encode a code point number into the UTF-8 encoding. + * + * This encoder implements the UTF-8 encoding algorithm for converting + * a code point into a byte sequence. If it receives an invalid code + * point it will return the Unicode Replacement Character U+FFFD `ļæ½`. + * + * Example: + * + * 'šŸ…°' === WP_HTML_Decoder::code_point_to_utf8_bytes( 0x1f170 ); + * + * // Half of a surrogate pair is an invalid code point. + * 'ļæ½' === WP_HTML_Decoder::code_point_to_utf8_bytes( 0xd83c ); + * + * @since 6.6.0 + * + * @see https://www.rfc-editor.org/rfc/rfc3629 For the UTF-8 standard. + * + * @param int $code_point Which code point to convert. + * @return string Converted code point, or `ļæ½` if invalid. + */ + public static function code_point_to_utf8_bytes( $code_point ): string { + // Pre-check to ensure a valid code point. + if ( + $code_point <= 0 || + ( $code_point >= 0xD800 && $code_point <= 0xDFFF ) || + $code_point > 0x10FFFF + ) { + return 'ļæ½'; + } + + if ( $code_point <= 0x7F ) { + return chr( $code_point ); + } + + if ( $code_point <= 0x7FF ) { + $byte1 = chr( ( $code_point >> 6 ) | 0xC0 ); + $byte2 = chr( $code_point & 0x3F | 0x80 ); + + return "{$byte1}{$byte2}"; + } + + if ( $code_point <= 0xFFFF ) { + $byte1 = chr( ( $code_point >> 12 ) | 0xE0 ); + $byte2 = chr( ( $code_point >> 6 ) & 0x3F | 0x80 ); + $byte3 = chr( $code_point & 0x3F | 0x80 ); + + return "{$byte1}{$byte2}{$byte3}"; + } + + // Any values above U+10FFFF are eliminated above in the pre-check. + $byte1 = chr( ( $code_point >> 18 ) | 0xF0 ); + $byte2 = chr( ( $code_point >> 12 ) & 0x3F | 0x80 ); + $byte3 = chr( ( $code_point >> 6 ) & 0x3F | 0x80 ); + $byte4 = chr( $code_point & 0x3F | 0x80 ); + + return "{$byte1}{$byte2}{$byte3}{$byte4}"; + } +} diff --git a/lib/compat/wordpress-6.5/html-api/class-gutenberg-html-open-elements-6-5.php b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-open-elements-6-7.php similarity index 67% rename from lib/compat/wordpress-6.5/html-api/class-gutenberg-html-open-elements-6-5.php rename to lib/compat/wordpress-6.7/html-api/class-gutenberg-html-open-elements-6-7.php index 0d8901225c792e..fd2b252432455e 100644 --- a/lib/compat/wordpress-6.5/html-api/class-gutenberg-html-open-elements-6-5.php +++ b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-open-elements-6-7.php @@ -27,7 +27,7 @@ * @see https://html.spec.whatwg.org/#stack-of-open-elements * @see WP_HTML_Processor */ -class Gutenberg_HTML_Open_Elements_6_5 { +class Gutenberg_HTML_Open_Elements_6_7 { /** * Holds the stack of open element references. * @@ -51,6 +51,56 @@ class Gutenberg_HTML_Open_Elements_6_5 { */ private $has_p_in_button_scope = false; + /** + * A function that will be called when an item is popped off the stack of open elements. + * + * The function will be called with the popped item as its argument. + * + * @since 6.6.0 + * + * @var Closure|null + */ + private $pop_handler = null; + + /** + * A function that will be called when an item is pushed onto the stack of open elements. + * + * The function will be called with the pushed item as its argument. + * + * @since 6.6.0 + * + * @var Closure|null + */ + private $push_handler = null; + + /** + * Sets a pop handler that will be called when an item is popped off the stack of + * open elements. + * + * The function will be called with the pushed item as its argument. + * + * @since 6.6.0 + * + * @param Closure $handler The handler function. + */ + public function set_pop_handler( Closure $handler ): void { + $this->pop_handler = $handler; + } + + /** + * Sets a push handler that will be called when an item is pushed onto the stack of + * open elements. + * + * The function will be called with the pushed item as its argument. + * + * @since 6.6.0 + * + * @param Closure $handler The handler function. + */ + public function set_push_handler( Closure $handler ): void { + $this->push_handler = $handler; + } + /** * Reports if a specific node is in the stack of open elements. * @@ -59,7 +109,7 @@ class Gutenberg_HTML_Open_Elements_6_5 { * @param WP_HTML_Token $token Look for this node in the stack. * @return bool Whether the referenced node is in the stack of open elements. */ - public function contains_node( $token ) { + public function contains_node( Gutenberg_HTML_Token_6_7 $token ): bool { foreach ( $this->walk_up() as $item ) { if ( $token->bookmark_name === $item->bookmark_name ) { return true; @@ -76,7 +126,7 @@ public function contains_node( $token ) { * * @return int How many node are in the stack of open elements. */ - public function count() { + public function count(): int { return count( $this->stack ); } @@ -88,19 +138,56 @@ public function count() { * * @return WP_HTML_Token|null Last node in the stack of open elements, if one exists, otherwise null. */ - public function current_node() { + public function current_node(): ?Gutenberg_HTML_Token_6_7 { $current_node = end( $this->stack ); return $current_node ? $current_node : null; } /** - * Returns whether an element is in a specific scope. + * Indicates if the current node is of a given type or name. + * + * It's possible to pass either a node type or a node name to this function. + * In the case there is no current element it will always return `false`. + * + * Example: + * + * // Is the current node a text node? + * $stack->current_node_is( '#text' ); + * + * // Is the current node a DIV element? + * $stack->current_node_is( 'DIV' ); * - * ## HTML Support + * // Is the current node any element/tag? + * $stack->current_node_is( '#tag' ); * - * This function skips checking for the termination list because there - * are no supported elements which appear in the termination list. + * @see WP_HTML_Tag_Processor::get_token_type + * @see WP_HTML_Tag_Processor::get_token_name + * + * @since 6.7.0 + * + * @access private + * + * @param string $identity Check if the current node has this name or type (depending on what is provided). + * @return bool Whether there is a current element that matches the given identity, whether a token name or type. + */ + public function current_node_is( string $identity ): bool { + $current_node = end( $this->stack ); + if ( false === $current_node ) { + return false; + } + + $current_node_name = $current_node->node_name; + + return ( + $current_node_name === $identity || + ( '#doctype' === $identity && 'html' === $current_node_name ) || + ( '#tag' === $identity && ctype_upper( $current_node_name ) ) + ); + } + + /** + * Returns whether an element is in a specific scope. * * @since 6.4.0 * @@ -110,7 +197,7 @@ public function current_node() { * @param string[] $termination_list List of elements that terminate the search. * @return bool Whether the element was found in a specific scope. */ - public function has_element_in_specific_scope( $tag_name, $termination_list ) { + public function has_element_in_specific_scope( string $tag_name, $termination_list ): bool { foreach ( $this->walk_up() as $node ) { if ( $node->node_name === $tag_name ) { return true; @@ -146,7 +233,7 @@ public function has_element_in_specific_scope( $tag_name, $termination_list ) { * @param string $tag_name Name of tag to check. * @return bool Whether given element is in scope. */ - public function has_element_in_scope( $tag_name ) { + public function has_element_in_scope( string $tag_name ): bool { return $this->has_element_in_specific_scope( $tag_name, array( @@ -173,7 +260,7 @@ public function has_element_in_scope( $tag_name ) { * @param string $tag_name Name of tag to check. * @return bool Whether given element is in scope. */ - public function has_element_in_list_item_scope( $tag_name ) { + public function has_element_in_list_item_scope( string $tag_name ): bool { return $this->has_element_in_specific_scope( $tag_name, array( @@ -194,7 +281,7 @@ public function has_element_in_list_item_scope( $tag_name ) { * @param string $tag_name Name of tag to check. * @return bool Whether given element is in scope. */ - public function has_element_in_button_scope( $tag_name ) { + public function has_element_in_button_scope( string $tag_name ): bool { return $this->has_element_in_specific_scope( $tag_name, array( 'BUTTON' ) ); } @@ -210,8 +297,8 @@ public function has_element_in_button_scope( $tag_name ) { * @param string $tag_name Name of tag to check. * @return bool Whether given element is in scope. */ - public function has_element_in_table_scope( $tag_name ) { - throw new WP_HTML_Unsupported_Exception( 'Cannot process elements depending on table scope.' ); + public function has_element_in_table_scope( string $tag_name ): bool { + throw new Gutenberg_HTML_Unsupported_Exception_6_7( 'Cannot process elements depending on table scope.' ); return false; // The linter requires this unreachable code until the function is implemented and can return. } @@ -219,19 +306,38 @@ public function has_element_in_table_scope( $tag_name ) { /** * Returns whether a particular element is in select scope. * - * @since 6.4.0 + * This test differs from the others like it, in that its rules are inverted. + * Instead of arriving at a match when one of any tag in a termination group + * is reached, this one terminates if any other tag is reached. * - * @see https://html.spec.whatwg.org/#has-an-element-in-select-scope + * > The stack of open elements is said to have a particular element in select scope when it has + * > that element in the specific scope consisting of all element types except the following: + * > - optgroup in the HTML namespace + * > - option in the HTML namespace * - * @throws WP_HTML_Unsupported_Exception Always until this function is implemented. + * @since 6.4.0 Stub implementation (throws). + * @since 6.7.0 Full implementation. + * + * @see https://html.spec.whatwg.org/#has-an-element-in-select-scope * * @param string $tag_name Name of tag to check. - * @return bool Whether given element is in scope. + * @return bool Whether the given element is in SELECT scope. */ - public function has_element_in_select_scope( $tag_name ) { - throw new WP_HTML_Unsupported_Exception( 'Cannot process elements depending on select scope.' ); + public function has_element_in_select_scope( string $tag_name ): bool { + foreach ( $this->walk_up() as $node ) { + if ( $node->node_name === $tag_name ) { + return true; + } - return false; // The linter requires this unreachable code until the function is implemented and can return. + if ( + 'OPTION' !== $node->node_name && + 'OPTGROUP' !== $node->node_name + ) { + return false; + } + } + + return false; } /** @@ -243,7 +349,7 @@ public function has_element_in_select_scope( $tag_name ) { * * @return bool Whether a P is in BUTTON scope. */ - public function has_p_in_button_scope() { + public function has_p_in_button_scope(): bool { return $this->has_p_in_button_scope; } @@ -256,13 +362,17 @@ public function has_p_in_button_scope() { * * @return bool Whether a node was popped off of the stack. */ - public function pop() { + public function pop(): bool { $item = array_pop( $this->stack ); - if ( null === $item ) { return false; } + if ( 'context-node' === $item->bookmark_name ) { + $this->stack[] = $item; + return false; + } + $this->after_element_pop( $item ); return true; } @@ -277,8 +387,12 @@ public function pop() { * @param string $tag_name Name of tag that needs to be popped off of the stack of open elements. * @return bool Whether a tag of the given name was found and popped off of the stack of open elements. */ - public function pop_until( $tag_name ) { + public function pop_until( string $tag_name ): bool { foreach ( $this->walk_up() as $item ) { + if ( 'context-node' === $item->bookmark_name ) { + return true; + } + $this->pop(); if ( @@ -305,7 +419,7 @@ public function pop_until( $tag_name ) { * * @param WP_HTML_Token $stack_item Item to add onto stack. */ - public function push( $stack_item ) { + public function push( Gutenberg_HTML_Token_6_7 $stack_item ): void { $this->stack[] = $stack_item; $this->after_element_push( $stack_item ); } @@ -318,7 +432,11 @@ public function push( $stack_item ) { * @param WP_HTML_Token $token The node to remove from the stack of open elements. * @return bool Whether the node was found and removed from the stack of open elements. */ - public function remove_node( $token ) { + public function remove_node( Gutenberg_HTML_Token_6_7 $token ): bool { + if ( 'context-node' === $token->bookmark_name ) { + return false; + } + foreach ( $this->walk_up() as $position_from_end => $item ) { if ( $token->bookmark_name !== $item->bookmark_name ) { continue; @@ -381,9 +499,10 @@ public function walk_down() { * @since 6.4.0 * @since 6.5.0 Accepts $above_this_node to start traversal above a given node, if it exists. * - * @param ?WP_HTML_Token $above_this_node Start traversing above this node, if provided and if the node exists. + * @param WP_HTML_Token|null $above_this_node Optional. Start traversing above this node, + * if provided and if the node exists. */ - public function walk_up( $above_this_node = null ) { + public function walk_up( ?Gutenberg_HTML_Token_6_7 $above_this_node = null ) { $has_found_node = null === $above_this_node; for ( $i = count( $this->stack ) - 1; $i >= 0; $i-- ) { @@ -415,7 +534,7 @@ public function walk_up( $above_this_node = null ) { * * @param WP_HTML_Token $item Element that was added to the stack of open elements. */ - public function after_element_push( $item ) { + public function after_element_push( Gutenberg_HTML_Token_6_7 $item ): void { /* * When adding support for new elements, expand this switch to trap * cases where the precalculated value needs to change. @@ -429,6 +548,10 @@ public function after_element_push( $item ) { $this->has_p_in_button_scope = true; break; } + + if ( null !== $this->push_handler ) { + ( $this->push_handler )( $item ); + } } /** @@ -444,7 +567,7 @@ public function after_element_push( $item ) { * * @param WP_HTML_Token $item Element that was removed from the stack of open elements. */ - public function after_element_pop( $item ) { + public function after_element_pop( Gutenberg_HTML_Token_6_7 $item ): void { /* * When adding support for new elements, expand this switch to trap * cases where the precalculated value needs to change. @@ -458,5 +581,18 @@ public function after_element_pop( $item ) { $this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' ); break; } + + if ( null !== $this->pop_handler ) { + ( $this->pop_handler )( $item ); + } + } + + /** + * Wakeup magic method. + * + * @since 6.6.0 + */ + public function __wakeup() { + throw new \LogicException( __CLASS__ . ' should never be unserialized' ); } } diff --git a/lib/compat/wordpress-6.5/html-api/class-gutenberg-html-processor-6-5.php b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-processor-6-7.php similarity index 51% rename from lib/compat/wordpress-6.5/html-api/class-gutenberg-html-processor-6-5.php rename to lib/compat/wordpress-6.7/html-api/class-gutenberg-html-processor-6-7.php index 02879b72ea9cbb..15cbbdc6adda8c 100644 --- a/lib/compat/wordpress-6.5/html-api/class-gutenberg-html-processor-6-5.php +++ b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-processor-6-7.php @@ -101,7 +101,7 @@ * * - Containers: ADDRESS, BLOCKQUOTE, DETAILS, DIALOG, DIV, FOOTER, HEADER, MAIN, MENU, SPAN, SUMMARY. * - Custom elements: All custom elements are supported. :) - * - Form elements: BUTTON, DATALIST, FIELDSET, INPUT, LABEL, LEGEND, METER, PROGRESS, SEARCH. + * - Form elements: BUTTON, DATALIST, FIELDSET, INPUT, LABEL, LEGEND, METER, OPTGROUP, OPTION, PROGRESS, SEARCH, SELECT. * - Formatting elements: B, BIG, CODE, EM, FONT, I, PRE, SMALL, STRIKE, STRONG, TT, U, WBR. * - Heading elements: H1, H2, H3, H4, H5, H6, HGROUP. * - Links: A. @@ -136,7 +136,7 @@ * @see WP_HTML_Tag_Processor * @see https://html.spec.whatwg.org/ */ -class Gutenberg_HTML_Processor_6_5 extends Gutenberg_HTML_Tag_Processor_6_5 { +class Gutenberg_HTML_Processor_6_7 extends Gutenberg_HTML_Tag_Processor_6_7 { /** * The maximum number of bookmarks allowed to exist at any given time. * @@ -159,7 +159,7 @@ class Gutenberg_HTML_Processor_6_5 extends Gutenberg_HTML_Tag_Processor_6_5 { * * @var WP_HTML_Processor_State */ - private $state = null; + private $state; /** * Used to create unique bookmark names. @@ -188,6 +188,17 @@ class Gutenberg_HTML_Processor_6_5 extends Gutenberg_HTML_Tag_Processor_6_5 { */ private $last_error = null; + /** + * Stores context for why the parser bailed on unsupported HTML, if it did. + * + * @see self::get_unsupported_exception + * + * @since 6.7.0 + * + * @var WP_HTML_Unsupported_Exception|null + */ + private $unsupported_exception = null; + /** * Releases a bookmark when PHP garbage-collects its wrapping WP_HTML_Token instance. * @@ -197,10 +208,65 @@ class Gutenberg_HTML_Processor_6_5 extends Gutenberg_HTML_Tag_Processor_6_5 { * * @since 6.4.0 * - * @var closure + * @var Closure|null */ private $release_internal_bookmark_on_destruct = null; + /** + * Stores stack events which arise during parsing of the + * HTML document, which will then supply the "match" events. + * + * @since 6.6.0 + * + * @var WP_HTML_Stack_Event[] + */ + private $element_queue = array(); + + /** + * Stores the current breadcrumbs. + * + * @since 6.7.0 + * + * @var string[] + */ + private $breadcrumbs = array(); + + /** + * Current stack event, if set, representing a matched token. + * + * Because the parser may internally point to a place further along in a document + * than the nodes which have already been processed (some "virtual" nodes may have + * appeared while scanning the HTML document), this will point at the "current" node + * being processed. It comes from the front of the element queue. + * + * @since 6.6.0 + * + * @var WP_HTML_Stack_Event|null + */ + private $current_element = null; + + /** + * Context node if created as a fragment parser. + * + * @var WP_HTML_Token|null + */ + private $context_node = null; + + /** + * Whether the parser has yet processed the context node, + * if created as a fragment parser. + * + * The context node will be initially pushed onto the stack of open elements, + * but when created as a fragment parser, this context element (and the implicit + * HTML document node above it) should not be exposed as a matched token or node. + * + * This boolean indicates whether the processor should skip over the current + * node in its initial search for the first node created from the input HTML. + * + * @var bool + */ + private $has_seen_context_node = false; + /* * Public Interface Functions */ @@ -230,42 +296,44 @@ class Gutenberg_HTML_Processor_6_5 extends Gutenberg_HTML_Tag_Processor_6_5 { * - The only supported document encoding is `UTF-8`, which is the default value. * * @since 6.4.0 + * @since 6.6.0 Returns `static` instead of `self` so it can create subclass instances. * * @param string $html Input HTML fragment to process. * @param string $context Context element for the fragment, must be default of ``. * @param string $encoding Text encoding of the document; must be default of 'UTF-8'. - * @return WP_HTML_Processor|null The created processor if successful, otherwise null. + * @return static|null The created processor if successful, otherwise null. */ public static function create_fragment( $html, $context = '', $encoding = 'UTF-8' ) { if ( '' !== $context || 'UTF-8' !== $encoding ) { return null; } - $p = new self( $html, self::CONSTRUCTOR_UNLOCK_CODE ); - $p->state->context_node = array( 'BODY', array() ); - $p->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; + $processor = new static( $html, self::CONSTRUCTOR_UNLOCK_CODE ); + $processor->state->context_node = array( 'BODY', array() ); + $processor->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_BODY; // @todo Create "fake" bookmarks for non-existent but implied nodes. - $p->bookmarks['root-node'] = new WP_HTML_Span( 0, 0 ); - $p->bookmarks['context-node'] = new WP_HTML_Span( 0, 0 ); + $processor->bookmarks['root-node'] = new Gutenberg_HTML_Span_6_7( 0, 0 ); + $processor->bookmarks['context-node'] = new Gutenberg_HTML_Span_6_7( 0, 0 ); - $p->state->stack_of_open_elements->push( - new WP_HTML_Token( + $processor->state->stack_of_open_elements->push( + new Gutenberg_HTML_Token_6_7( 'root-node', 'HTML', false ) ); - $p->state->stack_of_open_elements->push( - new WP_HTML_Token( - 'context-node', - $p->state->context_node[0], - false - ) + $context_node = new Gutenberg_HTML_Token_6_7( + 'context-node', + $processor->state->context_node[0], + false ); - return $p; + $processor->context_node = $context_node; + $processor->breadcrumbs = array( 'HTML', $context_node->node_name ); + + return $processor; } /** @@ -297,18 +365,73 @@ public function __construct( $html, $use_the_static_create_methods_instead = nul ); } - $this->state = new Gutenberg_HTML_Processor_State_6_5(); + $this->state = new Gutenberg_HTML_Processor_State_6_7(); + + $this->state->stack_of_open_elements->set_push_handler( + function ( Gutenberg_HTML_Token_6_7 $token ): void { + $is_virtual = ! isset( $this->state->current_token ) || $this->is_tag_closer(); + $same_node = isset( $this->state->current_token ) && $token->node_name === $this->state->current_token->node_name; + $provenance = ( ! $same_node || $is_virtual ) ? 'virtual' : 'real'; + $this->element_queue[] = new Gutenberg_HTML_Stack_Event_6_7( $token, Gutenberg_HTML_Stack_Event_6_7::PUSH, $provenance ); + } + ); + + $this->state->stack_of_open_elements->set_pop_handler( + function ( Gutenberg_HTML_Token_6_7 $token ): void { + $is_virtual = ! isset( $this->state->current_token ) || ! $this->is_tag_closer(); + $same_node = isset( $this->state->current_token ) && $token->node_name === $this->state->current_token->node_name; + $provenance = ( ! $same_node || $is_virtual ) ? 'virtual' : 'real'; + $this->element_queue[] = new Gutenberg_HTML_Stack_Event_6_7( $token, Gutenberg_HTML_Stack_Event_6_7::POP, $provenance ); + } + ); /* * Create this wrapper so that it's possible to pass * a private method into WP_HTML_Token classes without * exposing it to any public API. */ - $this->release_internal_bookmark_on_destruct = function ( $name ) { + $this->release_internal_bookmark_on_destruct = function ( string $name ): void { parent::release_bookmark( $name ); }; } + /** + * Stops the parser and terminates its execution when encountering unsupported markup. + * + * @throws WP_HTML_Unsupported_Exception Halts execution of the parser. + * + * @since 6.7.0 + * + * @param string $message Explains support is missing in order to parse the current node. + */ + private function bail( string $message ) { + $here = $this->bookmarks[ $this->state->current_token->bookmark_name ]; + $token = substr( $this->html, $here->start, $here->length ); + + $open_elements = array(); + foreach ( $this->state->stack_of_open_elements->stack as $item ) { + $open_elements[] = $item->node_name; + } + + $active_formats = array(); + foreach ( $this->state->active_formatting_elements->walk_down() as $item ) { + $active_formats[] = $item->node_name; + } + + $this->last_error = self::ERROR_UNSUPPORTED; + + $this->unsupported_exception = new Gutenberg_HTML_Unsupported_Exception_6_7( + $message, + $this->state->current_token->node_name, + $here->start, + $token, + $open_elements, + $active_formats + ); + + throw $this->unsupported_exception; + } + /** * Returns the last error, if any. * @@ -332,16 +455,32 @@ public function __construct( $html, $use_the_static_create_methods_instead = nul * * @return string|null The last error, if one exists, otherwise null. */ - public function get_last_error() { + public function get_last_error(): ?string { return $this->last_error; } + /** + * Returns context for why the parser aborted due to unsupported HTML, if it did. + * + * This is meant for debugging purposes, not for production use. + * + * @since 6.7.0 + * + * @see self::$unsupported_exception + * + * @return WP_HTML_Unsupported_Exception|null + */ + public function get_unsupported_exception() { + return $this->unsupported_exception; + } + /** * Finds the next tag matching the $query. * * @todo Support matching the class name and tag name. * * @since 6.4.0 + * @since 6.6.0 Visits all tokens, including virtual ones. * * @throws Exception When unable to allocate a bookmark for the next token in the input HTML document. * @@ -349,6 +488,7 @@ public function get_last_error() { * Optional. Which tag name to find, having which class, etc. Default is to find any tag. * * @type string|null $tag_name Which tag to find, or `null` for "any tag." + * @type string $tag_closers 'visit' to pause at tag closers, 'skip' or unset to only visit openers. * @type int|null $match_offset Find the Nth tag matching all search criteria. * 1 for "first" tag, 3 for "third," etc. * Defaults to first tag. @@ -358,14 +498,16 @@ public function get_last_error() { * } * @return bool Whether a tag was matched. */ - public function next_tag( $query = null ) { + public function next_tag( $query = null ): bool { + $visit_closers = isset( $query['tag_closers'] ) && 'visit' === $query['tag_closers']; + if ( null === $query ) { - while ( $this->step() ) { + while ( $this->next_token() ) { if ( '#tag' !== $this->get_token_type() ) { continue; } - if ( ! $this->is_tag_closer() ) { + if ( ! $this->is_tag_closer() || $visit_closers ) { return true; } } @@ -386,13 +528,25 @@ public function next_tag( $query = null ) { return false; } + $needs_class = ( isset( $query['class_name'] ) && is_string( $query['class_name'] ) ) + ? $query['class_name'] + : null; + if ( ! ( array_key_exists( 'breadcrumbs', $query ) && is_array( $query['breadcrumbs'] ) ) ) { - while ( $this->step() ) { + while ( $this->next_token() ) { if ( '#tag' !== $this->get_token_type() ) { continue; } - if ( ! $this->is_tag_closer() ) { + if ( isset( $query['tag_name'] ) && $query['tag_name'] !== $this->get_token_name() ) { + continue; + } + + if ( isset( $needs_class ) && ! $this->has_class( $needs_class ) ) { + continue; + } + + if ( ! $this->is_tag_closer() || $visit_closers ) { return true; } } @@ -400,20 +554,15 @@ public function next_tag( $query = null ) { return false; } - if ( isset( $query['tag_closers'] ) && 'visit' === $query['tag_closers'] ) { - _doing_it_wrong( - __METHOD__, - __( 'Cannot visit tag closers in HTML Processor.' ), - '6.4.0' - ); - return false; - } - $breadcrumbs = $query['breadcrumbs']; $match_offset = isset( $query['match_offset'] ) ? (int) $query['match_offset'] : 1; - while ( $match_offset > 0 && $this->step() ) { - if ( '#tag' !== $this->get_token_type() ) { + while ( $match_offset > 0 && $this->next_token() ) { + if ( '#tag' !== $this->get_token_type() || $this->is_tag_closer() ) { + continue; + } + + if ( isset( $needs_class ) && ! $this->has_class( $needs_class ) ) { continue; } @@ -439,8 +588,94 @@ public function next_tag( $query = null ) { * * @return bool */ - public function next_token() { - return $this->step(); + public function next_token(): bool { + $this->current_element = null; + + if ( isset( $this->last_error ) ) { + return false; + } + + /* + * Prime the events if there are none. + * + * @todo In some cases, probably related to the adoption agency + * algorithm, this call to step() doesn't create any new + * events. Calling it again creates them. Figure out why + * this is and if it's inherent or if it's a bug. Looping + * until there are events or until there are no more + * tokens works in the meantime and isn't obviously wrong. + */ + while ( empty( $this->element_queue ) && $this->step() ) { + continue; + } + + // Process the next event on the queue. + $this->current_element = array_shift( $this->element_queue ); + if ( ! isset( $this->current_element ) ) { + return false; + } + + $is_pop = Gutenberg_HTML_Stack_Event_6_7::POP === $this->current_element->operation; + + /* + * The root node only exists in the fragment parser, and closing it + * indicates that the parse is complete. Stop before popping if from + * the breadcrumbs. + */ + if ( 'root-node' === $this->current_element->token->bookmark_name ) { + return ! $is_pop && $this->next_token(); + } + + // Adjust the breadcrumbs for this event. + if ( $is_pop ) { + array_pop( $this->breadcrumbs ); + } else { + $this->breadcrumbs[] = $this->current_element->token->node_name; + } + + // Avoid sending close events for elements which don't expect a closing. + if ( $is_pop && ! static::expects_closer( $this->current_element->token ) ) { + return $this->next_token(); + } + + return true; + } + + /** + * Indicates if the current tag token is a tag closer. + * + * Example: + * + * $p = WP_HTML_Processor::create_fragment( '
' ); + * $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; + * + * @since 6.6.0 Subclassed for HTML Processor. + * + * @return bool Whether the current tag is a tag closer. + */ + public function is_tag_closer(): bool { + return $this->is_virtual() + ? ( Gutenberg_HTML_Stack_Event_6_7::POP === $this->current_element->operation && '#tag' === $this->get_token_type() ) + : parent::is_tag_closer(); + } + + /** + * Indicates if the currently-matched token is virtual, created by a stack operation + * while processing HTML, rather than a token found in the HTML text itself. + * + * @since 6.6.0 + * + * @return bool Whether the current token is virtual. + */ + private function is_virtual(): bool { + return ( + isset( $this->current_element->provenance ) && + 'virtual' === $this->current_element->provenance + ); } /** @@ -468,7 +703,7 @@ public function next_token() { * May also contain the wildcard `*` which matches a single element, e.g. `array( 'SECTION', '*' )`. * @return bool Whether the currently-matched tag is found at the given nested structure. */ - public function matches_breadcrumbs( $breadcrumbs ) { + public function matches_breadcrumbs( $breadcrumbs ): bool { // Everything matches when there are zero constraints. if ( 0 === count( $breadcrumbs ) ) { return true; @@ -481,10 +716,11 @@ public function matches_breadcrumbs( $breadcrumbs ) { return false; } - foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { + for ( $i = count( $this->breadcrumbs ) - 1; $i >= 0; $i-- ) { + $node = $this->breadcrumbs[ $i ]; $crumb = strtoupper( current( $breadcrumbs ) ); - if ( '*' !== $crumb && $node->node_name !== $crumb ) { + if ( '*' !== $crumb && $node !== $crumb ) { return false; } @@ -496,6 +732,46 @@ public function matches_breadcrumbs( $breadcrumbs ) { return false; } + /** + * Indicates if the currently-matched node expects a closing + * token, or if it will self-close on the next step. + * + * Most HTML elements expect a closer, such as a P element or + * a DIV element. Others, like an IMG element are void and don't + * have a closing tag. Special elements, such as SCRIPT and STYLE, + * are treated just like void tags. Text nodes and self-closing + * foreign content will also act just like a void tag, immediately + * closing as soon as the processor advances to the next token. + * + * @since 6.6.0 + * + * @todo When adding support for foreign content, ensure that + * this returns false for self-closing elements in the + * SVG and MathML namespace. + * + * @param WP_HTML_Token|null $node Optional. Node to examine, if provided. + * Default is to examine current node. + * @return bool|null Whether to expect a closer for the currently-matched node, + * or `null` if not matched on any token. + */ + public function expects_closer( $node = null ): ?bool { + $token_name = $node->node_name ?? $this->get_token_name(); + if ( ! isset( $token_name ) ) { + return null; + } + + return ! ( + // Comments, text nodes, and other atomic tokens. + '#' === $token_name[0] || + // Doctype declarations. + 'html' === $token_name || + // Void elements. + self::is_void( $token_name ) || + // Special atomic elements. + in_array( $token_name, array( 'IFRAME', 'NOEMBED', 'NOFRAMES', 'SCRIPT', 'STYLE', 'TEXTAREA', 'TITLE', 'XMP' ), true ) + ); + } + /** * Steps through the HTML document and stop at the next tag, if any. * @@ -509,7 +785,7 @@ public function matches_breadcrumbs( $breadcrumbs ) { * @param string $node_to_process Whether to parse the next node or reprocess the current node. * @return bool Whether a tag was matched. */ - public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { + public function step( $node_to_process = self::PROCESS_NEXT_NODE ): bool { // Refuse to proceed if there was a previous error. if ( null !== $this->last_error ) { return false; @@ -531,16 +807,7 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { * is provided in the opening tag, otherwise it expects a tag closer. */ $top_node = $this->state->stack_of_open_elements->current_node(); - if ( - $top_node && ( - // Void elements. - self::is_void( $top_node->node_name ) || - // Comments, text nodes, and other atomic tokens. - '#' === $top_node->node_name[0] || - // Doctype declarations. - 'html' === $top_node->node_name - ) - ) { + if ( isset( $top_node ) && ! static::expects_closer( $top_node ) ) { $this->state->stack_of_open_elements->pop(); } } @@ -551,13 +818,13 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { // Finish stepping when there are no more tokens in the document. if ( - WP_HTML_Tag_Processor::STATE_INCOMPLETE_INPUT === $this->parser_state || - WP_HTML_Tag_Processor::STATE_COMPLETE === $this->parser_state + Gutenberg_HTML_Tag_Processor_6_7::STATE_INCOMPLETE_INPUT === $this->parser_state || + Gutenberg_HTML_Tag_Processor_6_7::STATE_COMPLETE === $this->parser_state ) { return false; } - $this->state->current_token = new WP_HTML_Token( + $this->state->current_token = new Gutenberg_HTML_Token_6_7( $this->bookmark_token(), $this->get_token_name(), $this->has_self_closing_flag(), @@ -566,14 +833,80 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { try { switch ( $this->state->insertion_mode ) { - case WP_HTML_Processor_State::INSERTION_MODE_IN_BODY: + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_INITIAL: + return $this->step_initial(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_BEFORE_HTML: + return $this->step_before_html(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_BEFORE_HEAD: + return $this->step_before_head(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_HEAD: + return $this->step_in_head(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_HEAD_NOSCRIPT: + return $this->step_in_head_noscript(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_AFTER_HEAD: + return $this->step_after_head(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_BODY: return $this->step_in_body(); + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_TABLE: + return $this->step_in_table(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_TABLE_TEXT: + return $this->step_in_table_text(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_CAPTION: + return $this->step_in_caption(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_COLUMN_GROUP: + return $this->step_in_column_group(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_TABLE_BODY: + return $this->step_in_table_body(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_ROW: + return $this->step_in_row(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_CELL: + return $this->step_in_cell(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_SELECT: + return $this->step_in_select(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_SELECT_IN_TABLE: + return $this->step_in_select_in_table(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_TEMPLATE: + return $this->step_in_template(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_AFTER_BODY: + return $this->step_after_body(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_FRAMESET: + return $this->step_in_frameset(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_AFTER_FRAMESET: + return $this->step_after_frameset(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_AFTER_AFTER_BODY: + return $this->step_after_after_body(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_AFTER_AFTER_FRAMESET: + return $this->step_after_after_frameset(); + + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_FOREIGN_CONTENT: + return $this->step_in_foreign_content(); + + // This should be unreachable but PHP doesn't have total type checking on switch. default: - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + $this->bail( "Unaware of the requested parsing mode: '{$this->state->insertion_mode}'." ); } - } catch ( WP_HTML_Unsupported_Exception $e ) { + } catch ( Gutenberg_HTML_Unsupported_Exception_6_7 $e ) { /* * Exceptions are used in this class to escape deep call stacks that * otherwise might involve messier calling and return conventions. @@ -602,13 +935,151 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { * * @return string[]|null Array of tag names representing path to matched node, if matched, otherwise NULL. */ - public function get_breadcrumbs() { - $breadcrumbs = array(); - foreach ( $this->state->stack_of_open_elements->walk_down() as $stack_item ) { - $breadcrumbs[] = $stack_item->node_name; - } + public function get_breadcrumbs(): ?array { + return $this->breadcrumbs; + } + + /** + * Returns the nesting depth of the current location in the document. + * + * Example: + * + * $processor = WP_HTML_Processor::create_fragment( '

' ); + * // The processor starts in the BODY context, meaning it has depth from the start: HTML > BODY. + * 2 === $processor->get_current_depth(); + * + * // Opening the DIV element increases the depth. + * $processor->next_token(); + * 3 === $processor->get_current_depth(); + * + * // Opening the P element increases the depth. + * $processor->next_token(); + * 4 === $processor->get_current_depth(); + * + * // The P element is closed during `next_token()` so the depth is decreased to reflect that. + * $processor->next_token(); + * 3 === $processor->get_current_depth(); + * + * @since 6.6.0 + * + * @return int Nesting-depth of current location in the document. + */ + public function get_current_depth(): int { + return count( $this->breadcrumbs ); + } + + /** + * Parses next element in the 'initial' insertion mode. + * + * This internal function performs the 'initial' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#the-initial-insertion-mode + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_initial(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'before html' insertion mode. + * + * This internal function performs the 'before html' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#the-before-html-insertion-mode + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_before_html(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'before head' insertion mode. + * + * This internal function performs the 'before head' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#the-before-head-insertion-mode + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_before_head(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'in head' insertion mode. + * + * This internal function performs the 'in head' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_head(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'in head noscript' insertion mode. + * + * This internal function performs the 'in head noscript' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-inheadnoscript + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_head_noscript(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } - return $breadcrumbs; + /** + * Parses next element in the 'after head' insertion mode. + * + * This internal function performs the 'after head' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#the-after-head-insertion-mode + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_after_head(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); } /** @@ -626,10 +1097,10 @@ public function get_breadcrumbs() { * * @return bool Whether an element was found. */ - private function step_in_body() { + private function step_in_body(): bool { $token_name = $this->get_token_name(); $token_type = $this->get_token_type(); - $op_sigil = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : ''; + $op_sigil = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : ''; $op = "{$op_sigil}{$token_name}"; switch ( $op ) { @@ -776,7 +1247,7 @@ private function step_in_body() { } $this->generate_implied_end_tags(); - if ( $this->state->stack_of_open_elements->current_node()->node_name !== $token_name ) { + if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) { // @todo Record parse error: this error doesn't impact parsing. } $this->state->stack_of_open_elements->pop_until( $token_name ); @@ -841,7 +1312,7 @@ private function step_in_body() { $this->generate_implied_end_tags(); - if ( $this->state->stack_of_open_elements->current_node()->node_name !== $token_name ) { + if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) { // @todo Record parse error: this error doesn't impact parsing. } @@ -867,7 +1338,7 @@ private function step_in_body() { if ( $is_li ? 'LI' === $node->node_name : ( 'DD' === $node->node_name || 'DT' === $node->node_name ) ) { $node_name = $is_li ? 'LI' : $node->node_name; $this->generate_implied_end_tags( $node_name ); - if ( $node_name !== $this->state->stack_of_open_elements->current_node()->node_name ) { + if ( ! $this->state->stack_of_open_elements->current_node_is( $node_name ) ) { // @todo Indicate a parse error once it's possible. This error does not impact the logic here. } @@ -944,7 +1415,7 @@ private function step_in_body() { $this->generate_implied_end_tags( $token_name ); - if ( $token_name !== $this->state->stack_of_open_elements->current_node()->node_name ) { + if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) { // @todo Indicate a parse error once it's possible. This error does not impact the logic here. } @@ -1030,8 +1501,9 @@ private function step_in_body() { * > than the end tag token that it actually is. */ case '-BR': - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( 'Closing BR tags require unimplemented special handling.' ); + $this->bail( 'Closing BR tags require unimplemented special handling.' ); + // This return required because PHPCS can't determine that the call to bail() throws. + return false; /* * > A start tag whose tag name is one of: "area", "br", "embed", "img", "keygen", "wbr" @@ -1083,17 +1555,59 @@ private function step_in_body() { case '+TRACK': $this->insert_html_element( $this->state->current_token ); return true; - } - /* - * These tags require special handling in the 'in body' insertion mode - * but that handling hasn't yet been implemented. - * - * As the rules for each tag are implemented, the corresponding tag - * name should be removed from this list. An accompanying test should - * help ensure this list is maintained. - * - * @see Tests_HtmlApi_WpHtmlProcessor::test_step_in_body_fails_on_unsupported_tags + /* + * > A start tag whose tag name is "select" + */ + case '+SELECT': + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->state->current_token ); + $this->state->frameset_ok = false; + + switch ( $this->state->insertion_mode ) { + /* + * > If the insertion mode is one of "in table", "in caption", "in table body", "in row", + * > or "in cell", then switch the insertion mode to "in select in table". + */ + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_TABLE: + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_CAPTION: + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_TABLE_BODY: + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_ROW: + case Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_CELL: + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_SELECT_IN_TABLE; + break; + + /* + * > Otherwise, switch the insertion mode to "in select". + */ + default: + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_SELECT; + break; + } + return true; + + /* + * > A start tag whose tag name is one of: "optgroup", "option" + */ + case '+OPTGROUP': + case '+OPTION': + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) { + $this->state->stack_of_open_elements->pop(); + } + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->state->current_token ); + return true; + } + + /* + * These tags require special handling in the 'in body' insertion mode + * but that handling hasn't yet been implemented. + * + * As the rules for each tag are implemented, the corresponding tag + * name should be removed from this list. An accompanying test should + * help ensure this list is maintained. + * + * @see Tests_HtmlApi_WpHtmlProcessor::test_step_in_body_fails_on_unsupported_tags * * Since this switch structure throws a WP_HTML_Unsupported_Exception, it's * possible to handle "any other start tag" and "any other end tag" below, @@ -1125,8 +1639,6 @@ private function step_in_body() { case 'NOFRAMES': case 'NOSCRIPT': case 'OBJECT': - case 'OPTGROUP': - case 'OPTION': case 'PLAINTEXT': case 'RB': case 'RP': @@ -1134,7 +1646,6 @@ private function step_in_body() { case 'RTC': case 'SARCASM': case 'SCRIPT': - case 'SELECT': case 'STYLE': case 'SVG': case 'TABLE': @@ -1148,11 +1659,10 @@ private function step_in_body() { case 'TITLE': case 'TR': case 'XMP': - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( "Cannot process {$token_name} element." ); + $this->bail( "Cannot process {$token_name} element." ); } - if ( ! $this->is_tag_closer() ) { + if ( ! parent::is_tag_closer() ) { /* * > Any other start tag */ @@ -1164,35 +1674,501 @@ private function step_in_body() { * > Any other end tag */ - /* - * Find the corresponding tag opener in the stack of open elements, if - * it exists before reaching a special element, which provides a kind - * of boundary in the stack. For example, a `` should not - * close anything beyond its containing `P` or `DIV` element. - */ - foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { - if ( $token_name === $node->node_name ) { - break; - } + /* + * Find the corresponding tag opener in the stack of open elements, if + * it exists before reaching a special element, which provides a kind + * of boundary in the stack. For example, a `` should not + * close anything beyond its containing `P` or `DIV` element. + */ + foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { + if ( $token_name === $node->node_name ) { + break; + } + + if ( self::is_special( $node->node_name ) ) { + // This is a parse error, ignore the token. + return $this->step(); + } + } + + $this->generate_implied_end_tags( $token_name ); + if ( $node !== $this->state->stack_of_open_elements->current_node() ) { + // @todo Record parse error: this error doesn't impact parsing. + } + + foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { + $this->state->stack_of_open_elements->pop(); + if ( $node === $item ) { + return true; + } + } + } + } + + /** + * Parses next element in the 'in table' insertion mode. + * + * This internal function performs the 'in table' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intable + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_table(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'in table text' insertion mode. + * + * This internal function performs the 'in table text' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intabletext + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_table_text(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'in caption' insertion mode. + * + * This internal function performs the 'in caption' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-incaption + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_caption(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'in column group' insertion mode. + * + * This internal function performs the 'in column group' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-incolgroup + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_column_group(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'in table body' insertion mode. + * + * This internal function performs the 'in table body' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intbody + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_table_body(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'in row' insertion mode. + * + * This internal function performs the 'in row' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intr + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_row(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'in cell' insertion mode. + * + * This internal function performs the 'in cell' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intd + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_cell(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'in select' insertion mode. + * + * This internal function performs the 'in select' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inselect + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_select(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + + switch ( $op ) { + /* + * > Any other character token + */ + case '#text': + $current_token = $this->bookmarks[ $this->state->current_token->bookmark_name ]; + + /* + * > A character token that is U+0000 NULL + * + * If a text node only comprises null bytes then it should be + * entirely ignored and should not return to calling code. + */ + if ( + 1 <= $current_token->length && + "\x00" === $this->html[ $current_token->start ] && + strspn( $this->html, "\x00", $current_token->start, $current_token->length ) === $current_token->length + ) { + // Parse error: ignore the token. + return $this->step(); + } + + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A DOCTYPE token + */ + case 'html': + // Parse error: ignore the token. + return $this->step(); + + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': + return $this->step_in_body(); + + /* + * > A start tag whose tag name is "option" + */ + case '+OPTION': + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) { + $this->state->stack_of_open_elements->pop(); + } + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is "optgroup" + * > A start tag whose tag name is "hr" + * + * These rules are identical except for the treatment of the self-closing flag and + * the subsequent pop of the HR void element, all of which is handled elsewhere in the processor. + */ + case '+OPTGROUP': + case '+HR': + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) { + $this->state->stack_of_open_elements->pop(); + } + + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTGROUP' ) ) { + $this->state->stack_of_open_elements->pop(); + } + + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > An end tag whose tag name is "optgroup" + */ + case '-OPTGROUP': + $current_node = $this->state->stack_of_open_elements->current_node(); + if ( $current_node && 'OPTION' === $current_node->node_name ) { + foreach ( $this->state->stack_of_open_elements->walk_up( $current_node ) as $parent ) { + break; + } + if ( $parent && 'OPTGROUP' === $parent->node_name ) { + $this->state->stack_of_open_elements->pop(); + } + } + + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTGROUP' ) ) { + $this->state->stack_of_open_elements->pop(); + return true; + } + + // Parse error: ignore the token. + return $this->step(); + + /* + * > An end tag whose tag name is "option" + */ + case '-OPTION': + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) { + $this->state->stack_of_open_elements->pop(); + return true; + } + + // Parse error: ignore the token. + return $this->step(); + + /* + * > An end tag whose tag name is "select" + * > A start tag whose tag name is "select" + * + * > It just gets treated like an end tag. + */ + case '-SELECT': + case '+SELECT': + if ( ! $this->state->stack_of_open_elements->has_element_in_select_scope( 'SELECT' ) ) { + // Parse error: ignore the token. + return $this->step(); + } + $this->state->stack_of_open_elements->pop_until( 'SELECT' ); + $this->reset_insertion_mode(); + return true; + + /* + * > A start tag whose tag name is one of: "input", "keygen", "textarea" + * + * All three of these tags are considered a parse error when found in this insertion mode. + */ + case '+INPUT': + case '+KEYGEN': + case '+TEXTAREA': + if ( ! $this->state->stack_of_open_elements->has_element_in_select_scope( 'SELECT' ) ) { + // Ignore the token. + return $this->step(); + } + $this->state->stack_of_open_elements->pop_until( 'SELECT' ); + $this->reset_insertion_mode(); + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > A start tag whose tag name is one of: "script", "template" + * > An end tag whose tag name is "template" + */ + case '+SCRIPT': + case '+TEMPLATE': + case '-TEMPLATE': + return $this->step_in_head(); + } + + /* + * > Anything else + * > Parse error: ignore the token. + */ + return $this->step(); + } + + /** + * Parses next element in the 'in select in table' insertion mode. + * + * This internal function performs the 'in select in table' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-inselectintable + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_select_in_table(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'in template' insertion mode. + * + * This internal function performs the 'in template' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intemplate + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_template(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'after body' insertion mode. + * + * This internal function performs the 'after body' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-afterbody + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_after_body(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'in frameset' insertion mode. + * + * This internal function performs the 'in frameset' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-inframeset + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_frameset(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } + + /** + * Parses next element in the 'after frameset' insertion mode. + * + * This internal function performs the 'after frameset' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-afterframeset + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_after_frameset(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } - if ( self::is_special( $node->node_name ) ) { - // This is a parse error, ignore the token. - return $this->step(); - } - } + /** + * Parses next element in the 'after after body' insertion mode. + * + * This internal function performs the 'after after body' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#the-after-after-body-insertion-mode + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_after_after_body(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } - $this->generate_implied_end_tags( $token_name ); - if ( $node !== $this->state->stack_of_open_elements->current_node() ) { - // @todo Record parse error: this error doesn't impact parsing. - } + /** + * Parses next element in the 'after after frameset' insertion mode. + * + * This internal function performs the 'after after frameset' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#the-after-after-frameset-insertion-mode + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_after_after_frameset(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + } - foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { - $this->state->stack_of_open_elements->pop(); - if ( $node === $item ) { - return true; - } - } - } + /** + * Parses next element in the 'in foreign content' insertion mode. + * + * This internal function performs the 'in foreign content' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-inforeign + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_foreign_content(): bool { + $this->bail( "No support for parsing in the '{$this->state->insertion_mode}' state." ); } /* @@ -1243,11 +2219,15 @@ private function bookmark_token() { * * @return string|null Name of currently matched tag in input HTML, or `null` if none found. */ - public function get_tag() { + public function get_tag(): ?string { if ( null !== $this->last_error ) { return null; } + if ( $this->is_virtual() ) { + return $this->current_element->token->node_name; + } + $tag_name = parent::get_tag(); switch ( $tag_name ) { @@ -1263,6 +2243,286 @@ public function get_tag() { } } + /** + * 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.6.0 Subclassed for the HTML Processor. + * + * @return bool Whether the currently matched tag contains the self-closing flag. + */ + public function has_self_closing_flag(): bool { + return $this->is_virtual() ? false : parent::has_self_closing_flag(); + } + + /** + * Returns the node name represented by the token. + * + * This matches the DOM API value `nodeName`. Some values + * are static, such as `#text` for a text node, while others + * are dynamically generated from the token itself. + * + * Dynamic names: + * - Uppercase tag name for tag matches. + * - `html` for DOCTYPE declarations. + * + * Note that if the Tag Processor is not matched on a token + * then this function will return `null`, either because it + * hasn't yet found a token or because it reached the end + * of the document without matching a token. + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @return string|null Name of the matched token. + */ + public function get_token_name(): ?string { + return $this->is_virtual() + ? $this->current_element->token->node_name + : parent::get_token_name(); + } + + /** + * Indicates the kind of matched token, if any. + * + * This differs from `get_token_name()` in that it always + * returns a static string indicating the type, whereas + * `get_token_name()` may return values derived from the + * token itself, such as a tag name or processing + * instruction tag. + * + * Possible values: + * - `#tag` when matched on a tag. + * - `#text` when matched on a text node. + * - `#cdata-section` when matched on a CDATA node. + * - `#comment` when matched on a comment. + * - `#doctype` when matched on a DOCTYPE declaration. + * - `#presumptuous-tag` when matched on an empty tag closer. + * - `#funky-comment` when matched on a funky comment. + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @return string|null What kind of token is matched, or null. + */ + public function get_token_type(): ?string { + if ( $this->is_virtual() ) { + /* + * This logic comes from the Tag Processor. + * + * @todo It would be ideal not to repeat this here, but it's not clearly + * better to allow passing a token name to `get_token_type()`. + */ + $node_name = $this->current_element->token->node_name; + $starting_char = $node_name[0]; + if ( 'A' <= $starting_char && 'Z' >= $starting_char ) { + return '#tag'; + } + + if ( 'html' === $node_name ) { + return '#doctype'; + } + + return $node_name; + } + + return parent::get_token_type(); + } + + /** + * Returns the value of a requested attribute from a matched tag opener if that attribute exists. + * + * Example: + * + * $p = WP_HTML_Processor::create_fragment( '
Test
' ); + * $p->next_token() === 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.6.0 Subclassed for HTML Processor. + * + * @param string $name Name of attribute whose value is requested. + * @return string|true|null Value of attribute or `null` if not available. Boolean attributes return `true`. + */ + public function get_attribute( $name ) { + return $this->is_virtual() ? null : parent::get_attribute( $name ); + } + + /** + * Updates or creates a new attribute on the currently matched tag with the passed value. + * + * For boolean attributes special handling is provided: + * - When `true` is passed as the value, then only the attribute name is added to the tag. + * - When `false` is passed, the attribute gets removed if it existed before. + * + * For string attributes, the value is escaped using the `esc_attr` function. + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @param string $name The attribute name to target. + * @param string|bool $value The new attribute value. + * @return bool Whether an attribute value was set. + */ + public function set_attribute( $name, $value ): bool { + return $this->is_virtual() ? false : parent::set_attribute( $name, $value ); + } + + /** + * Remove an attribute from the currently-matched tag. + * + * @since 6.6.0 Subclassed for HTML Processor. + * + * @param string $name The attribute name to remove. + * @return bool Whether an attribute was removed. + */ + public function remove_attribute( $name ): bool { + return $this->is_virtual() ? false : parent::remove_attribute( $name ); + } + + /** + * Gets lowercase names of all attributes matching a given prefix in the current tag. + * + * Note that matching is case-insensitive. This is in accordance with the spec: + * + * > There must never be two or more attributes on + * > the same start tag whose names are an ASCII + * > case-insensitive match for each other. + * - HTML 5 spec + * + * Example: + * + * $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() === false; + * $p->get_attribute_names_with_prefix( 'data-' ) === null; + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @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. + */ + public function get_attribute_names_with_prefix( $prefix ): ?array { + return $this->is_virtual() ? null : parent::get_attribute_names_with_prefix( $prefix ); + } + + /** + * Adds a new class name to the currently matched tag. + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @param string $class_name The class name to add. + * @return bool Whether the class was set to be added. + */ + public function add_class( $class_name ): bool { + return $this->is_virtual() ? false : parent::add_class( $class_name ); + } + + /** + * Removes a class name from the currently matched tag. + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @param string $class_name The class name to remove. + * @return bool Whether the class was set to be removed. + */ + public function remove_class( $class_name ): bool { + return $this->is_virtual() ? false : parent::remove_class( $class_name ); + } + + /** + * Returns if a matched tag contains the given ASCII case-insensitive class name. + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @param string $wanted_class Look for this CSS class name, ASCII case-insensitive. + * @return bool|null Whether the matched tag contains the given class name, or null if not matched. + */ + public function has_class( $wanted_class ): ?bool { + return $this->is_virtual() ? null : parent::has_class( $wanted_class ); + } + + /** + * Generator for a foreach loop to step through each class name for the matched tag. + * + * This generator function is designed to be used inside a "foreach" loop. + * + * Example: + * + * $p = WP_HTML_Processor::create_fragment( "
" ); + * $p->next_tag(); + * foreach ( $p->class_list() as $class_name ) { + * echo "{$class_name} "; + * } + * // Outputs: "free lang-en " + * + * @since 6.6.0 Subclassed for the HTML Processor. + */ + public function class_list() { + return $this->is_virtual() ? null : parent::class_list(); + } + + /** + * Returns the modifiable text for a matched token, or an empty string. + * + * Modifiable text is text content that may be read and changed without + * changing the HTML structure of the document around it. This includes + * the contents of `#text` nodes in the HTML as well as the inner + * contents of HTML comments, Processing Instructions, and others, even + * though these nodes aren't part of a parsed DOM tree. They also contain + * the contents of SCRIPT and STYLE tags, of TEXTAREA tags, and of any + * other section in an HTML document which cannot contain HTML markup (DATA). + * + * If a token has no modifiable text then an empty string is returned to + * avoid needless crashing or type errors. An empty string does not mean + * that a token has modifiable text, and a token with modifiable text may + * have an empty string (e.g. a comment with no contents). + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @return string + */ + public function get_modifiable_text(): string { + return $this->is_virtual() ? '' : parent::get_modifiable_text(); + } + + /** + * Indicates what kind of comment produced the comment node. + * + * Because there are different kinds of HTML syntax which produce + * comments, the Tag Processor tracks and exposes this as a type + * for the comment. Nominally only regular HTML comments exist as + * they are commonly known, but a number of unrelated syntax errors + * also produce comments. + * + * @see self::COMMENT_AS_ABRUPTLY_CLOSED_COMMENT + * @see self::COMMENT_AS_CDATA_LOOKALIKE + * @see self::COMMENT_AS_INVALID_HTML + * @see self::COMMENT_AS_HTML_COMMENT + * @see self::COMMENT_AS_PI_NODE_LOOKALIKE + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @return string|null + */ + public function get_comment_type(): ?string { + return $this->is_virtual() ? null : parent::get_comment_type(); + } + /** * Removes a bookmark that is no longer needed. * @@ -1274,7 +2534,7 @@ public function get_tag() { * @param string $bookmark_name Name of the bookmark to remove. * @return bool Whether the bookmark already existed before removal. */ - public function release_bookmark( $bookmark_name ) { + public function release_bookmark( $bookmark_name ): bool { return parent::release_bookmark( "_{$bookmark_name}" ); } @@ -1295,7 +2555,7 @@ public function release_bookmark( $bookmark_name ) { * @param string $bookmark_name Jump to the place in the document identified by this bookmark name. * @return bool Whether the internal cursor was successfully moved to the bookmark's location. */ - public function seek( $bookmark_name ) { + public function seek( $bookmark_name ): bool { // Flush any pending updates to the document before beginning. $this->get_updated_html(); @@ -1357,8 +2617,16 @@ public function seek( $bookmark_name ) { } parent::seek( 'context-node' ); - $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_BODY; $this->state->frameset_ok = true; + $this->element_queue = array(); + $this->current_element = null; + + if ( isset( $this->context_node ) ) { + $this->breadcrumbs = array_slice( $this->breadcrumbs, 0, 2 ); + } else { + $this->breadcrumbs = array(); + } } // When moving forwards, reparse the document until reaching the same location as the original bookmark. @@ -1366,8 +2634,11 @@ public function seek( $bookmark_name ) { return true; } - while ( $this->step() ) { + while ( $this->next_token() ) { if ( $bookmark_starts_at === $this->bookmarks[ $this->state->current_token->bookmark_name ]->start ) { + while ( isset( $this->current_element ) && Gutenberg_HTML_Stack_Event_6_7::POP === $this->current_element->operation ) { + $this->current_element = array_shift( $this->element_queue ); + } return true; } } @@ -1455,7 +2726,7 @@ public function seek( $bookmark_name ) { * @param string $bookmark_name Identifies this particular bookmark. * @return bool Whether the bookmark was successfully created. */ - public function set_bookmark( $bookmark_name ) { + public function set_bookmark( $bookmark_name ): bool { return parent::set_bookmark( "_{$bookmark_name}" ); } @@ -1467,7 +2738,7 @@ public function set_bookmark( $bookmark_name ) { * @param string $bookmark_name Name to identify a bookmark that potentially exists. * @return bool Whether that bookmark exists. */ - public function has_bookmark( $bookmark_name ) { + public function has_bookmark( $bookmark_name ): bool { return parent::has_bookmark( "_{$bookmark_name}" ); } @@ -1484,7 +2755,7 @@ public function has_bookmark( $bookmark_name ) { * * @see https://html.spec.whatwg.org/#close-a-p-element */ - private function close_a_p_element() { + private function close_a_p_element(): void { $this->generate_implied_end_tags( 'P' ); $this->state->stack_of_open_elements->pop_until( 'P' ); } @@ -1493,23 +2764,31 @@ private function close_a_p_element() { * Closes elements that have implied end tags. * * @since 6.4.0 + * @since 6.7.0 Full spec support. * * @see https://html.spec.whatwg.org/#generate-implied-end-tags * * @param string|null $except_for_this_element Perform as if this element doesn't exist in the stack of open elements. */ - private function generate_implied_end_tags( $except_for_this_element = null ) { + private function generate_implied_end_tags( ?string $except_for_this_element = null ): void { $elements_with_implied_end_tags = array( 'DD', 'DT', 'LI', + 'OPTGROUP', + 'OPTION', 'P', + 'RB', + 'RP', + 'RT', + 'RTC', ); - $current_node = $this->state->stack_of_open_elements->current_node(); + $no_exclusions = ! isset( $except_for_this_element ); + while ( - $current_node && $current_node->node_name !== $except_for_this_element && - in_array( $this->state->stack_of_open_elements->current_node(), $elements_with_implied_end_tags, true ) + ( $no_exclusions || ! $this->state->stack_of_open_elements->current_node_is( $except_for_this_element ) ) && + in_array( $this->state->stack_of_open_elements->current_node()->node_name, $elements_with_implied_end_tags, true ) ) { $this->state->stack_of_open_elements->pop(); } @@ -1522,19 +2801,34 @@ private function generate_implied_end_tags( $except_for_this_element = null ) { * different from generating end tags in the normal sense. * * @since 6.4.0 + * @since 6.7.0 Full spec support. * * @see WP_HTML_Processor::generate_implied_end_tags * @see https://html.spec.whatwg.org/#generate-implied-end-tags */ - private function generate_implied_end_tags_thoroughly() { + private function generate_implied_end_tags_thoroughly(): void { $elements_with_implied_end_tags = array( + 'CAPTION', + 'COLGROUP', 'DD', 'DT', 'LI', + 'OPTGROUP', + 'OPTION', 'P', + 'RB', + 'RP', + 'RT', + 'RTC', + 'TBODY', + 'TD', + 'TFOOT', + 'TH', + 'THEAD', + 'TR', ); - while ( in_array( $this->state->stack_of_open_elements->current_node(), $elements_with_implied_end_tags, true ) ) { + while ( in_array( $this->state->stack_of_open_elements->current_node()->node_name, $elements_with_implied_end_tags, true ) ) { $this->state->stack_of_open_elements->pop(); } } @@ -1554,7 +2848,7 @@ private function generate_implied_end_tags_thoroughly() { * * @return bool Whether any formatting elements needed to be reconstructed. */ - private function reconstruct_active_formatting_elements() { + private function reconstruct_active_formatting_elements(): bool { /* * > If there are no entries in the list of active formatting elements, then there is nothing * > to reconstruct; stop this algorithm. @@ -1582,8 +2876,190 @@ private function reconstruct_active_formatting_elements() { return false; } - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( 'Cannot reconstruct active formatting elements when advancing and rewinding is required.' ); + $this->bail( 'Cannot reconstruct active formatting elements when advancing and rewinding is required.' ); + } + + /** + * Runs the reset the insertion mode appropriately algorithm. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#reset-the-insertion-mode-appropriately + */ + public function reset_insertion_mode(): void { + // Set the first node. + $first_node = null; + foreach ( $this->state->stack_of_open_elements->walk_down() as $first_node ) { + break; + } + + /* + * > 1. Let _last_ be false. + */ + $last = false; + foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { + /* + * > 2. Let _node_ be the last node in the stack of open elements. + * > 3. _Loop_: If _node_ is the first node in the stack of open elements, then set _last_ + * > to true, and, if the parser was created as part of the HTML fragment parsing + * > algorithm (fragment case), set node to the context element passed to + * > that algorithm. + * > ā€¦ + */ + if ( $node === $first_node ) { + $last = true; + if ( isset( $this->context_node ) ) { + $node = $this->context_node; + } + } + + switch ( $node->node_name ) { + /* + * > 4. If node is a `select` element, run these substeps: + * > 1. If _last_ is true, jump to the step below labeled done. + * > 2. Let _ancestor_ be _node_. + * > 3. _Loop_: If _ancestor_ is the first node in the stack of open elements, + * > jump to the step below labeled done. + * > 4. Let ancestor be the node before ancestor in the stack of open elements. + * > ā€¦ + * > 7. Jump back to the step labeled _loop_. + * > 8. _Done_: Switch the insertion mode to "in select" and return. + */ + case 'SELECT': + if ( ! $last ) { + foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $ancestor ) { + switch ( $ancestor->node_name ) { + /* + * > 5. If _ancestor_ is a `template` node, jump to the step below + * > labeled _done_. + */ + case 'TEMPLATE': + break 2; + + /* + * > 6. If _ancestor_ is a `table` node, switch the insertion mode to + * > "in select in table" and return. + */ + case 'TABLE': + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_SELECT_IN_TABLE; + return; + } + } + } + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_SELECT; + return; + + /* + * > 5. If _node_ is a `td` or `th` element and _last_ is false, then switch the + * > insertion mode to "in cell" and return. + */ + case 'TD': + case 'TH': + if ( ! $last ) { + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_CELL; + return; + } + break; + + /* + * > 6. If _node_ is a `tr` element, then switch the insertion mode to "in row" + * > and return. + */ + case 'TR': + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_ROW; + return; + + /* + * > 7. If _node_ is a `tbody`, `thead`, or `tfoot` element, then switch the + * > insertion mode to "in table body" and return. + */ + case 'TBODY': + case 'THEAD': + case 'TFOOT': + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_TABLE_BODY; + return; + + /* + * > 8. If _node_ is a `caption` element, then switch the insertion mode to + * > "in caption" and return. + */ + case 'CAPTION': + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_CAPTION; + return; + + /* + * > 9. If _node_ is a `colgroup` element, then switch the insertion mode to + * > "in column group" and return. + */ + case 'COLGROUP': + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_COLUMN_GROUP; + return; + + /* + * > 10. If _node_ is a `table` element, then switch the insertion mode to + * > "in table" and return. + */ + case 'TABLE': + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_TABLE; + return; + + /* + * > 11. If _node_ is a `template` element, then switch the insertion mode to the + * > current template insertion mode and return. + */ + case 'TEMPLATE': + $this->state->insertion_mode = end( $this->state->stack_of_template_insertion_modes ); + return; + + /* + * > 12. If _node_ is a `head` element and _last_ is false, then switch the + * > insertion mode to "in head" and return. + */ + case 'HEAD': + if ( ! $last ) { + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_HEAD; + return; + } + break; + + /* + * > 13. If _node_ is a `body` element, then switch the insertion mode to "in body" + * > and return. + */ + case 'BODY': + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_BODY; + return; + + /* + * > 14. If _node_ is a `frameset` element, then switch the insertion mode to + * > "in frameset" and return. (fragment case) + */ + case 'FRAMESET': + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_FRAMESET; + return; + + /* + * > 15. If _node_ is an `html` element, run these substeps: + * > 1. If the head element pointer is null, switch the insertion mode to + * > "before head" and return. (fragment case) + * > 2. Otherwise, the head element pointer is not null, switch the insertion + * > mode to "after head" and return. + */ + case 'HTML': + $this->state->insertion_mode = isset( $this->state->head_element ) + ? Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_AFTER_HEAD + : Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_BEFORE_HEAD; + return; + } + } + + /* + * > 16. If _last_ is true, then switch the insertion mode to "in body" + * > and return. (fragment case) + * + * This is only reachable if `$last` is true, as per the fragment parsing case. + */ + $this->state->insertion_mode = Gutenberg_HTML_Processor_State_6_7::INSERTION_MODE_IN_BODY; } /** @@ -1595,7 +3071,7 @@ private function reconstruct_active_formatting_elements() { * * @see https://html.spec.whatwg.org/#adoption-agency-algorithm */ - private function run_adoption_agency_algorithm() { + private function run_adoption_agency_algorithm(): void { $budget = 1000; $subject = $this->get_tag(); $current_node = $this->state->stack_of_open_elements->current_node(); @@ -1636,8 +3112,7 @@ private function run_adoption_agency_algorithm() { // > If there is no such element, then return and instead act as described in the "any other end tag" entry above. if ( null === $formatting_element ) { - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( 'Cannot run adoption agency when "any other end tag" is required.' ); + $this->bail( 'Cannot run adoption agency when "any other end tag" is required.' ); } // > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return. @@ -1689,12 +3164,10 @@ private function run_adoption_agency_algorithm() { } } - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( 'Cannot extract common ancestor in adoption agency algorithm.' ); + $this->bail( 'Cannot extract common ancestor in adoption agency algorithm.' ); } - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( 'Cannot run adoption agency when looping required.' ); + $this->bail( 'Cannot run adoption agency when looping required.' ); } /** @@ -1706,7 +3179,7 @@ private function run_adoption_agency_algorithm() { * * @param WP_HTML_Token $token Name of bookmark pointing to element in original input HTML. */ - private function insert_html_element( $token ) { + private function insert_html_element( Gutenberg_HTML_Token_6_7 $token ): void { $this->state->stack_of_open_elements->push( $token ); } @@ -1724,7 +3197,7 @@ private function insert_html_element( $token ) { * @param string $tag_name Name of element to check. * @return bool Whether the element of the given name is in the special category. */ - public static function is_special( $tag_name ) { + public static function is_special( $tag_name ): bool { $tag_name = strtoupper( $tag_name ); return ( @@ -1839,7 +3312,7 @@ public static function is_special( $tag_name ) { * @param string $tag_name Name of HTML tag to check. * @return bool Whether the given tag is an HTML Void Element. */ - public static function is_void( $tag_name ) { + public static function is_void( $tag_name ): bool { $tag_name = strtoupper( $tag_name ); return ( diff --git a/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-processor-state-6-7.php b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-processor-state-6-7.php new file mode 100644 index 00000000000000..5a3941c415dd79 --- /dev/null +++ b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-processor-state-6-7.php @@ -0,0 +1,417 @@ + + */ + public $stack_of_template_insertion_modes = array(); + + /** + * Tracks open elements while scanning HTML. + * + * This property is initialized in the constructor and never null. + * + * @since 6.4.0 + * + * @see https://html.spec.whatwg.org/#stack-of-open-elements + * + * @var WP_HTML_Open_Elements + */ + public $stack_of_open_elements; + + /** + * Tracks open formatting elements, used to handle mis-nested formatting element tags. + * + * This property is initialized in the constructor and never null. + * + * @since 6.4.0 + * + * @see https://html.spec.whatwg.org/#list-of-active-formatting-elements + * + * @var WP_HTML_Active_Formatting_Elements + */ + public $active_formatting_elements; + + /** + * Refers to the currently-matched tag, if any. + * + * @since 6.4.0 + * + * @var WP_HTML_Token|null + */ + public $current_token = null; + + /** + * Tree construction insertion mode. + * + * @since 6.4.0 + * + * @see https://html.spec.whatwg.org/#insertion-mode + * + * @var string + */ + public $insertion_mode = self::INSERTION_MODE_INITIAL; + + /** + * Context node initializing fragment parser, if created as a fragment parser. + * + * @since 6.4.0 + * + * @see https://html.spec.whatwg.org/#concept-frag-parse-context + * + * @var [string, array]|null + */ + public $context_node = null; + + /** + * HEAD element pointer. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#head-element-pointer + * + * @var WP_HTML_Token|null + */ + public $head_element = null; + + /** + * The frameset-ok flag indicates if a `FRAMESET` element is allowed in the current state. + * + * > The frameset-ok flag is set to "ok" when the parser is created. It is set to "not ok" after certain tokens are seen. + * + * @since 6.4.0 + * + * @see https://html.spec.whatwg.org/#frameset-ok-flag + * + * @var bool + */ + public $frameset_ok = true; + + /** + * Constructor - creates a new and empty state value. + * + * @since 6.4.0 + * + * @see WP_HTML_Processor + */ + public function __construct() { + $this->stack_of_open_elements = new Gutenberg_HTML_Open_Elements_6_7(); + $this->active_formatting_elements = new Gutenberg_HTML_Active_Formatting_Elements_6_7(); + } +} diff --git a/lib/compat/wordpress-6.5/html-api/class-gutenberg-html-span-6-5.php b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-span-6-7.php similarity index 91% rename from lib/compat/wordpress-6.5/html-api/class-gutenberg-html-span-6-5.php rename to lib/compat/wordpress-6.7/html-api/class-gutenberg-html-span-6-7.php index ed596f1266ab5d..5a55d9f5568746 100644 --- a/lib/compat/wordpress-6.5/html-api/class-gutenberg-html-span-6-5.php +++ b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-span-6-7.php @@ -22,7 +22,7 @@ * * @see WP_HTML_Tag_Processor */ -class Gutenberg_HTML_Span_6_5 { +class Gutenberg_HTML_Span_6_7 { /** * Byte offset into document where span begins. * @@ -49,7 +49,7 @@ class Gutenberg_HTML_Span_6_5 { * @param int $start Byte offset into document where replacement span begins. * @param int $length Byte length of span. */ - public function __construct( $start, $length ) { + public function __construct( int $start, int $length ) { $this->start = $start; $this->length = $length; } diff --git a/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-stack-event-6-7.php b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-stack-event-6-7.php new file mode 100644 index 00000000000000..30a8024b1c07c5 --- /dev/null +++ b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-stack-event-6-7.php @@ -0,0 +1,82 @@ +token = $token; + $this->operation = $operation; + $this->provenance = $provenance; + } +} diff --git a/lib/compat/wordpress-6.5/html-api/class-gutenberg-html-tag-processor-6-5.php b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-tag-processor-6-7.php similarity index 95% rename from lib/compat/wordpress-6.5/html-api/class-gutenberg-html-tag-processor-6-5.php rename to lib/compat/wordpress-6.7/html-api/class-gutenberg-html-tag-processor-6-7.php index 3bbcc5047a076f..0377706d5eaa63 100644 --- a/lib/compat/wordpress-6.5/html-api/class-gutenberg-html-tag-processor-6-5.php +++ b/lib/compat/wordpress-6.7/html-api/class-gutenberg-html-tag-processor-6-7.php @@ -15,10 +15,6 @@ * - Prune the whitespace when removing classes/attributes: e.g. "a b c" -> "c" not " c". * This would increase the size of the changes for some operations but leave more * natural-looking output HTML. - * - Properly decode HTML character references in `get_attribute()`. PHP's - * `html_entity_decode()` is wrong in a couple ways: it doesn't account for the - * no-ambiguous-ampersand rule, and it improperly handles the way semicolons may - * or may not terminate a character reference. * * @package WordPress * @subpackage HTML-API @@ -298,8 +294,8 @@ * * The special elements are: * - `SCRIPT` whose contents are treated as raw plaintext but supports a legacy - * style of including Javascript inside of HTML comments to avoid accidentally - * closing the SCRIPT from inside a Javascript string. E.g. `console.log( '' )`. + * style of including JavaScript inside of HTML comments to avoid accidentally + * closing the SCRIPT from inside a JavaScript string. E.g. `console.log( '' )`. * - `TITLE` and `TEXTAREA` whose contents are treated as plaintext and then any * character references are decoded. E.g. `1 < 2 < 3` becomes `1 < 2 < 3`. * - `IFRAME`, `NOSCRIPT`, `NOEMBED`, `NOFRAME`, `STYLE` whose contents are treated as @@ -412,7 +408,7 @@ * Introduces "special" elements which act like void elements, e.g. TITLE, STYLE. * Allows scanning through all tokens and processing modifiable text, where applicable. */ -class Gutenberg_HTML_Tag_Processor_6_5 { +class Gutenberg_HTML_Tag_Processor_6_7 { /** * The maximum number of bookmarks allowed to exist at * any given time. @@ -788,7 +784,7 @@ public function __construct( $html ) { * } * @return bool Whether a tag was matched. */ - public function next_tag( $query = null ) { + public function next_tag( $query = null ): bool { $this->parse_query( $query ); $already_found = 0; @@ -836,7 +832,7 @@ public function next_tag( $query = null ) { * * @return bool Whether a token was parsed. */ - public function next_token() { + public function next_token(): bool { return $this->base_class_next_token(); } @@ -855,7 +851,7 @@ public function next_token() { * * @return bool Whether a token was parsed. */ - private function base_class_next_token() { + private function base_class_next_token(): bool { $was_at = $this->bytes_already_parsed; $this->after_tag(); @@ -926,8 +922,8 @@ private function base_class_next_token() { return false; } $this->parser_state = self::STATE_MATCHED_TAG; - $this->token_length = $tag_ends_at - $this->token_starts_at; $this->bytes_already_parsed = $tag_ends_at + 1; + $this->token_length = $this->bytes_already_parsed - $this->token_starts_at; /* * For non-DATA sections which might contain text that looks like HTML tags but @@ -1013,7 +1009,7 @@ private function base_class_next_token() { */ $this->token_starts_at = $was_at; $this->token_length = $this->bytes_already_parsed - $this->token_starts_at; - $this->text_starts_at = $tag_ends_at + 1; + $this->text_starts_at = $tag_ends_at; $this->text_length = $this->tag_name_starts_at - $this->text_starts_at; $this->tag_name_starts_at = $tag_name_starts_at; $this->tag_name_length = $tag_name_length; @@ -1037,7 +1033,7 @@ private function base_class_next_token() { * * @return bool Whether the parse paused at the start of an incomplete token. */ - public function paused_at_incomplete_token() { + public function paused_at_incomplete_token(): bool { return self::STATE_INCOMPLETE_INPUT === $this->parser_state; } @@ -1116,7 +1112,7 @@ public function class_list() { * @param string $wanted_class Look for this CSS class name, ASCII case-insensitive. * @return bool|null Whether the matched tag contains the given class name, or null if not matched. */ - public function has_class( $wanted_class ) { + public function has_class( $wanted_class ): ?bool { if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { return null; } @@ -1213,7 +1209,7 @@ public function has_class( $wanted_class ) { * @param string $name Identifies this particular bookmark. * @return bool Whether the bookmark was successfully created. */ - public function set_bookmark( $name ) { + public function set_bookmark( $name ): bool { // It only makes sense to set a bookmark if the parser has paused on a concrete token. if ( self::STATE_COMPLETE === $this->parser_state || @@ -1231,7 +1227,7 @@ public function set_bookmark( $name ) { return false; } - $this->bookmarks[ $name ] = new Gutenberg_HTML_Span_6_5( $this->token_starts_at, $this->token_length ); + $this->bookmarks[ $name ] = new Gutenberg_HTML_Span_6_7( $this->token_starts_at, $this->token_length ); return true; } @@ -1246,7 +1242,7 @@ public function set_bookmark( $name ) { * @param string $name Name of the bookmark to remove. * @return bool Whether the bookmark already existed before removal. */ - public function release_bookmark( $name ) { + public function release_bookmark( $name ): bool { if ( ! array_key_exists( $name, $this->bookmarks ) ) { return false; } @@ -1266,7 +1262,7 @@ public function release_bookmark( $name ) { * @param string $tag_name The uppercase tag name which will close the RAWTEXT region. * @return bool Whether an end to the RAWTEXT region was found before the end of the document. */ - private function skip_rawtext( $tag_name ) { + private function skip_rawtext( string $tag_name ): bool { /* * These two functions distinguish themselves on whether character references are * decoded, and since functionality to read the inner markup isn't supported, it's @@ -1285,7 +1281,7 @@ private function skip_rawtext( $tag_name ) { * @param string $tag_name The uppercase 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. */ - private function skip_rcdata( $tag_name ) { + private function skip_rcdata( string $tag_name ): bool { $html = $this->html; $doc_length = strlen( $html ); $tag_length = strlen( $tag_name ); @@ -1373,7 +1369,7 @@ private function skip_rcdata( $tag_name ) { * * @return bool Whether the script tag was closed before the end of the document. */ - private function skip_script_data() { + private function skip_script_data(): bool { $state = 'unescaped'; $html = $this->html; $doc_length = strlen( $html ); @@ -1520,7 +1516,7 @@ private function skip_script_data() { * * @return bool Whether a tag was found before the end of the document. */ - private function parse_next_tag() { + private function parse_next_tag(): bool { $this->after_tag(); $html = $this->html; @@ -1528,21 +1524,10 @@ private function parse_next_tag() { $was_at = $this->bytes_already_parsed; $at = $was_at; - while ( false !== $at && $at < $doc_length ) { + while ( $at < $doc_length ) { $at = strpos( $html, '<', $at ); - - /* - * This does not imply an incomplete parse; it indicates that there - * can be nothing left in the document other than a #text node. - */ if ( false === $at ) { - $this->parser_state = self::STATE_TEXT_NODE; - $this->token_starts_at = $was_at; - $this->token_length = strlen( $html ) - $was_at; - $this->text_starts_at = $was_at; - $this->text_length = $this->token_length; - $this->bytes_already_parsed = strlen( $html ); - return true; + break; } if ( $at > $was_at ) { @@ -1558,19 +1543,9 @@ private function parse_next_tag() { * * @see https://html.spec.whatwg.org/#tag-open-state */ - if ( strlen( $html ) > $at + 1 ) { - $next_character = $html[ $at + 1 ]; - $at_another_node = ( - '!' === $next_character || - '/' === $next_character || - '?' === $next_character || - ( 'A' <= $next_character && $next_character <= 'Z' ) || - ( 'a' <= $next_character && $next_character <= 'z' ) - ); - if ( ! $at_another_node ) { - ++$at; - continue; - } + if ( 1 !== strspn( $html, '!/?abcdefghijklmnopqrstuvwxyzABCEFGHIJKLMNOPQRSTUVWXYZ', $at + 1, 1 ) ) { + ++$at; + continue; } $this->parser_state = self::STATE_TEXT_NODE; @@ -1629,16 +1604,12 @@ private function parse_next_tag() { * `is_closing_tag && '!' === $html[ $at + 1 ] ) { /* * ` diff --git a/packages/a11y/package.json b/packages/a11y/package.json index 0e9590381a290a..d3337485a18284 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/a11y", - "version": "4.2.0", + "version": "4.8.0", "description": "Accessibility (a11y) utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,6 +28,7 @@ "module": "build-module/index.js", "react-native": "src/index", "types": "build-types", + "wpScriptModuleExports": "./build-module/module/index.js", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/dom-ready": "file:../dom-ready", diff --git a/packages/a11y/src/index.js b/packages/a11y/src/index.js index 957c76500c4344..cdc3be10545155 100644 --- a/packages/a11y/src/index.js +++ b/packages/a11y/src/index.js @@ -6,10 +6,10 @@ import domReady from '@wordpress/dom-ready'; /** * Internal dependencies */ -import addIntroText from './add-intro-text'; -import addContainer from './add-container'; -import clear from './clear'; -import filterMessage from './filter-message'; +import addContainer from './script/add-container'; +import addIntroText from './script/add-intro-text'; + +export { speak } from './shared/index'; /** * Create the live regions. @@ -38,51 +38,3 @@ export function setup() { * Run setup on domReady. */ domReady( setup ); - -/** - * Allows you to easily announce dynamic interface updates to screen readers using ARIA live regions. - * This module is inspired by the `speak` function in `wp-a11y.js`. - * - * @param {string} message The message to be announced by assistive technologies. - * @param {string} [ariaLive] The politeness level for aria-live; default: 'polite'. - * - * @example - * ```js - * import { speak } from '@wordpress/a11y'; - * - * // For polite messages that shouldn't interrupt what screen readers are currently announcing. - * speak( 'The message you want to send to the ARIA live region' ); - * - * // For assertive messages that should interrupt what screen readers are currently announcing. - * speak( 'The message you want to send to the ARIA live region', 'assertive' ); - * ``` - */ -export function speak( message, ariaLive ) { - /* - * Clear previous messages to allow repeated strings being read out and hide - * the explanatory text from assistive technologies. - */ - clear(); - - message = filterMessage( message ); - - const introText = document.getElementById( 'a11y-speak-intro-text' ); - const containerAssertive = document.getElementById( - 'a11y-speak-assertive' - ); - const containerPolite = document.getElementById( 'a11y-speak-polite' ); - - if ( containerAssertive && ariaLive === 'assertive' ) { - containerAssertive.textContent = message; - } else if ( containerPolite ) { - containerPolite.textContent = message; - } - - /* - * Make the explanatory text available to assistive technologies by removing - * the 'hidden' HTML attribute. - */ - if ( introText ) { - introText.removeAttribute( 'hidden' ); - } -} diff --git a/packages/a11y/src/index.native.js b/packages/a11y/src/index.native.js index f6f53b6343adb0..e17597a8b2747d 100644 --- a/packages/a11y/src/index.native.js +++ b/packages/a11y/src/index.native.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import filterMessage from './filter-message'; +import filterMessage from './shared/filter-message'; /** * Update the ARIA live notification area text node. diff --git a/packages/a11y/src/module/index.ts b/packages/a11y/src/module/index.ts new file mode 100644 index 00000000000000..a06882f068dd3a --- /dev/null +++ b/packages/a11y/src/module/index.ts @@ -0,0 +1,11 @@ +/** + * Internal dependencies + */ +export { speak } from '../shared/index'; + +/** + * This no-op function is exported to provide compatibility with the `wp-a11y` Script. + * + * Filters should inject the relevant HTML on page load instead of requiring setup. + */ +export const setup = () => {}; diff --git a/packages/a11y/src/add-container.js b/packages/a11y/src/script/add-container.js similarity index 100% rename from packages/a11y/src/add-container.js rename to packages/a11y/src/script/add-container.js diff --git a/packages/a11y/src/add-intro-text.js b/packages/a11y/src/script/add-intro-text.ts similarity index 100% rename from packages/a11y/src/add-intro-text.js rename to packages/a11y/src/script/add-intro-text.ts diff --git a/packages/a11y/src/test/add-container.test.js b/packages/a11y/src/script/test/add-container.test.js similarity index 100% rename from packages/a11y/src/test/add-container.test.js rename to packages/a11y/src/script/test/add-container.test.js diff --git a/packages/a11y/src/clear.js b/packages/a11y/src/shared/clear.js similarity index 100% rename from packages/a11y/src/clear.js rename to packages/a11y/src/shared/clear.js diff --git a/packages/a11y/src/filter-message.js b/packages/a11y/src/shared/filter-message.js similarity index 100% rename from packages/a11y/src/filter-message.js rename to packages/a11y/src/shared/filter-message.js diff --git a/packages/a11y/src/shared/index.js b/packages/a11y/src/shared/index.js new file mode 100644 index 00000000000000..2b6353720d0add --- /dev/null +++ b/packages/a11y/src/shared/index.js @@ -0,0 +1,53 @@ +/** + * Internal dependencies + */ +import clear from './clear'; +import filterMessage from './filter-message'; + +/** + * Allows you to easily announce dynamic interface updates to screen readers using ARIA live regions. + * This module is inspired by the `speak` function in `wp-a11y.js`. + * + * @param {string} message The message to be announced by assistive technologies. + * @param {'polite'|'assertive'} [ariaLive] The politeness level for aria-live; default: 'polite'. + * + * @example + * ```js + * import { speak } from '@wordpress/a11y'; + * + * // For polite messages that shouldn't interrupt what screen readers are currently announcing. + * speak( 'The message you want to send to the ARIA live region' ); + * + * // For assertive messages that should interrupt what screen readers are currently announcing. + * speak( 'The message you want to send to the ARIA live region', 'assertive' ); + * ``` + */ +export function speak( message, ariaLive ) { + /* + * Clear previous messages to allow repeated strings being read out and hide + * the explanatory text from assistive technologies. + */ + clear(); + + message = filterMessage( message ); + + const introText = document.getElementById( 'a11y-speak-intro-text' ); + const containerAssertive = document.getElementById( + 'a11y-speak-assertive' + ); + const containerPolite = document.getElementById( 'a11y-speak-polite' ); + + if ( containerAssertive && ariaLive === 'assertive' ) { + containerAssertive.textContent = message; + } else if ( containerPolite ) { + containerPolite.textContent = message; + } + + /* + * Make the explanatory text available to assistive technologies by removing + * the 'hidden' HTML attribute. + */ + if ( introText ) { + introText.removeAttribute( 'hidden' ); + } +} diff --git a/packages/a11y/src/test/clear.test.js b/packages/a11y/src/shared/test/clear.test.js similarity index 100% rename from packages/a11y/src/test/clear.test.js rename to packages/a11y/src/shared/test/clear.test.js diff --git a/packages/a11y/src/test/filter-message.test.js b/packages/a11y/src/shared/test/filter-message.test.js similarity index 100% rename from packages/a11y/src/test/filter-message.test.js rename to packages/a11y/src/shared/test/filter-message.test.js diff --git a/packages/a11y/src/test/index.test.js b/packages/a11y/src/test/index.test.js index 4815baa2205047..0f6b9d0bd572ed 100644 --- a/packages/a11y/src/test/index.test.js +++ b/packages/a11y/src/test/index.test.js @@ -7,10 +7,10 @@ import domReady from '@wordpress/dom-ready'; * Internal dependencies */ import { setup, speak } from '../'; -import clear from '../clear'; -import filterMessage from '../filter-message'; +import clear from '../shared/clear'; +import filterMessage from '../shared/filter-message'; -jest.mock( '../clear', () => { +jest.mock( '../shared/clear', () => { return jest.fn(); } ); jest.mock( '@wordpress/dom-ready', () => { @@ -18,7 +18,7 @@ jest.mock( '@wordpress/dom-ready', () => { callback(); } ); } ); -jest.mock( '../filter-message', () => { +jest.mock( '../shared/filter-message', () => { return jest.fn( ( message ) => { return message; } ); diff --git a/packages/annotations/CHANGELOG.md b/packages/annotations/CHANGELOG.md index b92426072f1351..7ac3e50c3fd8b6 100644 --- a/packages/annotations/CHANGELOG.md +++ b/packages/annotations/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +## 3.8.0 (2024-09-19) + +## 3.7.0 (2024-09-05) + +## 3.6.0 (2024-08-21) + +## 3.5.0 (2024-08-07) + +## 3.4.0 (2024-07-24) + +## 3.3.0 (2024-07-10) + ## 3.2.0 (2024-06-26) ## 3.1.0 (2024-06-15) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 2f030df0e6cb96..edaae0ca7617bd 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "3.2.0", + "version": "3.8.0", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/api-fetch/CHANGELOG.md b/packages/api-fetch/CHANGELOG.md index 6abbab65d6a821..7c333170b6cc11 100644 --- a/packages/api-fetch/CHANGELOG.md +++ b/packages/api-fetch/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +## 7.8.0 (2024-09-19) + +## 7.7.0 (2024-09-05) + +## 7.6.0 (2024-08-21) + +## 7.5.0 (2024-08-07) + +## 7.4.0 (2024-07-24) + +## 7.3.0 (2024-07-10) + ## 7.2.0 (2024-06-26) ## 7.1.0 (2024-06-15) diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 112d5d8fb501d4..54fb852a75402e 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "7.2.0", + "version": "7.8.0", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/api-fetch/src/middlewares/media-upload.js b/packages/api-fetch/src/middlewares/media-upload.js index 417abf775db636..ddd0be4e4ab436 100644 --- a/packages/api-fetch/src/middlewares/media-upload.js +++ b/packages/api-fetch/src/middlewares/media-upload.js @@ -63,6 +63,11 @@ const mediaUploadMiddleware = ( options, next ) => { return next( { ...options, parse: false } ) .catch( ( response ) => { + // `response` could actually be an error thrown by `defaultFetchHandler`. + if ( ! response.headers ) { + return Promise.reject( response ); + } + const attachmentId = response.headers.get( 'x-wp-upload-attachment-id' ); diff --git a/packages/autop/CHANGELOG.md b/packages/autop/CHANGELOG.md index cfff0201fc277e..4bb494dcfff5c0 100644 --- a/packages/autop/CHANGELOG.md +++ b/packages/autop/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +## 4.8.0 (2024-09-19) + +## 4.7.0 (2024-09-05) + +## 4.6.0 (2024-08-21) + +## 4.5.0 (2024-08-07) + +## 4.4.0 (2024-07-24) + +## 4.3.0 (2024-07-10) + ## 4.2.0 (2024-06-26) ## 4.1.0 (2024-06-15) diff --git a/packages/autop/package.json b/packages/autop/package.json index a232e196e3c241..49ddffcf79f9ce 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/autop", - "version": "4.2.0", + "version": "4.8.0", "description": "WordPress's automatic paragraph functions `autop` and `removep`.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index 7bb25199ff8471..5a68cf8eb40400 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +## 5.8.0 (2024-09-19) + +## 5.7.0 (2024-09-05) + +## 5.6.0 (2024-08-21) + +## 5.5.0 (2024-08-07) + +## 5.4.0 (2024-07-24) + +## 5.3.0 (2024-07-10) + ## 5.2.0 (2024-06-26) ## 5.1.0 (2024-06-15) diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index 88d09cecebf374..d06a750eb0d91e 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "5.2.0", + "version": "5.8.0", "description": "Babel transform plugin for automatically injecting an import to be used as the pragma for the React JSX Transform plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-makepot/CHANGELOG.md b/packages/babel-plugin-makepot/CHANGELOG.md index 19214e1c791779..d878e1fb6b020c 100644 --- a/packages/babel-plugin-makepot/CHANGELOG.md +++ b/packages/babel-plugin-makepot/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +## 6.8.0 (2024-09-19) + +## 6.7.0 (2024-09-05) + +## 6.6.0 (2024-08-21) + +## 6.5.0 (2024-08-07) + +## 6.4.0 (2024-07-24) + +## 6.3.0 (2024-07-10) + ## 6.2.0 (2024-06-26) ## 6.1.0 (2024-06-15) diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 1ff539297bddb2..5c9cbb1384796b 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-makepot", - "version": "6.2.0", + "version": "6.8.0", "description": "WordPress Babel internationalization (i18n) plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index 56b58422f47f35..b31be6ffd8d56d 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -2,6 +2,26 @@ ## Unreleased +### Bug Fixes + +- Fix a bug in 8.8.1 due to missing files in the published package ([#65481](https://github.com/WordPress/gutenberg/pull/65481)). + +## 8.8.0 (2024-09-19) + +### Internal + +- Added `addPolyfillComments` option. When used, it will automatically add magic comments to mark files that need `wp-polyfill` ([#65292](https://github.com/WordPress/gutenberg/pull/65292)). + +## 8.7.0 (2024-09-05) + +## 8.6.0 (2024-08-21) + +## 8.5.0 (2024-08-07) + +## 8.4.0 (2024-07-24) + +## 8.3.0 (2024-07-10) + ## 8.2.0 (2024-06-26) ## 8.1.0 (2024-06-15) diff --git a/packages/babel-preset-default/README.md b/packages/babel-preset-default/README.md index fb853a73ea3c8b..301d2583dcf865 100644 --- a/packages/babel-preset-default/README.md +++ b/packages/babel-preset-default/README.md @@ -43,7 +43,7 @@ For example, if you'd like to use a new language feature proposal which has not There is a complementary `build/polyfill.js` (minified version ā€“ `build/polyfill.min.js`) file available that polyfills ECMAScript features missing in the [browsers supported](https://make.wordpress.org/core/handbook/best-practices/browser-support/) by the WordPress project ([#31279](https://github.com/WordPress/gutenberg/pull/31279)). It's a drop-in replacement for the deprecated `@babel/polyfill` package, and it's also based on [`core-js`](https://github.com/zloirock/core-js) project. -This needs to be included before all your compiled Babel code. You can either prepend it to your compiled code or include it in a ` + + + +HTML; + + $expected = << + + + + + + + + + + + +HTML; + + $actual = gutenberg_add_crossorigin_attributes( $html ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::gutenberg_override_media_templates + */ + public function test_gutenberg_override_media_templates(): void { + if ( ! function_exists( '\wp_print_media_templates' ) ) { + require_once ABSPATH . WPINC . '/media-template.php'; + } + + gutenberg_override_media_templates(); + + ob_start(); + do_action( 'admin_footer' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound + $output = ob_get_clean(); + + $this->assertStringContainsString( '