diff --git a/.eslintignore b/.eslintignore
index cfc995e9e9aef4..416bce0a8e0567 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,6 +1,6 @@
build
build-module
node_modules
-test/e2e/test-plugins
+packages/e2e-tests/plugins
vendor
packages/block-serialization-spec-parser/parser.js
diff --git a/.eslintrc.js b/.eslintrc.js
index feced45620657e..2f4a4f05e3e772 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -19,7 +19,7 @@ const majorMinorRegExp = escapeRegExp( version.replace( /\.\d+$/, '' ) ) + '(\\.
module.exports = {
root: true,
extends: [
- '@wordpress/eslint-config',
+ 'plugin:@wordpress/eslint-plugin/recommended',
'plugin:jest/recommended',
],
rules: {
@@ -32,77 +32,6 @@ module.exports = {
selector: 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]',
message: 'Path access on WordPress dependencies is not allowed.',
},
- {
- selector: 'ImportDeclaration[source.value=/^api-fetch(\\u002F|$)/]',
- message: 'Use @wordpress/api-fetch as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^blob(\\u002F|$)/]',
- message: 'Use @wordpress/blob as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^block-serialization-spec-parser(\\u002F|$)/]',
- message: 'Use @wordpress/block-serialization-spec-parser as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^blocks(\\u002F|$)/]',
- message: 'Use @wordpress/blocks as import path instead.',
- },{
- selector: 'ImportDeclaration[source.value=/^components(\\u002F|$)/]',
- message: 'Use @wordpress/components as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^data(\\u002F|$)/]',
- message: 'Use @wordpress/data as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^date(\\u002F|$)/]',
- message: 'Use @wordpress/date as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^deprecated(\\u002F|$)/]',
- message: 'Use @wordpress/deprecated as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^dom(\\u002F|$)/]',
- message: 'Use @wordpress/dom as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^editor(\\u002F|$)/]',
- message: 'Use @wordpress/editor as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^element(\\u002F|$)/]',
- message: 'Use @wordpress/element as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^keycodes(\\u002F|$)/]',
- message: 'Use @wordpress/keycodes as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^nux(\\u002F|$)/]',
- message: 'Use @wordpress/nux as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^edit-post(\\u002F|$)/]',
- message: 'Use @wordpress/edit-post as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^viewport(\\u002F|$)/]',
- message: 'Use @wordpress/viewport as import path instead.',
- },
- {
- selector: 'ImportDeclaration[source.value=/^plugins(\\u002F|$)/]',
- message: 'Use @wordpress/plugins as import path instead.',
- },
- {
- "selector": "ImportDeclaration[source.value=/^core-data$/]",
- "message": "Use @wordpress/core-data as import path instead."
- },
- {
- "selector": "ImportDeclaration[source.value=/^block-library$/]",
- "message": "Use @wordpress/block-library as import path instead."
- },
{
selector: 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' + majorMinorRegExp + '/]',
message: 'Deprecated functions must be removed before releasing this version.',
@@ -182,7 +111,7 @@ module.exports = {
},
overrides: [
{
- files: [ 'test/e2e/**/*.js' ],
+ files: [ 'packages/e2e-test*/**/*.js' ],
env: {
browser: true,
},
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000000000..5a0454ca08e56f
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,95 @@
+# Data
+/packages/api-fetch @youknowriad @aduth @nerrad @mmtr
+/packages/core-data @youknowriad @gziolo @aduth @nerrad @noisysocks
+/packages/data @youknowriad @aduth @nerrad @coderkevin
+/packages/redux-routine @youknowriad @aduth @nerrad
+
+# Blocks
+/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan @noisysocks @notnownikki
+
+# Editor
+/packages/annotations @youknowriad @gziolo @aduth @atimmer
+/packages/autop @youknowriad @aduth
+/packages/block-serialization-spec-parser @youknowriad @gziolo @aduth @dmsnell
+/packages/block-serialization-default-parser @youknowriad @gziolo @aduth @dmsnell
+/packages/blocks @youknowriad @gziolo @aduth @noisysocks
+/packages/edit-post @youknowriad @gziolo @talldan @noisysocks
+/packages/editor @youknowriad @gziolo @talldan @noisysocks
+/packages/list-reusable-blocks @youknowriad @aduth @noisysocks
+/packages/shortcode @youknowriad @aduth
+
+# Widgets
+/packages/edit-widgets @youknowriad
+
+# Tooling
+/bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
+/packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw
+/packages/babel-plugin-makepot @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
+/packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw
+/packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
+/packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
+/packages/docgen @nosolosw @mkaz
+/packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
+/packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @talldan
+/packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw
+/packages/jest-console @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
+/packages/jest-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
+/packages/jest-puppeteer-axe @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
+/packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
+/packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
+/packages/postcss-themes @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
+/packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw @mkaz
+
+# UI Components
+/packages/components @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks @chrisvanpatten
+/packages/compose @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks
+/packages/element @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks
+/packages/notices @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks
+/packages/nux @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks
+/packages/viewport @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks
+
+# Utilities
+/packages/a11y @youknowriad @gziolo @aduth
+/packages/blob @youknowriad @gziolo @aduth
+/packages/date @youknowriad @gziolo @aduth
+/packages/deprecated @youknowriad @gziolo @aduth
+/packages/dom @youknowriad @gziolo @aduth @nosolosw
+/packages/dom-ready @youknowriad @gziolo @aduth
+/packages/escape-html @youknowriad @gziolo @aduth
+/packages/html-entities @youknowriad @gziolo @aduth
+/packages/i18n @youknowriad @aduth @swissspidy
+/packages/is-shallow-equal @youknowriad @gziolo @aduth
+/packages/keycodes @youknowriad @gziolo @aduth @talldan
+/packages/priority-queue @youknowriad @gziolo @aduth
+/packages/token-list @youknowriad @gziolo @aduth
+/packages/url @youknowriad @gziolo @aduth @talldan
+/packages/wordcount @youknowriad @gziolo @aduth
+
+# Extensibility
+/packages/hooks @youknowriad @gziolo @aduth @adamsilverstein
+/packages/plugins @youknowriad @gziolo @aduth @adamsilverstein
+
+# Rich Text
+/packages/format-library @youknowriad @gziolo @aduth @iseulde @jorgefilipecosta
+/packages/rich-text @youknowriad @aduth @iseulde @jorgefilipecosta
+/packages/editor/src/components/rich-text @youknowriad @aduth @iseulde @jorgefilipecosta
+
+# PHP
+/lib @youknowriad @gziolo @aduth
+
+# Documentation
+/docs @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki
+
+# Styles
+*.scss @chrisvanpatten
+
+# Native (Unowned)
+*.native.js @ghost
+*.android.js @ghost
+*.ios.js @ghost
+*.native.scss @ghost
+*.android.scss @ghost
+*.ios.scss @ghost
+
+# Project Management
+/.github @youknowriad @mapk @karmatosed
diff --git a/.github/ISSUE_TEMPLATE/Custom.md b/.github/ISSUE_TEMPLATE/Custom.md
index 89783e5bbea906..196cdeb63305fa 100644
--- a/.github/ISSUE_TEMPLATE/Custom.md
+++ b/.github/ISSUE_TEMPLATE/Custom.md
@@ -1,13 +1,17 @@
---
-name: Question
-about: Questions or 'how to' about Gutenberg
+name: Help Request
+about: Please post help requests or ‘how to’ questions in support channels first
---
-If you have a question you have a few places that you can ask this:
+Search first! Your issue may have already been reported.
-- Support Forums: https://wordpress.org/support/plugin/gutenberg
-- Handbook: https://wordpress.org/gutenberg/handbook
-- https://chat.wordpress.org #core-editor
+For general help requests, please post in the support forum at https://wordpress.org/support/forum/how-to-and-troubleshooting/.
-If you are unable to ask in those places you can ask here, however you will get faster responses through those recommended places.
+Technical help requests have their own section of the support forum at https://wordpress.org/support/forum/wp-advanced/.
+
+You may also ask for technical support at https://wordpress.stackexchange.com/.
+
+Please make sure you have checked the Handbook at https://wordpress.org/gutenberg/handbook before asking your question.
+
+Thank you!
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index a968800c43fed1..cb60c1c2f0354d 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -19,3 +19,4 @@
- [ ] My code follows the WordPress code style.
- [ ] My code follows the accessibility standards.
- [ ] My code has proper inline documentation.
+- [ ] I've included developer documentation if appropriate.
diff --git a/.gitignore b/.gitignore
index 5ba04948b2faf7..2fb814f9616e02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,8 +4,6 @@ build-module
build-style
node_modules
gutenberg.zip
-languages/gutenberg.pot
-/languages/gutenberg-translations.php
# Directories/files that may appear in your environment
.DS_Store
diff --git a/.travis.yml b/.travis.yml
index 2dc47aa01d6422..7b87e251a25258 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
dist: trusty
-language: php
+language: generic
services:
- docker
@@ -13,56 +13,89 @@ notifications:
cache:
directories:
- $HOME/.composer/cache
- - $HOME/.phpbrew
+ - $HOME/.jest-cache
- $HOME/.npm
-
-before_install:
- - nvm install && nvm use
- - npm install npm -g
+ - $HOME/.nvm/.cache
+ - $HOME/.phpbrew
branches:
only:
- master
+before_install:
+ - nvm install
+
jobs:
include:
- - stage: test
+ - name: JS unit tests
env: WP_VERSION=latest
+ before_install:
+ - nvm install --latest-npm
+ install:
+ - npm ci
script:
- - npm install || exit 1
- - npm run ci || exit 1
+ - npm run build
+ - npm run lint
+ - npm run check-local-changes
+ - npm run check-licenses
+ - npm run test-unit -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache"
- - stage: test
+ - name: PHP unit tests (Docker)
env: WP_VERSION=latest DOCKER=true
script:
- ./bin/run-wp-unit-tests.sh
- - stage: test
+ - name: PHP unit tests (PHP 5.6)
+ language: php
php: 5.6
env: WP_VERSION=latest
script:
- ./bin/run-wp-unit-tests.sh
if: branch = master and type != "pull_request"
- - stage: test
- php: 7.1
+ - name: PHP unit tests (PHP 5.3)
env: WP_VERSION=latest SWITCH_TO_PHP=5.3
script:
- ./bin/run-wp-unit-tests.sh
if: branch = master and type != "pull_request"
- - stage: test
- php: 7.1
+ - name: PHP unit tests (PHP 5.2)
env: WP_VERSION=latest SWITCH_TO_PHP=5.2
script:
- ./bin/run-wp-unit-tests.sh
- - stage: test
- env: WP_VERSION=latest
+ - name: E2E tests (Admin with plugins) (1/2)
+ env: WP_VERSION=latest POPULAR_PLUGINS=true
+ install:
+ - ./bin/setup-local-env.sh
+ script:
+ - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests
+ - npm run build
+ - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests )
+
+ - name: E2E tests (Admin with plugins) (2/2)
+ env: WP_VERSION=latest POPULAR_PLUGINS=true
+ install:
+ - ./bin/setup-local-env.sh
+ script:
+ - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests
+ - npm run build
+ - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests )
+
+ - name: E2E tests (Author without plugins) (1/2)
+ env: WP_VERSION=latest E2E_ROLE=author
+ install:
+ - ./bin/setup-local-env.sh
script:
- - ./bin/run-e2e-tests.sh || exit 1
+ - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests
+ - npm run build
+ - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests )
- - stage: test
+ - name: E2E tests (Author without plugins) (2/2)
env: WP_VERSION=latest E2E_ROLE=author
+ install:
+ - ./bin/setup-local-env.sh
script:
- - ./bin/run-e2e-tests.sh || exit 1
+ - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests
+ - npm run build
+ - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests )
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4ccb59aa2df443..576e78b27a3eeb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,12 +1,14 @@
# Contributing
-Thank you for thinking about contributing to WordPress' Gutenberg project! If you're unsure of anything, know that you're 💯 welcome to submit an issue or pull request on any topic. The worst that can happen is that you'll be politely directed to the best location to ask your question, or to change something in your pull request. We appreciate any sort of contribution, and don't want a wall of rules to get in the way of that.
+Thank you for thinking about contributing to WordPress' Gutenberg project! If you're unsure of anything, know that you're 💯 welcome to submit an issue or pull request on any topic. The worst that can happen is that you'll be politely directed to the best location to ask your question or to change something in your pull request. We appreciate any sort of contribution and don't want a wall of rules to get in the way of that.
-As with all WordPress projects, we want to ensure a welcoming environment for everyone. With that in mind, all contributors are expected to follow our [Code of Conduct](CODE_OF_CONDUCT.md).
+As with all WordPress projects, we want to ensure a welcoming environment for everyone. With that in mind, all contributors are expected to follow our [Code of Conduct](/CODE_OF_CONDUCT.md).
-Before contributing, we encourage you to read our [Contributing Policy](CONTRIBUTING.md) (you're here already!) and our [Handbook](https://wordpress.org/gutenberg/handbook/). If you have any questions on any of these, please open an issue so we can help clarify them.
+Before contributing, we encourage you to review the [Contributor Handbook](https://wordpress.org/gutenberg/handbook/contributors/). If you have any questions, please ask, either in Slack or open an issue in GitHub so we can help clarify.
-All WordPress projects are [licensed under the GPLv2+](LICENSE.md), and all contributions to Gutenberg will be released under the GPLv2+ license. You maintain copyright over any contribution you make, and by submitting a pull request, you are agreeing to release that contribution under the GPLv2+ license.
+All WordPress projects are [licensed under the GPLv2+](/LICENSE.md), and all contributions to Gutenberg will be released under the GPLv2+ license. You maintain copyright over any contribution you make, and by submitting a pull request, you are agreeing to release that contribution under the GPLv2+ license.
+
+This document covers the technical details around setup, and submitting your contribution to the Gutenberg project.
## Getting Started
@@ -14,7 +16,7 @@ Gutenberg is a Node.js-based project, built primarily in JavaScript.
The easiest way to get started (on MacOS, Linux, or Windows 10 with the Linux Subsystem) is by running the Local Environment setup script, `./bin/setup-local-env.sh`. This will check if you have everything installed and updated, and help you download any extra tools you need.
-For other version of Windows, or if you prefer to set things up manually, be sure to have Node.js installed first. You should be running a Node version matching the [current active LTS release](https://github.com/nodejs/Release#release-schedule) or newer for this plugin to work correctly. You can check your Node.js version by typing `node -v` in the Terminal prompt.
+For another version of Windows, or if you prefer to set things up manually, be sure to have Node.js installed first. You should be running a Node version matching the [current active LTS release](https://github.com/nodejs/Release#release-schedule) or newer for this plugin to work correctly. You can check your Node.js version by typing `node -v` in the Terminal prompt.
If you have an incompatible version of Node in your development environment, you can use [nvm](https://github.com/creationix/nvm) to change node versions on the command line:
@@ -23,7 +25,7 @@ npx nvm install
npx nvm use
```
-You should also have the latest release of [npm installed][npm]. npm is a separate project from Node.js and is updated frequently. If you've just installed Node.js which includes a version of npm within the installation you most likely will need to also update your npm installation. To update npm, type this into your terminal: `npm install npm@latest -g`
+You also should have the latest release of [npm installed][npm]. npm is a separate project from Node.js and is updated frequently. If you've just installed Node.js which includes a version of npm within the installation you most likely will need also to update your npm installation. To update npm, type this into your terminal: `npm install npm@latest -g`
To test the plugin, or to contribute to it, you can clone this repository and build the plugin files using Node. How you do that depends on whether you're developing locally or uploading the plugin to a remote host.
@@ -45,10 +47,10 @@ Then, run a setup script to check if docker and node are configured properly and
**If you're developing themes, or core WordPress functionality alongside Gutenberg**, you can make the WordPress files accessible in `wordpress/` by following these instructions instead:
1. If this is your first time setting up the environment, run `DOCKER_ENV=localwpdev ./bin/setup-local-env.sh` instead of `./bin/setup-local-env.sh`
-2. If you've already had the previous environment set up, you need to start fresh, and you can do that by first running `docker-compose down --rmi all`. After that you can repeat step 1.
+2. If you've already had the previous environment set up, you need to start fresh, and you can do that by first running `docker-compose down --rmi all`. After that, you can repeat step 1.
3. If you turn off your computer or restart Docker, you can get your local WordPress dev environment back by typing `docker-compose -f docker-compose.yml -f docker-compose-localdev.yml up`. If you just run `docker-compose up`, you will get the vanilla install that doesn't expose the WordPress folder.
-**If everything was successful**, you'll see the following ascii art:
+**If everything was successful**, you'll see the following ASCII art:
```
Welcome to...
@@ -59,8 +61,8 @@ Welcome to...
`---'
```
-The WordPress installation should be available at `http://localhost:8888` (username: `admin`, password: `password`).
-Inside the "docker" directory, you can use any docker command to interact with your containers. If this port is in use, you can override it in your `docker-compose.override.yml` file. If you're running [e2e tests](https://wordpress.org/gutenberg/handbook/reference/testing-overview/#end-to-end-testing), this change will be used correctly.
+The WordPress installation should be available at `http://localhost:8888` (**Username**: `admin`, **Password**: `password`).
+Inside the "docker" directory, you can use any docker command to interact with your containers. If this port is in use, you can override it in your `docker-compose.override.yml` file. If you're running [e2e tests](/docs/contributors/testing-overview.md#end-to-end-testing), this change will be used correctly.
To bring down this local WordPress instance later run:
```
@@ -79,7 +81,7 @@ Next, open a terminal (or if on Windows, a command prompt) and navigate to the r
### On A Remote Server
-Open a terminal (or if on Windows, a command prompt) and navigate to the repository you cloned. Now type `npm install` to get the dependencies all set up. Once that finishes, you can type `npm run build`. You can now upload the entire repository to your `wp-content/plugins` directory on your webserver and activate the plugin from the WordPress admin.
+Open a terminal (or if on Windows, a command prompt) and navigate to the repository you cloned. Now type `npm install` to get the dependencies all set up. Once that finishes, you can type `npm run build`. You can now upload the entire repository to your `wp-content/plugins` directory on your web server and activate the plugin from the WordPress admin.
You can also type `npm run package-plugin` which will run the two commands above and create a zip file automatically for you which you can use to install Gutenberg through the WordPress admin.
@@ -88,9 +90,9 @@ You can also type `npm run package-plugin` which will run the two commands above
A good workflow for new contributors to follow is listed below:
- Fork Gutenberg repository
- Clone forked repository
-- Create new branch
+- Create a new branch
- Make code changes
-- Commit code changes within newly created branch
+- Commit code changes within the newly created branch
- Push branch to forked repository
- Submit Pull Request to Gutenberg repository
@@ -104,155 +106,35 @@ For example, `add/gallery-block` means you're working on adding a new gallery bl
You can pick among all the tickets, or some of the ones labelled Good First Issue.
-The workflow is documented in greater detail in the [repository management](./docs/contributors/repository-management.md) document.
+The workflow is documented in greater detail in the [repository management](/docs/contributors/repository-management.md) document.
## Testing
-Gutenberg contains both PHP and JavaScript code, and encourages testing and code style linting for both. It also incorporates end-to-end testing using [Google Puppeteer](https://developers.google.com/web/tools/puppeteer/). You can find out more details in [Testing Overview document](./docs/contributors/testing-overview.md).
-
-## Managing packages
-
-This repository uses [lerna] to manage Gutenberg modules and publish them as packages to [npm].
-
-### Creating new package
-
-When creating a new package you need to provide at least the following:
-
-1. `package.json` based on the template:
- ```json
- {
- "name": "@wordpress/package-name",
- "version": "1.0.0-beta.0",
- "description": "Package description.",
- "author": "The WordPress Contributors",
- "license": "GPL-2.0-or-later",
- "keywords": [
- "wordpress"
- ],
- "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/package-name/README.md",
- "repository": {
- "type": "git",
- "url": "https://github.com/WordPress/gutenberg.git"
- },
- "bugs": {
- "url": "https://github.com/WordPress/gutenberg/issues"
- },
- "main": "build/index.js",
- "module": "build-module/index.js",
- "react-native": "src/index",
- "dependencies": {
- "@babel/runtime": "^7.0.0"
- },
- "publishConfig": {
- "access": "public"
- }
- }
- ```
- This assumes that your code is located in the `src` folder and will be transpiled with `Babel`.
-2. `.npmrc` file which disables creating `package-lock.json` file for the package:
- ```
- package-lock=false
- ```
-3. `README.md` file containing at least:
- - Package name
- - Package description
- - Installation details
- - Usage example
- - `Code is Poetry` logo (`

Block_List = $(!Block .)* (Block $(!Block .)*)* $(.*)
Block = Block_Void / Block_Balanced
Block_Void = "<!--" __ "wp:" Block_Name __ (Block_Attributes __)? "/-->"
Block_Balanced = Block_Start (Block / $(!Block_End .))* Block_End
Block_Start = "<!--" __ "wp:" Block_Name __ (Block_Attributes __)? "-->"
Block_End = "<!--" __ "/wp:" Block_Name __ "-->"
Block_Name = Namespaced_Block_Name diff --git a/docs/contributors/outreach.md b/docs/contributors/outreach.md index 9df69e7ac58b6c..208a1eaf707e14 100644 --- a/docs/contributors/outreach.md +++ b/docs/contributors/outreach.md @@ -1,8 +1,58 @@ # Outreach -This will include talks, meetups 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 just let us know. +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 @@ -13,15 +63,3 @@ https://www.meetup.com/WordPress-Melbourne/events/241543639 https://wpmeetups.de/termin/29-wp-meetup-stuttgart-gutenberg-editor-rueckblick-wordcamp-europe/ -## Testing events: -https://www.meetup.com/Turku-WordPress-Meetup/events/241195076/ - -https://www.meetup.com/Vancouver-WordPress-Meetup-Group/events/241575161/ - -## Calls for testing: -https://make.wordpress.org/test/2017/06/27/call-for-testing-gutenberg/ - -http://www.wpswfl.org/new-wordpress-editor-gutenberg-early-beta-needs-testers/ - -https://gutenberg.eastbaywp.com - diff --git a/docs/contributors/outreach/articles.md b/docs/contributors/outreach/articles.md deleted file mode 100644 index fe4bce7e328a82..00000000000000 --- a/docs/contributors/outreach/articles.md +++ /dev/null @@ -1,30 +0,0 @@ -# Articles - -This includes useful articles for those wanting to run a meetup or promote 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/) diff --git a/docs/contributors/outreach/meetups.md b/docs/contributors/outreach/meetups.md deleted file mode 100644 index 58cd8bbb72c992..00000000000000 --- a/docs/contributors/outreach/meetups.md +++ /dev/null @@ -1,17 +0,0 @@ -# Meetups - -A list of meetups about Gutenberg so far: - -- [Gutenberg and the Future of WordPress](https://www.meetup.com/Vancouver-WordPress-Meetup-Group/events/241575161/), Vancouver, Canada -- [Page builders and the upcoming Gutenberg Editor](https://www.meetup.com/Turku-WordPress-Meetup/events/241195076/), Turku, Finland -- [Discussion about Gutenberg](https://www.facebook.com/events/278785795934302/), Andria, Italy -- [Plugins and Gutenberg](https://wpleeds.co.uk/events/plugins-gutenberg-wordpress-leeds-july-2017/), Leeds, UK -- [Gutenberg Introduction & Demo](https://www.meetup.com/WordPress-Melbourne/events/241543639/), Melbourne, Australia -- [Gutenberg Editor & Review](https://wpmeetups.de/termin/29-wp-meetup-stuttgart-gutenberg-editor-rueckblick-wordcamp-europe/), WordCamp Europe -- [Diving into Gutenberg by Tammie Lister](https://www.meetup.com/Big-Media-Enterprise-WordPress-London-Meetup/events/243302081/), London, UK -- [What's New In WordPress 4.9 and Gutenberg 1.5](https://www.meetup.com/Tuscaloosa-WordPress-Meetup/events/244584939/), Tuscaloosa, Alabama, USA -- [WordPress & JavaScript: Let's talk Gutenberg!](https://www.meetup.com/WordPress-Lahore/events/246446478/), Lahore, PK -- [The state of Gutenberg](https://www.meetup.com/WP-Porto/events/245585131/), Porto, Portugal -- [Discuss and learn about the new WordPress Editor : Gutenberg](https://www.meetup.com/Pune-WordPress-Knowledge-Exchange/events/248496830/), Pune, India -- [An Introduction to Gutenberg](https://www.meetup.com/Okanagan-WordPress-Meetup/events/249167218/), Vernon, BC, Canada -- [WordPress 5.0 - Gutenberg is upon us](https://www.meetup.com/WordPress-Perth/events/249490075/), Perth, Australia diff --git a/docs/contributors/outreach/resources.md b/docs/contributors/outreach/resources.md deleted file mode 100644 index 3c5dd598f60858..00000000000000 --- a/docs/contributors/outreach/resources.md +++ /dev/null @@ -1,11 +0,0 @@ -# Resources - -All resources here can be used by anyone. Feel free to use the decks to make your own talks or to use the gifs in your blog posts and other resources. - -## Slidedecks to use - -- v1: https://cloudup.com/cqEJppQ8m-5 : August 2017 - -## Gif collection to use - -- https://cloudup.com/c9OKU3OJD9r diff --git a/docs/contributors/outreach/talks.md b/docs/contributors/outreach/talks.md deleted file mode 100644 index a21f9c468c4814..00000000000000 --- a/docs/contributors/outreach/talks.md +++ /dev/null @@ -1,17 +0,0 @@ -# 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 diff --git a/docs/contributors/readme.md b/docs/contributors/readme.md index e69de29bb2d1d6..685af4590b4737 100644 --- a/docs/contributors/readme.md +++ b/docs/contributors/readme.md @@ -0,0 +1,18 @@ +# Contributors Guide + +Welcome to the Gutenberg Project Contributors Guide. + +The following guidelines are in place to create consistency across the project and the numerous contributors. See the [Contributing Documentation](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) for technical details around setup, and submitting your contributions. + +## Philosophy + +* [Architectural and UX Principles of Gutenberg](/docs/contributors/principles.md) + +## Sections + +The contributors guide has the following different sections by contribution type: + +* [Design Contributions](/docs/contributors/design.md) +* [Developer Contributions](/docs/contributors/develop.md) +* [Documentation Contributions](/docs/contributors/document.md) + diff --git a/docs/contributors/reference.md b/docs/contributors/reference.md index 2a4714d5e75a23..95c0b603323e50 100644 --- a/docs/contributors/reference.md +++ b/docs/contributors/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/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) ## Logo@@ -14,4 +14,4 @@ Released under GPL license, made by [Cristel Rossignol](https://twitter.com/cris ## Mockups -Mockup Sketch files are available in the Design section. +Mockup Sketch files are available in [the Design section](/docs/designers-developers/designers/design-resources.md). diff --git a/docs/contributors/release.md b/docs/contributors/release.md index e909ab5281aabb..d88c260a3904a9 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -1,170 +1,154 @@ # Gutenberg Release Process -This document serves as a checklist for building and releasing a new version of Gutenberg. It documents our release process, but it is helpful if you'd like to understand how Gutenberg ships release candidates and new versions. +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]. 🙂 +To release Gutenberg, you need commit access to the [WordPress.org plugin repository][plugin repository]. 🙂 -## Gutenberg Versioning +## Plugin Releases -Gutenberg follows semantic versioning; we release a new major version approximately every two weeks. The current and next versions are [tracked in GitHub milestones](https://github.com/WordPress/gutenberg/milestones), along with each version's tagging date. The date in the milestone is the date of **tagging the release candidate**, not the final version that will be published to the [plugin repository]. +### Versioning -Up until version `4.0.0`, "point releases"/minor releases (eg. `3.8` > `3.9`) effectively constituted a major version bump. +We release a new major version approximately every two weeks. The current and next versions are [tracked in GitHub milestones](https://github.com/WordPress/gutenberg/milestones), along with each version's tagging date. -Our [deprecation policy](./deprecated.md) maintains deprecated APIs/functions for two major versions, whenever possible. +### Release Candidates -# Release Candidates +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 release candidate can be published. -Since [`3.8`](https://github.com/WordPress/gutenberg/releases/tag/v3.8.0), we [publish a release candidate](https://github.com/WordPress/gutenberg/releases/tag/v3.8.0-rc.1) at least one week before we publish a new version. This release candidate is available for plugin authors and for users to test. If any bugs/regressions are found with a release candidate, they should be cherry-picked into a new release candidate and it should be published. + +The date in the milestone is the date of **tagging the release candidate**. 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. -## Creating a Release Candidate +#### Creating the first Release Candidate -Creating a release candidate involves: +Releasing the first release candidate for this milestone (`x.x`) involves: 1. writing a release blog post and changelog -2. bumping the version -3. tagging the release +2. creating the release branch +3. bumping the version and tagging the release 4. building the plugin -5. publishing the new release to GitHub -6. committing to the [plugin repository] +5. publishing the release to GitHub +6. publishing the call for testing -### Writing the Release Post and Changelog +##### Writing the Release Post and Changelog 1. Open the [list of closed pull requests](https://github.com/WordPress/gutenberg/pulls?utf8=✓&q=is%3Apr+is%3Aclosed+sort%3Acreated-desc+) and filter by the current milestone. 2. Read through each PR to determine if it needs to be included in the blog post and/or changelog. 3. Choose a few features to highlight in the release post; record an animation of them in use. 4. Save the draft post on [make.wordpress.org/core](https://make.wordpress.org/core/); this post should be published after the actual release. -### Bumping the Version +##### Creating the Release Branch -1. Create [a pull request like this](https://github.com/WordPress/gutenberg/pull/9663), bumping the version number in `gutenberg.php`, `package.json`, and `package-lock.json`. -2. Check that there's no work-in-progress that's just about to land. [Inform committers in `#core-editor` on Slack](https://make.wordpress.org/chat/) to hold off on merging any changes until after the release candidate is tagged. -3. Merge the version bump pull request. +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. -### Tag the Release +``` +git checkout master +git checkout -b release/x.x +git push origin release/x.x +``` -1. [Create a new release on GitHub](https://github.com/WordPress/gutenberg/releases/new). -2. If you were releasing the `5.0.0` release candidate, label it `v5.0.0-rc.1`. -3. The GitHub release screen should look like this: -[](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/reference/release-screenshot.png) -4. Creative emojis related to a key feature in this release are encouraged. Emojis are fun! -5. Publish the release. +##### Bumping the Version and Tagging the Release + +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. +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. -### Build the Plugin +##### Build the Plugin 1. Run `git fetch --tags`. -2. Check out the tag for this release; it will be the tag you created on the GitHub release page; assuming version `5.0.0`, you should run `git checkout v5.0.0-rc.1`. +2. Check out the tag for this release, you should run `git checkout vx.x.0-rc.1`. 3. Run `./bin/build-plugin-zip.sh` from the root of project. This packages a zip file with a release build of `gutenberg.zip`. -### Publish the Release on GitHub +##### Publish the Release on GitHub -1. Upload the a `gutenberg.zip` file to the [GitHub Releases page](https://github.com/WordPress/gutenberg/releases) -2. Post a link to the release page to the [`#core-editor` channel](https://wordpress.slack.com/messages/C02QB2JS7). - -Here's an example [release candidate page](https://github.com/WordPress/gutenberg/releases/tag/v3.8.0-rc.1); yours should look like that when you're finished. +1. [Create a new release on GitHub](https://github.com/WordPress/gutenberg/releases/new). +2. If you were releasing the `x.x.0-rc.1` release candidate, label it `x.x.0-rc.1` and use the `vx.x.x-rc.1` as a tag. +3. Upload the a `gutenberg.zip` file into the release. +4. Use the changelog as a description of the release. +5. Publish the release. -### Commit to the Plugin Repository +Here's an example [release candidate page](https://github.com/WordPress/gutenberg/releases/tag/v4.6.0-rc.1); yours should look like that when you're finished. -You'll need to use Subversion for this step. +##### Publishing the Call For Testing -1. Do an SVN checkout of `https://wordpress.org/plugins/gutenberg/`: - * If this is your first checkout, run: `svn checkout https://plugins.svn.wordpress.org/gutenberg` - * If you already have a copy, run: `svn up` -2. Delete the contents of `trunk` except for the `readme.txt` and `changelog.txt` files (these files don’t exist in the `git` repo, only in Subversion). -3. Extract the contents of the zip file to `trunk`. -4. Edit `readme.txt`, replacing the changelog for the previous version with the current release's changelog. -5. Add the changelog for the current release to `changelog.txt`. -6. Add new files/remove deleted files from the repository: -```bash -# Add new files: -svn st | grep '^\?' | awk '{print $2}' | xargs svn add -# Delete old files: -svn st | grep '^!' | awk '{print $2}' | xargs svn rm -``` -7. Commit the new version to `trunk`: -```bash -# Replace vX.X.X-rc.1 with your version: -svn ci -m "Committing Gutenberg version vX.X.X-rc.1" -``` +Ping someone from the `[#core-test](https://wordpress.slack.com/messages/C03B0H5J0)` team to publish a call for testing post. Here's an [example call for testing post.](https://make.wordpress.org/test/2019/01/04/call-for-testing-gutenberg-4-8/) -## Creating Release Candidate Patches (done via `git cherry-pick`) +#### 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`. 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 `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: -1. Create a pull request against master that only bumps the release candidate number in `gutenberg.php`, `package.json`, and `package-lock.json`. If your current release candidate is `v3.8.0-rc.1`, you would change the version number to `v3.8.0-rc.2` -2. After this pull request is merged into master, note its commit SHA. -3. Run `git fetch --tags` to load the tags. -4. Check out the latest release candidate (for example: `git checkout v3.8.0-rc.1`). -5. Cherry-pick fix commits (in chronological order) with `git cherry-pick [SHA]`. -6. Cherry-pick the version bump commit you noted in step 2. -7. Tag this release by incrementing its `rc.X` number and push it to GitHub: -```bash -git tag v3.8.0-rc.2 -git push origin v3.8.0-rc.2 -``` -8. Follow the steps in [tag the release](#tag-the-release), [build the plugin](#build-the-plugin), [publish the release on GitHub](#publish-the-release-on-github), and [commit to the plugin repository](#commit-to-the-plugin-repository). You can copy the existing changelog from the previous release candidate. +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. +5. Tag the RC version. `git tag vx.x.0-rc.2` from the release branch. +6. Push the tag `git push --tags`. +7. Merge the version bump pull request and avoid removing the release branch. +8. Follow the steps in [build the plugin](#build-the-plugin) and [publish the release on GitHub](#publish-the-release-on-github). -It's worth mentioning that a new release candidate has been released in the [`#core-editor` channel](https://wordpress.slack.com/messages/C02QB2JS7). +You can copy the existing changelog from the previous release candidate. Let other contributors know that a new release candidate has been released in the [`#core-editor` channel](https://wordpress.slack.com/messages/C02QB2JS7) and the call for testing post. -# Official Gutenberg Releases™ +### Official Gutenberg Releases™ -The process of releasing Gutenberg is similar to creating a release candidate, except we don't use the `-rc.X` in the `git` tag and we publish a new branch in the subversion repository. This updates the version available in the WordPress plugin repository and will cause WordPress sites around the world to prompt users to update to this new version. The steps below are very similar to the ones above, for a release candidate, but they're spelled out here in their entirely to reduce confusion. 😅 +The process of releasing Gutenberg is similar to creating a release candidate, except we don't use the `-rc.X` in the `git` tag and we publish a new branch in the subversion repository. This updates the version available in the WordPress plugin repository and will cause WordPress sites around the world to prompt users to update to this new version. -## Creating a Release +#### Creating a Release Creating a release involves: 1. verifying the release blog post and changelog 2. bumping the version -4. building the plugin -5. publishing the new release to GitHub -6. committing to the [plugin repository] -7. publishing the release blog post +3. building the plugin +4. publishing the new release to GitHub +5. committing to the [plugin repository] +6. publishing the release blog post -### Verifying the Release Post and Changelog +##### Verifying the Release Post and Changelog 1. Check the draft post on [make.wordpress.org/core](https://make.wordpress.org/core/); make sure the changelog reflects what's shipping in the release. -### Bumping the Version +##### Bumping the Version + +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`. + +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`. +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`. +9. Merge the version bump pull request. -1. Create [a pull request with 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`. -2. Merge the version bump pull request. (If you want to live dangerously, you can push the version bump directly to `master`. 😎) -3. Note the commit SHA of the version bump. -4. Cherry-pick the version bump commit into your release candidate branch: -```bash -# Assuming you're releasing version v5.0.0, based on v5.0.0-rc.1. -git fetch --tags -git checkout v.5.0.0-rc.1 -git cherry-pick [VERSION-BUMP-COMMIT-SHA] -git tag v5.0.0 -git push origin v5.0.0 -``` -### Build the Plugin +##### Build the Plugin 1. Run `git fetch --tags`. -2. Check out the tag for this release; it will be the tag you created on the GitHub release page; assuming version `5.0.0`, you should run `git checkout v5.0.0-rc.1`. +2. Check out the tag for this release, you should run `git checkout vx.x.0`. 3. Run `./bin/build-plugin-zip.sh` from the root of project. This packages a zip file with a release build of `gutenberg.zip`. -### Publish the Release on GitHub +##### Publish the Release on GitHub 1. [Create a new release on GitHub](https://github.com/WordPress/gutenberg/releases/new). -2. If you are releasing `5.0.0`, target the tag `v5.0.0`. -3. Upload the `gutenberg.zip` file -4. Post a link to the release page to the [`#core-editor` channel](https://wordpress.slack.com/messages/C02QB2JS7). - -Here's an example [release page](https://github.com/WordPress/gutenberg/releases/tag/v3.8.0); yours should look like that when you're finished. +2. If you were releasing the `x.x.0` release candidate, label it `x.x.0` and use the `vx.x.x` as a tag. +3. Upload the a `gutenberg.zip` file into the release. +4. Use the changelog as a description of the release. +5. Publish the release. -### Commit to the Plugin Repository +##### Commit to the Plugin Repository You'll need to use Subversion to publish the plugin to WordPress.org. -1. Do an SVN checkout of `https://wordpress.org/plugins/gutenberg/`: - * If this is your first checkout, run: `svn checkout https://plugins.svn.wordpress.org/gutenberg` +1. Do an SVN checkout of `https://wordpress.org/plugins/gutenberg/trunk`: + * If this is your first checkout, run: `svn checkout https://plugins.svn.wordpress.org/gutenberg/trunk` * If you already have a copy, run: `svn up` -2. Delete the contents of `trunk` except for the `readme.txt` and `changelog.txt` files (these files don’t exist in the `git` repo, only in Subversion). -3. Extract the contents of the zip file to `trunk`. +2. Delete the contents except for the `readme.txt` and `changelog.txt` files (these files don’t exist in the `git` repo, only in Subversion). +3. Extract the contents of the zip file. 4. Edit `readme.txt`, replacing the changelog for the previous version with the current release's changelog. 5. Add the changelog for the current release to `changelog.txt`. 6. Add new files/remove deleted files from the repository: @@ -174,17 +158,16 @@ svn st | grep '^\?' | awk '{print $2}' | xargs svn add # Delete old files: svn st | grep '^!' | awk '{print $2}' | xargs svn rm ``` -7. Commit the new version to `trunk`: +7. Commit the new version: ```bash -# Replace vX.X.X with your version: -svn ci -m "Committing Gutenberg version vX.X.X" +# Replace X.X.X with your version: +svn ci -m "Committing Gutenberg version X.X.X" ``` -8. Tag the new version. Make sure you're in the root directory of `gutenberg`, then run: +8. Tag the new version: ```bash -svn cp trunk tags/X.X.X -svn ci -m "Tagging Gutenberg version X.X.X" +svn cp https://plugins.svn.wordpress.org/gutenberg/trunk https://plugins.svn.wordpress.org/gutenberg/tags/X.X.X -m "Tagging Gutenberg version X.X.X" ``` -9. Edit `trunk/readme.txt` to point to the new tag. The **Stable version** header in `readme.txt` should be updated to match the new release version number. After updating and committing that, the new version should be released: +9. Edit `readme.txt` to point to the new tag. The **Stable version** header in `readme.txt` should be updated to match the new release version number. After updating and committing that, the new version should be released: ```bash svn ci -m "Releasing Gutenberg version X.X.X" ``` @@ -198,10 +181,63 @@ You should check that folks are able to install the new version from their Dashb 1. Publish the [make/core](https://make.wordpress.org/core/) release blog post drafted earlier. 2. Pat yourself on the back! 👍 -If you don't have access to [make.wordpress.org/core](https://make.wordpress.org/core/), ping [Matias Ventura](https://profiles.wordpress.org/matveb) or someone else on the Gutenberg Core team to publish the post. +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 + +The Gutenberg repository mirrors the [WordPress SVN repository](https://make.wordpress.org/core/handbook/about/release-cycle/) in terms of branching for each SVN branch, a corresponding Gutenberg `wp/*` branch is created: + + - The `wp/trunk` branch contains all the packages that are published and used in the `trunk` branch of WordPress. + - A Gutenberg branch targeting a specific WordPress major release (including its further minor increments) is created (example `wp/5.2`) based on the `wp/trunk` Gutenberg branch when the WordPress `trunk` branch is marked as "feature-freezed". (This usually happens when the first `beta` of the next WordPress major version is released). + +### Synchronizing WordPress Trunk + +For each Gutenberg plugin release, WordPress trunk should be synchronized with this release. This involves the following steps: + +**Note:** 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. + +1. Ensure the WordPress `trunk` branch is open for enhancements. +2. Check out the last published Gutenberg release branch `git checkout release/x.x` +3. Create a Pull Request from this branch targeting `wp/trunk`. +4. Merge the Pull Request using the "Rebase and Merge" button to keep the history of the commits. + +Now, the branch is ready to be used to publish the npm packages. + +1. Check out the `wp/trunk` branch. +2. Run the [package release process] but when asked for the version numbers to choose for each package, (assuming the package versions are written using this format `major.minor.patch`) make sure to bump at least the `minor` version number. For example, if the CHANGELOG of the package to be released indicates that the next unreleased version is `5.6.1`, choose `5.7.0` as a version. +3. Update the `CHANGELOG.md` files of the published packages with the new released versions and commit to the `wp/trunk` branch. +4. Cherry-pick the "Publish" (created by Lerna) and the CHANGELOG update commits into the `master` branch of Gutenberg. + +Now, the npm packages should be ready and a patch can be created and committed into WordPress `trunk`. + + +### Minor WordPress Releases + +The following workflow is needed when bug fixes or security releases need to be backported into WordPress Core. This can happen in a few use-cases: + + - During the `beta` and the `RC` period of the WordPress release cycle. + - For WordPress minor releases and WordPress security releases (example `5.1.1`). + +1. Cherry-pick +2. Check out the last published Gutenberg release branch `git checkout release/x.x` +3. Create a Pull Request from this branch targeting the WordPress related major branch (Example `wp/5.2`). +4. Merge the Pull Request using the "Rebase and Merge" button to keep the history of the commits. + +Now, the branch is ready to be used to publish the npm packages. + +1. Check out the WordPress branch used before (Example `wp/5.2`). +2. Run the [package release process] but when asked for the version numbers to choose for each package, (assuming the package versions are written using this format `major.minor.patch`) make sure to bump only the `patch` version number. For example, if the last published package version for this WordPress branch was `5.6.0`, choose `5.6.1` as a version. + +**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. + +Now, the npm packages should be ready and a patch can be created and committed into the corresponding WordPress SVN branch. --------- Ta-da! 🎉 [plugin repository]: https://plugins.trac.wordpress.org/browser/gutenberg/ +[package release process]: https://github.com/WordPress/gutenberg/blob/master/packages/README.md#releasing-packages diff --git a/docs/contributors/repository-management.md b/docs/contributors/repository-management.md index 132f46d0df1edd..7eaa1cb28661e0 100644 --- a/docs/contributors/repository-management.md +++ b/docs/contributors/repository-management.md @@ -1,6 +1,6 @@ # Repository Management -The goal is for this to be a living document explaining how we collaboratively manage the Gutenberg repository. If you’d like to suggest a change, please open an issue for discussion or submit a pull request to the document. +This is a living document explaining how we collaboratively manage the Gutenberg repository. If you’d like to suggest a change, please open an issue for discussion or submit a pull request to the document. This document covers: @@ -17,15 +17,25 @@ This document covers: ## Issues -A healthy issue backlog is one where issues are relevant and actionable. *Relevant* in the sense that they relate to the project’s current priorities. *Actionable* in the sense that it’s clear what action(s) need to be taken to resolve the issue. +A healthy issue list is one where issues are relevant and actionable. *Relevant* in the sense that they relate to the project’s current priorities. *Actionable* in the sense that it’s clear what action(s) need to be taken to resolve the issue. -Any issues that are irrelevant or not actionable should be closed, because they get in the way of making progress on the project. Imagine the issue backlog as a desk: the more clutter you have on it, the more difficult it is to use the space to get work done. +Any issues that are irrelevant or not actionable should be closed, because they get in the way of making progress on the project. Imagine the issue list as a desk: the more clutter you have on it, the more difficult it is to use the space to get work done. ### Labels -To better organize the issue backlog, all issues should have [one or more labels](https://github.com/WordPress/gutenberg/labels). Here are some you might commonly see: +All issues should have [one or more labels](https://github.com/WordPress/gutenberg/labels). -- [Accessibility](https://github.com/WordPress/gutenberg/labels/Accessibility) - Changes that impact accessibility and need corresponding review (e.g. markup changes). +Workflow labels start with “Needs” and may be applied as needed. Ideally, each workflow label will have a group that follows it, such as the Accessibility Team for `Needs Accessibility Feedback`, the Testing Team for `Needs Testing`, etc. + +[Priority High](https://github.com/WordPress/gutenberg/labels/Priority%20High) and [Priority OMGWTFBBQ](https://github.com/WordPress/gutenberg/labels/Priority%20OMGWTFBBQ) issues should have an assignee and/or be in an active milestone. + +Help requests or 'how to' questions should be posted in a relevant support forum as a first step. If something might be a bug but it's not clear, the Support Team or a forum volunteer can help troubleshoot the case to help get all the right information needed for an effective bug report. + +Here are some labels you might commonly see: + +- [Good First Issue](https://github.com/WordPress/gutenberg/labels/Good%20First%20Issue) - Issues identified as good for new contributors to work on. Comment to note that you intend to work on the issue and reference the issue number in the pull request you submit. +- [Good First Review](https://github.com/WordPress/gutenberg/labels/Good%20First%20Review) - Pull requests identified as good for new contributors who are interested in doing code reviews. +- [Needs Accessibility Feedback](https://github.com/WordPress/gutenberg/labels/Accessibility) - Changes that impact accessibility and need corresponding review (e.g. markup changes). - [Needs Design Feedback](https://github.com/WordPress/gutenberg/labels/Needs%20Design%20Feedback) - Changes that modify the design or user experience in some way and need sign-off. - [[Type] Bug](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Bug) - An existing feature is broken in some way. - [[Type] Enhancement](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Enhancement) - Gutenberg would be better with this improvement added. @@ -36,52 +46,43 @@ To better organize the issue backlog, all issues should have [one or more labels ### Milestones -We put issues into [milestones](https://github.com/wordpress/gutenberg/milestones) to better categorize them. Here are some you might see: - -- The next 2 releases we have milestones for (e.g. 2.2, 2.3). -- [Feature Complete](https://github.com/WordPress/gutenberg/milestone/8): This includes big features and is what will be managing the vision of Gutenberg. All of this would be done before even merge proposal is thought about. Examples here include nesting, drag and drop and extensibility API. -- [Merge Proposal: Editor](https://github.com/WordPress/gutenberg/milestone/22): All issues related to merge proposal for the editor. -- [Merge Proposal: Rest API](https://github.com/WordPress/gutenberg/milestone/39): All issues related to merge proposal for the Rest API -- [Merge Proposal: Accessibility](https://github.com/WordPress/gutenberg/milestone/43): All accessibility issues related to merge proposal. -- [Merge Proposal: Media](https://github.com/WordPress/gutenberg/milestone/42): All issues related to merge proposal for the media component. -- [Merge Proposal: Documentation](https://github.com/WordPress/gutenberg/milestone/50): All issues related to documentation for the merge proposal. -- [Merge Proposal: i18n](https://github.com/WordPress/gutenberg/milestone/49): All translation issues for the merge proposal. -- [Merge Proposal: Customization](https://github.com/WordPress/gutenberg/milestone/44): All Customization issues for the merge proposal. -- [Merge Proposal: Plugin](https://github.com/WordPress/gutenberg/milestone/48): All plugin and extensibility issues for the merge proposal. -- [Merge Proposal: Back Compat](https://github.com/WordPress/gutenberg/milestone/47): All back compatibility issues for the merge proposal. -- [Merge Proposal: Themes](https://github.com/WordPress/gutenberg/milestone/48): All theme issues for the merge proposal. -- [Merge Proposal: Core](https://github.com/WordPress/gutenberg/milestone/45): All core issues for the merge proposal that don't fit other merge proposal milestones. -- [Bonus Features](https://github.com/WordPress/gutenberg/milestone/32): Again likely not part of triage and includes nice to haves for the project, if time before merge. A few examples include collaborative editing and footnotes. +We put issues into [milestones](https://github.com/wordpress/gutenberg/milestones) to better categorize them. Issues are added to milestones starting with `WordPress` and pull requests are added to milestones ending in `(Gutenberg)`. + +Here are some milestones you might see: + +- [WordPress X.Y](https://github.com/WordPress/gutenberg/milestone/70): Tasks that should be done for future WordPress releases. +- [X.Y (Gutenberg)](https://github.com/WordPress/gutenberg/milestone/85): PRs targeted for the Gutenberg Plugin X.Y release. - [Future](https://github.com/WordPress/gutenberg/milestone/35): this is something that is confirmed by everyone as a good thing but doesn’t fall into other criteria. ### Triaging Issues -To keep the issue backlog healthy, it needs to be triaged regularly. *Triage* is the practice of reviewing existing issues to make sure they’re relevant, actionable, and have all the information they need. +To keep the issue list healthy, it needs to be triaged regularly. *Triage* is the practice of reviewing existing issues to make sure they’re relevant, actionable, and have all the information they need. -Anyone can help triage the backlog, although you’ll need contributor permission on the Gutenberg repository to modify an issue’s labels or edit its title. +Anyone can help triage, although you’ll need contributor permission on the Gutenberg repository to modify an issue’s labels or edit its title. Here are a couple places you can start: -- [All Gutenberg issues without an assigned label](https://github.com/wordpress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc+no%3Alabel) -- [The least recently updated Gutenberg issues](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc) +- [All Gutenberg issues without an assigned label](https://github.com/wordpress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc+no%3Alabel). +- [The least recently updated Gutenberg issues](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc). -When reviewing the issue backlog, here are some steps you can perform: +When reviewing issues, here are some steps you can perform: -- If it’s a bug report, test to confirm the report. If there is not enough information to confirm the report, add the `[Status] Needs More Info` label. -- If the issue is missing labels, add some to better categorize it. -- If the issue is duplicate of another already in the backlog, close the issue by commenting with “Duplicate of #
”. Add any relevant new details to the existing issue. +- First search for duplicates. If the issue is duplicate, close it by commenting with “Duplicate of # ” and add any relevant new details to the existing issue. +- If the issue is missing labels, add some to better categorize it (requires proper permissions). +- If the title doesn’t communicate the issue, edit it for clarity (requires proper permissions). +- If it’s a bug report, test to confirm the report or add the `Needs Testing` label. If there is not enough information to confirm the report, add the `[Status] Needs More Info` label and ask for the details needed. +- Remove the `[Status] Needs More Info` if the author of the issue has responded with enough details. +- Close the issue with a note if it has a `[Status] Needs More Info` label but the author didn't respond in 2+ weeks. - If there was conversation on the issue but no actionable steps identified, follow up with the participants to see what’s actionable. -- If the title doesn’t communicate the issue, edit it for clarity. - If you feel comfortable triaging the issue further, then you can also: - Check that the bug report is valid by debugging it to see if you can track down the technical specifics. - - Check if the issue is missing some detail and see if you can fill in those details. For instance, if a bug report is missing visual detail, it’s helpful to reproduce the issue locally and upload a GIF. + - Check if the issue is missing some detail and see if you can fill in those details. For instance, if a bug report is missing visual detail, it’s helpful to reproduce the issue locally and upload a screenshot or GIF. ## Pull Requests Gutenberg follows a feature branch pull request workflow for all code and documentation changes. At a high-level, the process looks like this: - 1. Check out a new feature branch locally. 2. Make your changes, testing thoroughly. 3. Commit your changes when you’re happy with them, and push the branch. @@ -125,6 +126,7 @@ 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. The final pull request merge decision is made by the **@wordpress/gutenberg-core** team. @@ -154,6 +156,6 @@ We use [GitHub projects](https://github.com/WordPress/gutenberg/projects) to kee Some key projects include: -* [Customization](https://github.com/WordPress/gutenberg/projects/13) - Blocks and tasks needed for customization in Gutenberg. -* [Extensibility](https://github.com/WordPress/gutenberg/projects/14) - Comprises the entirety of extensibility APIs. See [Native Gutenberg Extensibility Overview](https://github.com/WordPress/gutenberg/issues/3330) for more details. -* [Third-Party Compatibility](https://github.com/WordPress/gutenberg/projects/15) - Issue that impact Gutenberg's adoption in the real world. +* [Phase 2](https://github.com/WordPress/gutenberg/projects/13) - Development tasks needed for Phase 2 of Gutenberg. +* [Phase 2 design](https://github.com/WordPress/gutenberg/projects/21) - Tasks for design in Phase 2. Note: specific projects may have their own boards. +* [Ideas](https://github.com/WordPress/gutenberg/projects/8) - Project containing tickets that, while closed for the time being, can be revisited in the future. diff --git a/docs/contributors/scripts.md b/docs/contributors/scripts.md index dd7e1d295d528c..4928ec91bde64b 100644 --- a/docs/contributors/scripts.md +++ b/docs/contributors/scripts.md @@ -8,38 +8,38 @@ The editor includes a number of packages to enable various pieces of functionali | Script Name | Handle | Description | |-------------|--------|-------------| -| [Blob](https://wordpress.org/gutenberg/handbook/packages/packages-blob/) | wp-blob | Blob utilities | -| [Block Library](https://wordpress.org/gutenberg/handbook/packages/packages-block-library/) | wp-block-library | Block library for the editor | -| [Blocks](https://wordpress.org/gutenberg/handbook/packages/packages-blocks/) | wp-blocks | Block creations | -| [Block Serialization Default Parser](https://wordpress.org/gutenberg/handbook/packages/packages-block-serialization-default-parser/) | wp-block-serialization-default-parser | Default block serialization parser implementations for WordPress documents | -| [Block Serialization Spec Parser](https://wordpress.org/gutenberg/handbook/packages/packages-block-serialization-spec-parser/) | wp-block-serialization-spec-parser | Grammar file (grammar.pegjs) for WordPress posts | -| [Components](https://wordpress.org/gutenberg/handbook/packages/packages-components/) | wp-components | Generic components to be used for creating common UI elements | -| [Compose](https://wordpress.org/gutenberg/handbook/packages/packages-compose/) | wp-compose | Collection of handy Higher Order Components (HOCs) | -| [Core Data](https://wordpress.org/gutenberg/handbook/packages/packages-core-data/) | wp-core-data | Simplify access to and manipulation of core WordPress entities | -| [Data](https://wordpress.org/gutenberg/handbook/packages/packages-data/) | wp-data | Data module serves as a hub to manage application state for both plugins and WordPress itself | -| [Date](https://wordpress.org/gutenberg/handbook/packages/packages-date/) | wp-date | Date module for WordPress | -| [Deprecated](https://wordpress.org/gutenberg/handbook/packages/packages-deprecated/) | wp-deprecated | Utility to log a message to notify developers about a deprecated feature | -| [Dom](https://wordpress.org/gutenberg/handbook/packages/packages-dom/) | wp-dom | DOM utilities module for WordPress | -| [Dom Ready](https://wordpress.org/gutenberg/handbook/packages/packages-dom-ready/) | wp-dom-ready | Execute callback after the DOM is loaded | -| [Editor](https://wordpress.org/gutenberg/handbook/packages/packages-editor/) | wp-editor | Building blocks for WordPress editors | -| [Edit Post](https://wordpress.org/gutenberg/handbook/packages/packages-edit-post/) | wp-edit-post | Edit Post Module for WordPress | -| [Element](https://wordpress.org/gutenberg/handbook/packages/packages-element/) | wp-element |Element is, quite simply, an abstraction layer atop [React](https://reactjs.org/) | -| [Escape Html](https://wordpress.org/gutenberg/handbook/packages/packages-escape-html/) | wp-escape-html | Escape HTML utils | -| [Hooks](https://wordpress.org/gutenberg/handbook/packages/packages-hooks/) | wp-hooks | A lightweight and efficient EventManager for JavaScript | -| [Html Entities](https://wordpress.org/gutenberg/handbook/packages/packages-html-entities/) | wp-html-entities | HTML entity utilities for WordPress | -| [I18N](https://wordpress.org/gutenberg/handbook/packages/packages-i18n/) | wp-i18n | Internationalization utilities for client-side localization | -| [Is Shallow Equal](https://wordpress.org/gutenberg/handbook/packages/packages-is-shallow-equal/) | wp-is-shallow-equal | A function for performing a shallow comparison between two objects or arrays | -| [Keycodes](https://wordpress.org/gutenberg/handbook/packages/packages-keycodes/) | wp-keycodes | Keycodes utilities for WordPress, used to check the key pressed in events like `onKeyDown` | -| [List Reusable Bocks](https://wordpress.org/gutenberg/handbook/packages/packages-list-reusable-blocks/) | wp-list-reusable-blocks | Package used to add import/export links to the listing page of the reusable blocks | -| [NUX](https://wordpress.org/gutenberg/handbook/packages/packages-nux/) | wp-nux | Components, and wp.data methods useful for onboarding a new user to the WordPress admin interface | -| [Plugins](https://wordpress.org/gutenberg/handbook/packages/packages-plugins/) | wp-plugins | Plugins module for WordPress | -| [Redux Routine](https://wordpress.org/gutenberg/handbook/packages/packages-redux-routine/) | wp-redux-routine | Redux middleware for generator coroutines | -| [Rich Text](https://wordpress.org/gutenberg/handbook/packages/packages-rich-text/) | wp-rich-text | Helper functions to convert HTML or a DOM tree into a rich text value and back | -| [Shortcode](https://wordpress.org/gutenberg/handbook/packages/packages-shortcode/) | wp-shortcode | Shortcode module for WordPress | -| [Token List](https://wordpress.org/gutenberg/handbook/packages/packages-token-list/) | wp-token-list | Constructable, plain JavaScript [DOMTokenList](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList) implementation, supporting non-browser runtimes | -| [URL](https://wordpress.org/gutenberg/handbook/packages/packages-url/) | wp-url | A collection of utilities to manipulate URLs | -| [Viewport](https://wordpress.org/gutenberg/handbook/packages/packages-viewport/) | wp-viewport | Module for responding to changes in the browser viewport size | -| [Wordcount](https://wordpress.org/gutenberg/handbook/packages/packages-wordcount/) | wp-wordcount | WordPress word count utility | +| [Blob](/packages/blob/README.md) | wp-blob | Blob utilities | +| [Block Library](/packages/block-library/README.md) | wp-block-library | Block library for the editor | +| [Blocks](/packages/blocks/README.md) | wp-blocks | Block creations | +| [Block Serialization Default Parser](/packages/block-serialization-default-parser/README.md) | wp-block-serialization-default-parser | Default block serialization parser implementations for WordPress documents | +| [Block Serialization Spec Parser](/packages/block-serialization-spec-parser/README.md) | wp-block-serialization-spec-parser | Grammar file (grammar.pegjs) for WordPress posts | +| [Components](/packages/components/README.md) | wp-components | Generic components to be used for creating common UI elements | +| [Compose](/packages/compose/README.md) | wp-compose | Collection of handy Higher Order Components (HOCs) | +| [Core Data](/packages/core-data/README.md) | wp-core-data | Simplify access to and manipulation of core WordPress entities | +| [Data](/packages/data/README.md) | wp-data | Data module serves as a hub to manage application state for both plugins and WordPress itself | +| [Date](/packages/date/README.md) | wp-date | Date module for WordPress | +| [Deprecated](/packages/deprecated/README.md) | wp-deprecated | Utility to log a message to notify developers about a deprecated feature | +| [Dom](/packages/dom/README.md) | wp-dom | DOM utilities module for WordPress | +| [Dom Ready](/packages/dom-ready/README.md) | wp-dom-ready | Execute callback after the DOM is loaded | +| [Editor](/packages/editor/README.md) | wp-editor | Building blocks for WordPress editors | +| [Edit Post](/packages/edit-post/README.md) | wp-edit-post | Edit Post Module for WordPress | +| [Element](/packages/element/README.md) | wp-element |Element is, quite simply, an abstraction layer atop [React](https://reactjs.org/) | +| [Escape Html](/packages/escape-html/README.md) | wp-escape-html | Escape HTML utils | +| [Hooks](/packages/hooks/README.md) | wp-hooks | A lightweight and efficient EventManager for JavaScript | +| [Html Entities](/packages/html-entities/README.md) | wp-html-entities | HTML entity utilities for WordPress | +| [I18N](/packages/i18n/README.md) | wp-i18n | Internationalization utilities for client-side localization | +| [Is Shallow Equal](/packages/is-shallow-equal/README.md) | wp-is-shallow-equal | A function for performing a shallow comparison between two objects or arrays | +| [Keycodes](/packages/keycodes/README.md) | wp-keycodes | Keycodes utilities for WordPress, used to check the key pressed in events like `onKeyDown` | +| [List Reusable Blocks](/packages/list-reusable-blocks/README.md) | wp-list-reusable-blocks | Package used to add import/export links to the listing page of the reusable blocks | +| [NUX](/packages/nux/README.md) | wp-nux | Components, and wp.data methods useful for onboarding a new user to the WordPress admin interface | +| [Plugins](/packages/plugins/README.md) | wp-plugins | Plugins module for WordPress | +| [Redux Routine](/packages/redux-routine/README.md) | wp-redux-routine | Redux middleware for generator coroutines | +| [Rich Text](/packages/rich-text/README.md) | wp-rich-text | Helper functions to convert HTML or a DOM tree into a rich text value and back | +| [Shortcode](/packages/shortcode/README.md) | wp-shortcode | Shortcode module for WordPress | +| [Token List](/packages/token-list/README.md) | wp-token-list | Constructable, plain JavaScript [DOMTokenList](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList) implementation, supporting non-browser runtimes | +| [URL](/packages/url/README.md) | wp-url | A collection of utilities to manipulate URLs | +| [Viewport](/packages/viewport/README.md) | wp-viewport | Module for responding to changes in the browser viewport size | +| [Wordcount](/packages/wordcount/README.md) | wp-wordcount | WordPress word count utility | ## Vendor Scripts @@ -55,7 +55,7 @@ The editor also uses some popular third-party packages and scripts. Plugin devel ## Polyfill Scripts The editor also provides polyfills for certain features that may not be available in all modern browsers. -It is recommened to use the main `wp-polyfill` script handle which takes care of loading all the below mentioned polyfills. +It is recommended to use the main `wp-polyfill` script handle which takes care of loading all the below mentioned polyfills. | Script Name | Handle | Description | |-------------|--------|-------------| diff --git a/docs/contributors/testing-overview.md b/docs/contributors/testing-overview.md index c54ef5a7e6033b..6992bd664c3ec2 100644 --- a/docs/contributors/testing-overview.md +++ b/docs/contributors/testing-overview.md @@ -19,7 +19,7 @@ When writing tests consider the following: ## JavaScript Testing -Tests for JavaScript use [Jest](http://facebook.github.io/jest/) as the test runner and its API for [globals](https://facebook.github.io/jest/docs/en/api.html) (`describe`, `test`, `beforeEach` and so on) [assertions](http://facebook.github.io/jest/docs/en/expect.html), [mocks](http://facebook.github.io/jest/docs/en/mock-functions.html), [spies](http://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname) and [mock functions](https://facebook.github.io/jest/docs/en/mock-function-api.html). If needed, you can also use [Enzyme](https://github.com/airbnb/enzyme) for React component testing. +Tests for JavaScript use [Jest](https://jestjs.io/) as the test runner and its API for [globals](https://jestjs.io/docs/en/api.html) (`describe`, `test`, `beforeEach` and so on) [assertions](https://jestjs.io/docs/en/expect.html), [mocks](https://jestjs.io/docs/en/mock-functions.html), [spies](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname) and [mock functions](https://jestjs.io/docs/en/mock-function-api.html). If needed, you can also use [Enzyme](https://github.com/airbnb/enzyme) for React component testing. Assuming you've followed the [instructions](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) to install Node and project dependencies, tests can be run from the command-line with NPM: @@ -88,9 +88,9 @@ describe( 'CheckboxWithLabel', () => { ### Setup and Teardown methods -The Jest API includes some nifty [setup and teardown methods](https://facebook.github.io/jest/docs/en/setup-teardown.html) that allow you to perform tasks *before* and *after* each or all of your tests, or tests within a specific `describe` block. +The Jest API includes some nifty [setup and teardown methods](https://jestjs.io/docs/en/setup-teardown.html) that allow you to perform tasks *before* and *after* each or all of your tests, or tests within a specific `describe` block. -These methods can handle asynchronous code to allow setup that you normally cannot do inline. As with [individual test cases](https://facebook.github.io/jest/docs/en/asynchronous.html#promises), you can return a Promise and Jest will wait for it to resolve: +These methods can handle asynchronous code to allow setup that you normally cannot do inline. As with [individual test cases](https://jestjs.io/docs/en/asynchronous.html#promises), you can return a Promise and Jest will wait for it to resolve: ```javascript // one-time setup for *all* tests @@ -188,7 +188,7 @@ describe( 'The bilbo module', () => { ### Testing globals -We can use [Jest spies](http://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname) to test code that calls global methods. +We can use [Jest spies](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname) to test code that calls global methods. ```javascript import { myModuleFunctionThatOpensANewWindow } from '../my-module'; @@ -220,7 +220,7 @@ However, if the change was intentional, follow these steps to update the snapsho npm run test-unit -- --updateSnapshot --testPathPattern path/to/tests ``` 1. Review the diff and ensure the changes are expected and intentional. -1. Commit. +2. Commit. #### What are snapshots? @@ -286,7 +286,7 @@ describe( 'SolarSystem', () => { } ); ``` -Reducer tests are also be a great fit for snapshots. They are often large, complex data structures that shouldn't change unexpectedly, exactly what snapshots excel at! +Reducer tests are also a great fit for snapshots. They are often large, complex data structures that shouldn't change unexpectedly, exactly what snapshots excel at! #### Working with snapshots @@ -356,6 +356,12 @@ or interactively npm run test-e2e:watch ``` +Sometimes it's useful to observe the browser while running tests. To do so you can use these environment variables: + +```bash +PUPPETEER_HEADLESS=false PUPPETEER_SLOWMO=80 npm run test-e2e:watch +``` + If you're using a different setup, you can provide the base URL, username and password like this: ```bash @@ -374,5 +380,5 @@ Code style in PHP is enforced using [PHP_CodeSniffer](https://github.com/squizla To run unit tests only, without the linter, use `npm run test-unit-php` instead. -[snapshot testing]: https://facebook.github.io/jest/docs/en/snapshot-testing.html -[update snapshots]: https://facebook.github.io/jest/docs/en/snapshot-testing.html#updating-snapshots +[snapshot testing]: https://jestjs.io/docs/en/snapshot-testing.html +[update snapshots]: https://jestjs.io/docs/en/snapshot-testing.html#updating-snapshots diff --git a/docs/designers-developers/assets/fancy-quote-in-inspector.png b/docs/designers-developers/assets/fancy-quote-in-inspector.png new file mode 100644 index 00000000000000..6bd8c06a9e3979 Binary files /dev/null and b/docs/designers-developers/assets/fancy-quote-in-inspector.png differ diff --git a/docs/designers-developers/assets/fancy-quote-with-style.png b/docs/designers-developers/assets/fancy-quote-with-style.png new file mode 100644 index 00000000000000..31f38063a1f1dd Binary files /dev/null and b/docs/designers-developers/assets/fancy-quote-with-style.png differ diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/inspector.png b/docs/designers-developers/assets/inspector.png similarity index 100% rename from docs/designers-developers/developers/tutorials/block-tutorial/inspector.png rename to docs/designers-developers/assets/inspector.png diff --git a/docs/designers-developers/assets/js-tutorial-console-log-error.png b/docs/designers-developers/assets/js-tutorial-console-log-error.png new file mode 100644 index 00000000000000..836a663484192f Binary files /dev/null and b/docs/designers-developers/assets/js-tutorial-console-log-error.png differ diff --git a/docs/designers-developers/assets/js-tutorial-console-log-success.png b/docs/designers-developers/assets/js-tutorial-console-log-success.png new file mode 100644 index 00000000000000..7b42853fb40642 Binary files /dev/null and b/docs/designers-developers/assets/js-tutorial-console-log-success.png differ diff --git a/docs/designers-developers/assets/js-tutorial-error-blocks-undefined.png b/docs/designers-developers/assets/js-tutorial-error-blocks-undefined.png new file mode 100644 index 00000000000000..1f27c36ce75956 Binary files /dev/null and b/docs/designers-developers/assets/js-tutorial-error-blocks-undefined.png differ diff --git a/docs/designers-developers/assets/sidebar-style-and-controls.png b/docs/designers-developers/assets/sidebar-style-and-controls.png new file mode 100644 index 00000000000000..725ddfdd87a4bd Binary files /dev/null and b/docs/designers-developers/assets/sidebar-style-and-controls.png differ diff --git a/docs/designers-developers/assets/sidebar-up-and-running.png b/docs/designers-developers/assets/sidebar-up-and-running.png new file mode 100644 index 00000000000000..12bc2947d48a37 Binary files /dev/null and b/docs/designers-developers/assets/sidebar-up-and-running.png differ diff --git a/docs/designers-developers/assets/toolbar-text.png b/docs/designers-developers/assets/toolbar-text.png new file mode 100644 index 00000000000000..76b18c6b8f368f Binary files /dev/null and b/docs/designers-developers/assets/toolbar-text.png differ diff --git a/docs/designers-developers/assets/toolbar-with-custom-button.png b/docs/designers-developers/assets/toolbar-with-custom-button.png new file mode 100644 index 00000000000000..3b22afaca318ac Binary files /dev/null and b/docs/designers-developers/assets/toolbar-with-custom-button.png differ diff --git a/docs/designers-developers/designers/README.md b/docs/designers-developers/designers/README.md index 362cf794885f20..a630847b37661e 100644 --- a/docs/designers-developers/designers/README.md +++ b/docs/designers-developers/designers/README.md @@ -1,3 +1,3 @@ # Designer Documentation -For those designing blocks and other Block Editor integrations, this documentation will provide resources for creating beautiful and intuitive layouts. +For those designing blocks and other block editor integrations, this documentation will provide resources for creating beautiful and intuitive layouts. diff --git a/docs/designers-developers/designers/animation.md b/docs/designers-developers/designers/animation.md new file mode 100644 index 00000000000000..24948723e0d8c5 --- /dev/null +++ b/docs/designers-developers/designers/animation.md @@ -0,0 +1,40 @@ +# Animation + +Animation can help reinforce a sense of hierarchy and spatial orientation. This document goes into principles you should follow when you add animation. + +## Principles + +### Point of Origin + +- Animation can help anchor an interface element. For example a menu can scale up from the button that opened it. +- Animation can help give a sense of place; for example a sidebar can animate in from the side, implying it was always hidden off-screen. +- Design your animations as if you're working with real-world materials. Imagine your user interface elements are made of real materials — when not on screen, where are they? Use animation to help express that. + +### Speed + +- Animations should never block a user interaction. They should be fast, almost always complete in less than 0.2 seconds. +- A user should not have to wait for an animation to finish before they can interact. +- Animations should be performant. Use `transform` CSS properties when you can, these render elements on the GPU, making them smooth. +- If an animation can't be made fast & performant, leave it out. + +### Simple + +- Don't bounce if the material isn't made of rubber. +- Don't rotate, fold, or animate on a curved path. Keep it simple. + +### Consistency + +In creating consistent animations, we have to establish physical rules for how elements behave when animated. When all animations follow these rules, they feel consistent, related, and predictable. An animation should match user expectations, if it doesn't, it's probably not the right animation for the job. + +Reuse animations if one already exists for your task. + +## Accessibility Considerations + +- Animations should be subtle. Be cognizent of users with [vestibular disorders triggered by motion](https://www.ncbi.nlm.nih.gov/pubmed/29017000). +- Don't animate elements that are currently reporting content to adaptive technology (e.g., an `aria-live` region that's receiving updates). This can cause confusion wherein the technology tries to parse a region that's actively changing. +- Avoid animations that aren't directly triggered by user behaviors. +- Whenever possible, ensure that animations respect the OS-level "Reduce Motion" settings. This can be done by utilizing the [`prefers-reduce-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media query. Gutenberg includes a `@reduce-motion` mixin for this, to be used alongside rules that include a CSS `animate` property. + +## Inventory of Reused Animations + +The generic `Animate` component is used to animate different parts of the interface. See [the component documentation](/packages/components/src/animate/README.md) for more details about the available animations. diff --git a/docs/designers-developers/designers/block-design.md b/docs/designers-developers/designers/block-design.md index ae6abaf4c8a37f..3840c863dd4875 100644 --- a/docs/designers-developers/designers/block-design.md +++ b/docs/designers-developers/designers/block-design.md @@ -17,13 +17,44 @@ Basic block settings won’t always make sense in the context of the placeholder ### The block sidebar should only be used for advanced, tertiary controls -The sidebar is not visible by default on a small / mobile screen, and may also be collapsed in a desktop view. Therefore, it should not be relied on for anything that is necessary for the basic operation of the block. Pick good defaults, make important actions available in the block toolbar, and think of the sidebar as something that only power users may discover. In addition, use sections and headers in the block sidebar if there are more than a handful of options, in order to allow users to easily scan and understand the options available. +The sidebar is not visible by default on a small / mobile screen, and may also be collapsed in a desktop view. Therefore, it should not be relied on for anything that is necessary for the basic operation of the block. Pick good defaults, make important actions available in the block toolbar, and think of the sidebar as something that most users should not need to open. -## Do's and Don'ts +In addition, use sections and headers in the block sidebar if there are more than a handful of options, in order to allow users to easily scan and understand the options available. + +Each block sidebar comes with an "Advanced" section by default. This area houses an "Additional CSS Class" field, and should be used to house other power user controls. + +## Setup state vs. live preview state + +Setup states, sometimes referred to as "placeholders", can be used to walk users through an initial process before showing the live preview state of the block. The setup process gathers information from the user that is needed to render the block. A block’s setup state is indicated with a grey background to provide clear differentiation for the user. Not all blocks have setup states — for example, the paragraph block. + + + +A setup state is **not** necessary if: + +- You can provide good default content in the block that will meet most people’s needs. +- That default content is easy to edit and customize. + +Use a setup state if: + +- There isn’t a clear default state that would work for most users. +- You need to gather input from the user that doesn’t have a 1-1 relationship with the live preview of the block (for example, if you need the user to input an API key to render content). +- You need more information from the user in order to render useful default content. + +For blocks that do have setup states, once the user has gone through the setup process, the placeholder is replaced with the live preview state of that block. + + + +When the block is selected, additional controls may be revealed to customize the block’s contents. For example, when the image gallery is selected, it reveals controls to remove or add images. + + + +In most cases, a block’s setup state is only shown once and then further customization is done via the live preview state. However, in some cases it might be desirable to allow the user to return to the setup state — for example, if all the block content has been deleted or via a link from the block’s toolbar or sidebar. + +## Do's and Don'ts ### Blocks -A block should have a straightforward, short name so users can easily find it in the Block Library. A block named "YouTube" is easy to find and understand. The same block, named "Embedded Video (YouTube)", would be less clear and harder to find in the Block Library. +A block should have a straightforward, short name so users can easily find it in the Block Library. A block named "YouTube" is easy to find and understand. The same block, named "Embedded Video (YouTube)", would be less clear and harder to find in the Block Library. 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. @@ -85,6 +116,10 @@ Because the Drop Cap feature is not necessary for the basic operation of the blo Check how your block looks, feels, and works on as many devices and screen sizes as you can. +### 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. + ## Examples To demonstrate some of these practices, here are a few annotated examples of default Gutenberg blocks: diff --git a/docs/designers-developers/designers/menu-item.md b/docs/designers-developers/designers/menu-item.md new file mode 100644 index 00000000000000..246a8fde1d4570 --- /dev/null +++ b/docs/designers-developers/designers/menu-item.md @@ -0,0 +1,98 @@ +# MenuItem + +`MenuItem` is a component which renders a button intended to be used in combination with the `MenuGroup` component. + + + +1. MenuItem + +## Table of contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) + +## Design guidelines + +### Usage + +A `MenuGroup` containing `MenuItem`s can be used within a `Dropdown`. A `MenuGroup` can also have other `MenuGroup`s within it so menus can be nested. + +## Development guidelines + +### Usage + +```jsx +import { MenuItem } from '@wordpress/components'; +import { withState } from '@wordpress/compose'; + +const MyMenuItem = withState( { + isActive: true, +} )( ( { isActive, setState } ) => ( + +) ); +``` + +### Props + +`MenuItem` supports the following props. Any additional props are passed through to the underlying [Button](../button) or [IconButton](../icon-button) component. + +#### `children` + +- Type: `WPElement` +- Required: No + +Element to render as child of button. + +Element + +#### `label` + +- Type: `string` +- Required: No + +String to use as primary button label text, applied as `aria-label`. Useful in cases where an `info` prop is passed, where `label` should be the minimal text of the button, described in further detail by `info`. + +Defaults to the value of `children`, if `children` is passed as a string. + +#### `info` + +- Type: `string` +- Required: No + +Text to use as description for button text. + +Refer to documentation for [`label`](#label). + +#### `icon` + +- Type: `string` +- Required: No + +Refer to documentation for [IconButton's `icon` prop](../icon-button/README.md#icon). + +#### `shortcut` + +- Type: `string` +- Required: No + +Refer to documentation for [Shortcut's `shortcut` prop](../shortcut/README.md#shortcut). + +#### `role` + +- Type: `string` +- Require: No +- Default: `'menuitem'` + +[Aria Spec](https://www.w3.org/TR/wai-aria-1.1/#aria-checked). If you need to have selectable menu items use `MenuItemRadio` for single select, and `MenuItemCheckbox` for multiselect. + +## Related components + +- The `DropdownMenu` displays a list of actions (each contained in a `MenuItem`, `MenuItemsChoice`, or `MenuGroup`) in a compact way. It appears in a `Popover` after the user has interacted with an element (a button or icon) or when they perform a specific action. +- `MenuItemsChoice` diff --git a/docs/designers-developers/developers/README.md b/docs/designers-developers/developers/README.md index 5c552b2bd835ee..162c45fbcfa4eb 100644 --- a/docs/designers-developers/developers/README.md +++ b/docs/designers-developers/developers/README.md @@ -1,45 +1,45 @@ # Developer Documentation -Gutenberg is highly flexible, like most of WordPress. You can build custom blocks, modify the editor's appearance, add special plugins, and much more. +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 -Gutenberg is about blocks, and the main extensibility API of Gutenberg is the Block API. It allows you to create your own static blocks, dynamic blocks 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 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, the [Blocks Tutorial](../../../docs/designers-developers/developers/tutorials/block-tutorial/readme.md) is the best place to start. +If you want to learn more about block creation, the [Blocks Tutorial](/docs/designers-developers/developers/tutorials/block-tutorial/readme.md) is 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/reference/hooks/block-filters.md) section. +Learn more in the [Block Filters](/docs/designers-developers/developers/filters/block-filters.md) section. ## Extending the Editor UI Extending the editor UI can be accomplished with the `registerPlugin` API, allowing you to define all your plugin's UI elements in one place. -Refer to the [Plugins](https://github.com/WordPress/gutenberg/blob/master/packages/plugins/README.md) and [Edit Post](https://github.com/WordPress/gutenberg/blob/master/packages/edit-post/README.md) section for more information. +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/reference/hooks/editor-filters.md) page. +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. ## Meta Boxes -**Porting PHP meta boxes to blocks and Gutenberg plugins is highly encouraged!** +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). -Discover how [Meta Box](../../../docs/designers-developers/developers/backwards-compatibility/meta-box.md) support works in Gutenberg. +See how the new editor [supports existing Meta Boxes](/docs/designers-developers/developers/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/designers-developers/developers/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/designers-developers/developers/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/designers-developers/developers/filters/parser-filters.md) section. diff --git a/docs/designers-developers/developers/accessibility.md b/docs/designers-developers/developers/accessibility.md new file mode 100644 index 00000000000000..ab6c0d0066b1dc --- /dev/null +++ b/docs/designers-developers/developers/accessibility.md @@ -0,0 +1,17 @@ +# Accessibility + +Accessibility documentation for developers working on the Gutenberg Project. + +For more information on accessibility and WordPress see the [Make WordPress Accessibility Handbook](https://make.wordpress.org/accessibility/handbook/) and the [Accessibility Team section](https://make.wordpress.org/accessibility/). + +## Landmark Regions + +It is a best practice to include ALL content on the page in landmarks, so that screen reader users who rely on them to navigate from section to section do not lose track of content. + +For setting up navigation between different regions, see the [navigateRegions package](/packages/components/src/higher-order/navigate-regions/README.md) for additional documentation. + +Read more regarding landmark design from W3C: + +- [General Principles of Landmark Design](https://www.w3.org/TR/wai-aria-practices-1.1/#general-principles-of-landmark-design) +- [ARIA Landmarks Example](https://www.w3.org/TR/wai-aria-practices/examples/landmarks/) +- [HTML5 elements that by default define ARIA landmarks](https://www.w3.org/TR/wai-aria-practices/examples/landmarks/HTML5.html) diff --git a/docs/designers-developers/developers/backward-compatibility/README.md b/docs/designers-developers/developers/backward-compatibility/README.md new file mode 100644 index 00000000000000..bd559617c5b17f --- /dev/null +++ b/docs/designers-developers/developers/backward-compatibility/README.md @@ -0,0 +1 @@ +# Backward Compatibility diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md new file mode 100644 index 00000000000000..496a8b3e5d571a --- /dev/null +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -0,0 +1,273 @@ +# Deprecations + +The Gutenberg project's deprecation policy is intended to support backward compatibility for releases, when possible. 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. + +## 5.5.0 + +- The PHP function `gutenberg_init` has been removed. +- The PHP function `is_gutenberg_page` has been removed. Use [`WP_Screen::is_block_editor`](https://developer.wordpress.org/reference/classes/wp_screen/is_block_editor/) instead. +- The PHP function `the_gutenberg_project` has been removed. +- The PHP function `gutenberg_default_post_format_template` has been removed. +- The PHP function `gutenberg_get_available_image_sizes` has been removed. +- The PHP function `gutenberg_get_autosave_newer_than_post_save` has been removed. +- The PHP function `gutenberg_default_post_format_template` has been removed. +- The PHP function `gutenberg_editor_scripts_and_styles` has been removed. + +## 5.4.0 + +- The PHP function `gutenberg_load_plugin_textdomain` has been removed. +- The PHP function `gutenberg_get_jed_locale_data` has been removed. +- The PHP function `gutenberg_load_locale_data` has been removed. + +## 5.3.0 + +- The PHP function `gutenberg_redirect_to_classic_editor_when_saving_posts` has been removed. +- The PHP function `gutenberg_revisions_link_to_editor` has been removed. +- The PHP function `gutenberg_remember_classic_editor_when_saving_posts` has been removed. +- The PHP function `gutenberg_can_edit_post_type` has been removed. Use [`use_block_editor_for_post_type`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post_type/) instead. +- The PHP function `gutenberg_can_edit_post` has been removed. Use [`use_block_editor_for_post`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post/) instead. + +## 5.2.0 + +- The PHP function `gutenberg_parse_blocks` has been removed. Use [`parse_blocks`](https://developer.wordpress.org/reference/functions/parse_blocks/) instead. +- The PHP function `get_dynamic_blocks_regex` has been removed. +- The PHP function `gutenberg_render_block` has been removed. Use [`render_block`](https://developer.wordpress.org/reference/functions/render_block/) instead. +- The PHP function `strip_dynamic_blocks` has been removed. For use in excerpt preparation, consider [`excerpt_remove_blocks`](https://developer.wordpress.org/reference/functions/excerpt_remove_blocks/) instead. +- The PHP function `strip_dynamic_blocks_add_filter` has been removed. +- The PHP function `strip_dynamic_blocks_remove_filter` has been removed. +- The PHP function `gutenberg_post_has_blocks` has been removed. Use [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/) instead. +- The PHP function `gutenberg_content_has_blocks` has been removed. Use [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/) instead. +- The PHP function `gutenberg_register_rest_routes` has been removed. +- The PHP function `gutenberg_add_taxonomy_visibility_field` has been removed. +- The PHP function `gutenberg_get_taxonomy_visibility_data` has been removed. +- The PHP function `gutenberg_add_permalink_template_to_posts` has been removed. +- The PHP function `gutenberg_add_block_format_to_post_content` has been removed. +- The PHP function `gutenberg_add_target_schema_to_links` has been removed. +- The PHP function `gutenberg_register_post_prepare_functions` has been removed. +- The PHP function `gutenberg_silence_rest_errors` has been removed. +- The PHP function `gutenberg_filter_post_type_labels` has been removed. +- The PHP function `gutenberg_preload_api_request` has been removed. Use [`rest_preload_api_request`](https://developer.wordpress.org/reference/functions/rest_preload_api_request/) instead. +- The PHP function `gutenberg_remove_wpcom_markdown_support` has been removed. +- The PHP function `gutenberg_add_gutenberg_post_state` has been removed. +- The PHP function `gutenberg_bulk_post_updated_messages` has been removed. +- The PHP function `gutenberg_kses_allowedtags` has been removed. +- The PHP function `gutenberg_add_responsive_body_class` has been removed. +- The PHP function `gutenberg_add_edit_link_filters` has been removed. +- The PHP function `gutenberg_add_edit_link` has been removed. +- The PHP function `gutenberg_block_bulk_actions` has been removed. +- The PHP function `gutenberg_replace_default_add_new_button` has been removed. +- The PHP function `gutenberg_content_block_version` has been removed. Use [`block_version`](https://developer.wordpress.org/reference/functions/block_version/) instead. +- The PHP function `gutenberg_get_block_categories` has been removed. Use [`get_block_categories`](https://developer.wordpress.org/reference/functions/get_block_categories/) instead. +- The PHP function `register_tinymce_scripts` has been removed. Use [`wp_register_tinymce_scripts`](https://developer.wordpress.org/reference/functions/wp_register_tinymce_scripts/) instead. +- The PHP function `gutenberg_register_post_types` has been removed. +- The `gutenberg` theme support option has been removed. Use [`align-wide`](https://wordpress.org/gutenberg/handbook/designers-developers/developers/themes/theme-support/#wide-alignment) instead. +- The PHP function `gutenberg_prepare_blocks_for_js` has been removed. Use [`get_block_editor_server_block_settings`](https://developer.wordpress.org/reference/functions/get_block_editor_server_block_settings/) instead. +- The PHP function `gutenberg_load_list_reusable_blocks` has been removed. +- The PHP function `_gutenberg_utf8_split` has been removed. Use `_mb_substr` instead. +- The PHP function `gutenberg_disable_editor_settings_wpautop` has been removed. +- The PHP function `gutenberg_add_rest_nonce_to_heartbeat_response_headers` has been removed. +- The PHP function `gutenberg_check_if_classic_needs_warning_about_blocks` has been removed. +- The PHP function `gutenberg_warn_classic_about_blocks` has been removed. +- The PHP function `gutenberg_show_privacy_policy_help_text` has been removed. +- The PHP function `gutenberg_common_scripts_and_styles` has been removed. Use [`wp_common_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_common_block_scripts_and_styles/) instead. +- The PHP function `gutenberg_enqueue_registered_block_scripts_and_styles` has been removed. Use [`wp_enqueue_registered_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_enqueue_registered_block_scripts_and_styles/) instead. +- The PHP function `gutenberg_meta_box_save` has been removed. +- The PHP function `gutenberg_meta_box_save_redirect` has been removed. +- The PHP function `gutenberg_filter_meta_boxes` has been removed. +- The PHP function `gutenberg_intercept_meta_box_render` has been removed. +- The PHP function `gutenberg_override_meta_box_callback` has been removed. +- The PHP function `gutenberg_show_meta_box_warning` has been removed. +- The PHP function `the_gutenberg_metaboxes` has been removed. Use [`the_block_editor_meta_boxes`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_boxes/) instead. +- The PHP function `gutenberg_meta_box_post_form_hidden_fields` has been removed. Use [`the_block_editor_meta_box_post_form_hidden_fields`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_box_post_form_hidden_fields/) instead. +- The PHP function `gutenberg_toggle_custom_fields` has been removed. +- The PHP function `gutenberg_collect_meta_box_data` has been removed. Use [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/) instead. +- `window._wpLoadGutenbergEditor` has been removed. Use `window._wpLoadBlockEditor` instead. Note: This is a private API, not intended for public use. It may be removed in the future. +- The PHP function `gutenberg_get_script_polyfill` has been removed. Use [`wp_get_script_polyfill`](https://developer.wordpress.org/reference/functions/wp_get_script_polyfill/) instead. +- The PHP function `gutenberg_add_admin_body_class` has been removed. Use the `.block-editor-page` class selector in your stylesheets if you need to scope styles to the block editor screen. + +## 4.5.0 +- `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. +- `wp.editor.PostPublishPanelToggle` has been deprecated in favor of `wp.editor.PostPublishButton`. + +## 4.4.0 + +- `wp.date.getSettings` has been removed. Please use `wp.date.__experimentalGetSettings` instead. +- `wp.compose.remountOnPropChange` has been removed. +- The following editor store actions have been removed: `createNotice`, `removeNotice`, `createSuccessNotice`, `createInfoNotice`, `createErrorNotice`, `createWarningNotice`. Use the equivalent actions by the same name from the `@wordpress/notices` module. +- The id prop of wp.nux.DotTip has been removed. Please use the tipId prop instead. +- `wp.blocks.isValidBlock` has been removed. Please use `wp.blocks.isValidBlockContent` instead but keep in mind that the order of params has changed. +- `wp.data` `registry.registerReducer` has been deprecated. Use `registry.registerStore` instead. +- `wp.data` `registry.registerSelectors` has been deprecated. Use `registry.registerStore` instead. +- `wp.data` `registry.registerActions` has been deprecated. Use `registry.registerStore` instead. +- `wp.data` `registry.registerResolvers` has been deprecated. Use `registry.registerStore` instead. +- `moment` has been removed from the public API for the date module. + +## 4.3.0 + +- `isEditorSidebarPanelOpened` selector (`core/edit-post`) has been removed. Please use `isEditorPanelEnabled` instead. +- `toggleGeneralSidebarEditorPanel` action (`core/edit-post`) has been removed. Please use `toggleEditorPanelOpened` instead. +- `wp.components.PanelColor` component has been removed. Please use `wp.editor.PanelColorSettings` instead. +- `wp.editor.PanelColor` component has been removed. Please use `wp.editor.PanelColorSettings` instead. + +## 4.2.0 + +- Writing resolvers as async generators has been removed. Use the controls plugin instead. +- `wp.components.AccessibleSVG` component has been removed. Please use `wp.components.SVG` instead. +- The `wp.editor.UnsavedChangesWarning` component no longer accepts a `forceIsDirty` prop. +- `setActiveMetaBoxLocations` action (`core/edit-post`) has been removed. +- `initializeMetaBoxState` action (`core/edit-post`) has been removed. +- `wp.editPost.initializeEditor` no longer returns an object. Use the `setActiveMetaBoxLocations` action (`core/edit-post`) in place of the existing object's `initializeMetaBoxes` function. +- `setMetaBoxSavedData` action (`core/edit-post`) has been removed. +- `getMetaBoxes` selector (`core/edit-post`) has been removed. Use `getActiveMetaBoxLocations` selector (`core/edit-post`) instead. +- `getMetaBox` selector (`core/edit-post`) has been removed. Use `isMetaBoxLocationActive` selector (`core/edit-post`) instead. +- Attribute type coercion has been removed. Omit the source to preserve type via serialized comment demarcation. +- `mediaDetails` in object passed to `onFileChange` callback of `wp.editor.mediaUpload`. Please use `media_details` property instead. +- `wp.components.CodeEditor` has been removed. Used `wp.codeEditor` directly instead. +- `wp.blocks.setUnknownTypeHandlerName` has been removed. Please use `setFreeformContentHandlerName` and `setUnregisteredTypeHandlerName` instead. +- `wp.blocks.getUnknownTypeHandlerName` has been removed. Please use `getFreeformContentHandlerName` and `getUnregisteredTypeHandlerName` instead. +- The Reusable Blocks Data API was marked as experimental as it's subject to change in the future. + +## 4.1.0 + +- `wp.data.dispatch( 'core/editor' ).checkTemplateValidity` has been removed. Validity is verified automatically upon block reset. + +## 4.0.0 + +- `wp.editor.RichTextProvider` has been removed. Please use `wp.data.select( 'core/editor' )` methods instead. +- `wp.components.Draggable` as a DOM node drag handler has been removed. Please, use `wp.components.Draggable` as a wrap component for your DOM node drag handler. +- `wp.i18n.getI18n` has been removed. Use `__`, `_x`, `_n`, or `_nx` instead. +- `wp.i18n.dcnpgettext` has been removed. Use `__`, `_x`, `_n`, or `_nx` instead. + +## 3.9.0 + +- RichText `getSettings` prop has been removed. The `unstableGetSettings` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice. +- RichText `onSetup` prop has been removed. The `unstableOnSetup` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice. +- `wp.editor.getColorName` has been removed. Please use `wp.editor.getColorObjectByColorValue` instead. +- `wp.editor.getColorClass` has been renamed. Please use `wp.editor.getColorClassName` instead. +- `value` property in color objects passed by `wp.editor.withColors` has been removed. Please use color property instead. +- The Subheading block has been removed. Please use the Paragraph block instead. +- `wp.blocks.getDefaultBlockForPostFormat` has been removed. + +## 3.8.0 + + - `wp.components.withContext` has been removed. Please use `wp.element.createContext` instead. See: https://reactjs.org/docs/context.html. + - `wp.coreBlocks.registerCoreBlocks` has been removed. Please use `wp.blockLibrary.registerCoreBlocks` instead. + - `wp.editor.DocumentTitle` component has been removed. + - `getDocumentTitle` selector (`core/editor`) has been removed. + +## 3.7.0 + + - `wp.components.withAPIData` has been removed. Please use the Core Data module or `wp.apiFetch` directly instead. + - `wp.data.dispatch("core").receiveTerms` has been deprecated. Please use `wp.data.dispatch("core").receiveEntityRecords` instead. + - `getCategories` resolver has been deprecated. Please use `getEntityRecords` resolver instead. + - `wp.data.select("core").getTerms` has been deprecated. Please use `wp.data.select("core").getEntityRecords` instead. + - `wp.data.select("core").getCategories` has been deprecated. Please use `wp.data.select("core").getEntityRecords` instead. + - `wp.data.select("core").isRequestingCategories` has been deprecated. Please use `wp.data.select("core/data").isResolving` instead. + - `wp.data.select("core").isRequestingTerms` has been deprecated. Please use `wp.data.select("core").isResolving` instead. + - `wp.data.restrictPersistence`, `wp.data.setPersistenceStorage` and `wp.data.setupPersistence` has been removed. Please use the data persistence plugin instead. + +## 3.6.0 + + - `wp.editor.editorMediaUpload` has been removed. Please use `wp.editor.mediaUpload` instead. + - `wp.utils.getMimeTypesArray` has been removed. + - `wp.utils.mediaUpload` has been removed. Please use `wp.editor.mediaUpload` instead. + - `wp.utils.preloadImage` has been removed. + - `supports.wideAlign` has been removed from the Block API. Please use `supports.alignWide` instead. + - `wp.blocks.isSharedBlock` has been removed. Use `wp.blocks.isReusableBlock` instead. + - `fetchSharedBlocks` action (`core/editor`) has been removed. Use `fetchReusableBlocks` instead. + - `receiveSharedBlocks` action (`core/editor`) has been removed. Use `receiveReusableBlocks` instead. + - `saveSharedBlock` action (`core/editor`) has been removed. Use `saveReusableBlock` instead. + - `deleteSharedBlock` action (`core/editor`) has been removed. Use `deleteReusableBlock` instead. + - `updateSharedBlockTitle` action (`core/editor`) has been removed. Use `updateReusableBlockTitle` instead. + - `convertBlockToSaved` action (`core/editor`) has been removed. Use `convertBlockToReusable` instead. + - `getSharedBlock` selector (`core/editor`) has been removed. Use `getReusableBlock` instead. + - `isSavingSharedBlock` selector (`core/editor`) has been removed. Use `isSavingReusableBlock` instead. + - `isFetchingSharedBlock` selector (`core/editor`) has been removed. Use `isFetchingReusableBlock` instead. + - `getSharedBlocks` selector (`core/editor`) has been removed. Use `getReusableBlocks` instead. + +## 3.5.0 + + - `wp.components.ifCondition` has been removed. Please use `wp.compose.ifCondition` instead. + - `wp.components.withGlobalEvents` has been removed. Please use `wp.compose.withGlobalEvents` instead. + - `wp.components.withInstanceId` has been removed. Please use `wp.compose.withInstanceId` instead. + - `wp.components.withSafeTimeout` has been removed. Please use `wp.compose.withSafeTimeout` instead. + - `wp.components.withState` has been removed. Please use `wp.compose.withState` instead. + - `wp.element.pure` has been removed. Please use `wp.compose.pure` instead. + - `wp.element.compose` has been removed. Please use `wp.compose.compose` instead. + - `wp.element.createHigherOrderComponent` has been removed. Please use `wp.compose.createHigherOrderComponent` instead. + - `wp.utils.buildTermsTree` has been removed. + - `wp.utils.decodeEntities` has been removed. Please use `wp.htmlEntities.decodeEntities` instead. + - All references to a block's `uid` have been replaced with equivalent props and selectors for `clientId`. + - The `wp.editor.MediaPlaceholder` component `onSelectUrl` prop has been renamed to `onSelectURL`. + - The `wp.editor.UrlInput` component has been renamed to `wp.editor.URLInput`. + - The Text Columns block has been removed. Please use the Columns block instead. + - `InnerBlocks` grouped layout is removed. Use intermediary nested inner blocks instead. See Columns / Column block for reference implementation. + - `RichText` explicit `element` format removed. Please use the compatible `children` format instead. + +## 3.4.0 + + - `focusOnMount` prop in the `Popover` component has been changed from `Boolean`-only to an enum-style property that accepts `"firstElement"`, `"container"`, or `false`. Please convert any ` ` usage to ` `. + - `wp.utils.keycodes` utilities are removed. Please use `wp.keycodes` instead. + - Block `id` prop in `edit` function removed. Please use block `clientId` prop instead. + - `property` source removed. Please use equivalent `text`, `html`, or `attribute` source, or comment attribute instead. + +## 3.3.0 + + - `useOnce: true` has been removed from the Block API. Please use `supports.multiple: false` instead. + - Serializing components using `componentWillMount` lifecycle method. Please use the constructor instead. + - `blocks.Autocomplete.completers` filter removed. Please use `editor.Autocomplete.completers` instead. + - `blocks.BlockEdit` filter removed. Please use `editor.BlockEdit` instead. + - `blocks.BlockListBlock` filter removed. Please use `editor.BlockListBlock` instead. + - `blocks.MediaUpload` filter removed. Please use `editor.MediaUpload` instead. + +## 3.2.0 + + - `wp.data.withRehydratation` has been renamed to `wp.data.withRehydration`. + - The `wp.editor.ImagePlaceholder` component is removed. Please use `wp.editor.MediaPlaceholder` instead. + - `wp.utils.deprecated` function removed. Please use `wp.deprecated` instead. + - `wp.utils.blob` removed. Please use `wp.blob` instead. + - `getInserterItems`: the `allowedBlockTypes` argument was removed and the `parentUID` argument was added. + - `getFrecentInserterItems` selector removed. Please use `getInserterItems` instead. + - `getSupportedBlocks` selector removed. Please use `canInsertBlockType` instead. + +## 3.1.0 + + - All components in `wp.blocks.*` are removed. Please use `wp.editor.*` instead. + - `wp.blocks.withEditorSettings` is removed. Please use the data module to access the editor settings `wp.data.select( "core/editor" ).getEditorSettings()`. + - All DOM utils in `wp.utils.*` are removed. Please use `wp.dom.*` instead. + - `isPrivate: true` has been removed from the Block API. Please use `supports.inserter: false` instead. + - `wp.utils.isExtraSmall` function removed. Please use `wp.viewport` module instead. + - `getEditedPostExcerpt` selector removed (`core/editor`). Use `getEditedPostAttribute( 'excerpt' )` instead. + +## 3.0.0 + + - `wp.blocks.registerCoreBlocks` function removed. Please use `wp.coreBlocks.registerCoreBlocks` instead. + - Raw TinyMCE event handlers for `RichText` have been deprecated. Please use [documented props](/packages/editor/src/components/rich-text/README.md), ancestor event handler, or onSetup access to the internal editor instance event hub instead. + +## 2.8.0 + + - `Original autocompleter interface in wp.components.Autocomplete` updated. Please use `latest autocompleter interface` instead. See [autocomplete](/packages/components/src/autocomplete/README.md) for more info. + - `getInserterItems`: the `allowedBlockTypes` argument is now mandatory. + - `getFrecentInserterItems`: the `allowedBlockTypes` argument is now mandatory. + +## 2.7.0 + + - `wp.element.getWrapperDisplayName` function removed. Please use `wp.element.createHigherOrderComponent` instead. + +## 2.6.0 + + - `wp.blocks.getBlockDefaultClassname` function removed. Please use `wp.blocks.getBlockDefaultClassName` instead. + - `wp.blocks.Editable` component removed. Please use the `wp.blocks.RichText` component instead. + +## 2.5.0 + + - Returning raw HTML from block `save` is unsupported. Please use the `wp.element.RawHTML` component instead. + - `wp.data.query` higher-order component removed. Please use `wp.data.withSelect` instead. + +## 2.4.0 + + - `wp.blocks.BlockDescription` component removed. Please use the `description` block property instead. + - `wp.blocks.InspectorControls.*` components removed. Please use `wp.components.*` components instead. + - `wp.blocks.source.*` matchers removed. Please use the declarative attributes instead. See [block attributes](/docs/designers-developers/developers/block-api/block-attributes.md) for more info. + - `wp.data.select( 'selector', ...args )` removed. Please use `wp.data.select( reducerKey' ).*` instead. + - `wp.blocks.MediaUploadButton` component removed. Please use `wp.blocks.MediaUpload` component instead. diff --git a/docs/designers-developers/developers/backward-compatibility/meta-box.md b/docs/designers-developers/developers/backward-compatibility/meta-box.md new file mode 100644 index 00000000000000..e7350dadba7fb3 --- /dev/null +++ b/docs/designers-developers/developers/backward-compatibility/meta-box.md @@ -0,0 +1,80 @@ +# Meta Boxes + +This is a brief document detailing how meta box support works in Gutenberg. 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. + +### Testing, Converting, and Maintaining Existing Meta Boxes + +Before converting meta boxes to blocks, it may be easier to test if a meta box works with Gutenberg, and explicitly mark it as such. + +If a meta box *doesn't* work with in Gutenberg, and updating it to work correctly is not an option, the next step is to add the `__block_editor_compatible_meta_box` argument to the meta box declaration: + +```php +add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', + null, 'normal', 'high', + array( + '__block_editor_compatible_meta_box' => false, + ) +); +``` + +WordPress won't show the meta box but a message saying that it isn't compatible with the block editor, including a link to the Classic Editor plugin. By default, `__block_editor_compatible_meta_box` is `true`. + +After a meta box is converted to a block, it can be declared as existing for backward compatibility: + +```php +add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', + null, 'normal', 'high', + array( + '__back_compat_meta_box' => true, + ) +); +``` + +When Gutenberg is used, this meta box will no longer be displayed in the meta box area, as it now only exists for backward compatibility purposes. It will continue to display correctly in the classic editor. + +### Meta Box Data Collection + +On each Gutenberg page load, we register an action that collects the meta box data to determine if an area is empty. The original global state is reset upon collection of meta box data. + +See [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/). + +It will run through the functions and hooks that `post.php` runs to register meta boxes; namely `add_meta_boxes`, `add_meta_boxes_{$post->post_type}`, and `do_meta_boxes`. + +Meta boxes are filtered to strip out any core meta boxes, standard custom taxonomy meta boxes, and any meta boxes that have declared themselves as only existing for backward compatibility purposes. + +Then each location for this particular type of meta box is checked for whether it is active. If it is not empty a value of true is stored, if it is empty a value of false is stored. This meta box location data is then dispatched by the editor Redux store in `INITIALIZE_META_BOX_STATE`. + +Ideally, this could be done at instantiation of the editor and help simplify this flow. However, it is not possible to know the meta box state before `admin_enqueue_scripts`, where we are calling `initializeEditor()`. This will have to do, unless we want to move `initializeEditor()` to fire in the footer or at some point after `admin_head`. With recent changes to editor bootstrapping this might now be possible. Test with ACF to make sure. + +### Redux and React Meta Box Management + +When rendering the Gutenberg Page, the meta boxes are rendered to a hidden div `#metaboxes`. + +*The Redux store will hold all meta boxes as inactive by default*. When +`INITIALIZE_META_BOX_STATE` comes in, the store will update any active meta box areas by setting the `isActive` flag to `true`. Once this happens React will check for the new props sent in by Redux on the `MetaBox` component. If that `MetaBox` is now active, instead of rendering null, a `MetaBoxArea` component will be rendered. The `MetaBox` component is the container component that mediates between the `MetaBoxArea` and the Redux Store. *If no meta boxes are active, nothing happens. This will be the default behavior, as all core meta boxes have been stripped.* + +#### MetaBoxArea Component + +When the component renders it will store a reference to the meta boxes container and retrieve the meta boxes HTML from the prefetch location. + +When the post is updated, only meta box areas that are active will be submitted. This prevents unnecessary requests. No extra revisions are created by the meta box submissions. A Redux action will trigger on `REQUEST_POST_UPDATE` for any active meta box. See `editor/effects.js`. The `REQUEST_META_BOX_UPDATES` action will set that meta box's state to `isUpdating`. The `isUpdating` prop will be sent into the `MetaBoxArea` and cause a form submission. + +When the meta box area is saving, we display an updating overlay, to prevent users from changing the form values while a save is in progress. + +An example save url would look like: + +`mysite.com/wp-admin/post.php?post=1&action=edit&meta-box-loader=1` + +This url is automatically passed into React via a `_wpMetaBoxUrl` global variable. + +This page mimics the `post.php` post form, so when it is submitted it will fire all of the normal hooks and actions, and have the proper global state to correctly fire any PHP meta box mumbo jumbo without needing to modify any existing code. On successful submission, React will signal a `handleMetaBoxReload` to remove the updating overlay. + +### Common Compatibility Issues + +Most PHP meta boxes should continue to work in Gutenberg, but some meta boxes that include advanced functionality could break. Here are some common reasons why meta boxes might not work as expected in Gutenberg: + +- Plugins relying on selectors that target the post title, post content fields, and other metaboxes (of the old editor). +- Plugins relying on TinyMCE's API because there's no longer a single TinyMCE instance to talk to in Gutenberg. +- Plugins making updates to their DOM on "submit" or on "save". + +Please also note that if your plugin triggers a PHP warning or notice to be output on the page, this will cause the HTML document type (``) to be output incorrectly. This will cause the browser to render using "Quirks Mode", which is a compatibility layer that gets enabled when the browser doesn't know what type of document it is parsing. The block editor is not meant to work in this mode, but it can _appear_ to be working just fine. If you encounter issues such as *meta boxes overlaying the editor* or other layout issues, please check the raw page source of your document to see that the document type definition is the first thing output on the page. There will also be a warning in the JavaScript console, noting the issue. diff --git a/docs/designers-developers/developers/backwards-compatibility/README.md b/docs/designers-developers/developers/backwards-compatibility/README.md deleted file mode 100644 index bd453cba0e56a4..00000000000000 --- a/docs/designers-developers/developers/backwards-compatibility/README.md +++ /dev/null @@ -1 +0,0 @@ -# Backwards Compatibility diff --git a/docs/designers-developers/developers/backwards-compatibility/deprecations.md b/docs/designers-developers/developers/backwards-compatibility/deprecations.md deleted file mode 100644 index 804f55c0a2c19a..00000000000000 --- a/docs/designers-developers/developers/backwards-compatibility/deprecations.md +++ /dev/null @@ -1,190 +0,0 @@ -# Deprecations - -Gutenberg's deprecation policy is intended to support backwards-compatibility for releases, when possible. 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. - -## 4.5.0 -- `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. -- `wp.editor.PostPublishPanelToggle` has been deprecated in favor of `wp.editor.PostPublishButton`. - -## 4.4.0 - -- `wp.date.getSettings` has been removed. Please use `wp.date.__experimentalGetSettings` instead. -- `wp.compose.remountOnPropChange` has been removed. -- The following editor store actions have been removed: `createNotice`, `removeNotice`, `createSuccessNotice`, `createInfoNotice`, `createErrorNotice`, `createWarningNotice`. Use the equivalent actions by the same name from the `@wordpress/notices` module. -- The id prop of wp.nux.DotTip has been removed. Please use the tipId prop instead. -- `wp.blocks.isValidBlock` has been removed. Please use `wp.blocks.isValidBlockContent` instead but keep in mind that the order of params has changed. -- `wp.data` `registry.registerReducer` has been deprecated. Use `registry.registerStore` instead. -- `wp.data` `registry.registerSelectors` has been deprecated. Use `registry.registerStore` instead. -- `wp.data` `registry.registerActions` has been deprecated. Use `registry.registerStore` instead. -- `wp.data` `registry.registerResolvers` has been deprecated. Use `registry.registerStore` instead. -- `moment` has been removed from the public API for the date module. - -## 4.3.0 - -- `isEditorSidebarPanelOpened` selector (`core/edit-post`) has been removed. Please use `isEditorPanelEnabled` instead. -- `toggleGeneralSidebarEditorPanel` action (`core/edit-post`) has been removed. Please use `toggleEditorPanelOpened` instead. -- `wp.components.PanelColor` component has been removed. Please use `wp.editor.PanelColorSettings` instead. -- `wp.editor.PanelColor` component has been removed. Please use `wp.editor.PanelColorSettings` instead. - -## 4.2.0 - -- Writing resolvers as async generators has been removed. Use the controls plugin instead. -- `wp.components.AccessibleSVG` component has been removed. Please use `wp.components.SVG` instead. -- The `wp.editor.UnsavedChangesWarning` component no longer accepts a `forceIsDirty` prop. -- `setActiveMetaBoxLocations` action (`core/edit-post`) has been removed. -- `initializeMetaBoxState` action (`core/edit-post`) has been removed. -- `wp.editPost.initializeEditor` no longer returns an object. Use the `setActiveMetaBoxLocations` action (`core/edit-post`) in place of the existing object's `initializeMetaBoxes` function. -- `setMetaBoxSavedData` action (`core/edit-post`) has been removed. -- `getMetaBoxes` selector (`core/edit-post`) has been removed. Use `getActiveMetaBoxLocations` selector (`core/edit-post`) instead. -- `getMetaBox` selector (`core/edit-post`) has been removed. Use `isMetaBoxLocationActive` selector (`core/edit-post`) instead. -- Attribute type coercion has been removed. Omit the source to preserve type via serialized comment demarcation. -- `mediaDetails` in object passed to `onFileChange` callback of `wp.editor.mediaUpload`. Please use `media_details` property instead. -- `wp.components.CodeEditor` has been removed. Used `wp.codeEditor` directly instead. -- `wp.blocks.setUnknownTypeHandlerName` has been removed. Please use `setFreeformContentHandlerName` and `setUnregisteredTypeHandlerName` instead. -- `wp.blocks.getUnknownTypeHandlerName` has been removed. Please use `getFreeformContentHandlerName` and `getUnregisteredTypeHandlerName` instead. -- The Reusable Blocks Data API was marked as experimental as it's subject to change in the future. - -## 4.1.0 - -- `wp.data.dispatch( 'core/editor' ).checkTemplateValidity` has been removed. Validity is verified automatically upon block reset. - -## 4.0.0 - -- `wp.editor.RichTextProvider` has been removed. Please use `wp.data.select( 'core/editor' )` methods instead. -- `wp.components.Draggable` as a DOM node drag handler has been removed. Please, use `wp.components.Draggable` as a wrap component for your DOM node drag handler. -- `wp.i18n.getI18n` has been removed. Use `__`, `_x`, `_n`, or `_nx` instead. -- `wp.i18n.dcnpgettext` has been removed. Use `__`, `_x`, `_n`, or `_nx` instead. - -## 3.9.0 - -- RichText `getSettings` prop has been removed. The `unstableGetSettings` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice. -- RichText `onSetup` prop has been removed. The `unstableOnSetup` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice. -- `wp.editor.getColorName` has been removed. Please use `wp.editor.getColorObjectByColorValue` instead. -- `wp.editor.getColorClass` has been renamed. Please use `wp.editor.getColorClassName` instead. -- `value` property in color objects passed by `wp.editor.withColors` has been removed. Please use color property instead. -- The Subheading block has been removed. Please use the Paragraph block instead. -- `wp.blocks.getDefaultBlockForPostFormat` has been removed. - -## 3.8.0 - - - `wp.components.withContext` has been removed. Please use `wp.element.createContext` instead. See: https://reactjs.org/docs/context.html. - - `wp.coreBlocks.registerCoreBlocks` has been removed. Please use `wp.blockLibrary.registerCoreBlocks` instead. - - `wp.editor.DocumentTitle` component has been removed. - - `getDocumentTitle` selector (`core/editor`) has been removed. - -## 3.7.0 - - - `wp.components.withAPIData` has been removed. Please use the Core Data module or `wp.apiFetch` directly instead. - - `wp.data.dispatch("core").receiveTerms` has been deprecated. Please use `wp.data.dispatch("core").receiveEntityRecords` instead. - - `getCategories` resolver has been deprecated. Please use `getEntityRecords` resolver instead. - - `wp.data.select("core").getTerms` has been deprecated. Please use `wp.data.select("core").getEntityRecords` instead. - - `wp.data.select("core").getCategories` has been deprecated. Please use `wp.data.select("core").getEntityRecords` instead. - - `wp.data.select("core").isRequestingCategories` has been deprecated. Please use `wp.data.select("core/data").isResolving` instead. - - `wp.data.select("core").isRequestingTerms` has been deprecated. Please use `wp.data.select("core").isResolving` instead. - - `wp.data.restrictPersistence`, `wp.data.setPersistenceStorage` and `wp.data.setupPersistence` has been removed. Please use the data persistence plugin instead. - -## 3.6.0 - - - `wp.editor.editorMediaUpload` has been removed. Please use `wp.editor.mediaUpload` instead. - - `wp.utils.getMimeTypesArray` has been removed. - - `wp.utils.mediaUpload` has been removed. Please use `wp.editor.mediaUpload` instead. - - `wp.utils.preloadImage` has been removed. - - `supports.wideAlign` has been removed from the Block API. Please use `supports.alignWide` instead. - - `wp.blocks.isSharedBlock` has been removed. Use `wp.blocks.isReusableBlock` instead. - - `fetchSharedBlocks` action (`core/editor`) has been removed. Use `fetchReusableBlocks` instead. - - `receiveSharedBlocks` action (`core/editor`) has been removed. Use `receiveReusableBlocks` instead. - - `saveSharedBlock` action (`core/editor`) has been removed. Use `saveReusableBlock` instead. - - `deleteSharedBlock` action (`core/editor`) has been removed. Use `deleteReusableBlock` instead. - - `updateSharedBlockTitle` action (`core/editor`) has been removed. Use `updateReusableBlockTitle` instead. - - `convertBlockToSaved` action (`core/editor`) has been removed. Use `convertBlockToReusable` instead. - - `getSharedBlock` selector (`core/editor`) has been removed. Use `getReusableBlock` instead. - - `isSavingSharedBlock` selector (`core/editor`) has been removed. Use `isSavingReusableBlock` instead. - - `isFetchingSharedBlock` selector (`core/editor`) has been removed. Use `isFetchingReusableBlock` instead. - - `getSharedBlocks` selector (`core/editor`) has been removed. Use `getReusableBlocks` instead. - -## 3.5.0 - - - `wp.components.ifCondition` has been removed. Please use `wp.compose.ifCondition` instead. - - `wp.components.withGlobalEvents` has been removed. Please use `wp.compose.withGlobalEvents` instead. - - `wp.components.withInstanceId` has been removed. Please use `wp.compose.withInstanceId` instead. - - `wp.components.withSafeTimeout` has been removed. Please use `wp.compose.withSafeTimeout` instead. - - `wp.components.withState` has been removed. Please use `wp.compose.withState` instead. - - `wp.element.pure` has been removed. Please use `wp.compose.pure` instead. - - `wp.element.compose` has been removed. Please use `wp.compose.compose` instead. - - `wp.element.createHigherOrderComponent` has been removed. Please use `wp.compose.createHigherOrderComponent` instead. - - `wp.utils.buildTermsTree` has been removed. - - `wp.utils.decodeEntities` has been removed. Please use `wp.htmlEntities.decodeEntities` instead. - - All references to a block's `uid` have been replaced with equivalent props and selectors for `clientId`. - - The `wp.editor.MediaPlaceholder` component `onSelectUrl` prop has been renamed to `onSelectURL`. - - The `wp.editor.UrlInput` component has been renamed to `wp.editor.URLInput`. - - The Text Columns block has been removed. Please use the Columns block instead. - - `InnerBlocks` grouped layout is removed. Use intermediary nested inner blocks instead. See Columns / Column block for reference implementation. - - `RichText` explicit `element` format removed. Please use the compatible `children` format instead. - -## 3.4.0 - - - `focusOnMount` prop in the `Popover` component has been changed from `Boolean`-only to an enum-style property that accepts `"firstElement"`, `"container"`, or `false`. Please convert any ` ` usage to ` `. - - `wp.utils.keycodes` utilities are removed. Please use `wp.keycodes` instead. - - Block `id` prop in `edit` function removed. Please use block `clientId` prop instead. - - `property` source removed. Please use equivalent `text`, `html`, or `attribute` source, or comment attribute instead. - -## 3.3.0 - - - `useOnce: true` has been removed from the Block API. Please use `supports.multiple: false` instead. - - Serializing components using `componentWillMount` lifecycle method. Please use the constructor instead. - - `blocks.Autocomplete.completers` filter removed. Please use `editor.Autocomplete.completers` instead. - - `blocks.BlockEdit` filter removed. Please use `editor.BlockEdit` instead. - - `blocks.BlockListBlock` filter removed. Please use `editor.BlockListBlock` instead. - - `blocks.MediaUpload` filter removed. Please use `editor.MediaUpload` instead. - -## 3.2.0 - - - `wp.data.withRehydratation` has been renamed to `wp.data.withRehydration`. - - The `wp.editor.ImagePlaceholder` component is removed. Please use `wp.editor.MediaPlaceholder` instead. - - `wp.utils.deprecated` function removed. Please use `wp.deprecated` instead. - - `wp.utils.blob` removed. Please use `wp.blob` instead. - - `getInserterItems`: the `allowedBlockTypes` argument was removed and the `parentUID` argument was added. - - `getFrecentInserterItems` selector removed. Please use `getInserterItems` instead. - - `getSupportedBlocks` selector removed. Please use `canInsertBlockType` instead. - -## 3.1.0 - - - All components in `wp.blocks.*` are removed. Please use `wp.editor.*` instead. - - `wp.blocks.withEditorSettings` is removed. Please use the data module to access the editor settings `wp.data.select( "core/editor" ).getEditorSettings()`. - - All DOM utils in `wp.utils.*` are removed. Please use `wp.dom.*` instead. - - `isPrivate: true` has been removed from the Block API. Please use `supports.inserter: false` instead. - - `wp.utils.isExtraSmall` function removed. Please use `wp.viewport` module instead. - - `getEditedPostExcerpt` selector removed (`core/editor`). Use `getEditedPostAttribute( 'excerpt' )` instead. - -## 3.0.0 - - - `wp.blocks.registerCoreBlocks` function removed. Please use `wp.coreBlocks.registerCoreBlocks` instead. - - Raw TinyMCE event handlers for `RichText` have been deprecated. Please use [documented props](https://wordpress.org/gutenberg/handbook/block-api/rich-text-api/), ancestor event handler, or onSetup access to the internal editor instance event hub instead. - -## 2.8.0 - - - `Original autocompleter interface in wp.components.Autocomplete` updated. Please use `latest autocompleter interface` instead. See: https://github.com/WordPress/gutenberg/blob/master/components/autocomplete/README.md. - - `getInserterItems`: the `allowedBlockTypes` argument is now mandatory. - - `getFrecentInserterItems`: the `allowedBlockTypes` argument is now mandatory. - -## 2.7.0 - - - `wp.element.getWrapperDisplayName` function removed. Please use `wp.element.createHigherOrderComponent` instead. - -## 2.6.0 - - - `wp.blocks.getBlockDefaultClassname` function removed. Please use `wp.blocks.getBlockDefaultClassName` instead. - - `wp.blocks.Editable` component removed. Please use the `wp.blocks.RichText` component instead. - -## 2.5.0 - - - Returning raw HTML from block `save` is unsupported. Please use the `wp.element.RawHTML` component instead. - - `wp.data.query` higher-order component removed. Please use `wp.data.withSelect` instead. - -## 2.4.0 - - - `wp.blocks.BlockDescription` component removed. Please use the `description` block property instead. - - `wp.blocks.InspectorControls.*` components removed. Please use `wp.components.*` components instead. - - `wp.blocks.source.*` matchers removed. Please use the declarative attributes instead. See: https://wordpress.org/gutenberg/handbook/block-api/attributes/. - - `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/backwards-compatibility/meta-box.md b/docs/designers-developers/developers/backwards-compatibility/meta-box.md deleted file mode 100644 index eb96bfc38e0ffb..00000000000000 --- a/docs/designers-developers/developers/backwards-compatibility/meta-box.md +++ /dev/null @@ -1,84 +0,0 @@ -# Meta Boxes - -This is a brief document detailing how meta box support works in Gutenberg. With the superior developer and user experience of blocks, especially once block templates are available, **porting PHP meta boxes to blocks is highly encouraged!** - -### Testing, Converting, and Maintaining Existing Meta Boxes - -Before converting meta boxes to blocks, it may be easier to test if a meta box works with Gutenberg, and explicitly mark it as such. - -If a meta box *doesn't* work with in Gutenberg, and updating it to work correctly is not an option, the next step is to add the `__block_editor_compatible_meta_box` argument to the meta box declaration: - -```php -add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', - null, 'normal', 'high', - array( - '__block_editor_compatible_meta_box' => false, - ) -); -``` - -WordPress will fall back to the Classic editor, where the meta box will continue working as before. - -Explicitly setting `__block_editor_compatible_meta_box` to `true` will cause WordPress to stay in Gutenberg (assuming another meta box doesn't cause a fallback). - -After a meta box is converted to a block, it can be declared as existing for backwards compatibility: - -```php -add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', - null, 'normal', 'high', - array( - '__back_compat_meta_box' => false, - ) -); -``` - -When Gutenberg is used, this meta box will no longer be displayed in the meta box area, as it now only exists for backwards compatibility purposes. It will continue to display correctly in the Classic editor, should some other meta box cause a fallback. - -### Meta Box Data Collection - -On each Gutenberg page load, we register an action that collects the meta box data to determine if an area is empty. The original global state is reset upon collection of meta box data. - -See `lib/register.php gutenberg_trick_plugins_into_registering_meta_boxes()` - -`gutenberg_collect_meta_box_data()` is hooked in later on `admin_head`. It will run through the functions and hooks that `post.php` runs to register meta boxes; namely `add_meta_boxes`, `add_meta_boxes_{$post->post_type}`, and `do_meta_boxes`. - -A copy of the global `$wp_meta_boxes` is made then filtered through `apply_filters( 'filter_gutenberg_meta_boxes', $_meta_boxes_copy );`, which will strip out any core meta boxes, standard custom taxonomy meta boxes, and any meta boxes that have declared themselves as only existing for backwards compatibility purposes. - -Then each location for this particular type of meta box is checked for whether it is active. If it is not empty a value of true is stored, if it is empty a value of false is stored. This meta box location data is then dispatched by the editor Redux store in `INITIALIZE_META_BOX_STATE`. - -Ideally, this could be done at instantiation of the editor and help simplify this flow. However, it is not possible to know the meta box state before `admin_enqueue_scripts`, where we are calling `initializeEditor()`. This will have to do, unless we want to move `initializeEditor()` to fire in the footer or at some point after `admin_head`. With recent changes to editor bootstrapping this might now be possible. Test with ACF to make sure. - -### Redux and React Meta Box Management - -When rendering the Gutenberg Page, the meta boxes are rendered to a hidden div `#metaboxes`. - -*The Redux store will hold all meta boxes as inactive by default*. When -`INITIALIZE_META_BOX_STATE` comes in, the store will update any active meta box areas by setting the `isActive` flag to `true`. Once this happens React will check for the new props sent in by Redux on the `MetaBox` component. If that `MetaBox` is now active, instead of rendering null, a `MetaBoxArea` component will be rendered. The `MetaBox` component is the container component that mediates between the `MetaBoxArea` and the Redux Store. *If no meta boxes are active, nothing happens. This will be the default behavior, as all core meta boxes have been stripped.* - -#### MetaBoxArea Component - -When the component renders it will store a reference to the meta boxes container and retrieve the meta boxes HTML from the prefetch location. - -When the post is updated, only meta box areas that are active will be submitted. This prevents unnecessary requests. No extra revisions are created by the meta box submissions. A Redux action will trigger on `REQUEST_POST_UPDATE` for any active meta box. See `editor/effects.js`. The `REQUEST_META_BOX_UPDATES` action will set that meta box's state to `isUpdating`. The `isUpdating` prop will be sent into the `MetaBoxArea` and cause a form submission. - -When the meta box area is saving, we display an updating overlay, to prevent users from changing the form values while a save is in progress. - -After the new block editor is made into the default editor, it will be necessary to provide the classic-editor flag to access the meta box partial page. - -`gutenberg_meta_box_save()` saves meta box changes. A `meta_box` request parameter should be present and should match one of `'advanced'`, `'normal'`, or `'side'`. This value will determine which meta box area is served. - -So an example url would look like: - -`mysite.com/wp-admin/post.php?post=1&action=edit&meta_box=$location&classic-editor` - -This url is automatically passed into React via a `_wpMetaBoxUrl` global variable. - -This page mimics the `post.php` post form, so when it is submitted it will fire all of the normal hooks and actions, and have the proper global state to correctly fire any PHP meta box mumbo jumbo without needing to modify any existing code. On successful submission, React will signal a `handleMetaBoxReload` to remove the updating overlay. - -### Common Compatibility Issues - -Most PHP meta boxes should continue to work in Gutenberg, but some meta boxes that include advanced functionality could break. Here are some common reasons why meta boxes might not work as expected in Gutenberg: - -- Plugins relying on selectors that target the post title, post content fields, and other metaboxes (of the old editor). -- Plugins relying on TinyMCE's API because there's no longer a single TinyMCE instance to talk to in Gutenberg. -- Plugins making updates to their DOM on "submit" or on "save". diff --git a/docs/designers-developers/developers/block-api/README.md b/docs/designers-developers/developers/block-api/README.md index 2c18a261d8fc5c..56d159bda6d135 100644 --- a/docs/designers-developers/developers/block-api/README.md +++ b/docs/designers-developers/developers/block-api/README.md @@ -1,11 +1,11 @@ # Block API Reference -Blocks are the fundamental element of the Gutenberg editor. They are the primary way in which plugins and themes can register their own functionality and extend the capabilities of the editor. +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. ## Registering a block -All blocks must be registered before they can be used in the editor. You can learn about block registration, and the available options, in the [block registration](../../../../docs/designers-developers/developers/block-api/block-registration.md) documentation. +All blocks must be registered before they can be used in the editor. You can learn about block registration, and the available options, in the [block registration](/docs/designers-developers/developers/block-api/block-registration.md) documentation. ## Block `edit` and `save` -The `edit` and `save` functions define the editor interface with which a user would interact, and the markup to be serialized back when a post is saved. They are the heart of how a block operates, so they are [covered separately](../../../../docs/designers-developers/developers/block-api/block-edit-save.md). +The `edit` and `save` functions define the editor interface with which a user would interact, and the markup to be serialized back when a post is saved. They are the heart of how a block operates, so they are [covered separately](/docs/designers-developers/developers/block-api/block-edit-save.md). diff --git a/docs/designers-developers/developers/block-api/block-attributes.md b/docs/designers-developers/developers/block-api/block-attributes.md index 9bab342a935001..fd61866f5286c4 100644 --- a/docs/designers-developers/developers/block-api/block-attributes.md +++ b/docs/designers-developers/developers/block-api/block-attributes.md @@ -4,7 +4,7 @@ Attribute sources are used to define the strategy by which 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](../language.md). +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). Each source accepts an optional selector as the first argument. If a selector is specified, the source behavior will be run against the corresponding element(s) contained within the block. Otherwise it will be run against the block's root node. diff --git a/docs/designers-developers/developers/block-api/block-deprecation.md b/docs/designers-developers/developers/block-api/block-deprecation.md index 3fd500c1645417..7e64bfb89a9991 100644 --- a/docs/designers-developers/developers/block-api/block-deprecation.md +++ b/docs/designers-developers/developers/block-api/block-deprecation.md @@ -9,9 +9,9 @@ A block can have several deprecated versions. A deprecation will be tried if a p 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. -- `support` (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/designers-developers/developers/block-api/block-attributes.md) of the deprecated form of the block. +- `support` (Object): The [supports definition](/docs/designers-developers/developers/block-api/block-registration.md) of the deprecated form of the block. +- `save` (Function): The [save implementation](/docs/designers-developers/developers/block-api/block-edit-save.md) of the deprecated form of the block. - `migrate` (Function, Optional): A function which, given the attributes and inner blocks of the parsed block, is expected to return either the attributes compatible with the deprecated block, or a tuple array of `[ attributes, innerBlocks ]`. - `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. This is particularly useful in cases where a block is technically valid even once deprecated, and requires updates to its attributes or inner blocks. @@ -275,4 +275,4 @@ registerBlockType( 'gutenberg/block-with-deprecated-version', { In the example above we updated the block to use an inner paragraph block with a title instead of a title attribute. -*Above are example cases of block deprecation. For more, real-world examples, check for deprecations in the [core block library](https://github.com/WordPress/gutenberg/tree/master/packages/block-library/src). Core blocks have been updated across releases and contain simple and complex deprecations.* +*Above are example cases of block deprecation. For more, real-world examples, check for deprecations in the [core block library](/packages/block-library/src/README.md). Core blocks have been updated across releases and contain simple and complex deprecations.* diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index c6f4d603cc3085..c5e35eddb10a72 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -6,25 +6,52 @@ When registering a block, the `edit` and `save` functions provide the interface The `edit` function describes the structure of your block in the context of the editor. This represents what the editor will render when the block is used. +{% codetabs %} +{% ES5 %} ```js -// Defining the edit interface -edit() { - return
; +// A static div +edit: function() { + return wp.element.createElement( + 'div', + null, + 'Your block.' + ); +} +``` +{% ESNext %} +```jsx +edit: () => { + returnYour block.; } ``` +{% end %} The function receives the following properties through an object argument: ### attributes -This property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. 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: +This property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. See [attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for how to specify attribute sources. +In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function: + +{% codetabs %} +{% ES5 %} ```js -// Defining the edit interface -edit( { attributes } ) { +edit: function( props ) { + return wp.element.createElement( + 'div', + null, + props.attributes.content + ); +} +``` +{% ESNext %} +```js +edit: ( { attributes } ) => { return{ attributes.content }; } ``` +{% end %} The value of `attributes.content` will be displayed inside the `div` when inserting the block in the editor. @@ -32,23 +59,53 @@ The value of `attributes.content` will be displayed inside the `div` when insert This property returns the class name for the wrapper element. This is automatically added in the `save` method, but not on `edit`, as the root element may not correspond to what is _visually_ the main element of the block. You can request it to add it to the correct element in your function. +{% codetabs %} +{% ES5 %} +```js +edit: function( props ) { + return wp.element.createElement( + 'div', + { className: props.className }, + props.attributes.content + ); +} +``` +{% ESNext %} ```js -// Defining the edit interface -edit( { attributes, className } ) { +edit: ( { attributes, className } ) => { return{ attributes.content }; } ``` +{% end %} ### isSelected The isSelected property is an object that communicates whether the block is currently selected. +{% codetabs %} +{% ES5 %} ```js -// Defining the edit interface -edit( { attributes, className, isSelected } ) { +edit: function( props ) { + return wp.element.createElement( + 'div', + { className: props.className }, + [ + 'Your block.', + props.isSelected ? wp.element.createElement( + 'span', + null, + 'Shows only when the block is selected.' + ) + ] + ); +} +``` +{% ESNext %} +```jsx +edit: ( { attributes, className, isSelected } ) => { return (- { attributes.content } + Your block. { isSelected && Shows only when the block is selected. } @@ -56,14 +113,39 @@ edit( { attributes, className, isSelected } ) { ); } ``` +{% end %} ### setAttributes This function allows the block to update individual attributes based on user interactions. +{% codetabs %} +{% ES5 %} ```js -// Defining the edit interface -edit( { attributes, setAttributes, className, isSelected } ) { +edit: function( props ) { + // Simplify access to attributes + let content = props.attributes.content; + let mySetting = props.attributes.mySetting; + + // Toggle a setting when the user clicks the button + let toggleSetting = () => props.setAttributes( { mySetting: ! mySetting } ); + return wp.element.createElement( + 'div', + { className: props.className }, + [ + content, + props.isSelected ? wp.element.createElement( + 'button', + { onClick: toggleSetting }, + 'Toggle setting' + ) : null + ] + ); +}, +``` +{% ESNext %} +```jsx +edit: ( { attributes, setAttributes, className, isSelected } ) => { // Simplify access to attributes const { content, mySetting } = attributes; @@ -79,6 +161,43 @@ edit( { attributes, setAttributes, className, isSelected } ) { ); } ``` +{% end %} + +When using attributes that are objects or arrays it's a good idea to copy or clone the attribute prior to updating it: + +{% codetabs %} +{% ES5 %} +```js +// Good - cloning the old list +var newList = attributes.list.slice(); + +var addListItem = function( newListItem ) { + setAttributes( { list: newList.concat( [ newListItem ] ) } ); +}; + +// Bad - the list from the existing attribute is modified directly to add the new list item: +var list = attributes.list; +var addListItem = function( newListItem ) { + list.push( newListItem ); + setAttributes( { list: list } ); +}; +``` +{% ESNext %} +```js +// Good - a new array is created from the old list attribute and a new list item: +const { list } = attributes; +const addListItem = ( newListItem ) => setAttributes( { list: [ ...list, newListItem ] } ); + +// Bad - the list from the existing attribute is modified directly to add the new list item: +const { list } = attributes; +const addListItem = ( newListItem ) => { + list.push( newListItem ); + setAttributes( { list } ); +}; +``` +{% end %} + +Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, Gutenberg follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes. ## Save @@ -87,23 +206,27 @@ The `save` function defines the way in which the different attributes should be {% codetabs %} {% ES5 %} ```js -save() { - return wp.element.createElement( 'hr' ); +save: function() { + return wp.element.createElement( + 'div', + null, + 'Your block.' + ); } ``` {% ESNext %} ```jsx -save() { - return
; +save: () => { + returnYour block.; } ``` {% end %} -For most blocks, the return value of `save` should be an [instance of WordPress Element](https://github.com/WordPress/gutenberg/blob/master/packages/element/README.md) representing how the block is to appear on the front of the site. +For most blocks, the return value of `save` should be an [instance of WordPress Element](/packages/element/README.md) representing how the block is to appear on the front of the site. _Note:_ While it is possible to return a string value from `save`, it _will be escaped_. If the string includes HTML markup, the markup will be shown on the front of the site verbatim, not as the equivalent HTML node content. If you must return raw HTML from `save`, use `wp.element.RawHTML`. As the name implies, this is prone to [cross-site scripting](https://en.wikipedia.org/wiki/Cross-site_scripting) and therefore is discouraged in favor of a WordPress Element hierarchy whenever possible. -For [dynamic blocks](../../../../docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md), the return value of `save` could either represent a cached copy of the block's content to be shown only in case the plugin implementing the block is ever disabled. Alternatively, return a `null` (empty) value to 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. +For [dynamic blocks](/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md), the return value of `save` could either represent a cached copy of the block's content to be shown only in case the plugin implementing the block is ever disabled. Alternatively, return a `null` (empty) value to 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. ### attributes @@ -112,7 +235,7 @@ As with `edit`, the `save` function also receives an object argument including a {% codetabs %} {% ES5 %} ```js -save( props ) { +save: function( props ) { return wp.element.createElement( 'div', null, @@ -122,12 +245,136 @@ save( props ) { ``` {% ESNext %} ```jsx -save( { attributes } ) { +save: ( { attributes } ) => { return{ attributes.content }; } ``` {% 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. + +## 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. + +### Saving Attributes to Child Elements + +{% codetabs %} +{% ES5 %} +```js +attributes: { + content: { + type: 'string', + source: 'html', + selector: 'p' + } +}, + +edit: function( props ) { + var updateFieldValue = function( val ) { + props.setAttributes( { content: val } ); + } + return wp.element.createElement( + wp.components.TextControl, + { + label: 'My Text Field', + value: props.attributes.content, + onChange: updateFieldValue, + + } + ); +}, + +save: function( props ) { + return el( 'p', {}, props.attributes.content ); +}, +``` +{% ESNext %} +```jsx +attributes: { + content: { + type: 'string', + source: 'html', + selector: 'p' + } +}, + +edit: ( { attributes, setAttributes } ) => { + const updateFieldValue = ( val ) => { + setAttributes( { content: val } ); + } + return; +}, + +save: ( { attributes } ) => { + return { attributes.content }
; +}, +``` +{% end %} + +### Saving Attributes via Serialization + +Ideally, the attributes saved should be included in the markup. However, there are times when this is not practical, so if no attribute source is specified the attribute is serialized and saved to the block's comment delimiter. + +This example could be for a dynamic block, such as the [Latest Posts block](https://github.com/WordPress/gutenberg/blob/master/packages/block-library/src/latest-posts/index.js), which renders the markup server-side. The save function is still required, however in this case it simply returns null since the block is not saving content from the editor. + +{% codetabs %} +{% ES5 %} +```js +attributes: { + postsToShow: { + type: 'number', + } +}, + +edit: function( props ) { + return wp.element.createElement( + wp.components.TextControl, + { + label: 'Number Posts to Show', + value: props.attributes.postsToShow, + onChange: function( val ) { + props.setAttributes( { postsToShow: parseInt( val ) } ); + }, + } + ); +}, + +save: function() { + return null; +} +``` +{% ESNext %} +```jsx +attributes: { + postsToShow: { + type: 'number', + } +}, + +edit: ( { attributes, setAttributes } ) => { + return{ + setAttributes( { postsToShow: parseInt( val ) } ); + }}, + } + ); +}, + +save: () => { + return null; +} +``` +{% end %} + + ## Validation When the editor loads, all blocks within post content are validated to determine their accuracy in order to protect against content loss. This is closely related to the saving implementation of a block, as a user may unintentionally remove or modify their content if the editor is unable to restore a block correctly. During editor initialization, the saved markup for each block is regenerated using the attributes that were parsed from the post's content. If the newly-generated markup does not match what was already stored in post content, the block is marked as invalid. This is because we assume that unless the user makes edits, the markup should remain identical to the saved content. @@ -153,10 +400,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/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'`). 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-deprecations.md) to learn more about how to accommodate legacy content in intentional markup changes. +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. diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index af8aaa36a75ed2..f4a214b741fbe2 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -66,7 +66,7 @@ The core provided categories are: category: 'widgets', ``` -Plugins and Themes can also register [custom block categories](../docs/extensibility/extending-blocks/#managing-block-categories). +Plugins and Themes can also register [custom block categories](/docs/designers-developers/developers/filters/block-filters.md#managing-block-categories). #### Icon (optional) @@ -82,7 +82,7 @@ icon: 'book-alt', icon: , ``` -**Note:** Custom SVG icons are automatically wrapped in the [`wp.components.SVG` component](https://github.com/WordPress/gutenberg/tree/master/packages/components/src/primitives/svg/) to add accessibility attributes (`aria-hidden`, `role`, and `focusable`). +**Note:** Custom SVG icons are automatically wrapped in the [`wp.components.SVG` component](/packages/components/src/primitives/svg/) to add accessibility attributes (`aria-hidden`, `role`, and `focusable`). An object can also be passed as icon, in this case, icon, as specified above, should be included in the src property. Besides src the object can contain background and foreground colors, this colors will appear with the icon @@ -104,7 +104,7 @@ icon: { * **Type:** `Array` -Sometimes a block could have aliases that help users discover it while searching. For example, an `image` block could also want to be discovered by `photo`. You can do so by providing an array of terms (which can be translated). It is only allowed to add as much as three terms per block. +Sometimes a block could have aliases that help users discover it while searching. For example, an `image` block could also want to be discovered by `photo`. You can do so by providing an array of terms (which can be translated). ```js // Make it easier to discover a block with keyword aliases. @@ -138,7 +138,7 @@ styles: [ ], ``` -Plugins and Themes can also register [custom block style](../docs/extensibility/extending-blocks/#block-style-variations) for existing blocks. +Plugins and Themes can also register [custom block style](/docs/designers-developers/developers/filters/block-filters.md#block-style-variations) for existing blocks. #### Attributes (optional) @@ -166,7 +166,7 @@ attributes: { }, ``` -* **See: [Attributes](../docs/block-api/attributes.md).** +* **See: [Attributes](/docs/designers-developers/developers/block-api/block-attributes.md).** #### Transforms (optional) @@ -184,9 +184,9 @@ transforms: { { type: 'block', blocks: [ 'core/paragraph' ], - transform: function ( content ) { + transform: function ( attributes ) { return createBlock( 'core/heading', { - content, + content: attributes.content, } ); }, }, @@ -284,9 +284,9 @@ transforms: { { type: 'block', blocks: [ 'core/paragraph' ], - transform: function( content ) { + transform: function( attributes ) { return createBlock( 'core/paragraph', { - content, + content: attributes.content, } ); }, }, @@ -311,6 +311,39 @@ transforms: { ``` {% end %} +A block with innerBlocks can also be transformed from and to another block with innerBlocks. + +{% codetabs %} +{% ES5 %} +```js +transforms: { + to: [ + { + type: 'block', + blocks: [ 'some/block-with-innerblocks' ], + transform: function( attributes, innerBlocks ) { + return createBlock( 'some/other-block-with-innerblocks', attributes, innerBlocks ); + }, + }, + ], +}, +``` +{% ESNext %} +```js +transforms: { + to: [ + { + type: 'block', + blocks: [ 'some/block-with-innerblocks' ], + transform: ( attributes, innerBlocks ) => { + return createBlock( 'some/other-block-with-innerblocks', attributes, innerBlocks); + }, + }, + ], +}, +``` +{% end %} + An optional `isMatch` function can be specified on a transform object. This provides an opportunity to perform additional checks on whether a transform should be possible. Returning `false` from this function will prevent the transform from being displayed as an option to the user. {% codetabs %} @@ -321,12 +354,12 @@ transforms: { { type: 'block', blocks: [ 'core/paragraph' ], - isMatch: function( attribute ) { + isMatch: function( attributes ) { return attributes.isText; }, - transform: function( content ) { + transform: function( attributes ) { return createBlock( 'core/paragraph', { - content, + content: attributes.content, } ); }, }, @@ -453,7 +486,7 @@ transforms: { * **Type:** `Array` -Blocks are able to be inserted into blocks that use [`InnerBlocks`](https://github.com/WordPress/gutenberg/blob/master/packages/editor/src/components/inner-blocks/README.md) as nested content. Sometimes it is useful to restrict a block so that it is only available as a nested block. For example, you might want to allow an 'Add to Cart' block to only be available within a 'Product' block. +Blocks are able to be inserted into blocks that use [`InnerBlocks`](/packages/editor/src/components/inner-blocks/README.md) as nested content. Sometimes it is useful to restrict a block so that it is only available as a nested block. For example, you might want to allow an 'Add to Cart' block to only be available within a 'Product' block. Setting `parent` lets a block require that it is only available when nested within the specified blocks. @@ -492,7 +525,7 @@ attributes: { } ``` -- `alignWide` (default `true`): This property allows to enable [wide alignment](../docs/extensibility/theme-support.md#wide-alignment) for your theme. To disable this behavior for a single block, set this flag to `false`. +- `alignWide` (default `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`. ```js // Remove the support for wide alignment. diff --git a/docs/designers-developers/developers/block-api/block-templates.md b/docs/designers-developers/developers/block-api/block-templates.md index a26d822d0100e5..caa96ff20b5bd4 100644 --- a/docs/designers-developers/developers/block-api/block-templates.md +++ b/docs/designers-developers/developers/block-api/block-templates.md @@ -17,18 +17,50 @@ Planned additions: Templates can be declared in JS or in PHP as an array of blockTypes (block name and optional attributes). +The first example in PHP creates a template for posts that includes an image block to start, you can add as many or as few blocks to your template as needed. + +PHP example: + +```php +template = array( + array( 'core/image' ), + ); +} +add_action( 'init', 'myplugin_register_template' ); +``` + +The following example in JavaScript creates a new block using [InnerBlocks](/packages/editor/src/components/inner-blocks) and templates, when inserted creates a set of blocks based off the template. + ```js -const template = [ - [ 'block/name', {} ], // [ blockName, attributes ] +const el = wp.element.createElement; +const { registerBlockType } = wp.blocks; +const { InnerBlocks } = wp.editor; + +const BLOCKS_TEMPLATE = [ + [ 'core/image', {} ], + [ 'core/paragraph', { placeholder: 'Image Details' } ], ]; -``` -```php -'template' => array( - array( 'block/name' ), -), +registerBlockType( 'myplugin/template', { + title: 'My Template Block', + category: 'widgets', + edit: ( props ) => { + return el( InnerBlocks, { + template: BLOCKS_TEMPLATE, + templateLock: false + }); + }, + save: ( props ) => { + return el( InnerBlocks.Content, {} ); + }, +}); ``` +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. + ## Custom Post types A custom post type can register its own template during registration: @@ -61,20 +93,7 @@ add_action( 'init', 'myplugin_register_book_post_type' ); Sometimes the intention might be to lock the template on the UI so that the blocks presented cannot be manipulated. This is achieved with a `template_lock` property. ```php -'template_lock' => 'all', // or 'insert' to allow moving -``` - -*Options:* - -- `all` — prevents all operations. It is not possible to insert new blocks, move existing blocks, or delete blocks. -- `insert` — prevents inserting or removing blocks, but allows moving existing blocks. - -## Existing Post Types - -It is also possible to assign a template to an existing post type like "posts" and "pages": - -```php -function my_add_template_to_posts() { +function myplugin_register_template() { $post_type_object = get_post_type_object( 'post' ); $post_type_object->template = array( array( 'core/paragraph', array( @@ -83,12 +102,17 @@ function my_add_template_to_posts() { ); $post_type_object->template_lock = 'all'; } -add_action( 'init', 'my_add_template_to_posts' ); +add_action( 'init', 'myplugin_register_template' ); ``` +*Options:* + +- `all` — prevents all operations. It is not possible to insert new blocks, move existing blocks, or delete blocks. +- `insert` — prevents inserting or removing blocks, but allows moving existing blocks. + ## Nested Templates -Container blocks like the columns blocks also support templates. This is achieved by assigned a nested template to the block. +Container blocks like the columns blocks also support templates. This is achieved by assigning a nested template to the block. ```php $template = array( diff --git a/docs/designers-developers/developers/data/README.md b/docs/designers-developers/developers/data/README.md index 5233753c1445e5..7408d171144cf4 100644 --- a/docs/designers-developers/developers/data/README.md +++ b/docs/designers-developers/developers/data/README.md @@ -1,10 +1,11 @@ # 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/editor**: The 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 + - [**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/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md new file mode 100644 index 00000000000000..03929e3671685d --- /dev/null +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -0,0 +1,1050 @@ +# **core/block-editor**: The Block Editor’s Data + +## Selectors + +### getBlockDependantsCacheBust + +Returns a new reference when the inner blocks of a given block client ID +change. This is used exclusively as a memoized selector dependant, relying +on this selector's shared return value and recursively those of its inner +blocks defined as dependencies. This abuses mechanics of the selector +memoization to return from the original selector function only when +dependants change. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +### getBlockName + +Returns a block's name given its client ID, or null if no block exists with +the client ID. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Block name. + +### isBlockValid + +Returns whether a block is valid or not. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Is Valid. + +### getBlockAttributes + +Returns a block's attributes given its client ID, or null if no block exists with +the client ID. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Block attributes. + +### getBlock + +Returns a block given its client ID. This is a parsed copy of the block, +containing its `blockName`, `clientId`, and current `attributes` state. This +is not the block's registration settings, which must be retrieved from the +blocks module registration store. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Parsed block object. + +### getBlocks + +Returns all block objects for the current post being edited as an array in +the order they appear in the post. + +Note: It's important to memoize this selector to avoid return a new instance +on each call + +*Parameters* + + * state: Editor state. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Post blocks. + +### getClientIdsOfDescendants + +Returns an array containing the clientIds of all descendants +of the blocks given. + +*Parameters* + + * state: Global application state. + * clientIds: Array of blocks to inspect. + +*Returns* + +ids of descendants. + +### getClientIdsWithDescendants + +Returns an array containing the clientIds of the top-level blocks +and their descendants of any depth (for nested blocks). + +*Parameters* + + * state: Global application state. + +*Returns* + +ids of top-level and descendant blocks. + +### getGlobalBlockCount + +Returns the total number of blocks, or the total number of blocks with a specific name in a post. +The number returned includes nested blocks. + +*Parameters* + + * state: Global application state. + * blockName: Optional block name, if specified only blocks of that type will be counted. + +*Returns* + +Number of blocks in the post, or number of blocks with name equal to blockName. + +### getBlocksByClientId + +Given an array of block client IDs, returns the corresponding array of block +objects. + +*Parameters* + + * state: Editor state. + * clientIds: Client IDs for which blocks are to be returned. + +*Returns* + +Block objects. + +### getBlockCount + +Returns the number of blocks currently present in the post. + +*Parameters* + + * state: Editor state. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Number of blocks in the post. + +### getBlockSelectionStart + +Returns the current block selection start. This value may be null, and it +may represent either a singular block selection or multi-selection start. +A selection is singular if its start and end match. + +*Parameters* + + * state: Global application state. + +*Returns* + +Client ID of block selection start. + +### getBlockSelectionEnd + +Returns the current block selection end. This value may be null, and it +may represent either a singular block selection or multi-selection end. +A selection is singular if its start and end match. + +*Parameters* + + * state: Global application state. + +*Returns* + +Client ID of block selection end. + +### getSelectedBlockCount + +Returns the number of blocks currently selected in the post. + +*Parameters* + + * state: Global application state. + +*Returns* + +Number of blocks selected in the post. + +### hasSelectedBlock + +Returns true if there is a single selected block, or false otherwise. + +*Parameters* + + * state: Editor state. + +*Returns* + +Whether a single block is selected. + +### getSelectedBlockClientId + +Returns the currently selected block client ID, or null if there is no +selected block. + +*Parameters* + + * state: Editor state. + +*Returns* + +Selected block client ID. + +### getSelectedBlock + +Returns the currently selected block, or null if there is no selected block. + +*Parameters* + + * state: Global application state. + +*Returns* + +Selected block. + +### getBlockRootClientId + +Given a block client ID, returns the root block from which the block is +nested, an empty string for top-level blocks, or null if the block does not +exist. + +*Parameters* + + * state: Editor state. + * clientId: Block from which to find root client ID. + +*Returns* + +Root client ID, if exists + +### getBlockHierarchyRootClientId + +Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. + +*Parameters* + + * state: Editor state. + * clientId: Block from which to find root client ID. + +*Returns* + +Root client ID + +### getAdjacentBlockClientId + +Returns the client ID of the block adjacent one at the given reference +startClientId and modifier directionality. Defaults start startClientId to +the selected block, and direction as next block. Returns null if there is no +adjacent block. + +*Parameters* + + * state: Editor state. + * startClientId: Optional client ID of block from which to + search. + * modifier: Directionality multiplier (1 next, -1 + previous). + +*Returns* + +Return the client ID of the block, or null if none exists. + +### getPreviousBlockClientId + +Returns the previous block's client ID from the given reference start ID. +Defaults start to the selected block. Returns null if there is no previous +block. + +*Parameters* + + * state: Editor state. + * startClientId: Optional client ID of block from which to + search. + +*Returns* + +Adjacent block's client ID, or null if none exists. + +### getNextBlockClientId + +Returns the next block's client ID from the given reference start ID. +Defaults start to the selected block. Returns null if there is no next +block. + +*Parameters* + + * state: Editor state. + * startClientId: Optional client ID of block from which to + search. + +*Returns* + +Adjacent block's client ID, or null if none exists. + +### getSelectedBlocksInitialCaretPosition + +Returns the initial caret position for the selected block. +This position is to used to position the caret properly when the selected block changes. + +*Parameters* + + * state: Global application state. + +*Returns* + +Selected block. + +### getMultiSelectedBlockClientIds + +Returns the current multi-selection set of block client IDs, or an empty +array if there is no multi-selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +Multi-selected block client IDs. + +### getMultiSelectedBlocks + +Returns the current multi-selection set of blocks, or an empty array if +there is no multi-selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +Multi-selected block objects. + +### getFirstMultiSelectedBlockClientId + +Returns the client ID of the first block in the multi-selection set, or null +if there is no multi-selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +First block client ID in the multi-selection set. + +### getLastMultiSelectedBlockClientId + +Returns the client ID of the last block in the multi-selection set, or null +if there is no multi-selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +Last block client ID in the multi-selection set. + +### isFirstMultiSelectedBlock + +Returns true if a multi-selection exists, and the block corresponding to the +specified client ID is the first block of the multi-selection set, or false +otherwise. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Whether block is first in multi-selection. + +### isBlockMultiSelected + +Returns true if the client ID occurs within the block multi-selection, or +false otherwise. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Whether block is in multi-selection set. + +### isAncestorMultiSelected + +Returns true if an ancestor of the block is multi-selected, or false +otherwise. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Whether an ancestor of the block is in multi-selection + set. + +### getMultiSelectedBlocksStartClientId + +Returns the client ID of the block which begins the multi-selection set, or +null if there is no multi-selection. + +This is not necessarily the first client ID in the selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +Client ID of block beginning multi-selection. + +### getMultiSelectedBlocksEndClientId + +Returns the client ID of the block which ends the multi-selection set, or +null if there is no multi-selection. + +This is not necessarily the last client ID in the selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +Client ID of block ending multi-selection. + +### getBlockOrder + +Returns an array containing all block client IDs in the editor in the order +they appear. Optionally accepts a root client ID of the block list for which +the order should be returned, defaulting to the top-level block order. + +*Parameters* + + * state: Editor state. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Ordered client IDs of editor blocks. + +### getBlockIndex + +Returns the index at which the block corresponding to the specified client +ID occurs within the block order, or `-1` if the block does not exist. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Index at which block exists in order. + +### isBlockSelected + +Returns true if the block corresponding to the specified client ID is +currently selected and no multi-selection exists, or false otherwise. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Whether block is selected and multi-selection exists. + +### hasSelectedInnerBlock + +Returns true if one of the block's inner blocks is selected. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + * deep: Perform a deep check. + +*Returns* + +Whether the block as an inner block selected + +### isBlockWithinSelection + +Returns true if the block corresponding to the specified client ID is +currently selected but isn't the last of the selected blocks. Here "last" +refers to the block sequence in the document, _not_ the sequence of +multi-selection, which is why `state.blockSelection.end` isn't used. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Whether block is selected and not the last in the + selection. + +### hasMultiSelection + +Returns true if a multi-selection has been made, or false otherwise. + +*Parameters* + + * state: Editor state. + +*Returns* + +Whether multi-selection has been made. + +### isMultiSelecting + +Whether in the process of multi-selecting or not. This flag is only true +while the multi-selection is being selected (by mouse move), and is false +once the multi-selection has been settled. + +*Parameters* + + * state: Global application state. + +*Returns* + +True if multi-selecting, false if not. + +### isSelectionEnabled + +Selector that returns if multi-selection is enabled or not. + +*Parameters* + + * state: Global application state. + +*Returns* + +True if it should be possible to multi-select blocks, false if multi-selection is disabled. + +### getBlockMode + +Returns the block's editing mode, defaulting to "visual" if not explicitly +assigned. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Block editing mode. + +### isTyping + +Returns true if the user is typing, or false otherwise. + +*Parameters* + + * state: Global application state. + +*Returns* + +Whether user is typing. + +### isCaretWithinFormattedText + +Returns true if the caret is within formatted text, or false otherwise. + +*Parameters* + + * state: Global application state. + +*Returns* + +Whether the caret is within formatted text. + +### getBlockInsertionPoint + +Returns the insertion point, the index at which the new inserted block would +be placed. Defaults to the last index. + +*Parameters* + + * state: Editor state. + +*Returns* + +Insertion point object with `rootClientId`, `index`. + +### isBlockInsertionPointVisible + +Returns true if we should show the block insertion point. + +*Parameters* + + * state: Global application state. + +*Returns* + +Whether the insertion point is visible or not. + +### isValidTemplate + +Returns whether the blocks matches the template or not. + +*Parameters* + + * state: null + +*Returns* + +Whether the template is valid or not. + +### getTemplate + +Returns the defined block template + +*Parameters* + + * state: null + +*Returns* + +Block Template + +### getTemplateLock + +Returns the defined block template lock. Optionally accepts a root block +client ID as context, otherwise defaulting to the global context. + +*Parameters* + + * state: Editor state. + * rootClientId: Optional block root client ID. + +*Returns* + +Block Template Lock + +### canInsertBlockType + +Determines if the given block type is allowed to be inserted into the block list. + +*Parameters* + + * state: Editor state. + * blockName: The name of the block type, e.g.' core/paragraph'. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Whether the given block type is allowed to be inserted. + +### getInserterItems + +Determines the items that appear in the inserter. Includes both static +items (e.g. a regular block type) and dynamic items (e.g. a reusable block). + +Each item object contains what's necessary to display a button in the +inserter and handle its selection. + +The 'utility' property indicates how useful we think an item will be to the +user. There are 4 levels of utility: + +1. Blocks that are contextually useful (utility = 3) +2. Blocks that have been previously inserted (utility = 2) +3. Blocks that are in the common category (utility = 1) +4. All other blocks (utility = 0) + +The 'frecency' property is a heuristic (https://en.wikipedia.org/wiki/Frecency) +that combines block usage frequenty and recency. + +Items are returned ordered descendingly by their 'utility' and 'frecency'. + +*Parameters* + + * state: Editor state. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Items that appear in inserter. + +### hasInserterItems + +Determines whether there are items to show in the inserter. + +*Parameters* + + * state: Editor state. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Items that appear in inserter. + +### getBlockListSettings + +Returns the Block List settings of a block, if any exist. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Block settings of the block if set. + +### getSettings + +Returns the editor settings. + +*Parameters* + + * state: Editor state. + +*Returns* + +The editor settings object. + +### isLastBlockChangePersistent + +Returns true if the most recent block change is be considered persistent, or +false otherwise. A persistent change is one committed by BlockEditorProvider +via its `onChange` callback, in addition to `onInput`. + +*Parameters* + + * state: Block editor state. + +*Returns* + +Whether the most recent block change was persistent. + +## Actions + +### resetBlocks + +Returns an action object used in signalling that blocks state should be +reset to the specified array of blocks, taking precedence over any other +content reflected as an edit in state. + +*Parameters* + + * blocks: Array of blocks. + +### receiveBlocks + +Returns an action object used in signalling that blocks have been received. +Unlike resetBlocks, these should be appended to the existing known set, not +replacing. + +*Parameters* + + * blocks: Array of block objects. + +### updateBlockAttributes + +Returns an action object used in signalling that the block attributes with +the specified client ID has been updated. + +*Parameters* + + * clientId: Block client ID. + * attributes: Block attributes to be merged. + +### updateBlock + +Returns an action object used in signalling that the block with the +specified client ID has been updated. + +*Parameters* + + * clientId: Block client ID. + * updates: Block attributes to be merged. + +### selectBlock + +Returns an action object used in signalling that the block with the +specified client ID has been selected, optionally accepting a position +value reflecting its selection directionality. An initialPosition of -1 +reflects a reverse selection. + +*Parameters* + + * clientId: Block client ID. + * initialPosition: Optional initial position. Pass as -1 to + reflect reverse selection. + +### selectPreviousBlock + +Yields action objects used in signalling that the block preceding the given +clientId should be selected. + +*Parameters* + + * clientId: Block client ID. + +### selectNextBlock + +Yields action objects used in signalling that the block following the given +clientId should be selected. + +*Parameters* + + * clientId: Block client ID. + +### startMultiSelect + +Returns an action object used in signalling that a block multi-selection has started. + +### stopMultiSelect + +Returns an action object used in signalling that block multi-selection stopped. + +### multiSelect + +Returns an action object used in signalling that block multi-selection changed. + +*Parameters* + + * start: First block of the multi selection. + * end: Last block of the multiselection. + +### clearSelectedBlock + +Returns an action object used in signalling that the block selection is cleared. + +### toggleSelection + +Returns an action object that enables or disables block selection. + +*Parameters* + + * boolean: [isSelectionEnabled=true] Whether block selection should + be enabled. + +### replaceBlocks + +Returns an action object signalling that a blocks should be replaced with +one or more replacement blocks. + +*Parameters* + + * clientIds: Block client ID(s) to replace. + * blocks: Replacement block(s). + +### replaceBlock + +Returns an action object signalling that a single block should be replaced +with one or more replacement blocks. + +*Parameters* + + * clientId: Block client ID to replace. + * block: Replacement block(s). + +### moveBlockToPosition + +Returns an action object signalling that an indexed block should be moved +to a new index. + +*Parameters* + + * clientId: The client ID of the block. + * fromRootClientId: Root client ID source. + * toRootClientId: Root client ID destination. + * index: The index to move the block into. + +### insertBlock + +Returns an action object used in signalling that a single block should be +inserted, optionally at a specific index respective a root block list. + +*Parameters* + + * block: Block object to insert. + * index: Index at which block should be inserted. + * rootClientId: Optional root client ID of block list on which to insert. + * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. + +### insertBlocks + +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. + +*Parameters* + + * blocks: Block objects to insert. + * index: Index at which block should be inserted. + * rootClientId: Optional root client ID of block list on which to insert. + * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. + +### showInsertionPoint + +Returns an action object used in signalling that the insertion point should +be shown. + +*Parameters* + + * rootClientId: Optional root client ID of block list on + which to insert. + * index: Index at which block should be inserted. + +### hideInsertionPoint + +Returns an action object hiding the insertion point. + +### setTemplateValidity + +Returns an action object resetting the template validity. + +*Parameters* + + * isValid: template validity flag. + +### synchronizeTemplate + +Returns an action object synchronize the template with the list of blocks + +### mergeBlocks + +Returns an action object used in signalling that two blocks should be merged + +*Parameters* + + * firstBlockClientId: Client ID of the first block to merge. + * secondBlockClientId: Client ID of the second block to merge. + +### removeBlocks + +Yields action objects used in signalling that the blocks corresponding to +the set of specified client IDs are to be removed. + +*Parameters* + + * clientIds: Client IDs of blocks to remove. + * selectPrevious: True if the previous block should be + selected when a block is removed. + +### removeBlock + +Returns an action object used in signalling that the block with the +specified client ID is to be removed. + +*Parameters* + + * clientId: Client ID of block to remove. + * selectPrevious: True if the previous block should be + selected when a block is removed. + +### toggleBlockMode + +Returns an action object used to toggle the block editing mode between +visual and HTML modes. + +*Parameters* + + * clientId: Block client ID. + +### startTyping + +Returns an action object used in signalling that the user has begun to type. + +### stopTyping + +Returns an action object used in signalling that the user has stopped typing. + +### enterFormattedText + +Returns an action object used in signalling that the caret has entered formatted text. + +### exitFormattedText + +Returns an action object used in signalling that the user caret has exited formatted text. + +### insertDefaultBlock + +Returns an action object used in signalling that a new block of the default +type should be added to the block list. + +*Parameters* + + * attributes: Optional attributes of the block to assign. + * rootClientId: Optional root client ID of block list on which + to append. + * index: Optional index where to insert the default block + +### updateBlockListSettings + +Returns an action object that changes the nested settings of a given block. + +*Parameters* + + * clientId: Client ID of the block whose nested setting are + being received. + * settings: Object with the new settings for the nested block. + +### updateSettings + +Returns an action object used in signalling that the block editor settings have been updated. + +*Parameters* + + * settings: Updated settings + +### __unstableSaveReusableBlock + +Returns an action object used in signalling that a temporary reusable blocks have been saved +in order to switch its temporary id with the real id. + +*Parameters* + + * id: Reusable block's id. + * updatedId: Updated block's id. + +### __unstableMarkLastChangeAsPersistent + +Returns an action object used in signalling that the last block change should be marked explicitely as persistent. \ No newline at end of file diff --git a/docs/designers-developers/developers/data/data-core-blocks.md b/docs/designers-developers/developers/data/data-core-blocks.md index abdb30b903773c..c7a63e769f350f 100644 --- a/docs/designers-developers/developers/data/data-core-blocks.md +++ b/docs/designers-developers/developers/data/data-core-blocks.md @@ -129,6 +129,21 @@ Returns true if the block defines support for a feature, or false otherwise. Whether block supports feature. +### isMatchingSearchTerm + +Returns true if the block type by the given name or object value matches a +search term, or false otherwise. + +*Parameters* + + * state: Blocks state. + * nameOrType: Block name or type object. + * searchTerm: Search term by which to filter. + +*Returns* + +Wheter block type matches search term. + ### hasChildBlocks Returns a boolean indicating if a block has child blocks or not. diff --git a/docs/designers-developers/developers/data/data-core-edit-post.md b/docs/designers-developers/developers/data/data-core-edit-post.md index c12cf62a83d633..f36cf5bc47068f 100644 --- a/docs/designers-developers/developers/data/data-core-edit-post.md +++ b/docs/designers-developers/developers/data/data-core-edit-post.md @@ -339,6 +339,24 @@ Returns an action object used to toggle a plugin name flag. * pluginName: Plugin name. +### hideBlockTypes + +Returns an action object used in signalling that block types by the given +name(s) should be hidden. + +*Parameters* + + * blockNames: Names of block types to hide. + +### showBlockTypes + +Returns an action object used in signalling that block types by the given +name(s) should be shown. + +*Parameters* + + * blockNames: Names of block types to show. + ### setAvailableMetaBoxesPerLocation Returns an action object used in signaling diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index 800537ba65d225..cd251dc92d6787 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -1,4 +1,4 @@ -# **core/editor**: The Editor’s Data +# **core/editor**: The Post Editor’s Data ## Selectors @@ -365,676 +365,6 @@ and modified date are the same. Whether the edited post has a floating date value. -### getBlockDependantsCacheBust - -Returns a new reference when the inner blocks of a given block client ID -change. This is used exclusively as a memoized selector dependant, relying -on this selector's shared return value and recursively those of its inner -blocks defined as dependencies. This abuses mechanics of the selector -memoization to return from the original selector function only when -dependants change. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -A value whose reference will change only when inner blocks of - the given block client ID change. - -### getBlockName - -Returns a block's name given its client ID, or null if no block exists with -the client ID. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Block name. - -### isBlockValid - -Returns whether a block is valid or not. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Is Valid. - -### getBlockAttributes - -Returns a block's attributes given its client ID, or null if no block exists with -the client ID. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Block attributes. - -### getBlock - -Returns a block given its client ID. This is a parsed copy of the block, -containing its `blockName`, `clientId`, and current `attributes` state. This -is not the block's registration settings, which must be retrieved from the -blocks module registration store. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Parsed block object. - -### getBlocks - -Returns all block objects for the current post being edited as an array in -the order they appear in the post. - -Note: It's important to memoize this selector to avoid return a new instance -on each call - -*Parameters* - - * state: Editor state. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Post blocks. - -### getClientIdsOfDescendants - -Returns an array containing the clientIds of all descendants -of the blocks given. - -*Parameters* - - * state: Global application state. - * clientIds: Array of blocks to inspect. - -*Returns* - -ids of descendants. - -### getClientIdsWithDescendants - -Returns an array containing the clientIds of the top-level blocks -and their descendants of any depth (for nested blocks). - -*Parameters* - - * state: Global application state. - -*Returns* - -ids of top-level and descendant blocks. - -### getGlobalBlockCount - -Returns the total number of blocks, or the total number of blocks with a specific name in a post. -The number returned includes nested blocks. - -*Parameters* - - * state: Global application state. - * blockName: Optional block name, if specified only blocks of that type will be counted. - -*Returns* - -Number of blocks in the post, or number of blocks with name equal to blockName. - -### getBlocksByClientId - -Given an array of block client IDs, returns the corresponding array of block -objects. - -*Parameters* - - * state: Editor state. - * clientIds: Client IDs for which blocks are to be returned. - -*Returns* - -Block objects. - -### getBlockCount - -Returns the number of blocks currently present in the post. - -*Parameters* - - * state: Editor state. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Number of blocks in the post. - -### getBlockSelectionStart - -Returns the current block selection start. This value may be null, and it -may represent either a singular block selection or multi-selection start. -A selection is singular if its start and end match. - -*Parameters* - - * state: Global application state. - -*Returns* - -Client ID of block selection start. - -### getBlockSelectionEnd - -Returns the current block selection end. This value may be null, and it -may represent either a singular block selection or multi-selection end. -A selection is singular if its start and end match. - -*Parameters* - - * state: Global application state. - -*Returns* - -Client ID of block selection end. - -### getSelectedBlockCount - -Returns the number of blocks currently selected in the post. - -*Parameters* - - * state: Global application state. - -*Returns* - -Number of blocks selected in the post. - -### hasSelectedBlock - -Returns true if there is a single selected block, or false otherwise. - -*Parameters* - - * state: Editor state. - -*Returns* - -Whether a single block is selected. - -### getSelectedBlockClientId - -Returns the currently selected block client ID, or null if there is no -selected block. - -*Parameters* - - * state: Editor state. - -*Returns* - -Selected block client ID. - -### getSelectedBlock - -Returns the currently selected block, or null if there is no selected block. - -*Parameters* - - * state: Global application state. - -*Returns* - -Selected block. - -### getBlockRootClientId - -Given a block client ID, returns the root block from which the block is -nested, an empty string for top-level blocks, or null if the block does not -exist. - -*Parameters* - - * state: Editor state. - * clientId: Block from which to find root client ID. - -*Returns* - -Root client ID, if exists - -### getBlockHierarchyRootClientId - -Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. - -*Parameters* - - * state: Editor state. - * clientId: Block from which to find root client ID. - -*Returns* - -Root client ID - -### getAdjacentBlockClientId - -Returns the client ID of the block adjacent one at the given reference -startClientId and modifier directionality. Defaults start startClientId to -the selected block, and direction as next block. Returns null if there is no -adjacent block. - -*Parameters* - - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. - * modifier: Directionality multiplier (1 next, -1 - previous). - -*Returns* - -Return the client ID of the block, or null if none exists. - -### getPreviousBlockClientId - -Returns the previous block's client ID from the given reference start ID. -Defaults start to the selected block. Returns null if there is no previous -block. - -*Parameters* - - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. - -*Returns* - -Adjacent block's client ID, or null if none exists. - -### getNextBlockClientId - -Returns the next block's client ID from the given reference start ID. -Defaults start to the selected block. Returns null if there is no next -block. - -*Parameters* - - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. - -*Returns* - -Adjacent block's client ID, or null if none exists. - -### getSelectedBlocksInitialCaretPosition - -Returns the initial caret position for the selected block. -This position is to used to position the caret properly when the selected block changes. - -*Parameters* - - * state: Global application state. - -*Returns* - -Selected block. - -### getMultiSelectedBlockClientIds - -Returns the current multi-selection set of block client IDs, or an empty -array if there is no multi-selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -Multi-selected block client IDs. - -### getMultiSelectedBlocks - -Returns the current multi-selection set of blocks, or an empty array if -there is no multi-selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -Multi-selected block objects. - -### getFirstMultiSelectedBlockClientId - -Returns the client ID of the first block in the multi-selection set, or null -if there is no multi-selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -First block client ID in the multi-selection set. - -### getLastMultiSelectedBlockClientId - -Returns the client ID of the last block in the multi-selection set, or null -if there is no multi-selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -Last block client ID in the multi-selection set. - -### isFirstMultiSelectedBlock - -Returns true if a multi-selection exists, and the block corresponding to the -specified client ID is the first block of the multi-selection set, or false -otherwise. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Whether block is first in multi-selection. - -### isBlockMultiSelected - -Returns true if the client ID occurs within the block multi-selection, or -false otherwise. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Whether block is in multi-selection set. - -### isAncestorMultiSelected - -Returns true if an ancestor of the block is multi-selected, or false -otherwise. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Whether an ancestor of the block is in multi-selection - set. - -### getMultiSelectedBlocksStartClientId - -Returns the client ID of the block which begins the multi-selection set, or -null if there is no multi-selection. - -This is not necessarily the first client ID in the selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -Client ID of block beginning multi-selection. - -### getMultiSelectedBlocksEndClientId - -Returns the client ID of the block which ends the multi-selection set, or -null if there is no multi-selection. - -This is not necessarily the last client ID in the selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -Client ID of block ending multi-selection. - -### getBlockOrder - -Returns an array containing all block client IDs in the editor in the order -they appear. Optionally accepts a root client ID of the block list for which -the order should be returned, defaulting to the top-level block order. - -*Parameters* - - * state: Editor state. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Ordered client IDs of editor blocks. - -### getBlockIndex - -Returns the index at which the block corresponding to the specified client -ID occurs within the block order, or `-1` if the block does not exist. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Index at which block exists in order. - -### isBlockSelected - -Returns true if the block corresponding to the specified client ID is -currently selected and no multi-selection exists, or false otherwise. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Whether block is selected and multi-selection exists. - -### hasSelectedInnerBlock - -Returns true if one of the block's inner blocks is selected. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - * deep: Perform a deep check. - -*Returns* - -Whether the block as an inner block selected - -### isBlockWithinSelection - -Returns true if the block corresponding to the specified client ID is -currently selected but isn't the last of the selected blocks. Here "last" -refers to the block sequence in the document, _not_ the sequence of -multi-selection, which is why `state.blockSelection.end` isn't used. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Whether block is selected and not the last in the - selection. - -### hasMultiSelection - -Returns true if a multi-selection has been made, or false otherwise. - -*Parameters* - - * state: Editor state. - -*Returns* - -Whether multi-selection has been made. - -### isMultiSelecting - -Whether in the process of multi-selecting or not. This flag is only true -while the multi-selection is being selected (by mouse move), and is false -once the multi-selection has been settled. - -*Parameters* - - * state: Global application state. - -*Returns* - -True if multi-selecting, false if not. - -### isSelectionEnabled - -Selector that returns if multi-selection is enabled or not. - -*Parameters* - - * state: Global application state. - -*Returns* - -True if it should be possible to multi-select blocks, false if multi-selection is disabled. - -### getBlockMode - -Returns the block's editing mode, defaulting to "visual" if not explicitly -assigned. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Block editing mode. - -### isTyping - -Returns true if the user is typing, or false otherwise. - -*Parameters* - - * state: Global application state. - -*Returns* - -Whether user is typing. - -### isCaretWithinFormattedText - -Returns true if the caret is within formatted text, or false otherwise. - -*Parameters* - - * state: Global application state. - -*Returns* - -Whether the caret is within formatted text. - -### getBlockInsertionPoint - -Returns the insertion point, the index at which the new inserted block would -be placed. Defaults to the last index. - -*Parameters* - - * state: Editor state. - -*Returns* - -Insertion point object with `rootClientId`, `index`. - -### isBlockInsertionPointVisible - -Returns true if we should show the block insertion point. - -*Parameters* - - * state: Global application state. - -*Returns* - -Whether the insertion point is visible or not. - -### isValidTemplate - -Returns whether the blocks matches the template or not. - -*Parameters* - - * state: null - -*Returns* - -Whether the template is valid or not. - -### getTemplate - -Returns the defined block template - -*Parameters* - - * state: null - -*Returns* - -Block Template - -### getTemplateLock - -Returns the defined block template lock. Optionally accepts a root block -client ID as context, otherwise defaulting to the global context. - -*Parameters* - - * state: Editor state. - * rootClientId: Optional block root client ID. - -*Returns* - -Block Template Lock - ### isSavingPost Returns true if the post is currently being saved, or false otherwise. @@ -1149,63 +479,6 @@ before falling back to serialization of block state. Post content. -### canInsertBlockType - -Determines if the given block type is allowed to be inserted into the block list. - -*Parameters* - - * state: Editor state. - * blockName: The name of the block type, e.g.' core/paragraph'. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Whether the given block type is allowed to be inserted. - -### getInserterItems - -Determines the items that appear in the inserter. Includes both static -items (e.g. a regular block type) and dynamic items (e.g. a reusable block). - -Each item object contains what's necessary to display a button in the -inserter and handle its selection. - -The 'utility' property indicates how useful we think an item will be to the -user. There are 4 levels of utility: - -1. Blocks that are contextually useful (utility = 3) -2. Blocks that have been previously inserted (utility = 2) -3. Blocks that are in the common category (utility = 1) -4. All other blocks (utility = 0) - -The 'frecency' property is a heuristic (https://en.wikipedia.org/wiki/Frecency) -that combines block usage frequenty and recency. - -Items are returned ordered descendingly by their 'utility' and 'frecency'. - -*Parameters* - - * state: Editor state. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Items that appear in inserter. - -### hasInserterItems - -Determines whether there are items to show in the inserter. - -*Parameters* - - * state: Editor state. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Items that appear in inserter. - ### __experimentalGetReusableBlock Returns the reusable block with the given ID. @@ -1336,44 +609,6 @@ before state satisfies the given predicate function. Whether predicate matches for some history. -### getBlockListSettings - -Returns the Block List settings of a block, if any exist. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Block settings of the block if set. - -### getEditorSettings - -Returns the editor settings. - -*Parameters* - - * state: Editor state. - -*Returns* - -The editor settings object. - -### getTokenSettings - -Returns the token settings. - -*Parameters* - - * state: Editor state. - * name: Token name. - -*Returns* - -Token settings object, or the named token settings object if set. - ### isPostLocked Returns whether the post is locked. @@ -1459,229 +694,165 @@ or skipped when the user clicks the "publish" button. Whether the pre-publish panel should be shown or not. -## Actions - -### setupEditor - -Returns an action object used in signalling that editor has initialized with -the specified post object and editor settings. - -*Parameters* - - * post: Post object. - * edits: Initial edited attributes object. - -### resetPost - -Returns an action object used in signalling that the latest version of the -post has been received, either by initialization or save. - -*Parameters* - - * post: Post object. - -### resetAutosave - -Returns an action object used in signalling that the latest autosave of the -post has been received, by initialization or autosave. - -*Parameters* - - * post: Autosave post object. +### getEditorBlocks -### updatePost - -Returns an action object used in signalling that a patch of updates for the -latest version of the post have been received. +Return the current block list. *Parameters* - * edits: Updated post fields. - -### setupEditorState - -Returns an action object used to setup the editor state when first opening an editor. + * state: null -*Parameters* +*Returns* - * post: Post object. - * blocks: Array of blocks. +Block list. -### resetBlocks +### __unstableIsEditorReady -Returns an action object used in signalling that blocks state should be -reset to the specified array of blocks, taking precedence over any other -content reflected as an edit in state. +Is the editor ready *Parameters* - * blocks: Array of blocks. - -### receiveBlocks - -Returns an action object used in signalling that blocks have been received. -Unlike resetBlocks, these should be appended to the existing known set, not -replacing. + * state: null -*Parameters* +*Returns* - * blocks: Array of block objects. +is Ready. -### updateBlockAttributes +### getEditorSettings -Returns an action object used in signalling that the block attributes with -the specified client ID has been updated. +Returns the post editor settings. *Parameters* - * clientId: Block client ID. - * attributes: Block attributes to be merged. - -### updateBlock + * state: Editor state. -Returns an action object used in signalling that the block with the -specified client ID has been updated. +*Returns* -*Parameters* +The editor settings object. - * clientId: Block client ID. - * updates: Block attributes to be merged. +## Actions -### selectBlock +### setupEditor -Returns an action object used in signalling that the block with the -specified client ID has been selected, optionally accepting a position -value reflecting its selection directionality. An initialPosition of -1 -reflects a reverse selection. +Returns an action object used in signalling that editor has initialized with +the specified post object and editor settings. *Parameters* - * clientId: Block client ID. - * initialPosition: Optional initial position. Pass as -1 to - reflect reverse selection. + * post: Post object. + * edits: Initial edited attributes object. + * template: Block Template. -### toggleSelection +### resetPost -Returns an action object that enables or disables block selection. +Returns an action object used in signalling that the latest version of the +post has been received, either by initialization or save. *Parameters* - * boolean: [isSelectionEnabled=true] Whether block selection should - be enabled. + * post: Post object. -### replaceBlocks +### resetAutosave -Returns an action object signalling that a blocks should be replaced with -one or more replacement blocks. +Returns an action object used in signalling that the latest autosave of the +post has been received, by initialization or autosave. *Parameters* - * clientIds: Block client ID(s) to replace. - * blocks: Replacement block(s). + * post: Autosave post object. -### replaceBlock +### __experimentalRequestPostUpdateStart -Returns an action object signalling that a single block should be replaced -with one or more replacement blocks. +Optimistic action for dispatching that a post update request has started. *Parameters* - * clientId: Block client ID to replace. - * block: Replacement block(s). + * options: null -### moveBlockToPosition +### __experimentalRequestPostUpdateSuccess -Returns an action object signalling that an indexed block should be moved -to a new index. +Optimistic action for indicating that the request post update has completed +successfully. *Parameters* - * clientId: The client ID of the block. - * fromRootClientId: Root client ID source. - * toRootClientId: Root client ID destination. - * index: The index to move the block into. + * data: The data for the action. + * data.previousPost: The previous post prior to update. + * data.post: The new post after update + * data.isRevision: Whether the post is a revision or not. + * data.options: Options passed through from the original + action dispatch. + * data.postType: The post type object. -### insertBlock +### __experimentalRequestPostUpdateFailure -Returns an action object used in signalling that a single block should be -inserted, optionally at a specific index respective a root block list. +Optimistic action for indicating that the request post update has completed +with a failure. *Parameters* - * block: Block object to insert. - * index: Index at which block should be inserted. - * rootClientId: Optional root client ID of block list on which to insert. - * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. + * data: The data for the action + * data.post: The post that failed updating. + * data.edits: The fields that were being updated. + * data.error: The error from the failed call. + * data.options: Options passed through from the original + action dispatch. -### insertBlocks +### updatePost -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. +Returns an action object used in signalling that a patch of updates for the +latest version of the post have been received. *Parameters* - * blocks: Block objects to insert. - * index: Index at which block should be inserted. - * rootClientId: Optional root cliente ID of block list on which to insert. - * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. + * edits: Updated post fields. -### showInsertionPoint +### setupEditorState -Returns an action object used in signalling that the insertion point should -be shown. +Returns an action object used to setup the editor state when first opening +an editor. *Parameters* - * rootClientId: Optional root client ID of block list on - which to insert. - * index: Index at which block should be inserted. - -### hideInsertionPoint - -Returns an action object hiding the insertion point. + * post: Post object. -### setTemplateValidity +### editPost -Returns an action object resetting the template validity. +Returns an action object used in signalling that attributes of the post have +been edited. *Parameters* - * isValid: template validity flag. - -### synchronizeTemplate - -Returns an action object synchronize the template with the list of blocks + * edits: Post attributes to edit. -### editPost +### __experimentalOptimisticUpdatePost -Returns an action object used in signalling that attributes of the post have -been edited. +Returns action object produced by the updatePost creator augmented by +an optimist option that signals optimistically applying updates. *Parameters* - * edits: Post attributes to edit. + * edits: Updated post fields. ### savePost -Returns an action object to save the post. +Action generator for saving the current post in the editor. *Parameters* - * options: Options for the save. - * options.isAutosave: Perform an autosave if true. + * options: null -### mergeBlocks +### refreshPost -Returns an action object used in signalling that two blocks should be merged +Action generator for handling refreshing the current post. -*Parameters* +### trashPost - * firstBlockClientId: Client ID of the first block to merge. - * secondBlockClientId: Client ID of the second block to merge. +Action generator for trashing the current post in the editor. ### autosave -Returns an action object used in signalling that the post should autosave. +Action generator used in signalling that the post should autosave. *Parameters* @@ -1701,53 +872,6 @@ Returns an action object used in signalling that undo history should pop. Returns an action object used in signalling that undo history record should be created. -### removeBlocks - -Returns an action object used in signalling that the blocks corresponding to -the set of specified client IDs are to be removed. - -*Parameters* - - * clientIds: Client IDs of blocks to remove. - * selectPrevious: True if the previous block should be - selected when a block is removed. - -### removeBlock - -Returns an action object used in signalling that the block with the -specified client ID is to be removed. - -*Parameters* - - * clientId: Client ID of block to remove. - * selectPrevious: True if the previous block should be - selected when a block is removed. - -### toggleBlockMode - -Returns an action object used to toggle the block editing mode between -visual and HTML modes. - -*Parameters* - - * clientId: Block client ID. - -### startTyping - -Returns an action object used in signalling that the user has begun to type. - -### stopTyping - -Returns an action object used in signalling that the user has stopped typing. - -### enterFormattedText - -Returns an action object used in signalling that the caret has entered formatted text. - -### exitFormattedText - -Returns an action object used in signalling that the user caret has exited formatted text. - ### updatePostLock Returns an action object used to lock the editor. @@ -1806,7 +930,8 @@ to be updated. ### __experimentalConvertBlockToStatic -Returns an action object used to convert a reusable block into a static block. +Returns an action object used to convert a reusable block into a static +block. *Parameters* @@ -1814,62 +939,52 @@ Returns an action object used to convert a reusable block into a static block. ### __experimentalConvertBlockToReusable -Returns an action object used to convert a static block into a reusable block. +Returns an action object used to convert a static block into a reusable +block. *Parameters* * clientIds: The client IDs of the block to detach. -### insertDefaultBlock +### enablePublishSidebar -Returns an action object used in signalling that a new block of the default -type should be added to the block list. +Returns an action object used in signalling that the user has enabled the +publish sidebar. -*Parameters* +### disablePublishSidebar - * attributes: Optional attributes of the block to assign. - * rootClientId: Optional root client ID of block list on which - to append. - * index: Optional index where to insert the default block +Returns an action object used in signalling that the user has disabled the +publish sidebar. -### updateBlockListSettings +### lockPostSaving -Returns an action object that changes the nested settings of a given block. +Returns an action object used to signal that post saving is locked. *Parameters* - * clientId: Client ID of the block whose nested setting are - being received. - * settings: Object with the new settings for the nested block. + * lockName: The lock name. -### updateEditorSettings +### unlockPostSaving -Returns an action object used in signalling that the editor settings have been updated. +Returns an action object used to signal that post saving is unlocked. *Parameters* - * settings: Updated settings - -### enablePublishSidebar - -Returns an action object used in signalling that the user has enabled the publish sidebar. - -### disablePublishSidebar - -Returns an action object used in signalling that the user has disabled the publish sidebar. + * lockName: The lock name. -### lockPostSaving +### resetEditorBlocks -Returns an action object used to signal that post saving is locked. +Returns an action object used to signal that the blocks have been updated. *Parameters* - * lockName: The lock name. + * blocks: Block Array. + * options: Optional options. -### unlockPostSaving +### updateEditorSettings -Returns an action object used to signal that post saving is unlocked. +Returns an action object used in signalling that the post editor settings have been updated. *Parameters* - * lockName: The lock name. \ No newline at end of file + * settings: Updated settings \ No newline at end of file diff --git a/docs/designers-developers/developers/data/data-core.md b/docs/designers-developers/developers/data/data-core.md index 2a98c3bb5d3974..9b01fe325c295b 100644 --- a/docs/designers-developers/developers/data/data-core.md +++ b/docs/designers-developers/developers/data/data-core.md @@ -136,17 +136,50 @@ get back from the oEmbed preview API. Is the preview for the URL an oEmbed link fallback. -### hasUploadPermissions +### hasUploadPermissions (deprecated) -Return Upload Permissions. +Returns whether the current user can upload media. + +Calling this may trigger an OPTIONS request to the REST API via the +`canUser()` resolver. + +https://developer.wordpress.org/rest-api/reference/ + +*Deprecated* + +Deprecated since 5.0. Callers should use the more generic `canUser()` selector instead of + `hasUploadPermissions()`, e.g. `canUser( 'create', 'media' )`. *Parameters* - * state: State tree. + * state: Data state. *Returns* -Upload Permissions. +Whether or not the user can upload media. Defaults to `true` if the OPTIONS + request is being made. + +### canUser + +Returns whether the current user can perform the given action on the given +REST resource. + +Calling this may trigger an OPTIONS request to the REST API via the +`canUser()` resolver. + +https://developer.wordpress.org/rest-api/reference/ + +*Parameters* + + * state: Data state. + * action: Action to check. One of: 'create', 'read', 'update', 'delete'. + * resource: REST resource to check, e.g. 'media' or 'posts'. + * id: Optional ID of the rest resource to check. + +*Returns* + +Whether or not the user can perform the action, + or `undefined` if the OPTIONS request is still being made. ## Actions @@ -213,4 +246,14 @@ Returns an action object used in signalling that Upload permissions have been re *Parameters* - * hasUploadPermissions: Does the user have permission to upload files? \ No newline at end of file + * hasUploadPermissions: Does the user have permission to upload files? + +### receiveUserPermission + +Returns an action object used in signalling that the current user has +permission to perform an action on a REST resource. + +*Parameters* + + * key: A key that represents the action and REST resource. + * isAllowed: Whether or not the user can perform the action. \ No newline at end of file diff --git a/docs/designers-developers/developers/feature-flags.md b/docs/designers-developers/developers/feature-flags.md new file mode 100644 index 00000000000000..47a28d0fa42c39 --- /dev/null +++ b/docs/designers-developers/developers/feature-flags.md @@ -0,0 +1,110 @@ +# Feature Flags + +With phase 2 of the Gutenberg project there's a need for improved control over how code changes are released. Newer features developed for phase 2 and beyond should only be released to the Gutenberg plugin, while improvements and bug fixes should still continue to make their way into core releases. + +The technique for handling this is known as a 'feature flag'. + +## Introducing `process.env.GUTENBERG_PHASE` + +The `process.env.GUTENBERG_PHASE` is an environment variable containing a number that represents the phase. When the codebase is built for the plugin, this variable will be set to `2`. When building for core, it will be set to `1`. + +## Basic Use + +A phase 2 function or constant should be exported using the following ternary syntax: + +```js +function myPhaseTwoFeature() { + // implementation +} + +export const phaseTwoFeature = process.env.GUTENBERG_PHASE === 2 ? myPhaseTwoFeature : undefined; +``` + +In phase 1 environments the `phaseTwoFeature` export will be `undefined`. + +If you're attempting to import and call a phase 2 feature, be sure to wrap the call to the function in an if statement to avoid an error: +```js +import { phaseTwoFeature } from '@wordpress/foo'; + +if ( process.env.GUTENBERG_PHASE === 2) { + phaseTwoFeature(); +} +``` + +### How it works + +During the webpack build, any instances of `process.env.GUTENBERG_PHASE` will be replaced using webpack's define plugin (https://webpack.js.org/plugins/define-plugin/). + +If you write the following code: +```js +if ( process.env.GUTENBERG_PHASE === 2 ) { + phaseTwoFeature(); +} +``` + +When building the codebase for the plugin the variable will be replaced with the number literal `2`: +```js +if ( 2 === 2 ) { + phaseTwoFeature(); +} +``` + +Any code within the body of the if statement will be executed within the gutenberg plugin since `2 === 2` evaluates to `true`. + +For core, the `process.env.GUTENBERG_PHASE` variable is replaced with `1`, so the built code will look like: +```js +if ( 1 === 2 ) { + phaseTwoFeature(); +} +``` + +`1 === 2` evaluates to false so the phase 2 feature will not be executed within core. + +### Dead Code Elimination + +When building code for production, webpack 'minifies' code (https://en.wikipedia.org/wiki/Minification_(programming)), removing the amount of unnecessary JavaScript as much as possible. One of the steps involves something known as 'dead code elimination'. + +When the following code is encountered, webpack determines that the surrounding `if`statement is unnecessary: +```js +if ( 2 === 2 ) { + phaseTwoFeature(); +} +``` + + The condition will alway evaluates to `true`, so can be removed leaving just the code in the body: + ```js + phaseTwoFeature(); + ``` + +Similarly when building for core, the condition in the following `if` statement always resolves to false: +```js +if ( 1 === 2 ) { + phaseTwoFeature(); +} +``` + +The minification process will remove the entire `if` statement including the body, ensuring code destined for phase 2 is not included in the built JavaScript intended for core. + +## FAQ + +#### Why should I only use `===` or `!==` when comparing `process.env.GUTENBERG_PHASE` and not `>`, `>=`, `<` or `<=`? + +This is a restriction due to the behaviour of the greater than or less than operators in JavaScript when `process.env.GUTENBERG_PHASE` is undefined, as might be the case for third party users of WordPress npm packages. Both `process.env.GUTENBERG_PHASE < 2` and `process.env.GUTENBERG_PHASE > 1` resolve to false. When writing `if ( process.env.GUTENBERG_PHASE > 1 )`, the intention might be to avoid executing the phase 2 code in the following `if` statement's body. That's fine since it will evaluate to false. + +However, the following code doesn't quite have the intended behaviour: + +``` +function myPhaseTwoFeature() { + if ( process.env.GUTENBERG_PHASE < 2 ) { + return; + } + + // implementation of phase 2 feature +} +``` + +Here an early return is used to avoid execution of a phase 2 feature, but because the `if` condition resolves to false, the early return is bypassed and the phase 2 feature is incorrectly triggered. + +#### Why shouldn't I assign the result of an expression involving `GUTENBERG_PHASE` to a variable, e.g. `const isMyFeatureActive = process.env.GUTENBERG_PHASE === 2`? + +The aim here is to avoid introducing any complexity that could result in webpack's minifier not being able to eliminate dead code. See the [Dead Code Elimination](#dead-code-elimination) section for further details. diff --git a/docs/designers-developers/developers/filters/README.md b/docs/designers-developers/developers/filters/README.md index 9715e15885935c..c110d2ed3a5543 100644 --- a/docs/designers-developers/developers/filters/README.md +++ b/docs/designers-developers/developers/filters/README.md @@ -1,7 +1,7 @@ # Filter Reference -[Hooks](https://developer.wordpress.org/plugins/hooks/) are a way for one piece of code to interact/modify another piece of code. They provide one way for plugins and themes interact with Gutenberg, but they’re also used extensively by WordPress Core itself. +[Hooks](https://developer.wordpress.org/plugins/hooks/) are a way for one piece of code to interact/modify another piece of code. They provide one way for plugins and themes interact with the editor, but they’re also used extensively by WordPress Core itself. -There are two types of hooks: [Actions](https://developer.wordpress.org/plugins/hooks/actions/) and [Filters](https://developer.wordpress.org/plugins/hooks/filters/). In addition to PHP actions and filters, Gutenberg also provides a mechanism for registering and executing hooks in JavaScript. This functionality is also available on npm as the [@wordpress/hooks](https://www.npmjs.com/package/@wordpress/hooks) package, for general purpose use. +There are two types of hooks: [Actions](https://developer.wordpress.org/plugins/hooks/actions/) and [Filters](https://developer.wordpress.org/plugins/hooks/filters/). In addition to PHP actions and filters, WordPress also provides a mechanism for registering and executing hooks in JavaScript. This functionality is also available on npm as the [@wordpress/hooks](https://www.npmjs.com/package/@wordpress/hooks) package, for general purpose use. -You can also learn more about both APIs: [PHP](https://codex.wordpress.org/Plugin_API/) and [JavaScript](https://github.com/WordPress/packages/tree/master/packages/hooks). +You can also learn more about both APIs: [PHP](https://codex.wordpress.org/Plugin_API/) and [JavaScript](/packages/tree/master/packages/hooks). diff --git a/docs/designers-developers/developers/filters/autocomplete-filters.md b/docs/designers-developers/developers/filters/autocomplete-filters.md index 2b9d60476de80a..1707080e867e8c 100644 --- a/docs/designers-developers/developers/filters/autocomplete-filters.md +++ b/docs/designers-developers/developers/filters/autocomplete-filters.md @@ -1,8 +1,8 @@ # Autocomplete -Gutenberg provides an `editor.Autocomplete.completers` filter for extending and overriding the list of autocompleters used by blocks. +The `editor.Autocomplete.completers` filter is for extending and overriding the list of autocompleters used by blocks. -The `Autocomplete` component found in `@wordpress/editor` applies this filter. The `@wordpress/components` package provides the foundational `Autocomplete` component that does not apply such a filter, but blocks should generally use the component provided by `@wordpress/editor`. +The `Autocomplete` component found in `@wordpress/block-editor` applies this filter. The `@wordpress/components` package provides the foundational `Autocomplete` component that does not apply such a filter, but blocks should generally use the component provided by `@wordpress/block-editor`. ### Example diff --git a/docs/designers-developers/developers/filters/block-filters.md b/docs/designers-developers/developers/filters/block-filters.md index a8df36979dd1e0..99ba4cc0a0d49f 100644 --- a/docs/designers-developers/developers/filters/block-filters.md +++ b/docs/designers-developers/developers/filters/block-filters.md @@ -1,10 +1,10 @@ # Block Filters -To modify the behavior of existing blocks, Gutenberg exposes several APIs: +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. +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. _Example:_ @@ -17,17 +17,41 @@ wp.blocks.registerBlockStyle( 'core/quote', { The example above registers a block style variation named `fancy-quote` to the `core/quote` block. When the user selects this block style variation from the styles selector, an `is-style-fancy-quote` className will be added to the block's wrapper. -By adding `isDefault: true`, you can make registered style variation to be active by default when a block is inserted. +By adding `isDefault: true` you can mark the registered style variation as the one that is recognized as active when no custom class name is provided. It also means that there will be no custom class name added to the HTML output for the style that is marked as default. To remove a block style variation use `wp.blocks.unregisterBlockStyle()`. _Example:_ ```js -wp.blocks.unregisterBlockStyle( 'core/quote', 'fancy-quote' ); +wp.blocks.unregisterBlockStyle( 'core/quote', 'large' ); ``` -The above removes the variation named `fancy-quote` from the `core/quote` block. +The above removes the variation named `large` from the `core/quote` block. + +**Important:** When unregistering a block style, there can be a [race condition](https://en.wikipedia.org/wiki/Race_condition) on which code runs first: registering the style, or unregistering the style. You want your unregister code to run last. The way to do that is specify the component that is registering the style as a dependency, in this case `wp-edit-post`. Additionally, using `wp.domReady()` ensures the unregister code runs once the dom is loaded. + +Enqueue your JavaScript with the following PHP code: + +```php +function myguten_enqueue() { + wp_enqueue_script( + 'myguten-script', + plugins_url( 'myguten.js', __FILE__ ), + array( 'wp-blocks', 'wp-dom-ready', 'wp-edit-post' ), + filemtime( plugin_dir_path( __FILE__ ) . '/myguten.js' ) + ); +} +add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); +``` + +The JavaScript code in `myguten.js`: + +```js +wp.domReady( function() { + wp.blocks.unregisterBlockStyle( 'core/quote', 'large' ); +} ); +``` ### Filters @@ -89,7 +113,7 @@ wp.hooks.addFilter( ); ``` -_Note:_ This filter must always be run on every page load, and not in your browser's developer tools console. Otherwise, a [block validation](../../../../docs/designers-developers/developers/block-api/block-edit-save.md#validation) error will occur the next time the post is edited. This is due to the fact that block validation occurs by verifying that the saved output matches what is stored in the post's content during editor initialization. So, if this filter does not exist when the editor loads, the block will be marked as invalid. +_Note:_ This filter must always be run on every page load, and not in your browser's developer tools console. Otherwise, a [block validation](/docs/designers-developers/developers/block-api/block-edit-save.md#validation) error will occur the next time the post is edited. This is due to the fact that block validation occurs by verifying that the saved output matches what is stored in the post's content during editor initialization. So, if this filter does not exist when the editor loads, the block will be marked as invalid. #### `blocks.getBlockDefaultClassName` @@ -115,7 +139,7 @@ wp.hooks.addFilter( #### `blocks.switchToBlockType.transformedBlock` -Used to filters an individual transform result from block transformation. All of the original blocks are passed, since transformations are many-to-many, not one-to-one. +Used to filter an individual transform result from block transformation. All of the original blocks are passed since transformations are many-to-many, not one-to-one. #### `blocks.getBlockAttributes` @@ -200,7 +224,7 @@ var withClientIdClassName = wp.compose.createHigherOrderComponent( function( Blo {}, props, { - classsName: "block-" + props.clientId, + className: "block-" + props.clientId, } ); @@ -237,8 +261,9 @@ Adding blocks is easy enough, removing them is as easy. Plugin or theme authors ```js // my-plugin.js - -wp.blocks.unregisterBlockType( 'core/verse' ); +wp.domReady( function() { + wp.blocks.unregisterBlockType( 'core/verse' ); +} ); ``` and load this script in the Editor @@ -251,7 +276,7 @@ function my_plugin_blacklist_blocks() { wp_enqueue_script( 'my-plugin-blacklist-blocks', plugins_url( 'my-plugin.js', __FILE__ ), - array( 'wp-blocks' ) + array( 'wp-blocks', 'wp-dom-ready', 'wp-edit-post' ) ); } add_action( 'enqueue_block_editor_assets', 'my_plugin_blacklist_blocks' ); @@ -322,9 +347,9 @@ function my_plugin_block_categories( $categories, $post ) { add_filter( 'block_categories', 'my_plugin_block_categories', 10, 2 ); ``` -You can also display an icon with your block category by setting an `icon` attribute.The value can be the slug of a [WordPress Dashicon](https://developer.wordpress.org/resource/dashicons/). +You can also display an icon with your block category by setting an `icon` attribute. The value can be the slug of a [WordPress Dashicon](https://developer.wordpress.org/resource/dashicons/). -It is possible to set an SVG as the icon of the category if a custom icon is needed.To do so, the icon should be rendered and set on the frontend, so it can make use of WordPress SVG, allowing mobile compatibility and making the icon more accessible. +It is possible to set an SVG as the icon of the category if a custom icon is needed. To do so, the icon should be rendered and set on the frontend, so it can make use of WordPress SVG, allowing mobile compatibility and making the icon more accessible. To set an SVG icon for the category shown in the previous example, add the following example JavaScript code to the editor calling `wp.blocks.updateCategory` e.g: ```js @@ -335,5 +360,5 @@ To set an SVG icon for the category shown in the previous example, add the follo var svgIcon = el( SVG, { width: 20, height: 20, viewBox: '0 0 20 20'}, circle); wp.blocks.updateCategory( 'my-category', { icon: svgIcon } ); } )(); -``` +``` diff --git a/docs/designers-developers/developers/filters/editor-filters.md b/docs/designers-developers/developers/filters/editor-filters.md index 1289b8241936ba..9421d7cd688019 100644 --- a/docs/designers-developers/developers/filters/editor-filters.md +++ b/docs/designers-developers/developers/filters/editor-filters.md @@ -1,10 +1,10 @@ # Editor Filters (Experimental) -To modify the behavior of the editor experience, Gutenberg exposes the following Filters: +To modify the behavior of the editor experience, the following Filters are exposed: ### `editor.PostFeaturedImage.imageSize` -Used to modify the image size displayed in the Post Featured Image component. It defaults to `'post-thumbnail'`, and will fail back to the `full` image size when the specified image size doesn't exist in the media object. It's modeled after the `admin_post_thumbnail_size` filter in the Classic Editor. +Used to modify the image size displayed in the Post Featured Image component. It defaults to `'post-thumbnail'`, and will fail back to the `full` image size when the specified image size doesn't exist in the media object. It's modeled after the `admin_post_thumbnail_size` filter in the classic editor. _Example:_ @@ -16,3 +16,17 @@ var withImageSize = function( size, mediaId, postId ) { wp.hooks.addFilter( 'editor.PostFeaturedImage.imageSize', 'my-plugin/with-image-size', withImageSize ); ``` +### `editor.PostPreview.interstitialMarkup` + +Filters the interstitial message shown when generating previews. + +_Example:_ + +```js +var customPreviewMessage = function() { + return 'Post preview is being generated!'; +}; + +wp.hooks.addFilter( 'editor.PostPreview.interstitialMarkup', 'my-plugin/custom-preview-message', customPreviewMessage ); +``` + diff --git a/docs/designers-developers/developers/internationalization.md b/docs/designers-developers/developers/internationalization.md index 68cafea0e4257f..2ae1b8f8777f38 100644 --- a/docs/designers-developers/developers/internationalization.md +++ b/docs/designers-developers/developers/internationalization.md @@ -1,47 +1,232 @@ # Internationalization -This document aims to give an overview of the possibilities for both internationalization and localization when developing with Gutenberg. +## What is Internationalization? -## PHP +Internationalization is the process to provide multiple language support to software, in this case WordPress. Internationalization is often abbreviated as **i18n**, where 18 stands for the number of letters between the first _i_ and the last _n_. -For years, WordPress has been providing the necessary tools and functions to internationalize plugins and themes. This includes helper functions like `__()` and similar. +Providing i18n support to your plugin and theme allows it to reach the largest possible audience, even without requiring you to provide the additional language translations. When you upload your software to WordPress.org, all JS and PHP files will automatically be parsed. Any detected translation strings are added to [translate.wordpress.org](https://translate.wordpress.org/) to allow the community to translate, ensuring WordPress plugins and themes are available in as many languages as possible. -### Common Methods +For PHP, WordPress has a long established process, see [How to Internationalize Your Plugin](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/). The release of WordPress 5.0 brings a similar process for translation to JavaScript code. -- `__( 'Hello World', 'my-text-domain' )`: Translate a certain string. -- `_x( 'Block', 'noun', 'my-text-domain' )`: Translate a certain string with some additional context. -- `_e( 'Hello World', 'my-text-domain' )`: Translate and print a certain string. -- `esc_html__( 'Hello World', 'my-text-domain' )`: Translate a certain string and escape it for safe use in HTML output. -- `esc_html_e( 'Hello World', 'my-text-domain' )`: Translate a certain string, escape it for safe use in HTML output, and print it. -- `_n( '%s Comment', '%s Comments', $number, 'my-text-domain' )`: Translate and retrieve the singular or plural form based on the supplied number. - Usually used in combination with `sprintf()` and `number_format_i18n()`. +## How to use i18n in JavaScript -## JavaScript +WordPress 5.0 introduced the wp-i18n JavaScript package that provides the functions needed to add translatable strings as you would in PHP. -Historically, `wp_localize_script()` has been used to put server-side PHP data into a properly-escaped native JavaScript object. +First, add **wp-i18n** as a dependency when registering your script: -The new editor introduces a new approach to translating strings for the editor through a new package called `@wordpress/i18n`. +```php + 'myguten-script', + ) ); +} +add_action( 'init', 'myguten_block_init' ); +``` -Depending on your developer workflow, you might want to use WP-CLI's `wp i18n make-pot` command or a build tool for Babel called `@wordpress/babel-plugin-makepot` to create the necessary translation file. The latter approach integrates with Babel to extract the I18N methods. +In your code, you can include the i18n functions. The most common function is **__** (a double underscore) which provides translation of a simple string. Here is a basic static block example, this is in a file called `block.js`: -### Common methods in wp.i18n (may look similar) +```js +const { __ } = wp.i18n; +const el = wp.element.createElement; +const { registerBlockType } = wp.blocks; -- `setLocaleData( data: Object, domain: string )`: Creates a new I18N instance providing translation data for a domain. -- `__( 'Hello World', 'my-text-domain' )`: Translate a certain string. -- `_n( '%s Comment', '%s Comments', numberOfComments, 'my-text-domain' )`: Translate and retrieve the singular or plural form based on the supplied number. -- `_x( 'Default', 'block style', 'my-text-domain' )`: Translate a certain string with some additional context. -- `sprintf()`: JavaScript port of the PHP function with the same name. +registerBlockType( 'myguten/simple', { + title: __('Simple Block', 'myguten'), + category: 'widgets', -### Loading Translations + edit: () => { + return el( + 'p', + { style: { color:'red'}, }, + __('Hello World', 'myguten') + ); + }, -WordPress 5.0 introduces a new function called `wp_set_script_translations( 'my-script-handle', 'my-text-domain' )` to load translation files for a given script handle. + save: () => { + return el( + 'p', + { style: { color:'red'}, }, + __('Hello World', 'myguten') + ); + } +}); +``` + +In the above example, the function will use the first argument for the string to be translated. The second argument is the text domain which must match the text domain slug specified by your plugin. + +Common functions available, these mirror their PHP counterparts are: + +- `__( 'Hello World', 'my-text-domain' )` - Translate a certain string. +- `_n( '%s Comment', '%s Comments', numberOfComments, 'my-text-domain' )` - Translate and retrieve the singular or plural form based on the supplied number. +- `_x( 'Default', 'block style', 'my-text-domain' )` - Translate a certain string with some additional context. + +**Note:** Every string displayed to the user should be wrapped in an i18n function. + +After all strings in your code is wrapped, the final step is to tell WordPress your JavaScript contains translations, using the [wp_set_script_translations()](https://developer.wordpress.org/reference/functions/wp_set_script_translations/) function. + +```php +\n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2019-03-08T11:26:56-08:00\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"X-Generator: WP-CLI 2.1.0\n" +"X-Domain: myguten\n" + +#. Plugin Name of the plugin +msgid "Scratch Plugin" +msgstr "" + +#: block.js:6 +msgid "Simple Block" +msgstr "" + +#: block.js:13 +#: block.js:21 +msgid "Hello World" +msgstr "" +``` + +Here, `msgid` is the string to be translated, and `msgstr` is the actual translation. In the POT file, `msgstr` will always be empty. + +This POT file can then be used as the template for new translations. You should **copy the file** using the language code you are going to translate, this example will use the Esperanto (eo) language: + +``` +cp myguten.pot myguten-eo.po +``` + +For this simple example, you can simply edit the `.po` file in your editor and add the translation to all the `msgstr` sets. For a larger, more complex set of translation, the [GlotPress](https://glotpress.blog/) and [Poedit](https://poedit.net/) tools exist to help. + +You need also to add the `Language: eo` parameter. Here is full `myguten-eo.po` translated file + +``` +# Copyright (C) 2019 +# This file is distributed under the same license as the Scratch Plugin plugin. +msgid "" +msgstr "" +"Project-Id-Version: Scratch Plugin\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/scratch\n" +"Last-Translator: Marcus Kazmierczak \n" +"Language-Team: Esperanto \n" +"Language: eo\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2019-02-18T07:20:46-08:00\n" +"PO-Revision-Date: 2019-02-18 08:16-0800\n" +"X-Generator: Poedit 2.2.1\n" +"X-Domain: myguten\n" -You can learn more about it in [the JavaScript I18N dev note](https://make.wordpress.org/core/2018/11/09/new-javascript-i18n-support-in-wordpress/). +#. Plugin Name of the plugin +msgid "Scratch Plugin" +msgstr "Scratch kromprogrameto" + +#: block.js:6 +msgid "Simple Block" +msgstr "Simpla bloko" + +#: block.js:13 block.js:21 +msgid "Hello World" +msgstr "Saltuon mundo" +``` + +The last step to create the translation file is to convert the `myguten-eo.po` to the JSON format needed. For this, you can use the [po2json utility](https://github.com/mikeedwards/po2json) which you install using npm. It might be easiest to install globally using: `npm install -g po2json`. Once installed, use the following command to convert to JED format: + +``` +po2json myguten-eo.po myguten-eo.json -f jed +``` + +This will generate the JSON file `myguten-eo.json` which looks like: + +```json +{ + "domain": "messages", + "locale_data": { + "messages": { + "": { + "domain": "messages", + "lang": "eo" + }, + "Scratch Plugin": [ + "Scratch kromprogrameto" + ], + "Simple Block": [ + "Simpla bloko" + ], + "Hello World": [ + "Saltuon mundo" + ] + } + } +} +``` + + +### Load Translation File + +The final part is to tell WordPress where it can look to find the translation file. The `wp_set_script_translations` function accepts an optional third argument that is the path it will first check for translations. For example: + +```php + General and change your site language to Esperanto. + +With the language set, create a new post, add the block, and you will see the translations used. -## More Resources - -- [WP-CLI I18N command to generate translation catalogues](https://github.com/wp-cli/i18n-command) -- [Plugin Developer Handbook](https://developer.wordpress.org/plugins/internationalization/) -- [Theme Developer Handbook](https://developer.wordpress.org/themes/internationalization/) diff --git a/docs/designers-developers/developers/packages.md b/docs/designers-developers/developers/packages.md index 7096f67115a28e..b5ef06c18782a2 100644 --- a/docs/designers-developers/developers/packages.md +++ b/docs/designers-developers/developers/packages.md @@ -1,8 +1,8 @@ # Packages -Gutenberg exposes a list of JavaScript packages and tools for WordPress development. +WordPress exposes a list of JavaScript packages and tools for WordPress development. -## Using the packages via WordPress global +## Using the Packages via WordPress Global JavaScript packages are available as a registered script in WordPress and can be accessed using the `wp` global variable. @@ -22,19 +22,19 @@ const { PlainText } = wp.editor; ``` -## Using the packages via npm +## Using the Packages via npm All the packages are also available on [npm](https://www.npmjs.com/org/wordpress) if you want to bundle them in your code. -Using the same `PlainText` example, you would install the editor module with npm: +Using the same `PlainText` example, you would install the block editor module with npm: ```bash -npm install @wordpress/editor --save +npm install @wordpress/block-editor --save ``` Once installed, you can access the component in your code using: ```js -import { PlainText } from '@wordpress/editor'; +import { PlainText } from '@wordpress/block-editor'; ``` diff --git a/docs/designers-developers/developers/themes/theme-support.md b/docs/designers-developers/developers/themes/theme-support.md index a5da2c7199e97b..57a2f748f641c8 100644 --- a/docs/designers-developers/developers/themes/theme-support.md +++ b/docs/designers-developers/developers/themes/theme-support.md @@ -5,7 +5,7 @@ The new Blocks include baseline support in all themes, enhancements to opt-in to There are a few new concepts to consider when building themes: - **Editor Color Palette** - A default set of colors is provided, but themes can register their own and optionally lock users into picking from the defined palette. -- **Editor Text Size Palette** - A default set of sizes is provided, but themes and register their own and optionally lock users into picking from preselected sizes. +- **Editor Text Size Palette** - A default set of sizes is provided, but themes can register their own and optionally lock users into picking from preselected sizes. - **Responsive Embeds** - Themes must opt-in to responsive embeds. - **Frontend & Editor Styles** - To get the most out of blocks, theme authors will want to make sure Core styles look good and opt-in, or write their own styles to best fit their theme. - **Dark Mode** - If a Theme is a Dark Theme with a dark background containing light text, the theme author can opt-in to the Dark Mode. @@ -137,28 +137,24 @@ Blocks may allow the user to configure the font sizes they use, e.g., the paragr ```php add_theme_support( 'editor-font-sizes', array( array( - 'name' => __( 'small', 'themeLangDomain' ), - 'shortName' => __( 'S', 'themeLangDomain' ), + 'name' => __( 'Small', 'themeLangDomain' ), 'size' => 12, 'slug' => 'small' ), array( - 'name' => __( 'regular', 'themeLangDomain' ), - 'shortName' => __( 'M', 'themeLangDomain' ), + 'name' => __( 'Normal', 'themeLangDomain' ), 'size' => 16, - 'slug' => 'regular' + 'slug' => 'normal' ), array( - 'name' => __( 'large', 'themeLangDomain' ), - 'shortName' => __( 'L', 'themeLangDomain' ), + 'name' => __( 'Large', 'themeLangDomain' ), 'size' => 36, 'slug' => 'large' ), array( - 'name' => __( 'larger', 'themeLangDomain' ), - 'shortName' => __( 'XL', 'themeLangDomain' ), + 'name' => __( 'Huge', 'themeLangDomain' ), 'size' => 50, - 'slug' => 'larger' + 'slug' => 'huge' ) ) ); ``` @@ -270,7 +266,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](https://wordpress.org/gutenberg/handbook/blocks/applying-styles-with-stylesheets/). +Further reading: [Applying Styles with Stylesheets](/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md). ## Default block styles diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md index de6509e4c42b73..1bad64b3d95329 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md @@ -4,7 +4,7 @@ To simplify block customization and ensure a consistent experience for users, th ## Toolbar - + 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 an RichText component. @@ -171,7 +171,7 @@ Note that `BlockControls` is only visible when the block is currently selected a ## Inspector -
+ The inspector is used to display less-often-used settings or settings that require more screen space. The inspector should be used for **block-level settings only**. diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md index e905d0b1a9a941..aa785ff85e31f6 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md @@ -124,7 +124,7 @@ There are a few things to notice: ## Live rendering in Gutenberg editor -Gutenberg 2.8 added the [`
`](https://github.com/WordPress/gutenberg/tree/master/packages/components/src/server-side-render) block which enables rendering to take place on the server using PHP rather than in JavaScript. +Gutenberg 2.8 added the [` `](/packages/components/src/server-side-render) block which enables rendering to take place on the server using PHP rather than in JavaScript. *Server-side render is meant as a fallback; client-side rendering in JavaScript is always preferred (client rendering is faster and allows better editor manipulation).* diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md b/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md index 81c3fa0373a82b..b8fc6862dc69dc 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md @@ -5,7 +5,7 @@ It turns out that writing the simplest possible block which contains only static - [zgordon/gutenberg-course](https://github.com/zgordon/gutenberg-course) - a repository for Zac Gordon's Gutenberg Development Course - [ahmadawais/create-guten-block](https://github.com/ahmadawais/create-guten-block) - A zero-configuration developer toolkit for building WordPress Gutenberg block plugins -It might be also a good idea to browse the folder with [all core blocks](https://github.com/WordPress/gutenberg/tree/master/packages/block-library/src) to see how they are implemented. +It might be also a good idea to browse the folder with [all core blocks](/packages/block-library/src) to see how they are implemented. ## WP-CLI @@ -62,7 +62,7 @@ This will generate 4 files inside the `movies` plugin directory. All files conta * Registers all block assets so that they can be enqueued through Gutenberg in * the corresponding context. * - * @see https://wordpress.org/gutenberg/handbook/blocks/writing-your-first-block-type/#enqueuing-block-scripts + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type/ */ function movie_block_init() { $dir = dirname( __FILE__ ); @@ -109,23 +109,23 @@ add_action( 'init', 'movie_block_init' ); ( function( wp ) { /** * Registers a new block provided a unique name and an object defining its behavior. - * @see https://github.com/WordPress/gutenberg/tree/master/blocks#api + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/#registering-a-block */ var registerBlockType = wp.blocks.registerBlockType; /** * Returns a new element of given type. Element is an abstraction layer atop React. - * @see https://github.com/WordPress/gutenberg/tree/master/packages/element#element + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-element/ */ var el = wp.element.createElement; /** * Retrieves the translation of text. - * @see https://github.com/WordPress/gutenberg/tree/master/i18n#api + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-i18n/ */ var __ = wp.i18n.__; /** * Every block starts by registering a new block type definition. - * @see https://wordpress.org/gutenberg/handbook/block-api/ + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/#registering-a-block */ registerBlockType( 'movies/movie', { /** @@ -151,7 +151,7 @@ add_action( 'init', 'movie_block_init' ); /** * The edit function describes the structure of your block in the context of the editor. * This represents what the editor will render when the block is used. - * @see https://wordpress.org/gutenberg/handbook/block-edit-save/#edit + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#edit * * @param {Object} [props] Properties passed from the editor. * @return {Element} Element to render. @@ -167,7 +167,7 @@ add_action( 'init', 'movie_block_init' ); /** * The save function defines the way in which the different attributes should be combined * into the final markup, which is then serialized by Gutenberg into `post_content`. - * @see https://wordpress.org/gutenberg/handbook/block-edit-save/#save + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#save * * @return {Element} Element to render. */ diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md index 3a993ce1c2b71a..c51bd505bdd2e2 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md @@ -110,7 +110,7 @@ registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-03', { ``` {% end %} -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/designers-developers/developers/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, we will extract the `content` value as the HTML of the paragraph element in the saved post's markup. @@ -118,7 +118,21 @@ In the code snippet above, when loading the editor, we will extract the `content Earlier examples used the `createElement` function to create DOM nodes, but it's also possible to encapsulate this behavior into ["components"](). This abstraction helps as a pattern to share common behaviors and to hide complexity into self-contained units. There are a number of components available to use in implementing your blocks. You can see one such component in the snippet above: the [`RichText` component](). -The `RichText` component can be considered as a super-powered `textarea` element, enabling rich content editing including bold, italics, hyperlinks, etc. It is not too much unlike the single editor region of the legacy post editor, and is in fact powered by the same TinyMCE library. +The `RichText` component can be considered as a super-powered `textarea` element, enabling rich content editing including bold, italics, hyperlinks, etc. + +To use the `RichText` component, add `wp-editor` to the array of registered script handles when calling `wp_register_script`. + +```php +wp_register_script( + 'gutenberg-boilerplate-es5-step03', + plugins_url( 'step-03/block.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-element', + 'wp-editor', // Note the addition of wp-editor to the dependencies + ) +); +``` Implementing this behavior as a component enables you as the block implementer to be much more granular about editable fields. Your block may not need `RichText` at all, or it may need many independent `RichText` elements, each operating on a subset of the overall block state. diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md index e696b73e4e911d..ea9a18bbf23684 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md @@ -28,7 +28,7 @@ add_action( 'init', 'gutenberg_boilerplate_block' ); Note the two script dependencies: - __`wp-blocks`__ includes block type registration and related functions -- __`wp-element`__ includes the [WordPress Element abstraction](https://github.com/WordPress/gutenberg/tree/master/packages/element) for describing the structure of your blocks +- __`wp-element`__ includes the [WordPress Element abstraction](/packages/element/README.md) for describing the structure of your blocks If you were to use a component from the `wp-editor` package, for example the RichText component, you would also need to add `wp-editor` to the dependency list. @@ -82,7 +82,7 @@ registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-01', { ``` {% end %} -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](https://wordpress.org/gutenberg/handbook/block-api/#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/designers-developers/developers/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. diff --git a/docs/designers-developers/developers/tutorials/format-api/1-register-format.md b/docs/designers-developers/developers/tutorials/format-api/1-register-format.md new file mode 100644 index 00000000000000..6a18806f6a48a4 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/format-api/1-register-format.md @@ -0,0 +1,48 @@ +# Register a New Format + +The first thing you're going to do in this tutorial is to register the new format that the plugin intends to apply. WordPress has the [`registerFormatType`](/packages/rich-text/README.md#registerFormatType) function to do so. + +Let's prepare a minimal plugin to make this work. Create a new folder and a file named `my-custom-format.php` within it containing the necessary PHP code to register and enqueue the JavaScript assets: + +```php +` tag) may be considered binary - either a text selection has the tag or not. Taking that into account, the `toggleFormat` primitive seems more convenient. + +Update `my-custom-format.js` with this new code: + +```js +( function( wp ) { + var MyCustomButton = function( props ) { + return wp.element.createElement( + wp.editor.RichTextToolbarButton, { + icon: 'editor-code', + title: 'Sample output', + onClick: function() { + props.onChange( wp.richText.toggleFormat( + props.value, + { type: 'my-custom-format/sample-output' } + ) ); + }, + isActive: props.isActive, + } + ); + } + wp.richText.registerFormatType( + 'my-custom-format/sample-output', { + title: 'Sample output', + tagName: 'samp', + className: null, + edit: MyCustomButton, + } + ); +} )( window.wp ); +``` + +Now, let's check that is working as intended: reload the post/page, make a text selection, click the button, and then change to HTML view to confirm that the tag was effectively applied. + +The expected behavior is that the format will be toggled, meaning that the text selected will be wrapped by a `` tag if it isn't yet, or the tag will be removed if the selection is already wrapped with the tag. Notice that the button renders a different style depending on whether the selection has the tag or not as well - this is controlled by the `isActive` property of the `RichTextToolbarButton` component. + +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! diff --git a/docs/designers-developers/developers/tutorials/format-api/README.md b/docs/designers-developers/developers/tutorials/format-api/README.md new file mode 100644 index 00000000000000..3ecb76c07e0596 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/format-api/README.md @@ -0,0 +1,13 @@ +# Introduction to the Format API + +The purpose of this tutorial is to introduce you to the Format API. The Format API makes it possible for developers to add custom buttons to the formatting toolbar and have them apply a _format_ to a text selection. Bold is an example of a standard button in the formatting toolbar. + +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. + +## 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) diff --git a/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md b/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md new file mode 100644 index 00000000000000..024b1f50e93676 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md @@ -0,0 +1,66 @@ +# 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. + +Replace the existing `console.log()` code in your `myguten.js` file with: + +```js +wp.blocks.registerBlockStyle( 'core/quote', { + name: 'fancy-quote', + label: 'Fancy Quote' +} ); +``` + +**Important:** Notice that you are using a function from `wp.blocks` package. This means you must specify it as a dependency when you enqueue the script. Update the `myguten-plugin.php` file to: + +```php + Hola, mundo!, + save: () =>Hola, mundo!, +} ); +``` + +To configure npm to run a script, you use the scripts section in `package.json` webpack: + +```json + "scripts": { + "build": "wp-scripts build" + }, +``` + +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`. + +## Finishing Touches + +### Development Mode + +The **build** command in `@wordpress/scripts` runs in a "production" mode. This shrinks the code down so it downloads faster, but makes it difficult to read in the process. You can use the **start** command which runs a development mode that does not shrink the code, and additionally continues a running process to watch the source file for more changes and rebuild as you develop. + +The start command can be added to the same scripts section of `package.json`: + +```json + "scripts": { + "start": "wp-scripts start", + "build": "wp-scripts build" + }, +``` + +Now, when you run `npm start` a watcher will run in the terminal. You can then edit away in your text editor; after each save, it will automatically build. You can then use the familiar edit/save/reload development process. + +**Note:** keep an eye on your terminal for any errors. If you make a typo or syntax error, the build will fail and the error will be in the terminal. + + +### Source Control + +Because a typical `node_modules` folder will contain thousands of files that change with every software update, you should exclude `node_modules/` from your source control. If you ever start from a fresh clone, simply run `npm install` in the same folder your `package.json` is located to pull your required packages. + +Likewise, you do not need to include `node_modules` or any of the above configuration files in your plugin because they will be bundled inside the file that webpack builds. **Be sure to enqueue the `build/index.js` file** in your plugin PHP. This is the only JavaScript file needed for your block to run. + +## Summary + +Yes, the initial setup is a bit more involved, but the additional features and benefits are usually worth the trade off in setup time. + +With a setup in place, the standard workflow is: + +- Install dependencies: `npm install` +- Start development builds: `npm start` +- Develop. Test. Repeat. +- Create production build: `npm run build` diff --git a/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md b/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md new file mode 100644 index 00000000000000..f48775df5782d9 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md @@ -0,0 +1,49 @@ +# Loading JavaScript + +With the plugin in place, you can add the code that loads the JavaScript. This methodology follows the standard WordPress procedure of enqueuing scripts, see [enqueuing section of the Plugin Handbook](https://developer.wordpress.org/plugins/javascript/enqueuing/). + +Add the following code to your `myguten-plugin.php` file: + +```php +function myguten_enqueue() { + wp_enqueue_script( + 'myguten-script', + plugins_url( 'myguten.js', __FILE__ ) + ); +} +add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); +``` + +The `enqueue_block_editor_assets` hook is used, which is called when the block editor loads, and will enqueue the JavaScript file `myguten.js`. + +Create a file called `myguten.js` and add: + +```js +console.log( "I'm loaded!" ); +``` + +Next, create a new post in the block editor. + +We'll check the JavaScript console in your browser's Developer Tools, to see if the message is displayed. If you're not sure what developer tools are, Mozilla's ["What are browser developer tools?"](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools) documentation provides more information, including more background on the [JavaScript console](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools#The_JavaScript_console). + +If your code is registered and enqueued correctly, you should see a message in your console: + + + +**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: + +```php +function myguten_enqueue() { + wp_enqueue_script( + 'myguten-script', + get_template_directory_uri() . '/myguten.js' + ); +} +add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); +``` + +### Recap + +At this point, you have a plugin in the directory `wp-content/plugins/myguten-plugin` with two files: the PHP server-side code in `myguten-plugin.php`, and the JavaScript which runs in the browser in `myguten.js`. + +This puts all the initial pieces in place for you to start extending the block editor. diff --git a/docs/designers-developers/developers/tutorials/javascript/plugins-background.md b/docs/designers-developers/developers/tutorials/javascript/plugins-background.md new file mode 100644 index 00000000000000..91a6ee302fe758 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/javascript/plugins-background.md @@ -0,0 +1,16 @@ +# Plugins Background + +The primary means of extending WordPress is the plugin. WordPress' [Plugin Basics](https://developer.wordpress.org/plugins/the-basics/) documentation provides for more details on building a plugin. The quickest way to start is to create a new directory in `wp-content/plugins/` to hold your plugin code, for this example you can call it `myguten-plugin`. + +Inside of this new directory, create a file called `myguten-plugin.php` which is the server-side code that runs when your plugin is active. For now place the following in that file: + +```php + Historically, JavaScript files loaded in a web page share the same scope. + +Notice the _historically_. + +JavaScript has evolved quite a bit since its creation. As of 2015, the language supports modules, also known as _ES6 modules_, that introduce separate scope per file: a global variable in `first.js` wouldn't be exposed to `second.js`. This feature is already [supported by modern browsers](https://caniuse.com/#feat=es6-module), but not all of them do. If your code needs to run in browsers that don't support modules, your last resort is using IIFEs. diff --git a/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md b/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md new file mode 100644 index 00000000000000..c5b6d9895c91b5 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md @@ -0,0 +1,34 @@ +# Troubleshooting + +If you're having trouble getting your code to work, here are a few ways to troubleshoot. + +## Console Log + +The console log is a JavaScript developer's best friend. It is a good practice to work with it open, as it collects errors and notices into one place. See Mozilla's [JavaScript console](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools#The_JavaScript_console) documentation for more. + +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. + + + +## Confirm JavaScript is Loading + +If you are not seeing your changes, check that your JavaScript file is being enqueued. Open the page source in your browser's web inspector (some browsers may allow you to view the page source by right clicking on the page and selecting "View Page Source"), and look for the ` - is_block_editor(); } - -/** - * Adds attributes to kses allowed tags that aren't in the default list - * and that Gutenberg needs to save blocks such as the Gallery block. - * - * @param array $tags Allowed HTML. - * @return array (Maybe) modified allowed HTML. - */ -function gutenberg_kses_allowedtags( $tags ) { - if ( isset( $tags['img'] ) ) { - $tags['img']['data-link'] = true; - $tags['img']['data-id'] = true; - } - return $tags; -} - -add_filter( 'wp_kses_allowed_html', 'gutenberg_kses_allowedtags', 10, 2 ); - -/** - * Adds the wp-embed-responsive class to the body tag if the theme has opted in to - * Gutenberg responsive embeds. - * - * @since 4.1.0 - * - * @param Array $classes Array of classes being added to the body tag. - * @return Array The $classes array, with wp-embed-responsive appended. - */ -function gutenberg_add_responsive_body_class( $classes ) { - if ( current_theme_supports( 'responsive-embeds' ) ) { - $classes[] = 'wp-embed-responsive'; - } - return $classes; -} - -add_filter( 'body_class', 'gutenberg_add_responsive_body_class' ); diff --git a/jsconfig.json b/jsconfig.json index 7e800a9c053b43..51f5ce09b49e00 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -9,7 +9,7 @@ "build", "build-module", "node_modules", - "test/e2e/test-plugins", + "packages/e2e-tests/plugins", "vendor" ] } diff --git a/languages/README.md b/languages/README.md deleted file mode 100644 index 3f3ba1d4478c68..00000000000000 --- a/languages/README.md +++ /dev/null @@ -1,10 +0,0 @@ -Languages -========= - -The generated POT template file is not included in this repository. To create this file locally, follow instructions from [CONTRIBUTING.md](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) to install the project, then run the following command: - -``` -npm run build -``` - -After the build completes, you'll find a `gutenberg.pot` strings file in this directory. diff --git a/lib/blocks.php b/lib/blocks.php deleted file mode 100644 index 0b11041d0576de..00000000000000 --- a/lib/blocks.php +++ /dev/null @@ -1,271 +0,0 @@ -register( $name, $args ); - } -} - -if ( ! function_exists( 'unregister_block_type' ) ) { - /** - * Unregisters a block type. - * - * @since 0.1.0 - * @since 0.6.0 Now also accepts a WP_Block_Type instance as first parameter. - * - * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a - * complete WP_Block_Type instance. - * @return WP_Block_Type|false The unregistered block type on success, or false on failure. - */ - function unregister_block_type( $name ) { - return WP_Block_Type_Registry::get_instance()->unregister( $name ); - } -} - -if ( ! function_exists( 'gutenberg_parse_blocks' ) ) { - /** - * Parses blocks out of a content string. - * - * @since 0.5.0 - * - * @param string $content Post content. - * @return array Array of parsed block objects. - */ - function gutenberg_parse_blocks( $content ) { - /** - * Filter to allow plugins to replace the server-side block parser - * - * @since 3.8.0 - * - * @param string $parser_class Name of block parser class - */ - $parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' ); - // Load default block parser for server-side parsing if the default parser class is being used. - if ( 'WP_Block_Parser' === $parser_class ) { - require_once dirname( __FILE__ ) . '/../packages/block-serialization-default-parser/parser.php'; - } - $parser = new $parser_class(); - return $parser->parse( $content ); - } -} - -if ( ! function_exists( 'get_dynamic_block_names' ) ) { - /** - * Returns an array of the names of all registered dynamic block types. - * - * @return array Array of dynamic block names. - */ - function get_dynamic_block_names() { - $dynamic_block_names = array(); - - $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered(); - foreach ( $block_types as $block_type ) { - if ( $block_type->is_dynamic() ) { - $dynamic_block_names[] = $block_type->name; - } - } - - return $dynamic_block_names; - } -} - -if ( ! function_exists( 'get_dynamic_blocks_regex' ) ) { - /** - * Retrieve the dynamic blocks regular expression for searching. - * - * @since 3.6.0 - * - * @return string - */ - function get_dynamic_blocks_regex() { - $dynamic_block_names = get_dynamic_block_names(); - $dynamic_block_pattern = ( - '//' - ); - - return $dynamic_block_pattern; - } -} - -/** - * Renders a single block into a HTML string. - * - * @since 1.9.0 - * @since 4.4.0 renders full nested tree of blocks before reassembling into HTML string - * @global WP_Post $post The post to edit. - * - * @param array $block A single parsed block object. - * @return string String of rendered HTML. - */ -function gutenberg_render_block( $block ) { - global $post; - - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - $is_dynamic = $block['blockName'] && null !== $block_type && $block_type->is_dynamic(); - $inner_content = ''; - $index = 0; - - foreach ( $block['innerContent'] as $chunk ) { - $inner_content .= is_string( $chunk ) ? $chunk : gutenberg_render_block( $block['innerBlocks'][ $index++ ] ); - } - - if ( $is_dynamic ) { - $attributes = is_array( $block['attrs'] ) ? (array) $block['attrs'] : array(); - $global_post = $post; - $output = $block_type->render( $attributes, $inner_content ); - $post = $global_post; - - return $output; - } - - return $inner_content; -} - -if ( ! function_exists( 'do_blocks' ) ) { - /** - * Parses dynamic blocks out of `post_content` and re-renders them. - * - * @since 0.1.0 - * @since 4.4.0 performs full parse on input post content - * - * @param string $content Post content. - * @return string Updated post content. - */ - function do_blocks( $content ) { - // If there are blocks in this content, we shouldn't run wpautop() on it later. - $priority = has_filter( 'the_content', 'wpautop' ); - if ( false !== $priority && doing_filter( 'the_content' ) && has_blocks( $content ) ) { - remove_filter( 'the_content', 'wpautop', $priority ); - add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 ); - } - - $blocks = gutenberg_parse_blocks( $content ); - $output = ''; - - foreach ( $blocks as $block ) { - $output .= gutenberg_render_block( $block ); - } - - return $output; - } - - add_filter( 'the_content', 'do_blocks', 7 ); // BEFORE do_shortcode() and oembed. -} - -if ( ! function_exists( '_restore_wpautop_hook' ) ) { - /** - * If do_blocks() needs to remove wpautop() from the `the_content` filter, - * this re-adds it afterwards, for subsequent `the_content` usage. - * - * @access private - * - * @since 4.6.0 - * - * @param string $content The post content running through this filter. - * @return string The unmodified content. - */ - function _restore_wpautop_hook( $content ) { - $current_priority = has_filter( 'the_content', '_restore_wpautop_hook' ); - - add_filter( 'the_content', 'wpautop', $current_priority - 1 ); - remove_filter( 'the_content', '_restore_wpautop_hook', $current_priority ); - - return $content; - } -} - -if ( ! function_exists( 'strip_dynamic_blocks' ) ) { - /** - * Remove all dynamic blocks from the given content. - * - * @since 3.6.0 - * - * @param string $content Content of the current post. - * @return string - */ - function strip_dynamic_blocks( $content ) { - return preg_replace( get_dynamic_blocks_regex(), '', $content ); - } -} - -if ( ! function_exists( 'strip_dynamic_blocks_add_filter' ) ) { - /** - * Adds the content filter to strip dynamic blocks from excerpts. - * - * It's a bit hacky for now, but once this gets merged into core the function - * can just be called in `wp_trim_excerpt()`. - * - * @since 3.6.0 - * - * @param string $text Excerpt. - * @return string - */ - function strip_dynamic_blocks_add_filter( $text ) { - add_filter( 'the_content', 'strip_dynamic_blocks', 6 ); - - return $text; - } - add_filter( 'get_the_excerpt', 'strip_dynamic_blocks_add_filter', 9 ); // Before wp_trim_excerpt(). -} - -if ( ! function_exists( 'strip_dynamic_blocks_remove_filter' ) ) { - /** - * Removes the content filter to strip dynamic blocks from excerpts. - * - * It's a bit hacky for now, but once this gets merged into core the function - * can just be called in `wp_trim_excerpt()`. - * - * @since 3.6.0 - * - * @param string $text Excerpt. - * @return string - */ - function strip_dynamic_blocks_remove_filter( $text ) { - remove_filter( 'the_content', 'strip_dynamic_blocks', 6 ); - - return $text; - } - add_filter( 'wp_trim_excerpt', 'strip_dynamic_blocks_remove_filter', 0 ); // Before all other. -} diff --git a/lib/class-wp-block-type-registry.php b/lib/class-wp-block-type-registry.php deleted file mode 100644 index f95dec6ce4b9d1..00000000000000 --- a/lib/class-wp-block-type-registry.php +++ /dev/null @@ -1,182 +0,0 @@ - $instance` pairs. - * - * @since 0.6.0 - * @access private - * @var WP_Block_Type[] - */ - private $registered_block_types = array(); - - /** - * Container for the main instance of the class. - * - * @since 0.6.0 - * @access private - * @static - * @var WP_Block_Type_Registry|null - */ - private static $instance = null; - - /** - * Registers a block type. - * - * @since 0.6.0 - * @access public - * - * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a - * complete WP_Block_Type instance. In case a WP_Block_Type - * is provided, the $args parameter will be ignored. - * @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. - * @type array $attributes Block attributes mapping, property name to schema. - * } - * @return WP_Block_Type|false The registered block type on success, or false on failure. - */ - public function register( $name, $args = array() ) { - $block_type = null; - if ( $name instanceof WP_Block_Type ) { - $block_type = $name; - $name = $block_type->name; - } - - if ( ! is_string( $name ) ) { - $message = __( 'Block type names must be strings.', 'gutenberg' ); - _doing_it_wrong( __METHOD__, $message, '0.1.0' ); - return false; - } - - if ( preg_match( '/[A-Z]+/', $name ) ) { - $message = __( 'Block type names must not contain uppercase characters.', 'gutenberg' ); - _doing_it_wrong( __METHOD__, $message, '1.5.0' ); - return false; - } - - $name_matcher = '/^[a-z0-9-]+\/[a-z0-9-]+$/'; - if ( ! preg_match( $name_matcher, $name ) ) { - $message = __( 'Block type names must contain a namespace prefix. Example: my-plugin/my-custom-block-type', 'gutenberg' ); - _doing_it_wrong( __METHOD__, $message, '0.1.0' ); - return false; - } - - if ( $this->is_registered( $name ) ) { - /* translators: 1: block name */ - $message = sprintf( __( 'Block type "%s" is already registered.', 'gutenberg' ), $name ); - _doing_it_wrong( __METHOD__, $message, '0.1.0' ); - return false; - } - - if ( ! $block_type ) { - $block_type = new WP_Block_Type( $name, $args ); - } - - $this->registered_block_types[ $name ] = $block_type; - - return $block_type; - } - - /** - * Unregisters a block type. - * - * @since 0.6.0 - * @access public - * - * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a - * complete WP_Block_Type instance. - * @return WP_Block_Type|false The unregistered block type on success, or false on failure. - */ - public function unregister( $name ) { - if ( $name instanceof WP_Block_Type ) { - $name = $name->name; - } - - if ( ! $this->is_registered( $name ) ) { - /* translators: 1: block name */ - $message = sprintf( __( 'Block type "%s" is not registered.', 'gutenberg' ), $name ); - _doing_it_wrong( __METHOD__, $message, '0.1.0' ); - return false; - } - - $unregistered_block_type = $this->registered_block_types[ $name ]; - unset( $this->registered_block_types[ $name ] ); - - return $unregistered_block_type; - } - - /** - * Retrieves a registered block type. - * - * @since 0.6.0 - * @access public - * - * @param string $name Block type name including namespace. - * @return WP_Block_Type|null The registered block type, or null if it is not registered. - */ - public function get_registered( $name ) { - if ( ! $this->is_registered( $name ) ) { - return null; - } - - return $this->registered_block_types[ $name ]; - } - - /** - * Retrieves all registered block types. - * - * @since 0.6.0 - * @access public - * - * @return WP_Block_Type[] Associative array of `$block_type_name => $block_type` pairs. - */ - public function get_all_registered() { - return $this->registered_block_types; - } - - /** - * Checks if a block type is registered. - * - * @since 0.6.0 - * @access public - * - * @param string $name Block type name including namespace. - * @return bool True if the block type is registered, false otherwise. - */ - public function is_registered( $name ) { - return isset( $this->registered_block_types[ $name ] ); - } - - /** - * Utility method to retrieve the main instance of the class. - * - * The instance will be created if it does not exist yet. - * - * @since 0.6.0 - * @access public - * @static - * - * @return WP_Block_Type_Registry The main instance. - */ - public static function get_instance() { - if ( null === self::$instance ) { - self::$instance = new self(); - } - - return self::$instance; - } -} diff --git a/lib/class-wp-block-type.php b/lib/class-wp-block-type.php deleted file mode 100644 index c186eec88a0a3b..00000000000000 --- a/lib/class-wp-block-type.php +++ /dev/null @@ -1,209 +0,0 @@ -name = $block_type; - - $this->set_props( $args ); - } - - /** - * Renders the block type output for given attributes. - * - * @since 0.6.0 - * - * @param array $attributes Optional. Block attributes. Default empty array. - * @param string $content Optional. Block content. Default empty string. - * @return string Rendered block type output. - */ - public function render( $attributes = array(), $content = '' ) { - if ( ! $this->is_dynamic() ) { - return ''; - } - - $attributes = $this->prepare_attributes_for_render( $attributes ); - - return (string) call_user_func( $this->render_callback, $attributes, $content ); - } - - /** - * Returns true if the block type is dynamic, or false otherwise. A dynamic - * block is one which defers its rendering to occur on-demand at runtime. - * - * @return boolean Whether block type is dynamic. - */ - public function is_dynamic() { - return is_callable( $this->render_callback ); - } - - /** - * Validates attributes against the current block schema, populating - * defaulted and missing values. - * - * @param array $attributes Original block attributes. - * @return array Prepared block attributes. - */ - public function prepare_attributes_for_render( $attributes ) { - // If there are no attribute definitions for the block type, skip - // processing and return vebatim. - if ( ! isset( $this->attributes ) ) { - return $attributes; - } - - foreach ( $attributes as $attribute_name => $value ) { - // If the attribute is not defined by the block type, it cannot be - // validated. - if ( ! isset( $this->attributes[ $attribute_name ] ) ) { - continue; - } - - $schema = $this->attributes[ $attribute_name ]; - - // Validate value by JSON schema. An invalid value should revert to - // its default, if one exists. This occurs by virtue of the missing - // attributes loop immediately following. If there is not a default - // assigned, the attribute value should remain unset. - $is_valid = rest_validate_value_from_schema( $value, $schema ); - if ( is_wp_error( $is_valid ) ) { - unset( $attributes[ $attribute_name ] ); - } - } - - // Populate values of any missing attributes for which the block type - // defines a default. - $missing_schema_attributes = array_diff_key( $this->attributes, $attributes ); - foreach ( $missing_schema_attributes as $attribute_name => $schema ) { - if ( isset( $schema['default'] ) ) { - $attributes[ $attribute_name ] = $schema['default']; - } - } - - return $attributes; - } - - /** - * Sets block type properties. - * - * @since 0.6.0 - * - * @param array|string $args Array or string of arguments for registering a block type. - */ - public function set_props( $args ) { - $args = wp_parse_args( - $args, - array( - 'render_callback' => null, - ) - ); - - $args['name'] = $this->name; - - foreach ( $args as $property_name => $property_value ) { - $this->$property_name = $property_value; - } - } - - /** - * Get all available block attributes including possible layout attribute from Columns block. - * - * @return array Array of attributes. - */ - public function get_attributes() { - return is_array( $this->attributes ) ? - array_merge( - $this->attributes, - array( - 'layout' => array( - 'type' => 'string', - ), - ) - ) : - array( - 'layout' => array( - 'type' => 'string', - ), - ); - } -} diff --git a/lib/class-wp-rest-autosaves-controller.php b/lib/class-wp-rest-autosaves-controller.php deleted file mode 100644 index 6f6496c35790e3..00000000000000 --- a/lib/class-wp-rest-autosaves-controller.php +++ /dev/null @@ -1,426 +0,0 @@ -parent_post_type = $parent_post_type; - $post_type_object = get_post_type_object( $parent_post_type ); - - // Ensure that post type-specific controller logic is available. - $parent_controller_class = ! empty( $post_type_object->rest_controller_class ) ? $post_type_object->rest_controller_class : 'WP_REST_Posts_Controller'; - - $this->parent_controller = new $parent_controller_class( $post_type_object->name ); - $this->revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type ); - $this->rest_namespace = 'wp/v2'; - $this->rest_base = 'autosaves'; - $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; - } - - /** - * Registers routes for autosaves. - * - * @since 5.0.0 - * - * @see register_rest_route() - */ - public function register_routes() { - register_rest_route( - $this->rest_namespace, - '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base, - array( - 'args' => array( - 'parent' => array( - 'description' => __( 'The ID for the parent of the object.', 'gutenberg' ), - 'type' => 'integer', - ), - ), - 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->parent_controller->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - - register_rest_route( - $this->rest_namespace, - '/' . $this->parent_base . '/(?P [\d]+)/' . $this->rest_base . '/(?P [\d]+)', - array( - 'args' => array( - 'parent' => array( - 'description' => __( 'The ID for the parent of the object.', 'gutenberg' ), - 'type' => 'integer', - ), - 'id' => array( - 'description' => __( 'The ID for the object.', 'gutenberg' ), - 'type' => 'integer', - ), - ), - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ), - 'args' => array( - 'context' => $this->get_context_param( array( 'default' => 'view' ) ), - ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - - } - - /** - * Get the parent post. - * - * @since 5.0.0 - * - * @param int $parent_id Supplied ID. - * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. - */ - protected function get_parent( $parent_id ) { - return $this->revisions_controller->get_parent( $parent_id ); - } - - /** - * Checks if a given request has access to get autosaves. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full data 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 ) { - $parent = $this->get_parent( $request['id'] ); - if ( is_wp_error( $parent ) ) { - return $parent; - } - - $parent_post_type_obj = get_post_type_object( $parent->post_type ); - if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) { - return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to view revisions of this post.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - - /** - * Checks if a given request has access to create an autosave revision. - * - * Autosave revisions inherit permissions from the parent post, - * check if the current user has permission to edit the post. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise. - */ - public function create_item_permissions_check( $request ) { - $id = $request->get_param( 'id' ); - if ( empty( $id ) ) { - return new WP_Error( 'rest_post_invalid_id', __( 'Invalid item ID.', 'gutenberg' ), array( 'status' => 404 ) ); - } - - return $this->parent_controller->update_item_permissions_check( $request ); - } - - /** - * Creates, updates or deletes an autosave revision. - * - * @since 5.0.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 ) { - - if ( ! defined( 'DOING_AUTOSAVE' ) ) { - define( 'DOING_AUTOSAVE', true ); - } - - $post = get_post( $request['id'] ); - - if ( is_wp_error( $post ) ) { - return $post; - } - - $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); - $prepared_post->ID = $post->ID; - $user_id = get_current_user_id(); - - if ( ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) && $post->post_author == $user_id ) { - // Draft posts for the same author: autosaving updates the post and does not create a revision. - // Convert the post object to an array and add slashes, wp_update_post expects escaped array. - $autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true ); - } else { - // Non-draft posts: create or update the post autosave. - $autosave_id = $this->create_post_autosave( (array) $prepared_post ); - } - - if ( is_wp_error( $autosave_id ) ) { - return $autosave_id; - } - - $autosave = get_post( $autosave_id ); - $request->set_param( 'context', 'edit' ); - - $response = $this->prepare_item_for_response( $autosave, $request ); - $response = rest_ensure_response( $response ); - - return $response; - } - - /** - * Get the autosave, if the ID is valid. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full data about the request. - * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. - */ - public function get_item( $request ) { - $parent_id = (int) $request->get_param( 'parent' ); - - if ( $parent_id <= 0 ) { - return new WP_Error( 'rest_post_invalid_id', __( 'Invalid parent post ID.', 'gutenberg' ), array( 'status' => 404 ) ); - } - - $autosave = wp_get_post_autosave( $parent_id ); - - if ( ! $autosave ) { - return new WP_Error( 'rest_post_no_autosave', __( 'There is no autosave revision for this post.', 'gutenberg' ), array( 'status' => 404 ) ); - } - - $response = $this->prepare_item_for_response( $autosave, $request ); - return $response; - } - - /** - * Gets a collection of autosaves using wp_get_post_autosave. - * - * Contains the user's autosave, for empty if it doesn't exist. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full data about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function get_items( $request ) { - $parent = $this->get_parent( $request['id'] ); - if ( is_wp_error( $parent ) ) { - return $parent; - } - - $response = array(); - $parent_id = $parent->ID; - $revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) ); - - foreach ( $revisions as $revision ) { - if ( false !== strpos( $revision->post_name, "{$parent_id}-autosave" ) ) { - $data = $this->prepare_item_for_response( $revision, $request ); - $response[] = $this->prepare_response_for_collection( $data ); - } - } - - return rest_ensure_response( $response ); - } - - - /** - * Retrieves the autosave's schema, conforming to JSON Schema. - * - * @since 5.0.0 - * - * @return array Item schema data. - */ - public function get_item_schema() { - $schema = $this->revisions_controller->get_item_schema(); - - $schema['properties']['preview_link'] = array( - 'description' => __( 'Preview link for the post.', 'gutenberg' ), - 'type' => 'string', - 'format' => 'uri', - 'context' => array( 'edit' ), - 'readonly' => true, - ); - - return $schema; - } - - /** - * Creates autosave for the specified post. - * - * From wp-admin/post.php. - * - * @since 5.0.0 - * - * @param mixed $post_data Associative array containing the post data. - * @return mixed The autosave revision ID or WP_Error. - */ - public function create_post_autosave( $post_data ) { - - $post_id = (int) $post_data['ID']; - $post = get_post( $post_id ); - - if ( is_wp_error( $post ) ) { - return $post; - } - - $user_id = get_current_user_id(); - - // Store one autosave per author. If there is already an autosave, overwrite it. - $old_autosave = wp_get_post_autosave( $post_id, $user_id ); - - if ( $old_autosave ) { - $new_autosave = _wp_post_revision_data( $post_data, true ); - $new_autosave['ID'] = $old_autosave->ID; - $new_autosave['post_author'] = $user_id; - - // If the new autosave has the same content as the post, delete the autosave. - $autosave_is_different = false; - - foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) { - if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) { - $autosave_is_different = true; - break; - } - } - - if ( ! $autosave_is_different ) { - wp_delete_post_revision( $old_autosave->ID ); - return new WP_Error( 'rest_autosave_no_changes', __( 'There is nothing to save. The autosave and the post content are the same.', 'gutenberg' ), array( 'status' => 400 ) ); - } - - /** - * This filter is documented in wp-admin/post.php. - */ - do_action( 'wp_creating_autosave', $new_autosave ); - - // wp_update_post expects escaped array. - return wp_update_post( wp_slash( $new_autosave ) ); - } - - // Create the new autosave as a special post revision. - return _wp_put_post_revision( $post_data, true ); - } - - /** - * Prepares the revision for the REST response. - * - * @since 5.0.0 - * - * @param WP_Post $post Post revision object. - * @param WP_REST_Request $request Request object. - * - * @return WP_REST_Response Response object. - */ - public function prepare_item_for_response( $post, $request ) { - - $response = $this->revisions_controller->prepare_item_for_response( $post, $request ); - - $schema = $this->get_item_schema(); - - if ( ! empty( $schema['properties']['preview_link'] ) ) { - $parent_id = wp_is_post_autosave( $post ); - $preview_post_id = false === $parent_id ? $post->ID : $parent_id; - $preview_query_args = array(); - - if ( false !== $parent_id ) { - $preview_query_args['preview_id'] = $parent_id; - $preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id ); - } - - $response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args ); - } - - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $response->data = $this->filter_response_by_context( $response->data, $context ); - - /** - * Filters a revision returned from the API. - * - * Allows modification of the revision right before it is returned. - * - * @since 5.0.0 - * - * @param WP_REST_Response $response The response object. - * @param WP_Post $post The original revision object. - * @param WP_REST_Request $request Request used to generate the response. - */ - return apply_filters( 'rest_prepare_autosave', $response, $post, $request ); - } - - /** - * Retrieves the query params for the autosaves collection. - * - * @since 5.0.0 - * - * @return array Collection parameters. - */ - public function get_collection_params() { - return array( - 'context' => $this->get_context_param( array( 'default' => 'view' ) ), - ); - } -} diff --git a/lib/class-wp-rest-block-renderer-controller.php b/lib/class-wp-rest-block-renderer-controller.php deleted file mode 100644 index b9839f080376a2..00000000000000 --- a/lib/class-wp-rest-block-renderer-controller.php +++ /dev/null @@ -1,178 +0,0 @@ -namespace = 'wp/v2'; - $this->rest_base = 'block-renderer'; - } - - /** - * Registers the necessary REST API routes, one for each dynamic block. - * - * @access public - */ - public function register_routes() { - $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered(); - foreach ( $block_types as $block_type ) { - if ( ! $block_type->is_dynamic() ) { - continue; - } - - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/(?P ' . $block_type->name . ')', - array( - 'args' => array( - 'name' => array( - 'description' => __( 'Unique registered name for the block.', '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' ) ), - 'attributes' => array( - /* translators: %s is the name of the block */ - 'description' => sprintf( __( 'Attributes for %s block', 'gutenberg' ), $block_type->name ), - 'type' => 'object', - 'additionalProperties' => false, - 'properties' => $block_type->get_attributes(), - 'default' => array(), - ), - 'post_id' => array( - 'description' => __( 'ID of the post context.', 'gutenberg' ), - 'type' => 'integer', - ), - ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - } - } - - /** - * Checks if a given request has access to read blocks. - * - * @since 2.8.0 - * @access public - * - * @param WP_REST_Request $request Request. - * @return true|WP_Error True if the request has read access, WP_Error object otherwise. - */ - public function get_item_permissions_check( $request ) { - global $post; - - $post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0; - - if ( 0 < $post_id ) { - $post = get_post( $post_id ); - if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) { - return new WP_Error( - 'gutenberg_block_cannot_read', - __( 'Sorry, you are not allowed to read Gutenberg blocks of this post.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); - } - } else { - if ( ! current_user_can( 'edit_posts' ) ) { - return new WP_Error( - 'gutenberg_block_cannot_read', - __( 'Sorry, you are not allowed to read Gutenberg blocks as this user.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); - } - } - - return true; - } - - /** - * Returns block output from block's registered render_callback. - * - * @since 2.8.0 - * @access public - * - * @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 ) { - global $post; - - $post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0; - - if ( 0 < $post_id ) { - $post = get_post( $post_id ); - - // Set up postdata since this will be needed if post_id was set. - setup_postdata( $post ); - } - $registry = WP_Block_Type_Registry::get_instance(); - $block = $registry->get_registered( $request['name'] ); - - if ( null === $block ) { - return new WP_Error( - 'gutenberg_block_invalid', - __( 'Invalid block.', 'gutenberg' ), - array( - 'status' => 404, - ) - ); - } - - $data = array( - 'rendered' => $block->render( $request->get_param( 'attributes' ) ), - ); - return rest_ensure_response( $data ); - } - - /** - * Retrieves block's output schema, conforming to JSON Schema. - * - * @since 2.8.0 - * @access public - * - * @return array Item schema data. - */ - public function get_item_schema() { - return array( - '$schema' => 'http://json-schema.org/schema#', - 'title' => 'rendered-block', - 'type' => 'object', - 'properties' => array( - 'rendered' => array( - 'description' => __( 'The rendered block.', 'gutenberg' ), - 'type' => 'string', - 'required' => true, - 'context' => array( 'edit' ), - ), - ), - ); - } -} diff --git a/lib/class-wp-rest-blocks-controller.php b/lib/class-wp-rest-blocks-controller.php deleted file mode 100644 index 47882fafbff042..00000000000000 --- a/lib/class-wp-rest-blocks-controller.php +++ /dev/null @@ -1,89 +0,0 @@ -post_type ); - if ( ! current_user_can( $post_type->cap->read_post, $post->ID ) ) { - return false; - } - - return parent::check_read_permission( $post ); - } - - /** - * Filters a response based on the context defined in the schema. - * - * @since 4.4.0 - * - * @param array $data Response data to fiter. - * @param string $context Context defined in the schema. - * @return array Filtered response. - */ - public function filter_response_by_context( $data, $context ) { - $data = parent::filter_response_by_context( $data, $context ); - - /* - * Remove `title.rendered` and `content.rendered` from the response. It - * doesn't make sense for a reusable block to have rendered content on its - * own, since rendering a block requires it to be inside a post or a page. - */ - unset( $data['title']['rendered'] ); - unset( $data['content']['rendered'] ); - - return $data; - } - - /** - * Retrieves the block's schema, conforming to JSON Schema. - * - * @since 4.4.0 - * - * @return array Item schema data. - */ - public function get_item_schema() { - $schema = parent::get_item_schema(); - - /* - * Allow all contexts to access `title.raw` and `content.raw`. Clients always - * need the raw markup of a reusable block to do anything useful, e.g. parse - * it or display it in an editor. - */ - $schema['properties']['title']['properties']['raw']['context'] = array( 'view', 'edit' ); - $schema['properties']['content']['properties']['raw']['context'] = array( 'view', 'edit' ); - - /* - * Remove `title.rendered` and `content.rendered` from the schema. It doesn’t - * make sense for a reusable block to have rendered content on its own, since - * rendering a block requires it to be inside a post or a page. - */ - unset( $schema['properties']['title']['properties']['rendered'] ); - unset( $schema['properties']['content']['properties']['rendered'] ); - - return $schema; - } - -} diff --git a/lib/class-wp-rest-post-search-handler.php b/lib/class-wp-rest-post-search-handler.php deleted file mode 100644 index 64d19174320121..00000000000000 --- a/lib/class-wp-rest-post-search-handler.php +++ /dev/null @@ -1,190 +0,0 @@ -type = 'post'; - - // Support all public post types except attachments. - $this->subtypes = array_diff( - array_values( - get_post_types( - array( - 'public' => true, - 'show_in_rest' => true, - ), - 'names' - ) - ), - array( 'attachment' ) - ); - } - - /** - * Searches the object type content for a given search request. - * - * @since 3.3.0 - * - * @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 ) { - - // Get the post types to search for the current request. - $post_types = $request[ WP_REST_Search_Controller::PROP_SUBTYPE ]; - if ( in_array( WP_REST_Search_Controller::TYPE_ANY, $post_types, true ) ) { - $post_types = $this->subtypes; - } - - $query_args = array( - 'post_type' => $post_types, - 'post_status' => 'publish', - 'paged' => (int) $request['page'], - 'posts_per_page' => (int) $request['per_page'], - 'ignore_sticky_posts' => true, - 'fields' => 'ids', - ); - - if ( ! empty( $request['search'] ) ) { - $query_args['s'] = $request['search']; - } - - $query = new WP_Query(); - $found_ids = $query->query( $query_args ); - $total = $query->found_posts; - - return array( - self::RESULT_IDS => $found_ids, - self::RESULT_TOTAL => $total, - ); - } - - /** - * Prepares the search result for a given ID. - * - * @since 3.3.0 - * - * @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 ) { - $post = get_post( $id ); - - $data = array(); - - if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_ID ] = (int) $post->ID; - } - - if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) { - if ( post_type_supports( $post->post_type, 'title' ) ) { - add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); - $data[ WP_REST_Search_Controller::PROP_TITLE ] = get_the_title( $post->ID ); - remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); - } else { - $data[ WP_REST_Search_Controller::PROP_TITLE ] = ''; - } - } - - if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_URL ] = get_permalink( $post->ID ); - } - - if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_TYPE ] = $this->type; - } - - if ( in_array( WP_REST_Search_Controller::PROP_SUBTYPE, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_SUBTYPE ] = $post->post_type; - } - - return $data; - } - - /** - * Prepares links for the search result of a given ID. - * - * @since 3.3.0 - * - * @param int $id Item ID. - * @return array Links for the given item. - */ - public function prepare_item_links( $id ) { - $post = get_post( $id ); - - $links = array(); - - $item_route = $this->detect_rest_item_route( $post ); - if ( ! empty( $item_route ) ) { - $links['self'] = array( - 'href' => rest_url( $item_route ), - 'embeddable' => true, - ); - } - - $links['about'] = array( - 'href' => rest_url( 'wp/v2/types/' . $post->post_type ), - ); - - return $links; - } - - /** - * Overwrites the default protected title format. - * - * By default, WordPress will show password protected posts with a title of - * "Protected: %s". As the REST API communicates the protected status of a post - * in a machine readable format, we remove the "Protected: " prefix. - * - * @since 3.3.0 - * - * @return string Protected title format. - */ - public function protected_title_format() { - return '%s'; - } - - /** - * Attempts to detect the route to access a single item. - * - * @since 3.3.0 - * - * @param WP_Post $post Post object. - * @return string REST route relative to the REST base URI, or empty string if unknown. - */ - protected function detect_rest_item_route( $post ) { - $post_type = get_post_type_object( $post->post_type ); - if ( ! $post_type ) { - return ''; - } - - // It's currently impossible to detect the REST URL from a custom controller. - if ( ! empty( $post_type->rest_controller_class ) && 'WP_REST_Posts_Controller' !== $post_type->rest_controller_class ) { - return ''; - } - - $namespace = 'wp/v2'; - $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name; - - return sprintf( '%s/%s/%d', $namespace, $rest_base, $post->ID ); - } -} diff --git a/lib/class-wp-rest-search-controller.php b/lib/class-wp-rest-search-controller.php deleted file mode 100644 index 69776c5a7be342..00000000000000 --- a/lib/class-wp-rest-search-controller.php +++ /dev/null @@ -1,363 +0,0 @@ -namespace = 'wp/v2'; - $this->rest_base = 'search'; - - foreach ( $search_handlers as $search_handler ) { - if ( ! $search_handler instanceof WP_REST_Search_Handler ) { - - /* translators: %s: PHP class name */ - _doing_it_wrong( __METHOD__, sprintf( __( 'REST search handlers must extend the %s class.', 'gutenberg' ), 'WP_REST_Search_Handler' ), '3.3.0' ); - continue; - } - - $this->search_handlers[ $search_handler->get_type() ] = $search_handler; - } - } - - /** - * Registers the routes for the objects of the controller. - * - * @since 3.3.0 - * - * @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_permission_check' ), - 'args' => $this->get_collection_params(), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - } - - /** - * Checks if a given request has access to search content. - * - * @since 3.3.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has search access, WP_Error object otherwise. - */ - public function get_items_permission_check( $request ) { - return true; - } - - /** - * Retrieves a collection of search results. - * - * @since 3.3.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 ) { - $handler = $this->get_search_handler( $request ); - if ( is_wp_error( $handler ) ) { - return $handler; - } - - $result = $handler->search_items( $request ); - - if ( ! isset( $result[ WP_REST_Search_Handler::RESULT_IDS ] ) || ! is_array( $result[ WP_REST_Search_Handler::RESULT_IDS ] ) || ! isset( $result[ WP_REST_Search_Handler::RESULT_TOTAL ] ) ) { - return new WP_Error( 'rest_search_handler_error', __( 'Internal search handler error.', 'gutenberg' ), array( 'status' => 500 ) ); - } - - $ids = array_map( 'absint', $result[ WP_REST_Search_Handler::RESULT_IDS ] ); - - $results = array(); - foreach ( $ids as $id ) { - $data = $this->prepare_item_for_response( $id, $request ); - $results[] = $this->prepare_response_for_collection( $data ); - } - - $total = (int) $result[ WP_REST_Search_Handler::RESULT_TOTAL ]; - $page = (int) $request['page']; - $per_page = (int) $request['per_page']; - $max_pages = ceil( $total / $per_page ); - - if ( $page > $max_pages && $total > 0 ) { - return new WP_Error( 'rest_search_invalid_page_number', __( 'The page number requested is larger than the number of pages available.', 'gutenberg' ), array( 'status' => 400 ) ); - } - - $response = rest_ensure_response( $results ); - $response->header( 'X-WP-Total', $total ); - $response->header( 'X-WP-TotalPages', $max_pages ); - - $request_params = $request->get_query_params(); - $base = add_query_arg( $request_params, rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); - - if ( $page > 1 ) { - $prev_link = add_query_arg( 'page', $page - 1, $base ); - $response->link_header( 'prev', $prev_link ); - } - if ( $page < $max_pages ) { - $next_link = add_query_arg( 'page', $page + 1, $base ); - $response->link_header( 'next', $next_link ); - } - - return $response; - } - - /** - * Prepares a single search result for response. - * - * @since 3.3.0 - * - * @param int $id ID of the item to prepare. - * @param WP_REST_Request $request Request object. - * @return WP_REST_Response Response object. - */ - public function prepare_item_for_response( $id, $request ) { - $handler = $this->get_search_handler( $request ); - if ( is_wp_error( $handler ) ) { - return new WP_REST_Response(); - } - - if ( method_exists( $this, 'get_fields_for_response' ) ) { - $fields = $this->get_fields_for_response( $request ); - } else { - $schema = $this->get_item_schema(); - $fields = array_keys( $schema['properties'] ); - } - - $data = $handler->prepare_item( $id, $fields ); - $data = $this->add_additional_fields_to_object( $data, $request ); - - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $data = $this->filter_response_by_context( $data, $context ); - - $response = rest_ensure_response( $data ); - - $links = $handler->prepare_item_links( $id ); - $links['collection'] = array( - 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), - ); - $response->add_links( $links ); - - return $response; - } - - /** - * Retrieves the item schema, conforming to JSON Schema. - * - * @since 3.3.0 - * - * @return array Item schema data. - */ - public function get_item_schema() { - $types = array(); - $subtypes = array(); - foreach ( $this->search_handlers as $search_handler ) { - $types[] = $search_handler->get_type(); - $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() ); - } - - $types = array_unique( $types ); - $subtypes = array_unique( $subtypes ); - - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'search-result', - 'type' => 'object', - 'properties' => array( - self::PROP_ID => array( - 'description' => __( 'Unique identifier for the object.', 'gutenberg' ), - 'type' => 'integer', - 'context' => array( 'view', 'embed' ), - 'readonly' => true, - ), - self::PROP_TITLE => array( - 'description' => __( 'The title for the object.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'view', 'embed' ), - 'readonly' => true, - ), - self::PROP_URL => array( - 'description' => __( 'URL to the object.', 'gutenberg' ), - 'type' => 'string', - 'format' => 'uri', - 'context' => array( 'view', 'embed' ), - 'readonly' => true, - ), - self::PROP_TYPE => array( - 'description' => __( 'Object type.', 'gutenberg' ), - 'type' => 'string', - 'enum' => $types, - 'context' => array( 'view', 'embed' ), - 'readonly' => true, - ), - self::PROP_SUBTYPE => array( - 'description' => __( 'Object subtype.', 'gutenberg' ), - 'type' => 'string', - 'enum' => $subtypes, - 'context' => array( 'view', 'embed' ), - 'readonly' => true, - ), - ), - ); - - return $this->add_additional_fields_schema( $schema ); - } - - /** - * Retrieves the query params for the search results collection. - * - * @since 3.3.0 - * - * @return array Collection parameters. - */ - public function get_collection_params() { - $types = array(); - $subtypes = array(); - foreach ( $this->search_handlers as $search_handler ) { - $types[] = $search_handler->get_type(); - $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() ); - } - - $types = array_unique( $types ); - $subtypes = array_unique( $subtypes ); - - $query_params = parent::get_collection_params(); - - $query_params['context']['default'] = 'view'; - - $query_params[ self::PROP_TYPE ] = array( - 'default' => $types[0], - 'description' => __( 'Limit results to items of an object type.', 'gutenberg' ), - 'type' => 'string', - 'enum' => $types, - ); - - $query_params[ self::PROP_SUBTYPE ] = array( - 'default' => self::TYPE_ANY, - 'description' => __( 'Limit results to items of one or more object subtypes.', 'gutenberg' ), - 'type' => 'array', - 'items' => array( - 'enum' => array_merge( $subtypes, array( self::TYPE_ANY ) ), - 'type' => 'string', - ), - 'sanitize_callback' => array( $this, 'sanitize_subtypes' ), - ); - - return $query_params; - } - - /** - * Sanitizes the list of subtypes, to ensure only subtypes of the passed type are included. - * - * @since 3.3.0 - * - * @param string|array $subtypes One or more subtypes. - * @param WP_REST_Request $request Full details about the request. - * @param string $parameter Parameter name. - * @return array|WP_Error List of valid subtypes, or WP_Error object on failure. - */ - public function sanitize_subtypes( $subtypes, $request, $parameter ) { - $subtypes = wp_parse_slug_list( $subtypes ); - - $subtypes = rest_parse_request_arg( $subtypes, $request, $parameter ); - if ( is_wp_error( $subtypes ) ) { - return $subtypes; - } - - // 'any' overrides any other subtype. - if ( in_array( self::TYPE_ANY, $subtypes, true ) ) { - return array( self::TYPE_ANY ); - } - - $handler = $this->get_search_handler( $request ); - if ( is_wp_error( $handler ) ) { - return $handler; - } - - return array_intersect( $subtypes, $handler->get_subtypes() ); - } - - /** - * Gets the search handler to handle the current request. - * - * @since 3.3.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Search_Handler|WP_Error Search handler for the request type, or WP_Error object on failure. - */ - protected function get_search_handler( $request ) { - $type = $request->get_param( self::PROP_TYPE ); - - if ( ! $type || ! isset( $this->search_handlers[ $type ] ) ) { - return new WP_Error( 'rest_search_invalid_type', __( 'Invalid type parameter.', 'gutenberg' ), array( 'status' => 400 ) ); - } - - return $this->search_handlers[ $type ]; - } -} diff --git a/lib/class-wp-rest-search-handler.php b/lib/class-wp-rest-search-handler.php deleted file mode 100644 index 6c5351d45b0029..00000000000000 --- a/lib/class-wp-rest-search-handler.php +++ /dev/null @@ -1,96 +0,0 @@ -type; - } - - /** - * Gets the object subtypes managed by this search handler. - * - * @since 3.3.0 - * - * @return array Array of object subtype identifiers. - */ - public function get_subtypes() { - return $this->subtypes; - } - - /** - * Searches the object type content for a given search request. - * - * @since 3.3.0 - * - * @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. - */ - abstract public function search_items( WP_REST_Request $request ); - - /** - * Prepares the search result for a given ID. - * - * @since 3.3.0 - * - * @param int $id Item ID. - * @param array $fields Fields to include for the item. - * @return array Associative array containing all fields for the item. - */ - abstract public function prepare_item( $id, array $fields ); - - /** - * Prepares links for the search result of a given ID. - * - * @since 3.3.0 - * - * @param int $id Item ID. - * @return array Links for the given item. - */ - abstract public function prepare_item_links( $id ); -} diff --git a/lib/class-wp-rest-themes-controller.php b/lib/class-wp-rest-themes-controller.php deleted file mode 100644 index b2ddf4e0e39101..00000000000000 --- a/lib/class-wp-rest-themes-controller.php +++ /dev/null @@ -1,237 +0,0 @@ -namespace = 'wp/v2'; - $this->rest_base = 'themes'; - } - - /** - * Registers the routes for the objects of the controller. - * - * @since 5.0.0 - * - * @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_item_schema' ), - ) - ); - } - - /** - * Checks if a given request has access to read the theme. - * - * @since 5.0.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, otherwise WP_Error object. - */ - public function get_items_permissions_check( $request ) { - if ( ! is_user_logged_in() || ! current_user_can( 'edit_posts' ) ) { - return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to view themes.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - - /** - * Retrieves a collection of themes. - * - * @since 5.0.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 ) { - // Retrieve the list of registered collection query parameters. - $registered = $this->get_collection_params(); - $themes = array(); - - if ( isset( $registered['status'], $request['status'] ) && in_array( 'active', $request['status'], true ) ) { - $active_theme = wp_get_theme(); - $active_theme = $this->prepare_item_for_response( $active_theme, $request ); - $themes[] = $this->prepare_response_for_collection( $active_theme ); - } - - $response = rest_ensure_response( $themes ); - - $response->header( 'X-WP-Total', count( $themes ) ); - $response->header( 'X-WP-TotalPages', count( $themes ) ); - - return $response; - } - - /** - * Prepares a single theme output for response. - * - * @since 5.0.0 - * - * @param WP_Theme $theme Theme object. - * @param WP_REST_Request $request Request object. - * @return WP_REST_Response Response object. - */ - public function prepare_item_for_response( $theme, $request ) { - $data = array(); - $fields = $this->get_fields_for_response( $request ); - - if ( in_array( 'theme_supports', $fields, true ) ) { - $formats = get_theme_support( 'post-formats' ); - $formats = is_array( $formats ) ? array_values( $formats[0] ) : array(); - $formats = array_merge( array( 'standard' ), $formats ); - $data['theme_supports']['formats'] = $formats; - - $data['theme_supports']['post-thumbnails'] = false; - $data['theme_supports']['responsive-embeds'] = (bool) get_theme_support( 'responsive-embeds' ); - $post_thumbnails = get_theme_support( 'post-thumbnails' ); - - if ( $post_thumbnails ) { - // $post_thumbnails can contain a nested array of post types. - // e.g. array( array( 'post', 'page' ) ). - $data['theme_supports']['post-thumbnails'] = is_array( $post_thumbnails ) ? $post_thumbnails[0] : true; - } - } - - $data = $this->add_additional_fields_to_object( $data, $request ); - - // Wrap the data in a response object. - $response = rest_ensure_response( $data ); - - /** - * Filters theme data returned from the REST API. - * - * @since 5.0.0 - * - * @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. - */ - return apply_filters( 'rest_prepare_theme', $response, $theme, $request ); - } - - /** - * Retrieves the theme's schema, conforming to JSON Schema. - * - * @since 5.0.0 - * - * @return array Item schema data. - */ - public function get_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'theme', - 'type' => 'object', - 'properties' => array( - 'theme_supports' => array( - 'description' => __( 'Features supported by this theme.', 'gutenberg' ), - 'type' => 'array', - 'readonly' => true, - 'properties' => array( - 'formats' => array( - 'description' => __( 'Post formats supported.', 'gutenberg' ), - 'type' => 'array', - 'readonly' => true, - ), - 'post-thumbnails' => array( - 'description' => __( 'Whether the theme supports post thumbnails.', 'gutenberg' ), - 'type' => array( 'array', 'bool' ), - 'readonly' => true, - ), - 'responsive-embeds' => array( - 'description' => __( 'Whether the theme supports responsive embedded content.', 'gutenberg' ), - 'type' => 'bool', - 'readonly' => true, - ), - ), - ), - ), - ); - - return $this->add_additional_fields_schema( $schema ); - } - - /** - * Retrieves the search params for the themes collection. - * - * @since 5.0.0 - * - * @return array Collection parameters. - */ - public function get_collection_params() { - $query_params = parent::get_collection_params(); - - $query_params['status'] = array( - 'description' => __( 'Limit result set to themes assigned one or more statuses.', 'gutenberg' ), - 'type' => 'array', - 'items' => array( - 'enum' => array( 'active' ), - 'type' => 'string', - ), - 'required' => true, - 'sanitize_callback' => array( $this, 'sanitize_theme_status' ), - ); - - /** - * Filter collection parameters for the themes controller. - * - * @since 5.0.0 - * - * @param array $query_params JSON Schema-formatted collection parameters. - */ - return apply_filters( 'rest_themes_collection_params', $query_params ); - } - - /** - * Sanitizes and validates the list of theme status. - * - * @since 5.0.0 - * - * @param string|array $statuses One or more theme statuses. - * @param WP_REST_Request $request Full details about the request. - * @param string $parameter Additional parameter to pass to validation. - * @return array|WP_Error A list of valid statuses, otherwise WP_Error object. - */ - public function sanitize_theme_status( $statuses, $request, $parameter ) { - $statuses = wp_parse_slug_list( $statuses ); - - foreach ( $statuses as $status ) { - $result = rest_validate_request_arg( $status, $request, $parameter ); - - if ( is_wp_error( $result ) ) { - return $result; - } - } - - return $statuses; - } -} diff --git a/lib/class-wp-rest-widget-updater-controller.php b/lib/class-wp-rest-widget-updater-controller.php new file mode 100644 index 00000000000000..23c46887ba573b --- /dev/null +++ b/lib/class-wp-rest-widget-updater-controller.php @@ -0,0 +1,186 @@ +namespace = 'wp/v2'; + $this->rest_base = 'widgets'; + } + + /** + * Registers the necessary REST API route. + * + * @access public + */ + public function register_routes() { + register_rest_route( + $this->namespace, + // Regex representing a PHP class extracted from http://php.net/manual/en/language.oop5.basic.php. + '/' . $this->rest_base . '/(?P [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/', + array( + 'args' => array( + 'identifier' => array( + 'description' => __( 'Class name of the widget.', 'gutenberg' ), + 'type' => 'string', + ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'permission_callback' => array( $this, 'compute_new_widget_permissions_check' ), + 'callback' => array( $this, 'compute_new_widget' ), + ), + ) + ); + } + + /** + * Checks if the user has permissions to make the request. + * + * @since 5.2.0 + * @access public + * + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function compute_new_widget_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; + } + + /** + * Returns the new widget instance and the form that represents it. + * + * @since 5.2.0 + * @access public + * + * @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 compute_new_widget( $request ) { + $url_params = $request->get_url_params(); + + $widget = $request->get_param( 'identifier' ); + + global $wp_widget_factory; + + if ( + null === $widget || + ! isset( $wp_widget_factory->widgets[ $widget ] ) || + ! ( $wp_widget_factory->widgets[ $widget ] instanceof WP_Widget ) + ) { + return new WP_Error( + 'widget_invalid', + __( 'Invalid widget.', 'gutenberg' ), + array( + 'status' => 404, + ) + ); + } + + $widget_obj = $wp_widget_factory->widgets[ $widget ]; + + $instance = $request->get_param( 'instance' ); + if ( null === $instance ) { + $instance = array(); + } + $id_to_use = $request->get_param( 'id_to_use' ); + if ( null === $id_to_use ) { + $id_to_use = -1; + } + + $widget_obj->_set( $id_to_use ); + ob_start(); + + $instance_changes = $request->get_param( 'instance_changes' ); + if ( null !== $instance_changes ) { + $old_instance = $instance; + $instance = $widget_obj->update( $instance_changes, $old_instance ); + /** + * Filters a widget's settings before saving. + * + * Returning false will effectively short-circuit the widget's ability + * to update settings. The old setting will be returned. + * + * @since 5.2.0 + * + * @param array $instance The current widget instance's settings. + * @param array $instance_changes Array of new widget settings. + * @param array $old_instance Array of old widget settings. + * @param WP_Widget $widget_ob The widget instance. + */ + $instance = apply_filters( 'widget_update_callback', $instance, $instance_changes, $old_instance, $widget_obj ); + if ( false === $instance ) { + $instance = $old_instance; + } + } + + $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. + * + * @since 5.2.0 + * + * @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. + */ + do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, $instance ) ); + } + + $id_base = $widget_obj->id_base; + $id = $widget_obj->id; + $form = ob_get_clean(); + + return rest_ensure_response( + array( + 'instance' => $instance, + 'form' => $form, + 'id_base' => $id_base, + 'id' => $id, + ) + ); + } +} +/** + * End: Include for phase 2 + */ diff --git a/lib/client-assets.php b/lib/client-assets.php index 13672f6516bcdd..2a97319dd9f1c3 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -35,83 +35,118 @@ function gutenberg_url( $path ) { } /** - * Returns contents of an inline script used in appending polyfill scripts for - * browsers which fail the provided tests. The provided array is a mapping from - * a condition to verify feature support to its polyfill script handle. + * Registers a script according to `wp_register_script`. Honors this request by + * reassigning internal dependency properties of any script handle already + * registered by that name. It does not deregister the original script, to + * avoid losing inline scripts which may have been attached. + * + * @since 4.1.0 * - * @param array $tests Features to detect. - * @return string Conditional polyfill inline script. + * @param string $handle Name of the script. Should be unique. + * @param string $src Full URL of the script, or path of the script relative to the WordPress root directory. + * @param array $deps Optional. An array of registered script handles this script depends on. Default empty array. + * @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL + * as a query string for cache busting purposes. If version is set to false, a version + * number is automatically added equal to current installed WordPress version. + * If set to null, no version is added. + * @param bool $in_footer Optional. Whether to enqueue the script before



my text
" +``` + +**Parameters** + +- **text** `string`: The text which has to be formatted. +- **br** `boolean`: Optional. If set, will convert all remaining line- breaks after paragraphing. Default true. + +**Returns** + +`string`: Text which has been converted into paragraph tags. -autop( 'my text' ); -// "my text
" +#### removep -removep( 'my text
' ); -// "my text" +[src/index.js#L303-L426](src/index.js#L303-L426) + +Replaces `` tags with two line breaks. "Opposite" of autop(). + +Replaces `
` tags with two line breaks except where the `
` has attributes. +Unifies whitespace. Indents `
my text
' ); // "my text" ``` -### API Usage +**Parameters** + +- **html** `string`: The content from the editor. + +**Returns** + +`string`: The content with stripped paragraph tags. + -* `autop( text: string ): string` -* `removep( text: string ): string` +
my text
" + * ``` + * * @return {string} Text which has been converted into paragraph tags. */ export function autop( text, br = true ) { @@ -162,7 +169,7 @@ export function autop( text, br = true ) { const allBlocks = '(?:table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)'; // Add a double line break above block-level opening tags. - text = text.replace( new RegExp( '(<' + allBlocks + '[\s\/>])', 'g' ), '\n\n$1' ); + text = text.replace( new RegExp( '(<' + allBlocks + '[\\s\/>])', 'g' ), '\n\n$1' ); // Add a double line break below block-level closing tags. text = text.replace( new RegExp( '(<\/' + allBlocks + '>)', 'g' ), '$1\n\n' ); @@ -226,7 +233,7 @@ export function autop( text, br = true ) { text = text.replace( /([^<]+)<\/(div|address|form)>/g, '
$1
$2>' ); // If an opening or closing block element tag is wrapped in a, unwrap it. - text = text.replace( new RegExp( '
\s*(<\/?' + allBlocks + '[^>]*>)\s*<\/p>', 'g' ), '$1' ); + text = text.replace( new RegExp( '
\\s*(<\/?' + allBlocks + '[^>]*>)\\s*<\/p>', 'g' ), '$1' ); // In some cases
, fix them. text = text.replace( /
(
tag, remove it. - text = text.replace( new RegExp( '
\s*(<\/?' + allBlocks + '[^>]*>)', 'g' ), '$1' ); + text = text.replace( new RegExp( '
\\s*(<\/?' + allBlocks + '[^>]*>)', 'g' ), '$1' ); // If an opening or closing block element tag is followed by a closing
tag, remove it.
- text = text.replace( new RegExp( '(<\/?' + allBlocks + '[^>]*>)\s*<\/p>', 'g' ), '$1' );
+ text = text.replace( new RegExp( '(<\/?' + allBlocks + '[^>]*>)\\s*<\/p>', 'g' ), '$1' );
// Optionally insert line breaks.
if ( br ) {
@@ -257,7 +264,7 @@ export function autop( text, br = true ) {
}
// If a
tag is after an opening or closing block tag, remove it.
- text = text.replace( new RegExp( '(<\/?' + allBlocks + '[^>]*>)\s*
', 'g' ), '$1' );
+ text = text.replace( new RegExp( '(<\/?' + allBlocks + '[^>]*>)\\s*
', 'g' ), '$1' );
// If a
tag is before a subset of opening or closing block tags, remove it.
text = text.replace( /
(\s*<\/?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)/g, '$1' );
@@ -278,12 +285,19 @@ export function autop( text, br = true ) {
}
/**
- * Replaces
tags with two line breaks. "Opposite" of autop(). + * Replaces `
` tags with two line breaks. "Opposite" of autop(). * - * Replaces
tags with two line breaks except where the
has attributes. - * Unifies whitespace. Indents
` tags with two line breaks except where the `
` has attributes. + * Unifies whitespace. Indents `
my text
' ); // "my text" + * ``` + * * @return {string} The content with stripped paragraph tags. */ export function removep( html ) { diff --git a/packages/autop/src/test/index.test.js b/packages/autop/src/test/index.test.js index ea8e4c605343ef..9e945d3d9e8ced 100644 --- a/packages/autop/src/test/index.test.js +++ b/packages/autop/src/test/index.test.js @@ -495,3 +495,10 @@ test( 'that autop doses not add extra closing p in figure', () => { expect( autop( content1 ).trim() ).toBe( expected1 ); expect( autop( content2 ).trim() ).toBe( expected2 ); } ); + +test( 'that autop correctly adds a start and end tag when followed by a div', () => { + const content = 'Testing autop with a div\nTesting autop with a div
\n


+ Child +
+Child
+{ __( 'Block Navigation' ) }
+ { hasHierarchy && ( +instead of in the
. + * Default 'false'. */ -function gutenberg_get_script_polyfill( $tests ) { +function gutenberg_override_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false ) { global $wp_scripts; - $polyfill = ''; - foreach ( $tests as $test => $handle ) { - if ( ! array_key_exists( $handle, $wp_scripts->registered ) ) { - continue; - } + $script = $wp_scripts->query( $handle, 'registered' ); + if ( $script ) { + /* + * In many ways, this is a reimplementation of `wp_register_script` but + * bypassing consideration of whether a script by the given handle had + * already been registered. + */ - $polyfill .= ( - // Test presence of feature... - '( ' . $test . ' ) || ' . - // ...appending polyfill on any failures. Cautious viewers may balk - // at the `document.write`. Its caveat of synchronous mid-stream - // blocking write is exactly the behavior we need though. - 'document.write( \' - true, - 'action' => 'edit', - 'classic-editor' => true, - 'post' => $post_id, - ), - admin_url( 'post.php' ) - ); - } - - return $location; -} - -add_filter( 'redirect_post_location', 'gutenberg_meta_box_save_redirect', 10, 2 ); - -/** - * Filter out core meta boxes as well as the post thumbnail. - * - * @since 1.5.0 - * - * @param array $meta_boxes Meta box data. - * @return array Meta box data without core meta boxes. - */ -function gutenberg_filter_meta_boxes( $meta_boxes ) { - $core_side_meta_boxes = array( - 'submitdiv', - 'formatdiv', - 'pageparentdiv', - 'postimagediv', - ); - - $custom_taxonomies = get_taxonomies( - array( - 'show_ui' => true, - ), - 'objects' - ); - - // Following the same logic as meta box generation in: - // https://github.com/WordPress/wordpress-develop/blob/c896326/src/wp-admin/edit-form-advanced.php#L288-L292. - foreach ( $custom_taxonomies as $custom_taxonomy ) { - $core_side_meta_boxes [] = $custom_taxonomy->hierarchical ? - $custom_taxonomy->name . 'div' : - 'tagsdiv-' . $custom_taxonomy->name; - } - - $core_normal_meta_boxes = array( - 'revisionsdiv', - 'postexcerpt', - 'trackbacksdiv', - 'commentstatusdiv', - 'commentsdiv', - 'slugdiv', - 'authordiv', - ); - - // Whether or not to load the 'postcustom' meta box is stored as a user meta - // field so that we're not always loading its assets. - $enable_custom_fields = (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ); - if ( ! $enable_custom_fields ) { - $core_normal_meta_boxes[] = 'postcustom'; - } - - $taxonomy_callbacks_to_unset = array( - 'post_tags_meta_box', - 'post_categories_meta_box', - ); - - foreach ( $meta_boxes as $page => $contexts ) { - foreach ( $contexts as $context => $priorities ) { - foreach ( $priorities as $priority => $boxes ) { - foreach ( $boxes as $name => $data ) { - if ( 'normal' === $context && in_array( $name, $core_normal_meta_boxes ) ) { - unset( $meta_boxes[ $page ][ $context ][ $priority ][ $name ] ); - } elseif ( 'side' === $context && in_array( $name, $core_side_meta_boxes ) ) { - unset( $meta_boxes[ $page ][ $context ][ $priority ][ $name ] ); - } - // Filter out any taxonomies as Gutenberg already provides JS alternative. - if ( isset( $data['callback'] ) && in_array( $data['callback'], $taxonomy_callbacks_to_unset ) ) { - unset( $meta_boxes[ $page ][ $context ][ $priority ][ $name ] ); - } - // Filter out meta boxes that are just registered for back compat. - if ( isset( $data['args']['__back_compat_meta_box'] ) && $data['args']['__back_compat_meta_box'] ) { - unset( $meta_boxes[ $page ][ $context ][ $priority ][ $name ] ); - } - } - } - } - } - - return $meta_boxes; -} - -add_filter( 'filter_gutenberg_meta_boxes', 'gutenberg_filter_meta_boxes' ); - -/** - * Go through the global metaboxes, and override the render callback, so we can trigger our warning if needed. - * - * @since 1.8.0 - */ -function gutenberg_intercept_meta_box_render() { - global $wp_meta_boxes; - - foreach ( $wp_meta_boxes as $post_type => $contexts ) { - foreach ( $contexts as $context => $priorities ) { - foreach ( $priorities as $priority => $boxes ) { - foreach ( $boxes as $id => $box ) { - if ( ! is_array( $box ) ) { - continue; - } - if ( ! is_array( $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args'] ) ) { - $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args'] = array(); - } - if ( ! isset( $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args']['__original_callback'] ) ) { - $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args']['__original_callback'] = $box['callback']; - $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['callback'] = 'gutenberg_override_meta_box_callback'; - } - } - } - } - } -} -add_action( 'submitpost_box', 'gutenberg_intercept_meta_box_render' ); -add_action( 'submitpage_box', 'gutenberg_intercept_meta_box_render' ); -add_action( 'edit_page_form', 'gutenberg_intercept_meta_box_render' ); -add_action( 'edit_form_advanced', 'gutenberg_intercept_meta_box_render' ); - -/** - * Check if this metabox only exists for back compat purposes, show a warning if it doesn't. - * - * @since 1.8.0 - * - * @param mixed $object The object being operated on, on this screen. - * @param array $box The current meta box definition. - */ -function gutenberg_override_meta_box_callback( $object, $box ) { - $callback = $box['args']['__original_callback']; - unset( $box['args']['__original_callback'] ); - - $block_compatible = true; - if ( isset( $box['args']['__block_editor_compatible_meta_box'] ) ) { - $block_compatible = (bool) $box['args']['__block_editor_compatible_meta_box']; - unset( $box['args']['__block_editor_compatible_meta_box'] ); - } - - if ( isset( $box['args']['__back_compat_meta_box'] ) ) { - $block_compatible |= (bool) $box['args']['__back_compat_meta_box']; - unset( $box['args']['__back_compat_meta_box'] ); - } - - if ( ! $block_compatible ) { - gutenberg_show_meta_box_warning( $callback ); - } - - call_user_func( $callback, $object, $box ); -} - -/** - * Display a warning in the metabox that the current plugin is causing the fallback to the old editor. - * - * @since 1.8.0 - * - * @param callable $callback The function that a plugin has defined to render a meta box. - */ -function gutenberg_show_meta_box_warning( $callback ) { - // Only show the warning when WP_DEBUG is enabled. - if ( ! WP_DEBUG ) { - return; - } - - // Don't show in the Gutenberg meta box UI. - if ( ! isset( $_REQUEST['classic-editor'] ) ) { - return; - } - - try { - if ( is_array( $callback ) ) { - $reflection = new ReflectionMethod( $callback[0], $callback[1] ); - } else { - $reflection = new ReflectionFunction( $callback ); - } - } catch ( ReflectionException $exception ) { - // We could not properly reflect on the callable, so we abort here. - return; - } - - if ( $reflection->isInternal() ) { - return; - } - - $filename = $reflection->getFileName(); - if ( strpos( $filename, WP_PLUGIN_DIR ) !== 0 ) { - return; - } - - $filename = str_replace( WP_PLUGIN_DIR, '', $filename ); - $filename = preg_replace( '|^/([^/]*/).*$|', '\\1', $filename ); - - $plugins = get_plugins(); - foreach ( $plugins as $name => $plugin ) { - if ( strpos( $name, $filename ) === 0 ) { - ?> -
- -
-- -
-
- -
- - id ][ $location ][ $priority ] ) ) { - $meta_boxes = (array) $wp_meta_boxes[ $current_screen->id ][ $location ][ $priority ]; - foreach ( $meta_boxes as $meta_box ) { - if ( false == $meta_box || ! $meta_box['title'] ) { - continue; - } - - $meta_boxes_per_location[ $location ][] = array( - 'id' => $meta_box['id'], - 'title' => $meta_box['title'], - ); - } - } - } - } - - /** - * Sadly we probably can not add this data directly into editor settings. - * - * ACF and other meta boxes need admin_head to fire for meta box registry. - * admin_head fires after admin_enqueue_scripts which is where we create our - * editor instance. If a cleaner solution can be imagined, please change - * this, and try to get this data to load directly into the editor settings. - */ - $script = 'window._wpLoadGutenbergEditor.then( function() { - wp.data.dispatch( \'core/edit-post\' ).setAvailableMetaBoxesPerLocation( ' . wp_json_encode( $meta_boxes_per_location ) . ' ); - } );'; - - wp_add_inline_script( 'wp-edit-post', $script ); - - /** - * When `wp-edit-post` is output in the `
`, the inline script needs to be manually printed. Otherwise, - * metaboxes will not display because inline scripts for `wp-edit-post` will not be printed again after this point. - * - * @see https://github.com/WordPress/gutenberg/issues/6963 - */ - if ( wp_script_is( 'wp-edit-post', 'done' ) ) { - printf( "\n", trim( $script ) ); - } - - /** - * If the 'postcustom' meta box is enabled, then we need to perform some - * extra initialization on it. - */ - $enable_custom_fields = (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ); - if ( $enable_custom_fields ) { - $script = "( function( $ ) { - if ( $('#postcustom').length ) { - $( '#the-list' ).wpList( { - addBefore: function( s ) { - s.data += '&post_id=$post->ID'; - return s; - }, - addAfter: function() { - $('table#list-table').show(); - } - }); - } - } )( jQuery );"; - - wp_enqueue_script( 'wp-lists' ); - wp_add_inline_script( 'wp-lists', $script ); - } - - // Reset meta box data. - $wp_meta_boxes = $_original_meta_boxes; -} - -/** - * Renders the hidden form required for the meta boxes form. - * - * @param WP_Post $post Current post object. - * - * @since 1.8.0 - */ -function gutenberg_meta_box_post_form_hidden_fields( $post ) { - $form_extra = ''; - if ( 'auto-draft' === $post->post_status ) { - $form_extra .= ""; - } - $form_action = 'editpost'; - $nonce_action = 'update-post_' . $post->ID; - $form_extra .= ""; - $referer = wp_get_referer(); - $current_user = wp_get_current_user(); - $user_id = $current_user->ID; - wp_nonce_field( $nonce_action ); - ?> - - - - - - - - - - array( - 'wp-hooks', 'wp-i18n', 'wp-url', ), @@ -28,6 +27,7 @@ 'wp-api-fetch', 'wp-autop', 'wp-blob', + 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', @@ -45,6 +45,28 @@ ), 'wp-block-serialization-default-parser' => array(), 'wp-block-serialization-spec-parser' => array(), + 'wp-block-editor' => array( + 'lodash', + 'wp-a11y', + 'wp-blob', + 'wp-blocks', + 'wp-compose', + 'wp-components', + 'wp-core-data', + 'wp-data', + 'wp-dom', + 'wp-element', + 'wp-hooks', + 'wp-html-entities', + 'wp-i18n', + 'wp-is-shallow-equal', + 'wp-keycodes', + 'wp-rich-text', + 'wp-token-list', + 'wp-url', + 'wp-viewport', + 'wp-wordcount', + ), 'wp-blocks' => array( 'lodash', 'wp-autop', @@ -84,6 +106,7 @@ 'lodash', 'wp-api-fetch', 'wp-data', + 'wp-deprecated', 'wp-url', ), 'wp-data' => array( @@ -91,6 +114,7 @@ 'wp-compose', 'wp-element', 'wp-is-shallow-equal', + 'wp-priority-queue', 'wp-redux-routine', ), 'wp-date' => array( @@ -101,7 +125,6 @@ ), 'wp-dom' => array( 'lodash', - 'wp-tinymce', ), 'wp-dom-ready' => array(), 'wp-edit-post' => array( @@ -112,6 +135,7 @@ 'media-views', 'wp-a11y', 'wp-api-fetch', + 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', @@ -130,13 +154,15 @@ 'wp-url', 'wp-viewport', ), + 'wp-edit-widgets' => array( + 'wp-element', + ), 'wp-editor' => array( - 'jquery', 'lodash', - 'wp-tinymce-lists', 'wp-a11y', 'wp-api-fetch', 'wp-blob', + 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', @@ -154,7 +180,6 @@ 'wp-notices', 'wp-nux', 'wp-rich-text', - 'wp-tinymce', 'wp-token-list', 'wp-url', 'wp-viewport', @@ -168,8 +193,8 @@ ), 'wp-escape-html' => array(), 'wp-format-library' => array( + 'wp-block-editor', 'wp-components', - 'wp-dom', 'wp-editor', 'wp-element', 'wp-i18n', @@ -212,6 +237,7 @@ 'wp-element', 'wp-hooks', ), + 'wp-priority-queue' => array(), 'wp-redux-routine' => array(), 'wp-rich-text' => array( 'lodash', diff --git a/lib/parser.php b/lib/parser.php deleted file mode 100644 index 8fbcaa19bb458f..00000000000000 --- a/lib/parser.php +++ /dev/null @@ -1,1681 +0,0 @@ -= $range[0] && $code <= $range[1]) { - return true; - } - } - return false; - } -} - -/* Syntax error exception */ -if (!class_exists("Gutenberg_PEG_SyntaxError", false)) { - class Gutenberg_PEG_SyntaxError extends Exception { - public $expected; - public $found; - public $grammarOffset; - public $grammarLine; - public $grammarColumn; - public $name; - public function __construct($message, $expected, $found, $offset, $line, $column) { - parent::__construct($message, 0); - $this->expected = $expected; - $this->found = $found; - $this->grammarOffset = $offset; - $this->grammarLine = $line; - $this->grammarColumn = $column; - $this->name = "Gutenberg_PEG_SyntaxError"; - } - } -} - -class Gutenberg_PEG_Parser { - private $peg_currPos = 0; - private $peg_reportedPos = 0; - private $peg_cachedPos = 0; - private $peg_cachedPosDetails = array('line' => 1, 'column' => 1, 'seenCR' => false ); - private $peg_maxFailPos = 0; - private $peg_maxFailExpected = array(); - private $peg_silentFails = 0; - private $input = array(); - private $input_length = 0; - - private function cleanup_state() { - $this->peg_currPos = 0; - $this->peg_reportedPos = 0; - $this->peg_cachedPos = 0; - $this->peg_cachedPosDetails = array('line' => 1, 'column' => 1, 'seenCR' => false ); - $this->peg_maxFailPos = 0; - $this->peg_maxFailExpected = array(); - $this->peg_silentFails = 0; - $this->input = array(); - $this->input_length = 0; - - } - - private function input_substr($start, $length) { - if ($length === 1 && $start < $this->input_length) { - return $this->input[$start]; - } - $substr = ''; - $max = min($start + $length, $this->input_length); - for ($i = $start; $i < $max; $i++) { - $substr .= $this->input[$i]; - } - return $substr; - } - - - private function text() { - return substr($this->input, $this->peg_reportedPos, $this->peg_reportedPos + $this->peg_currPos); - } - - private function offset() { - return $this->peg_reportedPos; - } - - private function line() { - $compute_pd = $this->peg_computePosDetails($this->peg_reportedPos); - return $compute_pd["line"]; - } - - private function column() { - $compute_pd = $this->peg_computePosDetails($this->peg_reportedPos); - return $compute_pd["column"]; - } - - private function expected($description) { - throw $this->peg_buildException( - null, - array(array("type" => "other", "description" => $description )), - $this->peg_reportedPos - ); - } - - private function error($message) { - throw $this->peg_buildException($message, null, $this->peg_reportedPos); - } - - private function peg_advancePos(&$details, $startPos, $endPos) { - for ($p = $startPos; $p < $endPos; $p++) { - $ch = $this->input_substr($p, 1); - if ($ch === "\n") { - if (!$details["seenCR"]) { $details["line"]++; } - $details["column"] = 1; - $details["seenCR"] = false; - } else if ($ch === "\r" || $ch === "\u2028" || $ch === "\u2029") { - $details["line"]++; - $details["column"] = 1; - $details["seenCR"] = true; - } else { - $details["column"]++; - $details["seenCR"] = false; - } - } - } - - private function peg_computePosDetails($pos) { - if ($this->peg_cachedPos !== $pos) { - if ($this->peg_cachedPos > $pos) { - $this->peg_cachedPos = 0; - $this->peg_cachedPosDetails = array( "line" => 1, "column" => 1, "seenCR" => false ); - } - $this->peg_advancePos($this->peg_cachedPosDetails, $this->peg_cachedPos, $pos); - $this->peg_cachedPos = $pos; - } - - return $this->peg_cachedPosDetails; - } - - private function peg_fail($expected) { - if ($this->peg_currPos < $this->peg_maxFailPos) { return; } - - if ($this->peg_currPos > $this->peg_maxFailPos) { - $this->peg_maxFailPos = $this->peg_currPos; - $this->peg_maxFailExpected = array(); - } - - $this->peg_maxFailExpected[] = $expected; - } - - private function peg_buildException_expectedComparator($a, $b) { - if ($a["description"] < $b["description"]) { - return -1; - } else if ($a["description"] > $b["description"]) { - return 1; - } else { - return 0; - } - } - - private function peg_buildException($message, $expected, $pos) { - $posDetails = $this->peg_computePosDetails($pos); - $found = $pos < $this->input_length ? $this->input[$pos] : null; - - if ($expected !== null) { - usort($expected, array($this, "peg_buildException_expectedComparator")); - $i = 1; - while ($i < count($expected)) { - if ($expected[$i - 1] === $expected[$i]) { - array_splice($expected, $i, 1); - } else { - $i++; - } - } - } - - if ($message === null) { - $expectedDescs = array_fill(0, count($expected), null); - - for ($i = 0; $i < count($expected); $i++) { - $expectedDescs[$i] = $expected[$i]["description"]; - } - - $expectedDesc = count($expected) > 1 - ? join(", ", array_slice($expectedDescs, 0, -1)) - . " or " - . $expectedDescs[count($expected) - 1] - : $expectedDescs[0]; - - $foundDesc = $found ? json_encode($found) : "end of input"; - - $message = "Expected " . $expectedDesc . " but " . $foundDesc . " found."; - } - - return new Gutenberg_PEG_SyntaxError( - $message, - $expected, - $found, - $pos, - $posDetails["line"], - $posDetails["column"] - ); - } - - private $peg_FAILED; - private $peg_c0; - private $peg_c1; - private $peg_c2; - private $peg_c3; - private $peg_c4; - private $peg_c5; - private $peg_c6; - private $peg_c7; - private $peg_c8; - private $peg_c9; - private $peg_c10; - private $peg_c11; - private $peg_c12; - private $peg_c13; - private $peg_c14; - private $peg_c15; - private $peg_c16; - private $peg_c17; - private $peg_c18; - private $peg_c19; - private $peg_c20; - private $peg_c21; - private $peg_c22; - private $peg_c23; - private $peg_c24; - - private function peg_f0($pre, $b, $html) { return array( $b, $html ); } - private function peg_f1($pre, $bs, $post) { return peg_join_blocks( $pre, $bs, $post ); } - private function peg_f2($blockName, $a) { return $a; } - private function peg_f3($blockName, $attrs) { - return array( - 'blockName' => $blockName, - 'attrs' => empty( $attrs ) ? peg_empty_attrs() : $attrs, - 'innerBlocks' => array(), - 'innerHTML' => '', - 'innerContent' => array(), - ); - } - private function peg_f4($s, $children, $e) { - list( $innerHTML, $innerBlocks, $innerContent ) = peg_process_inner_content( $children ); - - return array( - 'blockName' => $s['blockName'], - 'attrs' => empty( $s['attrs'] ) ? peg_empty_attrs() : $s['attrs'], - 'innerBlocks' => $innerBlocks, - 'innerHTML' => $innerHTML, - 'innerContent' => $innerContent, - ); - } - private function peg_f5($blockName, $attrs) { - return array( - 'blockName' => $blockName, - 'attrs' => isset( $attrs ) ? $attrs : array(), - ); - } - private function peg_f6($blockName) { - return array( - 'blockName' => $blockName, - ); - } - private function peg_f7($type) { return "core/$type"; } - private function peg_f8($attrs) { return json_decode( $attrs, true ); } - - private function peg_parseBlock_List() { - - $s0 = $this->peg_currPos; - $s1 = $this->peg_currPos; - $s2 = array(); - $s3 = $this->peg_currPos; - $s4 = $this->peg_currPos; - $this->peg_silentFails++; - $s5 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s5 === $this->peg_FAILED) { - $s4 = null; - } else { - $this->peg_currPos = $s4; - $s4 = $this->peg_FAILED; - } - if ($s4 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s5 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s5 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s5 !== $this->peg_FAILED) { - $s4 = array($s4, $s5); - $s3 = $s4; - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - while ($s3 !== $this->peg_FAILED) { - $s2[] = $s3; - $s3 = $this->peg_currPos; - $s4 = $this->peg_currPos; - $this->peg_silentFails++; - $s5 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s5 === $this->peg_FAILED) { - $s4 = null; - } else { - $this->peg_currPos = $s4; - $s4 = $this->peg_FAILED; - } - if ($s4 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s5 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s5 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s5 !== $this->peg_FAILED) { - $s4 = array($s4, $s5); - $s3 = $s4; - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } - if ($s2 !== $this->peg_FAILED) { - $s1 = $this->input_substr($s1, $this->peg_currPos - $s1); - } else { - $s1 = $s2; - } - if ($s1 !== $this->peg_FAILED) { - $s2 = array(); - $s3 = $this->peg_currPos; - $s4 = $this->peg_parseBlock(); - if ($s4 !== $this->peg_FAILED) { - $s5 = $this->peg_currPos; - $s6 = array(); - $s7 = $this->peg_currPos; - $s8 = $this->peg_currPos; - $this->peg_silentFails++; - $s9 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s9 === $this->peg_FAILED) { - $s8 = null; - } else { - $this->peg_currPos = $s8; - $s8 = $this->peg_FAILED; - } - if ($s8 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s9 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s9 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s9 !== $this->peg_FAILED) { - $s8 = array($s8, $s9); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - while ($s7 !== $this->peg_FAILED) { - $s6[] = $s7; - $s7 = $this->peg_currPos; - $s8 = $this->peg_currPos; - $this->peg_silentFails++; - $s9 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s9 === $this->peg_FAILED) { - $s8 = null; - } else { - $this->peg_currPos = $s8; - $s8 = $this->peg_FAILED; - } - if ($s8 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s9 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s9 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s9 !== $this->peg_FAILED) { - $s8 = array($s8, $s9); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } - if ($s6 !== $this->peg_FAILED) { - $s5 = $this->input_substr($s5, $this->peg_currPos - $s5); - } else { - $s5 = $s6; - } - if ($s5 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s3; - $s4 = $this->peg_f0($s1, $s4, $s5); - $s3 = $s4; - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - while ($s3 !== $this->peg_FAILED) { - $s2[] = $s3; - $s3 = $this->peg_currPos; - $s4 = $this->peg_parseBlock(); - if ($s4 !== $this->peg_FAILED) { - $s5 = $this->peg_currPos; - $s6 = array(); - $s7 = $this->peg_currPos; - $s8 = $this->peg_currPos; - $this->peg_silentFails++; - $s9 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s9 === $this->peg_FAILED) { - $s8 = null; - } else { - $this->peg_currPos = $s8; - $s8 = $this->peg_FAILED; - } - if ($s8 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s9 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s9 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s9 !== $this->peg_FAILED) { - $s8 = array($s8, $s9); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - while ($s7 !== $this->peg_FAILED) { - $s6[] = $s7; - $s7 = $this->peg_currPos; - $s8 = $this->peg_currPos; - $this->peg_silentFails++; - $s9 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s9 === $this->peg_FAILED) { - $s8 = null; - } else { - $this->peg_currPos = $s8; - $s8 = $this->peg_FAILED; - } - if ($s8 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s9 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s9 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s9 !== $this->peg_FAILED) { - $s8 = array($s8, $s9); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } - if ($s6 !== $this->peg_FAILED) { - $s5 = $this->input_substr($s5, $this->peg_currPos - $s5); - } else { - $s5 = $s6; - } - if ($s5 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s3; - $s4 = $this->peg_f0($s1, $s4, $s5); - $s3 = $s4; - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } - if ($s2 !== $this->peg_FAILED) { - $s3 = $this->peg_currPos; - $s4 = array(); - if ($this->input_length > $this->peg_currPos) { - $s5 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s5 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - while ($s5 !== $this->peg_FAILED) { - $s4[] = $s5; - if ($this->input_length > $this->peg_currPos) { - $s5 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s5 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - } - if ($s4 !== $this->peg_FAILED) { - $s3 = $this->input_substr($s3, $this->peg_currPos - $s3); - } else { - $s3 = $s4; - } - if ($s3 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f1($s1, $s2, $s3); - $s0 = $s1; - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - private function peg_parseBlock() { - - $s0 = $this->peg_parseBlock_Void(); - if ($s0 === $this->peg_FAILED) { - $s0 = $this->peg_parseBlock_Balanced(); - } - - return $s0; - } - - private function peg_parseBlock_Void() { - - $s0 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 4) === $this->peg_c1) { - $s1 = $this->peg_c1; - $this->peg_currPos += 4; - } else { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c2); - } - } - if ($s1 !== $this->peg_FAILED) { - $s2 = $this->peg_parse__(); - if ($s2 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c3) { - $s3 = $this->peg_c3; - $this->peg_currPos += 3; - } else { - $s3 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c4); - } - } - if ($s3 !== $this->peg_FAILED) { - $s4 = $this->peg_parseBlock_Name(); - if ($s4 !== $this->peg_FAILED) { - $s5 = $this->peg_parse__(); - if ($s5 !== $this->peg_FAILED) { - $s6 = $this->peg_currPos; - $s7 = $this->peg_parseBlock_Attributes(); - if ($s7 !== $this->peg_FAILED) { - $s8 = $this->peg_parse__(); - if ($s8 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s6; - $s7 = $this->peg_f2($s4, $s7); - $s6 = $s7; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 === $this->peg_FAILED) { - $s6 = null; - } - if ($s6 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 4) === $this->peg_c5) { - $s7 = $this->peg_c5; - $this->peg_currPos += 4; - } else { - $s7 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c6); - } - } - if ($s7 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f3($s4, $s6); - $s0 = $s1; - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - private function peg_parseBlock_Balanced() { - - $s0 = $this->peg_currPos; - $s1 = $this->peg_parseBlock_Start(); - if ($s1 !== $this->peg_FAILED) { - $s2 = array(); - $s3 = $this->peg_parseBlock(); - if ($s3 === $this->peg_FAILED) { - $s3 = $this->peg_currPos; - $s4 = array(); - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - $s7 = $this->peg_currPos; - $this->peg_silentFails++; - $s8 = $this->peg_parseBlock_End(); - $this->peg_silentFails--; - if ($s8 === $this->peg_FAILED) { - $s7 = null; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - if ($s7 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s8 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s8 !== $this->peg_FAILED) { - $s6 = array($s6, $s7, $s8); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - if ($s5 !== $this->peg_FAILED) { - while ($s5 !== $this->peg_FAILED) { - $s4[] = $s5; - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - $s7 = $this->peg_currPos; - $this->peg_silentFails++; - $s8 = $this->peg_parseBlock_End(); - $this->peg_silentFails--; - if ($s8 === $this->peg_FAILED) { - $s7 = null; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - if ($s7 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s8 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s8 !== $this->peg_FAILED) { - $s6 = array($s6, $s7, $s8); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } - } else { - $s4 = $this->peg_FAILED; - } - if ($s4 !== $this->peg_FAILED) { - $s3 = $this->input_substr($s3, $this->peg_currPos - $s3); - } else { - $s3 = $s4; - } - } - while ($s3 !== $this->peg_FAILED) { - $s2[] = $s3; - $s3 = $this->peg_parseBlock(); - if ($s3 === $this->peg_FAILED) { - $s3 = $this->peg_currPos; - $s4 = array(); - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - $s7 = $this->peg_currPos; - $this->peg_silentFails++; - $s8 = $this->peg_parseBlock_End(); - $this->peg_silentFails--; - if ($s8 === $this->peg_FAILED) { - $s7 = null; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - if ($s7 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s8 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s8 !== $this->peg_FAILED) { - $s6 = array($s6, $s7, $s8); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - if ($s5 !== $this->peg_FAILED) { - while ($s5 !== $this->peg_FAILED) { - $s4[] = $s5; - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - $s7 = $this->peg_currPos; - $this->peg_silentFails++; - $s8 = $this->peg_parseBlock_End(); - $this->peg_silentFails--; - if ($s8 === $this->peg_FAILED) { - $s7 = null; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - if ($s7 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s8 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s8 !== $this->peg_FAILED) { - $s6 = array($s6, $s7, $s8); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } - } else { - $s4 = $this->peg_FAILED; - } - if ($s4 !== $this->peg_FAILED) { - $s3 = $this->input_substr($s3, $this->peg_currPos - $s3); - } else { - $s3 = $s4; - } - } - } - if ($s2 !== $this->peg_FAILED) { - $s3 = $this->peg_parseBlock_End(); - if ($s3 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f4($s1, $s2, $s3); - $s0 = $s1; - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - private function peg_parseBlock_Start() { - - $s0 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 4) === $this->peg_c1) { - $s1 = $this->peg_c1; - $this->peg_currPos += 4; - } else { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c2); - } - } - if ($s1 !== $this->peg_FAILED) { - $s2 = $this->peg_parse__(); - if ($s2 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c3) { - $s3 = $this->peg_c3; - $this->peg_currPos += 3; - } else { - $s3 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c4); - } - } - if ($s3 !== $this->peg_FAILED) { - $s4 = $this->peg_parseBlock_Name(); - if ($s4 !== $this->peg_FAILED) { - $s5 = $this->peg_parse__(); - if ($s5 !== $this->peg_FAILED) { - $s6 = $this->peg_currPos; - $s7 = $this->peg_parseBlock_Attributes(); - if ($s7 !== $this->peg_FAILED) { - $s8 = $this->peg_parse__(); - if ($s8 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s6; - $s7 = $this->peg_f2($s4, $s7); - $s6 = $s7; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 === $this->peg_FAILED) { - $s6 = null; - } - if ($s6 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c7) { - $s7 = $this->peg_c7; - $this->peg_currPos += 3; - } else { - $s7 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c8); - } - } - if ($s7 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f5($s4, $s6); - $s0 = $s1; - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - private function peg_parseBlock_End() { - - $s0 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 4) === $this->peg_c1) { - $s1 = $this->peg_c1; - $this->peg_currPos += 4; - } else { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c2); - } - } - if ($s1 !== $this->peg_FAILED) { - $s2 = $this->peg_parse__(); - if ($s2 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 4) === $this->peg_c9) { - $s3 = $this->peg_c9; - $this->peg_currPos += 4; - } else { - $s3 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c10); - } - } - if ($s3 !== $this->peg_FAILED) { - $s4 = $this->peg_parseBlock_Name(); - if ($s4 !== $this->peg_FAILED) { - $s5 = $this->peg_parse__(); - if ($s5 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c7) { - $s6 = $this->peg_c7; - $this->peg_currPos += 3; - } else { - $s6 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c8); - } - } - if ($s6 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f6($s4); - $s0 = $s1; - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - private function peg_parseBlock_Name() { - - $s0 = $this->peg_parseNamespaced_Block_Name(); - if ($s0 === $this->peg_FAILED) { - $s0 = $this->peg_parseCore_Block_Name(); - } - - return $s0; - } - - private function peg_parseNamespaced_Block_Name() { - - $s0 = $this->peg_currPos; - $s1 = $this->peg_currPos; - $s2 = $this->peg_parseBlock_Name_Part(); - if ($s2 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c11) { - $s3 = $this->peg_c11; - $this->peg_currPos++; - } else { - $s3 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c12); - } - } - if ($s3 !== $this->peg_FAILED) { - $s4 = $this->peg_parseBlock_Name_Part(); - if ($s4 !== $this->peg_FAILED) { - $s2 = array($s2, $s3, $s4); - $s1 = $s2; - } else { - $this->peg_currPos = $s1; - $s1 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s1; - $s1 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s1; - $s1 = $this->peg_FAILED; - } - if ($s1 !== $this->peg_FAILED) { - $s0 = $this->input_substr($s0, $this->peg_currPos - $s0); - } else { - $s0 = $s1; - } - - return $s0; - } - - private function peg_parseCore_Block_Name() { - - $s0 = $this->peg_currPos; - $s1 = $this->peg_currPos; - $s2 = $this->peg_parseBlock_Name_Part(); - if ($s2 !== $this->peg_FAILED) { - $s1 = $this->input_substr($s1, $this->peg_currPos - $s1); - } else { - $s1 = $s2; - } - if ($s1 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f7($s1); - } - $s0 = $s1; - - return $s0; - } - - private function peg_parseBlock_Name_Part() { - - $s0 = $this->peg_currPos; - $s1 = $this->peg_currPos; - if (Gutenberg_PEG_peg_char_class_test($this->peg_c13, $this->input_substr($this->peg_currPos, 1))) { - $s2 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s2 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c14); - } - } - if ($s2 !== $this->peg_FAILED) { - $s3 = array(); - if (Gutenberg_PEG_peg_char_class_test($this->peg_c15, $this->input_substr($this->peg_currPos, 1))) { - $s4 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s4 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c16); - } - } - while ($s4 !== $this->peg_FAILED) { - $s3[] = $s4; - if (Gutenberg_PEG_peg_char_class_test($this->peg_c15, $this->input_substr($this->peg_currPos, 1))) { - $s4 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s4 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c16); - } - } - } - if ($s3 !== $this->peg_FAILED) { - $s2 = array($s2, $s3); - $s1 = $s2; - } else { - $this->peg_currPos = $s1; - $s1 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s1; - $s1 = $this->peg_FAILED; - } - if ($s1 !== $this->peg_FAILED) { - $s0 = $this->input_substr($s0, $this->peg_currPos - $s0); - } else { - $s0 = $s1; - } - - return $s0; - } - - private function peg_parseBlock_Attributes() { - - $this->peg_silentFails++; - $s0 = $this->peg_currPos; - $s1 = $this->peg_currPos; - $s2 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c18) { - $s3 = $this->peg_c18; - $this->peg_currPos++; - } else { - $s3 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c19); - } - } - if ($s3 !== $this->peg_FAILED) { - $s4 = array(); - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c20) { - $s8 = $this->peg_c20; - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c21); - } - } - if ($s8 !== $this->peg_FAILED) { - $s9 = $this->peg_parse__(); - if ($s9 !== $this->peg_FAILED) { - $s10 = $this->peg_c22; - if ($s10 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c11) { - $s11 = $this->peg_c11; - $this->peg_currPos++; - } else { - $s11 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c12); - } - } - if ($s11 === $this->peg_FAILED) { - $s11 = null; - } - if ($s11 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c7) { - $s12 = $this->peg_c7; - $this->peg_currPos += 3; - } else { - $s12 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c8); - } - } - if ($s12 !== $this->peg_FAILED) { - $s8 = array($s8, $s9, $s10, $s11, $s12); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s7 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s7 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s7 !== $this->peg_FAILED) { - $s6 = array($s6, $s7); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - while ($s5 !== $this->peg_FAILED) { - $s4[] = $s5; - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c20) { - $s8 = $this->peg_c20; - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c21); - } - } - if ($s8 !== $this->peg_FAILED) { - $s9 = $this->peg_parse__(); - if ($s9 !== $this->peg_FAILED) { - $s10 = $this->peg_c22; - if ($s10 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c11) { - $s11 = $this->peg_c11; - $this->peg_currPos++; - } else { - $s11 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c12); - } - } - if ($s11 === $this->peg_FAILED) { - $s11 = null; - } - if ($s11 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c7) { - $s12 = $this->peg_c7; - $this->peg_currPos += 3; - } else { - $s12 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c8); - } - } - if ($s12 !== $this->peg_FAILED) { - $s8 = array($s8, $s9, $s10, $s11, $s12); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s7 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s7 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s7 !== $this->peg_FAILED) { - $s6 = array($s6, $s7); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } - if ($s4 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c20) { - $s5 = $this->peg_c20; - $this->peg_currPos++; - } else { - $s5 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c21); - } - } - if ($s5 !== $this->peg_FAILED) { - $s3 = array($s3, $s4, $s5); - $s2 = $s3; - } else { - $this->peg_currPos = $s2; - $s2 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s2; - $s2 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s2; - $s2 = $this->peg_FAILED; - } - if ($s2 !== $this->peg_FAILED) { - $s1 = $this->input_substr($s1, $this->peg_currPos - $s1); - } else { - $s1 = $s2; - } - if ($s1 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f8($s1); - } - $s0 = $s1; - $this->peg_silentFails--; - if ($s0 === $this->peg_FAILED) { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c17); - } - } - - return $s0; - } - - private function peg_parse__() { - - $s0 = array(); - if (Gutenberg_PEG_peg_char_class_test($this->peg_c23, $this->input_substr($this->peg_currPos, 1))) { - $s1 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c24); - } - } - if ($s1 !== $this->peg_FAILED) { - while ($s1 !== $this->peg_FAILED) { - $s0[] = $s1; - if (Gutenberg_PEG_peg_char_class_test($this->peg_c23, $this->input_substr($this->peg_currPos, 1))) { - $s1 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c24); - } - } - } - } else { - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - public function parse($input) { - $arguments = func_get_args(); - $options = count($arguments) > 1 ? $arguments[1] : array(); - $this->cleanup_state(); - - if (is_array($input)) { - $this->input = $input; - } else { - preg_match_all("/./us", $input, $match); - $this->input = $match[0]; - } - $this->input_length = count($this->input); - - $this->peg_FAILED = new stdClass; - $this->peg_c0 = array("type" => "any", "description" => "any character" ); - $this->peg_c1 = ""; - $this->peg_c6 = array( "type" => "literal", "value" => "/-->", "description" => "\"/-->\"" ); - $this->peg_c7 = "-->"; - $this->peg_c8 = array( "type" => "literal", "value" => "-->", "description" => "\"-->\"" ); - $this->peg_c9 = "/wp:"; - $this->peg_c10 = array( "type" => "literal", "value" => "/wp:", "description" => "\"/wp:\"" ); - $this->peg_c11 = "/"; - $this->peg_c12 = array( "type" => "literal", "value" => "/", "description" => "\"/\"" ); - $this->peg_c13 = array(array(97,122)); - $this->peg_c14 = array( "type" => "class", "value" => "[a-z]", "description" => "[a-z]" ); - $this->peg_c15 = array(array(97,122), array(48,57), array(95,95), array(45,45)); - $this->peg_c16 = array( "type" => "class", "value" => "[a-z0-9_-]", "description" => "[a-z0-9_-]" ); - $this->peg_c17 = array("type" => "other", "description" => "JSON-encoded attributes embedded in a block's opening comment" ); - $this->peg_c18 = "{"; - $this->peg_c19 = array( "type" => "literal", "value" => "{", "description" => "\"{\"" ); - $this->peg_c20 = "}"; - $this->peg_c21 = array( "type" => "literal", "value" => "}", "description" => "\"}\"" ); - $this->peg_c22 = ""; - $this->peg_c23 = array(array(32,32), array(9,9), array(13,13), array(10,10)); - $this->peg_c24 = array( "type" => "class", "value" => "[ \t\r\n]", "description" => "[ \t\r\n]" ); - - $peg_startRuleFunctions = array( 'Block_List' => array($this, "peg_parseBlock_List") ); - $peg_startRuleFunction = array($this, "peg_parseBlock_List"); - if (isset($options["startRule"])) { - if (!(isset($peg_startRuleFunctions[$options["startRule"]]))) { - throw new Exception("Can't start parsing from rule \"" + $options["startRule"] + "\"."); - } - - $peg_startRuleFunction = $peg_startRuleFunctions[$options["startRule"]]; - } - - /* BEGIN initializer code */ - - // The `maybeJSON` function is not needed in PHP because its return semantics - // are the same as `json_decode` - - if ( ! function_exists( 'peg_empty_attrs' ) ) { - function peg_empty_attrs() { - static $empty_attrs = null; - - if ( null === $empty_attrs ) { - $empty_attrs = json_decode( '{}', true ); - } - - return $empty_attrs; - } - } - - // array arguments are backwards because of PHP - if ( ! function_exists( 'peg_process_inner_content' ) ) { - function peg_process_inner_content( $array ) { - $html = ''; - $blocks = array(); - $content = array(); - - foreach ( $array as $item ) { - if ( is_string( $item ) ) { - $html .= $item; - $content[] = $item; - } else { - $blocks[] = $item; - $content[] = null; - } - } - - return array( $html, $blocks, $content ); - } - } - - if ( ! function_exists( 'peg_join_blocks' ) ) { - function peg_join_blocks( $pre, $tokens, $post ) { - $blocks = array(); - - if ( ! empty( $pre ) ) { - $blocks[] = array( - 'blockName' => null, - 'attrs' => peg_empty_attrs(), - 'innerBlocks' => array(), - 'innerHTML' => $pre, - 'innerContent' => array( $pre ), - ); - } - - foreach ( $tokens as $token ) { - list( $token, $html ) = $token; - - $blocks[] = $token; - - if ( ! empty( $html ) ) { - $blocks[] = array( - 'blockName' => null, - 'attrs' => peg_empty_attrs(), - 'innerBlocks' => array(), - 'innerHTML' => $html, - 'innerContent' => array( $html ), - ); - } - } - - if ( ! empty( $post ) ) { - $blocks[] = array( - 'blockName' => null, - 'attrs' => peg_empty_attrs(), - 'innerBlocks' => array(), - 'innerHTML' => $post, - 'innerContent' => array( $post ), - ); - } - - return $blocks; - } - } - - - /* END initializer code */ - - $peg_result = call_user_func($peg_startRuleFunction); - - if ($peg_result !== $this->peg_FAILED && $this->peg_currPos === $this->input_length) { - $this->cleanup_state(); // Free up memory - return $peg_result; - } else { - if ($peg_result !== $this->peg_FAILED && $this->peg_currPos < $this->input_length) { - $this->peg_fail(array("type" => "end", "description" => "end of input" )); - } - - $exception = $this->peg_buildException(null, $this->peg_maxFailExpected, $this->peg_maxFailPos); - $this->cleanup_state(); // Free up memory - throw $exception; - } - } - -}; \ No newline at end of file diff --git a/lib/plugin-compat.php b/lib/plugin-compat.php deleted file mode 100644 index 963535624de4c3..00000000000000 --- a/lib/plugin-compat.php +++ /dev/null @@ -1,34 +0,0 @@ - tags. This adds a filter prior to saving the post via - * REST API to disable markdown support. Disables markdown support provided by - * plugins Jetpack, JP-Markdown, and WP Editor.MD - * - * @since 1.3.0 - * - * @param array $post Post object which contains content to check for block. - * @return array $post Post object. - */ -function gutenberg_remove_wpcom_markdown_support( $post ) { - if ( class_exists( 'WPCom_Markdown' ) && has_blocks( $post['post_content'] ) ) { - WPCom_Markdown::get_instance()->unload_markdown_for_posts(); - } - return $post; -} -add_filter( 'wp_insert_post_data', 'gutenberg_remove_wpcom_markdown_support', 9 ); diff --git a/lib/register.php b/lib/register.php deleted file mode 100644 index b86df4fd7a37f0..00000000000000 --- a/lib/register.php +++ /dev/null @@ -1,633 +0,0 @@ -post_type; - - if ( ! gutenberg_can_edit_post( $post->ID ) ) { - return; - } - } else { - // Eventually add handling for creating new posts of different types in Gutenberg. - } - $post_type = $post->post_type; - $post_type_object = get_post_type_object( $post_type ); - - if ( ! gutenberg_can_edit_post_type( $post_type ) ) { - return; - } - - // Disable hidden metaboxes because there's no UI to toggle visibility. - add_filter( 'hidden_meta_boxes', '__return_empty_array' ); - - $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ); - if ( ! $thumbnail_support && 'attachment' === $post_type && $post->post_mime_type ) { - if ( wp_attachment_is( 'audio', $post ) ) { - $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' ); - } elseif ( wp_attachment_is( 'video', $post ) ) { - $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' ); - } - } - - /* - * WIP: Collect and send information needed to render meta boxes. - * From wp-admin/edit-form-advanced.php - * Relevant code there: - * do_action( 'do_meta_boxes', $post_type, {'normal','advanced','side'}, $post ); - * do_meta_boxes( $post_type, 'side', $post ); - * do_meta_boxes( null, 'normal', $post ); - * do_meta_boxes( null, 'advanced', $post ); - */ - $publish_callback_args = null; - if ( post_type_supports( $post_type, 'revisions' ) && 'auto-draft' !== $post->post_status ) { - $revisions = wp_get_post_revisions( $post->ID ); - - // We should aim to show the revisions meta box only when there are revisions. - if ( count( $revisions ) > 1 ) { - reset( $revisions ); // Reset pointer for key(). - $publish_callback_args = array( - 'revisions_count' => count( $revisions ), - 'revision_id' => key( $revisions ), - ); - add_meta_box( 'revisionsdiv', __( 'Revisions', 'gutenberg' ), 'post_revisions_meta_box', $screen, 'normal', 'core' ); - } - } - - if ( 'attachment' == $post_type ) { - wp_enqueue_script( 'image-edit' ); - wp_enqueue_style( 'imgareaselect' ); - add_meta_box( 'submitdiv', __( 'Save', 'gutenberg' ), 'attachment_submit_meta_box', $screen, 'side', 'core' ); - add_action( 'edit_form_after_title', 'edit_form_image_editor' ); - - if ( wp_attachment_is( 'audio', $post ) ) { - add_meta_box( 'attachment-id3', __( 'Metadata', 'gutenberg' ), 'attachment_id3_data_meta_box', $screen, 'normal', 'core' ); - } - } else { - add_meta_box( 'submitdiv', __( 'Publish', 'gutenberg' ), 'post_submit_meta_box', $screen, 'side', 'core', $publish_callback_args ); - } - - if ( current_theme_supports( 'post-formats' ) && post_type_supports( $post_type, 'post-formats' ) ) { - add_meta_box( 'formatdiv', _x( 'Format', 'post format', 'gutenberg' ), 'post_format_meta_box', $screen, 'side', 'core' ); - } - - // All taxonomies. - foreach ( get_object_taxonomies( $post ) as $tax_name ) { - $taxonomy = get_taxonomy( $tax_name ); - if ( ! $taxonomy->show_ui || false === $taxonomy->meta_box_cb ) { - continue; - } - - $label = $taxonomy->labels->name; - - if ( ! is_taxonomy_hierarchical( $tax_name ) ) { - $tax_meta_box_id = 'tagsdiv-' . $tax_name; - } else { - $tax_meta_box_id = $tax_name . 'div'; - } - - add_meta_box( $tax_meta_box_id, $label, $taxonomy->meta_box_cb, $screen, 'side', 'core', array( 'taxonomy' => $tax_name ) ); - } - - if ( post_type_supports( $post_type, 'page-attributes' ) || count( get_page_templates( $post ) ) > 0 ) { - add_meta_box( 'pageparentdiv', $post_type_object->labels->attributes, 'page_attributes_meta_box', $screen, 'side', 'core' ); - } - - if ( $thumbnail_support && current_user_can( 'upload_files' ) ) { - add_meta_box( 'postimagediv', esc_html( $post_type_object->labels->featured_image ), 'post_thumbnail_meta_box', $screen, 'side', 'low' ); - } - - if ( post_type_supports( $post_type, 'excerpt' ) ) { - add_meta_box( 'postexcerpt', __( 'Excerpt', 'gutenberg' ), 'post_excerpt_meta_box', $screen, 'normal', 'core' ); - } - - if ( post_type_supports( $post_type, 'trackbacks' ) ) { - add_meta_box( 'trackbacksdiv', __( 'Send Trackbacks', 'gutenberg' ), 'post_trackback_meta_box', $screen, 'normal', 'core' ); - } - - if ( post_type_supports( $post_type, 'custom-fields' ) ) { - add_meta_box( 'postcustom', __( 'Custom Fields', 'gutenberg' ), 'post_custom_meta_box', $screen, 'normal', 'core' ); - } - - /** - * Fires in the middle of built-in meta box registration. - * - * @since 2.1.0 - * @deprecated 3.7.0 Use 'add_meta_boxes' instead. - * - * @param WP_Post $post Post object. - */ - do_action( 'dbx_post_advanced', $post ); - - // Allow the Discussion meta box to show up if the post type supports comments, - // or if comments or pings are open. - if ( comments_open( $post ) || pings_open( $post ) || post_type_supports( $post_type, 'comments' ) ) { - add_meta_box( 'commentstatusdiv', __( 'Discussion', 'gutenberg' ), 'post_comment_status_meta_box', $screen, 'normal', 'core' ); - } - - $stati = get_post_stati( array( 'public' => true ) ); - if ( empty( $stati ) ) { - $stati = array( 'publish' ); - } - $stati[] = 'private'; - - if ( in_array( get_post_status( $post ), $stati ) ) { - // If the post type support comments, or the post has comments, allow the - // Comments meta box. - if ( comments_open( $post ) || pings_open( $post ) || $post->comment_count > 0 || post_type_supports( $post_type, 'comments' ) ) { - add_meta_box( 'commentsdiv', __( 'Comments', 'gutenberg' ), 'post_comment_meta_box', $screen, 'normal', 'core' ); - } - } - - if ( ! ( 'pending' == get_post_status( $post ) && ! current_user_can( $post_type_object->cap->publish_posts ) ) ) { - add_meta_box( 'slugdiv', __( 'Slug', 'gutenberg' ), 'post_slug_meta_box', $screen, 'normal', 'core' ); - } - - if ( post_type_supports( $post_type, 'author' ) && current_user_can( $post_type_object->cap->edit_others_posts ) ) { - add_meta_box( 'authordiv', __( 'Author', 'gutenberg' ), 'post_author_meta_box', $screen, 'normal', 'core' ); - } - - // Run the hooks for adding meta boxes for a specific post type. - do_action( 'add_meta_boxes', $post_type, $post ); - do_action( "add_meta_boxes_{$post_type}", $post ); - - // Set up meta box locations. - $locations = array( 'normal', 'advanced', 'side' ); - - // Foreach location run the hooks meta boxes are potentially registered on. - foreach ( $locations as $location ) { - do_action( - 'do_meta_boxes', - $screen, - $location, - $post - ); - } - do_action( 'edit_form_advanced', $post ); - - // Copy meta box state. - $_meta_boxes_copy = $wp_meta_boxes; - - /** - * Documented in lib/meta-box-partial-page.php - * - * @param array $wp_meta_boxes Global meta box state. - */ - $_meta_boxes_copy = apply_filters( 'filter_gutenberg_meta_boxes', $_meta_boxes_copy ); - - // Redirect to classic editor if a meta box is incompatible. - foreach ( $locations as $location ) { - if ( ! isset( $_meta_boxes_copy[ $post->post_type ][ $location ] ) ) { - continue; - } - // Check if we have a meta box that has declared itself incompatible with the block editor. - foreach ( $_meta_boxes_copy[ $post->post_type ][ $location ] as $boxes ) { - foreach ( $boxes as $box ) { - /* - * If __block_editor_compatible_meta_box is declared as a false-y value, - * the meta box is not compatible with the block editor. - */ - if ( is_array( $box['args'] ) - && isset( $box['args']['__block_editor_compatible_meta_box'] ) - && ! $box['args']['__block_editor_compatible_meta_box'] ) { - $incompatible_meta_box = true; - ?> - - post_status ) { - $can_edit = false; - } - - if ( $can_edit && ! gutenberg_can_edit_post_type( $post->post_type ) ) { - $can_edit = false; - } - - if ( $can_edit && ! current_user_can( 'edit_post', $post->ID ) ) { - $can_edit = false; - } - - // Disable the editor if on the blog page and there is no content. - if ( $can_edit && absint( get_option( 'page_for_posts' ) ) === $post->ID && empty( $post->post_content ) ) { - $can_edit = false; - } - - /** - * Filter to allow plugins to enable/disable Gutenberg for particular post. - * - * @since 3.5 - * - * @param bool $can_edit Whether the post can be edited or not. - * @param WP_Post $post The post being checked. - */ - return apply_filters( 'gutenberg_can_edit_post', $can_edit, $post ); - -} - -/** - * Return whether the post type can be edited in Gutenberg. - * - * Gutenberg depends on the REST API, and if the post type is not shown in the - * REST API, then the post cannot be edited in Gutenberg. - * - * @since 1.5.2 - * - * @param string $post_type The post type. - * @return bool Whether the post type can be edited with Gutenberg. - */ -function gutenberg_can_edit_post_type( $post_type ) { - $can_edit = true; - if ( ! post_type_exists( $post_type ) ) { - $can_edit = false; - } - - if ( ! post_type_supports( $post_type, 'editor' ) ) { - $can_edit = false; - } - - $post_type_object = get_post_type_object( $post_type ); - if ( $post_type_object && ! $post_type_object->show_in_rest ) { - $can_edit = false; - } - - /** - * Filter to allow plugins to enable/disable Gutenberg for particular post types. - * - * @since 1.5.2 - * - * @param bool $can_edit Whether the post type can be edited or not. - * @param string $post_type The post type being checked. - */ - return apply_filters( 'gutenberg_can_edit_post_type', $can_edit, $post_type ); -} - -if ( ! function_exists( 'has_blocks' ) ) { - /** - * Determine whether a post or content string has blocks. - * - * This test optimizes for performance rather than strict accuracy, detecting - * the pattern of a block but not validating its structure. For strict accuracy - * you should use the block parser on post content. - * - * @since 3.6.0 - * @see gutenberg_parse_blocks() - * - * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post. - * @return bool Whether the post has blocks. - */ - function has_blocks( $post = null ) { - if ( ! is_string( $post ) ) { - $wp_post = get_post( $post ); - if ( $wp_post instanceof WP_Post ) { - $post = $wp_post->post_content; - } - } - - return false !== strpos( (string) $post, ' -### Usage +### setup -To make the `wp.a11y.speak` functionality more universally available, we've decided to create a dedicated JS module for it, called `speak`. Usage is very simple: +[src/index.js#L16-L26](src/index.js#L16-L26) -```JS +Create the live regions. + +### speak + +[src/index.js#L52-L66](src/index.js#L52-L66) + +Allows you to easily announce dynamic interface updates to screen readers using ARIA live regions. +This module is inspired by the `speak` function in wp-a11y.js + +**Usage** + +```js import { speak } from '@wordpress/a11y'; // For polite messages that shouldn't interrupt what screen readers are currently announcing. @@ -30,26 +41,38 @@ speak( 'The message you want to send to the ARIA live region' ); speak( 'The message you want to send to the ARIA live region', 'assertive' ); ``` +**Parameters** + +- **message** `string`: The message to be announced by Assistive Technologies. +- **ariaLive** `string`: Optional. The politeness level for aria-live. Possible values: polite or assertive. Default polite. + + + + ### Background + For context I'll quote [this article on WordPress.org](https://make.wordpress.org/accessibility/2015/04/15/let-wordpress-speak-new-in-wordpress-4-2/) by [@joedolson](https://github.com/joedolson): > #### Why. +> > In modern web development, updating discrete regions of a screen with JavaScript is common. The use of AJAX responses in modern JavaScript-based User Interfaces allows web developers to create beautiful interfaces similar to Desktop applications that don’t require pages to reload or refresh. - +> > JavaScript can create great usability improvements for most users – but when content is updated dynamically, it has the potential to introduce accessibility issues. This is one of the steps you can take to handle that problem. - +> > #### What. +> > When a portion of a page is updated with JavaScript, the update is usually highlighted with animation and bright colors, and is easy to see. But if you don’t have the ability to see the screen, you don’t know this has happened, unless the updated region is marked as an ARIA-live region. - +> > If this isn’t marked, there’s no notification for screen readers. But it’s also possible that all the region content will be announced after an update, if the ARIA live region is too large. You want to provide users with just a simple, concise message. - +> > #### How. +> > That’s what `wp.a11y.speak()` is meant for. It’s a simple tool that creates and appends an ARIA live notifications area to the