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

Block Editor: Tips feature #22109

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
36 changes: 25 additions & 11 deletions packages/block-editor/src/components/block-card/index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
/**
* WordPress dependencies
*/
import { Tip } from '@wordpress/components';

/**
* Internal dependencies
*/
import BlockIcon from '../block-icon';

function BlockCard( { blockType } ) {
function BlockCard( { blockType, tip } ) {
return (
<div className="block-editor-block-card">
<BlockIcon icon={ blockType.icon } showColors />
<div className="block-editor-block-card__content">
<h2 className="block-editor-block-card__title">
{ blockType.title }
</h2>
<span className="block-editor-block-card__description">
{ blockType.description }
</span>
<>
<div className="block-editor-block-card">
<BlockIcon icon={ blockType.icon } showColors />
<div className="block-editor-block-card__content">
<h2 className="block-editor-block-card__title">
{ blockType.title }
</h2>
<span className="block-editor-block-card__description">
{ blockType.description }
</span>
</div>
</div>
</div>
{ tip && (
Copy link
Contributor

Choose a reason for hiding this comment

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

Adding this here means potentially, the tip could show up in the inspector too. Did you really mean to add it to the BlockCard or more to the InserterPreviewPanel?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added it as an example of showing the scope of this approach, where it isn't limited only to the block inserter menu. There a handful of issues about Tips. Here, we'd like to show a block tip if its defined. This PR tackles it as well, allowing us registering tips by block type:

import { dispatch } from '@wordpress/data';

// Register Tips.
const { __experimentalRegisterBlockTip } = dispatch( 'core/block-editor' );
const tips = [
	__( 'Add alternative text to your images to make them more accessible.' ),
	__( 'Use a Cover block to add text on top of your image.' ),
	__( 'To place two images side by side, convert them to a Gallery.' ),
];

tips.forEach( ( desc ) => __experimentalRegisterBlockTip('core/image', desc ) );

<div className="block-editor-block-card">
<div className="block-editor-block-card__tip">
<Tip>{ tip }</Tip>
</div>
</div>
) }
</>
);
}

Expand Down
4 changes: 4 additions & 0 deletions packages/block-editor/src/components/block-card/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
width: 36px;
}

.block-editor-block-card__tip {
margin-top: -7px;
}

