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

Edit Post: Add block management modal #14224

Merged
merged 40 commits into from
Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
86c88a1
Edit Post: Provide default preferences by higher-order reducer
aduth Mar 4, 2019
8af0287
Edit Post: Add block management modal
aduth Mar 4, 2019
f83be63
Edit Post: Rephrase block disable as block hide
aduth Mar 8, 2019
8fc50d9
Edit Post: Update "Hide All" to "Hide all blocks"
aduth Mar 8, 2019
c61ccd7
Edit Post: Remove block manager autoFocus
aduth Mar 8, 2019
9ac28ec
Edit Post: Close Block Manager panels by default
aduth Mar 8, 2019
ae02300
Edit Post: Show visible label for Block Manager search
aduth Mar 8, 2019
40ffefe
Edit Post: Generalize block search to blocks selector
aduth Mar 8, 2019
c8d5b98
Edit Post: Show hidden block types in Block Manager as labelled
aduth Mar 8, 2019
b28cbb0
Edit Post: Avoid generating unnecessary hasChildBlocksWithInserterSup…
aduth Mar 8, 2019
b681ecb
Edit Post: Split Block Manager category render to own component
aduth Mar 8, 2019
f8a1874
Edit Post: Filter Block Manager items by inserter support
aduth Mar 8, 2019
98e55eb
Edit Post: Render Block Manager item as toggle button
aduth Mar 8, 2019
32e2490
Edit Post: Render Block Manager results as labelled region
aduth Mar 8, 2019
2de9a7e
Edit Post: Constrain Block Manager width to 485px
aduth Mar 8, 2019
8671702
Components: Add className prop support for ToggleControl
aduth Mar 8, 2019
fccc796
Edit Post: Add margin surrounding Hide all checkbox
aduth Mar 8, 2019
f092ff6
Edit Post: Avoid changing label in toggle button
aduth Mar 8, 2019
25b185c
Blocks: Regenerate docs for isMatchingSearchTerm
aduth Mar 8, 2019
fbf5410
Update components CHANGELOG to use "class name" phrasing
gziolo Mar 12, 2019
9ce3def
Edit Post: Use consistent "Manage Block Visibility" label
aduth Mar 12, 2019
bfc1a38
Blocks: Add unit tests for isMatchingSearchTerm
aduth Mar 12, 2019
6171177
Edit Post: Add "No Results" text for empty Manage Blocks search
aduth Mar 12, 2019
c0b9a23
Edit Post: Render Manage Blocks Visibility as checklist
aduth Mar 12, 2019
fc978e5
Edit Post: Reverse behavior of "all" visible toggle to show
aduth Mar 12, 2019
9297f8d
Components: Add mirror prop support to CheckboxControl
aduth Mar 12, 2019
e194aa0
Edit Post: Mirror checkboxes in Manage Blocks modal
aduth Mar 12, 2019
1555f58
Edit Post: Fix Firefox scroll overflow in Manage Blocks
aduth Mar 13, 2019
2d9b379
Edit Post: Render Manage Blocks group as role=group
aduth Mar 13, 2019
6798964
Checkbox margin adjustment.
mapk Mar 13, 2019
76fb031
Merge branch 'add/block-manager' of github.com:WordPress/gutenberg in…
mapk Mar 13, 2019
ac202ff
Icon colors changed to -gray-500
mapk Mar 13, 2019
a71f799
Changed title back to Block Manager based on design feedback
mapk Mar 13, 2019
3d7de09
Edit Post: Fix Block Manager "Show All" toggle checked as all checked
aduth Mar 13, 2019
6e8072a
Edit Post: Refactor Block Manager checkbox margin as precise override
aduth Mar 13, 2019
0d985a2
Revert "Edit Post: Fix Block Manager "Show All" toggle checked as all…
mapk Mar 13, 2019
dca4049
Revert "Components: Add mirror prop support to CheckboxControl"
aduth Mar 14, 2019
40c7694
Edit Post: Display block manager entries as indented checklist
aduth Mar 14, 2019
22ad54b
Edit Post: Restore Block Manager toggle label association
aduth Mar 14, 2019
0b45148
Edit Post: Adjust mobile, margin styling for mixed checkbox
aduth Mar 14, 2019
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
15 changes: 15 additions & 0 deletions docs/designers-developers/developers/data/data-core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,21 @@ Returns true if the block defines support for a feature, or false otherwise.

Whether block supports feature.

### isMatchingSearchTerm

Returns true if the block type by the given name or object value matches a
search term, or false otherwise.

*Parameters*

* state: Blocks state.
* nameOrType: Block name or type object.
* searchTerm: Search term by which to filter.

*Returns*

Wheter block type matches search term.

### hasChildBlocks

Returns a boolean indicating if a block has child blocks or not.
Expand Down
18 changes: 18 additions & 0 deletions docs/designers-developers/developers/data/data-core-edit-post.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,24 @@ Returns an action object used to toggle a plugin name flag.

* pluginName: Plugin name.

### hideBlockTypes

Returns an action object used in signalling that block types by the given
name(s) should be hidden.

*Parameters*

* blockNames: Names of block types to hide.

