Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding keyboard shortcuts to navigate on the Block Insert Menu #88

Merged
merged 3 commits into from
Feb 20, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 164 additions & 15 deletions blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,44 +45,52 @@ var config = {
],
blocks: [
{
id: 'paragraph',
label: 'Paragraph',
icon: '<svg height="24" width="24" class="type-icon-paragraph" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path id="path-1_2_" class="st0" d="M13 5h2v16h2V5h2V3h-6.7.2-3C6.5 3 4 5.5 4 8.5S6.5 14 9.5 14H11v7h2v-7h-.5.5V5z"/><path class="st1" d="M9.5 3C6.5 3 4 5.5 4 8.5S6.5 14 9.5 14H11v7h2V5h2v16h2V5h2V3H9.5z"/></svg>',
categories: [ 'frequent' ]
category: 'frequent'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't some blocks appear in multiple categories? I'm guessing that was the initial intention of having an array here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, actually I did this in a previous Pr. but as @jasmussen said is his comment, we should stick with one category for now #87 (comment)
which simplifies a lot the navigation using arrows

},
{
id: 'heading',
label: 'Heading',
icon: '<svg height="24" width="24" class="type-icon-heading" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Heading</title><rect x="0" fill="none" width="24" height="24"/><g><path d="M18 20h-3v-6H9v6H6V5.01h3V11h6V5.01h3V20z"/></g></svg>',
categories: [ 'frequent' ]
category: 'frequent'
},
{
id: 'image',
label: 'Image',
icon: '<svg width="24" height="24" class="type-icon-image" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Image</title><rect x="0" fill="none" width="24" height="24"/><g><path d="M13 9.5c0-.828.672-1.5 1.5-1.5s1.5.672 1.5 1.5-.672 1.5-1.5 1.5-1.5-.672-1.5-1.5zM22 6v12c0 1.105-.895 2-2 2H4c-1.105 0-2-.895-2-2V6c0-1.105.895-2 2-2h16c1.105 0 2 .895 2 2zm-2 0H4v7.444L8 9l5.895 6.55 1.587-1.85c.798-.932 2.24-.932 3.037 0L20 15.426V6z"/></g></svg>',
categories: [ 'frequent' ]
category: 'frequent'
},
{
id: 'quote',
label: 'Quote',
icon: '<svg class="gridicon gridicons-quote" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M11.192 15.757c0-.88-.23-1.618-.69-2.217-.326-.412-.768-.683-1.327-.812-.55-.128-1.07-.137-1.54-.028-.16-.95.1-1.956.76-3.022.66-1.065 1.515-1.867 2.558-2.403L9.373 5c-.8.396-1.56.898-2.26 1.505-.71.607-1.34 1.305-1.9 2.094s-.98 1.68-1.25 2.69-.346 2.04-.217 3.1c.168 1.4.62 2.52 1.356 3.35.735.84 1.652 1.26 2.748 1.26.965 0 1.766-.29 2.4-.878.628-.576.94-1.365.94-2.368l.002.003zm9.124 0c0-.88-.23-1.618-.69-2.217-.326-.42-.77-.692-1.327-.817-.56-.124-1.074-.13-1.54-.022-.16-.94.09-1.95.75-3.02.66-1.06 1.514-1.86 2.557-2.4L18.49 5c-.8.396-1.555.898-2.26 1.505-.708.607-1.34 1.305-1.894 2.094-.556.79-.97 1.68-1.24 2.69-.273 1-.345 2.04-.217 3.1.165 1.4.615 2.52 1.35 3.35.732.833 1.646 1.25 2.742 1.25.967 0 1.768-.29 2.402-.876.627-.576.942-1.365.942-2.368v.01z"></path></g></svg>',
categories: [ 'frequent' ]
category: 'frequent'
},
{
id: 'gallery',
label: 'Gallery',
icon: '<svg class="gridicon gridicons-image-multiple" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M15 7.5c0-.828.672-1.5 1.5-1.5s1.5.672 1.5 1.5S17.328 9 16.5 9 15 8.328 15 7.5zM4 20h14c0 1.105-.895 2-2 2H4c-1.1 0-2-.9-2-2V8c0-1.105.895-2 2-2v14zM22 4v12c0 1.105-.895 2-2 2H8c-1.105 0-2-.895-2-2V4c0-1.105.895-2 2-2h12c1.105 0 2 .895 2 2zM8 4v6.333L11 7l4.855 5.395.656-.73c.796-.886 2.183-.886 2.977 0l.513.57V4H8z"></path></g></svg>',
categories: [ 'media' ]
category: 'media'
},
{
id: 'unordered-list',
label: 'Unordered List',
icon: '<svg class="gridicon gridicons-list-unordered" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M9 19h12v-2H9v2zm0-6h12v-2H9v2zm0-8v2h12V5H9zm-4-.5c-.828 0-1.5.672-1.5 1.5S4.172 7.5 5 7.5 6.5 6.828 6.5 6 5.828 4.5 5 4.5zm0 6c-.828 0-1.5.672-1.5 1.5s.672 1.5 1.5 1.5 1.5-.672 1.5-1.5-.672-1.5-1.5-1.5zm0 6c-.828 0-1.5.672-1.5 1.5s.672 1.5 1.5 1.5 1.5-.672 1.5-1.5-.672-1.5-1.5-1.5z"></path></g></svg>',
categories: [ 'frequent' ]
category: 'frequent'
},
{
id: 'ordered-list',
label: 'Ordered List',
icon: '<svg class="gridicon gridicons-list-ordered" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M8 19h13v-2H8v2zm0-6h13v-2H8v2zm0-8v2h13V5H8zm-4.425.252c.107-.096.197-.188.27-.275-.013.228-.02.48-.02.756V8h1.176V3.717H3.96L2.487 4.915l.6.738.487-.4zm.334 7.764c.474-.426.784-.715.93-.867.145-.153.26-.298.35-.436.087-.138.152-.278.194-.42.042-.143.063-.298.063-.466 0-.225-.06-.427-.18-.608s-.29-.32-.507-.417c-.218-.1-.465-.148-.742-.148-.22 0-.42.022-.596.067s-.34.11-.49.195c-.15.085-.337.226-.558.423l.636.744c.174-.15.33-.264.467-.34.138-.078.274-.117.41-.117.13 0 .232.032.304.097.073.064.11.152.11.264 0 .09-.02.176-.055.258-.036.082-.1.18-.192.294-.092.114-.287.328-.586.64L2.42 13.238V14h3.11v-.955H3.91v-.03zm.53 4.746v-.018c.306-.086.54-.225.702-.414.162-.19.243-.42.243-.685 0-.31-.126-.55-.378-.727-.252-.176-.6-.264-1.043-.264-.307 0-.58.033-.816.1s-.47.178-.696.334l.48.773c.293-.183.576-.274.85-.274.147 0 .263.027.35.082s.13.14.13.252c0 .3-.294.45-.882.45h-.27v.87h.264c.217 0 .393.017.527.05.136.03.233.08.294.143.06.064.09.154.09.27 0 .153-.057.265-.173.337-.115.07-.3.106-.554.106-.164 0-.343-.022-.538-.07-.194-.044-.385-.115-.573-.21v.96c.228.088.44.148.637.182.196.033.41.05.64.05.56 0 .998-.114 1.314-.343.315-.228.473-.542.473-.94.002-.585-.356-.923-1.07-1.013z"></path></g></svg>',
categories: [ 'frequent' ]
category: 'frequent'
},
{
id: 'embed',
label: 'Embed',
icon: '<svg class="gridicon gridicons-video" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M20 4v2h-2V4H6v2H4V4c-1.105 0-2 .895-2 2v12c0 1.105.895 2 2 2v-2h2v2h12v-2h2v2c1.105 0 2-.895 2-2V6c0-1.105-.895-2-2-2zM6 16H4v-3h2v3zm0-5H4V8h2v3zm4 4V9l4.5 3-4.5 3zm10 1h-2v-3h2v3zm0-5h-2V8h2v3z"></path></g></svg>',
categories: [ 'media' ]
category: 'media'
}
]
};
Expand All @@ -107,6 +115,16 @@ var imageAlignRight = queryFirst( '.block-image__align-right' );