.block-editor-block-card__content {
flex-grow: 1;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/block-editor/src/components/inserter/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ function InserterMenu( {
</div>
{ showInserterHelpPanel && (
<div className="block-editor-inserter__tips">
<Tips />
<Tips filterValue={ filterValue } />
</div>
) }
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getBlockType,
} from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { select } from '@wordpress/data';
Copy link
Contributor

Choose a reason for hiding this comment

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

the usage of the global 'select' or 'dispatch' in Gutenberg should be limited as much as possible because they are singletons and not aware of the parent registries (nested contexts). They are also not reactive. So prefer useSelect and useDispatch instead.


/**
* Internal dependencies
Expand All @@ -17,6 +18,8 @@ import BlockPreview from '../block-preview';

function InserterPreviewPanel( { item } ) {
const hoveredItemBlockType = getBlockType( item.name );
const { __experimentalGetBlockTipsByType } = select( 'core/block-editor' );

return (
<div className="block-editor-inserter__menu-preview-panel">
<div className="block-editor-inserter__preview">
Expand Down Expand Up @@ -50,7 +53,12 @@ function InserterPreviewPanel( { item } ) {
</div>
) }
</div>
{ ! isReusableBlock( item ) && <BlockCard blockType={ item } /> }
{ ! isReusableBlock( item ) && (
<BlockCard
blockType={ item }
tip={ __experimentalGetBlockTipsByType( item.name, true ) }
/>
) }
</div>
);
}
Expand Down
10 changes: 9 additions & 1 deletion packages/block-editor/src/components/inserter/tips.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@

Copy link
Contributor

Choose a reason for hiding this comment

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

useless empty line :P

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { createInterpolateElement, useState } from '@wordpress/element';
import { Tip } from '@wordpress/components';
import { select } from '@wordpress/data';

const globalTips = [
createInterpolateElement(
Expand All @@ -28,7 +30,13 @@ const globalTips = [
__( "Change a block's type by pressing the block icon on the toolbar." ),
];

function Tips() {
function Tips( { filterValue } ) {
// Return a contextual tip when it's appropriate.
const contextualTip = select( 'core/block-editor' ).__experimentalGetBlockInserterTipsByContext( filterValue, true );
if ( contextualTip ) {
return <Tip>{ contextualTip }</Tip>;
}

const [ randomIndex ] = useState(
// Disable Reason: I'm not generating an HTML id.
// eslint-disable-next-line no-restricted-syntax
Expand Down
32 changes: 32 additions & 0 deletions packages/block-editor/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1010,3 +1010,35 @@ export function toggleBlockHighlight( clientId, isHighlighted ) {
isHighlighted,
};
}

export function __experimentalRegisterTip(
scope,
context,
keywords,
description
) {
return {
type: 'REGISTER_TIP',
scope,
context,
keywords,
description,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The more I think about this, the more I lean towards a dedicated package to register and retrieve tips across screens (and not in block-editor directly). A package similar to the keyboard-shortcuts one.

I'd like @aduth's opinion here too if possible.

It would also be cool to clarify the meaning of each argument (maybe using typing).
I assume:

  • scope means the tips "location" (inserter here for example)
  • context I don't know what it is
  • keywords, potential keywords to filter the tips
  • description (or content) is the actual content of the tip

Should we use an object in order to have a more extensible API?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The more I think about this, the more I lean towards a dedicated package to register and retrieve tips across screens (and not in block-editor directly). A package similar to the keyboard-shortcuts one.

Sounds good to me too as long as we agree that the Redux approach could fit well for the Tips feature.
Glad to move it to a dedicated package.

Should we use an object in order to have a more extensible API?

Probably, yes. Does it mean we could add arbitrary data into the state tree? Or maybe we could an extra field in order to store additional data.

context I don't know what it is

It's a good example of additional data required only for some tips. In this case, I used it as a way to define tip context in the block inserter menu. Its values could be CSS, header, theme, plugin and color. Not totally needed to be honest. We can get rid of it.

Copy link
Member

Choose a reason for hiding this comment

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

Could the @wordpress/notices package serve as prior art for this sort of requirement? Notices are somewhat similar in being data which represents a text-like representation somewhere in the page. Notices were even modeled to support multiple "groupings" / "areas" where they could be assigned (context), though we don't currently leverage it as far as I'm aware.


export function __experimentalRegisterBlockInserterTip(
context,
keywords,
description
) {
return __experimentalRegisterTip(
'blockInserter',
context,
keywords,
description
);
}

export function __experimentalRegisterBlockTip( type, description ) {
return __experimentalRegisterTip( type, null, null, description );
}
26 changes: 26 additions & 0 deletions packages/block-editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import {
identity,
difference,
omitBy,
uniq,
} from 'lodash';

/**
* WordPress dependencies
*/
import { combineReducers } from '@wordpress/data';
import { isReusableBlock } from '@wordpress/blocks';

/**
* Internal dependencies
*/
Expand Down Expand Up @@ -1479,6 +1481,29 @@ export function highlightedBlock( state, action ) {
return state;
}

export function tips( state, action ) {
const { scope, context, keywords, description } = action;

switch ( action.type ) {
case 'REGISTER_TIP':
const tipsByScope = state && state[ scope ] ? state[ scope ] : [];

return {
...state,
[ scope ]: [
...tipsByScope,
{
context,
keywords: uniq( keywords ),
description,
},
],
};
}

return state;
}

export default combineReducers( {
blocks,
isTyping,
Expand All @@ -1499,4 +1524,5 @@ export default combineReducers( {
isNavigationMode,
automaticChangeStatus,
highlightedBlock,
tips,
} );
53 changes: 53 additions & 0 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {
some,
find,
filter,
lowerCase,
uniq,
deburr,
} from 'lodash';
import createSelector from 'rememo';

Expand Down Expand Up @@ -1633,3 +1636,53 @@ export function didAutomaticChange( state ) {
export function isBlockHighlighted( state, clientId ) {
return state.highlightedBlock === clientId;
}

function getArrayIndex( array, random = true ) {
// eslint-disable-next-line no-restricted-syntax
return random ? Math.floor( Math.random() * array.length ) : 0;
}

export function __experimentalGetBlockInserterTipsByContext(
state,
searchTerm,
random = false
) {
if ( ! searchTerm ) {
return;
}

const tips = get( state, [ 'tips', 'blockInserter' ], EMPTY_ARRAY );
if ( ! tips.length ) {
return;
}

const normalizedSearchTerm = deburr( lowerCase( searchTerm ) ).replace(
/^\//,
''
);

const foundTips = filter(
tips,
( { keywords } ) =>
filter( keywords, ( keyword ) =>
includes( normalizedSearchTerm, keyword )
).length
);

if ( ! foundTips.length ) {
return;
}

const index = getArrayIndex( foundTips, random );
return get( foundTips, [ index, 'description' ] );
}

export function __experimentalGetBlockTipsByType( state, type, random = false ) {
const tips = get( state, [ 'tips', type ], EMPTY_ARRAY );
if ( ! tips.length ) {
return null;
}

const index = getArrayIndex( tips, random );
return get( tips, [ index, 'description' ] );
}
11 changes: 11 additions & 0 deletions packages/block-library/src/image/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import { __, _x } from '@wordpress/i18n';
import { image as icon } from '@wordpress/icons';
import { dispatch } from '@wordpress/data';

/**
* Internal dependencies
Expand All @@ -15,6 +16,16 @@ import transforms from './transforms';

const { name } = metadata;

// Register Tip.
const { __experimentalRegisterBlockTip } = dispatch( 'core/block-editor' );
const tips = [
__( 'Add alternative text to your images to make them more accessible.' ),
__( 'Use a Cover block to add text on top of your image.' ),
__( 'To place two images side by side, convert them to a Gallery.' ),
];

tips.forEach( ( desc ) => __experimentalRegisterBlockTip( name, desc ) );

export { metadata, name };

export const settings = {
Expand Down
10 changes: 10 additions & 0 deletions packages/block-library/src/video/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import { video as icon } from '@wordpress/icons';
import { dispatch } from '@wordpress/data';

/**
* Internal dependencies
Expand All @@ -14,6 +15,15 @@ import transforms from './transforms';

const { name } = metadata;

// Register Tip.
const { __experimentalRegisterBlockTip } = dispatch( 'core/block-editor' );
__experimentalRegisterBlockTip(
name,
__(
'The video block accepts uploads in the MP4, M4V, WebM, OGV, WMV, and FLV formats.'
)
);

export { metadata, name };

export const settings = {
Expand Down
14 changes: 13 additions & 1 deletion packages/edit-post/src/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import memize from 'memize';
import { size, map, without } from 'lodash';
import { size, map, without, each } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -28,6 +28,7 @@ import preventEventDiscovery from './prevent-event-discovery';
import Layout from './components/layout';
import EditorInitialization from './components/editor-initialization';
import EditPostSettings from './components/edit-post-settings';
import inserterContextualTips from './utils/tips';

class Editor extends Component {
constructor() {
Expand All @@ -36,6 +37,9 @@ class Editor extends Component {
this.getEditorSettings = memize( this.getEditorSettings, {
maxSize: 1,
} );

// Register Block Inserter Tips.
each( inserterContextualTips, this.props.registerBlockInserterTip );
}

getEditorSettings(
Expand Down Expand Up @@ -167,8 +171,16 @@ export default compose( [
} ),
withDispatch( ( dispatch ) => {
const { updatePreferredStyleVariations } = dispatch( 'core/edit-post' );
const { __experimentalRegisterBlockInserterTip } = dispatch(
'core/block-editor'
);

const registerBlockInserterTip = ( args ) =>
__experimentalRegisterBlockInserterTip( ...args );

return {
updatePreferredStyleVariations,
registerBlockInserterTip,
};
} ),
] )( Editor );
Loading