### showBlockTypes

Returns an action object used in signalling that block types by the given
name(s) should be shown.

*Parameters*

* blockNames: Names of block types to show.

### setAvailableMetaBoxesPerLocation

Returns an action object used in signaling
Expand Down
65 changes: 61 additions & 4 deletions packages/blocks/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,22 @@
* External dependencies
*/
import createSelector from 'rememo';
import { filter, get, includes, map, some } from 'lodash';
import { filter, get, includes, map, some, flow, deburr } from 'lodash';

/**
* Given a block name or block type object, returns the corresponding
* normalized block type object.
*
* @param {Object} state Blocks state.
* @param {(string|Object)} nameOrType Block name or type object
*
* @return {Object} Block type object.
*/
const getNormalizedBlockType = ( state, nameOrType ) => (
'string' === typeof nameOrType ?
getBlockType( state, nameOrType ) :
nameOrType
);

/**
* Returns all the available block types.
Expand Down Expand Up @@ -120,9 +135,7 @@ export const getChildBlockNames = createSelector(
* @return {?*} Block support value
*/
export const getBlockSupport = ( state, nameOrType, feature, defaultSupports ) => {
const blockType = 'string' === typeof nameOrType ?
getBlockType( state, nameOrType ) :
nameOrType;
const blockType = getNormalizedBlockType( state, nameOrType );

return get( blockType, [
'supports',
Expand All @@ -145,6 +158,50 @@ export function hasBlockSupport( state, nameOrType, feature, defaultSupports ) {
return !! getBlockSupport( state, nameOrType, feature, defaultSupports );
}

/**
* Returns true if the block type by the given name or object value matches a
* search term, or false otherwise.
*
* @param {Object} state Blocks state.
* @param {(string|Object)} nameOrType Block name or type object.
* @param {string} searchTerm Search term by which to filter.
*
* @return {Object[]} Wheter block type matches search term.
*/
export function isMatchingSearchTerm( state, nameOrType, searchTerm ) {
Copy link
Member

Choose a reason for hiding this comment

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

In the follow-up PR we should cover this new selector with basic unit tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

In the follow-up PR we should cover this new selector with basic unit tests.

Yeah, I can get those in place today.

Copy link
Member Author

Choose a reason for hiding this comment

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

In the follow-up PR we should cover this new selector with basic unit tests.

Yeah, I can get those in place today.

Added unit tests in bfc1a38.

const blockType = getNormalizedBlockType( state, nameOrType );

const getNormalizedSearchTerm = flow( [
// Disregard diacritics.
// Input: "média"
deburr,

// Lowercase.
// Input: "MEDIA"
( term ) => term.toLowerCase(),

// Strip leading and trailing whitespace.
// Input: " media "
( term ) => term.trim(),
] );

const normalizedSearchTerm = getNormalizedSearchTerm( searchTerm );

const isSearchMatch = flow( [
getNormalizedSearchTerm,
( normalizedCandidate ) => includes(
normalizedCandidate,
normalizedSearchTerm
),
] );

return (
isSearchMatch( blockType.title ) ||
some( blockType.keywords, isSearchMatch ) ||
isSearchMatch( blockType.category )
);
}

/**
* Returns a boolean indicating if a block has child blocks or not.
*
Expand Down
67 changes: 66 additions & 1 deletion packages/blocks/src/store/test/selectors.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/**
* Internal dependencies
*/
import { getChildBlockNames } from '../selectors';
import {
getChildBlockNames,
isMatchingSearchTerm,
} from '../selectors';

describe( 'selectors', () => {
describe( 'getChildBlockNames', () => {
Expand Down Expand Up @@ -134,4 +137,66 @@ describe( 'selectors', () => {
expect( getChildBlockNames( state, 'parent2' ) ).toEqual( [ 'child2' ] );
} );
} );

describe( 'isMatchingSearchTerm', () => {
const name = 'core/paragraph';
const blockType = {
title: 'Paragraph',
category: 'common',
keywords: [ 'text' ],
};

const state = {
blockTypes: {
[ name ]: blockType,
},
};

describe.each( [
Copy link
Member

Choose a reason for hiding this comment

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

This thing (https://jestjs.io/docs/en/api#describeeachtable-name-fn-timeout) has a bit hard to follow API but once you learn it is awesome 👍

I like how it keeps the readability of the describe and it interface but allows to remove some duplication.

[ 'name', name ],
[ 'block type', blockType ],
] )( 'by %s', ( label, nameOrType ) => {
it( 'should return false if not match', () => {
const result = isMatchingSearchTerm( state, nameOrType, 'Quote' );

expect( result ).toBe( false );
} );

it( 'should return true if match by title', () => {
const result = isMatchingSearchTerm( state, nameOrType, 'Paragraph' );

expect( result ).toBe( true );
} );

it( 'should return true if match ignoring case', () => {
const result = isMatchingSearchTerm( state, nameOrType, 'PARAGRAPH' );

expect( result ).toBe( true );
} );

it( 'should return true if match ignoring diacritics', () => {
const result = isMatchingSearchTerm( state, nameOrType, 'PÁRAGRAPH' );

expect( result ).toBe( true );
} );

it( 'should return true if match ignoring whitespace', () => {
const result = isMatchingSearchTerm( state, nameOrType, ' PARAGRAPH ' );

expect( result ).toBe( true );
} );

it( 'should return true if match using the keywords', () => {
const result = isMatchingSearchTerm( state, nameOrType, 'TEXT' );

expect( result ).toBe( true );
} );

it( 'should return true if match using the categories', () => {
const result = isMatchingSearchTerm( state, nameOrType, 'COMMON' );

expect( result ).toBe( true );
} );
} );
} );
} );
2 changes: 2 additions & 0 deletions packages/edit-post/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import TextEditor from '../text-editor';
import VisualEditor from '../visual-editor';
import EditorModeKeyboardShortcuts from '../keyboard-shortcuts';
import KeyboardShortcutHelpModal from '../keyboard-shortcut-help-modal';
import ManageBlocksModal from '../manage-blocks-modal';
import OptionsModal from '../options-modal';
import MetaBoxes from '../meta-boxes';
import SettingsSidebar from '../sidebar/settings-sidebar';
Expand Down Expand Up @@ -83,6 +84,7 @@ function Layout( {
<PreserveScrollInReorder />
<EditorModeKeyboardShortcuts />
<KeyboardShortcutHelpModal />
<ManageBlocksModal />
<OptionsModal />
{ ( mode === 'text' || ! isRichEditingEnabled ) && <TextEditor /> }
{ isRichEditingEnabled && mode === 'visual' && <VisualEditor /> }
Expand Down
103 changes: 103 additions & 0 deletions packages/edit-post/src/components/manage-blocks-modal/category.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* External dependencies
*/
import { without, map } from 'lodash';

/**
* WordPress dependencies
*/
import { withSelect, withDispatch } from '@wordpress/data';
import { compose, withInstanceId } from '@wordpress/compose';
import { CheckboxControl } from '@wordpress/components';

/**
* Internal dependencies
*/
import BlockTypesChecklist from './checklist';

function BlockManagerCategory( {
instanceId,
category,
blockTypes,
hiddenBlockTypes,
toggleVisible,
toggleAllVisible,
} ) {
if ( ! blockTypes.length ) {
return null;
}

const checkedBlockNames = without(
map( blockTypes, 'name' ),
...hiddenBlockTypes
);

const titleId = 'edit-post-manage-blocks-modal__category-title-' + instanceId;

const isAllChecked = checkedBlockNames.length === blockTypes.length;

let ariaChecked;
if ( isAllChecked ) {
ariaChecked = 'true';
} else if ( checkedBlockNames.length > 0 ) {
ariaChecked = 'mixed';
} else {
ariaChecked = 'false';
}

return (
<div
role="group"
aria-labelledby={ titleId }
className="edit-post-manage-blocks-modal__category"
>
<CheckboxControl
checked={ isAllChecked }
onChange={ toggleAllVisible }
className="edit-post-manage-blocks-modal__category-title"
aria-checked={ ariaChecked }
label={ <span id={ titleId }>{ category.title }</span> }
/>
<BlockTypesChecklist
blockTypes={ blockTypes }
value={ checkedBlockNames }
onItemChange={ toggleVisible }
/>
</div>
);
}

export default compose( [
withInstanceId,
withSelect( ( select ) => {
const { getPreference } = select( 'core/edit-post' );

return {
hiddenBlockTypes: getPreference( 'hiddenBlockTypes' ),
};
} ),
withDispatch( ( dispatch, ownProps ) => {
const {
showBlockTypes,
hideBlockTypes,
} = dispatch( 'core/edit-post' );

return {
toggleVisible( blockName, nextIsChecked ) {
if ( nextIsChecked ) {
showBlockTypes( blockName );
} else {
hideBlockTypes( blockName );
}
},
toggleAllVisible( nextIsChecked ) {
const blockNames = map( ownProps.blockTypes, 'name' );
if ( nextIsChecked ) {
showBlockTypes( blockNames );
} else {
hideBlockTypes( blockNames );
}
},
};
} ),
] )( BlockManagerCategory );
37 changes: 37 additions & 0 deletions packages/edit-post/src/components/manage-blocks-modal/checklist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* External dependencies
*/
import { partial } from 'lodash';

/**
* WordPress dependencies
*/
import { Fragment } from '@wordpress/element';
import { BlockIcon } from '@wordpress/block-editor';
import { CheckboxControl } from '@wordpress/components';

function BlockTypesChecklist( { blockTypes, value, onItemChange } ) {
return (
<ul className="edit-post-manage-blocks-modal__checklist">
{ blockTypes.map( ( blockType ) => (
<li
key={ blockType.name }
className="edit-post-manage-blocks-modal__checklist-item"
>
<CheckboxControl
label={ (
<Fragment>
{ blockType.title }
<BlockIcon icon={ blockType.icon } />
</Fragment>
) }
checked={ value.includes( blockType.name ) }
onChange={ partial( onItemChange, blockType.name ) }
/>
</li>
) ) }
</ul>
);
}

export default BlockTypesChecklist;
Loading