Skip to content

Commit

Permalink
Merge pull request #4768 from WordPress/add/reusable-blocks-to-recent…
Browse files Browse the repository at this point in the history
…-tab

Display reusable blocks in the inserter's 'Recent' tab
  • Loading branch information
noisysocks authored Feb 1, 2018
2 parents 4ed5cd2 + 6d77903 commit 14a6e40
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 61 deletions.
2 changes: 1 addition & 1 deletion editor/store/defaults.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export const PREFERENCES_DEFAULTS = {
recentlyUsedBlocks: [],
recentInserts: [],
};
45 changes: 19 additions & 26 deletions editor/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { combineReducers } from 'redux';
import {
flow,
partialRight,
difference,
reduce,
keyBy,
first,
Expand All @@ -21,7 +20,7 @@ import {
/**
* WordPress dependencies
*/
import { getBlockTypes, getBlockType } from '@wordpress/blocks';
import { isReusableBlock } from '@wordpress/blocks';

/**
* Internal dependencies
Expand All @@ -30,11 +29,6 @@ import withHistory from '../utils/with-history';
import withChangeDetection from '../utils/with-change-detection';
import { PREFERENCES_DEFAULTS } from './defaults';

/***
* Module constants
*/
const MAX_RECENT_BLOCKS = 8;

/**
* Returns a post attribute value, flattening nested rendered content using its
* raw value in place of its original object form.
Expand Down Expand Up @@ -531,28 +525,27 @@ export function isInsertionPointVisible( state = false, action ) {
export function preferences( state = PREFERENCES_DEFAULTS, action ) {
switch ( action.type ) {
case 'INSERT_BLOCKS':
// put the block in the recently used blocks
let recentlyUsedBlocks = [ ...state.recentlyUsedBlocks ];
action.blocks.forEach( ( block ) => {
recentlyUsedBlocks = [ block.name, ...without( recentlyUsedBlocks, block.name ) ].slice( 0, MAX_RECENT_BLOCKS );
} );
return {
...state,
recentlyUsedBlocks,
};
case 'SETUP_EDITOR':
const isBlockDefined = name => getBlockType( name ) !== undefined;
const filterInvalidBlocksFromList = list => list.filter( isBlockDefined );
const commonBlocks = getBlockTypes()
.filter( ( blockType ) => 'common' === blockType.category )
.map( ( blockType ) => blockType.name );
return action.blocks.reduce( ( prevState, block ) => {
const insert = { name: block.name };
if ( isReusableBlock( block ) ) {
insert.ref = block.attributes.ref;
}

const isSameAsInsert = ( { name, ref } ) => name === insert.name && ref === insert.ref;

return {
...prevState,
recentInserts: [
insert,
...reject( prevState.recentInserts, isSameAsInsert ),
],
};
}, state );

case 'REMOVE_REUSABLE_BLOCK':
return {
...state,
// recently used gets filled up to `MAX_RECENT_BLOCKS` with blocks from the common category
recentlyUsedBlocks: filterInvalidBlocksFromList( [ ...state.recentlyUsedBlocks ] )
.concat( difference( commonBlocks, state.recentlyUsedBlocks ) )
.slice( 0, MAX_RECENT_BLOCKS ),
recentInserts: reject( state.recentInserts, insert => insert.ref === action.id ),
};
}

Expand Down
32 changes: 27 additions & 5 deletions editor/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
compact,
find,
some,
unionWith,
} from 'lodash';
import createSelector from 'rememo';

Expand All @@ -24,6 +25,7 @@ import { addQueryArgs } from '@wordpress/url';
/***
* Module constants
*/
const MAX_RECENT_BLOCKS = 8;
export const POST_UPDATE_TRANSACTION_ID = 'post-update';

/**
Expand Down Expand Up @@ -1084,6 +1086,22 @@ export function getInserterItems( state, enabledBlockTypes = true ) {
return compact( items );
}

const getRecentInserts = createSelector(
state => {
// Filter out any inserts that are associated with a block type that isn't registered
const inserts = state.preferences.recentInserts.filter( insert => getBlockType( insert.name ) );

// Common blocks that we'll use to pad out our list
const commonInserts = getBlockTypes()
.filter( blockType => blockType.category === 'common' )
.map( blockType => ( { name: blockType.name } ) );

const areInsertsEqual = ( a, b ) => a.name === b.name && a.ref === b.ref;
return unionWith( inserts, commonInserts, areInsertsEqual );
},
state => state.preferences.recentInserts
);

/**
* Determines the items that appear in the 'Recent' tab of the inserter.
*
Expand All @@ -1097,13 +1115,17 @@ export function getRecentInserterItems( state, enabledBlockTypes = true ) {
return [];
}

const items = state.preferences.recentlyUsedBlocks.map( name =>
buildInserterItemFromBlockType( state, enabledBlockTypes, getBlockType( name ) )
);
const items = getRecentInserts( state ).map( insert => {
if ( insert.ref ) {
const reusableBlock = getReusableBlock( state, insert.ref );
return buildInserterItemFromReusableBlock( enabledBlockTypes, reusableBlock );
}

// TODO: Merge in recently used reusable blocks
const blockType = getBlockType( insert.name );
return buildInserterItemFromBlockType( state, enabledBlockTypes, blockType );
} );

return compact( items );
return compact( items ).slice( 0, MAX_RECENT_BLOCKS );
}

/**
Expand Down
57 changes: 33 additions & 24 deletions editor/store/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
registerCoreBlocks,
registerBlockType,
unregisterBlockType,
getBlockType,
} from '@wordpress/blocks';

/**
Expand Down Expand Up @@ -966,55 +965,65 @@ describe( 'state', () => {
const state = preferences( undefined, {} );

expect( state ).toEqual( {
recentlyUsedBlocks: [],
recentInserts: [],
} );
} );

it( 'should record recently used blocks', () => {
const state = preferences( deepFreeze( { recentlyUsedBlocks: [] } ), {
const state = preferences( deepFreeze( { recentInserts: [] } ), {
type: 'INSERT_BLOCKS',
blocks: [ {
uid: 'bacon',
name: 'core-embed/twitter',
} ],
} );

expect( state.recentlyUsedBlocks[ 0 ] ).toEqual( 'core-embed/twitter' );
expect( state ).toEqual( {
recentInserts: [
{ name: 'core-embed/twitter' },
],
} );

const twoRecentBlocks = preferences( deepFreeze( { recentlyUsedBlocks: [] } ), {
const twoRecentBlocks = preferences( deepFreeze( { recentInserts: [] } ), {
type: 'INSERT_BLOCKS',
blocks: [ {
uid: 'eggs',
name: 'core-embed/twitter',
}, {
uid: 'bacon',
name: 'core-embed/youtube',
name: 'core/block',
attributes: { ref: 123 },
} ],
} );

expect( twoRecentBlocks.recentlyUsedBlocks[ 0 ] ).toEqual( 'core-embed/youtube' );
expect( twoRecentBlocks.recentlyUsedBlocks[ 1 ] ).toEqual( 'core-embed/twitter' );
} );

it( 'should populate recentlyUsedBlocks, filling up with common blocks, on editor setup', () => {
const state = preferences( deepFreeze( { recentlyUsedBlocks: [ 'core-embed/twitter', 'core-embed/youtube' ] } ), {
type: 'SETUP_EDITOR',
expect( twoRecentBlocks ).toEqual( {
recentInserts: [
{ name: 'core/block', ref: 123 },
{ name: 'core-embed/twitter' },
],
} );
} );

expect( state.recentlyUsedBlocks[ 0 ] ).toEqual( 'core-embed/twitter' );
expect( state.recentlyUsedBlocks[ 1 ] ).toEqual( 'core-embed/youtube' );
it( 'should remove recorded reusable blocks that are deleted', () => {
const initialState = {
recentInserts: [
{ name: 'core-embed/twitter' },
{ name: 'core/block', ref: 123 },
{ name: 'core/block', ref: 456 },
],
};

state.recentlyUsedBlocks.slice( 2 ).forEach(
block => expect( getBlockType( block ).category ).toEqual( 'common' )
);
expect( state.recentlyUsedBlocks ).toHaveLength( 8 );
} );
const state = preferences( deepFreeze( initialState ), {
type: 'REMOVE_REUSABLE_BLOCK',
id: 123,
} );

it( 'should remove unregistered blocks from persisted recent usage', () => {
const state = preferences( deepFreeze( { recentlyUsedBlocks: [ 'core-embed/i-do-not-exist', 'core-embed/youtube' ] } ), {
type: 'SETUP_EDITOR',
expect( state ).toEqual( {
recentInserts: [
{ name: 'core-embed/twitter' },
{ name: 'core/block', ref: 456 },
],
} );
expect( state.recentlyUsedBlocks[ 0 ] ).toEqual( 'core-embed/youtube' );
} );
} );

Expand Down
63 changes: 58 additions & 5 deletions editor/store/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import moment from 'moment';
import { union } from 'lodash';

/**
* WordPress dependencies
Expand Down Expand Up @@ -1921,18 +1922,70 @@ describe( 'selectors', () => {
} );

describe( 'getRecentInserterItems', () => {
beforeEach( () => {
beforeAll( () => {
registerCoreBlocks();
} );
it( 'should return the most recently used blocks', () => {

it( 'should return the 8 most recently used blocks', () => {
const state = {
preferences: {
recentlyUsedBlocks: [ 'core/deleted-block', 'core/paragraph', 'core/image' ],
recentInserts: [
{ name: 'core/deleted-block' }, // Deleted blocks should be filtered out
{ name: 'core/block', ref: 456 }, // Deleted reusable blocks should be filtered out
{ name: 'core/paragraph' },
{ name: 'core/block', ref: 123 },
{ name: 'core/image' },
{ name: 'core/quote' },
{ name: 'core/gallery' },
{ name: 'core/heading' },
{ name: 'core/list' },
{ name: 'core/video' },
{ name: 'core/audio' },
{ name: 'core/code' },
],
},
editor: {
present: {
blockOrder: [],
},
},
reusableBlocks: {
data: {
123: { id: 123, type: 'core/test-block' },
},
},
};

expect( getRecentInserterItems( state ) ).toMatchObject( [
{ name: 'core/paragraph', initialAttributes: {} },
{ name: 'core/block', initialAttributes: { ref: 123 } },
{ name: 'core/image', initialAttributes: {} },
{ name: 'core/quote', initialAttributes: {} },
{ name: 'core/gallery', initialAttributes: {} },
{ name: 'core/heading', initialAttributes: {} },
{ name: 'core/list', initialAttributes: {} },
{ name: 'core/video', initialAttributes: {} },
] );
} );

it( 'should pad list out with blocks from the common category', () => {
const state = {
preferences: {
recentInserts: [
{ name: 'core/paragraph' },
],
},
editor: {
present: {
blockOrder: [],
},
},
};

expect( getRecentInserterItems( state ).map( ( item ) => item.name ) )
.toEqual( [ 'core/paragraph', 'core/image' ] );
// We should get back 8 items with no duplicates
const items = getRecentInserterItems( state );
const blockNames = items.map( item => item.name );
expect( union( blockNames ) ).toHaveLength( 8 );
} );
} );

Expand Down

0 comments on commit 14a6e40

Please sign in to comment.