diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 2de6c549969961..b4001b7eda58c7 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -13,12 +13,12 @@
/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan @noisysocks @notnownikki
# Editor
-/packages/annotations @youknowriad @aduth @atimmer
+/packages/annotations @youknowriad @aduth @atimmer @ellatrix
/packages/autop @youknowriad @aduth
-/packages/block-editor @youknowriad @gziolo @talldan @noisysocks
+/packages/block-editor @youknowriad @gziolo @talldan @noisysocks @ellatrix
/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/blocks @youknowriad @gziolo @aduth @noisysocks @ellatrix
/packages/edit-post @youknowriad @talldan @noisysocks
/packages/editor @youknowriad @talldan @noisysocks
/packages/list-reusable-blocks @youknowriad @aduth @noisysocks
@@ -29,7 +29,7 @@
# Tooling
/bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra
-/docs/tool @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki
+/docs/tool @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki
/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
@@ -60,13 +60,13 @@
/packages/blob @youknowriad @gziolo @aduth
/packages/date @youknowriad @gziolo @aduth
/packages/deprecated @youknowriad @gziolo @aduth
-/packages/dom @youknowriad @gziolo @aduth @nosolosw
+/packages/dom @youknowriad @gziolo @aduth @nosolosw @ellatrix
/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/keycodes @youknowriad @gziolo @aduth @talldan @ellatrix
/packages/priority-queue @youknowriad @gziolo @aduth
/packages/token-list @youknowriad @gziolo @aduth
/packages/url @youknowriad @gziolo @aduth @talldan
@@ -77,9 +77,9 @@
/packages/plugins @youknowriad @gziolo @aduth @adamsilverstein
# Rich Text
-/packages/format-library @youknowriad @aduth @iseulde @jorgefilipecosta
-/packages/rich-text @youknowriad @aduth @iseulde @jorgefilipecosta
-/packages/block-editor/src/components/rich-text @youknowriad @aduth @iseulde @jorgefilipecosta
+/packages/format-library @youknowriad @aduth @ellatrix @jorgefilipecosta
+/packages/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta
+/packages/block-editor/src/components/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta
# PHP
/lib @youknowriad @gziolo @aduth
diff --git a/.travis.yml b/.travis.yml
index 26b72a571f723d..e1a21e3f72cc5c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -99,3 +99,15 @@ jobs:
- $( 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 )
+ allow_failures:
+ - 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"
+
+ - name: PHP unit tests (PHP 5.2)
+ env: WP_VERSION=latest SWITCH_TO_PHP=5.2
+ script:
+ - ./bin/run-wp-unit-tests.sh
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d2f9e9ec7fe6f7..f3c371c089f38a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -21,8 +21,8 @@ For another version of Windows, or if you prefer to set things up manually, be s
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:
```
-npx nvm install
-npx nvm use
+nvm install
+nvm use
```
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`
diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss
index 89297e6335bc92..0d602338bd8727 100644
--- a/assets/stylesheets/_mixins.scss
+++ b/assets/stylesheets/_mixins.scss
@@ -484,7 +484,7 @@
background-color: $white;
@include break-medium() {
- margin: -3px 0 0 -3px;
+ margin: 3px 0 0 3px;
}
}
}
diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss
index 86ab07f3e28ab1..3c8b921b1fce0c 100644
--- a/assets/stylesheets/_z-index.scss
+++ b/assets/stylesheets/_z-index.scss
@@ -99,7 +99,11 @@ $z-layers: (
".nux-dot-tip": 1000001,
// Show tooltips above NUX tips, wp-admin menus, submenus, and sidebar:
- ".components-tooltip": 1000002
+ ".components-tooltip": 1000002,
+
+ // Make sure corner handles are above side handles for ResizableBox component
+ ".components-resizable-box__side-handle": 1,
+ ".components-resizable-box__corner-handle": 2
);
@function z-index( $key ) {
diff --git a/babel.config.js b/babel.config.js
index b56ad5b149478b..7679cc1e837041 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -3,5 +3,6 @@ module.exports = function( api ) {
return {
presets: [ '@wordpress/babel-preset-default' ],
+ plugins: [ 'babel-plugin-inline-json-import' ],
};
};
diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md
index 47d2f70876143e..dff770151e8e9c 100644
--- a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md
+++ b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md
@@ -1,48 +1,56 @@
# Applying Styles From a Stylesheet
-In the previous section, the block had applied its own styles by an inline `style` attribute. While this might be adequate for very simple components, you will quickly find that it becomes easier to write your styles by extracting them to a separate stylesheet file.
+In the previous step, the block had applied its own styles by an inline `style` attribute. While this might be adequate for very simple components, you will quickly find that it becomes easier to write your styles by extracting them to a separate stylesheet file.
-The editor will automatically generate a class name for each block type to simplify styling. It can be accessed from the object argument passed to the edit and save functions:
+The editor will automatically generate a class name for each block type to simplify styling. It can be accessed from the object argument passed to the edit and save functions. In step 2, we will create a stylesheet to use that class name.
{% codetabs %}
{% ES5 %}
```js
-var el = wp.element.createElement,
- registerBlockType = wp.blocks.registerBlockType;
-
-registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-02', {
- title: 'Hello World (Step 2)',
-
- icon: 'universal-access-alt',
-
- category: 'layout',
-
- edit: function( props ) {
- return el( 'p', { className: props.className }, 'Hello editor.' );
- },
-
- save: function() {
- return el( 'p', {}, 'Hello saved content.' );
- }
-} );
+( function( blocks, element ) {
+ var el = element.createElement;
+
+ blocks.registerBlockType( 'gutenberg-examples/example-02-stylesheets', {
+ title: 'Example: Stylesheets',
+ icon: 'universal-access-alt',
+ category: 'layout',
+ edit: function( props ) {
+ return el(
+ 'p',
+ { className: props.className },
+ 'Hello World, step 2 (from the editor, in green).'
+ );
+ },
+ save: function() {
+ return el(
+ 'p',
+ {},
+ 'Hello World, step 2 (from the frontend, in red).'
+ );
+ },
+ } );
+}(
+ window.wp.blocks,
+ window.wp.element
+) );
```
{% ESNext %}
```js
const { registerBlockType } = wp.blocks;
-registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-02', {
- title: 'Hello World (Step 2)',
+registerBlockType( 'gutenberg-examples/example-02-stylesheets', {
+ title: 'Example: Stylesheets',
icon: 'universal-access-alt',
category: 'layout',
edit( { className } ) {
- return
Hello editor.
;
+ return
Hello World, step 2 (from the editor, in green).
;
},
save() {
- return
Hello saved content.
;
+ return
Hello World, step 2 (from the frontend, in red)./p>;
}
} );
```
@@ -50,8 +58,16 @@ registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-02', {
The class name is generated using the block's name prefixed with `wp-block-`, replacing the `/` namespace separator with a single `-`.
+## Enqueueing Editor and Front end Assets
+
+Like scripts, you need to enqueue your block's styles. As explained in the section before, you use the `editor_style` handle for styles only relevant in the editor, and the `style` handle for common styles applied both in the editor and the front of your site.
+
+The stylesheets enqueued by `style` are the base styles and are loaded first. The `editor` stylesheet will be loaded after it.
+
+Let's move on into code. Create a file called `editor.css`:
+
```css
-.wp-block-gutenberg-boilerplate-es5-hello-world-step-02 {
+.wp-block-gutenberg-examples-example-02-stylesheets {
color: green;
background: #cfc;
border: 2px solid #9c9;
@@ -59,56 +75,52 @@ The class name is generated using the block's name prefixed with `wp-block-`, re
}
```
-## Enqueueing Editor-only Block Assets
+And a new `style.css` file containing:
-Like scripts, your block's editor-specific styles should be enqueued by assigning the `editor_styles` setting of the registered block type:
+```css
+.wp-block-gutenberg-examples-example-02-stylesheets {
+ color: darkred;
+ background: #fcc;
+ border: 2px solid #c99;
+ padding: 20px;
+}
+```
+
+Configure your plugin to use these new styles:
```php
'gutenberg-boilerplate-es5-step02-editor',
- 'editor_style' => 'gutenberg-boilerplate-es5-step02-editor',
- ) );
-}
-add_action( 'init', 'gutenberg_boilerplate_block' );
-```
-
-## Enqueueing Editor and Front end Assets
-
-While a block's scripts are usually only necessary to load in the editor, you'll want to load styles both on the front of your site and in the editor. You may even want distinct styles in each context.
-
-When registering a block, you can assign one or both of `style` and `editor_style` to respectively assign styles always loaded for a block or styles only loaded in the editor.
-
-```php
- 'gutenberg-boilerplate-es5-step02',
+ register_block_type( 'gutenberg-examples/example-02-stylesheets', array(
+ 'style' => 'gutenberg-examples-02',
+ 'editor_style' => 'gutenberg-examples-02-editor',
+ 'editor_script' => 'gutenberg-examples-02',
) );
}
-add_action( 'init', 'gutenberg_boilerplate_block' );
+add_action( 'init', 'gutenberg_examples_02_register_block' );
```
-
-Since your block is likely to share some styles in both contexts, you can consider `style.css` as the base stylesheet, placing editor-specific styles in `editor.css`.
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 b2efe54ef4d1ac..bfabb7379b40a6 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
@@ -13,50 +13,45 @@ You can also customize the toolbar to include controls specific to your block ty
{% codetabs %}
{% ES5 %}
```js
-var el = wp.element.createElement,
- Fragment = wp.element.Fragment
- registerBlockType = wp.blocks.registerBlockType,
- RichText = wp.editor.RichText,
- BlockControls = wp.editor.BlockControls,
- AlignmentToolbar = wp.editor.AlignmentToolbar;
-
-registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-04', {
- title: 'Hello World (Step 4)',
-
- icon: 'universal-access-alt',
-
- category: 'layout',
-
- attributes: {
- content: {
- type: 'string',
- source: 'html',
- selector: 'p',
+( function( blocks, editor, element ) {
+ var el = element.createElement;
+ var RichText = editor.RichText;
+ var AlignmentToolbar = editor.AlignmentToolbar;
+ var BlockControls = editor.BlockControls;
+
+ blocks.registerBlockType( 'gutenberg-examples/example-04-controls', {
+ title: 'Example: Controls',
+ icon: 'universal-access-alt',
+ category: 'layout',
+
+ attributes: {
+ content: {
+ type: 'array',
+ source: 'children',
+ selector: 'p',
+ },
+ alignment: {
+ type: 'string',
+ default: 'none',
+ },
},
- alignment: {
- type: 'string',
- },
- },
- edit: function( props ) {
- var content = props.attributes.content,
- alignment = props.attributes.alignment;
+ edit: function( props ) {
+ var content = props.attributes.content;
+ var alignment = props.attributes.alignment;
- function onChangeContent( newContent ) {
- props.setAttributes( { content: newContent } );
- }
+ function onChangeContent( newContent ) {
+ props.setAttributes( { content: newContent } );
+ }
- function onChangeAlignment( newAlignment ) {
- props.setAttributes( { alignment: newAlignment } );
- }
+ function onChangeAlignment( newAlignment ) {
+ props.setAttributes( { alignment: newAlignment === undefined ? 'none' : newAlignment } );
+ }
- return (
- el(
- Fragment,
- null,
+ return [
el(
BlockControls,
- null,
+ { key: 'controls' },
el(
AlignmentToolbar,
{
@@ -68,98 +63,99 @@ registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-04', {
el(
RichText,
{
- key: 'editable',
+ key: 'richtext',
tagName: 'p',
- className: props.className,
style: { textAlign: alignment },
+ className: props.className,
onChange: onChangeContent,
value: content,
}
- )
- )
- );
- },
-
- save: function( props ) {
- var content = props.attributes.content,
- alignment = props.attributes.alignment;
+ ),
+ ];
+ },
- return el( RichText.Content, {
- tagName: 'p',
- className: props.className,
- style: { textAlign: alignment },
- value: content
- } );
- },
-} );
+ save: function( props ) {
+ return el( RichText.Content, {
+ tagName: 'p',
+ className: 'gutenberg-examples-align-' + props.attributes.alignment,
+ value: props.attributes.content,
+ } );
+ },
+ } );
+}(
+ window.wp.blocks,
+ window.wp.editor,
+ window.wp.element
+) );
```
{% ESNext %}
```js
const { registerBlockType } = wp.blocks;
-const { Fragment } = wp.element;
+
const {
RichText,
- BlockControls,
AlignmentToolbar,
+ BlockControls,
} = wp.editor;
-registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-04', {
- title: 'Hello World (Step 4)',
-
+registerBlockType( 'gutenberg-examples/example-04-controls-esnext', {
+ title: 'Example: Controls (esnext)',
icon: 'universal-access-alt',
-
category: 'layout',
-
attributes: {
content: {
- type: 'string',
- source: 'html',
+ type: 'array',
+ source: 'children',
selector: 'p',
},
alignment: {
type: 'string',
+ default: 'none',
},
},
+ edit: ( props ) => {
+ const {
+ attributes: {
+ content,
+ alignment,
+ },
+ className,
+ } = props;
+
+ const onChangeContent = ( newContent ) => {
+ props.setAttributes( { content: newContent } );
+ };
- edit( { attributes, className, setAttributes } ) {
- const { content, alignment } = attributes;
-
- function onChangeContent( newContent ) {
- setAttributes( { content: newContent } );
- }
-
- function onChangeAlignment( newAlignment ) {
- setAttributes( { alignment: newAlignment } );
- }
+ const onChangeAlignment = ( newAlignment ) => {
+ props.setAttributes( { alignment: newAlignment === undefined ? 'none' : newAlignment } );
+ };
return (
-
-
-
-
+
+ {
+
+
+
+ }
-
+
);
},
-
- save( { attributes } ) {
- const { content, alignment } = attributes;
-
+ save: ( props ) => {
return (
);
},
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 ab7543c392c9cb..8c92d48349b7e8 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,56 +1,59 @@
# Creating dynamic blocks
-It is possible to create dynamic blocks. These 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 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.
-The following code example shows how to create the latest post block dynamic block.
+The following code example shows how to create a dynamic block that shows only the last post as a link.
{% codetabs %}
{% ES5 %}
```js
-// myblock.js
+( function( blocks, element, data ) {
-var el = wp.element.createElement,
- registerBlockType = wp.blocks.registerBlockType,
- withSelect = wp.data.withSelect;
+ var el = element.createElement,
+ registerBlockType = blocks.registerBlockType,
+ withSelect = data.withSelect;
-registerBlockType( 'my-plugin/latest-post', {
- title: 'Latest Post',
- icon: 'megaphone',
- category: 'widgets',
-
- edit: withSelect( function( select ) {
- return {
- posts: select( 'core' ).getEntityRecords( 'postType', 'post' )
- };
- } )( function( props ) {
+ registerBlockType( 'gutenberg-examples/example-05-dynamic', {
+ title: 'Example: last post',
+ icon: 'megaphone',
+ category: 'widgets',
- if ( ! props.posts ) {
- return "Loading...";
- }
-
- if ( props.posts.length === 0 ) {
- return "No posts";
- }
- var className = props.className;
- var post = props.posts[ 0 ];
-
- return el(
- 'a',
- { className: className, href: post.link },
- post.title.rendered
- );
- } ),
-} );
+ edit: withSelect( function( select ) {
+ return {
+ posts: select( 'core' ).getEntityRecords( 'postType', 'post' )
+ };
+ } )( function( props ) {
+
+ if ( ! props.posts ) {
+ return "Loading...";
+ }
+
+ if ( props.posts.length === 0 ) {
+ return "No posts";
+ }
+ var className = props.className;
+ var post = props.posts[ 0 ];
+
+ return el(
+ 'a',
+ { className: className, href: post.link },
+ post.title.rendered
+ );
+ } ),
+ } );
+}(
+ window.wp.blocks,
+ window.wp.element,
+ window.wp.data,
+) );
```
{% ESNext %}
```js
-// myblock.js
-
const { registerBlockType } = wp.blocks;
const { withSelect } = wp.data;
-registerBlockType( 'my-plugin/latest-post', {
- title: 'Latest Post',
+registerBlockType( 'gutenberg-examples/example-05-dynamic', {
+ title: 'Example: last post',
icon: 'megaphone',
category: 'widgets',
@@ -78,13 +81,16 @@ registerBlockType( 'my-plugin/latest-post', {
```
{% end %}
-Because it is a dynamic block it doesn't need to override the default `save` implementation on the client. Instead, it needs a server component. The rendering can be added using the `render_callback` property when using the `register_block_type` function.
+Because it is a dynamic block it doesn't need to override the default `save` implementation on the client. Instead, it needs a server component. The contents in the front of your site depend on the function called by the `render_callback` property of `register_block_type`.
```php
1,
'post_status' => 'publish',
@@ -101,9 +107,21 @@ function my_plugin_render_block_latest_post( $attributes, $content ) {
);
}
-register_block_type( 'my-plugin/latest-post', array(
- 'render_callback' => 'my_plugin_render_block_latest_post',
-) );
+function gutenberg_examples_05_dynamic() {
+ wp_register_script(
+ 'gutenberg-examples-05',
+ plugins_url( 'block.js', __FILE__ ),
+ array( 'wp-blocks', 'wp-element', 'wp-data' )
+ );
+
+ register_block_type( 'gutenberg-examples/example-05-dynamic', array(
+ 'editor_script' => 'gutenberg-examples-05',
+ 'render_callback' => 'gutenberg_examples_05_dynamic_render_callback'
+ ) );
+
+}
+add_action( 'init', 'gutenberg_examples_05_dynamic' );
+
```
There are a few things to notice:
@@ -121,45 +139,47 @@ Gutenberg 2.8 added the [``](/packages/components/src/server-s
{% codetabs %}
{% ES5 %}
```js
-// myblock.js
-
-var el = wp.element.createElement,
- registerBlockType = wp.blocks.registerBlockType,
- ServerSideRender = wp.components.ServerSideRender;
-
-registerBlockType( 'my-plugin/latest-post', {
- title: 'Latest Post',
- icon: 'megaphone',
- category: 'widgets',
-
- edit: function( props ) {
- // ensure the block attributes matches this plugin's name
- return (
- el(ServerSideRender, {
- block: "my-plugin/latest-post",
- attributes: props.attributes
- })
- );
- },
-} );
+( function( blocks, element, components ) {
+
+ var el = element.createElement,
+ registerBlockType = blocks.registerBlockType,
+ ServerSideRender = components.ServerSideRender;
+
+ registerBlockType( 'gutenberg-examples/example-05-dynamic', {
+ title: 'Example: last post',
+ icon: 'megaphone',
+ category: 'widgets',
+
+ edit: function( props ) {
+
+ return (
+ el(ServerSideRender, {
+ block: "gutenberg-examples/example-05-dynamic",
+ attributes: props.attributes
+ } )
+ );
+ },
+ } );
+}(
+ window.wp.blocks,
+ window.wp.element,
+ window.wp.components,
+) );
```
{% ESNext %}
```js
-// myblock.js
-
const { registerBlockType } = wp.blocks;
const { ServerSideRender } = wp.components;
-registerBlockType( 'my-plugin/latest-post', {
- title: 'Latest Post',
+registerBlockType( 'gutenberg-examples/example-05-dynamic', {
+ title: 'Example: last post',
icon: 'megaphone',
category: 'widgets',
edit: function( props ) {
- // ensure the block attributes matches this plugin's name
return (
);
@@ -168,4 +188,4 @@ registerBlockType( 'my-plugin/latest-post', {
```
{% end %}
-The PHP code is the same as above and is automatically handled through the WP REST API.
+Note that this code uses the `wp.components` utility but not `wp.data`. Make sure to update the `wp-data` dependency to `wp-compononents` in the PHP code.
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 c51bd505bdd2e2..738ed6640ce55f 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
@@ -1,6 +1,6 @@
# Introducing Attributes and Editable Fields
-Our example block is still not very interesting because it lacks options to customize the appearance of the message. In this section, we will implement a RichText field allowing the user to specify their own message. Before doing so, it's important to understand how the state of a block (its "attributes") is maintained and changed over time.
+The example blocks so far are still not very interesting because they lack options to customize the appearance of the message. In this section, we will implement a RichText field allowing the user to specify their own message. Before doing so, it's important to understand how the state of a block (its "attributes") is maintained and changed over time.
## Attributes
@@ -8,84 +8,121 @@ Until now, the `edit` and `save` functions have returned a simple representation
One challenge of maintaining the representation of a block as a JavaScript object is that we must be able to extract this object again from the saved content of a post. This is achieved with the block type's `attributes` property:
-{% codetabs %}
-{% ES5 %}
```js
-var el = wp.element.createElement,
- registerBlockType = wp.blocks.registerBlockType,
- RichText = wp.editor.RichText;
-
-registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-03', {
- title: 'Hello World (Step 3)',
-
- icon: 'universal-access-alt',
-
- category: 'layout',
-
attributes: {
content: {
- type: 'string',
- source: 'html',
+ type: 'array',
+ source: 'children',
selector: 'p',
- }
+ },
},
+```
- edit: function( props ) {
- var content = props.attributes.content;
+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, the `content` value will be extracted from the HTML of the paragraph element in the saved post's markup.
+
+## Components and the `RichText` Component
+
+Earlier examples used the `createElement` function to create DOM nodes, but it's also possible to encapsulate this behavior into "components". This abstraction helps you share common behaviors and hide complexity in self-contained units.
- function onChangeContent( newContent ) {
- props.setAttributes( { content: newContent } );
- }
+There are a number of [components available](/docs/designers-developers/developers/packages/packages-editor.md#components) to use in implementing your blocks. You can see one such component in the code below: the [`RichText` component](/docs/designers-developers/developers/packages/packages-editor.md#richtext).
- return el(
- RichText,
- {
- tagName: 'p',
- className: props.className,
- onChange: onChangeContent,
- value: content,
+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 dependency array of registered script handles when calling `wp_register_script`.
+
+```php
+wp_register_script(
+ 'gutenberg-examples-03',
+ plugins_url( 'block.js', __FILE__ ),
+ array(
+ 'wp-blocks',
+ 'wp-element',
+ 'wp-editor' // Note the addition of wp-editor to the dependencies
+ ),
+ filemtime( plugin_dir_path( __FILE__ ) . 'block.js' )
+);
+```
+
+Do not forget to also update the `editor_script` handle in `register_block_type` to `gutenberg-examples-03`.
+
+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.
+
+Because `RichText` allows for nested nodes, you'll most often use it in conjunction with the `html` attribute source when extracting the value from saved content. You'll also use `RichText.Content` in the `save` function to output RichText values.
+
+Here is the complete block definition for Example 03.
+
+{% codetabs %}
+{% ES5 %}
+```js
+( function( blocks, editor, element ) {
+ var el = element.createElement;
+ var RichText = editor.RichText;
+
+ blocks.registerBlockType( 'gutenberg-examples/example-03-editable', {
+ title: 'Example: Editable',
+ icon: 'universal-access-alt',
+ category: 'layout',
+
+ attributes: {
+ content: {
+ type: 'array',
+ source: 'children',
+ selector: 'p',
+ },
+ },
+
+ edit: function( props ) {
+ var content = props.attributes.content;
+ function onChangeContent( newContent ) {
+ props.setAttributes( { content: newContent } );
}
- );
- },
- save: function( props ) {
- var content = props.attributes.content;
+ return el(
+ RichText,
+ {
+ tagName: 'p',
+ className: props.className,
+ onChange: onChangeContent,
+ value: content,
+ }
+ );
+ },
- return el( RichText.Content, {
- tagName: 'p',
- className: props.className,
- value: content
- } );
- },
-} );
+ save: function( props ) {
+ return el( RichText.Content, {
+ tagName: 'p', value: props.attributes.content,
+ } );
+ },
+ } );
+}(
+ window.wp.blocks,
+ window.wp.editor,
+ window.wp.element
+) );
```
{% ESNext %}
```js
const { registerBlockType } = wp.blocks;
const { RichText } = wp.editor;
-registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-03', {
- title: 'Hello World (Step 3)',
-
+registerBlockType( 'gutenberg-examples/example-03-editable-esnext', {
+ title: 'Example: Editable (esnext)',
icon: 'universal-access-alt',
-
category: 'layout',
-
attributes: {
content: {
- type: 'string',
- source: 'html',
+ type: 'array',
+ source: 'children',
selector: 'p',
},
},
-
- edit( { attributes, className, setAttributes } ) {
- const { content } = attributes;
-
- function onChangeContent( newContent ) {
+ edit: ( props ) => {
+ const { attributes: { content }, setAttributes, className } = props;
+ const onChangeContent = ( newContent ) => {
setAttributes( { content: newContent } );
- }
-
+ };
return (
);
},
-
- save( { attributes } ) {
- const { content } = attributes;
-
- return (
-
- );
+ save: ( props ) => {
+ return ;
},
} );
```
{% 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.
-
-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.
-
-## Components and the `RichText` Component
-
-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.
-
-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.
-
-Because `RichText` allows for nested nodes, you'll most often use it in conjunction with the `html` attribute source when extracting the value from saved content. You'll also use `RichText.Content` in the `save` function to output RichText values.
diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/readme.md b/docs/designers-developers/developers/tutorials/block-tutorial/readme.md
index 0ff9418d01743a..a3ee5191dbca79 100644
--- a/docs/designers-developers/developers/tutorials/block-tutorial/readme.md
+++ b/docs/designers-developers/developers/tutorials/block-tutorial/readme.md
@@ -4,4 +4,6 @@ The purpose of this tutorial is to step through the fundamentals of creating a n
To follow along with this tutorial, you can [download the accompanying WordPress plugin](https://github.com/WordPress/gutenberg-examples) which includes all of the examples for you to try on your own site. At each step along the way, you should feel free to experiment by modifying the examples with your own ideas and observing the effects they have on the block's behavior.
-Code snippets are provided both for "classic" JavaScript (ECMAScript 5, or "ES5"), as well as newer versions of the language standard (ES2015 and newer, or "ESNext"). You can change between them using tabs found above each code example. When choosing to author your blocks with ESNext, you will need a build step in order to support older browsers. Note that it is not required to use ESNext to create a new block, and you are welcome to use classic JavaScript if you so choose.
+Code snippets are provided both for "classic" JavaScript (ECMAScript 5, or "ES5"), as well as newer versions of the language standard (ES2015 and newer, or "ESNext"). You can change between them using tabs found above each code example. When choosing to author your blocks with ESNext, you need to run [the JavaScript build step](/docs/designers-developers/developers/tutorials/javascript/js-build-setup/) in order to support older browsers.
+
+Note that it is not required to use ESNext to create a new block, and you are welcome to use classic JavaScript if you so choose.
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 3dfb4645fcc3d6..c959b4cd5aec58 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
@@ -10,19 +10,22 @@ While the block's editor behaviors are implemented in JavaScript, you'll need to
```php
'gutenberg-boilerplate-es5-step01',
+ register_block_type( 'gutenberg-examples/example-01-basic', array(
+ 'editor_script' => 'gutenberg-examples-01',
) );
+
}
-add_action( 'init', 'gutenberg_boilerplate_block' );
+add_action( 'init', 'gutenberg_examples_01_register_block' );
```
Note the two script dependencies:
@@ -39,44 +42,58 @@ With the script enqueued, let's look at the implementation of the block itself:
{% codetabs %}
{% ES5 %}
```js
-var el = wp.element.createElement,
- registerBlockType = wp.blocks.registerBlockType,
- blockStyle = { backgroundColor: '#900', color: '#fff', padding: '20px' };
-
-registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-01', {
- title: 'Hello World (Step 1)',
-
- icon: 'universal-access-alt',
-
- category: 'layout',
-
- edit: function() {
- return el( 'p', { style: blockStyle }, 'Hello editor.' );
- },
-
- save: function() {
- return el( 'p', { style: blockStyle }, 'Hello saved content.' );
- },
-} );
+( function( blocks, element ) {
+ var el = element.createElement;
+
+ var blockStyle = {
+ backgroundColor: '#900',
+ color: '#fff',
+ padding: '20px',
+ };
+
+ blocks.registerBlockType( 'gutenberg-examples/example-01-basic', {
+ title: 'Example: Basic',
+ icon: 'universal-access-alt',
+ category: 'layout',
+ edit: function() {
+ return el(
+ 'p',
+ { style: blockStyle },
+ 'Hello World, step 1 (from the editor).'
+ );
+ },
+ save: function() {
+ return el(
+ 'p',
+ { style: blockStyle },
+ 'Hello World, step 1 (from the frontend).'
+ );
+ },
+ } );
+}(
+ window.wp.blocks,
+ window.wp.element
+) );
```
{% ESNext %}
```js
const { registerBlockType } = wp.blocks;
-const blockStyle = { backgroundColor: '#900', color: '#fff', padding: '20px' };
-registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-01', {
- title: 'Hello World (Step 1)',
+const blockStyle = {
+ backgroundColor: '#900',
+ color: '#fff',
+ padding: '20px',
+};
+registerBlockType( 'gutenberg-examples/example-01-basic-esnext', {
+ title: 'Example: Basic (esnext)',
icon: 'universal-access-alt',
-
category: 'layout',
-
edit() {
- return
Hello editor.
;
+ return
Basic example with JSX! (editor)
;
},
-
save() {
- return
Hello saved content.
;
+ return
Basic example with JSX! (front)
;
},
} );
```
@@ -86,6 +103,6 @@ _By now you should be able to see `Hello editor` in the admin side and `Hello sa
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.
+A block name must be prefixed with a namespace specific to your plugin. This helps prevent conflicts when more than one plugin registers a block with the same name. In this example, the namespace is `gutenberg-examples`.
The `edit` and `save` functions describe the structure of your block in the context of the editor and the saved content respectively. While the difference is not obvious in this simple example, in the following sections we'll explore how these are used to enable customization of the block in the editor preview.
diff --git a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md
index 64f513d605cf99..134c3046ab3149 100644
--- a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md
+++ b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md
@@ -86,7 +86,7 @@ After installing, a `node_modules` directory is created with the modules and the
Also, if you look at package.json file it will include a new section:
```json
-"dependencies": {
+"devDependencies": {
"@wordpress/scripts": "3.1.0"
}
```
diff --git a/gutenberg.php b/gutenberg.php
index 549d2251b17a3f..90b1961840fd19 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: 5.3.0
+ * Version: 5.4.0-rc.1
* Author: Gutenberg Team
* Text Domain: gutenberg
*
diff --git a/lib/client-assets.php b/lib/client-assets.php
index 2a97319dd9f1c3..f3ad7c7ee92dfa 100644
--- a/lib/client-assets.php
+++ b/lib/client-assets.php
@@ -245,7 +245,9 @@ function gutenberg_register_scripts_and_styles() {
);
// TEMPORARY: Core does not (yet) provide persistence migration from the
- // introduction of the block editor.
+ // introduction of the block editor and still calls the data plugins.
+ // We unset the existing inline scripts first.
+ $wp_scripts->registered['wp-data']->extra['after'] = array();
wp_add_inline_script(
'wp-data',
implode(
@@ -254,8 +256,10 @@ function gutenberg_register_scripts_and_styles() {
'( function() {',
' var userId = ' . get_current_user_ID() . ';',
' var storageKey = "WP_DATA_USER_" + userId;',
+ ' wp.data',
+ ' .use( wp.data.plugins.persistence, { storageKey: storageKey } );',
' wp.data.plugins.persistence.__unstableMigrate( { storageKey: storageKey } );',
- '} )()',
+ '} )();',
)
)
);
diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php
index fe839ee056704c..3bfbecc382c490 100644
--- a/lib/packages-dependencies.php
+++ b/lib/packages-dependencies.php
@@ -111,6 +111,7 @@
'wp-data' => array(
'lodash',
'wp-compose',
+ 'wp-deprecated',
'wp-element',
'wp-is-shallow-equal',
'wp-priority-queue',
diff --git a/package-lock.json b/package-lock.json
index 5f6113ab8415f8..3d13faa76d4059 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "gutenberg",
- "version": "5.3.0",
+ "version": "5.4.0-rc.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2767,6 +2767,7 @@
"requires": {
"@babel/runtime": "^7.3.1",
"@wordpress/compose": "file:packages/compose",
+ "@wordpress/deprecated": "file:packages/deprecated",
"@wordpress/element": "file:packages/element",
"@wordpress/is-shallow-equal": "file:packages/is-shallow-equal",
"@wordpress/priority-queue": "file:packages/priority-queue",
@@ -4064,6 +4065,15 @@
}
}
},
+ "babel-plugin-inline-json-import": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-inline-json-import/-/babel-plugin-inline-json-import-0.3.2.tgz",
+ "integrity": "sha512-QNNJx08KjmMT25Cw7rAPQ6dlREDPiZGDyApHL8KQ9vrQHbrr4PTi7W8g1tMMZPz0jEMd39nx/eH7xjnDNxq5sA==",
+ "dev": true,
+ "requires": {
+ "decache": "^4.5.1"
+ }
+ },
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
@@ -4698,6 +4708,12 @@
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
"dev": true
},
+ "callsite": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
+ "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
+ "dev": true
+ },
"callsites": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz",
@@ -7210,6 +7226,15 @@
"integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=",
"dev": true
},
+ "decache": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/decache/-/decache-4.5.1.tgz",
+ "integrity": "sha512-5J37nATc6FmOTLbcsr9qx7Nm28qQyg1SK4xyEHqM0IBkNhWFp0Sm+vKoWYHD8wq+OUEb9jLyaKFfzzd1A9hcoA==",
+ "dev": true,
+ "requires": {
+ "callsite": "^1.0.0"
+ }
+ },
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
diff --git a/package.json b/package.json
index f63075eaa1525d..ee92bafcfda43d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gutenberg",
- "version": "5.3.0",
+ "version": "5.4.0-rc.1",
"private": true,
"description": "A new WordPress editor experience",
"repository": "git+https://github.com/WordPress/gutenberg.git",
@@ -77,6 +77,7 @@
"@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config",
"@wordpress/postcss-themes": "file:packages/postcss-themes",
"@wordpress/scripts": "file:packages/scripts",
+ "babel-plugin-inline-json-import": "0.3.2",
"benchmark": "2.1.4",
"browserslist": "4.4.1",
"chalk": "2.4.1",
diff --git a/packages/api-fetch/src/middlewares/preloading.js b/packages/api-fetch/src/middlewares/preloading.js
index 020cb913fced15..4de356243b7366 100644
--- a/packages/api-fetch/src/middlewares/preloading.js
+++ b/packages/api-fetch/src/middlewares/preloading.js
@@ -34,7 +34,11 @@ const createPreloadingMiddleware = ( preloadedData ) => ( options, next ) => {
if ( parse && 'GET' === method && preloadedData[ path ] ) {
return Promise.resolve( preloadedData[ path ].body );
- } else if ( 'OPTIONS' === method && preloadedData[ method ][ path ] ) {
+ } else if (
+ 'OPTIONS' === method &&
+ preloadedData[ method ] &&
+ preloadedData[ method ][ path ]
+ ) {
return Promise.resolve( preloadedData[ method ][ path ] );
}
}
diff --git a/packages/api-fetch/src/middlewares/test/preloading.js b/packages/api-fetch/src/middlewares/test/preloading.js
index d697af3b429494..e2d8a1f4d0c68d 100644
--- a/packages/api-fetch/src/middlewares/test/preloading.js
+++ b/packages/api-fetch/src/middlewares/test/preloading.js
@@ -25,20 +25,29 @@ describe( 'Preloading Middleware', () => {
} );
} );
- it( 'should move to the next middleware if no preloaded data', () => {
- const preloadedData = {};
- const prelooadingMiddleware = createPreloadingMiddleware( preloadedData );
- const requestOptions = {
- method: 'GET',
- path: 'wp/v2/posts',
- };
+ describe.each( [
+ [ 'GET' ],
+ [ 'OPTIONS' ],
+ ] )( '%s', ( method ) => {
+ describe.each( [
+ [ 'all empty', {} ],
+ [ 'method empty', { [ method ]: {} } ],
+ ] )( '%s', ( label, preloadedData ) => {
+ it( 'should move to the next middleware if no preloaded data', () => {
+ const prelooadingMiddleware = createPreloadingMiddleware( preloadedData );
+ const requestOptions = {
+ method,
+ path: 'wp/v2/posts',
+ };
- const callback = ( options ) => {
- expect( options ).toBe( requestOptions );
- return true;
- };
+ const callback = ( options ) => {
+ expect( options ).toBe( requestOptions );
+ return true;
+ };
- const ret = prelooadingMiddleware( requestOptions, callback );
- expect( ret ).toBe( true );
+ const ret = prelooadingMiddleware( requestOptions, callback );
+ expect( ret ).toBe( true );
+ } );
+ } );
} );
} );
diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss
index b1566d2ae03caa..03ed0f07bd5de3 100644
--- a/packages/block-editor/src/components/block-list/style.scss
+++ b/packages/block-editor/src/components/block-list/style.scss
@@ -904,6 +904,12 @@
.block-editor-block-contextual-toolbar > * {
pointer-events: auto;
}
+
+ // Full-aligned blocks have negative margins on the parent of the toolbar, so additional position adjustment is not required.
+ &[data-align="full"] .block-editor-block-contextual-toolbar {
+ left: 0;
+ right: 0;
+ }
}
.block-editor-block-list__block.is-focus-mode:not(.is-multi-selected) > .block-editor-block-contextual-toolbar {
diff --git a/packages/block-editor/src/components/media-placeholder/README.md b/packages/block-editor/src/components/media-placeholder/README.md
index 2890d2f1f162a1..9dc983ab7a8d8c 100644
--- a/packages/block-editor/src/components/media-placeholder/README.md
+++ b/packages/block-editor/src/components/media-placeholder/README.md
@@ -27,6 +27,92 @@ const { MediaPlaceholder } = wp.editor;
}
```
+## Props
+
+### accept
+
+A string passed to `FormFileUpload` that tells the browser which file types can be upload to the upload window the browser use e.g: `image/*,video/*`.
+More information about this string is available in https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers.
+This property is similar to the `allowedTypes` property. The difference is the format and the fact that this property affects the behavior of `FormFileUpload` while `allowedTypes` affects the behavior `MediaUpload`.
+
+- Type: `String`
+- Required: No
+
+### addToGallery
+
+If true, and if `gallery === true` the gallery media modal opens directly in the media library where the user can add additional images. When uploading/selecting files on the placeholder, the placeholder appends the files to the existing files list.
+If false the gallery media modal opens in the edit mode where the user can edit existing images, by reordering them, remove them, or change their attributes. When uploading/selecting files on the placeholder the files replace the existing files list.
+
+- Type: `Boolean`
+- Required: No
+- Default: `false`
+
+### allowedTypes
+
+Array with the types of the media to upload/select from the media library.
+Each type is a string that can contain the general mime type e.g: `image`, `audio`, `text`,
+or the complete mime type e.g: `audio/mpeg`, `image/gif`.
+If allowedTypes is unset all mime types should be allowed.
+This property is similar to the `accept` property. The difference is the format and the fact that this property affects the behavior of `MediaUpload` while `accept` affects the behavior `FormFileUpload`.
+
+- Type: `Array`
+- Required: No
+
+### className
+
+Class name added to the placeholder.
+
+- Type: `String`
+- Required: No
+
+### isAppender
+
+If true, the property changes the look of the placeholder to be adequate to scenarios where new files are added to an already existing set of files, e.g., adding files to a gallery.
+If false the default placeholder style is used.
+
+- Type: `Boolean`
+- Required: No
+- Default: `false`
+
+### labels
+
+An object that can contain a `title` and `instructions` properties. These properties are passed to the placeholder component as `label` and `instructions` respectively.
+
+- Type: `Object`
+- Required: No
+
+
+### multiple
+
+Whether to allow multiple selection of files or not.
+
+- Type: `Boolean`
+- Required: No
+- Default: `false`
+
+### onError
+
+Callback called when an upload error happens.
+
+- Type: `Function`
+- Required: No
+
+### onSelect
+
+Callback called when the files are selected/uploaded.
+The call back receives an array with the new files. Each element of the collection is an object containing the media properties of the file e.g.: `url`, `id`,...
+
+- Type: `Function`
+- Required: Yes
+
+### value
+
+Media ID (or media IDs if multiple is true) to be selected by default when opening the media library.
+
+- Type: `Number|Array`
+- Required: No
+
+
## Extend
It includes a `wp.hooks` filter `editor.MediaPlaceholder` that enables developers to replace or extend it.
diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js
index fd788477a0d6d1..f321b19dc1de8d 100644
--- a/packages/block-editor/src/components/provider/index.js
+++ b/packages/block-editor/src/components/provider/index.js
@@ -3,9 +3,14 @@
*/
import { Component } from '@wordpress/element';
import { DropZoneProvider, SlotFillProvider } from '@wordpress/components';
-import { withDispatch, withRegistry } from '@wordpress/data';
+import { withDispatch } from '@wordpress/data';
import { compose } from '@wordpress/compose';
+/**
+ * Internal dependencies
+ */
+import withRegistryProvider from './with-registry-provider';
+
class BlockEditorProvider extends Component {
componentDidMount() {
this.props.updateSettings( this.props.settings );
@@ -115,6 +120,7 @@ class BlockEditorProvider extends Component {
}
export default compose( [
+ withRegistryProvider,
withDispatch( ( dispatch ) => {
const {
updateSettings,
@@ -126,5 +132,4 @@ export default compose( [
resetBlocks,
};
} ),
- withRegistry,
] )( BlockEditorProvider );
diff --git a/packages/block-editor/src/components/provider/with-registry-provider.js b/packages/block-editor/src/components/provider/with-registry-provider.js
new file mode 100644
index 00000000000000..cdb3b7fe7b2ded
--- /dev/null
+++ b/packages/block-editor/src/components/provider/with-registry-provider.js
@@ -0,0 +1,41 @@
+/**
+ * WordPress dependencies
+ */
+import { useState, useEffect } from '@wordpress/element';
+import { withRegistry, createRegistry, RegistryProvider } from '@wordpress/data';
+import { createHigherOrderComponent } from '@wordpress/compose';
+
+/**
+ * Internal dependencies
+ */
+import { storeConfig } from '../../store';
+import applyMiddlewares from '../../store/middlewares';
+
+const withRegistryProvider = createHigherOrderComponent( ( WrappedComponent ) => {
+ return withRegistry( ( { useSubRegistry = true, registry, ...props } ) => {
+ if ( ! useSubRegistry ) {
+ return ;
+ }
+
+ const [ subRegistry, setSubRegistry ] = useState( null );
+ useEffect( () => {
+ const newRegistry = createRegistry( {}, registry );
+ const store = newRegistry.registerStore( 'core/block-editor', storeConfig );
+ // This should be removed after the refactoring of the effects to controls.
+ applyMiddlewares( store );
+ setSubRegistry( newRegistry );
+ }, [ registry ] );
+
+ if ( ! subRegistry ) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+ } );
+}, 'withRegistryProvider' );
+
+export default withRegistryProvider;
diff --git a/packages/block-editor/src/store/index.js b/packages/block-editor/src/store/index.js
index 0119e63d7a3d16..485238f46f606d 100644
--- a/packages/block-editor/src/store/index.js
+++ b/packages/block-editor/src/store/index.js
@@ -17,11 +17,15 @@ import controls from './controls';
*/
const MODULE_KEY = 'core/block-editor';
-const store = registerStore( MODULE_KEY, {
+export const storeConfig = {
reducer,
selectors,
actions,
controls,
+};
+
+const store = registerStore( MODULE_KEY, {
+ ...storeConfig,
persist: [ 'preferences' ],
} );
applyMiddlewares( store );
diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js
index 03f02965457099..fb431d1a48d0f6 100644
--- a/packages/block-editor/src/store/selectors.js
+++ b/packages/block-editor/src/store/selectors.js
@@ -1115,7 +1115,7 @@ export const canInsertBlockType = createSelector(
* the number of inserts that have occurred.
*/
function getInsertUsage( state, id ) {
- return state.preferences.insertUsage[ id ] || null;
+ return get( state.preferences.insertUsage, [ id ], null );
}
/**
diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js
index fde8a74fefa91f..3bbe1abd7f3a85 100644
--- a/packages/block-editor/src/store/test/selectors.js
+++ b/packages/block-editor/src/store/test/selectors.js
@@ -1927,9 +1927,11 @@ describe( 'selectors', () => {
{ id: 1, isTemporary: false, clientId: 'block1', title: 'Reusable Block 1' },
],
},
- preferences: {
- insertUsage: {},
- },
+ // Intentionally include a test case which considers
+ // `insertUsage` as not present within preferences.
+ //
+ // See: https://github.com/WordPress/gutenberg/issues/14580
+ preferences: {},
blockListSettings: {},
};
const items = getInserterItems( state );
diff --git a/packages/block-library/src/columns/style.scss b/packages/block-library/src/columns/style.scss
index e377480792af02..2cc0cf0ec4999e 100644
--- a/packages/block-library/src/columns/style.scss
+++ b/packages/block-library/src/columns/style.scss
@@ -10,6 +10,7 @@
}
.wp-block-column {
+ margin-bottom: 1em;
flex-grow: 1;
// Responsiveness: Show at most one columns on mobile.
@@ -44,14 +45,6 @@
}
}
-// Specificity overide to ensure margin is applied
-// and preserved on last child to ensure that when columns
-// are aligned to bottom they are are flush with each other
-.wp-block-column,
-.entry-content > .wp-block-columns .wp-block-column:last-child {
- margin-bottom: 1em;
-}
-
/**
* All Columns Alignment
*/
diff --git a/packages/block-library/src/embed/edit.js b/packages/block-library/src/embed/edit.js
index 0e251a8f0aafd0..fcc305d9e8a7c3 100644
--- a/packages/block-library/src/embed/edit.js
+++ b/packages/block-library/src/embed/edit.js
@@ -61,13 +61,24 @@ export function getEmbedEditComponent( title, icon, responsive = true ) {
if ( switchedPreview || switchedURL ) {
if ( this.props.cannotEmbed ) {
- // Can't embed this URL, and we've just received or switched the preview.
+ // We either have a new preview or a new URL, but we can't embed it.
+ if ( ! this.props.fetching ) {
+ // If we're not fetching the preview, then we know it can't be embedded, so try
+ // removing any trailing slash, and resubmit.
+ this.resubmitWithoutTrailingSlash();
+ }
return;
}
this.handleIncomingPreview();
}
}
+ resubmitWithoutTrailingSlash() {
+ this.setState( ( prevState ) => ( {
+ url: prevState.url.replace( /\/$/, '' ),
+ } ), this.setUrl );
+ }
+
setUrl( event ) {
if ( event ) {
event.preventDefault();
diff --git a/packages/block-library/src/embed/util.js b/packages/block-library/src/embed/util.js
index 085ea4b22ff8c8..9cba9f50364745 100644
--- a/packages/block-library/src/embed/util.js
+++ b/packages/block-library/src/embed/util.js
@@ -46,7 +46,7 @@ export const findBlock = ( url ) => {
};
export const isFromWordPress = ( html ) => {
- return includes( html, 'class="wp-embedded-content" data-secret' );
+ return includes( html, 'class="wp-embedded-content"' );
};
export const getPhotoHtml = ( photo ) => {
diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js
index 51d2587591eda3..c6336b03508364 100644
--- a/packages/block-library/src/image/edit.js
+++ b/packages/block-library/src/image/edit.js
@@ -28,6 +28,7 @@ import {
ToggleControl,
Toolbar,
withNotices,
+ ExternalLink,
} from '@wordpress/components';
import { compose } from '@wordpress/compose';
import { withSelect } from '@wordpress/data';
@@ -456,7 +457,14 @@ class ImageEdit extends Component {
label={ __( 'Alt Text (Alternative Text)' ) }
value={ alt }
onChange={ this.updateAlt }
- help={ __( 'Alternative text describes your image to people who can’t see it. Add a short description with its key details.' ) }
+ help={
+
+
+ { __( 'Describe the purpose of the image' ) }
+
+ { __( 'Leave empty if the image is purely decorative.' ) }
+
+ }
/>
{ ! isEmpty( imageSizeOptions ) && (
{
if ( ! block ) {
return;
}
- const { name, settings } = block;
+ const { metadata, settings, name } = block;
+ if ( metadata ) {
+ unstable__bootstrapServerSideBlockDefinitions( { [ name ]: metadata } ); // eslint-disable-line camelcase
+ }
registerBlockType( name, settings );
} );
diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js
index 061d36ccf6c697..ccf77324a8ce88 100644
--- a/packages/block-library/src/media-text/edit.js
+++ b/packages/block-library/src/media-text/edit.js
@@ -21,6 +21,7 @@ import {
TextareaControl,
ToggleControl,
Toolbar,
+ ExternalLink,
} from '@wordpress/components';
/**
* Internal dependencies
@@ -170,7 +171,14 @@ class MediaTextEdit extends Component {
label={ __( 'Alt Text (Alternative Text)' ) }
value={ mediaAlt }
onChange={ onMediaAltChange }
- help={ __( 'Alternative text describes your image to people who can’t see it. Add a short description with its key details.' ) }
+ help={
+
+
+ { __( 'Describe the purpose of the image' ) }
+
+ { __( 'Leave empty if the image is purely decorative.' ) }
+
+ }
/> ) }
);
diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss
index 5599dafd7c463f..285c139b3b0fa4 100644
--- a/packages/block-library/src/style.scss
+++ b/packages/block-library/src/style.scss
@@ -150,5 +150,4 @@
// By providing a minimum of margin styles, we ensure it doesn't look broken or unstyled in those themes.
figcaption {
margin-top: 0.5em;
- margin-bottom: 1em;
}
diff --git a/packages/block-library/src/text-columns/block.json b/packages/block-library/src/text-columns/block.json
new file mode 100644
index 00000000000000..cf86b62ec572c0
--- /dev/null
+++ b/packages/block-library/src/text-columns/block.json
@@ -0,0 +1,26 @@
+{
+ "name": "core/text-columns",
+ "icon": "columns",
+ "category": "layout",
+ "attributes": {
+ "content": {
+ "type": "array",
+ "source": "query",
+ "selector": "p",
+ "query": {
+ "children": {
+ "type": "string",
+ "source": "html"
+ }
+ },
+ "default": [ {}, {} ]
+ },
+ "columns": {
+ "type": "number",
+ "default": 2
+ },
+ "width": {
+ "type": "string"
+ }
+ }
+}
diff --git a/packages/block-library/src/text-columns/index.js b/packages/block-library/src/text-columns/index.js
index f14b3b8153baa8..6ec2e8b977109a 100644
--- a/packages/block-library/src/text-columns/index.js
+++ b/packages/block-library/src/text-columns/index.js
@@ -18,7 +18,14 @@ import {
} from '@wordpress/block-editor';
import deprecated from '@wordpress/deprecated';
-export const name = 'core/text-columns';
+/**
+ * Internal dependencies
+ */
+import metadata from './block.json';
+
+const { name } = metadata;
+
+export { metadata, name };
export const settings = {
// Disable insertion as this block is deprecated and ultimately replaced by the Columns block.
@@ -30,32 +37,6 @@ export const settings = {
description: __( 'This block is deprecated. Please use the Columns block instead.' ),
- icon: 'columns',
-
- category: 'layout',
-
- attributes: {
- content: {
- type: 'array',
- source: 'query',
- selector: 'p',
- query: {
- children: {
- type: 'string',
- source: 'html',
- },
- },
- default: [ {}, {} ],
- },
- columns: {
- type: 'number',
- default: 2,
- },
- width: {
- type: 'string',
- },
- },
-
transforms: {
to: [
{
diff --git a/packages/block-library/src/verse/index.js b/packages/block-library/src/verse/index.js
index 4016915ad6689c..ab5434192877e8 100644
--- a/packages/block-library/src/verse/index.js
+++ b/packages/block-library/src/verse/index.js
@@ -18,7 +18,7 @@ export const settings = {
description: __( 'Insert poetry. Use special spacing formats. Or quote song lyrics.' ),
- icon: ,
+ icon: ,
category: 'formatting',
diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js
index 5b54e8ae0f0a4e..175d95871ecbb4 100644
--- a/packages/block-library/src/video/edit.js
+++ b/packages/block-library/src/video/edit.js
@@ -219,8 +219,10 @@ class VideoEdit extends Component {
+
+ { __( 'Poster Image' ) }
+ {
expect( block ).toBeUndefined();
} );
+ it( 'should reject blocks with invalid save function', () => {
+ const block = registerBlockType( 'my-plugin/fancy-block-5', {
+ ...defaultBlockSettings,
+ save: 'invalid',
+ } );
+ expect( console ).toHaveErroredWith( 'The "save" property must be a valid function.' );
+ expect( block ).toBeUndefined();
+ } );
+
it( 'should reject blocks with an invalid edit function', () => {
const blockType = { save: noop, edit: 'not-a-function', category: 'common', title: 'block title' },
block = registerBlockType( 'my-plugin/fancy-block-6', blockType );
@@ -318,12 +327,12 @@ describe( 'blocks', () => {
expect( block ).toBeUndefined();
} );
- it( 'should reject valid blocks when they become invalid after executing filter which removes save property', () => {
+ it( 'should reject blocks which become invalid after executing filter which does not return a plain object', () => {
addFilter( 'blocks.registerBlockType', 'core/blocks/without-save', ( settings ) => {
- return omit( settings, 'save' );
+ return [ settings ];
} );
const block = registerBlockType( 'my-plugin/fancy-block-13', defaultBlockSettings );
- expect( console ).toHaveErroredWith( 'The "save" property must be specified and must be a valid function.' );
+ expect( console ).toHaveErroredWith( 'Block settings must be a valid object.' );
expect( block ).toBeUndefined();
} );
} );
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index c76f5a75ac8d7d..1be0255a96866f 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -1,8 +1,9 @@
-## 7.2.1 (Unreleased)
+## 7.3.0 (Unreleased)
### New Features
- Added a new `render` property to `FormFileUpload` component. Allowing users of the component to custom the UI for their needs.
+- Added a new `BaseControl.VisualLabel` component.
### Bug fixes
diff --git a/packages/components/src/base-control/README.md b/packages/components/src/base-control/README.md
index b79ee413e21905..f451a75b6ee4fd 100644
--- a/packages/components/src/base-control/README.md
+++ b/packages/components/src/base-control/README.md
@@ -45,7 +45,7 @@ If this property is added, a label will be generated using label property as the
If this property is added, a help text will be generated using help property as the content.
-- Type: `String`
+- Type: `String|WPElement`
- Required: No
### className
@@ -62,3 +62,44 @@ The content to be displayed within the BaseControl.
- Type: `Element`
- Required: Yes
+
+## BaseControl.VisualLabel
+
+`BaseControl.VisualLabel` component is used to render a purely visual label inside a `BaseControl` component.
+It should only be used in cases where the children being rendered inside BaseControl are already properly labeled, e.g., a button, but we want an additional visual label for that section equivalent to the labels BaseControl would otherwise use if the label prop was passed.
+
+
+## Usage
+```jsx
+import { BaseControl } from '@wordpress/components';
+
+const MyBaseControl = () => (
+
+
+ Author
+
+
+
+);
+```
+
+### Props
+
+#### className
+
+The class that will be added with `components-base-control__label` to the classes of the wrapper div.
+If no className is passed only `components-base-control__label` is used.
+
+- Type: `String`
+- Required: No
+
+#### children
+
+The content to be displayed within the `BaseControl.VisualLabel`.
+
+- Type: `Element`
+- Required: Yes
diff --git a/packages/components/src/base-control/index.js b/packages/components/src/base-control/index.js
index 925914b7824154..37a5939e10775c 100644
--- a/packages/components/src/base-control/index.js
+++ b/packages/components/src/base-control/index.js
@@ -8,7 +8,7 @@ function BaseControl( { id, label, help, className, children } ) {