-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Recent blocks #1694
Recent blocks #1694
Changes from 2 commits
8f00652
ead5309
e455346
9286b68
1ef4308
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ import { getCategories, getBlockTypes } from 'blocks'; | |
*/ | ||
import './style.scss'; | ||
import { showInsertionPoint, hideInsertionPoint } from '../actions'; | ||
import { getRecentlyUsedBlocks } from '../selectors'; | ||
|
||
class InserterMenu extends Component { | ||
constructor() { | ||
|
@@ -29,11 +30,13 @@ class InserterMenu extends Component { | |
tab: 'recent', | ||
}; | ||
this.filter = this.filter.bind( this ); | ||
this.isShownBlock = this.isShownBlock.bind( this ); | ||
this.setSearchFocus = this.setSearchFocus.bind( this ); | ||
this.onKeyDown = this.onKeyDown.bind( this ); | ||
this.getVisibleBlocks = this.getVisibleBlocks.bind( this ); | ||
this.sortBlocksByCategory = this.sortBlocksByCategory.bind( this ); | ||
this.applySearchFilter = this.applySearchFilter.bind( this ); | ||
this.getBlocksForCurrentTab = this.getBlocksForCurrentTab.bind( this ); | ||
this.sortBlocks = this.sortBlocks.bind( this ); | ||
this.addRecentBlocks = this.addRecentBlocks.bind( this ); | ||
this.filterRecentForSearch = this.filterRecentForSearch.bind( this ); | ||
} | ||
|
||
componentDidMount() { | ||
|
@@ -44,10 +47,6 @@ class InserterMenu extends Component { | |
document.removeEventListener( 'keydown', this.onKeyDown ); | ||
} | ||
|
||
isShownBlock( block ) { | ||
return block.title.toLowerCase().indexOf( this.state.filterValue.toLowerCase() ) !== -1; | ||
} | ||
|
||
bindReferenceNode( nodeName ) { | ||
return ( node ) => this.nodes[ nodeName ] = node; | ||
} | ||
|
@@ -58,37 +57,69 @@ class InserterMenu extends Component { | |
} ); | ||
} | ||
|
||
selectBlock( name ) { | ||
selectBlock( blockType ) { | ||
return () => { | ||
this.props.onSelect( name ); | ||
this.props.onSelect( blockType.name ); | ||
this.setState( { | ||
filterValue: '', | ||
currentFocus: null, | ||
} ); | ||
}; | ||
} | ||
|
||
getVisibleBlocks( blockTypes ) { | ||
return filter( blockTypes, this.isShownBlock ); | ||
applySearchFilter( blockTypes ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be just be |
||
const matchesSearch = ( block ) => block.title.toLowerCase().indexOf( this.state.filterValue.toLowerCase() ) !== -1; | ||
return filter( blockTypes, matchesSearch ); | ||
} | ||
|
||
getBlocksForCurrentTab() { | ||
// if we're searching, use everything, otherwise just get the blocks visible in this tab | ||
if ( this.state.filterValue ) { | ||
return getBlockTypes(); | ||
} | ||
switch ( this.state.tab ) { | ||
case 'recent': | ||
return this.props.recentlyUsedBlocks; | ||
case 'blocks': | ||
return filter( getBlockTypes(), ( block ) => block.category !== 'embed' ); | ||
case 'embeds': | ||
return filter( getBlockTypes(), ( block ) => block.category === 'embed' ); | ||
} | ||
} | ||
|
||
sortBlocksByCategory( blockTypes ) { | ||
sortBlocks( blockTypes ) { | ||
if ( 'recent' === this.state.tab && ! this.state.filterValue ) { | ||
return blockTypes; | ||
} | ||
|
||
const getCategoryIndex = ( item ) => { | ||
return findIndex( getCategories(), ( category ) => category.slug === item.category ); | ||
}; | ||
|
||
return sortBy( blockTypes, getCategoryIndex ); | ||
} | ||
|
||
addRecentBlocks( blocksByCategory ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Add" feels a bit weird in this context. Why does it need to "add" anything? Should recent be a "category" or just returned on demand for the recent tab? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, it's "adding" .recent to the object that has the blocks groups by category. We'd need to rework the render method a bit to get rid of this, which I'm happy to do, it's just this PR was getting big enough as it was :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to clarify why this needs a separate step, 'recent' isn't a category because we don't have the ability for a block to be in two categories, so the method that sorts blocks by category has no knowledge of 'recent', and this method is adding the recent category to the object that holds the blocks grouped by category. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest, the render method could use cleaning up, there's a lot of duplication and we could simplify it a lot. Your call if you'd like this done in this PR or not, I'm happy to do it either here, or as another PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am ok refactoring later. |
||
blocksByCategory.recent = this.props.recentlyUsedBlocks; | ||
return blocksByCategory; | ||
} | ||
|
||
filterRecentForSearch( blocksByCategory ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I understand why we need this function. Wouldn't search just search all available blocks and list the results? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I guess this is made redundant by the recent "search searches everything" change. Will remove. |
||
blocksByCategory.recent = this.applySearchFilter( blocksByCategory.recent ); | ||
return blocksByCategory; | ||
} | ||
|
||
groupByCategory( blockTypes ) { | ||
return groupBy( blockTypes, ( blockType ) => blockType.category ); | ||
} | ||
|
||
getVisibleBlocksByCategory( blockTypes ) { | ||
return flow( | ||
this.getVisibleBlocks, | ||
this.sortBlocksByCategory, | ||
this.groupByCategory | ||
this.applySearchFilter, | ||
this.sortBlocks, | ||
this.groupByCategory, | ||
this.addRecentBlocks, | ||
this.filterRecentForSearch | ||
)( blockTypes ); | ||
} | ||
|
||
|
@@ -137,9 +168,9 @@ class InserterMenu extends Component { | |
|
||
focusNext() { | ||
const sortedByCategory = flow( | ||
this.getVisibleBlocks, | ||
this.sortBlocksByCategory, | ||
)( getBlockTypes() ); | ||
this.applySearchFilter, | ||
this.sortBlocks, | ||
)( this.getBlocksForCurrentTab() ); | ||
|
||
// If the block list is empty return early. | ||
if ( ! sortedByCategory.length ) { | ||
|
@@ -152,9 +183,9 @@ class InserterMenu extends Component { | |
|
||
focusPrevious() { | ||
const sortedByCategory = flow( | ||
this.getVisibleBlocks, | ||
this.sortBlocksByCategory, | ||
)( getBlockTypes() ); | ||
this.applySearchFilter, | ||
this.sortBlocks, | ||
)( this.getBlocksForCurrentTab() ); | ||
|
||
// If the block list is empty return early. | ||
if ( ! sortedByCategory.length ) { | ||
|
@@ -231,7 +262,7 @@ class InserterMenu extends Component { | |
role="menuitem" | ||
key={ block.name } | ||
className="editor-inserter__block" | ||
onClick={ this.selectBlock( block.name ) } | ||
onClick={ this.selectBlock( block ) } | ||
ref={ this.bindReferenceNode( block.name ) } | ||
tabIndex="-1" | ||
onMouseEnter={ this.props.showInsertionPoint } | ||
|
@@ -245,12 +276,13 @@ class InserterMenu extends Component { | |
|
||
switchTab( tab ) { | ||
this.setState( { tab: tab } ); | ||
this.setSearchFocus(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this? Sounds like it could mess with accessibility. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this was to try and make it easier to use for people who are mostly keyboard based. There's no way to switch between tabs with the keyboard right now, so to make it easier to find the block you want, I put the focus into search so you could either start typing the name of a block or start tabbing through the list without any further mouse clicks. Happy to take it out, but it saved a lot of mouse movement when testing search and tabbing! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There again, that was before search was at the top. I'll leave the call to someone who knows better! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove this for now to not mess with focus. We'll need another pass of keyboard flows here once things settle. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed. |
||
} | ||
|
||
render() { | ||
const { position, instanceId } = this.props; | ||
const visibleBlocksByCategory = this.getVisibleBlocksByCategory( getBlockTypes() ); | ||
const isSearching = this.state.filterValue; | ||
const visibleBlocksByCategory = this.getVisibleBlocksByCategory( this.getBlocksForCurrentTab() ); | ||
|
||
/* eslint-disable jsx-a11y/no-autofocus */ | ||
return ( | ||
|
@@ -272,19 +304,15 @@ class InserterMenu extends Component { | |
<div role="menu" className="editor-inserter__content"> | ||
{ this.state.tab === 'recent' && ! isSearching && | ||
<div className="editor-inserter__recent"> | ||
{ getCategories() | ||
.map( ( category ) => category.slug === 'common' && !! visibleBlocksByCategory[ category.slug ] && ( | ||
<div | ||
className="editor-inserter__category-blocks" | ||
role="menu" | ||
tabIndex="0" | ||
aria-labelledby={ `editor-inserter__separator-${ category.slug }-${ instanceId }` } | ||
key={ category.slug } | ||
> | ||
{ visibleBlocksByCategory[ category.slug ].map( ( block ) => this.getBlockItem( block ) ) } | ||
</div> | ||
) ) | ||
} | ||
<div | ||
className="editor-inserter__category-blocks" | ||
role="menu" | ||
tabIndex="0" | ||
aria-labelledby={ `editor-inserter__separator-${ 'recent' }-${ instanceId }` } | ||
key={ 'recent' } | ||
> | ||
{ visibleBlocksByCategory.recent.map( ( block ) => this.getBlockItem( block ) ) } | ||
</div> | ||
</div> | ||
} | ||
{ this.state.tab === 'blocks' && ! isSearching && | ||
|
@@ -375,7 +403,11 @@ class InserterMenu extends Component { | |
} | ||
|
||
const connectComponent = connect( | ||
undefined, | ||
( state ) => { | ||
return { | ||
recentlyUsedBlocks: getRecentlyUsedBlocks( state ), | ||
}; | ||
}, | ||
{ showInsertionPoint, hideInsertionPoint } | ||
); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,11 @@ import { combineReducers, applyMiddleware, createStore } from 'redux'; | |
import refx from 'refx'; | ||
import { reduce, keyBy, first, last, omit, without, flowRight } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { getBlockTypes } from 'blocks'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be internal dependency. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you clarify this? Imports from 'blocks' are WordPress dependencies in other places. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've just checked, and everywhere else that imports things from 'blocks', it's listed as a WordPress dependency. Does everywhere else need to change, or is this specific to the getBlockTypes function? If so, can you clarify why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, sorry, my bad, this is assuming how we'll expose these. We would need to fix this one instead: https://github.com/WordPress/gutenberg/pull/1694/files#diff-d9e543404329206d6652e45f824ae35fR11 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed! |
||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
|
@@ -219,6 +224,28 @@ export const editor = combineUndoableReducers( { | |
|
||
return state; | ||
}, | ||
|
||
recentlyUsedBlocks( state = [], action ) { | ||
const maxRecent = 8; | ||
switch ( action.type ) { | ||
case 'SETUP_NEW_POST': | ||
// This is where we initially populate the recently used blocks, | ||
// for now this inserts blocks from the common category. | ||
return getBlockTypes() | ||
.filter( ( blockType ) => 'common' === blockType.category ) | ||
.slice( 0, maxRecent ) | ||
.map( ( blockType ) => blockType.name ); | ||
case 'INSERT_BLOCKS': | ||
// This is where we record the block usage so it can show up in | ||
// the recent blocks. | ||
let newState = [ ...state ]; | ||
action.blocks.forEach( ( block ) => { | ||
newState = [ block.name, ...without( newState, block.name ) ]; | ||
} ); | ||
return newState.slice( 0, maxRecent ); | ||
} | ||
return state; | ||
}, | ||
}, { resetTypes: [ 'RESET_BLOCKS' ] } ); | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why pass
blockType
if only using the name?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we used to store the whole block before that data was moved into the app state. Will change!