diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 2b7c128f77cdcf..0b1cd656f6bdd9 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -111,3 +111,4 @@ This list is manually curated to include valuable contributions by volunteers th | @ajitbohra | | | @ChrisVanPatten | | | @tofumatt | @lonelyvegan | +| @LukePettway | @luke_pettway | \ No newline at end of file diff --git a/docs/block-api.md b/docs/block-api.md index 769ff8105680c4..a3df73ffcb80e6 100644 --- a/docs/block-api.md +++ b/docs/block-api.md @@ -140,7 +140,7 @@ styles: [ ], ``` -Plugins and Themes can also register [custom block style](../docs/extensibility/extending-blocks/#block-style-variations) for exisiting blocks. +Plugins and Themes can also register [custom block style](../docs/extensibility/extending-blocks/#block-style-variations) for existing blocks. #### Attributes (optional) @@ -490,7 +490,7 @@ className: false, 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 programatically, set `inserter` to `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`. ```js // Hide this block from the inserter. diff --git a/docs/data/data-core-editor.md b/docs/data/data-core-editor.md index 4ce76d80ea62a8..ebbc303649e17e 100644 --- a/docs/data/data-core-editor.md +++ b/docs/data/data-core-editor.md @@ -707,7 +707,7 @@ otherwise. *Returns* -Whether block is first in mult-selection. +Whether block is first in multi-selection. ### isBlockMultiSelected @@ -1074,17 +1074,13 @@ Post content. ### canInsertBlockType -Determines if the given block type is allowed to be inserted, and, if -parentClientId is provided, whether it is allowed to be nested within the -given parent. +Determines if the given block type is allowed to be inserted into the block list. *Parameters* * state: Editor state. - * blockName: The name of the given block type, e.g. - 'core/paragraph'. - * parentClientId: The parent that the given block is to be - nested within, or null. + * blockName: The name of the block type, e.g.' core/paragraph'. + * rootClientId: Optional root client ID of block list. *Returns* @@ -1114,7 +1110,7 @@ Items are returned ordered descendingly by their 'utility' and 'frecency'. *Parameters* * state: Editor state. - * parentClientId: The block we are inserting into, if any. + * rootClientId: Optional root client ID of block list. *Returns* @@ -1534,7 +1530,7 @@ be inserted, optionally at a specific index respective a root block list. * blocks: Block objects to insert. * index: Index at which block should be inserted. - * rootClientId: Optional root cliente ID of block list on + * rootClientId: Optional root client ID of block list on which to insert. ### showInsertionPoint @@ -1542,6 +1538,12 @@ be inserted, optionally at a specific index respective a root block list. Returns an action object used in signalling that the insertion point should be shown. +*Parameters* + + * rootClientId: Optional root client ID of block list on + which to insert. + * index: Index at which block should be inserted. + ### hideInsertionPoint Returns an action object hiding the insertion point. diff --git a/docs/language.md b/docs/language.md index 5a88f901bb81ba..8a71e707619fbc 100644 --- a/docs/language.md +++ b/docs/language.md @@ -22,7 +22,7 @@ Additionally, how do we even know this came from our editor? Maybe someone snuck A Gutenberg post is the proper block-aware representation of a post, a collection of semantically consistent descriptions of what each block is and what its essential data is. This representation only ever exists in memory. It is the [chase](https://en.wikipedia.org/wiki/Chase_(printing)) in the typesetter's workshop, ever-shifting as sorts are attached and repositioned. -A Gutenberg post is not the artefact it produces, namely the `post_content`. The latter is the printed page, optimized for the reader, but retaining its invisible markings for later editing. +A Gutenberg post is not the artifact it produces, namely the `post_content`. The latter is the printed page, optimized for the reader, but retaining its invisible markings for later editing. Later sections of this document will refer to _Gutenberg post_ and to _blocks_. These are to be assumed to not be the `post_content` or the invisible markings. diff --git a/gutenberg.php b/gutenberg.php index 1d123094d09330..09ed86af7aa590 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 4.1.1 + * Version: 4.2.0-rc.1 * Author: Gutenberg Team * * @package gutenberg diff --git a/lib/register.php b/lib/register.php index 0401af2f8a7c75..b86df4fd7a37f0 100644 --- a/lib/register.php +++ b/lib/register.php @@ -444,9 +444,27 @@ function gutenberg_register_post_types() { 'wp_block', array( 'labels' => array( - 'name' => __( 'Blocks', 'gutenberg' ), - 'singular_name' => __( 'Block', 'gutenberg' ), - 'search_items' => __( 'Search Blocks', 'gutenberg' ), + 'name' => _x( 'Blocks', 'post type general name', 'gutenberg' ), + 'singular_name' => _x( 'Block', 'post type singular name', 'gutenberg' ), + 'menu_name' => _x( 'Blocks', 'admin menu', 'gutenberg' ), + 'name_admin_bar' => _x( 'Block', 'add new on admin bar', 'gutenberg' ), + 'add_new' => _x( 'Add New', 'Block', 'gutenberg' ), + 'add_new_item' => __( 'Add New Block', 'gutenberg' ), + 'new_item' => __( 'New Block', 'gutenberg' ), + 'edit_item' => __( 'Edit Block', 'gutenberg' ), + 'view_item' => __( 'View Block', 'gutenberg' ), + 'all_items' => __( 'All Blocks', 'gutenberg' ), + 'search_items' => __( 'Search Blocks', 'gutenberg' ), + 'not_found' => __( 'No blocks found.', 'gutenberg' ), + 'not_found_in_trash' => __( 'No blocks found in Trash.', 'gutenberg' ), + 'filter_items_list' => __( 'Filter blocks list', 'gutenberg' ), + 'items_list_navigation' => __( 'Blocks list navigation', 'gutenberg' ), + 'items_list' => __( 'Blocks list', 'gutenberg' ), + 'item_published' => __( 'Block published.', 'gutenberg' ), + 'item_published_privately' => __( 'Block published privately.', 'gutenberg' ), + 'item_reverted_to_draft' => __( 'Block reverted to draft.', 'gutenberg' ), + 'item_scheduled' => __( 'Block scheduled.', 'gutenberg' ), + 'item_updated' => __( 'Block updated.', 'gutenberg' ), ), 'public' => false, 'show_ui' => true, @@ -516,6 +534,35 @@ function gutenberg_register_post_types() { } add_action( 'init', 'gutenberg_register_post_types' ); +/** + * Apply the correct labels for Reusable Blocks in the bulk action updated messages. + * + * @since 4.3.0 + * + * @param array $messages Arrays of messages, each keyed by the corresponding post type. + * @param array $bulk_counts Array of item counts for each message, used to build internationalized strings. + * + * @return array + */ +function gutenberg_bulk_post_updated_messages( $messages, $bulk_counts ) { + $messages['wp_block'] = array( + // translators: Number of blocks updated. + 'updated' => _n( '%s block updated.', '%s blocks updated.', $bulk_counts['updated'], 'gutenberg' ), + // translators: Blocks not updated because they're locked. + 'locked' => ( 1 == $bulk_counts['locked'] ) ? __( '1 block not updated, somebody is editing it.', 'gutenberg' ) : _n( '%s block not updated, somebody is editing it.', '%s blocks not updated, somebody is editing them.', $bulk_counts['locked'], 'gutenberg' ), + // translators: Number of blocks deleted. + 'deleted' => _n( '%s block permanently deleted.', '%s blocks permanently deleted.', $bulk_counts['deleted'], 'gutenberg' ), + // translators: Number of blocks trashed. + 'trashed' => _n( '%s block moved to the Trash.', '%s blocks moved to the Trash.', $bulk_counts['trashed'], 'gutenberg' ), + // translators: Number of blocks untrashed. + 'untrashed' => _n( '%s block restored from the Trash.', '%s blocks restored from the Trash.', $bulk_counts['untrashed'], 'gutenberg' ), + ); + + return $messages; +} + +add_filter( 'bulk_post_updated_messages', 'gutenberg_bulk_post_updated_messages', 10, 2 ); + /** * Injects a hidden input in the edit form to propagate the information that classic editor is selected. * diff --git a/package-lock.json b/package-lock.json index ed71b4aeeb4313..e34e319d22a7fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.1.1", + "version": "4.2.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2189,7 +2189,8 @@ "equivalent-key-map": "^0.2.2", "is-promise": "^2.1.0", "lodash": "^4.17.10", - "redux": "^4.0.0" + "redux": "^4.0.0", + "turbo-combine-reducers": "^1.0.2" } }, "@wordpress/date": { @@ -19933,6 +19934,11 @@ "safe-buffer": "^5.0.1" } }, + "turbo-combine-reducers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/turbo-combine-reducers/-/turbo-combine-reducers-1.0.2.tgz", + "integrity": "sha512-gHbdMZlA6Ym6Ur5pSH/UWrNQMIM9IqTH6SoL1DbHpqEdQ8i+cFunSmSlFykPt0eGQwZ4d/XTHOl74H0/kFBVWw==" + }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", diff --git a/package.json b/package.json index e55538173a0f0e..5ecc9d58868ec5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.1.1", + "version": "4.2.0-rc.1", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", diff --git a/packages/api-fetch/CHANGELOG.md b/packages/api-fetch/CHANGELOG.md index a10c2b53851316..a59aee8634fc73 100644 --- a/packages/api-fetch/CHANGELOG.md +++ b/packages/api-fetch/CHANGELOG.md @@ -1,4 +1,6 @@ -# 2.2.0 (2018-10-29) +## 2.2.1 (2018-10-30) + +## 2.2.0 (2018-10-29) ### New Feature diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 5a540c1cb2a623..2fcc7ce88b236a 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "2.2.0", + "version": "2.2.1", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index f3a7e80e7e4651..8df197bb991957 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.1.7 (2018-10-30) + ## 2.1.6 (2018-10-30) ### Bug Fixes diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 99378cfd77f3fb..98f546fb9f186d 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.1.6", + "version": "2.1.7", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/src/button/test/__snapshots__/index.js.snap b/packages/block-library/src/button/test/__snapshots__/index.js.snap index fa8009e4cbc03e..9d96ec60114b22 100644 --- a/packages/block-library/src/button/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/button/test/__snapshots__/index.js.snap @@ -23,7 +23,11 @@ exports[`core/button block edit matches snapshot 1`] = ` contenteditable="true" data-is-placeholder-visible="true" role="textbox" - /> + > +
+
diff --git a/packages/block-library/src/quote/index.js b/packages/block-library/src/quote/index.js index 4e2d635b5ea8af..3acf0e7d053eaf 100644 --- a/packages/block-library/src/quote/index.js +++ b/packages/block-library/src/quote/index.js @@ -111,7 +111,7 @@ export const settings = { blocks: [ 'core/paragraph' ], transform: ( { value, citation } ) => { const paragraphs = []; - if ( value ) { + if ( value && value !== '

' ) { paragraphs.push( ...split( create( { html: value, multilineTag: 'p' } ), '\u2028' ) .map( ( piece ) => @@ -121,7 +121,7 @@ export const settings = { ) ); } - if ( citation ) { + if ( citation && citation !== '

' ) { paragraphs.push( createBlock( 'core/paragraph', { content: citation, diff --git a/packages/block-library/src/quote/test/__snapshots__/index.js.snap b/packages/block-library/src/quote/test/__snapshots__/index.js.snap index 813b6d8417cb66..54f0293d643a5d 100644 --- a/packages/block-library/src/quote/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/quote/test/__snapshots__/index.js.snap @@ -21,7 +21,11 @@ exports[`core/quote block edit matches snapshot 1`] = ` contenteditable="true" data-is-placeholder-visible="true" role="textbox" - /> + > +
+
diff --git a/packages/block-library/src/text-columns/test/__snapshots__/index.js.snap b/packages/block-library/src/text-columns/test/__snapshots__/index.js.snap index b618bdb565e07f..19e26bedd4e4f5 100644 --- a/packages/block-library/src/text-columns/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/text-columns/test/__snapshots__/index.js.snap @@ -24,7 +24,11 @@ exports[`core/text-columns block edit matches snapshot 1`] = ` contenteditable="true" data-is-placeholder-visible="true" role="textbox" - /> + > +
+

@@ -55,7 +59,11 @@ exports[`core/text-columns block edit matches snapshot 1`] = ` contenteditable="true" data-is-placeholder-visible="true" role="textbox" - /> + > +
+

diff --git a/packages/block-library/src/verse/test/__snapshots__/index.js.snap b/packages/block-library/src/verse/test/__snapshots__/index.js.snap index dadd75177de95d..4141acd82063f3 100644 --- a/packages/block-library/src/verse/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/verse/test/__snapshots__/index.js.snap @@ -18,7 +18,11 @@ exports[`core/verse block edit matches snapshot 1`] = ` contenteditable="true" data-is-placeholder-visible="true" role="textbox" - /> + > +
+

diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md
index 7db53884fe79e3..dd08eb8aa02756 100644
--- a/packages/blocks/CHANGELOG.md
+++ b/packages/blocks/CHANGELOG.md
@@ -1,3 +1,5 @@
+## 5.1.1 (2018-10-30)
+
 ## 5.1.0 (2018-10-30)
 
 ### New feature
diff --git a/packages/blocks/package.json b/packages/blocks/package.json
index 395a73310bada4..348287e08367c4 100644
--- a/packages/blocks/package.json
+++ b/packages/blocks/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@wordpress/blocks",
-	"version": "5.1.0",
+	"version": "5.1.1",
 	"description": "Block API for WordPress.",
 	"author": "The WordPress Contributors",
 	"license": "GPL-2.0-or-later",
diff --git a/packages/blocks/src/api/index.native.js b/packages/blocks/src/api/index.native.js
index c83db0ad39e2c5..e987acbdaaf4e4 100644
--- a/packages/blocks/src/api/index.native.js
+++ b/packages/blocks/src/api/index.native.js
@@ -1,5 +1,6 @@
 export {
 	createBlock,
+	switchToBlockType,
 } from './factory';
 export {
 	default as parse,
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 7351a93a587db3..fe1414540b9cbd 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 5.0.2 (Unreleased)
+
+### Polish
+
+- Tooltip are no longer removed when Button becomes disabled, it's left to the component rendering the Tooltip.
+
+## 5.0.1 (2018-10-30)
+
 ## 5.0.0 (2018-10-29)
 
 ### Breaking Change
diff --git a/packages/components/package.json b/packages/components/package.json
index 8c4df8e4dadb6c..d01a75826df0e5 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@wordpress/components",
-	"version": "5.0.0",
+	"version": "5.0.1",
 	"description": "UI components for WordPress.",
 	"author": "The WordPress Contributors",
 	"license": "GPL-2.0-or-later",
diff --git a/packages/components/src/dropdown/index.js b/packages/components/src/dropdown/index.js
index c8ba61f4ad1837..c99c0c3e8ea47f 100644
--- a/packages/components/src/dropdown/index.js
+++ b/packages/components/src/dropdown/index.js
@@ -11,12 +11,13 @@ import Popover from '../popover';
 class Dropdown extends Component {
 	constructor() {
 		super( ...arguments );
+
 		this.toggle = this.toggle.bind( this );
 		this.close = this.close.bind( this );
-		this.clickOutside = this.clickOutside.bind( this );
-		this.bindContainer = this.bindContainer.bind( this );
 		this.refresh = this.refresh.bind( this );
+
 		this.popoverRef = createRef();
+
 		this.state = {
 			isOpen: false,
 		};
@@ -38,10 +39,6 @@ class Dropdown extends Component {
 		}
 	}
 
-	bindContainer( ref ) {
-		this.container = ref;
-	}
-
 	/**
 	 * When contents change height due to user interaction,
 	 * `refresh` can be called to re-render Popover with correct
@@ -59,12 +56,6 @@ class Dropdown extends Component {
 		} ) );
 	}
 
-	clickOutside( event ) {
-		if ( ! this.container.contains( event.target ) ) {
-			this.close();
-		}
-	}
-
 	close() {
 		this.setState( { isOpen: false } );
 	}
@@ -84,7 +75,7 @@ class Dropdown extends Component {
 		const args = { isOpen, onToggle: this.toggle, onClose: this.close };
 
 		return (
-			
+
{ renderToggle( args ) } { isOpen && ( diff --git a/packages/components/src/icon-button/index.js b/packages/components/src/icon-button/index.js index eec8ecd913a51f..b90ee85d2b6080 100644 --- a/packages/components/src/icon-button/index.js +++ b/packages/components/src/icon-button/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { isArray } from 'lodash'; +import { isArray, isString } from 'lodash'; /** * WordPress dependencies @@ -14,7 +14,7 @@ import { Component } from '@wordpress/element'; */ import Tooltip from '../tooltip'; import Button from '../button'; -import Icon from '../icon'; +import Dashicon from '../dashicon'; // This is intentionally a Component class, not a function component because it // is common to apply a ref to the button element (only supported in class) @@ -25,7 +25,7 @@ class IconButton extends Component { const tooltipText = tooltip || label; // Should show the tooltip if... - const showTooltip = ( + const showTooltip = ! additionalProps.disabled && ( // an explicit tooltip is passed or... tooltip || // there's a shortcut or... @@ -42,14 +42,17 @@ class IconButton extends Component { let element = ( ); if ( showTooltip ) { element = ( - + { element } ); diff --git a/packages/components/src/icon-button/test/index.js b/packages/components/src/icon-button/test/index.js index b93f46a3c5bf66..d5b67eec7930f6 100644 --- a/packages/components/src/icon-button/test/index.js +++ b/packages/components/src/icon-button/test/index.js @@ -20,7 +20,7 @@ describe( 'IconButton', () => { it( 'should render a Dashicon component matching the wordpress icon', () => { const iconButton = shallow( ); - expect( iconButton.find( 'Icon' ).prop( 'icon' ) ).toBe( 'wordpress' ); + expect( iconButton.find( 'Dashicon' ).shallow().hasClass( 'dashicons-wordpress' ) ).toBe( true ); } ); it( 'should render child elements when passed as children', () => { diff --git a/packages/components/src/tooltip/index.js b/packages/components/src/tooltip/index.js index de177eeb7492b1..3bb1f6c8b7ccb7 100644 --- a/packages/components/src/tooltip/index.js +++ b/packages/components/src/tooltip/index.js @@ -10,7 +10,6 @@ import { Component, Children, cloneElement, - findDOMNode, concatChildren, } from '@wordpress/element'; @@ -31,7 +30,6 @@ class Tooltip extends Component { constructor() { super( ...arguments ); - this.bindNode = this.bindNode.bind( this ); this.delayedSetIsOver = debounce( ( isOver ) => this.setState( { isOver } ), TOOLTIP_DELAY @@ -44,65 +42,6 @@ class Tooltip extends Component { componentWillUnmount() { this.delayedSetIsOver.cancel(); - this.disconnectDisabledAttributeObserver(); - } - - componentDidUpdate( prevProps, prevState ) { - const { isOver } = this.state; - if ( isOver !== prevState.isOver ) { - if ( isOver ) { - this.observeDisabledAttribute(); - } else { - this.disconnectDisabledAttributeObserver(); - } - } - } - - /** - * Assigns DOM node of the rendered component as an instance property. - * - * @param {Element} ref Rendered component reference. - */ - bindNode( ref ) { - // Disable reason: Because render clones the child, we don't know what - // type of element we have, but if it's a DOM node, we want to observe - // the disabled attribute. - // eslint-disable-next-line react/no-find-dom-node - this.node = findDOMNode( ref ); - } - - /** - * Disconnects any DOM observer attached to the rendered node. - */ - disconnectDisabledAttributeObserver() { - if ( this.observer ) { - this.observer.disconnect(); - } - } - - /** - * Adds a DOM observer to the rendered node, if supported and if the DOM - * node exists, to monitor for application of a disabled attribute. - */ - observeDisabledAttribute() { - if ( ! window.MutationObserver || ! this.node ) { - return; - } - - this.observer = new window.MutationObserver( ( [ mutation ] ) => { - if ( mutation.target.disabled ) { - // We can assume here that isOver is true, because mutation - // observer is only attached for duration of isOver active - this.setState( { isOver: false } ); - } - } ); - - // Monitor changes to the disable attribute on the DOM node - this.observer.observe( this.node, { - subtree: true, - attributes: true, - attributeFilter: [ 'disabled' ], - } ); } emitToChild( eventName, event ) { @@ -163,7 +102,6 @@ class Tooltip extends Component { const child = Children.only( children ); const { isOver } = this.state; return cloneElement( child, { - ref: this.bindNode, onMouseEnter: this.createToggleIsOver( 'onMouseEnter', true ), onMouseLeave: this.createToggleIsOver( 'onMouseLeave' ), onClick: this.createToggleIsOver( 'onClick' ), diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index efb7fe6c22c47d..f8286ef8c09abb 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.8 (2018-10-30) + ## 2.0.6 (2018-10-22) ## 2.0.5 (2018-10-19) diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 6ce9acdcdbb540..aa48096df8c46b 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "2.0.7", + "version": "2.0.8", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index bc8f8aee7bd42b..34ed8c5a73bb3c 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.0.1 (2018-10-30) + +### Internal + +- Replace Redux implementation of `combineReducers` with in-place-compatible `turbo-combine-reducers`. + ## 3.0.0 (2018-10-29) ### Breaking Changes @@ -10,7 +16,7 @@ ## 2.1.0 (2018-09-30) -## New Features +### New Features - Adding support for using controls in resolvers using the controls plugin. @@ -22,7 +28,7 @@ - Writing resolvers as async generators has been deprecated. Use the controls plugin instead. -## Bug Fixes +### Bug Fixes - Fix the promise middleware in Firefox. diff --git a/packages/data/package.json b/packages/data/package.json index 471486a547eb64..596cad5d3a3c84 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "3.0.0", + "version": "3.0.1", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,7 +29,8 @@ "equivalent-key-map": "^0.2.2", "is-promise": "^2.1.0", "lodash": "^4.17.10", - "redux": "^4.0.0" + "redux": "^4.0.0", + "turbo-combine-reducers": "^1.0.2" }, "devDependencies": { "deep-freeze": "^0.0.1", diff --git a/packages/data/src/index.js b/packages/data/src/index.js index 2e71774abcdbbf..122f9c261b760b 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { combineReducers } from 'redux'; +import combineReducers from 'turbo-combine-reducers'; /** * Internal dependencies diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 105e2b38412604..bb14a1d78bb856 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.2 (2018-10-30) + ## 2.0.1 (2018-10-30) ## 2.0.0 (2018-10-29) diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 3ae06ca6baa103..09b0d0ecbeec6c 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "2.0.1", + "version": "2.0.2", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap index 425ee6a645971d..244a185376638c 100644 --- a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap @@ -42,16 +42,22 @@ exports[`MoreMenu should match snapshot 1`] = ` onMouseLeave={[Function]} type="button" > - - - - - - - + /> + + + + diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index fbc5dbfd755a4c..23c040743210c9 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -237,7 +237,7 @@ body.block-editor-page { input[type="checkbox"] { border-radius: $radius-round-rectangle / 2; - &::before { + &:checked::before { margin: -4px 0 0 -5px; color: $white; } @@ -246,7 +246,7 @@ body.block-editor-page { input[type="radio"] { border-radius: $radius-round; - &::before { + &:checked::before { margin: 3px 0 0 3px; background-color: $white; } diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 8ef9c5cce31eaf..3b0ed28447c317 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,4 +1,4 @@ -## 6.1.0 (Unreleased) +## 6.1.0 (2018-10-30) ### Deprecations diff --git a/packages/editor/package.json b/packages/editor/package.json index 2d2253cd3d3607..3ac12f6921fa48 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "6.0.1", + "version": "6.1.0", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/src/components/autocompleters/block.js b/packages/editor/src/components/autocompleters/block.js index c864e2a77edb69..e48bf2f48e7380 100644 --- a/packages/editor/src/components/autocompleters/block.js +++ b/packages/editor/src/components/autocompleters/block.js @@ -23,14 +23,14 @@ function defaultGetBlockInsertionParentClientId() { /** * Returns the inserter items for the specified parent block. * - * @param {string} parentClientId Client ID of the block for which to retrieve - * inserter items. + * @param {string} rootClientId Client ID of the block for which to retrieve + * inserter items. * * @return {Array} The inserter items for the specified * parent. */ -function defaultGetInserterItems( parentClientId ) { - return select( 'core/editor' ).getInserterItems( parentClientId ); +function defaultGetInserterItems( rootClientId ) { + return select( 'core/editor' ).getInserterItems( rootClientId ); } /** diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index 1213d1c424453e..5858f8731fb7ae 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -412,10 +412,9 @@ export class BlockListBlock extends Component { const shouldShowMobileToolbar = shouldAppearSelected; const { error, dragging } = this.state; - // Insertion point can only be made visible when the side inserter is - // not present, and either the block is at the extent of a selection or - // is the first block in the top-level list rendering. - const shouldShowInsertionPoint = ( isPartOfMultiSelection && isFirst ) || ! isPartOfMultiSelection; + // 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; const canShowInBetweenInserter = ! isEmptyDefaultBlock && ! isPreviousBlockADefaultEmptyBlock; // The wp-block className is important for editor styles. @@ -501,7 +500,6 @@ export class BlockListBlock extends Component { clientId={ clientId } rootClientId={ rootClientId } canShowInserter={ canShowInBetweenInserter } - onInsert={ this.hideHoverEffects } /> ) } - { showInsertionPoint &&
} - { showInserter && ( -
- + ) } + { canShowInserter && ( +
+
) } @@ -74,44 +77,23 @@ class BlockInsertionPoint extends Component { ); } } -export default compose( - withSelect( ( select, { clientId, rootClientId, canShowInserter } ) => { - const { - canInsertBlockType, - getBlockIndex, - getBlockInsertionPoint, - getBlock, - isBlockInsertionPointVisible, - isTyping, - } = select( 'core/editor' ); - const { - getDefaultBlockName, - } = select( 'core/blocks' ); - const blockIndex = clientId ? getBlockIndex( clientId, rootClientId ) : -1; - const insertIndex = blockIndex; - const insertionPoint = getBlockInsertionPoint(); - const block = clientId ? getBlock( clientId ) : null; - const showInsertionPoint = ( - isBlockInsertionPointVisible() && - insertionPoint.index === insertIndex && - insertionPoint.rootClientId === rootClientId && - ( ! block || ! isUnmodifiedDefaultBlock( block ) ) - ); +export default withSelect( ( select, { clientId, rootClientId } ) => { + const { + getBlockIndex, + getBlockInsertionPoint, + getBlock, + isBlockInsertionPointVisible, + } = select( 'core/editor' ); + const blockIndex = getBlockIndex( clientId, rootClientId ); + const insertIndex = blockIndex; + const insertionPoint = getBlockInsertionPoint(); + const block = getBlock( clientId ); + const showInsertionPoint = ( + isBlockInsertionPointVisible() && + insertionPoint.index === insertIndex && + insertionPoint.rootClientId === rootClientId && + ! isUnmodifiedDefaultBlock( block ) + ); - const defaultBlockName = getDefaultBlockName(); - return { - canInsertDefaultBlock: canInsertBlockType( defaultBlockName, rootClientId ), - showInserter: ! isTyping() && canShowInserter, - index: insertIndex, - showInsertionPoint, - }; - } ), - ifCondition( ( { canInsertDefaultBlock } ) => canInsertDefaultBlock ), - withDispatch( ( dispatch ) => { - const { insertDefaultBlock, startTyping } = dispatch( 'core/editor' ); - return { - insertDefaultBlock, - startTyping, - }; - } ) -)( BlockInsertionPoint ); + return { showInsertionPoint, insertIndex }; +} )( BlockInsertionPoint ); diff --git a/packages/editor/src/components/block-list/style.scss b/packages/editor/src/components/block-list/style.scss index 5ae32d9bdc7edf..67f1aaa00a61d1 100644 --- a/packages/editor/src/components/block-list/style.scss +++ b/packages/editor/src/components/block-list/style.scss @@ -640,7 +640,7 @@ justify-content: center; // Show a clickable plus. - .editor-block-list__insertion-point-button { + .editor-inserter__toggle { margin-top: -4px; border-radius: 50%; color: $blue-medium-focus; @@ -666,7 +666,7 @@ // Don't show the sibling inserter before the selected block. .edit-post-layout:not(.has-fixed-toolbar) { // The child selector is necessary for this to work properly in nested contexts. - .is-selected > .editor-block-list__insertion-point > .editor-block-list__insertion-point-inserter { + .is-selected > .editor-block-list__insertion-point .editor-inserter__toggle { opacity: 0; pointer-events: none; diff --git a/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap b/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap index 46c017374b5149..71484219b7d98e 100644 --- a/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap @@ -23,7 +23,7 @@ exports[`DefaultBlockAppender should append a default block when input focused 1 value="Write your story" /> -
@@ -45,7 +45,7 @@ exports[`DefaultBlockAppender should match snapshot 1`] = ` value="Write your story" /> -
@@ -67,7 +67,7 @@ exports[`DefaultBlockAppender should optionally show without prompt 1`] = ` value="" /> -
diff --git a/packages/editor/src/components/inner-blocks/index.js b/packages/editor/src/components/inner-blocks/index.js index 0c06acd345eb5f..c9252e912b5df1 100644 --- a/packages/editor/src/components/inner-blocks/index.js +++ b/packages/editor/src/components/inner-blocks/index.js @@ -128,12 +128,12 @@ InnerBlocks = compose( [ getTemplateLock, } = select( 'core/editor' ); const { clientId } = ownProps; - const parentClientId = getBlockRootClientId( clientId ); + const rootClientId = getBlockRootClientId( clientId ); return { isSelectedBlockInRoot: isBlockSelected( clientId ) || hasSelectedInnerBlock( clientId ), block: getBlock( clientId ), blockListSettings: getBlockListSettings( clientId ), - parentLock: getTemplateLock( parentClientId ), + parentLock: getTemplateLock( rootClientId ), }; } ), withDispatch( ( dispatch, ownProps ) => { diff --git a/packages/editor/src/components/inserter/index.js b/packages/editor/src/components/inserter/index.js index d10f70e315af2c..8017fa4b65c1a2 100644 --- a/packages/editor/src/components/inserter/index.js +++ b/packages/editor/src/components/inserter/index.js @@ -3,10 +3,9 @@ */ import { __ } from '@wordpress/i18n'; import { Dropdown, IconButton } from '@wordpress/components'; -import { createBlock, isUnmodifiedDefaultBlock } from '@wordpress/blocks'; import { Component } from '@wordpress/element'; -import { withSelect, withDispatch } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { withSelect } from '@wordpress/data'; +import { compose, ifCondition } from '@wordpress/compose'; /** * Internal dependencies @@ -30,6 +29,8 @@ class Inserter extends Component { super( ...arguments ); this.onToggle = this.onToggle.bind( this ); + this.renderToggle = this.renderToggle.bind( this ); + this.renderContent = this.renderContent.bind( this ); } onToggle( isOpen ) { @@ -41,20 +42,46 @@ class Inserter extends Component { } } - render() { + /** + * Render callback to display Dropdown toggle element. + * + * @param {Function} options.onToggle Callback to invoke when toggle is + * pressed. + * @param {boolean} options.isOpen Whether dropdown is currently open. + * + * @return {WPElement} Dropdown toggle element. + */ + renderToggle( { onToggle, isOpen } ) { const { - items, - position, - title, - onInsertBlock, - rootClientId, disabled, renderToggle = defaultRenderToggle, } = this.props; - if ( items.length === 0 ) { - return null; - } + return renderToggle( { onToggle, isOpen, disabled } ); + } + + /** + * Render callback to display Dropdown content element. + * + * @param {Function} options.onClose Callback to invoke when dropdown is + * closed. + * + * @return {WPElement} Dropdown content element. + */ + renderContent( { onClose } ) { + const { rootClientId, index } = this.props; + + return ( + + ); + } + + render() { + const { position, title } = this.props; return ( renderToggle( { onToggle, isOpen, disabled } ) } - renderContent={ ( { onClose } ) => { - const onSelect = ( item ) => { - onInsertBlock( item ); - - onClose(); - }; - - return ( - - ); - } } + renderToggle={ this.renderToggle } + renderContent={ this.renderContent } /> ); } } export default compose( [ - withSelect( ( select, { rootClientId } ) => { + withSelect( ( select, { rootClientId, index } ) => { const { getEditedPostAttribute, getBlockInsertionPoint, - getSelectedBlock, getInserterItems, } = select( 'core/editor' ); - let index; - if ( rootClientId === undefined ) { + if ( rootClientId === undefined && index === undefined ) { // Unless explicitly provided, the default insertion point provided // by the store occurs immediately following the selected block. // Otherwise, the default behavior for an undefined index is to @@ -106,21 +117,10 @@ export default compose( [ return { title: getEditedPostAttribute( 'title' ), - selectedBlock: getSelectedBlock(), - items: getInserterItems( rootClientId ), - index, + hasItems: getInserterItems( rootClientId ).length > 0, rootClientId, + index, }; } ), - withDispatch( ( dispatch, ownProps ) => ( { - onInsertBlock: ( item ) => { - const { selectedBlock, index, rootClientId } = ownProps; - const { name, initialAttributes } = item; - const insertedBlock = createBlock( name, initialAttributes ); - if ( selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) { - return dispatch( 'core/editor' ).replaceBlocks( selectedBlock.clientId, insertedBlock ); - } - return dispatch( 'core/editor' ).insertBlock( insertedBlock, index, rootClientId ); - }, - } ) ), + ifCondition( ( { hasItems } ) => hasItems ), ] )( Inserter ); diff --git a/packages/editor/src/components/inserter/menu.js b/packages/editor/src/components/inserter/menu.js index 4bb2362377c390..be7623a6a50aaf 100644 --- a/packages/editor/src/components/inserter/menu.js +++ b/packages/editor/src/components/inserter/menu.js @@ -23,7 +23,12 @@ import scrollIntoView from 'dom-scroll-into-view'; import { __, _n, _x, sprintf } from '@wordpress/i18n'; import { Component, findDOMNode, createRef } from '@wordpress/element'; import { withSpokenMessages, PanelBody } from '@wordpress/components'; -import { getCategories, isReusableBlock } from '@wordpress/blocks'; +import { + getCategories, + isReusableBlock, + createBlock, + isUnmodifiedDefaultBlock, +} from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; import { withInstanceId, compose, withSafeTimeout } from '@wordpress/compose'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; @@ -125,10 +130,12 @@ export class InserterMenu extends Component { hoveredItem: item, } ); + const { showInsertionPoint, hideInsertionPoint } = this.props; if ( item ) { - this.props.showInsertionPoint(); + const { rootClientId, index } = this.props; + showInsertionPoint( rootClientId, index ); } else { - this.props.hideInsertionPoint(); + hideInsertionPoint(); } } @@ -340,21 +347,55 @@ export class InserterMenu extends Component { export default compose( withSelect( ( select, { rootClientId } ) => { const { - getChildBlockNames, - } = select( 'core/blocks' ); - const { + getEditedPostAttribute, + getSelectedBlock, + getInserterItems, getBlockName, } = select( 'core/editor' ); + const { + getChildBlockNames, + } = select( 'core/blocks' ); + const rootBlockName = getBlockName( rootClientId ); + return { + selectedBlock: getSelectedBlock(), rootChildBlocks: getChildBlockNames( rootBlockName ), + title: getEditedPostAttribute( 'title' ), + items: getInserterItems( rootClientId ), + rootClientId, + }; + } ), + withDispatch( ( dispatch, ownProps ) => { + const { + __experimentalFetchReusableBlocks: fetchReusableBlocks, + showInsertionPoint, + hideInsertionPoint, + } = dispatch( 'core/editor' ); + + return { + fetchReusableBlocks, + showInsertionPoint, + hideInsertionPoint, + onSelect( item ) { + const { + replaceBlocks, + insertBlock, + } = dispatch( 'core/editor' ); + const { selectedBlock, index, rootClientId } = ownProps; + const { name, initialAttributes } = item; + + const insertedBlock = createBlock( name, initialAttributes ); + if ( selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) { + replaceBlocks( selectedBlock.clientId, insertedBlock ); + } else { + insertBlock( insertedBlock, index, rootClientId ); + } + + ownProps.onSelect(); + }, }; } ), - withDispatch( ( dispatch ) => ( { - fetchReusableBlocks: dispatch( 'core/editor' ).__experimentalFetchReusableBlocks, - showInsertionPoint: dispatch( 'core/editor' ).showInsertionPoint, - hideInsertionPoint: dispatch( 'core/editor' ).hideInsertionPoint, - } ) ), withSpokenMessages, withInstanceId, withSafeTimeout diff --git a/packages/editor/src/components/post-taxonomies/flat-term-selector.js b/packages/editor/src/components/post-taxonomies/flat-term-selector.js index 55e2f6e9c47c84..7e2a322d042847 100644 --- a/packages/editor/src/components/post-taxonomies/flat-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/flat-term-selector.js @@ -174,12 +174,12 @@ class FlatTermSelector extends Component { const termNames = availableTerms.map( ( term ) => term.name ); const newTermLabel = get( taxonomy, - [ 'data', 'labels', 'add_new_item' ], + [ 'labels', 'add_new_item' ], slug === 'post_tag' ? __( 'Add New Tag' ) : __( 'Add New Term' ) ); const singularName = get( taxonomy, - [ 'data', 'labels', 'singular_name' ], + [ 'labels', 'singular_name' ], slug === 'post_tag' ? __( 'Tag' ) : __( 'Term' ) ); const termAddedLabel = sprintf( _x( '%s added', 'term' ), singularName ); diff --git a/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js b/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js index 482d65cf62abb8..7d3e60d17dcf29 100644 --- a/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js @@ -247,7 +247,7 @@ class HierarchicalTermSelector extends Component { const { filteredTermsTree, formName, formParent, isRequestingTerms, showForm, filterValue } = this.state; const labelWithFallback = ( labelProperty, fallbackIsCategory, fallbackIsNotCategory ) => get( taxonomy, - [ 'data', 'labels', labelProperty ], + [ 'labels', labelProperty ], slug === 'category' ? fallbackIsCategory : fallbackIsNotCategory ); const newTermButtonLabel = labelWithFallback( diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 2548240a6671f9..6b5decb63331dd 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -15,7 +15,7 @@ import memize from 'memize'; /** * WordPress dependencies */ -import { Component, Fragment, RawHTML, createRef } from '@wordpress/element'; +import { Component, Fragment, RawHTML } from '@wordpress/element'; import { isHorizontalEdge, getRectangleFromRange, @@ -115,7 +115,6 @@ export class RichText extends Component { this.formatToValue = memize( this.formatToValue.bind( this ), { size: 1 } ); this.savedContent = value; - this.containerRef = createRef(); this.patterns = getPatterns( { onReplace, multilineTag: this.multilineTag, @@ -933,7 +932,6 @@ export class RichText extends Component { return (
{ isSelected && ! inlineToolbar && ( diff --git a/packages/editor/src/components/rich-text/tinymce.js b/packages/editor/src/components/rich-text/tinymce.js index 25b3a2ac860326..c589dde327075d 100644 --- a/packages/editor/src/components/rich-text/tinymce.js +++ b/packages/editor/src/components/rich-text/tinymce.js @@ -101,9 +101,10 @@ export default class TinyMCE extends Component { constructor() { super(); this.bindEditorNode = this.bindEditorNode.bind( this ); + this.onFocus = this.onFocus.bind( this ); } - componentDidMount() { + onFocus() { this.initialize(); } @@ -158,6 +159,11 @@ export default class TinyMCE extends Component { browser_spellcheck: true, entity_encoding: 'raw', convert_urls: false, + // Disables TinyMCE's parsing to verify HTML. It makes + // initialisation a bit faster. Since we're setting raw HTML + // already with dangerouslySetInnerHTML, we don't need this to be + // verified. + verify_html: false, inline_boundaries_selector: 'a[href],code,b,i,strong,em,del,ins,sup,sub', plugins: [], } ); @@ -169,6 +175,18 @@ export default class TinyMCE extends Component { this.editor = editor; this.props.onSetup( editor ); + // TinyMCE resets the element content on initialization, even + // when it's already identical to what exists currently. This + // behavior clobbers a selection which exists at the time of + // initialization, thus breaking writing flow navigation. The + // hack here neutralizes setHTML during initialization. + let setHTML; + + editor.on( 'preinit', () => { + setHTML = editor.dom.setHTML; + editor.dom.setHTML = () => {}; + } ); + editor.on( 'init', () => { // See https://github.com/tinymce/tinymce/blob/master/src/core/main/ts/keyboard/FormatShortcuts.ts [ 'b', 'i', 'u' ].forEach( ( character ) => { @@ -177,6 +195,8 @@ export default class TinyMCE extends Component { [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ].forEach( ( number ) => { editor.shortcuts.remove( `access+${ number }` ); } ); + + editor.dom.setHTML = setHTML; } ); }, } ); @@ -247,6 +267,11 @@ export default class TinyMCE extends Component { } ); } + if ( initialHTML === '' ) { + // Ensure the field is ready to receive focus by TinyMCE. + initialHTML = '
'; + } + return createElement( tagName, { ...ariaProps, className: classnames( className, 'editor-rich-text__tinymce' ), @@ -258,6 +283,7 @@ export default class TinyMCE extends Component { dangerouslySetInnerHTML: { __html: initialHTML }, onPaste, onInput, + onFocus: this.onFocus, } ); } } diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 2935b833270f0e..d0946c4f0e3d0b 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -312,7 +312,7 @@ export function insertBlock( block, index, rootClientId ) { * * @param {Object[]} blocks Block objects to insert. * @param {?number} index Index at which block should be inserted. - * @param {?string} rootClientId Optional root cliente ID of block list on + * @param {?string} rootClientId Optional root client ID of block list on * which to insert. * * @return {Object} Action object. @@ -331,11 +331,17 @@ export function insertBlocks( blocks, index, rootClientId ) { * Returns an action object used in signalling that the insertion point should * be shown. * + * @param {?string} rootClientId Optional root client ID of block list on + * which to insert. + * @param {?number} index Index at which block should be inserted. + * * @return {Object} Action object. */ -export function showInsertionPoint() { +export function showInsertionPoint( rootClientId, index ) { return { type: 'SHOW_INSERTION_POINT', + rootClientId, + index, }; } diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 306451865409eb..fc6a1e387d9315 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -710,21 +710,23 @@ export function blocksMode( state = {}, action ) { } /** - * Reducer returning the block insertion point visibility, a boolean value - * reflecting whether the insertion point should be shown. + * 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 isInsertionPointVisible( state = false, action ) { +export function insertionPoint( state = null, action ) { switch ( action.type ) { case 'SHOW_INSERTION_POINT': - return true; + const { rootClientId, index } = action; + return { rootClientId, index }; case 'HIDE_INSERTION_POINT': - return false; + return null; } return state; @@ -1100,7 +1102,7 @@ export default optimist( combineReducers( { blockSelection, blocksMode, blockListSettings, - isInsertionPointVisible, + insertionPoint, preferences, saving, postLock, diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index ed734f7e5dcbfd..b31ff36d46794f 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1016,10 +1016,10 @@ export function getLastMultiSelectedBlockClientId( state ) { * specified client ID is the first block of the multi-selection set, or false * otherwise. * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. * - * @return {boolean} Whether block is first in mult-selection. + * @return {boolean} Whether block is first in multi-selection. */ export function isFirstMultiSelectedBlock( state, clientId ) { return getFirstMultiSelectedBlockClientId( state ) === clientId; @@ -1277,7 +1277,12 @@ export function isCaretWithinFormattedText( state ) { export function getBlockInsertionPoint( state ) { let rootClientId, index; - const { end } = state.blockSelection; + const { insertionPoint, blockSelection } = state; + if ( insertionPoint !== null ) { + return insertionPoint; + } + + const { end } = blockSelection; if ( end ) { rootClientId = getBlockRootClientId( state, end ) || undefined; index = getBlockIndex( state, end, rootClientId ) + 1; @@ -1296,7 +1301,7 @@ export function getBlockInsertionPoint( state ) { * @return {?boolean} Whether the insertion point is visible or not. */ export function isBlockInsertionPointVisible( state ) { - return state.isInsertionPointVisible; + return state.insertionPoint !== null; } /** @@ -1501,20 +1506,16 @@ export const getEditedPostContent = createSelector( ); /** - * Determines if the given block type is allowed to be inserted, and, if - * parentClientId is provided, whether it is allowed to be nested within the - * given parent. + * Determines if the given block type is allowed to be inserted into the block list. * - * @param {Object} state Editor state. - * @param {string} blockName The name of the given block type, e.g. - * 'core/paragraph'. - * @param {?string} parentClientId The parent that the given block is to be - * nested within, or null. + * @param {Object} state Editor state. + * @param {string} blockName The name of the block type, e.g.' core/paragraph'. + * @param {?string} rootClientId Optional root client ID of block list. * * @return {boolean} Whether the given block type is allowed to be inserted. */ export const canInsertBlockType = createSelector( - ( state, blockName, parentClientId = null ) => { + ( state, blockName, rootClientId = null ) => { const checkAllowList = ( list, item, defaultResult = null ) => { if ( isBoolean( list ) ) { return list; @@ -1537,17 +1538,17 @@ export const canInsertBlockType = createSelector( return false; } - const isLocked = !! getTemplateLock( state, parentClientId ); + const isLocked = !! getTemplateLock( state, rootClientId ); if ( isLocked ) { return false; } - const parentBlockListSettings = getBlockListSettings( state, parentClientId ); + const parentBlockListSettings = getBlockListSettings( state, rootClientId ); const parentAllowedBlocks = get( parentBlockListSettings, [ 'allowedBlocks' ] ); const hasParentAllowedBlock = checkAllowList( parentAllowedBlocks, blockName ); const blockAllowedParentBlocks = blockType.parent; - const parentName = getBlockName( state, parentClientId ); + const parentName = getBlockName( state, rootClientId ); const hasBlockAllowedParent = checkAllowList( blockAllowedParentBlocks, parentName ); if ( hasParentAllowedBlock !== null && hasBlockAllowedParent !== null ) { @@ -1560,9 +1561,9 @@ export const canInsertBlockType = createSelector( return true; }, - ( state, blockName, parentClientId ) => [ - state.blockListSettings[ parentClientId ], - state.editor.present.blocksByClientId[ parentClientId ], + ( state, blockName, rootClientId ) => [ + state.blockListSettings[ rootClientId ], + state.editor.present.blocksByClientId[ rootClientId ], state.settings.allowedBlockTypes, state.settings.templateLock, ], @@ -1602,8 +1603,8 @@ function getInsertUsage( state, id ) { * * Items are returned ordered descendingly by their 'utility' and 'frecency'. * - * @param {Object} state Editor state. - * @param {?string} parentClientId The block we are inserting into, if any. + * @param {Object} state Editor state. + * @param {?string} rootClientId Optional root client ID of block list. * * @return {Editor.InserterItem[]} Items that appear in inserter. * @@ -1621,7 +1622,7 @@ function getInsertUsage( state, id ) { * @property {number} frecency Hueristic that combines frequency and recency. */ export const getInserterItems = createSelector( - ( state, parentClientId = null ) => { + ( state, rootClientId = null ) => { const calculateUtility = ( category, count, isContextual ) => { if ( isContextual ) { return INSERTER_UTILITY_HIGH; @@ -1659,7 +1660,7 @@ export const getInserterItems = createSelector( return false; } - return canInsertBlockType( state, blockType.name, parentClientId ); + return canInsertBlockType( state, blockType.name, rootClientId ); }; const buildBlockTypeInserterItem = ( blockType ) => { @@ -1689,7 +1690,7 @@ export const getInserterItems = createSelector( }; const shouldIncludeReusableBlock = ( reusableBlock ) => { - if ( ! canInsertBlockType( state, 'core/block', parentClientId ) ) { + if ( ! canInsertBlockType( state, 'core/block', rootClientId ) ) { return false; } @@ -1703,7 +1704,7 @@ export const getInserterItems = createSelector( return false; } - if ( ! canInsertBlockType( state, referencedBlockType.name, parentClientId ) ) { + if ( ! canInsertBlockType( state, referencedBlockType.name, rootClientId ) ) { return false; } @@ -1748,8 +1749,8 @@ export const getInserterItems = createSelector( [ 'desc', 'desc' ] ); }, - ( state, parentClientId ) => [ - state.blockListSettings[ parentClientId ], + ( state, rootClientId ) => [ + state.blockListSettings[ rootClientId ], state.editor.present.blockOrder, state.editor.present.blocksByClientId, state.preferences.insertUsage, diff --git a/packages/editor/src/store/test/reducer.js b/packages/editor/src/store/test/reducer.js index c0462df96f4112..8ab789e987077a 100644 --- a/packages/editor/src/store/test/reducer.js +++ b/packages/editor/src/store/test/reducer.js @@ -30,7 +30,7 @@ import { preferences, saving, blocksMode, - isInsertionPointVisible, + insertionPoint, reusableBlocks, template, blockListSettings, @@ -1274,27 +1274,36 @@ describe( 'state', () => { } ); } ); - describe( 'isInsertionPointVisible', () => { - it( 'should default to false', () => { - const state = isInsertionPointVisible( undefined, {} ); + describe( 'insertionPoint', () => { + it( 'should default to null', () => { + const state = insertionPoint( undefined, {} ); - expect( state ).toBe( false ); + expect( state ).toBe( null ); } ); - it( 'should set insertion point visible', () => { - const state = isInsertionPointVisible( false, { + it( 'should set insertion point', () => { + const state = insertionPoint( null, { type: 'SHOW_INSERTION_POINT', + rootClientId: 'clientId1', + index: 0, } ); - expect( state ).toBe( true ); + expect( state ).toEqual( { + rootClientId: 'clientId1', + index: 0, + } ); } ); it( 'should clear the insertion point', () => { - const state = isInsertionPointVisible( true, { + const original = deepFreeze( { + rootClientId: 'clientId1', + index: 0, + } ); + const state = insertionPoint( original, { type: 'HIDE_INSERTION_POINT', } ); - expect( state ).toBe( false ); + expect( state ).toBe( null ); } ); } ); diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index dcfb2016d369ba..157bdb62a5926a 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -2761,6 +2761,40 @@ describe( 'selectors', () => { } ); describe( 'getBlockInsertionPoint', () => { + it( 'should return the explicitly assigned insertion point', () => { + const state = { + currentPost: {}, + preferences: { mode: 'visual' }, + blockSelection: { + start: 'clientId2', + end: 'clientId2', + }, + editor: { + present: { + blocksByClientId: { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + }, + blockOrder: { + '': [ 'clientId1' ], + clientId1: [ 'clientId2' ], + clientId2: [], + }, + edits: {}, + }, + }, + insertionPoint: { + rootClientId: undefined, + index: 0, + }, + }; + + expect( getBlockInsertionPoint( state ) ).toEqual( { + rootClientId: undefined, + index: 0, + } ); + } ); + it( 'should return an object for the selected block', () => { const state = { currentPost: {}, @@ -2781,7 +2815,7 @@ describe( 'selectors', () => { edits: {}, }, }, - isInsertionPointVisible: false, + insertionPoint: null, }; expect( getBlockInsertionPoint( state ) ).toEqual( { @@ -2812,7 +2846,7 @@ describe( 'selectors', () => { edits: {}, }, }, - isInsertionPointVisible: false, + insertionPoint: null, }; expect( getBlockInsertionPoint( state ) ).toEqual( { @@ -2843,7 +2877,7 @@ describe( 'selectors', () => { edits: {}, }, }, - isInsertionPointVisible: false, + insertionPoint: null, }; expect( getBlockInsertionPoint( state ) ).toEqual( { @@ -2874,7 +2908,7 @@ describe( 'selectors', () => { edits: {}, }, }, - isInsertionPointVisible: false, + insertionPoint: null, }; expect( getBlockInsertionPoint( state ) ).toEqual( { @@ -2885,9 +2919,20 @@ describe( 'selectors', () => { } ); describe( 'isBlockInsertionPointVisible', () => { - it( 'should return the value in state', () => { + it( 'should return false if no assigned insertion point', () => { + const state = { + insertionPoint: null, + }; + + expect( isBlockInsertionPointVisible( state ) ).toBe( false ); + } ); + + it( 'should return true if assigned insertion point', () => { const state = { - isInsertionPointVisible: true, + insertionPoint: { + rootClientId: undefined, + index: 5, + }, }; expect( isBlockInsertionPointVisible( state ) ).toBe( true ); diff --git a/packages/format-library/CHANGELOG.md b/packages/format-library/CHANGELOG.md index ab080e1495e9b6..60840015bcccfc 100644 --- a/packages/format-library/CHANGELOG.md +++ b/packages/format-library/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.0.2 (2018-10-30) + ## 1.0.1 (2018-10-30) ## 1.0.0 (2018-10-29) diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 2bbf6563d7de38..d1db967c54b29c 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.0.1", + "version": "1.0.2", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/keycodes/CHANGELOG.md b/packages/keycodes/CHANGELOG.md index a49cefcd0d75cf..9c0e0dcaa72dce 100644 --- a/packages/keycodes/CHANGELOG.md +++ b/packages/keycodes/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.3 (2018-10-30) + ## 2.0.0 (2018-09-05) ### Breaking Change diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 602472cd5025a1..331f0f40a03233 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keycodes", - "version": "2.0.2", + "version": "2.0.3", "description": "Keycodes utilities for WordPress. Used to check for keyboard events across browsers/operating systems.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/list-reusable-blocks/CHANGELOG.md b/packages/list-reusable-blocks/CHANGELOG.md index 6234aa478b9abb..0b07680b95a9aa 100644 --- a/packages/list-reusable-blocks/CHANGELOG.md +++ b/packages/list-reusable-blocks/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.1.6 (2018-10-30) + ## 1.1.4 (2018-10-22) ## 1.1.3 (2018-10-19) diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 026457330f64f5..68822f07f2db41 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "1.1.5", + "version": "1.1.6", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md index 69606f9c9fc37d..7e9aad2e81b11a 100644 --- a/packages/notices/CHANGELOG.md +++ b/packages/notices/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.0.1 (2018-10-30) + ## 1.0.0 (2018-10-29) - Initial release. diff --git a/packages/notices/package.json b/packages/notices/package.json index 93e9ac42d9900e..27a27545c0c9f7 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/notices", - "version": "1.0.0", + "version": "1.0.1", "description": "State management for notices.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/package.json b/packages/nux/package.json index 46a2cf510f3f62..4f5cbaa9acff12 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "2.0.7", + "version": "2.0.8", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/src/components/dot-tip/index.js b/packages/nux/src/components/dot-tip/index.js index dbd81c45d13a62..45c7a768e2089b 100644 --- a/packages/nux/src/components/dot-tip/index.js +++ b/packages/nux/src/components/dot-tip/index.js @@ -5,7 +5,7 @@ import { compose } from '@wordpress/compose'; import { Popover, Button, IconButton } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; -import { deprecated } from '@wordpress/deprecated'; +import deprecated from '@wordpress/deprecated'; function getAnchorRect( anchor ) { // The default getAnchorRect() excludes an element's top and bottom padding diff --git a/packages/rich-text/CHANGELOG.md b/packages/rich-text/CHANGELOG.md index 3efab18e80d667..07a1cf023dd80e 100644 --- a/packages/rich-text/CHANGELOG.md +++ b/packages/rich-text/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.1 (2018-10-30) + ## 2.0.0 (2018-10-30) - Remove `@wordpress/blocks` as a dependency. diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 6c2474383df7da..36a06be61f1d31 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "2.0.0", + "version": "2.0.1", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/rich-text/src/apply-format.js b/packages/rich-text/src/apply-format.js index ebdf6ecaecfea3..7580ebaee2086a 100644 --- a/packages/rich-text/src/apply-format.js +++ b/packages/rich-text/src/apply-format.js @@ -2,15 +2,13 @@ * External dependencies */ -import { find, reject } from 'lodash'; +import { find } from 'lodash'; /** * Internal dependencies */ import { normaliseFormats } from './normalise-formats'; -import { insert } from './insert'; -import { ZERO_WIDTH_NO_BREAK_SPACE } from './special-characters'; /** * Apply a format object to a Rich Text value from the given `startIndex` to the @@ -56,10 +54,16 @@ export function applyFormat( const previousFormat = newFormats[ startIndex - 1 ] || []; const hasType = find( previousFormat, { type: format.type } ); - return insert( { formats, text, start, end }, { - formats: hasType ? [ reject( previousFormat, { type: format.type } ) ] : [ [ ...previousFormat, format ] ], - text: ZERO_WIDTH_NO_BREAK_SPACE, - } ); + return { + formats, + text, + start, + end, + formatPlaceholder: { + index: startIndex, + format: hasType ? undefined : format, + }, + }; } } else { for ( let index = startIndex; index < endIndex; index++ ) { diff --git a/packages/rich-text/src/test/apply-format.js b/packages/rich-text/src/test/apply-format.js index 73416cdb5cfacc..3aa51c927e71c5 100644 --- a/packages/rich-text/src/test/apply-format.js +++ b/packages/rich-text/src/test/apply-format.js @@ -8,7 +8,6 @@ import deepFreeze from 'deep-freeze'; */ import { applyFormat } from '../apply-format'; -import { ZERO_WIDTH_NO_BREAK_SPACE } from '../special-characters'; import { getSparseArrayLength } from './helpers'; describe( 'applyFormat', () => { @@ -61,16 +60,17 @@ describe( 'applyFormat', () => { end: 0, }; const expected = { - formats: [ [ a2 ], , , , , [ a ], [ a ], [ a ], , , , , , , ], - text: `${ ZERO_WIDTH_NO_BREAK_SPACE }one two three`, - start: 1, - end: 1, + ...record, + formatPlaceholder: { + format: a2, + index: 0, + }, }; const result = applyFormat( deepFreeze( record ), a2 ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 4 ); + expect( getSparseArrayLength( result.formats ) ).toBe( 3 ); } ); it( 'should apply format on existing format if selection is collapsed', () => { diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index 319e8b61ee1377..b5851817b5db7f 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -159,6 +159,7 @@ export function toDom( { onEndIndex( body, pointer ) { endPath = createPathToNode( pointer, body, [ pointer.nodeValue.length ] ); }, + isEditableTree: true, } ); if ( createLinePadding ) { diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index d599db049ff9aa..893b331c44a37e 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -6,6 +6,7 @@ import { getFormatType } from './get-format-type'; import { LINE_SEPARATOR, OBJECT_REPLACEMENT_CHARACTER, + ZERO_WIDTH_NO_BREAK_SPACE, } from './special-characters'; function fromFormat( { type, attributes, object } ) { @@ -55,8 +56,9 @@ export function toTree( { appendText, onStartIndex, onEndIndex, + isEditableTree, } ) { - const { formats, text, start, end } = value; + const { formats, text, start, end, formatPlaceholder } = value; const formatsLength = formats.length + 1; const tree = createEmpty(); const multilineFormat = { type: multilineTag }; @@ -73,6 +75,22 @@ export function toTree( { append( tree, '' ); } + function setFormatPlaceholder( pointer, index ) { + if ( isEditableTree && formatPlaceholder && formatPlaceholder.index === index ) { + const parent = getParent( pointer ); + + if ( formatPlaceholder.format === undefined ) { + pointer = getParent( parent ); + } else { + pointer = append( parent, fromFormat( formatPlaceholder.format ) ); + } + + pointer = append( pointer, ZERO_WIDTH_NO_BREAK_SPACE ); + } + + return pointer; + } + for ( let i = 0; i < formatsLength; i++ ) { const character = text.charAt( i ); let characterFormats = formats[ i ]; @@ -146,6 +164,8 @@ export function toTree( { continue; } + pointer = setFormatPlaceholder( pointer, 0 ); + // If there is selection at 0, handle it before characters are inserted. if ( i === 0 ) { if ( onStartIndex && start === 0 ) { @@ -169,6 +189,8 @@ export function toTree( { } } + pointer = setFormatPlaceholder( pointer, i + 1 ); + if ( onStartIndex && start === i + 1 ) { onStartIndex( tree, pointer ); } diff --git a/packages/viewport/CHANGELOG.md b/packages/viewport/CHANGELOG.md index 9b4bad0bd4aa56..7e7b548c9ef068 100644 --- a/packages/viewport/CHANGELOG.md +++ b/packages/viewport/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.7 (2018-10-30) + ## 2.0.5 (2018-10-19) ## 2.0.4 (2018-10-18) diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 3fcab4650d3bfb..c7eb6ef08dc1ab 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/viewport", - "version": "2.0.6", + "version": "2.0.7", "description": "Viewport module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/test/e2e/specs/__snapshots__/rich-text.test.js.snap b/test/e2e/specs/__snapshots__/rich-text.test.js.snap index 2ddafbae544f22..fc96d2d6f1fab5 100644 --- a/test/e2e/specs/__snapshots__/rich-text.test.js.snap +++ b/test/e2e/specs/__snapshots__/rich-text.test.js.snap @@ -1,5 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`RichText should apply formatting when selection is collapsed 1`] = ` +" +

SomeĀ bold.

+" +`; + exports[`RichText should apply formatting with access shortcut 1`] = ` "

test

diff --git a/test/e2e/specs/adding-blocks.test.js b/test/e2e/specs/adding-blocks.test.js index d4eaf27ad8e45d..0c38f03fdbfa13 100644 --- a/test/e2e/specs/adding-blocks.test.js +++ b/test/e2e/specs/adding-blocks.test.js @@ -67,11 +67,20 @@ describe( 'adding blocks', () => { await page.click( '.editor-post-title__input' ); // Using the between inserter - const insertionPoint = await page.$( '[data-type="core/quote"] .editor-block-list__insertion-point-button' ); + const insertionPoint = await page.$( '[data-type="core/quote"] .editor-inserter__toggle' ); const rect = await insertionPoint.boundingBox(); await page.mouse.move( rect.x + ( rect.width / 2 ), rect.y + ( rect.height / 2 ), { steps: 10 } ); - await page.waitForSelector( '[data-type="core/quote"] .editor-block-list__insertion-point-button' ); - await page.click( '[data-type="core/quote"] .editor-block-list__insertion-point-button' ); + await page.waitForSelector( '[data-type="core/quote"] .editor-inserter__toggle' ); + await page.click( '[data-type="core/quote"] .editor-inserter__toggle' ); + // [TODO]: Search input should be focused immediately. It shouldn't be + // necessary to have `waitForFunction`. + await page.waitForFunction( () => ( + document.activeElement && + document.activeElement.classList.contains( 'editor-inserter__search' ) + ) ); + await page.keyboard.type( 'para' ); + await pressTimes( 'Tab', 3 ); + await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'Second paragraph' ); // Switch to Text Mode to check HTML Output diff --git a/test/e2e/specs/block-deletion.test.js b/test/e2e/specs/block-deletion.test.js index 7da0695f1ab436..fb6dd2b40abd5b 100644 --- a/test/e2e/specs/block-deletion.test.js +++ b/test/e2e/specs/block-deletion.test.js @@ -7,6 +7,7 @@ import { newPost, pressWithModifier, ACCESS_MODIFIER_KEYS, + waitForRichTextInitialization, } from '../support/utils'; const addThreeParagraphsToNewPost = async () => { @@ -16,8 +17,10 @@ const addThreeParagraphsToNewPost = async () => { await clickBlockAppender(); await page.keyboard.type( 'First paragraph' ); await page.keyboard.press( 'Enter' ); + await waitForRichTextInitialization(); await page.keyboard.type( 'Second paragraph' ); await page.keyboard.press( 'Enter' ); + await waitForRichTextInitialization(); }; const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { @@ -96,6 +99,7 @@ describe( 'block deletion -', () => { // Add a third paragraph for this test. await page.keyboard.type( 'Third paragraph' ); await page.keyboard.press( 'Enter' ); + await waitForRichTextInitialization(); // Press the up arrow once to select the third and fourth blocks. await pressWithModifier( 'Shift', 'ArrowUp' ); diff --git a/test/e2e/specs/blocks/__snapshots__/quote.test.js.snap b/test/e2e/specs/blocks/__snapshots__/quote.test.js.snap index 6a46b767390efc..5257716d67d00c 100644 --- a/test/e2e/specs/blocks/__snapshots__/quote.test.js.snap +++ b/test/e2e/specs/blocks/__snapshots__/quote.test.js.snap @@ -58,7 +58,11 @@ exports[`Quote can be converted to paragraphs and renders a paragraph for the ci " `; -exports[`Quote can be converted to paragraphs and renders a void paragraph if both the cite and quote are void 1`] = `""`; +exports[`Quote can be converted to paragraphs and renders a void paragraph if both the cite and quote are void 1`] = ` +" +

+" +`; exports[`Quote can be converted to paragraphs and renders one paragraph block per

within quote 1`] = ` " diff --git a/test/e2e/specs/rich-text.test.js b/test/e2e/specs/rich-text.test.js index 398196d0a37db7..9203ec32c303dd 100644 --- a/test/e2e/specs/rich-text.test.js +++ b/test/e2e/specs/rich-text.test.js @@ -45,4 +45,17 @@ describe( 'RichText', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should apply formatting when selection is collapsed', async () => { + await clickBlockAppender(); + await page.keyboard.type( 'Some ' ); + // All following characters should now be bold. + await pressWithModifier( META_KEY, 'b' ); + await page.keyboard.type( 'bold' ); + // All following characters should no longer be bold. + await pressWithModifier( META_KEY, 'b' ); + await page.keyboard.type( '.' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/test/e2e/specs/splitting-merging.test.js b/test/e2e/specs/splitting-merging.test.js index d059fbf42ea63d..fca2a8820a0135 100644 --- a/test/e2e/specs/splitting-merging.test.js +++ b/test/e2e/specs/splitting-merging.test.js @@ -8,6 +8,7 @@ import { pressTimes, pressWithModifier, META_KEY, + waitForRichTextInitialization, } from '../support/utils'; describe( 'splitting and merging blocks', () => { @@ -132,7 +133,9 @@ describe( 'splitting and merging blocks', () => { await pressWithModifier( META_KEY, 'b' ); await page.keyboard.press( 'ArrowRight' ); await page.keyboard.press( 'Enter' ); + await waitForRichTextInitialization(); await page.keyboard.press( 'Enter' ); + await waitForRichTextInitialization(); await page.keyboard.press( 'Backspace' ); @@ -172,8 +175,11 @@ describe( 'splitting and merging blocks', () => { await insertBlock( 'Paragraph' ); await page.keyboard.type( 'First' ); await page.keyboard.press( 'Enter' ); + await waitForRichTextInitialization(); await page.keyboard.press( 'Enter' ); + await waitForRichTextInitialization(); await page.keyboard.press( 'Enter' ); + await waitForRichTextInitialization(); await page.keyboard.type( 'Second' ); await page.keyboard.press( 'ArrowUp' ); await page.keyboard.press( 'ArrowUp' ); diff --git a/test/e2e/support/utils.js b/test/e2e/support/utils.js index 4ac62bf7372991..7275da4d1d2fcf 100644 --- a/test/e2e/support/utils.js +++ b/test/e2e/support/utils.js @@ -100,7 +100,7 @@ async function login() { * @return {Promise} Promise resolving once RichText is initialized, or is * determined to not be a container of the active element. */ -async function waitForRichTextInitialization() { +export async function waitForRichTextInitialization() { const isInRichText = await page.evaluate( () => { return !! document.activeElement.closest( '.editor-rich-text__tinymce' ); } );