var selectedBlock = null;
var searchBlockFilter = '';
var blockMenuOpened = false;
var menuSelectedBlock = null;

var orderedBlocks = config.blockCategories.reduce( function( memo, category ) {
var categoryBlocks = config.blocks.filter( function( block ) {
return block.category === category.id;
} );

return memo.concat( categoryBlocks );
}, [] );

var supportedBlockTags = Object.keys( config.tagTypes )
.slice( 0, -1 ) // remove 'default' option
Expand All @@ -128,6 +146,7 @@ attachBlockHandlers();
attachControlActions();
attachTypeSwitcherActions();
attachBlockMenuSearch();
attachKeyboardShortcuts();

/**
* Core logic
Expand Down Expand Up @@ -300,7 +319,7 @@ function attachTypeSwitcherActions() {
} );
}

function fillBlockMenu() {
function renderBlockMenu() {
insertBlockMenuContent.innerHTML = '';
config.blockCategories.forEach( function ( category ) {
var node = document.createElement( 'div' );
Expand All @@ -314,13 +333,13 @@ function fillBlockMenu() {
node.appendChild( nodeBlocks );
var categoryBlocks = config.blocks
.filter( function( block ) {
return block.categories.indexOf( category.id ) !== -1
return block.category === category.id
&& block.label.toLowerCase().indexOf( searchBlockFilter.toLowerCase() ) !== -1;
} );
categoryBlocks
.forEach( function( block ) {
var node = document.createElement( 'div' );
node.className = 'insert-block__block';
node.className = 'insert-block__block block-' + block.id + ( menuSelectedBlock === block ? ' is-active' : '' );
node.innerHTML = block.icon + ' ' + block.label;
nodeBlocks.appendChild(node);
} );
Expand All @@ -339,11 +358,134 @@ function fillBlockMenu() {
function attachBlockMenuSearch() {
insertBlockMenuSearchInput.addEventListener( 'keyup', filterBlockMenu, false );
insertBlockMenuSearchInput.addEventListener( 'input', filterBlockMenu, false );
fillBlockMenu();
selectBlockInMenu( 'none' );
renderBlockMenu();

function filterBlockMenu( event ) {
searchBlockFilter = event.target.value;
fillBlockMenu();
selectBlockInMenu( 'none' );
renderBlockMenu();
}
}

/**
* Select a block in the block menu
* @param direction direction from the current position (up/down/left/right)
*/
function selectBlockInMenu( direction ) {
var filteredBlocks = orderedBlocks.filter( function( block ) {
return block.label.toLowerCase().indexOf( searchBlockFilter.toLowerCase() ) !== -1;
} );
var countBlocksByCategories = filteredBlocks.reduce( function( memo, block ) {
if ( ! memo[ block.category ] ) {
memo[ block.category ] = 0;
}
memo[ block.category ]++;
return memo;
}, {} );

var selectedBlockIndex = filteredBlocks.indexOf( menuSelectedBlock );
selectedBlockIndex = selectedBlockIndex === -1 ? 0 : selectedBlockIndex;
var currentBlock = filteredBlocks[ selectedBlockIndex ];
var previousBlock = filteredBlocks[ selectedBlockIndex - 1 ];
var nextBlock = filteredBlocks[ selectedBlockIndex + 1 ];
var offset = 0;
switch ( direction ) {
case 'up':
offset = (
currentBlock
&& filteredBlocks[ selectedBlockIndex - 2 ]
&& (
filteredBlocks[ selectedBlockIndex - 2 ].category === currentBlock.category
|| countBlocksByCategories[ previousBlock.category ] % 2 === 0
)
) ? -2 : -1;
break;
case 'down':
offset = (
currentBlock
&& filteredBlocks[ selectedBlockIndex + 2 ]
&& (
currentBlock.category === filteredBlocks[ selectedBlockIndex + 2 ].category
|| filteredBlocks[ selectedBlockIndex + 2 ].category === nextBlock.category
|| nextBlock.category === currentBlock.category
)
) ? 2 : 1;
break;
case 'right':
offset = 1;
break;
case 'left':
offset = -1;
break;
}

menuSelectedBlock = filteredBlocks[ selectedBlockIndex + offset ] || menuSelectedBlock;

// Hack to wait for the rerender before scrolling
setTimeout( function() {
var blockElement = queryFirst( '.insert-block__block.block-' + menuSelectedBlock.id );
if ( blockElement ) {
insertBlockMenuContent.scrollTop = blockElement.offsetTop - 23;
}
} );
}

