diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 22a1f4852b1434..4391fa3fd2b83f 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,7 +1,7 @@
# Documentation
-/docs @youknowriad @chrisvanpatten @ajitbohra @notnownikki
-/docs/designers-developers/developers @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @notnownikki
-/docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra @notnownikki
+/docs @youknowriad @chrisvanpatten @ajitbohra
+/docs/designers-developers/developers @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra
+/docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra
# Data
/packages/api-fetch @youknowriad @aduth @nerrad @mmtr
@@ -10,7 +10,7 @@
/packages/redux-routine @youknowriad @aduth @nerrad
# Blocks
-/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan @notnownikki
+/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan
# Editor
/packages/annotations @youknowriad @aduth @atimmer @ellatrix
@@ -29,7 +29,7 @@
# Tooling
/bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw
-/docs/tool @youknowriad @gziolo @chrisvanpatten @ajitbohra @nosolosw @notnownikki
+/docs/tool @youknowriad @gziolo @chrisvanpatten @ajitbohra @nosolosw
/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
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index cb60c1c2f0354d..bef89730704cbb 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -19,4 +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.
+- [ ] I've included developer documentation if appropriate.
diff --git a/.travis.yml b/.travis.yml
index 60a40cec454eb7..9e42ed6163f3a5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -160,3 +160,16 @@ jobs:
env: WP_VERSION=latest SWITCH_TO_PHP=5.2
script:
- ./bin/run-wp-unit-tests.sh
+
+before_deploy:
+ - npm install
+ - npm run playground:build -- --public-url '/gutenberg'
+
+deploy:
+ provider: pages
+ skip_cleanup: true
+ github_token: $GITHUB_TOKEN
+ keep_history: true
+ local_dir: playground/dist
+ on:
+ branch: master
diff --git a/bin/packages/build.js b/bin/packages/build.js
index 9639f8b232f70e..e1a6bd7142f80d 100755
--- a/bin/packages/build.js
+++ b/bin/packages/build.js
@@ -64,6 +64,43 @@ function createStyleEntryTransform() {
} );
}
+/**
+ * Returns a stream transform which maps an individual block.json to the
+ * index.js that imports it. Presently, babel resolves the import of json
+ * files by inlining them as a JavaScript primitive in the importing file.
+ * This transform ensures the importing file is rebuilt.
+ *
+ * @return {Transform} Stream transform instance.
+ */
+function createBlockJsonEntryTransform() {
+ const blocks = new Set;
+
+ return new Transform( {
+ objectMode: true,
+ async transform( file, encoding, callback ) {
+ const matches = /block-library[\/\\]src[\/\\](.*)[\/\\]block.json$/.exec( file );
+ const blockName = matches ? matches[ 1 ] : undefined;
+
+ // Only block.json files in the block-library folder are subject to this transform.
+ if ( ! blockName ) {
+ this.push( file );
+ callback();
+ return;
+ }
+
+ // Only operate once per block, assuming entries are common.
+ if ( blockName && blocks.has( blockName ) ) {
+ callback();
+ return;
+ }
+
+ blocks.add( blockName );
+ this.push( file.replace( 'block.json', 'index.js' ) );
+ callback();
+ },
+ } );
+}
+
let onFileComplete = () => {};
let stream;
@@ -72,7 +109,9 @@ if ( files.length ) {
stream = new Readable( { encoding: 'utf8' } );
files.forEach( ( file ) => stream.push( file ) );
stream.push( null );
- stream = stream.pipe( createStyleEntryTransform() );
+ stream = stream
+ .pipe( createStyleEntryTransform() )
+ .pipe( createBlockJsonEntryTransform() );
} else {
const bar = new ProgressBar( 'Build Progress: [:bar] :percent', {
width: 30,
diff --git a/composer.json b/composer.json
index 864a34d9c092f7..e869eb3ccc7fff 100644
--- a/composer.json
+++ b/composer.json
@@ -11,10 +11,10 @@
"issues": "https://github.com/WordPress/gutenberg/issues"
},
"require-dev": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
- "squizlabs/php_codesniffer": "^3.1",
- "wimg/php-compatibility": "^8",
- "wp-coding-standards/wpcs": "^1.0.0"
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
+ "squizlabs/php_codesniffer": "^3.4.2",
+ "phpcompatibility/php-compatibility": "^9.2.0",
+ "wp-coding-standards/wpcs": "^2.1.1"
},
"require": {
"composer/installers": "~1.0"
diff --git a/composer.lock b/composer.lock
index a7c30dab21f451..e6bdc5bf4f1473 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "14614d0ab7be7d1f70eeae5bcd5f125d",
+ "content-hash": "78aec8b4f7bcfa500026de9ddb1abace",
"packages": [
{
"name": "composer/installers",
- "version": "v1.5.0",
+ "version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/composer/installers.git",
- "reference": "049797d727261bf27f2690430d935067710049c2"
+ "reference": "cfcca6b1b60bc4974324efb5783c13dca6932b5b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/installers/zipball/049797d727261bf27f2690430d935067710049c2",
- "reference": "049797d727261bf27f2690430d935067710049c2",
+ "url": "https://api.github.com/repos/composer/installers/zipball/cfcca6b1b60bc4974324efb5783c13dca6932b5b",
+ "reference": "cfcca6b1b60bc4974324efb5783c13dca6932b5b",
"shasum": ""
},
"require": {
@@ -124,35 +124,33 @@
"zend",
"zikula"
],
- "time": "2017-12-29T09:13:20+00:00"
+ "time": "2018-08-27T06:10:37+00:00"
}
],
"packages-dev": [
{
"name": "dealerdirect/phpcodesniffer-composer-installer",
- "version": "v0.4.4",
+ "version": "v0.5.0",
"source": {
"type": "git",
"url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
- "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08"
+ "reference": "e749410375ff6fb7a040a68878c656c2e610b132"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/2e41850d5f7797cbb1af7b030d245b3b24e63a08",
- "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08",
+ "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/e749410375ff6fb7a040a68878c656c2e610b132",
+ "reference": "e749410375ff6fb7a040a68878c656c2e610b132",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0",
"php": "^5.3|^7",
- "squizlabs/php_codesniffer": "*"
+ "squizlabs/php_codesniffer": "^2|^3"
},
"require-dev": {
"composer/composer": "*",
- "wimg/php-compatibility": "^8.0"
- },
- "suggest": {
- "dealerdirect/qa-tools": "All the PHP QA tools you'll need"
+ "phpcompatibility/php-compatibility": "^9.0",
+ "sensiolabs/security-checker": "^4.1.0"
},
"type": "composer-plugin",
"extra": {
@@ -170,13 +168,13 @@
"authors": [
{
"name": "Franck Nijhof",
- "email": "f.nijhof@dealerdirect.nl",
- "homepage": "http://workingatdealerdirect.eu",
- "role": "Developer"
+ "email": "franck.nijhof@dealerdirect.com",
+ "homepage": "http://www.frenck.nl",
+ "role": "Developer / IT Manager"
}
],
"description": "PHP_CodeSniffer Standards Composer Installer Plugin",
- "homepage": "http://workingatdealerdirect.eu",
+ "homepage": "http://www.dealerdirect.com",
"keywords": [
"PHPCodeSniffer",
"PHP_CodeSniffer",
@@ -194,135 +192,142 @@
"stylecheck",
"tests"
],
- "time": "2017-12-06T16:27:17+00:00"
+ "time": "2018-10-26T13:21:45+00:00"
},
{
- "name": "squizlabs/php_codesniffer",
- "version": "3.3.1",
+ "name": "phpcompatibility/php-compatibility",
+ "version": "9.2.0",
"source": {
"type": "git",
- "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
- "reference": "628a481780561150481a9ec74709092b9759b3ec"
+ "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
+ "reference": "3db1bf1e28123fd574a4ae2e9a84072826d51b5e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/628a481780561150481a9ec74709092b9759b3ec",
- "reference": "628a481780561150481a9ec74709092b9759b3ec",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/3db1bf1e28123fd574a4ae2e9a84072826d51b5e",
+ "reference": "3db1bf1e28123fd574a4ae2e9a84072826d51b5e",
"shasum": ""
},
"require": {
- "ext-simplexml": "*",
- "ext-tokenizer": "*",
- "ext-xmlwriter": "*",
- "php": ">=5.4.0"
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
+ },
+ "conflict": {
+ "squizlabs/php_codesniffer": "2.6.2"
},
"require-dev": {
- "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+ "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
},
- "bin": [
- "bin/phpcs",
- "bin/phpcbf"
- ],
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.x-dev"
- }
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
},
+ "type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
"license": [
- "BSD-3-Clause"
+ "LGPL-3.0-or-later"
],
"authors": [
{
- "name": "Greg Sherwood",
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
+ },
+ {
+ "name": "Wim Godden",
+ "homepage": "https://github.com/wimg",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
"role": "lead"
}
],
- "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
- "homepage": "http://www.squizlabs.com/php-codesniffer",
+ "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
+ "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
"keywords": [
+ "compatibility",
"phpcs",
"standards"
],
- "time": "2018-07-26T23:47:18+00:00"
+ "time": "2019-06-27T19:58:56+00:00"
},
{
- "name": "wimg/php-compatibility",
- "version": "8.2.0",
+ "name": "squizlabs/php_codesniffer",
+ "version": "3.4.2",
"source": {
"type": "git",
- "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
- "reference": "eaf613c1a8265bcfd7b0ab690783f2aef519f78a"
+ "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+ "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/eaf613c1a8265bcfd7b0ab690783f2aef519f78a",
- "reference": "eaf613c1a8265bcfd7b0ab690783f2aef519f78a",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8",
+ "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8",
"shasum": ""
},
"require": {
- "php": ">=5.3",
- "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
- },
- "conflict": {
- "squizlabs/php_codesniffer": "2.6.2"
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.4.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
- },
- "suggest": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
- "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
- "type": "phpcodesniffer-standard",
- "autoload": {
- "psr-4": {
- "PHPCompatibility\\": "PHPCompatibility/"
+ "bin": [
+ "bin/phpcs",
+ "bin/phpcbf"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPL-3.0-or-later"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Wim Godden",
+ "name": "Greg Sherwood",
"role": "lead"
}
],
- "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP version compatibility.",
- "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
"keywords": [
- "compatibility",
"phpcs",
"standards"
],
- "time": "2018-07-17T13:42:26+00:00"
+ "time": "2019-04-10T23:49:02+00:00"
},
{
"name": "wp-coding-standards/wpcs",
- "version": "1.0.0",
+ "version": "2.1.1",
"source": {
"type": "git",
- "url": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git",
- "reference": "539c6d74e6207daa22b7ea754d6f103e9abb2755"
+ "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
+ "reference": "bd9c33152115e6741e3510ff7189605b35167908"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/WordPress-Coding-Standards/WordPress-Coding-Standards/zipball/539c6d74e6207daa22b7ea754d6f103e9abb2755",
- "reference": "539c6d74e6207daa22b7ea754d6f103e9abb2755",
+ "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/bd9c33152115e6741e3510ff7189605b35167908",
+ "reference": "bd9c33152115e6741e3510ff7189605b35167908",
"shasum": ""
},
"require": {
- "php": ">=5.3",
- "squizlabs/php_codesniffer": "^2.9.0 || ^3.0.2"
+ "php": ">=5.4",
+ "squizlabs/php_codesniffer": "^3.3.1"
},
"require-dev": {
- "phpcompatibility/php-compatibility": "*"
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"suggest": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
},
"type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
@@ -341,7 +346,7 @@
"standards",
"wordpress"
],
- "time": "2018-07-25T18:10:35+00:00"
+ "time": "2019-05-21T02:50:00+00:00"
}
],
"aliases": [],
diff --git a/docs/contributors/release.md b/docs/contributors/release.md
index bfbc84636b3a50..c9d4a99715dd9c 100644
--- a/docs/contributors/release.md
+++ b/docs/contributors/release.md
@@ -6,18 +6,40 @@ To release Gutenberg, you need commit access to the [WordPress.org plugin reposi
## Plugin Releases
-### Versioning
+### Schedule
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.
-### 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.
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.
+Two days after the first release candidate, the stable version is created based on the last release candidate and any necessary regression fixes.
+
+Once the stable version is released, a post [like this](https://make.wordpress.org/core/2019/06/26/whats-new-in-gutenberg-26th-june/) describing the changes and performing a performance audit should be published.
+
+If critical bugs are discovered on stable versions of the plugin, patch versions can be released at any time.
+
+### Release Tool
+
+The plugin release process is entirely automated. To release the RC version of the plugin, run the following command and follow the instructions: (Note that at the time of writing, the tool doesn't support releasing multiple consecutive RC releases)
+
+```bash
+./bin/commander.js rc
+```
+
+To release a stable version, run:
+
+```bash
+./bin.commander.js stable
+```
+
+It is possible to run the "stable" release CLI in a consecutive way to release patch releases following the first stable release.
+
+### Manual Release Process
+
#### Creating the first Release Candidate
Releasing the first release candidate for this milestone (`x.x`) involves:
@@ -71,10 +93,6 @@ git push origin release/x.x
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.
-##### Publishing the Call For Testing
-
-Ping @karmatosed in 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`)
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:
diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md
index 7986be28bee4bc..9d78c4247e4ec4 100644
--- a/docs/designers-developers/developers/block-api/block-registration.md
+++ b/docs/designers-developers/developers/block-api/block-registration.md
@@ -520,7 +520,7 @@ transforms: {
* **Type:** `Array`
-Blocks are able to be inserted into blocks that use [`InnerBlocks`](/packages/block-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`](https://github.com/WordPress/gutenberg/blob/master/packages/block-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.
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 73448eadf0d023..f21c3b3f09988e 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
@@ -1,6 +1,17 @@
# Creating dynamic blocks
-Dynamic blocks are blocks that can change their content even if the post is not saved. One example from WordPress itself is the latest posts block. This block will update everywhere it is used when a new post is published.
+Dynamic blocks are blocks that build their structure and content on the fly when the block is rendered on the front end.
+
+There are two primary uses for dynamic blocks:
+
+1. Blocks where content should change even if a post has not been updated. One example from WordPress itself is the latest posts block. This block will update everywhere it is used when a new post is published.
+2. Blocks where updates to the code (HTML, CSS, JS) should be immediately shown on the front end of the website. For example, if you update the HTML structure of a block by adding a new class, adding a div, or changing the layout in any other way, if you want these changes to be applied immediately on all occurrences of that block across the site, a dynamic block should be used. (If a dynamic block is not used then when block code is updated Guterberg's [validation process](https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#validation) applies, causing users to see the validation message, "This block appears to have been modified externally").
+
+For many dynamic blocks, the `save` callback function should be returned as `null`, which tells the editor to save only the [block attributes](https://developer.wordpress.org/block-editor/developers/block-api/block-attributes/) to the database. These attributes are then passed into the server-side rendering callback, so you can decide how to display the block on the front end of your site. When you return `null`, the editor will skip the block markup validation process, avoiding issues with frequently-changing markup.
+
+You can also save an HTML representation of the block. If you provide a server-side rendering callback, this HTML will be replaced with the output of your callback, but will be rendered if your block is deactivated or your render callback is removed.
+
+Block attributes can be used for any content or setting you want to save for that block. In the first example above, with the latest posts block, the number of latest posts you want to show could be saved as an attribute. Or in the second example, attributes can be used for each piece of content you want to show in the front end - such as heading text, paragraph text, an image, a URL, etc.
The following code example shows how to create a dynamic block that shows only the last post as a link.
diff --git a/gutenberg.php b/gutenberg.php
index 5a03abdad7c309..977083ad622016 100644
--- a/gutenberg.php
+++ b/gutenberg.php
@@ -3,7 +3,7 @@
* Plugin Name: Gutenberg
* Plugin URI: https://github.com/WordPress/gutenberg
* Description: Printing since 1440. This is the development plugin for the new block editor in core.
- * Version: 6.0.0-rc.1
+ * Version: 6.0.0
* Author: Gutenberg Team
* Text Domain: gutenberg
*
diff --git a/lib/client-assets.php b/lib/client-assets.php
index 5e4d2651bb603d..724cf26f9c4023 100644
--- a/lib/client-assets.php
+++ b/lib/client-assets.php
@@ -628,7 +628,7 @@ function gutenberg_extend_block_editor_preload_paths( $preload_paths, $post ) {
$rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
$autosaves_path = sprintf( '/wp/v2/%s/%d/autosaves?context=edit', $rest_base, $post->ID );
- if ( ! in_array( $autosaves_path, $preload_paths ) ) {
+ if ( ! in_array( $autosaves_path, $preload_paths, true ) ) {
$preload_paths[] = $autosaves_path;
}
}
@@ -644,7 +644,7 @@ function gutenberg_extend_block_editor_preload_paths( $preload_paths, $post ) {
*/
$blocks_path = array( '/wp/v2/blocks', 'OPTIONS' );
- if ( ! in_array( $blocks_path, $preload_paths ) ) {
+ if ( ! in_array( $blocks_path, $preload_paths, true ) ) {
$preload_paths[] = $blocks_path;
}
diff --git a/package-lock.json b/package-lock.json
index 7b34929e4cd4c9..54148d3de97777 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "gutenberg",
- "version": "6.0.0-rc.1",
+ "version": "6.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2910,31 +2910,31 @@
}
},
"@tannin/compile": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.0.1.tgz",
- "integrity": "sha512-ymd9icvnkQin8UG4eRU3+xBc7gqTn/Kv5+EMY3ALWVwIl6j/7McWbCkxB8MgU40UaHJk8kLCk06wiKszXLdXWQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.0.3.tgz",
+ "integrity": "sha512-OkPHvaM/hIHdSco3+ZO1hzkOtfEddn5a0veWft2aDLvKnbdj9VusiLKNdEE9by3hCZIIcb9aWF+iBorhfrQOfw==",
"requires": {
- "@tannin/evaluate": "^1.0.0",
- "@tannin/postfix": "^1.0.0"
+ "@tannin/evaluate": "^1.1.1",
+ "@tannin/postfix": "^1.0.2"
}
},
"@tannin/evaluate": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@tannin/evaluate/-/evaluate-1.0.0.tgz",
- "integrity": "sha512-gO7YbJsD8sj5/nqUbFZv71Meu2++D9n4DZov/cWwp3YJbBwKShPlWwwlXr/0vz4vuxm/gys+3NiGbZkmhlXf0Q=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@tannin/evaluate/-/evaluate-1.1.1.tgz",
+ "integrity": "sha512-ALuSZHjrLHGnw0WxsHDHde74FJ2WW0Ck4rg3QBxFBCmxd6Wsac+e0HXfJ++Qion15LIOCmFhyVpWzawMgeBA8Q=="
},
"@tannin/plural-forms": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@tannin/plural-forms/-/plural-forms-1.0.1.tgz",
- "integrity": "sha512-SXutT+XLbMOECvmWDBSqIOHhS5hzWG9875HCFGKYgp8ghGPrJ4HZ325Xc0hsRThdjgrWMEQixlbpWl4SXOQTig==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tannin/plural-forms/-/plural-forms-1.0.3.tgz",
+ "integrity": "sha512-IUr9+FiCnzCiB9aRio3FVNR8TNL9SmX2zkV6tmfWWwSclX4uTCykoGsDhTGKK+sZnMrdPCTmb/OxbtGNdVyV4g==",
"requires": {
- "@tannin/compile": "^1.0.0"
+ "@tannin/compile": "^1.0.3"
}
},
"@tannin/postfix": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.0.0.tgz",
- "integrity": "sha512-59/mWwU7sXHfoU2kI3RcWRki2Jjbz5nEVJNBN4MUyIhPjXTebAcZqgsQACvlk+sjKVOTMEMHcrFrKQbaxz/1Dw=="
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.0.2.tgz",
+ "integrity": "sha512-Nggtk7/ljfNPpAX8CjxxLkMKuO6u2gH1ozmTvGclWF2pNcxTf6YGghYNYNWZRKrimXGhQ8yZqvAHep7h80K04g=="
},
"@types/babel__core": {
"version": "7.1.1",
@@ -3285,6 +3285,7 @@
"classnames": "^2.2.5",
"dom-scroll-into-view": "^1.2.1",
"lodash": "^4.17.10",
+ "react-spring": "^8.0.19",
"redux-multi": "^0.1.12",
"refx": "^3.0.0",
"rememo": "^3.0.0",
@@ -3381,7 +3382,7 @@
"memize": "^1.0.5",
"moment": "^2.22.1",
"mousetrap": "^1.6.2",
- "re-resizable": "^4.7.1",
+ "re-resizable": "^5.0.1",
"react-click-outside": "^3.0.0",
"react-dates": "^17.1.1",
"react-spring": "^8.0.20",
@@ -3649,7 +3650,7 @@
"lodash": "^4.17.11",
"memize": "^1.0.5",
"sprintf-js": "^1.1.1",
- "tannin": "^1.0.1"
+ "tannin": "^1.1.0"
}
},
"@wordpress/is-shallow-equal": {
@@ -9375,6 +9376,11 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
+ "fast-memoize": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.1.tgz",
+ "integrity": "sha512-xdmw296PCL01tMOXx9mdJSmWY29jQgxyuZdq0rEHMu+Tpe1eOEtCycoG6chzlcrWsNgpZP7oL8RiQr7+G6Bl6g=="
+ },
"fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
@@ -18366,9 +18372,12 @@
}
},
"re-resizable": {
- "version": "4.7.1",
- "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-4.7.1.tgz",
- "integrity": "sha512-pLJkPbZCe+3ml+9Q15z+R69qYZDsluj0KwrdFb8kSNaqDzYAveDUblf7voHH9hNTdKIiIvP8iIdGFFKSgffVaQ=="
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-5.0.1.tgz",
+ "integrity": "sha512-Iy8v5li7bhNBDxCN1DbA4l6G2Hk8NCZtcExoI1D+5pfvKyQcH8LH2P5h3DGoEfHhs0uyyRC1Qx8bHBomfrmxgA==",
+ "requires": {
+ "fast-memoize": "^2.5.1"
+ }
},
"react": {
"version": "16.8.4",
@@ -21146,11 +21155,11 @@
}
},
"tannin": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/tannin/-/tannin-1.0.1.tgz",
- "integrity": "sha512-dDtnwHQ63bS/Gz0ZLY+E+JCdRoTZkmoKDoC64y3hzAD2X2qrp8jSuWNUjtiYHA48mtj4Ens9xl4knAOm1t+rfQ==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/tannin/-/tannin-1.1.0.tgz",
+ "integrity": "sha512-LxhcXqpMHEOVeVKmuG5aCPPsTXFlO373vrWkqN7FSJBVLS6lFOAg8ZGzIyGhrOf7Ho3xB4jdGedY1gi/8J1FCA==",
"requires": {
- "@tannin/plural-forms": "^1.0.0"
+ "@tannin/plural-forms": "^1.0.3"
}
},
"tapable": {
diff --git a/package.json b/package.json
index 64a7a54a098d70..1087de7094828a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gutenberg",
- "version": "6.0.0-rc.1",
+ "version": "6.0.0",
"private": true,
"description": "A new WordPress editor experience.",
"author": "The WordPress Contributors",
diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md
index ff1a8579a0b016..f279d2994600d3 100644
--- a/packages/block-editor/README.md
+++ b/packages/block-editor/README.md
@@ -387,6 +387,19 @@ _Type_
- `Object`
+# **transformStyles**
+
+Applies a series of CSS rule transforms to wrap selectors inside a given class and/or rewrite URLs depending on the parameters passed.
+
+_Parameters_
+
+- _styles_ `Array`: CSS rules.
+- _wrapperClassName_ `string`: Wrapper Class Name.
+
+_Returns_
+
+- `Array`: converted rules.
+
# **URLInput**
_Related_
diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json
index da701fc0c08669..5add09430524e1 100644
--- a/packages/block-editor/package.json
+++ b/packages/block-editor/package.json
@@ -44,6 +44,7 @@
"classnames": "^2.2.5",
"dom-scroll-into-view": "^1.2.1",
"lodash": "^4.17.10",
+ "react-spring": "^8.0.19",
"redux-multi": "^0.1.12",
"refx": "^3.0.0",
"rememo": "^3.0.0",
diff --git a/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js b/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js
index e5ac71ee27ed34..96395c41c835f2 100644
--- a/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js
+++ b/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js
@@ -55,15 +55,12 @@ export default compose(
order: getBlockIndex( clientId ),
};
} ),
- withDispatch( ( dispatch, { clientId, rootClientId, onDelete } ) => {
+ withDispatch( ( dispatch, { clientId, rootClientId } ) => {
const { removeBlock } = dispatch( 'core/block-editor' );
return {
- onDelete() {
+ onDelete: () => {
Keyboard.dismiss();
removeBlock( clientId, rootClientId );
- if ( onDelete ) {
- onDelete( clientId );
- }
},
};
} ),
diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js
index aac68e2e6faa88..3556b829ba188d 100644
--- a/packages/block-editor/src/components/block-list/block.js
+++ b/packages/block-editor/src/components/block-list/block.js
@@ -3,6 +3,7 @@
*/
import classnames from 'classnames';
import { get, reduce, size, first, last } from 'lodash';
+import { animated } from 'react-spring/web.cjs';
/**
* WordPress dependencies
@@ -50,6 +51,7 @@ import InserterWithShortcuts from '../inserter-with-shortcuts';
import Inserter from '../inserter';
import useHoveredArea from './hover-area';
import { isInsideRootBlock } from '../../utils/dom';
+import useMovingAnimation from './moving-animation';
/**
* Prevents default dragging behavior within a block to allow for multi-
@@ -97,6 +99,8 @@ function BlockListBlock( {
toggleSelection,
onShiftSelection,
onSelectionStart,
+ animateOnChange,
+ enableAnimation,
} ) {
// Random state used to rerender the component if needed, ideally we don't need this
const [ , updateRerenderState ] = useState( {} );
@@ -247,6 +251,9 @@ function BlockListBlock( {
}
}, [ isFirstMultiSelected ] );
+ // Block Reordering animation
+ const style = useMovingAnimation( wrapper, isSelected || isPartOfMultiSelection, enableAnimation, animateOnChange );
+
// Other event handlers
/**
@@ -458,6 +465,8 @@ function BlockListBlock( {
tabIndex="0"
aria-label={ blockLabel }
childHandledEvents={ [ 'onDragStart', 'onMouseDown' ] }
+ tagName={ animated.div }
+ style={ style }
{ ...blockWrapperProps }
>
{ shouldShowInsertionPoint && (
diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js
index 79cc1b29273375..3e65a52fd3b2ec 100644
--- a/packages/block-editor/src/components/block-list/index.js
+++ b/packages/block-editor/src/components/block-list/index.js
@@ -3,7 +3,6 @@
*/
import {
findLast,
- map,
invert,
mapValues,
sortBy,
@@ -29,6 +28,12 @@ import BlockListBlock from './block';
import BlockListAppender from '../block-list-appender';
import { getBlockDOMNode } from '../../utils/dom';
+/**
+ * If the block count exceeds the threshold, we disable the reordering animation
+ * to avoid laginess.
+ */
+const BLOCK_ANIMATION_THRESHOLD = 200;
+
const forceSyncUpdates = ( WrappedComponent ) => ( props ) => {
return (
@@ -36,6 +41,7 @@ const forceSyncUpdates = ( WrappedComponent ) => ( props ) => {
);
};
+
class BlockList extends Component {
constructor( props ) {
super( props );
@@ -198,11 +204,12 @@ class BlockList extends Component {
multiSelectedBlockClientIds,
hasMultiSelection,
renderAppender,
+ enableAnimation,
} = this.props;
return (
- { map( blockClientIds, ( clientId ) => {
+ { blockClientIds.map( ( clientId ) => {
const isBlockInSelection = hasMultiSelection ?
multiSelectedBlockClientIds.includes( clientId ) :
selectedBlockClientId === clientId;
@@ -214,11 +221,17 @@ class BlockList extends Component {
isBlockInSelection={ isBlockInSelection }
>
);
@@ -248,6 +261,7 @@ export default compose( [
getSelectedBlockClientId,
getMultiSelectedBlockClientIds,
hasMultiSelection,
+ getGlobalBlockCount,
} = select( 'core/block-editor' );
const { rootClientId } = ownProps;
@@ -261,6 +275,7 @@ export default compose( [
selectedBlockClientId: getSelectedBlockClientId(),
multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(),
hasMultiSelection: hasMultiSelection(),
+ enableAnimation: getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD,
};
} ),
withDispatch( ( dispatch ) => {
diff --git a/packages/block-editor/src/components/block-list/moving-animation.js b/packages/block-editor/src/components/block-list/moving-animation.js
new file mode 100644
index 00000000000000..e0fdf75ed6d6b3
--- /dev/null
+++ b/packages/block-editor/src/components/block-list/moving-animation.js
@@ -0,0 +1,85 @@
+/**
+ * External dependencies
+ */
+import { useSpring, interpolate } from 'react-spring/web.cjs';
+
+/**
+ * WordPress dependencies
+ */
+import { useState, useLayoutEffect } from '@wordpress/element';
+import { useReducedMotion } from '@wordpress/compose';
+
+/**
+ * Hook used to compute the styles required to move a div into a new position.
+ *
+ * The way this animation works is the following:
+ * - It first renders the element as if there was no animation.
+ * - It takes a snapshot of the position of the block to use it
+ * as a destination point for the animation.
+ * - It restores the element to the previous position using a CSS transform
+ * - It uses the "resetAnimation" flag to reset the animation
+ * from the beginning in order to animate to the new destination point.
+ *
+ * @param {Object} ref Reference to the element to animate.
+ * @param {boolean} isSelected Whether it's the current block or not.
+ * @param {boolean} enableAnimation Enable/Disable animation.
+ * @param {*} triggerAnimationOnChange Variable used to trigger the animation if it changes.
+ *
+ * @return {Object} Style object.
+ */
+function useMovingAnimation( ref, isSelected, enableAnimation, triggerAnimationOnChange ) {
+ const prefersReducedMotion = useReducedMotion() || ! enableAnimation;
+ const [ resetAnimation, setResetAnimation ] = useState( false );
+ const [ transform, setTransform ] = useState( { x: 0, y: 0 } );
+
+ const previous = ref.current ? ref.current.getBoundingClientRect() : null;
+ useLayoutEffect( () => {
+ if ( prefersReducedMotion ) {
+ return;
+ }
+ ref.current.style.transform = 'none';
+ const destination = ref.current.getBoundingClientRect();
+ const newTransform = {
+ x: previous ? previous.left - destination.left : 0,
+ y: previous ? previous.top - destination.top : 0,
+ };
+ ref.current.style.transform = `translate3d(${ newTransform.x }px,${ newTransform.y }px,0)`;
+ setResetAnimation( true );
+ setTransform( newTransform );
+ }, [ triggerAnimationOnChange ] );
+ useLayoutEffect( () => {
+ if ( resetAnimation ) {
+ setResetAnimation( false );
+ }
+ }, [ resetAnimation ] );
+ const animationProps = useSpring( {
+ from: transform,
+ to: {
+ x: 0,
+ y: 0,
+ },
+ reset: resetAnimation,
+ config: { mass: 5, tension: 2000, friction: 200 },
+ immediate: prefersReducedMotion,
+ } );
+
+ return {
+ transformOrigin: 'center',
+ transform: interpolate(
+ [
+ animationProps.x,
+ animationProps.y,
+ ],
+ ( x, y ) => x === 0 && y === 0 ? undefined : `translate3d(${ x }px,${ y }px,0)`
+ ),
+ zIndex: interpolate(
+ [
+ animationProps.x,
+ animationProps.y,
+ ],
+ ( x, y ) => ! isSelected || ( x === 0 && y === 0 ) ? undefined : `1`
+ ),
+ };
+}
+
+export default useMovingAnimation;
diff --git a/packages/block-editor/src/components/ignore-nested-events/index.js b/packages/block-editor/src/components/ignore-nested-events/index.js
index cb707785bcb65e..42f902b3e61c95 100644
--- a/packages/block-editor/src/components/ignore-nested-events/index.js
+++ b/packages/block-editor/src/components/ignore-nested-events/index.js
@@ -6,7 +6,7 @@ import { reduce } from 'lodash';
/**
* WordPress dependencies
*/
-import { Component, forwardRef } from '@wordpress/element';
+import { Component, forwardRef, createElement } from '@wordpress/element';
/**
* Component which renders a div with passed props applied except the optional
@@ -66,7 +66,7 @@ export class IgnoreNestedEvents extends Component {
}
render() {
- const { childHandledEvents = [], forwardedRef, ...props } = this.props;
+ const { childHandledEvents = [], forwardedRef, tagName = 'div', ...props } = this.props;
const eventHandlers = reduce( [
...childHandledEvents,
@@ -96,7 +96,7 @@ export class IgnoreNestedEvents extends Component {
return result;
}, {} );
- return ;
+ return createElement( tagName, { ref: forwardedRef, ...props, ...eventHandlers } );
}
}
diff --git a/packages/block-editor/src/components/inner-blocks/style.scss b/packages/block-editor/src/components/inner-blocks/style.scss
index 0f8576fd5ecf34..e7bd479e04e02e 100644
--- a/packages/block-editor/src/components/inner-blocks/style.scss
+++ b/packages/block-editor/src/components/inner-blocks/style.scss
@@ -39,19 +39,23 @@
.block-editor-inner-blocks__template-picker-options.block-editor-inner-blocks__template-picker-options {
display: flex;
+ justify-content: center;
flex-direction: row;
- flex-wrap: nowrap;
+ flex-wrap: wrap;
width: 100%;
- margin: $grid-size-large 0;
+ margin: $grid-size-small 0;
list-style: none;
> li {
list-style: none;
- flex-basis: 100%;
+ margin: $grid-size;
flex-shrink: 1;
- margin: 0 $grid-size;
max-width: 100px;
}
+
+ .block-editor-inner-blocks__template-picker-option {
+ padding: $grid-size;
+ }
}
.block-editor-inner-blocks__template-picker-option {
diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js
index baca2921d1677f..8f96935d549895 100644
--- a/packages/block-editor/src/components/rich-text/index.js
+++ b/packages/block-editor/src/components/rich-text/index.js
@@ -112,8 +112,10 @@ function RichTextWraper( {
) }
- { inlineToolbar && (
-
+ { isSelected && inlineToolbar && (
+
) }
diff --git a/packages/block-editor/src/utils/index.js b/packages/block-editor/src/utils/index.js
index cb35b8d3420d15..95241bd4fa7673 100644
--- a/packages/block-editor/src/utils/index.js
+++ b/packages/block-editor/src/utils/index.js
@@ -1,4 +1 @@
-/**
- * Internal dependencies
- */
-export { default as __experimentalTransformStyles } from './transform-styles';
+export { default as transformStyles } from './transform-styles';
diff --git a/packages/block-editor/src/utils/transform-styles/index.js b/packages/block-editor/src/utils/transform-styles/index.js
index 5d98c856ed1160..b0f1d66fe55111 100644
--- a/packages/block-editor/src/utils/transform-styles/index.js
+++ b/packages/block-editor/src/utils/transform-styles/index.js
@@ -16,7 +16,7 @@ import urlRewrite from './transforms/url-rewrite';
import wrap from './transforms/wrap';
/**
- * Convert css rules.
+ * Applies a series of CSS rule transforms to wrap selectors inside a given class and/or rewrite URLs depending on the parameters passed.
*
* @param {Array} styles CSS rules.
* @param {string} wrapperClassName Wrapper Class Name.
diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss
index 784850095f97d2..7e8652b23a3bf2 100644
--- a/packages/block-library/src/editor.scss
+++ b/packages/block-library/src/editor.scss
@@ -51,7 +51,7 @@
// Provide every block with a default base margin. This margin provides a consistent spacing
// between blocks in the editor.
-[data-block] {
+.editor-styles-wrapper [data-block] {
margin-top: $default-block-margin;
margin-bottom: $default-block-margin;
}
diff --git a/packages/block-library/src/html/edit.js b/packages/block-library/src/html/edit.js
index 4ddc146ccf3d66..4f1ab45a073dfe 100644
--- a/packages/block-library/src/html/edit.js
+++ b/packages/block-library/src/html/edit.js
@@ -6,7 +6,7 @@ import { Component } from '@wordpress/element';
import {
BlockControls,
PlainText,
- __experimentalTransformStyles,
+ transformStyles,
} from '@wordpress/block-editor';
import { Disabled, SandBox } from '@wordpress/components';
import { withSelect } from '@wordpress/data';
@@ -38,7 +38,7 @@ class HTMLEdit extends Component {
this.setState( { styles: [
defaultStyles,
- ...__experimentalTransformStyles( styles ),
+ ...transformStyles( styles ),
] } );
}
diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js
index faabe881a04c94..b22d63da20cb9f 100644
--- a/packages/block-library/src/image/edit.js
+++ b/packages/block-library/src/image/edit.js
@@ -415,7 +415,7 @@ class ImageEdit extends Component {
const src = isExternal ? url : undefined;
const labels = {
title: ! url ? __( 'Image' ) : __( 'Edit image' ),
- instructions: __( 'Upload an image, pick one from your media library, or add one with a URL.' ),
+ instructions: __( 'Upload an image file, pick one from your media library, or add one with a URL.' ),
};
const mediaPreview = ( !! url && post_excerpt;
if ( ! ( $post_excerpt ) ) {
$post_excerpt = $post->post_content;
@@ -77,7 +77,7 @@ function render_block_core_latest_posts( $attributes ) {
}
if ( isset( $attributes['displayPostContent'] ) && $attributes['displayPostContent']
- && isset( $attributes['displayPostContentRadio'] ) && 'full_post' == $attributes['displayPostContentRadio'] ) {
+ && isset( $attributes['displayPostContentRadio'] ) && 'full_post' === $attributes['displayPostContentRadio'] ) {
$list_items_markup .= sprintf(
'
%1$s
',
wp_kses_post( html_entity_decode( $post->post_content, ENT_QUOTES, get_option( 'blog_charset' ) ) )
diff --git a/packages/block-library/src/rss/index.php b/packages/block-library/src/rss/index.php
index 1bf217be3a9b59..53879d5bb2716b 100644
--- a/packages/block-library/src/rss/index.php
+++ b/packages/block-library/src/rss/index.php
@@ -69,7 +69,7 @@ function render_block_core_rss( $attributes ) {
$excerpt = esc_attr( wp_trim_words( $excerpt, $attributes['excerptLength'], ' […]' ) );
// Change existing [...] to […].
- if ( '[...]' == substr( $excerpt, -5 ) ) {
+ if ( '[...]' === substr( $excerpt, -5 ) ) {
$excerpt = substr( $excerpt, 0, -5 ) . '[…]';
}
diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js
index 9277c4bb6de705..e183fc029cee31 100644
--- a/packages/block-library/src/video/edit.native.js
+++ b/packages/block-library/src/video/edit.native.js
@@ -11,7 +11,6 @@ import {
mediaUploadSync,
requestImageFailedRetryDialog,
requestImageUploadCancelDialog,
- requestImageUploadCancel,
} from 'react-native-gutenberg-bridge';
/**
@@ -33,6 +32,7 @@ import {
} from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import { isURL } from '@wordpress/url';
+import { doAction, hasAction } from '@wordpress/hooks';
/**
* Internal dependencies
@@ -70,8 +70,9 @@ class VideoEdit extends React.Component {
}
componentWillUnmount() {
- if ( this.state.isUploadInProgress ) {
- requestImageUploadCancel( this.props.attributes.id );
+ // this action will only exist if the user pressed the trash button on the block holder
+ if ( hasAction( 'blocks.onRemoveBlockCheckUpload' ) && this.state.isUploadInProgress ) {
+ doAction( 'blocks.onRemoveBlockCheckUpload', this.props.attributes.id );
}
}
diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js
index 162c5cec83c508..b40532957c5f05 100644
--- a/packages/blocks/src/api/parser.js
+++ b/packages/blocks/src/api/parser.js
@@ -169,45 +169,6 @@ export function isAmbiguousStringSource( attributeSchema ) {
return isStringSource && isSingleType;
}
-/**
- * Returns value coerced to the specified JSON schema type string.
- *
- * @see http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.25
- *
- * @param {*} value Original value.
- * @param {string} type Type to coerce.
- *
- * @return {*} Coerced value.
- */
-export function asType( value, type ) {
- switch ( type ) {
- case 'string':
- return String( value );
-
- case 'boolean':
- return Boolean( value );
-
- case 'object':
- return Object( value );
-
- case 'null':
- return null;
-
- case 'array':
- if ( Array.isArray( value ) ) {
- return value;
- }
-
- return Array.from( value );
-
- case 'integer':
- case 'number':
- return Number( value );
- }
-
- return value;
-}
-
/**
* Returns an hpq matcher given a source object.
*
@@ -238,7 +199,7 @@ export function matcherFromSource( sourceConfig ) {
case 'tag':
return flow( [
prop( sourceConfig.selector, 'nodeName' ),
- ( value ) => value.toLowerCase(),
+ ( nodeName ) => nodeName ? nodeName.toLowerCase() : undefined,
] );
default:
// eslint-disable-next-line no-console
diff --git a/packages/blocks/src/api/test/parser.js b/packages/blocks/src/api/test/parser.js
index f6c1569a420436..8c0e50feee3eec 100644
--- a/packages/blocks/src/api/test/parser.js
+++ b/packages/blocks/src/api/test/parser.js
@@ -10,7 +10,6 @@ import deepFreeze from 'deep-freeze';
import {
getBlockAttribute,
getBlockAttributes,
- asType,
createBlockWithFallback,
getMigratedBlock,
default as parsePegjs,
@@ -104,44 +103,6 @@ describe( 'block parser', () => {
} );
} );
- describe( 'asType()', () => {
- it( 'gracefully handles undefined type', () => {
- expect( asType( 5 ) ).toBe( 5 );
- } );
-
- it( 'gracefully handles unhandled type', () => {
- expect( asType( 5, '__UNHANDLED__' ) ).toBe( 5 );
- } );
-
- it( 'returns expected coerced values', () => {
- const arr = [];
- const obj = {};
-
- expect( asType( '5', 'string' ) ).toBe( '5' );
- expect( asType( 5, 'string' ) ).toBe( '5' );
-
- expect( asType( 5, 'integer' ) ).toBe( 5 );
- expect( asType( '5', 'integer' ) ).toBe( 5 );
-
- expect( asType( 5, 'number' ) ).toBe( 5 );
- expect( asType( '5', 'number' ) ).toBe( 5 );
-
- expect( asType( true, 'boolean' ) ).toBe( true );
- expect( asType( false, 'boolean' ) ).toBe( false );
- expect( asType( '5', 'boolean' ) ).toBe( true );
- expect( asType( 0, 'boolean' ) ).toBe( false );
-
- expect( asType( null, 'null' ) ).toBe( null );
- expect( asType( 0, 'null' ) ).toBe( null );
-
- expect( asType( arr, 'array' ) ).toBe( arr );
- expect( asType( new Set( [ 1, 2, 3 ] ), 'array' ) ).toEqual( [ 1, 2, 3 ] );
-
- expect( asType( obj, 'object' ) ).toBe( obj );
- expect( asType( {}, 'object' ) ).toEqual( {} );
- } );
- } );
-
describe( 'isOfType()', () => {
it( 'gracefully handles unhandled type', () => {
expect( isOfType( 5, '__UNHANDLED__' ) ).toBe( true );
@@ -282,6 +243,32 @@ describe( 'block parser', () => {
);
expect( value ).toBe( false );
} );
+
+ describe( 'source: tag', () => {
+ it( 'returns tag name of matching selector', () => {
+ const value = parseWithAttributeSchema(
+ '',
+ {
+ source: 'tag',
+ selector: ':nth-child(1)',
+ }
+ );
+
+ expect( value ).toBe( 'div' );
+ } );
+
+ it( 'returns undefined when no element matches selector', () => {
+ const value = parseWithAttributeSchema(
+ '',
+ {
+ source: 'tag',
+ selector: ':nth-child(2)',
+ }
+ );
+
+ expect( value ).toBe( undefined );
+ } );
+ } );
} );
describe( 'getBlockAttribute', () => {
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index b54dcec4b412bc..a2ce1b818505e4 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -1,9 +1,12 @@
-## Next release
+## Master
### New Features
- Added a new `popoverProps` prop to the `Dropdown` component which allows users of the `Dropdown` component to pass props directly to the `PopOver` component.
+### Bug Fixes
+
+- The `Button` component will no longer assign default styling (`is-default` class) when explicitly assigned as primary (the `isPrimary` prop). This should resolve potential conflicts affecting a combination of `isPrimary`, `isDefault`, and `isLarge` / `isSmall`, where the busy animation would appear with incorrect coloring.
## 8.0.0 (2019-06-12)
diff --git a/packages/components/package.json b/packages/components/package.json
index 99c44a249b7e42..f82a996cda1e65 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -40,7 +40,7 @@
"memize": "^1.0.5",
"moment": "^2.22.1",
"mousetrap": "^1.6.2",
- "re-resizable": "^4.7.1",
+ "re-resizable": "^5.0.1",
"react-click-outside": "^3.0.0",
"react-dates": "^17.1.1",
"react-spring": "^8.0.20",
diff --git a/packages/components/src/button/index.js b/packages/components/src/button/index.js
index 3d6f9a2cb251c8..da3441e7f2982a 100644
--- a/packages/components/src/button/index.js
+++ b/packages/components/src/button/index.js
@@ -28,7 +28,7 @@ export function Button( props, ref ) {
const classes = classnames( 'components-button', className, {
'is-button': isDefault || isPrimary || isLarge || isSmall,
- 'is-default': isDefault || isLarge || isSmall,
+ 'is-default': isDefault || ( ! isPrimary && ( isLarge || isSmall ) ),
'is-primary': isPrimary,
'is-large': isLarge,
'is-small': isSmall,
diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss
index 69776322ede4ee..ba13650e492dab 100644
--- a/packages/components/src/button/style.scss
+++ b/packages/components/src/button/style.scss
@@ -90,7 +90,7 @@
box-shadow:
inset 0 -1px 0 color(theme(button) shade(50%)),
0 0 0 1px $white,
- 0 0 0 3px $blue-medium-focus;
+ 0 0 0 3px color(theme(button) shade(5%));
}
&:active:enabled {
diff --git a/packages/components/src/button/test/index.js b/packages/components/src/button/test/index.js
index 6140b00b56b7c5..05e3580bfacbe6 100644
--- a/packages/components/src/button/test/index.js
+++ b/packages/components/src/button/test/index.js
@@ -27,14 +27,14 @@ describe( 'Button', () => {
expect( button.type() ).toBe( 'button' );
} );
- it( 'should render a button element with button-primary class', () => {
+ it( 'should render a button element with is-primary class', () => {
const button = shallow( );
expect( button.hasClass( 'is-large' ) ).toBe( false );
expect( button.hasClass( 'is-primary' ) ).toBe( true );
expect( button.hasClass( 'is-button' ) ).toBe( true );
} );
- it( 'should render a button element with button-large class', () => {
+ it( 'should render a button element with is-large class', () => {
const button = shallow( );
expect( button.hasClass( 'is-large' ) ).toBe( true );
expect( button.hasClass( 'is-default' ) ).toBe( true );
@@ -42,7 +42,12 @@ describe( 'Button', () => {
expect( button.hasClass( 'is-primary' ) ).toBe( false );
} );
- it( 'should render a button element with button-small class', () => {
+ it( 'should render a button element without is-default if primary', () => {
+ const button = shallow( );
+ expect( button.hasClass( 'is-default' ) ).toBe( false );
+ } );
+
+ it( 'should render a button element with is-small class', () => {
const button = shallow( );
expect( button.hasClass( 'is-default' ) ).toBe( true );
expect( button.hasClass( 'is-button' ) ).toBe( true );
diff --git a/packages/components/src/resizable-box/index.js b/packages/components/src/resizable-box/index.js
index 13e98a7f4da474..fc63dd126a24e8 100644
--- a/packages/components/src/resizable-box/index.js
+++ b/packages/components/src/resizable-box/index.js
@@ -2,7 +2,7 @@
* External dependencies
*/
import classnames from 'classnames';
-import ReResizableBox from 're-resizable';
+import { Resizable } from 're-resizable';
function ResizableBox( { className, ...props } ) {
// Removes the inline styles in the drag handles.
@@ -20,7 +20,7 @@ function ResizableBox( { className, ...props } ) {
const cornerHandleClassName = 'components-resizable-box__corner-handle';
return (
- {
- // This rule doesn't account yet for React Hooks
- // eslint-disable-next-line @wordpress/react-no-unsafe-timeout
const timeoutHandle = setTimeout( () => {
onRemove();
}, NOTICE_TIMEOUT );
diff --git a/packages/compose/src/hooks/use-reduced-motion/index.js b/packages/compose/src/hooks/use-reduced-motion/index.js
index 7b1e22792d37c5..a8b4cf0569b841 100644
--- a/packages/compose/src/hooks/use-reduced-motion/index.js
+++ b/packages/compose/src/hooks/use-reduced-motion/index.js
@@ -3,13 +3,20 @@
*/
import useMediaQuery from '../use-media-query';
+/**
+ * Whether or not the user agent is Internet Explorer.
+ *
+ * @type {boolean}
+ */
+const IS_IE = window.navigator.userAgent.indexOf( 'Trident' ) >= 0;
+
/**
* Hook returning whether the user has a preference for reduced motion.
*
* @return {boolean} Reduced motion preference value.
*/
const useReducedMotion =
- process.env.FORCE_REDUCED_MOTION ?
+ process.env.FORCE_REDUCED_MOTION || IS_IE ?
() => true :
() => useMediaQuery( '(prefers-reduced-motion: reduce)' );
diff --git a/packages/e2e-tests/plugins/plugins-api.php b/packages/e2e-tests/plugins/plugins-api.php
index a1c91596faea52..1accfb3fc718da 100644
--- a/packages/e2e-tests/plugins/plugins-api.php
+++ b/packages/e2e-tests/plugins/plugins-api.php
@@ -73,6 +73,19 @@ function enqueue_plugins_api_plugin_scripts() {
filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/annotations-sidebar.js' ),
true
);
+
+ wp_enqueue_script(
+ 'gutenberg-test-plugins-api-document-setting',
+ plugins_url( 'plugins-api/document-setting.js', __FILE__ ),
+ array(
+ 'wp-edit-post',
+ 'wp-element',
+ 'wp-i18n',
+ 'wp-plugins',
+ ),
+ filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/document-setting.js' ),
+ true
+ );
}
add_action( 'init', 'enqueue_plugins_api_plugin_scripts' );
diff --git a/packages/e2e-tests/plugins/plugins-api/document-setting.js b/packages/e2e-tests/plugins/plugins-api/document-setting.js
new file mode 100644
index 00000000000000..b244800c25809f
--- /dev/null
+++ b/packages/e2e-tests/plugins/plugins-api/document-setting.js
@@ -0,0 +1,21 @@
+( function() {
+ var el = wp.element.createElement;
+ var __ = wp.i18n.__;
+ var registerPlugin = wp.plugins.registerPlugin;
+ var PluginDocumentSettingPanel = wp.editPost.PluginDocumentSettingPanel;
+
+ function MyDocumentSettingPlugin() {
+ return el(
+ PluginDocumentSettingPanel,
+ {
+ className: 'my-document-setting-plugin',
+ title: 'My Custom Panel'
+ },
+ __( 'My Document Setting Panel' )
+ );
+ }
+
+ registerPlugin( 'my-document-setting-plugin', {
+ render: MyDocumentSettingPlugin
+ } );
+} )();
diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js
index 02cc0a0f5720ee..f5cfe7aec0da4d 100644
--- a/packages/e2e-tests/specs/block-deletion.test.js
+++ b/packages/e2e-tests/specs/block-deletion.test.js
@@ -110,8 +110,8 @@ describe( 'block deletion -', () => {
// Click on something that's not a block.
await page.click( '.editor-post-title' );
- // Click on the third (image) block so that its wrapper is selected and backspace to delete it.
- await page.click( '.block-editor-block-list__block:nth-child(3) .components-placeholder__label' );
+ // Click on the image block so that its wrapper is selected and backspace to delete it.
+ await page.click( '.wp-block[data-type="core/image"] .components-placeholder__label' );
await page.keyboard.press( 'Backspace' );
expect( await getEditedPostContent() ).toMatchSnapshot();
diff --git a/packages/e2e-tests/specs/block-grouping.test.js b/packages/e2e-tests/specs/block-grouping.test.js
index 57300ec2b9de66..a1b25f055edde9 100644
--- a/packages/e2e-tests/specs/block-grouping.test.js
+++ b/packages/e2e-tests/specs/block-grouping.test.js
@@ -102,6 +102,13 @@ describe( 'Block Grouping', () => {
expect( await getEditedPostContent() ).toMatchSnapshot();
} );
+
+ it( 'does not allow ungrouping a group block that has no children', async () => {
+ await insertBlock( 'Group' );
+ await clickBlockToolbarButton( 'More options' );
+ const ungroupButtons = await page.$x( '//button[text()="Ungroup"]' );
+ expect( ungroupButtons ).toHaveLength( 0 );
+ } );
} );
describe( 'Container Block availability', () => {
diff --git a/packages/e2e-tests/specs/change-detection.test.js b/packages/e2e-tests/specs/change-detection.test.js
index 7c9dab21d4d794..30895ce5590dea 100644
--- a/packages/e2e-tests/specs/change-detection.test.js
+++ b/packages/e2e-tests/specs/change-detection.test.js
@@ -199,7 +199,7 @@ describe( 'Change detection', () => {
// Ensure save update fails and presents button.
page.waitForXPath(
- '//*[contains(@class, "components-notice") and contains(@class, "is-error")]/*[text()="Updating failed"]'
+ '//*[contains(@class, "components-notice") and contains(@class, "is-error")]/*[text()="Updating failed. Error message: The response is not a valid JSON response."]'
),
page.waitForSelector( '.editor-post-save-draft' ),
] );
diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap
index a3dda4545d67d9..0d8533597b0411 100644
--- a/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap
+++ b/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap
@@ -1,3 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Using Plugins API Document Setting Custom Panel Should render a custom panel inside Document Setting sidebar 1`] = `
+"
+My Custom Panel
+"
+`;
+
exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"
+
+);
+
+ registerPlugin( 'document-setting-test', { render: MyDocumentSettingTest } );
+```
+
+_Parameters_
+
+- _props_ `Object`: Component properties.
+- _props.name_ `[string]`: The machine-friendly name for the panel.
+- _props.className_ `[string]`: An optional class name added to the row.
+- _props.title_ `[string]`: The title of the panel
+- _props.icon_ `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar.
+
+_Returns_
+
+- `WPElement`: The WPElement to be rendered.
+
# **PluginMoreMenuItem**
Renders a menu item in `Plugins` group in `More Menu` drop down, and can be used to as a button or link depending on the props provided.
diff --git a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js
new file mode 100644
index 00000000000000..a4ba4b924273ba
--- /dev/null
+++ b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js
@@ -0,0 +1,106 @@
+/**
+ * Defines as extensibility slot for the Settings sidebar
+ */
+
+/**
+ * WordPress dependencies
+ */
+import { createSlotFill, PanelBody } from '@wordpress/components';
+import { compose } from '@wordpress/compose';
+import { withPluginContext } from '@wordpress/plugins';
+import { withDispatch, withSelect } from '@wordpress/data';
+
+export const { Fill, Slot } = createSlotFill( 'PluginDocumentSettingPanel' );
+
+const PluginDocumentSettingFill = ( { isEnabled, opened, onToggle, className, title, icon, children } ) => {
+ if ( ! isEnabled ) {
+ return null;
+ }
+ return (
+
+
+ { children }
+
+
+ );
+};
+
+/**
+ * Renders items below the Status & Availability panel in the Document Sidebar.
+ *
+ * @param {Object} props Component properties.
+ * @param {string} [props.name] The machine-friendly name for the panel.
+ * @param {string} [props.className] An optional class name added to the row.
+ * @param {string} [props.title] The title of the panel
+ * @param {string|Element} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar.
+ *
+ * @example