diff --git a/blocks/editable/patterns.js b/blocks/editable/patterns.js index e7adec6d41228b..e96339e8f54bac 100644 --- a/blocks/editable/patterns.js +++ b/blocks/editable/patterns.js @@ -31,6 +31,7 @@ export default function( editor ) { const { paste: pastePatterns, enter: enterPatterns, + character: characterPatterns, undefined: spacePatterns, } = groupBy( getBlockTypes().reduce( ( acc, blockType ) => { const transformsFrom = get( blockType, 'transforms.from', [] ); @@ -72,6 +73,7 @@ export default function( editor ) { setTimeout( () => searchFirstText( spacePatterns ) ); } else if ( keyCode > 47 && ! ( keyCode >= 91 && keyCode <= 93 ) ) { setTimeout( inline ); + setTimeout( character ); } }, true ); @@ -219,6 +221,37 @@ export default function( editor ) { onReplace( [ block ] ); } + function character() { + if ( ! onReplace ) { + return; + } + + // Merge text nodes. + editor.getBody().normalize(); + + const content = getContent(); + + if ( content.length !== 1 ) { + return; + } + + const firstText = content[ 0 ]; + + if ( firstText.length !== 1 ) { + return; + } + + const pattern = find( characterPatterns, { character: firstText } ); + + if ( ! pattern ) { + return; + } + + const block = pattern.transform(); + + onReplace( [ block ] ); + } + function enter() { if ( ! onReplace ) { return; diff --git a/blocks/library/index.js b/blocks/library/index.js index e1a53c29de8444..b16eb69d474063 100644 --- a/blocks/library/index.js +++ b/blocks/library/index.js @@ -19,3 +19,4 @@ import './categories'; import './cover-image'; import './cover-text'; import './verse'; +import './inserter'; diff --git a/blocks/library/inserter/block.scss b/blocks/library/inserter/block.scss new file mode 100644 index 00000000000000..8b137891791fe9 --- /dev/null +++ b/blocks/library/inserter/block.scss @@ -0,0 +1 @@ + diff --git a/blocks/library/inserter/index.js b/blocks/library/inserter/index.js new file mode 100644 index 00000000000000..92c265093cce75 --- /dev/null +++ b/blocks/library/inserter/index.js @@ -0,0 +1,73 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { keycodes } from '@wordpress/utils'; + +/** + * Internal dependencies + */ +import './block.scss'; +import { registerBlockType, createBlock } from '../../api'; +import InserterMenu from '../../../editor/inserter/menu'; + +const { BACKSPACE, ESCAPE } = keycodes; + +registerBlockType( 'core/inserter', { + title: __( 'Inserter' ), + + transforms: { + from: [ + { + type: 'pattern', + trigger: 'character', + character: '/', + transform: () => createBlock( 'core/inserter' ), + }, + ], + }, + + edit( { className, onReplace } ) { + const onSelect = ( name ) => { + if ( ! name ) { + return; + } + + onReplace( [ + createBlock( name ), + ] ); + }; + + const onKeyDown = ( event ) => { + const { keyCode, target } = event; + + if ( ( keyCode === BACKSPACE && target.value === '' ) || keyCode === ESCAPE ) { + event.preventDefault(); + onReplace( [ + createBlock( 'core/paragraph' ), + ] ); + } + }; + + const style = { + margin: 0, + border: '1px solid #e2e4e7', + }; + + /* eslint-disable jsx-a11y/no-static-element-interactions */ + return ( +
+ +
+ ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ + }, + + save() { + return null; + }, +} ); diff --git a/blocks/test/fixtures/core__inserter.html b/blocks/test/fixtures/core__inserter.html new file mode 100644 index 00000000000000..6fa9f9415ab689 --- /dev/null +++ b/blocks/test/fixtures/core__inserter.html @@ -0,0 +1 @@ + diff --git a/blocks/test/fixtures/core__inserter.json b/blocks/test/fixtures/core__inserter.json new file mode 100644 index 00000000000000..e3e9fdef789cad --- /dev/null +++ b/blocks/test/fixtures/core__inserter.json @@ -0,0 +1,8 @@ +[ + { + "uid": "_uid_0", + "name": "core/inserter", + "isValid": true, + "attributes": {} + } +] diff --git a/blocks/test/fixtures/core__inserter.parsed.json b/blocks/test/fixtures/core__inserter.parsed.json new file mode 100644 index 00000000000000..3e88d9839107cb --- /dev/null +++ b/blocks/test/fixtures/core__inserter.parsed.json @@ -0,0 +1,11 @@ +[ + { + "blockName": "core/inserter", + "attrs": null, + "rawContent": "" + }, + { + "attrs": {}, + "rawContent": "\n" + } +] diff --git a/blocks/test/fixtures/core__inserter.serialized.html b/blocks/test/fixtures/core__inserter.serialized.html new file mode 100644 index 00000000000000..6fa9f9415ab689 --- /dev/null +++ b/blocks/test/fixtures/core__inserter.serialized.html @@ -0,0 +1 @@ + diff --git a/editor/inserter/menu.js b/editor/inserter/menu.js index 41fc46fba4d04a..541ed289824b54 100644 --- a/editor/inserter/menu.js +++ b/editor/inserter/menu.js @@ -11,7 +11,7 @@ import { __, _n, sprintf } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { Popover, withFocusReturn, withInstanceId, withSpokenMessages } from '@wordpress/components'; import { keycodes } from '@wordpress/utils'; -import { getCategories, getBlockTypes, BlockIcon } from '@wordpress/blocks'; +import { getCategories, getBlockTypes, BlockIcon } from '../../blocks'; /** * Internal dependencies @@ -31,6 +31,14 @@ export const searchBlocks = ( blocks, searchTerm ) => { ); }; +const Wrapper = ( { position, children, ...props } ) => { + if ( ! position ) { + return
{ children }
; + } + + return { children }; +}; + export class InserterMenu extends Component { constructor() { super( ...arguments ); @@ -291,8 +299,8 @@ export class InserterMenu extends Component { onClick={ this.selectBlock( block.name ) } ref={ this.bindReferenceNode( block.name ) } tabIndex="-1" - onMouseEnter={ ! disabled && this.props.showInsertionPoint } - onMouseLeave={ ! disabled && this.props.hideInsertionPoint } + onMouseEnter={ !! this.props.position && ! disabled && this.props.showInsertionPoint } + onMouseLeave={ !! this.props.position && ! disabled && this.props.hideInsertionPoint } disabled={ disabled } > @@ -306,13 +314,13 @@ export class InserterMenu extends Component { } render() { - const { position, instanceId } = this.props; + const { instanceId, position } = this.props; const isSearching = this.state.filterValue; const visibleBlocksByCategory = this.getVisibleBlocksByCategory( this.getBlocksForCurrentTab() ); /* eslint-disable jsx-a11y/no-autofocus */ return ( - + @@ -422,7 +430,7 @@ export class InserterMenu extends Component { } - + ); /* eslint-enable jsx-a11y/no-autofocus */ } diff --git a/editor/modes/visual-editor/block.js b/editor/modes/visual-editor/block.js index 70538362118b13..40a9d4f33698f3 100644 --- a/editor/modes/visual-editor/block.js +++ b/editor/modes/visual-editor/block.js @@ -86,6 +86,11 @@ class VisualEditorBlock extends Component { componentDidMount() { if ( this.props.focus ) { + // Already handled. + if ( this.node.contains( document.activeElement ) ) { + return; + } + this.node.focus(); } diff --git a/editor/selectors.js b/editor/selectors.js index 053ed074f8068f..b2469fdc180399 100644 --- a/editor/selectors.js +++ b/editor/selectors.js @@ -8,7 +8,7 @@ import createSelector from 'rememo'; /** * WordPress dependencies */ -import { getBlockType } from '@wordpress/blocks'; +import { getBlockType } from '../blocks'; import { __ } from '@wordpress/i18n'; /**