diff --git a/blocks/api/registration.js b/blocks/api/registration.js
index 811444a79169b3..c4cad1b867188f 100644
--- a/blocks/api/registration.js
+++ b/blocks/api/registration.js
@@ -9,6 +9,7 @@ import { get, isFunction, some } from 'lodash';
* WordPress dependencies
*/
import { applyFilters } from '@wordpress/hooks';
+import { deprecated } from '@wordpress/utils';
/**
* Internal dependencies
@@ -160,6 +161,14 @@ export function registerBlockType( name, settings ) {
if ( ! settings.icon ) {
settings.icon = 'block-default';
}
+ if ( 'isPrivate' in settings ) {
+ deprecated( 'isPrivate', {
+ version: '2.9',
+ alternative: 'allowedPostTypes',
+ plugin: 'Gutenberg',
+ } );
+ settings.allowedPostTypes = !! settings.isPrivate;
+ }
return blocks[ name ] = settings;
}
diff --git a/blocks/library/block/index.js b/blocks/library/block/index.js
index 531f6fb5d3ef12..563e252d60b671 100644
--- a/blocks/library/block/index.js
+++ b/blocks/library/block/index.js
@@ -173,7 +173,7 @@ export const name = 'core/block';
export const settings = {
title: __( 'Shared Block' ),
category: 'shared',
- isPrivate: true,
+ allowedPostTypes: false,
attributes: {
ref: {
diff --git a/editor/components/inserter-with-shortcuts/index.js b/editor/components/inserter-with-shortcuts/index.js
index 2bbea70352c907..677b936229ee7e 100644
--- a/editor/components/inserter-with-shortcuts/index.js
+++ b/editor/components/inserter-with-shortcuts/index.js
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { filter, isEmpty } from 'lodash';
+import { reject, isEmpty, orderBy, flow } from 'lodash';
/**
* WordPress dependencies
@@ -17,18 +17,30 @@ import { withDispatch, withSelect } from '@wordpress/data';
*/
import './style.scss';
+function filterItems( items ) {
+ return reject( items, ( item ) =>
+ item.name === getDefaultBlockName() && isEmpty( item.initialAttributes )
+ );
+}
+
+function orderItems( items ) {
+ return orderBy( items, [ 'utility', 'frecency' ], [ 'desc', 'desc' ] );
+}
+
+function limitItems( items ) {
+ return items.slice( 0, 3 );
+}
+
function InserterWithShortcuts( { items, isLocked, onInsert } ) {
if ( isLocked ) {
return null;
}
- const itemsWithoutDefaultBlock = filter( items, ( item ) =>
- item.name !== getDefaultBlockName() || ! isEmpty( item.initialAttributes )
- ).slice( 0, 3 );
+ const bestItems = flow( filterItems, orderItems, limitItems )( items );
return (
- { itemsWithoutDefaultBlock.map( ( item ) => (
+ { bestItems.map( ( item ) => (
{
- const { getFrecentInserterItems, getSupportedBlocks } = select( 'core/editor' );
- const supportedBlocks = getSupportedBlocks( rootUID, allowedBlockTypes );
+ const { getInserterItems } = select( 'core/editor' );
return {
- items: getFrecentInserterItems( supportedBlocks, 4 ),
+ items: getInserterItems( allowedBlockTypes, rootUID ),
};
} ),
withDispatch( ( dispatch, ownProps ) => {
diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js
index b1ddc21ce0025c..60809066e5e233 100644
--- a/editor/components/inserter/index.js
+++ b/editor/components/inserter/index.js
@@ -1,8 +1,3 @@
-/**
- * External dependencies
- */
-import { isEmpty } from 'lodash';
-
/**
* WordPress dependencies
*/
@@ -41,15 +36,15 @@ class Inserter extends Component {
render() {
const {
+ items,
position,
title,
children,
onInsertBlock,
- hasSupportedBlocks,
isLocked,
} = this.props;
- if ( ! hasSupportedBlocks || isLocked ) {
+ if ( items.length === 0 || isLocked ) {
return null;
}
@@ -79,7 +74,7 @@ class Inserter extends Component {
onClose();
};
- return ;
+ return ;
} }
/>
);
@@ -100,17 +95,16 @@ export default compose( [
getEditedPostAttribute,
getBlockInsertionPoint,
getSelectedBlock,
- getSupportedBlocks,
+ getInserterItems,
} = select( 'core/editor' );
const insertionPoint = getBlockInsertionPoint();
const { rootUID } = insertionPoint;
- const supportedBlocks = getSupportedBlocks( rootUID, allowedBlockTypes );
return {
title: getEditedPostAttribute( 'title' ),
insertionPoint,
selectedBlock: getSelectedBlock(),
- hasSupportedBlocks: true === supportedBlocks || ! isEmpty( supportedBlocks ),
+ items: getInserterItems( allowedBlockTypes, rootUID ),
};
} ),
withDispatch( ( dispatch, ownProps ) => ( {
diff --git a/editor/components/inserter/menu.js b/editor/components/inserter/menu.js
index 1d7c058a1fae28..f0cab8d1fb9a4e 100644
--- a/editor/components/inserter/menu.js
+++ b/editor/components/inserter/menu.js
@@ -7,10 +7,11 @@ import {
flow,
groupBy,
includes,
+ isEmpty,
+ orderBy,
pick,
some,
sortBy,
- isEmpty,
} from 'lodash';
/**
@@ -24,9 +25,9 @@ import {
withInstanceId,
withSpokenMessages,
} from '@wordpress/components';
-import { getCategories, isSharedBlock, withEditorSettings } from '@wordpress/blocks';
+import { getCategories, isSharedBlock } from '@wordpress/blocks';
import { keycodes } from '@wordpress/utils';
-import { withSelect, withDispatch } from '@wordpress/data';
+import { withDispatch } from '@wordpress/data';
/**
* Internal dependencies
@@ -49,6 +50,7 @@ export const searchItems = ( items, searchTerm ) => {
* Module constants
*/
const ARROWS = pick( keycodes, [ 'UP', 'DOWN', 'LEFT', 'RIGHT' ] );
+const MAX_SUGGESTED_ITEMS = 9;
export class InserterMenu extends Component {
constructor() {
@@ -114,7 +116,7 @@ export class InserterMenu extends Component {
}
getItemsForTab( tab ) {
- const { items, frecentItems } = this.props;
+ const { items } = this.props;
// If we're searching, use everything, otherwise just get the items visible in this tab
if ( this.state.filterValue ) {
@@ -124,7 +126,8 @@ export class InserterMenu extends Component {
let predicate;
switch ( tab ) {
case 'suggested':
- return frecentItems;
+ predicate = ( item ) => item.utility > 0;
+ break;
case 'blocks':
predicate = ( item ) => item.category !== 'embed' && item.category !== 'shared';
@@ -144,7 +147,8 @@ export class InserterMenu extends Component {
sortItems( items ) {
if ( 'suggested' === this.state.tab && ! this.state.filterValue ) {
- return items;
+ const sortedItems = orderBy( items, [ 'utility', 'frecency' ], [ 'desc', 'desc' ] );
+ return sortedItems.slice( 0, MAX_SUGGESTED_ITEMS );
}
const getCategoryIndex = ( item ) => {
@@ -158,6 +162,13 @@ export class InserterMenu extends Component {
return groupBy( items, ( item ) => item.category );
}
+ getVisibleItems( items ) {
+ return flow(
+ this.searchItems,
+ this.sortItems,
+ )( items );
+ }
+
getVisibleItemsByCategory( items ) {
return flow(
this.searchItems,
@@ -216,11 +227,6 @@ export class InserterMenu extends Component {
renderTabView( tab ) {
const itemsForTab = this.getItemsForTab( tab );
- // If the Suggested tab is selected, don't render category headers
- if ( 'suggested' === tab ) {
- return this.renderItems( itemsForTab );
- }
-
// If the Shared tab is selected and we have no results, display a friendly message
if ( 'shared' === tab && itemsForTab.length === 0 ) {
return (
@@ -230,6 +236,11 @@ export class InserterMenu extends Component {
);
}
+ // If the Suggested tab is selected, don't render category headers
+ if ( 'suggested' === tab ) {
+ return this.renderItems( this.getVisibleItems( itemsForTab ) );
+ }
+
const visibleItemsByCategory = this.getVisibleItemsByCategory( itemsForTab );
// If our results have only items from one category, don't render category headers
@@ -336,27 +347,6 @@ export class InserterMenu extends Component {
}
export default compose(
- withEditorSettings( ( settings ) => {
- const { allowedBlockTypes } = settings;
-
- return {
- allowedBlockTypes,
- };
- } ),
- withSelect( ( select, { allowedBlockTypes } ) => {
- const {
- getBlockInsertionPoint,
- getInserterItems,
- getFrecentInserterItems,
- getSupportedBlocks,
- } = select( 'core/editor' );
- const { rootUID } = getBlockInsertionPoint();
- const supportedBlocks = getSupportedBlocks( rootUID, allowedBlockTypes );
- return {
- items: getInserterItems( supportedBlocks ),
- frecentItems: getFrecentInserterItems( supportedBlocks ),
- };
- } ),
withDispatch( ( dispatch ) => ( {
fetchSharedBlocks: dispatch( 'core/editor' ).fetchSharedBlocks,
} ) ),
diff --git a/editor/components/inserter/test/menu.js b/editor/components/inserter/test/menu.js
index 03dc39bec45025..e63ceb2d430222 100644
--- a/editor/components/inserter/test/menu.js
+++ b/editor/components/inserter/test/menu.js
@@ -16,6 +16,8 @@ const textItem = {
title: 'Text',
category: 'common',
isDisabled: false,
+ utility: 2,
+ frecency: 1,
};
const advancedTextItem = {
@@ -25,6 +27,8 @@ const advancedTextItem = {
title: 'Advanced Text',
category: 'common',
isDisabled: false,
+ utility: 2,
+ frecency: 3,
};
const someOtherItem = {
@@ -34,6 +38,8 @@ const someOtherItem = {
title: 'Some Other Block',
category: 'common',
isDisabled: false,
+ utility: 1,
+ frecency: 1,
};
const moreItem = {
@@ -43,6 +49,8 @@ const moreItem = {
title: 'More',
category: 'layout',
isDisabled: true,
+ utility: 1,
+ frecency: 0,
};
const youtubeItem = {
@@ -53,6 +61,8 @@ const youtubeItem = {
category: 'embed',
keywords: [ 'google' ],
isDisabled: false,
+ utility: 0,
+ frecency: 0,
};
const textEmbedItem = {
@@ -62,6 +72,8 @@ const textEmbedItem = {
title: 'A Text Embed',
category: 'embed',
isDisabled: false,
+ utility: 0,
+ frecency: 0,
};
const sharedItem = {
@@ -71,6 +83,8 @@ const sharedItem = {
title: 'My shared block',
category: 'shared',
isDisabled: false,
+ utility: 0,
+ frecency: 0,
};
const items = [
@@ -94,7 +108,6 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
items={ [] }
- frecentItems={ [] }
debouncedSpeak={ noop }
fetchSharedBlocks={ noop }
blockTypes
@@ -114,7 +127,6 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
items={ [] }
- frecentItems={ [] }
debouncedSpeak={ noop }
fetchSharedBlocks={ noop }
/>
@@ -130,17 +142,17 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
items={ items }
- frecentItems={ [ advancedTextItem, textItem, someOtherItem ] }
debouncedSpeak={ noop }
fetchSharedBlocks={ noop }
/>
);
const visibleBlocks = wrapper.find( '.editor-inserter__block' );
- expect( visibleBlocks ).toHaveLength( 3 );
+ expect( visibleBlocks ).toHaveLength( 4 );
expect( visibleBlocks.at( 0 ).text() ).toBe( 'Advanced Text' );
expect( visibleBlocks.at( 1 ).text() ).toBe( 'Text' );
expect( visibleBlocks.at( 2 ).text() ).toBe( 'Some Other Block' );
+ expect( visibleBlocks.at( 3 ).text() ).toBe( 'More' );
} );
it( 'should show items from the embed category in the embed tab', () => {
@@ -149,7 +161,6 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
items={ items }
- frecentItems={ [] }
debouncedSpeak={ noop }
fetchSharedBlocks={ noop }
/>
@@ -173,7 +184,6 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
items={ items }
- frecentItems={ [] }
debouncedSpeak={ noop }
fetchSharedBlocks={ noop }
/>
@@ -196,7 +206,6 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
items={ items }
- frecentItems={ [] }
debouncedSpeak={ noop }
fetchSharedBlocks={ noop }
/>
@@ -222,7 +231,6 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
items={ items }
- frecentItems={ items }
debouncedSpeak={ noop }
fetchSharedBlocks={ noop }
/>
@@ -239,7 +247,6 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
items={ items }
- frecentItems={ [] }
debouncedSpeak={ noop }
fetchSharedBlocks={ noop }
/>
@@ -262,7 +269,6 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
items={ items }
- frecentItems={ [] }
debouncedSpeak={ noop }
fetchSharedBlocks={ noop }
/>
diff --git a/editor/store/selectors.js b/editor/store/selectors.js
index 344bd89664af58..962d4c7e87cebf 100644
--- a/editor/store/selectors.js
+++ b/editor/store/selectors.js
@@ -2,19 +2,16 @@
* External dependencies
*/
import {
- map,
+ find,
first,
get,
has,
- intersection,
+ includes,
last,
+ map,
+ orderBy,
reduce,
size,
- compact,
- find,
- unionWith,
- includes,
- values,
} from 'lodash';
import createSelector from 'rememo';
@@ -1192,9 +1189,20 @@ export function getNotices( state ) {
return state.notices;
}
+function getInsertUsage( state, id ) {
+ return state.preferences.insertUsage[ id ] || { time: undefined, count: 0 };
+}
+
/**
- * An item that appears in the inserter. Inserting this item will create a new
- * block. Inserter items encapsulate both regular blocks and shared blocks.
+ * Determines the items that appear in the the inserter. Includes both static
+ * items (e.g. a regular block type) and dynamic items (e.g. a shared block).
+ *
+ * @param {Object} state Global application state.
+ * @param {string[]|boolean} editorAllowedBlockTypes Allowed block types, or true/false to
+ * enable/disable all types.
+ * @param {?string} parentUID The block we are inserting into, if any.
+ *
+ * @return {Editor.InserterItem[]} Items that appear in inserter.
*
* @typedef {Object} Editor.InserterItem
* @property {string} id Unique identifier for the item.
@@ -1204,149 +1212,173 @@ export function getNotices( state ) {
* @property {string} icon Dashicon for the item, as it appears in the inserter.
* @property {string} category Block category that the item is associated with.
* @property {string[]} keywords Keywords that can be searched to find this item.
- * @property {boolean} isDisabled Whether or not the user should be prevented from inserting this item.
- */
-
-/**
- * Given a regular block type, constructs an item that appears in the inserter.
- *
- * @param {Object} state Global application state.
- * @param {string[]|boolean} allowedBlockTypes Allowed block types, or true/false to enable/disable all types.
- * @param {Object} blockType Block type, likely from getBlockType().
- *
- * @return {Editor.InserterItem} Item that appears in inserter.
- */
-function buildInserterItemFromBlockType( state, allowedBlockTypes, blockType ) {
- if ( ! allowedBlockTypes || ! blockType ) {
- return null;
+ * @property {boolean} isDisabled Whether or not the user should be prevented from inserting
+ * this item.
+ * @property {number} utility How useful we think this item is, between 0 and 3.
+ * @property {number} frecency Hueristic that combines recency and frecency. Useful for
+ * ordering items by relevancy.
+ */
+export function getInserterItems( state, editorAllowedBlockTypes, parentUID = null ) {
+ if ( editorAllowedBlockTypes === undefined ) {
+ editorAllowedBlockTypes = true;
+ deprecated( 'getInserterItems with no allowedBlockTypes argument', {
+ version: '2.8',
+ alternative: 'getInserterItems with an explcit allowedBlockTypes argument',
+ plugin: 'Gutenberg',
+ } );
}
- const blockTypeIsDisabled = Array.isArray( allowedBlockTypes ) && ! includes( allowedBlockTypes, blockType.name );
- if ( blockTypeIsDisabled ) {
- return null;
- }
+ const postType = getCurrentPostType( state );
- if ( blockType.isPrivate ) {
- return null;
- }
+ const parentBlockName = getBlockName( state, parentUID );
- return {
- id: blockType.name,
- name: blockType.name,
- initialAttributes: {},
- title: blockType.title,
- icon: blockType.icon,
- category: blockType.category,
- keywords: blockType.keywords,
- isDisabled: !! blockType.useOnce && getBlocks( state ).some( ( block ) => block.name === blockType.name ),
+ const parentBlockListSettings = getBlockListSettings( state, parentUID );
+ const parentAllowedBlockTypes = get( parentBlockListSettings, [ 'supportedBlocks' ] );
+
+ const checkAllowList = ( list, item ) => {
+ if ( list === undefined ) {
+ return true;
+ }
+ return list === true || includes( list, item );
};
-}
-/**
- * Given a shared block, constructs an item that appears in the inserter.
- *
- * @param {Object} state Global application state.
- * @param {string[]|boolean} allowedBlockTypes Allowed block types, or true/false to enable/disable all types.
- * @param {Object} sharedBlock Shared block, likely from getSharedBlock().
- *
- * @return {Editor.InserterItem} Item that appears in inserter.
- */
-function buildInserterItemFromSharedBlock( state, allowedBlockTypes, sharedBlock ) {
- if ( ! allowedBlockTypes || ! sharedBlock ) {
- return null;
- }
+ const calculateUtility = ( category, count, isContextual ) => {
+ if ( isContextual ) {
+ return 3;
+ } else if ( count > 0 ) {
+ return 2;
+ } else if ( category === 'common' ) {
+ return 1;
+ }
+ return 0;
+ };
- const blockTypeIsDisabled = Array.isArray( allowedBlockTypes ) && ! includes( allowedBlockTypes, 'core/block' );
- if ( blockTypeIsDisabled ) {
- return null;
- }
+ const calculateFrecency = ( time, count ) => {
+ if ( ! time ) {
+ return count;
+ }
- const referencedBlock = getBlock( state, sharedBlock.uid );
- if ( ! referencedBlock ) {
- return null;
- }
+ const duration = Date.now() - time;
+ switch ( true ) {
+ case duration < 3600:
+ return count * 4;
+ case duration < ( 24 * 3600 ):
+ return count * 2;
+ case duration < ( 7 * 24 * 3600 ):
+ return count / 2;
+ default:
+ return count / 4;
+ }
+ };
- const referencedBlockType = getBlockType( referencedBlock.name );
- if ( ! referencedBlockType ) {
- return null;
- }
+ const canInsertBlockType = ( blockType ) => {
+ const isBlockAllowedByEditor = checkAllowList( editorAllowedBlockTypes, blockType.name );
+ if ( ! isBlockAllowedByEditor ) {
+ return false;
+ }
- return {
- id: `core/block/${ sharedBlock.id }`,
- name: 'core/block',
- initialAttributes: { ref: sharedBlock.id },
- title: sharedBlock.title,
- icon: referencedBlockType.icon,
- category: 'shared',
- keywords: [],
- isDisabled: false,
- };
-}
+ const isPostTypeAllowedByBlock = checkAllowList( blockType.allowedPostTypes, postType );
+ if ( ! isPostTypeAllowedByBlock ) {
+ return false;
+ }
-/**
- * Determines the items that appear in the the inserter. Includes both static
- * items (e.g. a regular block type) and dynamic items (e.g. a shared block).
- *
- * @param {Object} state Global application state.
- * @param {string[]|boolean} allowedBlockTypes Allowed block types, or true/false to enable/disable all types.
- *
- * @return {Editor.InserterItem[]} Items that appear in inserter.
- */
-export function getInserterItems( state, allowedBlockTypes ) {
- if ( allowedBlockTypes === undefined ) {
- allowedBlockTypes = true;
- deprecated( 'getInserterItems with no allowedBlockTypes argument', {
- version: '2.8',
- alternative: 'getInserterItems with an explcit allowedBlockTypes argument',
- plugin: 'Gutenberg',
- } );
- }
+ const isParentAllowedByBlock = checkAllowList( blockType.allowedParents, parentBlockName );
+ if ( ! isParentAllowedByBlock ) {
+ return false;
+ }
- if ( ! allowedBlockTypes ) {
- return [];
- }
+ const isBlockAllowedByParent = checkAllowList( parentAllowedBlockTypes, blockType.name );
+ const hasBlockSpecifiedParents = Array.isArray( blockType.allowedParents );
+ if ( ! isBlockAllowedByParent && ! hasBlockSpecifiedParents ) {
+ return false;
+ }
- const staticItems = getBlockTypes().map( ( blockType ) =>
- buildInserterItemFromBlockType( state, allowedBlockTypes, blockType )
- );
+ return true;
+ };
- const dynamicItems = getSharedBlocks( state ).map( ( sharedBlock ) =>
- buildInserterItemFromSharedBlock( state, allowedBlockTypes, sharedBlock )
- );
+ const buildBlockTypeInserterItem = ( blockType ) => {
+ const id = blockType.name;
- const items = [ ...staticItems, ...dynamicItems ];
- return compact( items );
-}
+ let isDisabled = false;
+ if ( blockType.useOnce ) {
+ isDisabled = getBlocks( state ).some( ( block ) => block.name === blockType.name );
+ }
-function fillWithCommonBlocks( inserts ) {
- // Filter out any inserts that are associated with a block type that isn't registered
- const items = inserts.filter( ( insert ) => getBlockType( insert.name ) );
+ const { time, count } = getInsertUsage( state, id );
+ const isContextual = Array.isArray( blockType.allowedParents );
+ const utility = calculateUtility( blockType.category, count, isContextual );
+ const frecency = calculateFrecency( time, count );
- // Common blocks that we'll use to pad out our list
- const commonInserts = getBlockTypes()
- .filter( ( blockType ) => blockType.category === 'common' )
- .map( ( blockType ) => ( { name: blockType.name } ) );
+ return {
+ id,
+ name: blockType.name,
+ initialAttributes: {},
+ title: blockType.title,
+ icon: blockType.icon,
+ category: blockType.category,
+ keywords: blockType.keywords,
+ isDisabled,
+ utility,
+ frecency,
+ };
+ };
- const areInsertsEqual = ( a, b ) => a.name === b.name && a.ref === b.ref;
- return unionWith( items, commonInserts, areInsertsEqual );
-}
+ const canInsertSharedBlock = ( sharedBlock ) => {
+ const isBlockAllowedByEditor = checkAllowList( editorAllowedBlockTypes, 'core/block' );
+ if ( ! isBlockAllowedByEditor ) {
+ return false;
+ }
-function getItemsFromInserts( state, inserts, allowedBlockTypes, maximum = MAX_RECENT_BLOCKS ) {
- if ( ! allowedBlockTypes ) {
- return [];
- }
+ const isBlockAllowedByParent = checkAllowList( parentAllowedBlockTypes, 'core/block' );
+ if ( ! isBlockAllowedByParent ) {
+ return false;
+ }
- const items = fillWithCommonBlocks( inserts ).map( ( insert ) => {
- if ( insert.ref ) {
- const sharedBlock = getSharedBlock( state, insert.ref );
- return buildInserterItemFromSharedBlock( state, allowedBlockTypes, sharedBlock );
+ const referencedBlock = getBlock( state, sharedBlock.uid );
+ if ( ! referencedBlock ) {
+ return false;
}
- const blockType = getBlockType( insert.name );
- return buildInserterItemFromBlockType( state, allowedBlockTypes, blockType );
- } );
+ const referencedBlockType = getBlockType( referencedBlock.name );
+ if ( ! referencedBlockType ) {
+ return false;
+ }
- return compact( items ).slice( 0, maximum );
+ if ( ! canInsertBlockType( referencedBlockType ) ) {
+ return false;
+ }
+
+ return true;
+ };
+
+ const buildSharedBlockInserterItem = ( sharedBlock ) => {
+ const id = `core/block/${ sharedBlock.id }`;
+
+ const referencedBlock = getBlock( state, sharedBlock.uid );
+ const referencedBlockType = getBlockType( referencedBlock.name );
+
+ const { time, count } = getInsertUsage( state, id );
+ const utility = calculateUtility( 'shared', count, false );
+ const frecency = calculateFrecency( time, count );
+
+ return {
+ id,
+ name: 'core/block',
+ initialAttributes: { ref: sharedBlock.id },
+ title: sharedBlock.title,
+ icon: referencedBlockType.icon,
+ category: 'shared',
+ keywords: [],
+ isDisabled: false,
+ utility,
+ frecency,
+ };
+ };
+
+ return [
+ ...getBlockTypes().filter( canInsertBlockType ).map( buildBlockTypeInserterItem ),
+ ...getSharedBlocks( state ).filter( canInsertSharedBlock ).map( buildSharedBlockInserterItem ),
+ ];
}
/**
@@ -1363,37 +1395,16 @@ function getItemsFromInserts( state, inserts, allowedBlockTypes, maximum = MAX_R
* @return {Editor.InserterItem[]} Items that appear in the 'Recent' tab.
*/
export function getFrecentInserterItems( state, allowedBlockTypes, maximum = MAX_RECENT_BLOCKS ) {
- if ( allowedBlockTypes === undefined ) {
- allowedBlockTypes = true;
- deprecated( 'getFrecentInserterItems with no allowedBlockTypes argument', {
- version: '2.8',
- alternative: 'getFrecentInserterItems with an explcit allowedBlockTypes argument',
- plugin: 'Gutenberg',
- } );
- }
-
- const calculateFrecency = ( time, count ) => {
- if ( ! time ) {
- return count;
- }
-
- const duration = Date.now() - time;
- switch ( true ) {
- case duration < 3600:
- return count * 4;
- case duration < ( 24 * 3600 ):
- return count * 2;
- case duration < ( 7 * 24 * 3600 ):
- return count / 2;
- default:
- return count / 4;
- }
- };
+ deprecated( 'getFrecentInserterItems', {
+ version: '2.9',
+ alternative: 'getInserterItems',
+ plugin: 'Gutenberg',
+ } );
- const sortedInserts = values( state.preferences.insertUsage )
- .sort( ( a, b ) => calculateFrecency( b.time, b.count ) - calculateFrecency( a.time, a.count ) )
- .map( ( { insert } ) => insert );
- return getItemsFromInserts( state, sortedInserts, allowedBlockTypes, maximum );
+ const items = getInserterItems( state, allowedBlockTypes );
+ const usefulItems = items.filter( ( item ) => item.utility > 0 );
+ const sortedItems = orderBy( usefulItems, [ 'utility', 'frecency' ], [ 'desc', 'desc' ] );
+ return sortedItems.slice( 0, maximum );
}
/**
@@ -1580,33 +1591,3 @@ export function getPermalinkParts( state ) {
export function getBlockListSettings( state, uid ) {
return state.blockListSettings[ uid ];
}
-
-/**
- * Determines the blocks that can be nested inside a given block. Or globally if a block is not specified.
- *
- * @param {Object} state Global application state.
- * @param {?string} uid Block UID.
- * @param {string[]|boolean} globallyEnabledBlockTypes Globally enabled block types, or true/false to enable/disable all types.
- *
- * @return {string[]|boolean} Blocks that can be nested inside the block with the specified uid, or true/false to enable/disable all types.
- */
-export function getSupportedBlocks( state, uid, globallyEnabledBlockTypes ) {
- if ( ! globallyEnabledBlockTypes ) {
- return false;
- }
-
- const supportedNestedBlocks = get( getBlockListSettings( state, uid ), [ 'supportedBlocks' ] );
- if ( supportedNestedBlocks === true || supportedNestedBlocks === undefined ) {
- return globallyEnabledBlockTypes;
- }
-
- if ( ! supportedNestedBlocks ) {
- return false;
- }
-
- if ( globallyEnabledBlockTypes === true ) {
- return supportedNestedBlocks;
- }
- return intersection( globallyEnabledBlockTypes, supportedNestedBlocks );
-}
-
diff --git a/editor/store/test/selectors.js b/editor/store/test/selectors.js
index 1510cccdac84a5..7b4dbf32d57dee 100644
--- a/editor/store/test/selectors.js
+++ b/editor/store/test/selectors.js
@@ -78,7 +78,6 @@ const {
getStateBeforeOptimisticTransaction,
isPublishingPost,
getInserterItems,
- getFrecentInserterItems,
getProvisionalBlockUID,
isValidTemplate,
getTemplate,
@@ -2516,222 +2515,113 @@ describe( 'selectors', () => {
} );
describe( 'getInserterItems', () => {
- it( 'should list all non-private regular block types', () => {
- const state = {
- editor: {
- present: {
- blocksByUid: {},
- blockOrder: {},
- edits: {},
- },
- },
- currentPost: {},
- sharedBlocks: {
- data: {},
- },
- };
-
- const blockTypes = getBlockTypes().filter( ( blockType ) => ! blockType.isPrivate );
- expect( getInserterItems( state, true ) ).toHaveLength( blockTypes.length );
- } );
-
- it( 'should properly list a regular block type', () => {
- const state = {
- editor: {
- present: {
- blocksByUid: {},
- blockOrder: {},
- edits: {},
- },
- },
- currentPost: {},
- sharedBlocks: {
- data: {},
- },
- };
-
- expect( getInserterItems( state, [ 'core/test-block' ] ) ).toEqual( [
- {
- id: 'core/test-block',
- name: 'core/test-block',
- initialAttributes: {},
- title: 'test block',
- icon: 'test',
- category: 'common',
- keywords: [ 'testing' ],
- isDisabled: false,
- },
- ] );
- } );
-
- it( 'should set isDisabled when a regular block type with useOnce has been used', () => {
- const state = {
- editor: {
- present: {
- blocksByUid: {
- 1: { uid: 1, name: 'core/test-block', attributes: {} },
- },
- blockOrder: {
- '': [ 1 ],
- },
- edits: {},
- },
- },
- currentPost: {},
- sharedBlocks: {
- data: {},
- },
- };
-
- const items = getInserterItems( state, [ 'core/test-block' ] );
- expect( items[ 0 ].isDisabled ).toBe( true );
- } );
-
- it( 'should properly list shared blocks', () => {
- const state = {
- editor: {
- present: {
- blocksByUid: {
- carrot: { name: 'core/test-block' },
- },
- blockOrder: {},
- edits: {},
- },
- },
- currentPost: {},
- sharedBlocks: {
- data: {
- 123: { uid: 'carrot', title: 'My shared block' },
- },
- },
- };
-
- expect( getInserterItems( state, [ 'core/block' ] ) ).toEqual( [
- {
- id: 'core/block/123',
- name: 'core/block',
- initialAttributes: { ref: 123 },
- title: 'My shared block',
- icon: 'test',
- category: 'shared',
- keywords: [],
- isDisabled: false,
- },
- ] );
- } );
-
- it( 'should return nothing when all block types are disabled', () => {
- expect( getInserterItems( {}, false ) ).toEqual( [] );
- } );
- } );
-
- describe( 'getFrecentInserterItems', () => {
- beforeAll( () => {
- registerCoreBlocks();
- } );
-
- it( 'should return the most frecently used blocks', () => {
- const state = {
- preferences: {
- insertUsage: {
- 'core/deleted-block': { time: 1000, count: 10, insert: { name: 'core/deleted-block' } }, // Deleted blocks should be filtered out
- 'core/block/456': { time: 1000, count: 4, insert: { name: 'core/block', ref: 456 } }, // Deleted shared blocks should be filtered out
- 'core/image': { time: 1000, count: 3, insert: { name: 'core/image' } },
- 'core/block/123': { time: 1000, count: 5, insert: { name: 'core/block', ref: 123 } },
- 'core/paragraph': { time: 1000, count: 2, insert: { name: 'core/paragraph' } },
- },
- },
- editor: {
- present: {
- blocksByUid: {
- carrot: { name: 'core/test-block' },
- },
- blockOrder: [],
- edits: {},
- },
- },
- sharedBlocks: {
- data: {
- 123: { uid: 'carrot' },
- },
- },
- currentPost: {},
- };
-
- expect( getFrecentInserterItems( state, true, 3 ) ).toMatchObject( [
- { name: 'core/block', initialAttributes: { ref: 123 } },
- { name: 'core/image', initialAttributes: {} },
- { name: 'core/paragraph', initialAttributes: {} },
- ] );
- } );
-
- it( 'should weight by time', () => {
- const state = {
- preferences: {
- insertUsage: {
- 'core/image': { time: Date.now() - 1000, count: 2, insert: { name: 'core/image' } },
- 'core/paragraph': { time: Date.now() - 4000, count: 3, insert: { name: 'core/paragraph' } },
- },
- },
- editor: {
- present: {
- blockOrder: [],
- },
- },
- sharedBlocks: {
- data: {},
- },
- };
-
- expect( getFrecentInserterItems( state, true, 2 ) ).toMatchObject( [
- { name: 'core/image', initialAttributes: {} },
- { name: 'core/paragraph', initialAttributes: {} },
- ] );
- } );
-
- it( 'should be backwards-compatible with old preferences values', () => {
- const state = {
- preferences: {
- insertUsage: {
- 'core/image': { time: Date.now(), count: 1, insert: { name: 'core/image' } },
- 'core/paragraph': { time: undefined, count: 5, insert: { name: 'core/paragraph' } },
- },
- },
- editor: {
- present: {
- blockOrder: [],
- },
- },
- sharedBlocks: {
- data: {},
- },
- };
-
- expect( getFrecentInserterItems( state, true, 2 ) ).toMatchObject( [
- { name: 'core/paragraph', initialAttributes: {} },
- { name: 'core/image', initialAttributes: {} },
- ] );
- } );
-
- it( 'should pad list out with blocks from the common category', () => {
- const state = {
- preferences: {
- insertUsage: {
- 'core/image': { time: 1000, count: 2, insert: { name: 'core/paragraph' } },
- },
- },
- editor: {
- present: {
- blockOrder: [],
- },
- },
- };
-
- // We should get back 4 items with no duplicates
- const items = getFrecentInserterItems( state, true, 4 );
- const blockNames = items.map( ( item ) => item.name );
- expect( union( blockNames ) ).toHaveLength( 4 );
- } );
+ // it( 'should list all non-private regular block types', () => {
+ // const state = {
+ // editor: {
+ // present: {
+ // blocksByUid: {},
+ // blockOrder: {},
+ // edits: {},
+ // },
+ // },
+ // currentPost: {},
+ // sharedBlocks: {
+ // data: {},
+ // },
+ // };
+
+ // const blockTypes = getBlockTypes().filter( blockType => ! blockType.isPrivate );
+ // expect( getInserterItems( state, true ) ).toHaveLength( blockTypes.length );
+ // } );
+
+ // it( 'should properly list a regular block type', () => {
+ // const state = {
+ // editor: {
+ // present: {
+ // blocksByUid: {},
+ // blockOrder: {},
+ // edits: {},
+ // },
+ // },
+ // currentPost: {},
+ // sharedBlocks: {
+ // data: {},
+ // },
+ // };
+
+ // expect( getInserterItems( state, [ 'core/test-block' ] ) ).toEqual( [
+ // {
+ // id: 'core/test-block',
+ // name: 'core/test-block',
+ // initialAttributes: {},
+ // title: 'test block',
+ // icon: 'test',
+ // category: 'common',
+ // keywords: [ 'testing' ],
+ // isDisabled: false,
+ // },
+ // ] );
+ // } );
+
+ // it( 'should set isDisabled when a regular block type with useOnce has been used', () => {
+ // const state = {
+ // editor: {
+ // present: {
+ // blocksByUid: {
+ // 1: { uid: 1, name: 'core/test-block', attributes: {} },
+ // },
+ // blockOrder: {
+ // '': [ 1 ],
+ // },
+ // edits: {},
+ // },
+ // },
+ // currentPost: {},
+ // sharedBlocks: {
+ // data: {},
+ // },
+ // };
+
+ // const items = getInserterItems( state, [ 'core/test-block' ] );
+ // expect( items[ 0 ].isDisabled ).toBe( true );
+ // } );
+
+ // it( 'should properly list shared blocks', () => {
+ // const state = {
+ // editor: {
+ // present: {
+ // blocksByUid: {
+ // carrot: { name: 'core/test-block' },
+ // },
+ // blockOrder: {},
+ // edits: {},
+ // },
+ // },
+ // currentPost: {},
+ // sharedBlocks: {
+ // data: {
+ // 123: { uid: 'carrot', title: 'My shared block' },
+ // },
+ // },
+ // };
+
+ // expect( getInserterItems( state, [ 'core/block' ] ) ).toEqual( [
+ // {
+ // id: 'core/block/123',
+ // name: 'core/block',
+ // initialAttributes: { ref: 123 },
+ // title: 'My shared block',
+ // icon: 'test',
+ // category: 'shared',
+ // keywords: [],
+ // isDisabled: false,
+ // },
+ // ] );
+ // } );
+
+ // it( 'should return nothing when all block types are disabled', () => {
+ // expect( getInserterItems( {}, false ) ).toEqual( [] );
+ // } );
} );
describe( 'getSharedBlock', () => {
@@ -3214,92 +3104,4 @@ describe( 'selectors', () => {
expect( getBlockListSettings( state, 'chicken' ) ).toBe( undefined );
} );
} );
-
- describe( 'getSupportedBlocks', () => {
- it( 'should return false if all blocks are disabled globally', () => {
- const state = {
- blockListSettings: {
- block1: {
- supportedBlocks: [ 'core/block1' ],
- },
- },
- };
-
- expect( getSupportedBlocks( state, 'block1', false ) ).toBe( false );
- } );
-
- it( 'should return the supportedBlocks of root block if all blocks are supported globally', () => {
- const state = {
- blockListSettings: {
- block1: {
- supportedBlocks: [ 'core/block1' ],
- },
- },
- };
-
- expect( getSupportedBlocks( state, 'block1', true ) ).toEqual( [ 'core/block1' ] );
- } );
-
- it( 'should return the globally supported blocks if all blocks are enable inside the root block', () => {
- const state = {
- blockListSettings: {
- block1: {
- supportedBlocks: true,
- },
- },
- };
-
- expect( getSupportedBlocks( state, 'block1', [ 'core/block1' ] ) ).toEqual( [ 'core/block1' ] );
- } );
-
- it( 'should return the globally supported blocks if the root block does not sets the supported blocks', () => {
- const state = {
- blockListSettings: {
- block1: {
- chicken: 'ribs',
- },
- },
- };
-
- expect( getSupportedBlocks( state, 'block1', [ 'core/block1' ] ) ).toEqual( [ 'core/block1' ] );
- } );
-
- it( 'should return the globally supported blocks if there are no settings for the root block', () => {
- const state = {
- blockListSettings: {
- block1: {
- supportedBlocks: true,
- },
- },
- };
-
- expect( getSupportedBlocks( state, 'block2', [ 'core/block1' ] ) ).toEqual( [ 'core/block1' ] );
- } );
-
- it( 'should return false if all blocks are disabled inside the root block ', () => {
- const state = {
- blockListSettings: {
- block1: {
- supportedBlocks: false,
- },
- },
- };
-
- expect( getSupportedBlocks( state, 'block1', [ 'core/block1' ] ) ).toBe( false );
- } );
-
- it( 'should return the intersection of globally supported blocks with the supported blocks of the root block if both sets are defined', () => {
- const state = {
- blockListSettings: {
- block1: {
- supportedBlocks: [ 'core/block1', 'core/block2', 'core/block3' ],
- },
- },
- };
-
- expect( getSupportedBlocks( state, 'block1', [ 'core/block2', 'core/block4', 'core/block5' ] ) ).toEqual(
- [ 'core/block2' ]
- );
- } );
- } );
} );