function attachKeyboardShortcuts() {
document.addEventListener( 'keypress', handleKeyPress, false );
document.addEventListener( 'keyup', handleKeyUp, false );

function handleKeyPress( event ) {
if (
/[a-zA-Z0-9-_ ]/.test( String.fromCharCode( event.keyCode ) )
&& document.activeElement === insertBlockMenuContent
) {
searchBlockFilter = searchBlockFilter + String.fromCharCode( event.keyCode );
insertBlockMenuSearchInput.value = searchBlockFilter;
selectBlockInMenu( '' );
renderBlockMenu();
}
}

function handleKeyUp( event ) {
switch ( event.keyCode ) {
case 191:
if ( document.activeElement === editor ) return;
event.preventDefault();
! blockMenuOpened && openBlockMenu();
break;
case 13:
event.preventDefault();
blockMenuOpened && hideMenu();
break;
case 37:
event.preventDefault();
selectBlockInMenu( 'left' );
renderBlockMenu();
break;
case 38:
event.preventDefault();
selectBlockInMenu( 'up' );
renderBlockMenu();
break;
case 39:
event.preventDefault();
selectBlockInMenu( 'right' );
renderBlockMenu();
break;
case 40:
event.preventDefault();
selectBlockInMenu( 'down' );
renderBlockMenu();
break;
case 8:
event.preventDefault();
searchBlockFilter = searchBlockFilter.substr( 0, searchBlockFilter.length - 1 );
insertBlockMenuSearchInput.value = searchBlockFilter;
selectBlockInMenu( '' );
renderBlockMenu();
break;
}
}
}

