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';
/**