From b5e6b68bfd78ee6a6fd179233ac0396da257446e Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Fri, 23 Mar 2018 23:20:22 -0400 Subject: [PATCH 01/54] Global inserter renders Inline Blocks menu when appropriate Inserter checks whether the currently selected block has `canInsertTokens` attribute to determine whether Inline Blocks menu should be rendered instead of the Blocks menu. --- core-blocks/paragraph/index.js | 4 +++ core-blocks/test/fixtures/core__columns.json | 4 +++ .../core__paragraph__align-right.json | 1 + .../fixtures/core__paragraph__deprecated.json | 1 + .../core__text__converts-to-paragraph.json | 1 + editor/components/inserter/index.js | 6 ++++ editor/components/inserter/token-menu.js | 28 +++++++++++++++++++ 7 files changed, 45 insertions(+) create mode 100644 editor/components/inserter/token-menu.js diff --git a/core-blocks/paragraph/index.js b/core-blocks/paragraph/index.js index 0ef4c76a82ba4c..bd741d363d7bd0 100644 --- a/core-blocks/paragraph/index.js +++ b/core-blocks/paragraph/index.js @@ -291,6 +291,10 @@ const schema = { customFontSize: { type: 'number', }, + canInsertTokens: { + type: 'boolean', + default: true, + }, }; export const name = 'core/paragraph'; diff --git a/core-blocks/test/fixtures/core__columns.json b/core-blocks/test/fixtures/core__columns.json index baf28cfd7f291b..f1b0f1bdaefbba 100644 --- a/core-blocks/test/fixtures/core__columns.json +++ b/core-blocks/test/fixtures/core__columns.json @@ -12,6 +12,7 @@ "name": "core/paragraph", "isValid": true, "attributes": { + "canInsertTokens": true, "content": [ "Column One, Paragraph One" ], @@ -26,6 +27,7 @@ "name": "core/paragraph", "isValid": true, "attributes": { + "canInsertTokens": true, "content": [ "Column One, Paragraph Two" ], @@ -40,6 +42,7 @@ "name": "core/paragraph", "isValid": true, "attributes": { + "canInsertTokens": true, "content": [ "Column Two, Paragraph One" ], @@ -54,6 +57,7 @@ "name": "core/paragraph", "isValid": true, "attributes": { + "canInsertTokens": true, "content": [ "Column Three, Paragraph One" ], diff --git a/core-blocks/test/fixtures/core__paragraph__align-right.json b/core-blocks/test/fixtures/core__paragraph__align-right.json index 731657d06705f8..d1afe0ba5eb968 100644 --- a/core-blocks/test/fixtures/core__paragraph__align-right.json +++ b/core-blocks/test/fixtures/core__paragraph__align-right.json @@ -8,6 +8,7 @@ "... like this one, which is separate from the above and right aligned." ], "align": "right", + "canInsertTokens": true, "dropCap": false }, "innerBlocks": [], diff --git a/core-blocks/test/fixtures/core__paragraph__deprecated.json b/core-blocks/test/fixtures/core__paragraph__deprecated.json index 7fe227a9e14cb4..0c4d8ef8db8a08 100644 --- a/core-blocks/test/fixtures/core__paragraph__deprecated.json +++ b/core-blocks/test/fixtures/core__paragraph__deprecated.json @@ -15,6 +15,7 @@ "_store": {} } ], + "canInsertTokens": true, "dropCap": false }, "innerBlocks": [], diff --git a/core-blocks/test/fixtures/core__text__converts-to-paragraph.json b/core-blocks/test/fixtures/core__text__converts-to-paragraph.json index f9ff727423d80a..c667305d3863b1 100644 --- a/core-blocks/test/fixtures/core__text__converts-to-paragraph.json +++ b/core-blocks/test/fixtures/core__text__converts-to-paragraph.json @@ -12,6 +12,7 @@ }, " in #2135." ], + "canInsertTokens": true, "dropCap": false }, "innerBlocks": [], diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js index ea6b0aafd84f10..69a66bdbbd6061 100644 --- a/editor/components/inserter/index.js +++ b/editor/components/inserter/index.js @@ -16,6 +16,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; * Internal dependencies */ import InserterMenu from './menu'; +import InserterTokenMenu from './token-menu'; class Inserter extends Component { constructor() { @@ -47,6 +48,7 @@ class Inserter extends Component { onInsertBlock, hasSupportedBlocks, isLocked, + selectedBlock, } = this.props; if ( ! hasSupportedBlocks || isLocked ) { @@ -79,6 +81,10 @@ class Inserter extends Component { onClose(); }; + if ( selectedBlock && selectedBlock.attributes.canInsertTokens ) { + return ; + } + return ; } } /> diff --git a/editor/components/inserter/token-menu.js b/editor/components/inserter/token-menu.js new file mode 100644 index 00000000000000..2f59803ca8c8e7 --- /dev/null +++ b/editor/components/inserter/token-menu.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { NavigableMenu } from '@wordpress/components'; +import { BlockIcon } from '@wordpress/blocks'; + +export default class InserterTokenMenu extends Component { + render() { + return ( + + + + + ); + } +} From 1adb48fabbe1ea9811a8db6f79acd043f3fc231a Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Fri, 30 Mar 2018 20:10:48 -0400 Subject: [PATCH 02/54] Use block `supports` to determine Inserter state --- core-blocks/paragraph/index.js | 5 +---- core-blocks/test/fixtures/core__columns.json | 4 ---- .../fixtures/core__paragraph__align-right.json | 1 - .../fixtures/core__paragraph__deprecated.json | 1 - .../core__text__converts-to-paragraph.json | 1 - editor/components/inserter/index.js | 18 +++++++++++++----- 6 files changed, 14 insertions(+), 16 deletions(-) diff --git a/core-blocks/paragraph/index.js b/core-blocks/paragraph/index.js index bd741d363d7bd0..1068628fe977fa 100644 --- a/core-blocks/paragraph/index.js +++ b/core-blocks/paragraph/index.js @@ -254,6 +254,7 @@ class ParagraphBlock extends Component { const supports = { className: false, + inlineToken: true, }; const schema = { @@ -291,10 +292,6 @@ const schema = { customFontSize: { type: 'number', }, - canInsertTokens: { - type: 'boolean', - default: true, - }, }; export const name = 'core/paragraph'; diff --git a/core-blocks/test/fixtures/core__columns.json b/core-blocks/test/fixtures/core__columns.json index f1b0f1bdaefbba..baf28cfd7f291b 100644 --- a/core-blocks/test/fixtures/core__columns.json +++ b/core-blocks/test/fixtures/core__columns.json @@ -12,7 +12,6 @@ "name": "core/paragraph", "isValid": true, "attributes": { - "canInsertTokens": true, "content": [ "Column One, Paragraph One" ], @@ -27,7 +26,6 @@ "name": "core/paragraph", "isValid": true, "attributes": { - "canInsertTokens": true, "content": [ "Column One, Paragraph Two" ], @@ -42,7 +40,6 @@ "name": "core/paragraph", "isValid": true, "attributes": { - "canInsertTokens": true, "content": [ "Column Two, Paragraph One" ], @@ -57,7 +54,6 @@ "name": "core/paragraph", "isValid": true, "attributes": { - "canInsertTokens": true, "content": [ "Column Three, Paragraph One" ], diff --git a/core-blocks/test/fixtures/core__paragraph__align-right.json b/core-blocks/test/fixtures/core__paragraph__align-right.json index d1afe0ba5eb968..731657d06705f8 100644 --- a/core-blocks/test/fixtures/core__paragraph__align-right.json +++ b/core-blocks/test/fixtures/core__paragraph__align-right.json @@ -8,7 +8,6 @@ "... like this one, which is separate from the above and right aligned." ], "align": "right", - "canInsertTokens": true, "dropCap": false }, "innerBlocks": [], diff --git a/core-blocks/test/fixtures/core__paragraph__deprecated.json b/core-blocks/test/fixtures/core__paragraph__deprecated.json index 0c4d8ef8db8a08..7fe227a9e14cb4 100644 --- a/core-blocks/test/fixtures/core__paragraph__deprecated.json +++ b/core-blocks/test/fixtures/core__paragraph__deprecated.json @@ -15,7 +15,6 @@ "_store": {} } ], - "canInsertTokens": true, "dropCap": false }, "innerBlocks": [], diff --git a/core-blocks/test/fixtures/core__text__converts-to-paragraph.json b/core-blocks/test/fixtures/core__text__converts-to-paragraph.json index c667305d3863b1..f9ff727423d80a 100644 --- a/core-blocks/test/fixtures/core__text__converts-to-paragraph.json +++ b/core-blocks/test/fixtures/core__text__converts-to-paragraph.json @@ -12,7 +12,6 @@ }, " in #2135." ], - "canInsertTokens": true, "dropCap": false }, "innerBlocks": [], diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js index 69a66bdbbd6061..c1b1fcc5f530c0 100644 --- a/editor/components/inserter/index.js +++ b/editor/components/inserter/index.js @@ -8,7 +8,7 @@ import { isEmpty } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { Dropdown, IconButton } from '@wordpress/components'; -import { createBlock, isUnmodifiedDefaultBlock } from '@wordpress/blocks'; +import { createBlock, isUnmodifiedDefaultBlock, hasBlockSupport } from '@wordpress/blocks'; import { Component, compose } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -23,13 +23,22 @@ class Inserter extends Component { super( ...arguments ); this.onToggle = this.onToggle.bind( this ); + this.state = { + isInline: false, + }; } onToggle( isOpen ) { - const { onToggle } = this.props; + const { onToggle, selectedBlock } = this.props; if ( isOpen ) { - this.props.showInsertionPoint(); + if ( selectedBlock && hasBlockSupport( selectedBlock.name, 'inlineToken' ) ) { + this.setState( { isInline: true } ); + // TODO: show inline insertion point + } else { + this.setState( { isInline: false } ); + this.props.showInsertionPoint(); + } } else { this.props.hideInsertionPoint(); } @@ -48,7 +57,6 @@ class Inserter extends Component { onInsertBlock, hasSupportedBlocks, isLocked, - selectedBlock, } = this.props; if ( ! hasSupportedBlocks || isLocked ) { @@ -81,7 +89,7 @@ class Inserter extends Component { onClose(); }; - if ( selectedBlock && selectedBlock.attributes.canInsertTokens ) { + if ( this.state.isInline ) { return ; } From 9e21ccc3a88c73989526aed220986edf6d8fc2c9 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Fri, 30 Mar 2018 21:24:16 -0400 Subject: [PATCH 03/54] Don't show Inline menu if Paragraph content empty --- editor/components/inserter/index.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js index c1b1fcc5f530c0..85d28b05160bac 100644 --- a/editor/components/inserter/index.js +++ b/editor/components/inserter/index.js @@ -23,16 +23,17 @@ class Inserter extends Component { super( ...arguments ); this.onToggle = this.onToggle.bind( this ); + this.isInsertingInline = this.isInsertingInline.bind( this ); this.state = { isInline: false, }; } onToggle( isOpen ) { - const { onToggle, selectedBlock } = this.props; + const { onToggle } = this.props; if ( isOpen ) { - if ( selectedBlock && hasBlockSupport( selectedBlock.name, 'inlineToken' ) ) { + if ( this.isInsertingInline() ) { this.setState( { isInline: true } ); // TODO: show inline insertion point } else { @@ -49,6 +50,15 @@ class Inserter extends Component { } } + isInsertingInline() { + const { selectedBlock } = this.props; + + return selectedBlock && + hasBlockSupport( selectedBlock.name, 'inlineToken' ) && + selectedBlock.attributes.content && + selectedBlock.attributes.content.length > 0; + } + render() { const { position, From b7ab81c7d71fb89c29d155e06fea0664e5aa1a94 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Fri, 30 Mar 2018 22:55:59 -0400 Subject: [PATCH 04/54] Fix failing e2e test Need to press 'Enter' after typing in Paragraph so that the regular Inserter menu will render instead of the Inline Blocks menu --- test/e2e/specs/multi-block-selection.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/specs/multi-block-selection.test.js b/test/e2e/specs/multi-block-selection.test.js index 064540406b4f18..db131798b971d8 100644 --- a/test/e2e/specs/multi-block-selection.test.js +++ b/test/e2e/specs/multi-block-selection.test.js @@ -19,6 +19,7 @@ describe( 'Multi-block selection', () => { // Creating test blocks await page.click( '.editor-default-block-appender' ); await page.keyboard.type( 'First Paragraph' ); + await page.keyboard.press( 'Enter' ); await page.click( '.edit-post-header [aria-label="Add block"]' ); await page.keyboard.type( 'Image' ); await page.keyboard.press( 'Tab' ); From 2cdd67c738ab12fce53beb964463699a265fa0f3 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 2 Apr 2018 16:15:56 -0400 Subject: [PATCH 05/54] Must be in visual mode to insert Inline Blocks --- editor/components/inserter/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js index 85d28b05160bac..211abe49d87380 100644 --- a/editor/components/inserter/index.js +++ b/editor/components/inserter/index.js @@ -51,12 +51,13 @@ class Inserter extends Component { } isInsertingInline() { - const { selectedBlock } = this.props; + const { selectedBlock, getBlockMode } = this.props; return selectedBlock && hasBlockSupport( selectedBlock.name, 'inlineToken' ) && selectedBlock.attributes.content && - selectedBlock.attributes.content.length > 0; + selectedBlock.attributes.content.length > 0 && + getBlockMode( selectedBlock.uid ) === 'visual'; } render() { @@ -118,6 +119,7 @@ export default compose( [ getSelectedBlock, getSupportedBlocks, getEditorSettings, + getBlockMode, } = select( 'core/editor' ); const { allowedBlockTypes, templateLock } = getEditorSettings(); const insertionPoint = getBlockInsertionPoint(); @@ -129,6 +131,7 @@ export default compose( [ selectedBlock: getSelectedBlock(), hasSupportedBlocks: true === supportedBlocks || ! isEmpty( supportedBlocks ), isLocked: !! templateLock, + getBlockMode, }; } ), withDispatch( ( dispatch, ownProps ) => ( { From ea93dd78a310d03fd7843e102ead06880085e80d Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Wed, 4 Apr 2018 15:30:18 -0400 Subject: [PATCH 06/54] Add state for inline insertion point visibility --- editor/store/actions.js | 23 +++++++++++++++++++++++ editor/store/reducer.js | 22 ++++++++++++++++++++++ editor/store/selectors.js | 11 +++++++++++ 3 files changed, 56 insertions(+) diff --git a/editor/store/actions.js b/editor/store/actions.js index 9028f989452dda..e99e3d0211ff77 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -308,6 +308,29 @@ export function hideInsertionPoint() { }; } +/** + * Returns an action object used in signalling that the inline insertion point + * should be shown. + * + * @return {Object} Action object. + */ +export function showInlineInsertionPoint() { + return { + type: 'SHOW_INLINE_INSERTION_POINT', + }; +} + +/** + * Returns an action object hiding the inline insertion point. + * + * @return {Object} Action object. + */ +export function hideInlineInsertionPoint() { + return { + type: 'HIDE_INLINE_INSERTION_POINT', + }; +} + /** * Returns an action object resetting the template validity. * diff --git a/editor/store/reducer.js b/editor/store/reducer.js index 62217591e58d38..20ccddff9be3b8 100644 --- a/editor/store/reducer.js +++ b/editor/store/reducer.js @@ -769,6 +769,27 @@ export function isInsertionPointVisible( state = false, action ) { return state; } +/** + * Reducer returning the inline insertion point visibility, a boolean value + * reflecting whether the inline insertion point should be shown. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function isInlineInsertionPointVisible( state = false, action ) { + switch ( action.type ) { + case 'SHOW_INLINE_INSERTION_POINT': + return true; + + case 'HIDE_INLINE_INSERTION_POINT': + return false; + } + + return state; +} + /** * Reducer returning whether the post blocks match the defined template or not. * @@ -1065,6 +1086,7 @@ export default optimist( combineReducers( { blocksMode, blockListSettings, isInsertionPointVisible, + isInlineInsertionPointVisible, preferences, saving, notices, diff --git a/editor/store/selectors.js b/editor/store/selectors.js index d9a1615081253c..a405f55d481279 100644 --- a/editor/store/selectors.js +++ b/editor/store/selectors.js @@ -1052,6 +1052,17 @@ export function isBlockInsertionPointVisible( state ) { return state.isInsertionPointVisible; } +/** + * Returns true if we should show the inline insertion point. + * + * @param {Object} state Global application state. + * + * @return {?boolean} Whether the insertion point is visible or not. + */ +export function isInlineInsertionPointVisible( state ) { + return state.isInlineInsertionPointVisible; +} + /** * Returns whether the blocks matches the template or not. * From 8232ac0c57a030985d2bfe126b03c4b6f46e4285 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Wed, 4 Apr 2018 16:46:00 -0400 Subject: [PATCH 07/54] Inserter dispatches inline insertion actions --- editor/components/inserter/index.js | 36 ++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js index 211abe49d87380..26b216c84a7e70 100644 --- a/editor/components/inserter/index.js +++ b/editor/components/inserter/index.js @@ -24,6 +24,8 @@ class Inserter extends Component { this.onToggle = this.onToggle.bind( this ); this.isInsertingInline = this.isInsertingInline.bind( this ); + this.showInsertionPoint = this.showInsertionPoint.bind( this ); + this.hideInsertionPoint = this.hideInsertionPoint.bind( this ); this.state = { isInline: false, }; @@ -33,15 +35,9 @@ class Inserter extends Component { const { onToggle } = this.props; if ( isOpen ) { - if ( this.isInsertingInline() ) { - this.setState( { isInline: true } ); - // TODO: show inline insertion point - } else { - this.setState( { isInline: false } ); - this.props.showInsertionPoint(); - } + this.showInsertionPoint(); } else { - this.props.hideInsertionPoint(); + this.hideInsertionPoint(); } // Surface toggle callback to parent component @@ -50,6 +46,28 @@ class Inserter extends Component { } } + showInsertionPoint() { + const { showInlineInsertionPoint, showInsertionPoint } = this.props; + + if ( this.isInsertingInline() ) { + this.setState( { isInline: true } ); + showInlineInsertionPoint(); + } else { + this.setState( { isInline: false } ); + showInsertionPoint(); + } + } + + hideInsertionPoint() { + const { hideInlineInsertionPoint, hideInsertionPoint } = this.props; + + if ( this.state.isInline ) { + hideInlineInsertionPoint(); + } else { + hideInsertionPoint(); + } + } + isInsertingInline() { const { selectedBlock, getBlockMode } = this.props; @@ -147,5 +165,7 @@ export default compose( [ } return dispatch( 'core/editor' ).insertBlock( insertedBlock, index, rootUID ); }, + showInlineInsertionPoint: dispatch( 'core/editor' ).showInlineInsertionPoint, + hideInlineInsertionPoint: dispatch( 'core/editor' ).hideInlineInsertionPoint, } ) ), ] )( Inserter ); From c1fe71f9f12b13cae6d4615513cd4dbc28162fde Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Fri, 6 Apr 2018 22:31:13 -0400 Subject: [PATCH 08/54] Add InlineInsertionPoint component --- .../rich-text/inline-insertion-point/index.js | 16 +++++++++ .../inline-insertion-point/style.scss | 19 +++++++++++ editor/components/rich-text/index.js | 34 +++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 blocks/rich-text/inline-insertion-point/index.js create mode 100644 blocks/rich-text/inline-insertion-point/style.scss diff --git a/blocks/rich-text/inline-insertion-point/index.js b/blocks/rich-text/inline-insertion-point/index.js new file mode 100644 index 00000000000000..e986098c85ad5a --- /dev/null +++ b/blocks/rich-text/inline-insertion-point/index.js @@ -0,0 +1,16 @@ +/** + * Internal dependencies + */ +import './style.scss'; + +export default function InlineInsertionPoint( { style } ) { + return ( +
+
+
+
+ ); +} diff --git a/blocks/rich-text/inline-insertion-point/style.scss b/blocks/rich-text/inline-insertion-point/style.scss new file mode 100644 index 00000000000000..6a0ffc08dc81a3 --- /dev/null +++ b/blocks/rich-text/inline-insertion-point/style.scss @@ -0,0 +1,19 @@ +.blocks-inline-insertion-point { + display: inline-block; + z-index: 1; +} + +.blocks-inline-insertion-point__caret { + display: inline-block; + width: 1px; + height: 100%; + margin-right: 1px; + background: $black; +} + +.blocks-inline-insertion-point__indicator { + display: inline-block; + width: 3px; + height: 95%; + background: $blue-medium-500; +} diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index 200c34eacd3540..ee640524c13969 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -39,6 +39,7 @@ import Autocomplete from '../autocomplete'; import BlockFormatControls from '../block-format-controls'; import FormatToolbar from './format-toolbar'; import TinyMCE from './tinymce'; +import InlineInsertionPoint from './inline-insertion-point'; import { pickAriaProps } from './aria'; import patterns from './patterns'; import { EVENTS } from './constants'; @@ -417,6 +418,20 @@ export class RichText extends Component { this.context.onCreateUndoLevel(); } + getContainerPosition() { + // Find the parent "relative" or "absolute" positioned container + const findRelativeParent = ( node ) => { + const style = window.getComputedStyle( node ); + if ( style.position === 'relative' || style.position === 'absolute' ) { + return node; + } + return findRelativeParent( node.parentNode ); + }; + const container = findRelativeParent( this.editor.getBody() ); + + return container.getBoundingClientRect(); + } + /** * Calculates the relative position where the link toolbar should be. * @@ -439,6 +454,17 @@ export class RichText extends Component { }; } + getInsertionPosition() { + const containerPosition = this.getContainerPosition(); + const rect = getRectangleFromRange( this.editor.selection.getRng() ); + + return { + top: rect.top - containerPosition.top, + left: rect.right - containerPosition.left, + height: rect.height, + }; + } + /** * Handles a keydown event from tinyMCE. * @@ -834,6 +860,7 @@ export class RichText extends Component { formatters, autocompleters, format, + isInlineInsertionPointVisible = false, } = this.props; const ariaProps = { ...pickAriaProps( this.props ), 'aria-multiline': !! MultilineTag }; @@ -871,6 +898,11 @@ export class RichText extends Component { { formatToolbar }
) } + { isSelected && isInlineInsertionPointVisible && + + } { ( { isExpanded, listBoxId, activeId } ) => ( @@ -942,9 +974,11 @@ const RichTextContainer = compose( [ } ), withSelect( ( select ) => { const { isViewportMatch = identity } = select( 'core/viewport' ) || {}; + const { isInlineInsertionPointVisible = noop } = select( 'core/editor' ) || {}; return { isViewportSmall: isViewportMatch( '< small' ), + isInlineInsertionPointVisible: isInlineInsertionPointVisible(), }; } ), withSafeTimeout, From 01cc53d753bed558ac7d6bd6095889c649009ec7 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 9 Apr 2018 19:39:13 -0400 Subject: [PATCH 09/54] Clicking Inline Image button renders Media Library --- edit-post/hooks/blocks/media-upload/index.js | 10 +++ editor/components/inserter/index.js | 89 ++++++++++++-------- editor/components/inserter/token-menu.js | 1 + 3 files changed, 67 insertions(+), 33 deletions(-) diff --git a/edit-post/hooks/blocks/media-upload/index.js b/edit-post/hooks/blocks/media-upload/index.js index fe20dad65ec2ec..59d2c115f363b3 100644 --- a/edit-post/hooks/blocks/media-upload/index.js +++ b/edit-post/hooks/blocks/media-upload/index.js @@ -81,6 +81,7 @@ class MediaUpload extends Component { this.onOpen = this.onOpen.bind( this ); this.onSelect = this.onSelect.bind( this ); this.onUpdate = this.onUpdate.bind( this ); + this.onClose = this.onClose.bind( this ); this.processMediaCaption = this.processMediaCaption.bind( this ); if ( gallery ) { @@ -122,6 +123,7 @@ class MediaUpload extends Component { this.frame.on( 'select', this.onSelect ); this.frame.on( 'update', this.onUpdate ); this.frame.on( 'open', this.onOpen ); + this.frame.on( 'close', this.onClose ); } componentWillUnmount() { @@ -169,6 +171,14 @@ class MediaUpload extends Component { getAttachmentsCollection( castArray( this.props.value ) ).more(); } + onClose() { + const { onClose } = this.props; + + if ( onClose ) { + onClose(); + } + } + openModal() { this.frame.open(); } diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js index 26b216c84a7e70..337bd0c3baae96 100644 --- a/editor/components/inserter/index.js +++ b/editor/components/inserter/index.js @@ -8,7 +8,12 @@ import { isEmpty } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { Dropdown, IconButton } from '@wordpress/components'; -import { createBlock, isUnmodifiedDefaultBlock, hasBlockSupport } from '@wordpress/blocks'; +import { + createBlock, + isUnmodifiedDefaultBlock, + hasBlockSupport, + MediaUpload, +} from '@wordpress/blocks'; import { Component, compose } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -28,6 +33,7 @@ class Inserter extends Component { this.hideInsertionPoint = this.hideInsertionPoint.bind( this ); this.state = { isInline: false, + mediaLibraryOpen: false, }; } @@ -93,38 +99,55 @@ class Inserter extends Component { } return ( - ( - - { children } - - ) } - renderContent={ ( { onClose } ) => { - const onSelect = ( item ) => { - onInsertBlock( item ); - - onClose(); - }; - - if ( this.state.isInline ) { - return ; - } - - return ; - } } - /> +
+ { this.state.mediaLibraryOpen && + ( this.setState( { mediaLibraryOpen: false } ) ) } + onClose={ () => ( this.setState( { mediaLibraryOpen: false } ) ) } + render={ ( { open } ) => { + open(); + return null; + } } + /> + } + ( + + { children } + + ) } + renderContent={ ( { onClose } ) => { + const onSelect = ( item ) => { + onInsertBlock( item ); + + onClose(); + }; + + if ( this.state.isInline ) { + return ( + this.setState( { mediaLibraryOpen: true } ) } + /> + ); + } + + return ; + } } + /> +
); } } diff --git a/editor/components/inserter/token-menu.js b/editor/components/inserter/token-menu.js index 2f59803ca8c8e7..7caa68b5080fbd 100644 --- a/editor/components/inserter/token-menu.js +++ b/editor/components/inserter/token-menu.js @@ -18,6 +18,7 @@ export default class InserterTokenMenu extends Component {
); From 99d78aff1caea6fe0fbb2a2291e2fb03e5c0db10 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 23 Apr 2018 15:03:16 -0400 Subject: [PATCH 29/54] Move insert position to RichText state Prevents insert position calculation from happening on every render of the insertion point. --- editor/components/rich-text/index.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index 9e4d5473b7504a..b3162c376b185d 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -127,11 +127,12 @@ export class RichText extends Component { this.toggleInsertAvailable = this.toggleInsertAvailable.bind( this ); this.insertInlineBlock = this.insertInlineBlock.bind( this ); this.getFocusPosition = this.getFocusPosition.bind( this ); - this.getInsertionPosition = this.getInsertionPosition.bind( this ); + this.setInsertPosition = this.setInsertPosition.bind( this ); this.state = { formats: {}, selectedNodeId: 0, + insertPosition: {}, }; this.containerRef = createRef(); @@ -463,19 +464,20 @@ export class RichText extends Component { }; } - getInsertionPosition() { + setInsertPosition() { const container = this.getContainerNode(); const containerStyle = window.getComputedStyle( container ); const marginLeft = get( containerStyle, 'margin-left', 0 ); const containerPosition = container.getBoundingClientRect(); const rect = getRectangleFromRange( this.editor.selection.getRng() ); - - return { + const insertPosition = { top: rect.top - containerPosition.top, left: rect.right - containerPosition.left, marginLeft, height: rect.height, }; + + this.setState( { insertPosition } ); } insertInlineBlock() { @@ -806,6 +808,13 @@ export class RichText extends Component { this.toggleInsertAvailable(); } + if ( + this.props.isInlineInsertionPointVisible && + ! prevProps.isInlineInsertionPointVisible + ) { + this.setInsertPosition(); + } + if ( this.props.inlineBlockForInsert && this.props.isSelected ) { this.insertInlineBlock(); } @@ -964,7 +973,7 @@ export class RichText extends Component { ) } { isSelected && isInlineInsertionPointVisible && } From 0849feb80bc0feb5d0867f153be635076f069262 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Tue, 24 Apr 2018 13:44:48 -0400 Subject: [PATCH 30/54] CSS for Image and Gallery Block compatibility --- core-blocks/gallery/style.scss | 4 ++++ core-blocks/image/editor.scss | 4 ++++ editor/components/rich-text/index.js | 9 ++------- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/core-blocks/gallery/style.scss b/core-blocks/gallery/style.scss index 90152b22662b58..869f7d878d285d 100644 --- a/core-blocks/gallery/style.scss +++ b/core-blocks/gallery/style.scss @@ -38,6 +38,10 @@ width: 100%; max-height: 100%; overflow: auto; + + img { + display: inline; + } } } diff --git a/core-blocks/image/editor.scss b/core-blocks/image/editor.scss index edf2a48fb5e858..2fa87c27723466 100644 --- a/core-blocks/image/editor.scss +++ b/core-blocks/image/editor.scss @@ -14,6 +14,10 @@ &.is-transient img { @include loading_fade; } + + figcaption img { + display: inline; + } } .wp-block-image__resize-handler-top-right, diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index b3162c376b185d..a1478b1eac20a6 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -485,13 +485,8 @@ export class RichText extends Component { if ( inlineBlockForInsert.type === 'image' ) { const { url, alt, width } = inlineBlockForInsert; - let img; - - if ( width > 150 ) { - img = `${ alt }`; - } else { - img = `${ alt }`; - } + const imgWidth = width > 150 ? 150 : width; + const img = `${ alt }`; this.editor.insertContent( img ); } From b2e0feb9afc72b2dd94077899a6c69f605dd0a8c Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Tue, 24 Apr 2018 15:09:30 -0400 Subject: [PATCH 31/54] Style resize handles to match Image block --- editor/components/rich-text/style.scss | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/editor/components/rich-text/style.scss b/editor/components/rich-text/style.scss index 0e749d976865cc..4894a734e23af9 100644 --- a/editor/components/rich-text/style.scss +++ b/editor/components/rich-text/style.scss @@ -80,6 +80,19 @@ &.mce-content-body { line-height: $editor-line-height; } + + div.mce-resizehandle { + border-radius: 50%; + border: 2px solid white; + width: 15px; + height: 15px; + background: $blue-medium-500; + box-sizing: border-box; + + &:hover { + background: $blue-medium-500; + } + } } .block-rich-text__inline-toolbar { From bd4fa5a895b811daea2b617f8ee11cd54625db3c Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Thu, 26 Apr 2018 22:16:24 -0400 Subject: [PATCH 32/54] Fix failing e2e test Now that the Quote block uses nested blocks, we press Enter to move to the next block within the Quote before clicking the menu. This is necessary in order to render the standard inserter menu instead of the Inline Blocks menu. --- test/e2e/specs/adding-blocks.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/e2e/specs/adding-blocks.test.js b/test/e2e/specs/adding-blocks.test.js index a2aaa85ccbbacb..07c5e23ab55026 100644 --- a/test/e2e/specs/adding-blocks.test.js +++ b/test/e2e/specs/adding-blocks.test.js @@ -72,8 +72,7 @@ describe( 'adding blocks', () => { await page.keyboard.type( 'Quote block' ); // Using the regular inserter - await page.keyboard.press( 'Tab' ); - await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Enter' ); await page.click( '.edit-post-header [aria-label="Add block"]' ); await page.keyboard.type( 'code' ); await page.keyboard.press( 'Tab' ); From 8d551e1bc6bfc7fabac8a414ebacaa3f615ae3e8 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Fri, 27 Apr 2018 18:19:25 -0400 Subject: [PATCH 33/54] Add reducer tests --- editor/store/reducer.js | 9 ++++ editor/store/test/reducer.js | 84 ++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/editor/store/reducer.js b/editor/store/reducer.js index d6e149a257bb47..3831d3d315ed92 100644 --- a/editor/store/reducer.js +++ b/editor/store/reducer.js @@ -810,6 +810,15 @@ export function inlineBlockForInsert( state = null, action ) { return state; } +/** + * Reducer returning a boolean indicating whether a RichText component is + * selected and available for inline block insertion. + * + * @param {boolean} state Current state. + * @param {Object} action Dispatched action. + * + * @return {boolean} Updated state. + */ export function isInlineInsertAvailable( state = false, action ) { switch ( action.type ) { case 'SET_INLINE_INSERT_AVAILABLE': diff --git a/editor/store/test/reducer.js b/editor/store/test/reducer.js index 5bdb63393d6db1..32c8b6c39fa595 100644 --- a/editor/store/test/reducer.js +++ b/editor/store/test/reducer.js @@ -33,6 +33,9 @@ import { provisionalBlockUID, blocksMode, isInsertionPointVisible, + isInlineInsertionPointVisible, + isInlineInsertAvailable, + inlineBlockForInsert, sharedBlocks, template, blockListSettings, @@ -1298,6 +1301,87 @@ describe( 'state', () => { } ); } ); + describe( 'isInlineInsertionPointVisible', () => { + it( 'should default to false', () => { + const state = isInlineInsertionPointVisible( undefined, {} ); + + expect( state ).toBe( false ); + } ); + + it( 'should set inline insertion point visible', () => { + const state = isInlineInsertionPointVisible( false, { + type: 'SHOW_INLINE_INSERTION_POINT', + } ); + + expect( state ).toBe( true ); + } ); + + it( 'should clear the inline insertion point', () => { + const state = isInlineInsertionPointVisible( true, { + type: 'HIDE_INLINE_INSERTION_POINT', + } ); + + expect( state ).toBe( false ); + } ); + } ); + + describe( 'isInlineInsertAvailable', () => { + it( 'should default to false', () => { + const state = isInlineInsertAvailable( undefined, {} ); + + expect( state ).toBe( false ); + } ); + + it( 'should set inline insert available', () => { + const state = isInlineInsertAvailable( false, { + type: 'SET_INLINE_INSERT_AVAILABLE', + } ); + + expect( state ).toBe( true ); + } ); + + it( 'should set inline insert unavailable', () => { + const state = isInlineInsertAvailable( true, { + type: 'SET_INLINE_INSERT_UNAVAILABLE', + } ); + + expect( state ).toBe( false ); + } ); + } ); + + describe( 'inlineBlockForInsert', () => { + const inlineBlock = { + type: 'image', + url: 'http://localhost:8888/wp-content/uploads/2018/04/example.jpg', + alt: 'example', + width: 400, + height: 300, + }; + + it( 'should default to null', () => { + const state = inlineBlockForInsert( undefined, {} ); + + expect( state ).toBe( null ); + } ); + + it( 'should insert inline block object', () => { + const state = inlineBlockForInsert( null, { + type: 'INSERT_INLINE', + inlineBlock, + } ); + + expect( state ).toBe( inlineBlock ); + } ); + + it( 'should be null after insert complete', () => { + const state = inlineBlockForInsert( inlineBlock, { + type: 'INLINE_INSERT_COMPLETE', + } ); + + expect( state ).toBe( null ); + } ); + } ); + describe( 'isTyping()', () => { it( 'should set the typing flag to true', () => { const state = isTyping( false, { From d42271da9a23d3c94e8e6c5adc9e850a64f81ae5 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Fri, 27 Apr 2018 18:53:05 -0400 Subject: [PATCH 34/54] Add tests for actions --- editor/store/actions.js | 12 ++++++++ editor/store/test/actions.js | 54 ++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/editor/store/actions.js b/editor/store/actions.js index a2326ab0ec42b4..7dcfd250891137 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -358,12 +358,24 @@ export function hideInlineInsertionPoint() { }; } +/** + * Returns an action object used in signalling that a RichText component is + * selected and available for inline insertion. + * + * @return {Object} Action object. + */ export function setInlineInsertAvailable() { return { type: 'SET_INLINE_INSERT_AVAILABLE', }; } +/** + * Returns an action object used in signalling that inline insertion is not + * available. + * + * @return {Object} Action object. + */ export function setInlineInsertUnavailable() { return { type: 'SET_INLINE_INSERT_UNAVAILABLE', diff --git a/editor/store/test/actions.js b/editor/store/test/actions.js index 050ff2473015b3..5ca6ea301982f6 100644 --- a/editor/store/test/actions.js +++ b/editor/store/test/actions.js @@ -26,6 +26,12 @@ import { insertBlocks, showInsertionPoint, hideInsertionPoint, + insertInline, + completeInlineInsert, + showInlineInsertionPoint, + hideInlineInsertionPoint, + setInlineInsertAvailable, + setInlineInsertUnavailable, editPost, savePost, trashPost, @@ -229,6 +235,54 @@ describe( 'actions', () => { } ); } ); + describe( 'insertInline', () => { + it( 'should return the INSERT_INLINE action', () => { + expect( insertInline() ).toEqual( { + type: 'INSERT_INLINE', + } ); + } ); + } ); + + describe( 'completeInlineInsert', () => { + it( 'should return the INLINE_INSERT_COMPLETE action', () => { + expect( completeInlineInsert() ).toEqual( { + type: 'INLINE_INSERT_COMPLETE', + } ); + } ); + } ); + + describe( 'showInlineInsertionPoint', () => { + it( 'should return the SHOW_INLINE_INSERTION_POINT action', () => { + expect( showInlineInsertionPoint() ).toEqual( { + type: 'SHOW_INLINE_INSERTION_POINT', + } ); + } ); + } ); + + describe( 'hideInlineInsertionPoint', () => { + it( 'should return the HIDE_INLINE_INSERTION_POINT action', () => { + expect( hideInlineInsertionPoint() ).toEqual( { + type: 'HIDE_INLINE_INSERTION_POINT', + } ); + } ); + } ); + + describe( 'setInlineInsertAvailable', () => { + it( 'should return the SET_INLINE_INSERT_AVAILABLE action', () => { + expect( setInlineInsertAvailable() ).toEqual( { + type: 'SET_INLINE_INSERT_AVAILABLE', + } ); + } ); + } ); + + describe( 'setInlineInsertUnavailable', () => { + it( 'should return the SET_INLINE_INSERT_UNAVAILABLE action', () => { + expect( setInlineInsertUnavailable() ).toEqual( { + type: 'SET_INLINE_INSERT_UNAVAILABLE', + } ); + } ); + } ); + describe( 'editPost', () => { it( 'should return EDIT_POST action', () => { const edits = { format: 'sample' }; From 5a453fdf068ff5cf00ccfdc6a4fd2cfd37d3aeec Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Fri, 27 Apr 2018 19:38:42 -0400 Subject: [PATCH 35/54] Add tests for selectors --- editor/store/selectors.js | 10 ++++++- editor/store/test/selectors.js | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/editor/store/selectors.js b/editor/store/selectors.js index 7b59683cd6e2f8..fd9fa5d0f50a13 100644 --- a/editor/store/selectors.js +++ b/editor/store/selectors.js @@ -1068,12 +1068,20 @@ export function isInlineInsertionPointVisible( state ) { * * @param {Object} state Global application state. * - * @return {Object} Inline Block object with `type` and properties for the `type`. + * @return {Object} Inline Block object with `type` and properties for the `type`, + * or null when not ready for insert. */ export function getInlineBlockForInsert( state ) { return state.inlineBlockForInsert; } +/** + * Returns whether a RichText component is selected and available for inline + * insertion. + * + * @param {boolean} state + * @return {boolean} Whether inline insert is available. + */ export function isInlineInsertAvailable( state ) { return state.isInlineInsertAvailable; } diff --git a/editor/store/test/selectors.js b/editor/store/test/selectors.js index ebcea745c977ea..b860950c6f783a 100644 --- a/editor/store/test/selectors.js +++ b/editor/store/test/selectors.js @@ -67,6 +67,9 @@ const { isTyping, getBlockInsertionPoint, isBlockInsertionPointVisible, + isInlineInsertionPointVisible, + isInlineInsertAvailable, + getInlineBlockForInsert, isSavingPost, didPostSaveRequestSucceed, didPostSaveRequestFail, @@ -2418,6 +2421,51 @@ describe( 'selectors', () => { } ); } ); + describe( 'isInlineInsertionPointVisible', () => { + it( 'should return the value in state', () => { + const state = { + isInlineInsertionPointVisible: true, + }; + + expect( isInlineInsertionPointVisible( state ) ).toBe( true ); + } ); + } ); + + describe( 'isInlineInsertAvailable', () => { + it( 'should return the value in state', () => { + const state = { + isInlineInsertAvailable: true, + }; + + expect( isInlineInsertAvailable( state ) ).toBe( true ); + } ); + } ); + + describe( 'getInlineBlockForInsert', () => { + it( 'should return null when not inserting an inline block', () => { + const state = { + inlineBlockForInsert: null, + }; + + expect( getInlineBlockForInsert( state ) ).toBe( null ); + } ); + + it( 'should return inline block object when ready for insert', () => { + const inlineBlock = { + type: 'image', + url: 'http://localhost:8888/wp-content/uploads/2018/04/example.jpg', + alt: 'example', + width: 400, + height: 300, + }; + const state = { + inlineBlockForInsert: inlineBlock, + }; + + expect( getInlineBlockForInsert( state ) ).toBe( inlineBlock ); + } ); + } ); + describe( 'isSavingPost', () => { it( 'should return true if the post is currently being saved', () => { const state = { From 7895d6defc5b39414baa958cc982c99bda15d25c Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 30 Apr 2018 00:26:00 -0400 Subject: [PATCH 36/54] Add inline blocks module and reorganize code to use it Aims to make it possible to add more inline blocks besides inline images. --- blocks/inline-blocks/index.js | 20 +++++ blocks/inline-blocks/inline-image.js | 17 ++++ editor/components/inserter/index.js | 103 +++++++++------------- editor/components/inserter/inline-menu.js | 35 +++++--- editor/components/rich-text/index.js | 35 ++++++-- editor/store/actions.js | 6 +- editor/store/reducer.js | 10 +-- editor/store/selectors.js | 10 ++- editor/store/test/reducer.js | 24 ++--- editor/store/test/selectors.js | 14 +-- 10 files changed, 154 insertions(+), 120 deletions(-) create mode 100644 blocks/inline-blocks/index.js create mode 100644 blocks/inline-blocks/inline-image.js diff --git a/blocks/inline-blocks/index.js b/blocks/inline-blocks/index.js new file mode 100644 index 00000000000000..b393262989c646 --- /dev/null +++ b/blocks/inline-blocks/index.js @@ -0,0 +1,20 @@ +/** + * External dependencies + */ +import { find } from 'lodash'; + +/** + * Internal dependencies + */ +import { default as inlineImage } from './inline-image'; + +export function getInlineBlocks() { + return [ inlineImage ]; +} + +export function getInlineBlock( id ) { + const inlineBlocks = getInlineBlocks(); + + return find( inlineBlocks, { id } ); +} + diff --git a/blocks/inline-blocks/inline-image.js b/blocks/inline-blocks/inline-image.js new file mode 100644 index 00000000000000..9c3411e65344c0 --- /dev/null +++ b/blocks/inline-blocks/inline-image.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +export default { + id: 'inline-image', + title: __( 'Inline Image' ), + type: 'image', + icon: 'format-image', + render( { url, alt, width }, editor ) { + const imgWidth = width > 150 ? 150 : width; + const img = `${ alt }`; + + editor.insertContent( img ); + }, +}; diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js index d13e669eac14f8..d76d2fcd8f16ef 100644 --- a/editor/components/inserter/index.js +++ b/editor/components/inserter/index.js @@ -8,12 +8,8 @@ import { isEmpty } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { Dropdown, IconButton } from '@wordpress/components'; -import { - createBlock, - isUnmodifiedDefaultBlock, - MediaUpload, -} from '@wordpress/blocks'; -import { Component, compose, Fragment } from '@wordpress/element'; +import { createBlock, isUnmodifiedDefaultBlock } from '@wordpress/blocks'; +import { Component, compose } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; /** @@ -30,10 +26,7 @@ class Inserter extends Component { this.isInsertingInline = this.isInsertingInline.bind( this ); this.showInsertionPoint = this.showInsertionPoint.bind( this ); this.hideInsertionPoint = this.hideInsertionPoint.bind( this ); - this.state = { - isInline: false, - mediaLibraryOpen: false, - }; + this.state = { isInline: false }; } onToggle( isOpen ) { @@ -91,69 +84,53 @@ class Inserter extends Component { hasSupportedBlocks, isLocked, } = this.props; + const { isInline } = this.state; if ( ! hasSupportedBlocks || isLocked ) { return null; } return ( - - { this.state.mediaLibraryOpen && - { - onInsertInline( { type, url, alt, width, height } ); - this.setState( { mediaLibraryOpen: false } ); - } } - onClose={ () => ( this.setState( { mediaLibraryOpen: false } ) ) } - render={ ( { open } ) => { - open(); - return null; - } } - /> - } - ( - - { children } - - ) } - renderContent={ ( { onClose } ) => { - const onSelect = ( item ) => { + ( + + { children } + + ) } + renderContent={ ( { onClose } ) => { + const onSelect = ( item ) => { + if ( isInline ) { + onInsertInline( item.id ); + } else { onInsertBlock( item ); + } - onClose(); - }; - const onImageSelect = () => { - this.setState( { mediaLibraryOpen: true } ); - - onClose(); - }; + onClose(); + }; - if ( this.state.isInline ) { - return ( - - ); - } + if ( isInline ) { + return ( + + ); + } - return ; - } } - /> - + return ; + } } + /> ); } } diff --git a/editor/components/inserter/inline-menu.js b/editor/components/inserter/inline-menu.js index cefc8103f75293..590e2485a70cae 100644 --- a/editor/components/inserter/inline-menu.js +++ b/editor/components/inserter/inline-menu.js @@ -1,15 +1,29 @@ +/** + * External dependencies + */ +import { pick } from 'lodash'; + /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { NavigableMenu } from '@wordpress/components'; -import { BlockIcon } from '@wordpress/blocks'; +import { Component, Fragment } from '@wordpress/element'; +import { getInlineBlocks } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import InserterGroup from './group'; + export default class InserterInlineMenu extends Component { render() { + const inlineBlocks = getInlineBlocks(); + const items = inlineBlocks.map( ( inlineBlock ) => ( + pick( inlineBlock, [ 'id', 'title', 'icon' ] ) + ) ); + return ( - +
{ __( 'Inline Blocks' ) }
- -
+ + ); } } diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index a1478b1eac20a6..ad7ca0ea5e40f0 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -41,6 +41,7 @@ import BlockFormatControls from '../block-format-controls'; import FormatToolbar from './format-toolbar'; import TinyMCE from './tinymce'; import InlineInsertionPoint from './inline-insertion-point'; +import MediaUpload from '../media-upload'; import { pickAriaProps } from './aria'; import patterns from './patterns'; import { EVENTS } from './constants'; @@ -484,14 +485,11 @@ export class RichText extends Component { const { inlineBlockForInsert, completeInlineInsert } = this.props; if ( inlineBlockForInsert.type === 'image' ) { - const { url, alt, width } = inlineBlockForInsert; - const imgWidth = width > 150 ? 150 : width; - const img = `${ alt }`; - - this.editor.insertContent( img ); + this.setState( { mediaLibraryOpen: true } ); + } else { + inlineBlockForInsert.render( this.editor ); + completeInlineInsert(); } - - completeInlineInsert(); } toggleInsertAvailable() { @@ -810,7 +808,11 @@ export class RichText extends Component { this.setInsertPosition(); } - if ( this.props.inlineBlockForInsert && this.props.isSelected ) { + if ( + this.props.isSelected && + this.props.inlineBlockForInsert && + ! prevProps.inlineBlockForInsert + ) { this.insertInlineBlock(); } @@ -929,6 +931,8 @@ export class RichText extends Component { autocompleters, format, isInlineInsertionPointVisible = false, + inlineBlockForInsert, + completeInlineInsert, } = this.props; const ariaProps = { ...pickAriaProps( this.props ), 'aria-multiline': !! MultilineTag }; @@ -971,6 +975,21 @@ export class RichText extends Component { style={ this.state.insertPosition } /> } + { this.state.mediaLibraryOpen && + { + inlineBlockForInsert.render( media, this.editor ); + completeInlineInsert(); + this.setState( { mediaLibraryOpen: false } ); + } } + onClose={ () => ( this.setState( { mediaLibraryOpen: false } ) ) } + render={ ( { open } ) => { + open(); + return null; + } } + /> + } { ( { isExpanded, listBoxId, activeId } ) => ( diff --git a/editor/store/actions.js b/editor/store/actions.js index 7dcfd250891137..f094729e255e75 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -289,14 +289,14 @@ export function insertBlocks( blocks, index, rootUID ) { * Returns an action object used in signalling that an inline block should be * inserted. * - * @param {Object} inlineBlock Inline block object to insert. + * @param {string} inlineBlockId ID of inline block to insert. * * @return {Object} Action object. */ -export function insertInline( inlineBlock ) { +export function insertInline( inlineBlockId ) { return { type: 'INSERT_INLINE', - inlineBlock, + inlineBlockId, }; } diff --git a/editor/store/reducer.js b/editor/store/reducer.js index 3831d3d315ed92..2c9b6ae7bbd6bc 100644 --- a/editor/store/reducer.js +++ b/editor/store/reducer.js @@ -791,17 +791,17 @@ export function isInlineInsertionPointVisible( state = false, action ) { } /** - * Reducer returning an Inline Block object for insertion. + * Reducer returning an Inline Block id for insertion. * - * @param {Object} state Current state. + * @param {string} state Current state. * @param {Object} action Dispatched action. * * @return {Object} Updated state. */ -export function inlineBlockForInsert( state = null, action ) { +export function inlineBlockIdForInsert( state = null, action ) { switch ( action.type ) { case 'INSERT_INLINE': - return action.inlineBlock; + return action.inlineBlockId; case 'INLINE_INSERT_COMPLETE': return null; @@ -1128,7 +1128,7 @@ export default optimist( combineReducers( { blockListSettings, isInsertionPointVisible, isInlineInsertionPointVisible, - inlineBlockForInsert, + inlineBlockIdForInsert, isInlineInsertAvailable, preferences, saving, diff --git a/editor/store/selectors.js b/editor/store/selectors.js index fd9fa5d0f50a13..757c8a0713a989 100644 --- a/editor/store/selectors.js +++ b/editor/store/selectors.js @@ -22,7 +22,7 @@ import createSelector from 'rememo'; /** * WordPress dependencies */ -import { serialize, getBlockType, getBlockTypes } from '@wordpress/blocks'; +import { serialize, getBlockType, getBlockTypes, getInlineBlock } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; import { moment } from '@wordpress/date'; @@ -1068,11 +1068,13 @@ export function isInlineInsertionPointVisible( state ) { * * @param {Object} state Global application state. * - * @return {Object} Inline Block object with `type` and properties for the `type`, - * or null when not ready for insert. + * @return {Object} Inline Block object, or null when not ready for insert. */ export function getInlineBlockForInsert( state ) { - return state.inlineBlockForInsert; + const id = state.inlineBlockIdForInsert; + const inlineBlock = id ? getInlineBlock( id ) : null; + + return inlineBlock; } /** diff --git a/editor/store/test/reducer.js b/editor/store/test/reducer.js index 32c8b6c39fa595..4e84c6a94ebb96 100644 --- a/editor/store/test/reducer.js +++ b/editor/store/test/reducer.js @@ -35,7 +35,7 @@ import { isInsertionPointVisible, isInlineInsertionPointVisible, isInlineInsertAvailable, - inlineBlockForInsert, + inlineBlockIdForInsert, sharedBlocks, template, blockListSettings, @@ -1349,32 +1349,26 @@ describe( 'state', () => { } ); } ); - describe( 'inlineBlockForInsert', () => { - const inlineBlock = { - type: 'image', - url: 'http://localhost:8888/wp-content/uploads/2018/04/example.jpg', - alt: 'example', - width: 400, - height: 300, - }; + describe( 'inlineBlockIdForInsert', () => { + const inlineBlockId = 'inline-image'; it( 'should default to null', () => { - const state = inlineBlockForInsert( undefined, {} ); + const state = inlineBlockIdForInsert( undefined, {} ); expect( state ).toBe( null ); } ); - it( 'should insert inline block object', () => { - const state = inlineBlockForInsert( null, { + it( 'should insert inline block id', () => { + const state = inlineBlockIdForInsert( null, { type: 'INSERT_INLINE', - inlineBlock, + inlineBlockId, } ); - expect( state ).toBe( inlineBlock ); + expect( state ).toBe( inlineBlockId ); } ); it( 'should be null after insert complete', () => { - const state = inlineBlockForInsert( inlineBlock, { + const state = inlineBlockIdForInsert( inlineBlockId, { type: 'INLINE_INSERT_COMPLETE', } ); diff --git a/editor/store/test/selectors.js b/editor/store/test/selectors.js index b860950c6f783a..17bdb583101510 100644 --- a/editor/store/test/selectors.js +++ b/editor/store/test/selectors.js @@ -7,7 +7,7 @@ import { filter, property, union } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { registerBlockType, unregisterBlockType, getBlockTypes } from '@wordpress/blocks'; +import { registerBlockType, unregisterBlockType, getBlockTypes, getInlineBlock } from '@wordpress/blocks'; import { moment } from '@wordpress/date'; import { registerCoreBlocks } from '@wordpress/core-blocks'; @@ -2444,23 +2444,17 @@ describe( 'selectors', () => { describe( 'getInlineBlockForInsert', () => { it( 'should return null when not inserting an inline block', () => { const state = { - inlineBlockForInsert: null, + inlineBlockIdForInsert: null, }; expect( getInlineBlockForInsert( state ) ).toBe( null ); } ); it( 'should return inline block object when ready for insert', () => { - const inlineBlock = { - type: 'image', - url: 'http://localhost:8888/wp-content/uploads/2018/04/example.jpg', - alt: 'example', - width: 400, - height: 300, - }; const state = { - inlineBlockForInsert: inlineBlock, + inlineBlockIdForInsert: 'inline-image', }; + const inlineBlock = getInlineBlock( 'inline-image' ); expect( getInlineBlockForInsert( state ) ).toBe( inlineBlock ); } ); From e7485e2b653d1d481b4f3b8ffa945ec6966d5925 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 30 Apr 2018 15:31:59 -0400 Subject: [PATCH 37/54] Undo accidental change My vim configuration wanted to add a newline here. --- editor/components/rich-text/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/components/rich-text/README.md b/editor/components/rich-text/README.md index da88bf87108edf..302365b026cdff 100644 --- a/editor/components/rich-text/README.md +++ b/editor/components/rich-text/README.md @@ -140,4 +140,4 @@ registerBlockType( /* ... */, { } } ); ``` -{% end %} +{% end %} \ No newline at end of file From a2967fb15f2f0131663876abc1f1ed215d8bdeab Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 30 Apr 2018 17:43:09 -0400 Subject: [PATCH 38/54] Add image ID class and comment Enables WordPress display filter to make image responsive. --- blocks/inline-blocks/inline-image.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/blocks/inline-blocks/inline-image.js b/blocks/inline-blocks/inline-image.js index 9c3411e65344c0..e80e0212e303c4 100644 --- a/blocks/inline-blocks/inline-image.js +++ b/blocks/inline-blocks/inline-image.js @@ -8,9 +8,10 @@ export default { title: __( 'Inline Image' ), type: 'image', icon: 'format-image', - render( { url, alt, width }, editor ) { + render( { id, url, alt, width }, editor ) { const imgWidth = width > 150 ? 150 : width; - const img = `${ alt }`; + // set width in style attribute to prevent Block CSS from overriding it + const img = `${ alt }`; editor.insertContent( img ); }, From 980471c7ccb07f7f5ee46770a561e19e58d65506 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 30 Apr 2018 18:09:16 -0400 Subject: [PATCH 39/54] Remove unnecessary property picking --- editor/components/inserter/inline-menu.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/editor/components/inserter/inline-menu.js b/editor/components/inserter/inline-menu.js index 590e2485a70cae..a1e903c23906a7 100644 --- a/editor/components/inserter/inline-menu.js +++ b/editor/components/inserter/inline-menu.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { pick } from 'lodash'; - /** * WordPress dependencies */ @@ -18,9 +13,6 @@ import InserterGroup from './group'; export default class InserterInlineMenu extends Component { render() { const inlineBlocks = getInlineBlocks(); - const items = inlineBlocks.map( ( inlineBlock ) => ( - pick( inlineBlock, [ 'id', 'title', 'icon' ] ) - ) ); return ( @@ -32,7 +24,7 @@ export default class InserterInlineMenu extends Component { { __( 'Inline Blocks' ) } From 07c77d27f1f8f023fa5dddbb3a816221cc28a590 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 30 Apr 2018 20:07:08 -0400 Subject: [PATCH 40/54] Use ref instead of calculating positioned parent element --- editor/components/rich-text/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index ad7ca0ea5e40f0..d8a1e54a5f0b2a 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -111,6 +111,7 @@ export class RichText extends Component { constructor() { super( ...arguments ); + this.positionedContainerElement = createRef(); this.onInit = this.onInit.bind( this ); this.getSettings = this.getSettings.bind( this ); this.onSetup = this.onSetup.bind( this ); @@ -466,7 +467,7 @@ export class RichText extends Component { } setInsertPosition() { - const container = this.getContainerNode(); + const container = this.positionedContainerElement.current; const containerStyle = window.getComputedStyle( container ); const marginLeft = get( containerStyle, 'margin-left', 0 ); const containerPosition = container.getBoundingClientRect(); From c931139a1e6d67bcfb48586fdbba78903d7ed327 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 30 Apr 2018 20:35:05 -0400 Subject: [PATCH 41/54] Set `getComputedStyle` as a browser dependency --- editor/components/rich-text/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index d8a1e54a5f0b2a..b9fc54f0cc14bc 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -434,7 +434,7 @@ export class RichText extends Component { getContainerNode() { // Find the parent "relative" or "absolute" positioned container const findRelativeParent = ( node ) => { - const style = window.getComputedStyle( node ); + const style = getComputedStyle( node ); if ( style.position === 'relative' || style.position === 'absolute' ) { return node; } From 60f05824673e1748889f0bfafc29bc41a1848121 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Tue, 1 May 2018 12:50:54 -0400 Subject: [PATCH 42/54] Begin setting up inline blocks API Aims to create an API similar to that of standard blocks, so that new inline blocks can be registered. --- blocks/inline-blocks/index.js | 20 ----- core-inlineblocks/index.js | 17 ++++ .../inline-image.js | 8 +- editor/components/inserter/index.js | 2 +- editor/components/inserter/inline-menu.js | 8 +- editor/store/actions.js | 6 +- editor/store/reducer.js | 8 +- editor/store/selectors.js | 7 +- editor/store/test/reducer.js | 18 ++-- editor/store/test/selectors.js | 9 +- inline-blocks/api/index.js | 82 +++++++++++++++++++ inline-blocks/index.js | 1 + 12 files changed, 139 insertions(+), 47 deletions(-) delete mode 100644 blocks/inline-blocks/index.js create mode 100644 core-inlineblocks/index.js rename {blocks/inline-blocks => core-inlineblocks}/inline-image.js (86%) create mode 100644 inline-blocks/api/index.js create mode 100644 inline-blocks/index.js diff --git a/blocks/inline-blocks/index.js b/blocks/inline-blocks/index.js deleted file mode 100644 index b393262989c646..00000000000000 --- a/blocks/inline-blocks/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * External dependencies - */ -import { find } from 'lodash'; - -/** - * Internal dependencies - */ -import { default as inlineImage } from './inline-image'; - -export function getInlineBlocks() { - return [ inlineImage ]; -} - -export function getInlineBlock( id ) { - const inlineBlocks = getInlineBlocks(); - - return find( inlineBlocks, { id } ); -} - diff --git a/core-inlineblocks/index.js b/core-inlineblocks/index.js new file mode 100644 index 00000000000000..e380714c96aea1 --- /dev/null +++ b/core-inlineblocks/index.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { registerInlineBlockType } from '../inline-blocks'; + +/** + * Internal dependencies + */ +import * as inlineImage from './inline-image'; + +export const registerCoreInlineBlocks = () => { + [ + inlineImage, + ].forEach( ( { name, settings } ) => { + registerInlineBlockType( name, settings ); + } ); +}; diff --git a/blocks/inline-blocks/inline-image.js b/core-inlineblocks/inline-image.js similarity index 86% rename from blocks/inline-blocks/inline-image.js rename to core-inlineblocks/inline-image.js index e80e0212e303c4..ae79cdb5a21161 100644 --- a/blocks/inline-blocks/inline-image.js +++ b/core-inlineblocks/inline-image.js @@ -3,11 +3,15 @@ */ import { __ } from '@wordpress/i18n'; -export default { +export const settings = { id: 'inline-image', + title: __( 'Inline Image' ), + type: 'image', + icon: 'format-image', + render( { id, url, alt, width }, editor ) { const imgWidth = width > 150 ? 150 : width; // set width in style attribute to prevent Block CSS from overriding it @@ -16,3 +20,5 @@ export default { editor.insertContent( img ); }, }; + +export const name = 'core/inline-image'; diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js index d76d2fcd8f16ef..362cf4b22325c6 100644 --- a/editor/components/inserter/index.js +++ b/editor/components/inserter/index.js @@ -112,7 +112,7 @@ class Inserter extends Component { renderContent={ ( { onClose } ) => { const onSelect = ( item ) => { if ( isInline ) { - onInsertInline( item.id ); + onInsertInline( item.name ); } else { onInsertBlock( item ); } diff --git a/editor/components/inserter/inline-menu.js b/editor/components/inserter/inline-menu.js index a1e903c23906a7..fae8ff7ac72246 100644 --- a/editor/components/inserter/inline-menu.js +++ b/editor/components/inserter/inline-menu.js @@ -2,7 +2,8 @@ * WordPress dependencies */ import { Component, Fragment } from '@wordpress/element'; -import { getInlineBlocks } from '@wordpress/blocks'; +import { registerCoreInlineBlocks } from '../../../core-inlineblocks'; +import { getInlineBlockTypes } from '../../../inline-blocks'; import { __ } from '@wordpress/i18n'; /** @@ -10,9 +11,12 @@ import { __ } from '@wordpress/i18n'; */ import InserterGroup from './group'; +// TODO: move this to lib/client-assets.php +registerCoreInlineBlocks(); + export default class InserterInlineMenu extends Component { render() { - const inlineBlocks = getInlineBlocks(); + const inlineBlocks = getInlineBlockTypes(); return ( diff --git a/editor/store/actions.js b/editor/store/actions.js index f094729e255e75..1edf8e02b9c56c 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -289,14 +289,14 @@ export function insertBlocks( blocks, index, rootUID ) { * Returns an action object used in signalling that an inline block should be * inserted. * - * @param {string} inlineBlockId ID of inline block to insert. + * @param {string} inlineBlockName Name of inline block to insert. * * @return {Object} Action object. */ -export function insertInline( inlineBlockId ) { +export function insertInline( inlineBlockName ) { return { type: 'INSERT_INLINE', - inlineBlockId, + inlineBlockName, }; } diff --git a/editor/store/reducer.js b/editor/store/reducer.js index 2c9b6ae7bbd6bc..6d59dc543b659d 100644 --- a/editor/store/reducer.js +++ b/editor/store/reducer.js @@ -791,17 +791,17 @@ export function isInlineInsertionPointVisible( state = false, action ) { } /** - * Reducer returning an Inline Block id for insertion. + * Reducer returning an Inline Block name for insertion. * * @param {string} state Current state. * @param {Object} action Dispatched action. * * @return {Object} Updated state. */ -export function inlineBlockIdForInsert( state = null, action ) { +export function inlineBlockNameForInsert( state = null, action ) { switch ( action.type ) { case 'INSERT_INLINE': - return action.inlineBlockId; + return action.inlineBlockName; case 'INLINE_INSERT_COMPLETE': return null; @@ -1128,7 +1128,7 @@ export default optimist( combineReducers( { blockListSettings, isInsertionPointVisible, isInlineInsertionPointVisible, - inlineBlockIdForInsert, + inlineBlockNameForInsert, isInlineInsertAvailable, preferences, saving, diff --git a/editor/store/selectors.js b/editor/store/selectors.js index 757c8a0713a989..91d6665937e694 100644 --- a/editor/store/selectors.js +++ b/editor/store/selectors.js @@ -22,7 +22,8 @@ import createSelector from 'rememo'; /** * WordPress dependencies */ -import { serialize, getBlockType, getBlockTypes, getInlineBlock } from '@wordpress/blocks'; +import { serialize, getBlockType, getBlockTypes } from '@wordpress/blocks'; +import { getInlineBlockType } from '../../inline-blocks'; import { __ } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; import { moment } from '@wordpress/date'; @@ -1071,8 +1072,8 @@ export function isInlineInsertionPointVisible( state ) { * @return {Object} Inline Block object, or null when not ready for insert. */ export function getInlineBlockForInsert( state ) { - const id = state.inlineBlockIdForInsert; - const inlineBlock = id ? getInlineBlock( id ) : null; + const name = state.inlineBlockNameForInsert; + const inlineBlock = name ? getInlineBlockType( name ) : null; return inlineBlock; } diff --git a/editor/store/test/reducer.js b/editor/store/test/reducer.js index 4e84c6a94ebb96..5e93e95baafdf6 100644 --- a/editor/store/test/reducer.js +++ b/editor/store/test/reducer.js @@ -35,7 +35,7 @@ import { isInsertionPointVisible, isInlineInsertionPointVisible, isInlineInsertAvailable, - inlineBlockIdForInsert, + inlineBlockNameForInsert, sharedBlocks, template, blockListSettings, @@ -1349,26 +1349,26 @@ describe( 'state', () => { } ); } ); - describe( 'inlineBlockIdForInsert', () => { - const inlineBlockId = 'inline-image'; + describe( 'inlineBlockNameForInsert', () => { + const inlineBlockName = 'core/inline-image'; it( 'should default to null', () => { - const state = inlineBlockIdForInsert( undefined, {} ); + const state = inlineBlockNameForInsert( undefined, {} ); expect( state ).toBe( null ); } ); - it( 'should insert inline block id', () => { - const state = inlineBlockIdForInsert( null, { + it( 'should insert inline block name', () => { + const state = inlineBlockNameForInsert( null, { type: 'INSERT_INLINE', - inlineBlockId, + inlineBlockName, } ); - expect( state ).toBe( inlineBlockId ); + expect( state ).toBe( inlineBlockName ); } ); it( 'should be null after insert complete', () => { - const state = inlineBlockIdForInsert( inlineBlockId, { + const state = inlineBlockNameForInsert( inlineBlockName, { type: 'INLINE_INSERT_COMPLETE', } ); diff --git a/editor/store/test/selectors.js b/editor/store/test/selectors.js index 17bdb583101510..3e9baab1ecca17 100644 --- a/editor/store/test/selectors.js +++ b/editor/store/test/selectors.js @@ -7,7 +7,8 @@ import { filter, property, union } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { registerBlockType, unregisterBlockType, getBlockTypes, getInlineBlock } from '@wordpress/blocks'; +import { registerBlockType, unregisterBlockType, getBlockTypes } from '@wordpress/blocks'; +import { getInlineBlockType } from '../../../inline-blocks'; import { moment } from '@wordpress/date'; import { registerCoreBlocks } from '@wordpress/core-blocks'; @@ -2444,7 +2445,7 @@ describe( 'selectors', () => { describe( 'getInlineBlockForInsert', () => { it( 'should return null when not inserting an inline block', () => { const state = { - inlineBlockIdForInsert: null, + inlineBlockNameForInsert: null, }; expect( getInlineBlockForInsert( state ) ).toBe( null ); @@ -2452,9 +2453,9 @@ describe( 'selectors', () => { it( 'should return inline block object when ready for insert', () => { const state = { - inlineBlockIdForInsert: 'inline-image', + inlineBlockNameForInsert: 'core/inline-image', }; - const inlineBlock = getInlineBlock( 'inline-image' ); + const inlineBlock = getInlineBlockType( 'core/inline-image' ); expect( getInlineBlockForInsert( state ) ).toBe( inlineBlock ); } ); diff --git a/inline-blocks/api/index.js b/inline-blocks/api/index.js new file mode 100644 index 00000000000000..2385295ec81cb4 --- /dev/null +++ b/inline-blocks/api/index.js @@ -0,0 +1,82 @@ +/* eslint no-console: [ 'error', { allow: [ 'error' ] } ] */ + +/** + * Inline Block type definitions keyed by inline block name. + * + * @type {Object.} + */ +const inlineBlocks = {}; + +/** + * Registers a new inline block provided a unique name and an object defining + * its behavior. Once registered, the inline block is made available as an + * option to any editor interface where inline blocks are implemented. + * + * @param {string} name Inline Block name. + * @param {Object} settings Inline Block settings. + * + * @return {?WPInlineBlock} The inline block, if it has been successfully + * registered; otherwise `undefined`. + */ +export function registerInlineBlockType( name, settings ) { + settings = { + name, + ...settings, + }; + + if ( typeof name !== 'string' ) { + console.error( + 'Inline Block names must be strings.' + ); + return; + } + if ( ! /^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/.test( name ) ) { + console.error( + 'Inline Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-inline-block' + ); + return; + } + if ( inlineBlocks[ name ] ) { + console.error( + 'Inline Block "' + name + '" is already registered.' + ); + return; + } + if ( ! ( 'title' in settings ) || settings.title === '' ) { + console.error( + 'The inline block "' + name + '" must have a title.' + ); + return; + } + if ( typeof settings.title !== 'string' ) { + console.error( + 'Inline Block titles must be strings.' + ); + return; + } + if ( ! settings.icon ) { + settings.icon = 'block-default'; + } + + return inlineBlocks[ name ] = settings; +} + +/** + * Returns a registered inline block type. + * + * @param {string} name Inline Block name. + * + * @return {?Object} Inline Block type. + */ +export function getInlineBlockType( name ) { + return inlineBlocks[ name ]; +} + +/** + * Returns all registered inline blocks. + * + * @return {Array} Inline Block settings. + */ +export function getInlineBlockTypes() { + return Object.values( inlineBlocks ); +} diff --git a/inline-blocks/index.js b/inline-blocks/index.js new file mode 100644 index 00000000000000..b1c13e734067ac --- /dev/null +++ b/inline-blocks/index.js @@ -0,0 +1 @@ +export * from './api'; From 671b469e55d122d71f6235ca59be62a8a7591338 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Tue, 1 May 2018 14:42:23 -0400 Subject: [PATCH 43/54] Remove unused `getContainerNode` function No longer needed after rebasing, since we are using `ref` of the container instead. --- editor/components/rich-text/index.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index b9fc54f0cc14bc..715c28a347445f 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -111,7 +111,6 @@ export class RichText extends Component { constructor() { super( ...arguments ); - this.positionedContainerElement = createRef(); this.onInit = this.onInit.bind( this ); this.getSettings = this.getSettings.bind( this ); this.onSetup = this.onSetup.bind( this ); @@ -431,19 +430,6 @@ export class RichText extends Component { this.context.onCreateUndoLevel(); } - getContainerNode() { - // Find the parent "relative" or "absolute" positioned container - const findRelativeParent = ( node ) => { - const style = getComputedStyle( node ); - if ( style.position === 'relative' || style.position === 'absolute' ) { - return node; - } - return findRelativeParent( node.parentNode ); - }; - - return findRelativeParent( this.editor.getBody() ); - } - /** * Calculates the relative position where the link toolbar should be. * @@ -467,7 +453,7 @@ export class RichText extends Component { } setInsertPosition() { - const container = this.positionedContainerElement.current; + const container = this.containerRef.current; const containerStyle = window.getComputedStyle( container ); const marginLeft = get( containerStyle, 'margin-left', 0 ); const containerPosition = container.getBoundingClientRect(); From caf3342cebc9cc210401c2862b1442fdee820816 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Tue, 1 May 2018 15:01:57 -0400 Subject: [PATCH 44/54] Fix failing e2e test Reverts f817d3d since Quote block no longer using nested blocks after rebase. --- test/e2e/specs/adding-blocks.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/e2e/specs/adding-blocks.test.js b/test/e2e/specs/adding-blocks.test.js index 07c5e23ab55026..a2aaa85ccbbacb 100644 --- a/test/e2e/specs/adding-blocks.test.js +++ b/test/e2e/specs/adding-blocks.test.js @@ -72,7 +72,8 @@ describe( 'adding blocks', () => { await page.keyboard.type( 'Quote block' ); // Using the regular inserter - await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Tab' ); await page.click( '.edit-post-header [aria-label="Add block"]' ); await page.keyboard.type( 'code' ); await page.keyboard.press( 'Tab' ); From 18522d49944a9d9c9bdb08fedeeb7bf6b8c63cc6 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Tue, 1 May 2018 18:58:35 -0400 Subject: [PATCH 45/54] Remove editor dependency from inline image settings --- core-inlineblocks/inline-image.js | 4 ++-- editor/components/rich-text/index.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core-inlineblocks/inline-image.js b/core-inlineblocks/inline-image.js index ae79cdb5a21161..829cc6c4cb9f04 100644 --- a/core-inlineblocks/inline-image.js +++ b/core-inlineblocks/inline-image.js @@ -12,12 +12,12 @@ export const settings = { icon: 'format-image', - render( { id, url, alt, width }, editor ) { + render( { id, url, alt, width } ) { const imgWidth = width > 150 ? 150 : width; // set width in style attribute to prevent Block CSS from overriding it const img = `${ alt }`; - editor.insertContent( img ); + return img; }, }; diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index 715c28a347445f..0c09ffaef35285 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -474,7 +474,7 @@ export class RichText extends Component { if ( inlineBlockForInsert.type === 'image' ) { this.setState( { mediaLibraryOpen: true } ); } else { - inlineBlockForInsert.render( this.editor ); + this.editor.insertContent( inlineBlockForInsert.render() ); completeInlineInsert(); } } @@ -966,7 +966,8 @@ export class RichText extends Component { { - inlineBlockForInsert.render( media, this.editor ); + const img = inlineBlockForInsert.render( media ); + this.editor.insertContent( img ); completeInlineInsert(); this.setState( { mediaLibraryOpen: false } ); } } From e1b1f4ff26e76e49b4fddc4d1549a17516330aea Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Tue, 1 May 2018 20:04:02 -0400 Subject: [PATCH 46/54] Remove unnecessary margin calculation for insertion point After rebasing and using a `ref` for the positioned container, I'm no longer experiencing the problem with positioning the insertion point in the List block that the margin calculation addressed. --- editor/components/rich-text/index.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index 0c09ffaef35285..a2d6ae784146b0 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -12,7 +12,6 @@ import { defer, noop, reject, - get, } from 'lodash'; import 'element-closest'; @@ -453,15 +452,12 @@ export class RichText extends Component { } setInsertPosition() { - const container = this.containerRef.current; - const containerStyle = window.getComputedStyle( container ); - const marginLeft = get( containerStyle, 'margin-left', 0 ); - const containerPosition = container.getBoundingClientRect(); + // The container is relatively positioned. + const containerPosition = this.containerRef.current.getBoundingClientRect(); const rect = getRectangleFromRange( this.editor.selection.getRng() ); const insertPosition = { top: rect.top - containerPosition.top, left: rect.right - containerPosition.left, - marginLeft, height: rect.height, }; From 0264b4d549443f0775c4e3e6df6f996eb99f95c0 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Thu, 3 May 2018 15:31:19 -0400 Subject: [PATCH 47/54] Move inline block logic from RichText into new InlineBlocks component --- blocks/rich-text/inline-blocks.js | 135 +++++++++++++++++++++++++++ editor/components/rich-text/index.js | 114 ++-------------------- 2 files changed, 145 insertions(+), 104 deletions(-) create mode 100644 blocks/rich-text/inline-blocks.js diff --git a/blocks/rich-text/inline-blocks.js b/blocks/rich-text/inline-blocks.js new file mode 100644 index 00000000000000..ed3d4e267d9866 --- /dev/null +++ b/blocks/rich-text/inline-blocks.js @@ -0,0 +1,135 @@ +/** + * WordPress dependencies + */ +import { Component, Fragment, compose } from '@wordpress/element'; +import { withSelect, withDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import InlineInsertionPoint from './inline-insertion-point'; +import MediaUpload from '../media-upload'; + +class InlineBlocks extends Component { + constructor() { + super( ...arguments ); + + this.insert = this.insert.bind( this ); + this.onSelectMedia = this.onSelectMedia.bind( this ); + this.openMediaLibrary = this.openMediaLibrary.bind( this ); + this.closeMediaLibrary = this.closeMediaLibrary.bind( this ); + this.state = { mediaLibraryOpen: false }; + } + + componentDidMount() { + // When moving between two different RichText with the keyboard, we need to + // make sure `setInsertAvailable` is called after `setInsertUnavailable` + // from previous RichText so that editor state is correct + setTimeout( this.props.setInsertAvailable ); + } + + componentDidUpdate( prevProps ) { + if ( + this.props.inlineBlockForInsert && + ! prevProps.inlineBlockForInsert + ) { + this.insert(); + } + } + + componentWillUnmount() { + this.props.setInsertUnavailable(); + } + + insert() { + const { + inlineBlockForInsert, + completeInsert, + editor, + } = this.props; + + if ( inlineBlockForInsert.type === 'image' ) { + this.openMediaLibrary(); + } else { + editor.insertContent( inlineBlockForInsert.render() ); + completeInsert(); + } + } + + onSelectMedia( media ) { + const { + editor, + inlineBlockForInsert, + completeInsert, + } = this.props; + const img = inlineBlockForInsert.render( media ); + + editor.insertContent( img ); + completeInsert(); + this.closeMediaLibrary(); + } + + openMediaLibrary() { + this.setState( { mediaLibraryOpen: true } ); + } + + closeMediaLibrary() { + this.setState( { mediaLibraryOpen: false } ); + } + + render() { + const { + isInlineInsertionPointVisible, + getInsertPosition, + } = this.props; + const { mediaLibraryOpen } = this.state; + + return ( + + { isInlineInsertionPointVisible && + + } + { mediaLibraryOpen && + { + open(); + return null; + } } + /> + } + + ); + } +} + +export default compose( [ + withSelect( ( select ) => { + const { + isInlineInsertionPointVisible, + getInlineBlockForInsert, + } = select( 'core/editor' ); + + return { + isInlineInsertionPointVisible: isInlineInsertionPointVisible(), + inlineBlockForInsert: getInlineBlockForInsert(), + }; + } ), + withDispatch( ( dispatch ) => { + const { + setInlineInsertAvailable, + setInlineInsertUnavailable, + completeInlineInsert, + } = dispatch( 'core/editor' ); + + return { + setInsertAvailable: setInlineInsertAvailable, + setInsertUnavailable: setInlineInsertUnavailable, + completeInsert: completeInlineInsert, + }; + } ), +] )( InlineBlocks ); diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index a2d6ae784146b0..8ec6f6bfefd6cc 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -28,7 +28,7 @@ import { deprecated, } from '@wordpress/utils'; import { withInstanceId, withSafeTimeout, Slot } from '@wordpress/components'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { withSelect } from '@wordpress/data'; import { rawHandler } from '@wordpress/blocks'; /** @@ -39,8 +39,7 @@ import Autocomplete from '../autocomplete'; import BlockFormatControls from '../block-format-controls'; import FormatToolbar from './format-toolbar'; import TinyMCE from './tinymce'; -import InlineInsertionPoint from './inline-insertion-point'; -import MediaUpload from '../media-upload'; +import InlineBlocks from './inline-blocks'; import { pickAriaProps } from './aria'; import patterns from './patterns'; import { EVENTS } from './constants'; @@ -124,15 +123,12 @@ export class RichText extends Component { this.onPaste = this.onPaste.bind( this ); this.onCreateUndoLevel = this.onCreateUndoLevel.bind( this ); this.setFocusedElement = this.setFocusedElement.bind( this ); - this.toggleInsertAvailable = this.toggleInsertAvailable.bind( this ); - this.insertInlineBlock = this.insertInlineBlock.bind( this ); this.getFocusPosition = this.getFocusPosition.bind( this ); - this.setInsertPosition = this.setInsertPosition.bind( this ); + this.getInsertPosition = this.getInsertPosition.bind( this ); this.state = { formats: {}, selectedNodeId: 0, - insertPosition: {}, }; this.containerRef = createRef(); @@ -233,11 +229,6 @@ export class RichText extends Component { } onInit() { - const { isSelected, setInsertAvailable } = this.props; - - if ( isSelected ) { - setInsertAvailable(); - } this.registerCustomFormatters(); this.editor.shortcuts.add( rawShortcut.primary( 'k' ), '', () => this.changeFormats( { link: { isAdding: true } } ) ); @@ -451,45 +442,16 @@ export class RichText extends Component { }; } - setInsertPosition() { + getInsertPosition() { // The container is relatively positioned. const containerPosition = this.containerRef.current.getBoundingClientRect(); const rect = getRectangleFromRange( this.editor.selection.getRng() ); - const insertPosition = { + + return { top: rect.top - containerPosition.top, left: rect.right - containerPosition.left, height: rect.height, }; - - this.setState( { insertPosition } ); - } - - insertInlineBlock() { - const { inlineBlockForInsert, completeInlineInsert } = this.props; - - if ( inlineBlockForInsert.type === 'image' ) { - this.setState( { mediaLibraryOpen: true } ); - } else { - this.editor.insertContent( inlineBlockForInsert.render() ); - completeInlineInsert(); - } - } - - toggleInsertAvailable() { - const { - isSelected, - setInsertAvailable, - setInsertUnavailable, - } = this.props; - - if ( isSelected ) { - // When moving between two different RichText with the keyboard, we need to - // make sure `setInsertAvailable` is called after `setInsertUnavailable` - // from previous RichText so that editor state is correct - setTimeout( setInsertAvailable, 0 ); - } else { - setInsertUnavailable(); - } } /** @@ -780,25 +742,6 @@ export class RichText extends Component { return; } - if ( this.props.isSelected !== prevProps.isSelected ) { - this.toggleInsertAvailable(); - } - - if ( - this.props.isInlineInsertionPointVisible && - ! prevProps.isInlineInsertionPointVisible - ) { - this.setInsertPosition(); - } - - if ( - this.props.isSelected && - this.props.inlineBlockForInsert && - ! prevProps.inlineBlockForInsert - ) { - this.insertInlineBlock(); - } - // The `savedContent` var allows us to avoid updating the content right after an `onChange` call if ( this.props.tagName === prevProps.tagName && @@ -824,10 +767,6 @@ export class RichText extends Component { } } - componentWillUnmount() { - this.props.setInsertUnavailable(); - } - /** * Returns true if the field is currently empty, or false otherwise. * @@ -913,9 +852,6 @@ export class RichText extends Component { formatters, autocompleters, format, - isInlineInsertionPointVisible = false, - inlineBlockForInsert, - completeInlineInsert, } = this.props; const ariaProps = { ...pickAriaProps( this.props ), 'aria-multiline': !! MultilineTag }; @@ -953,25 +889,10 @@ export class RichText extends Component { { formatToolbar } ) } - { isSelected && isInlineInsertionPointVisible && - - } - { this.state.mediaLibraryOpen && - { - const img = inlineBlockForInsert.render( media ); - this.editor.insertContent( img ); - completeInlineInsert(); - this.setState( { mediaLibraryOpen: false } ); - } } - onClose={ () => ( this.setState( { mediaLibraryOpen: false } ) ) } - render={ ( { open } ) => { - open(); - return null; - } } + { isSelected && + } @@ -1045,24 +966,9 @@ const RichTextContainer = compose( [ } ), withSelect( ( select ) => { const { isViewportMatch = identity } = select( 'core/viewport' ) || {}; - const { isInlineInsertionPointVisible = noop } = select( 'core/editor' ) || {}; - const { getInlineBlockForInsert = noop } = select( 'core/editor' ) || {}; return { isViewportSmall: isViewportMatch( '< small' ), - isInlineInsertionPointVisible: isInlineInsertionPointVisible(), - inlineBlockForInsert: getInlineBlockForInsert(), - }; - } ), - withDispatch( ( dispatch ) => { - const { setInlineInsertAvailable = noop } = dispatch( 'core/editor' ) || {}; - const { setInlineInsertUnavailable = noop } = dispatch( 'core/editor' ) || {}; - const { completeInlineInsert = noop } = dispatch( 'core/editor' ) || {}; - - return { - setInsertAvailable: setInlineInsertAvailable, - setInsertUnavailable: setInlineInsertUnavailable, - completeInlineInsert, }; } ), withSafeTimeout, From 29a3833b1266605efc23ccfb9b081c7f3211d30e Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Thu, 3 May 2018 20:44:11 -0400 Subject: [PATCH 48/54] Experiment with resize handle styles - try white background - remove outline and selection background --- editor/components/rich-text/style.scss | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/editor/components/rich-text/style.scss b/editor/components/rich-text/style.scss index 4894a734e23af9..1bb37777d826bc 100644 --- a/editor/components/rich-text/style.scss +++ b/editor/components/rich-text/style.scss @@ -61,6 +61,16 @@ } } + img { + &[data-mce-selected] { + outline: none; + } + + &::selection { + background: none !important; + } + } + &[data-is-placeholder-visible="true"] { position: absolute; top: 0; @@ -83,14 +93,14 @@ div.mce-resizehandle { border-radius: 50%; - border: 2px solid white; - width: 15px; - height: 15px; - background: $blue-medium-500; + border: 1px solid $black; + width: 12px; + height: 12px; + background: $white; box-sizing: border-box; &:hover { - background: $blue-medium-500; + background: $white; } } } From 1184190e538e5b50bb15cd415669e40e1476edaf Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Sun, 6 May 2018 22:11:51 -0400 Subject: [PATCH 49/54] Add E2E test for adding inline blocks For now just tests adding an Inline Image in a Paragraph. --- .../assets/10x10_e2e_test_image_z9T8jK.png | Bin 0 -> 116 bytes test/e2e/specs/adding-inline-blocks.test.js | 81 ++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 test/e2e/assets/10x10_e2e_test_image_z9T8jK.png create mode 100644 test/e2e/specs/adding-inline-blocks.test.js diff --git a/test/e2e/assets/10x10_e2e_test_image_z9T8jK.png b/test/e2e/assets/10x10_e2e_test_image_z9T8jK.png new file mode 100644 index 0000000000000000000000000000000000000000..4d198c0023578e82e80d798e3d4f34a742edd749 GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVhivIab@`bABgxBvV4IeoCO|{ z#S9F5M?jcysy3fAP*B9v#W93qW^#f98!rzZuYypE0|SH9JSNAD@4kREF?hQAxvX { + beforeAll( async () => { + await newDesktopBrowserPage(); + await newPost(); + } ); + + it( 'Should insert inline image', async () => { + // Create a paragraph + await page.click( '.editor-default-block-appender' ); + await page.keyboard.type( 'Paragraph with inline image: ' ); + + // Use the global inserter to select Inline Image + await page.click( '.edit-post-header [aria-label="Add block"]' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Enter' ); + + // Select Media Library tab + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Enter' ); + + // Search for test image + await page.keyboard.type( testImage.key ); + + // Wait for image search results + await page.waitFor( 500 ); + + const searchResultElement = await page.$( '.media-frame .attachment' ); + + if ( searchResultElement ) { + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Enter' ); + } else { + // Upload test image + const inputElement = await page.$( 'input[type=file]' ); + await inputElement.uploadFile( testImagePath ); + await page.waitFor( 500 ); + } + + // Enter alt text + await page.click( '.media-frame [data-setting=caption]' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( testImage.alt ); + + // Select image + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Enter' ); + + // Switch to Text Mode to check HTML Output + await page.click( '.edit-post-more-menu [aria-label="More"]' ); + const codeEditorButton = ( await page.$x( '//button[contains(text(), \'Code Editor\')]' ) )[ 0 ]; + await codeEditorButton.click( 'button' ); + + // Assertions + const textEditorContent = await page.$eval( '.editor-post-text-editor', ( element ) => element.value ); + + expect( textEditorContent.indexOf( 'Paragraph with inline image:  Date: Mon, 7 May 2018 15:30:43 -0400 Subject: [PATCH 50/54] Cleanup after rebase RichText directory was moved from blocks/ to editor/components when I rebased. This commit moves the InlineInsertionPoint and InlineBlocks to the new location as well. --- {blocks => editor/components}/rich-text/inline-blocks.js | 0 .../components}/rich-text/inline-insertion-point/index.js | 0 .../components}/rich-text/inline-insertion-point/style.scss | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {blocks => editor/components}/rich-text/inline-blocks.js (100%) rename {blocks => editor/components}/rich-text/inline-insertion-point/index.js (100%) rename {blocks => editor/components}/rich-text/inline-insertion-point/style.scss (100%) diff --git a/blocks/rich-text/inline-blocks.js b/editor/components/rich-text/inline-blocks.js similarity index 100% rename from blocks/rich-text/inline-blocks.js rename to editor/components/rich-text/inline-blocks.js diff --git a/blocks/rich-text/inline-insertion-point/index.js b/editor/components/rich-text/inline-insertion-point/index.js similarity index 100% rename from blocks/rich-text/inline-insertion-point/index.js rename to editor/components/rich-text/inline-insertion-point/index.js diff --git a/blocks/rich-text/inline-insertion-point/style.scss b/editor/components/rich-text/inline-insertion-point/style.scss similarity index 100% rename from blocks/rich-text/inline-insertion-point/style.scss rename to editor/components/rich-text/inline-insertion-point/style.scss From 2e5fca4654281739309c9a98479d746159275c1a Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Thu, 10 May 2018 23:57:04 -0400 Subject: [PATCH 51/54] Rename core-inlineblocks directory to core-inline-blocks Previously I had used only a single dash, in preparation for adding to webpack where only the first dash was being replaced by the `camelCaseDash` function. However, now that this function has been updated to replace all dashes, this is no longer a limitation. --- {core-inlineblocks => core-inline-blocks}/index.js | 0 {core-inlineblocks => core-inline-blocks}/inline-image.js | 0 editor/components/inserter/inline-menu.js | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename {core-inlineblocks => core-inline-blocks}/index.js (100%) rename {core-inlineblocks => core-inline-blocks}/inline-image.js (100%) diff --git a/core-inlineblocks/index.js b/core-inline-blocks/index.js similarity index 100% rename from core-inlineblocks/index.js rename to core-inline-blocks/index.js diff --git a/core-inlineblocks/inline-image.js b/core-inline-blocks/inline-image.js similarity index 100% rename from core-inlineblocks/inline-image.js rename to core-inline-blocks/inline-image.js diff --git a/editor/components/inserter/inline-menu.js b/editor/components/inserter/inline-menu.js index fae8ff7ac72246..84994286589eb3 100644 --- a/editor/components/inserter/inline-menu.js +++ b/editor/components/inserter/inline-menu.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { Component, Fragment } from '@wordpress/element'; -import { registerCoreInlineBlocks } from '../../../core-inlineblocks'; +import { registerCoreInlineBlocks } from '../../../core-inline-blocks'; import { getInlineBlockTypes } from '../../../inline-blocks'; import { __ } from '@wordpress/i18n'; From f4a4358dbfeba0e5a1770a1632a5c5d920ccca91 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 14 May 2018 20:45:10 -0400 Subject: [PATCH 52/54] cleanup diff --- editor/components/rich-text/index.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index 8ec6f6bfefd6cc..3cecff5381e522 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -123,7 +123,6 @@ export class RichText extends Component { this.onPaste = this.onPaste.bind( this ); this.onCreateUndoLevel = this.onCreateUndoLevel.bind( this ); this.setFocusedElement = this.setFocusedElement.bind( this ); - this.getFocusPosition = this.getFocusPosition.bind( this ); this.getInsertPosition = this.getInsertPosition.bind( this ); this.state = { @@ -738,12 +737,9 @@ export class RichText extends Component { } componentDidUpdate( prevProps ) { - if ( ! this.editor ) { - return; - } - // The `savedContent` var allows us to avoid updating the content right after an `onChange` call if ( + !! this.editor && this.props.tagName === prevProps.tagName && this.props.value !== prevProps.value && this.props.value !== this.savedContent && From 7d6e6a1727538f6f06d15cd76f3812836d02e0c0 Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 14 May 2018 21:17:35 -0400 Subject: [PATCH 53/54] Use withSafeTimeout HOC for setTimeout --- editor/components/rich-text/inline-blocks.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/editor/components/rich-text/inline-blocks.js b/editor/components/rich-text/inline-blocks.js index ed3d4e267d9866..863c0e88bd4de9 100644 --- a/editor/components/rich-text/inline-blocks.js +++ b/editor/components/rich-text/inline-blocks.js @@ -3,6 +3,7 @@ */ import { Component, Fragment, compose } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; +import { withSafeTimeout } from '@wordpress/components'; /** * Internal dependencies @@ -22,10 +23,12 @@ class InlineBlocks extends Component { } componentDidMount() { + const { setTimeout, setInsertAvailable } = this.props; + // When moving between two different RichText with the keyboard, we need to // make sure `setInsertAvailable` is called after `setInsertUnavailable` // from previous RichText so that editor state is correct - setTimeout( this.props.setInsertAvailable ); + setTimeout( setInsertAvailable ); } componentDidUpdate( prevProps ) { @@ -132,4 +135,5 @@ export default compose( [ completeInsert: completeInlineInsert, }; } ), + withSafeTimeout, ] )( InlineBlocks ); From b8a225acf9479b2dc8ae18cb73feab5168b62d0c Mon Sep 17 00:00:00 2001 From: Jay Shenk Date: Mon, 14 May 2018 22:53:48 -0400 Subject: [PATCH 54/54] Move `getInsertPosition` from RichText to InlineBlocks --- editor/components/rich-text/index.js | 15 +------------ editor/components/rich-text/inline-blocks.js | 23 +++++++++++++++----- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/editor/components/rich-text/index.js b/editor/components/rich-text/index.js index 3cecff5381e522..3a687a76b53b29 100644 --- a/editor/components/rich-text/index.js +++ b/editor/components/rich-text/index.js @@ -123,7 +123,6 @@ export class RichText extends Component { this.onPaste = this.onPaste.bind( this ); this.onCreateUndoLevel = this.onCreateUndoLevel.bind( this ); this.setFocusedElement = this.setFocusedElement.bind( this ); - this.getInsertPosition = this.getInsertPosition.bind( this ); this.state = { formats: {}, @@ -441,18 +440,6 @@ export class RichText extends Component { }; } - getInsertPosition() { - // The container is relatively positioned. - const containerPosition = this.containerRef.current.getBoundingClientRect(); - const rect = getRectangleFromRange( this.editor.selection.getRng() ); - - return { - top: rect.top - containerPosition.top, - left: rect.right - containerPosition.left, - height: rect.height, - }; - } - /** * Handles a keydown event from tinyMCE. * @@ -888,7 +875,7 @@ export class RichText extends Component { { isSelected && } diff --git a/editor/components/rich-text/inline-blocks.js b/editor/components/rich-text/inline-blocks.js index 863c0e88bd4de9..ee7b55f15663a2 100644 --- a/editor/components/rich-text/inline-blocks.js +++ b/editor/components/rich-text/inline-blocks.js @@ -4,6 +4,7 @@ import { Component, Fragment, compose } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; import { withSafeTimeout } from '@wordpress/components'; +import { getRectangleFromRange } from '@wordpress/utils'; /** * Internal dependencies @@ -16,6 +17,7 @@ class InlineBlocks extends Component { super( ...arguments ); this.insert = this.insert.bind( this ); + this.getInsertPosition = this.getInsertPosition.bind( this ); this.onSelectMedia = this.onSelectMedia.bind( this ); this.openMediaLibrary = this.openMediaLibrary.bind( this ); this.closeMediaLibrary = this.closeMediaLibrary.bind( this ); @@ -44,6 +46,20 @@ class InlineBlocks extends Component { this.props.setInsertUnavailable(); } + getInsertPosition() { + const { containerRef, editor } = this.props; + + // The container is relatively positioned. + const containerPosition = containerRef.current.getBoundingClientRect(); + const rect = getRectangleFromRange( editor.selection.getRng() ); + + return { + top: rect.top - containerPosition.top, + left: rect.right - containerPosition.left, + height: rect.height, + }; + } + insert() { const { inlineBlockForInsert, @@ -81,17 +97,14 @@ class InlineBlocks extends Component { } render() { - const { - isInlineInsertionPointVisible, - getInsertPosition, - } = this.props; + const { isInlineInsertionPointVisible } = this.props; const { mediaLibraryOpen } = this.state; return ( { isInlineInsertionPointVisible && } { mediaLibraryOpen &&