Expand Down Expand Up @@ -394,13 +536,20 @@ function siblingGetter( direction ) {
function openBlockMenu( event ) {
hideInlineControls();
clearBlocks();
event.stopPropagation();
event && event.stopPropagation();
insertBlockMenu.style.display = 'block';
insertBlockMenuSearchInput.focus();
blockMenuOpened = true;
searchBlockFilter = '';
insertBlockMenuSearchInput.value = '';
menuSelectedBlock = false;
insertBlockMenuContent.focus();
selectBlockInMenu( 'none' );
renderBlockMenu();
}

function hideMenu() {
insertBlockMenu.style.display = 'none';
blockMenuOpened = false;
}

function showSwitcherMenu( event ) {
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ <h2>1.0 Is The Loneliest Number</h2>
</button>
<div class="insert-block__menu popover is-top">
<div class="popover__arrow"></div>
<div class="insert-block__content"></div>
<div class="insert-block__content" tabindex="1"></div>
<input class="insert-block__search" type="search" placeholder="Search..." />
</div>
</div>
Expand Down
5 changes: 5 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,10 @@ img.is-selected {
overflow: auto;
}

.insert-block__content:focus {
outline: none;
}

.insert_block__category-blocks {
display: flex;
flex-flow: row wrap;
Expand All @@ -442,6 +446,7 @@ img.is-selected {
align-items: center;
cursor: pointer;
}
.insert-block__block.is-active,
.insert-block__block:hover,
.switch-block__block:hover {
background: #f0f2f4;
Expand Down