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 API: Add supports object to block attributes to configure copy support #38643

Open
wants to merge 32 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
135c788
Add internal experimentalRole for attributes
stacimc Sep 10, 2021
7fde6d4
Add tests
stacimc Sep 10, 2021
22ce576
Refactor removeAttributesByRole to take role param and return block
stacimc Sep 21, 2021
f0a5e68
Remove internal attrs when copying via keyboard command
stacimc Sep 21, 2021
44e1659
Remove internal attrs when copying via CopyMenuItem
stacimc Sep 21, 2021
730f2e2
Do not alter attributes of block being duplicated
stacimc Sep 22, 2021
46596a5
Use `__experimentalCloneSanitizedBlock` in copy handlers
stacimc Sep 22, 2021
8256770
Use internal attribute for widgetId
stacimc Sep 22, 2021
250e4a8
Update tests for __experimentalRemoveAttributesByRole
stacimc Sep 22, 2021
b5fabc6
Update packages/widgets/package.json
ntsekouras Sep 23, 2021
3f5c24a
Move attribute registration to function called in widget initialization
stacimc Sep 23, 2021
4f83c53
Corrected docstring
stacimc Sep 23, 2021
1d4b93a
Check for existence of widget before moving sidebars
stacimc Oct 11, 2021
bc065eb
Do not separately sanitize attributes in cloneSanitizedBlock
stacimc Oct 11, 2021
9a72f51
Remove __experimentalSanitizeBlockAttributes
stacimc Oct 13, 2021
0d6388a
Remove unnecessary attribute sanitization in ServerSideRender component
stacimc Oct 13, 2021
a41909a
Remove removeAttributesByRole
stacimc Feb 1, 2022
a25b7cd
Remove experimentalCloneSanitizeBlock and make retaining internal att…
stacimc Feb 2, 2022
2b109ab
Add option to serialize to retain internal attrs
stacimc Feb 7, 2022
54989c6
Refactor to use a 'supports' property instead of '__experimentalRole'
stacimc Feb 8, 2022
ae5a3b5
Do not pass widgetId to /block-renderer
stacimc Feb 8, 2022
fea6451
Update test
stacimc Feb 8, 2022
42eefe8
Update use of 'internal' terminology in docs
stacimc Feb 8, 2022
c547f6b
Mark supports object as experimental
stacimc Feb 11, 2022
ad1749d
Update option name, mark as experimental
stacimc Feb 11, 2022
c5d3eae
Add TODO to remove special handling for internalWidgetId when possible
stacimc Feb 11, 2022
8a2c987
Correctly exclude noncopyable attrs in duplicate
stacimc Feb 23, 2022
085000f
Use __experimentalExcludeAttributes option
stacimc Feb 24, 2022
ccba197
Use new serializer options
stacimc Feb 24, 2022
94efe0c
Remove logic from serializer in favor of using cloneBlock in copy han…
stacimc Feb 25, 2022
aca0d35
Clarify arguments for cloneBlock
stacimc Mar 24, 2022
191c4f2
Rename argument
stacimc Mar 25, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { moreVertical } from '@wordpress/icons';
import { Children, cloneElement, useCallback } from '@wordpress/element';
import { serialize } from '@wordpress/blocks';
import { cloneBlock, serialize } from '@wordpress/blocks';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
import { useCopyToClipboard } from '@wordpress/compose';

Expand All @@ -33,7 +33,14 @@ const POPOVER_PROPS = {
};

function CopyMenuItem( { blocks, onCopy } ) {
const ref = useCopyToClipboard( () => serialize( blocks ), onCopy );
const ref = useCopyToClipboard( () => {
blocks = blocks.map( ( block ) =>
cloneBlock( block, {}, null, {
__experimentalRequiredAttributeSupports: [ 'copy' ],
} )
);
return serialize( blocks );
}, onCopy );
return <MenuItem ref={ ref }>{ __( 'Copy' ) }</MenuItem>;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { __experimentalGetBlockAttributesNamesByRole as getBlockAttributesNamesByRole } from '@wordpress/blocks';
import { __experimentalFilterBlockAttributes as filterBlockAttributes } from '@wordpress/blocks';

/**
* Try to find a matching block by a block's name in a provided
Expand Down Expand Up @@ -46,7 +46,9 @@ export const getMatchingBlockByName = (
* @return {Object} The block's attributes to retain.
*/
export const getRetainedBlockAttributes = ( name, attributes ) => {
const contentAttributes = getBlockAttributesNamesByRole( name, 'content' );
const contentAttributes = filterBlockAttributes( name, {
__experimentalRole: 'content',
} );
if ( ! contentAttributes?.length ) return attributes;

return contentAttributes.reduce( ( _accumulator, attribute ) => {
Expand Down
10 changes: 9 additions & 1 deletion packages/block-editor/src/components/copy-handler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import { useCallback } from '@wordpress/element';
import {
cloneBlock,
serialize,
pasteHandler,
store as blocksStore,
Expand Down Expand Up @@ -121,7 +122,14 @@ export function useClipboardHandler() {
flashBlock( selectedBlockClientIds[ 0 ] );
}
notifyCopy( event.type, selectedBlockClientIds );
const blocks = getBlocksByClientId( selectedBlockClientIds );
let blocks = getBlocksByClientId( selectedBlockClientIds );
if ( event.type === 'copy' ) {
blocks = blocks.map( ( block ) =>
cloneBlock( block, {}, null, {
__experimentalRequiredAttributeSupports: [ 'copy' ],
} )
);
}
const serialized = serialize( blocks );

event.clipboardData.setData( 'text/plain', serialized );
Expand Down
5 changes: 3 additions & 2 deletions packages/block-editor/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { castArray, findKey, first, isObject, last, some } from 'lodash';
*/
import {
cloneBlock,
__experimentalCloneSanitizedBlock,
createBlock,
doBlocksMatchTemplate,
getBlockType,
Expand Down Expand Up @@ -1182,7 +1181,9 @@ export const duplicateBlocks = ( clientIds, updateSelection = true ) => ( {
last( castArray( clientIds ) )
);
const clonedBlocks = blocks.map( ( block ) =>
__experimentalCloneSanitizedBlock( block )
cloneBlock( block, {}, null, {
__experimentalRequiredAttributeSupports: [ 'copy' ],
} )
);
dispatch.insertBlocks(
clonedBlocks,
Expand Down
4 changes: 3 additions & 1 deletion packages/blocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,15 @@ In the random image block above, we've given the `alt` attribute of the image a
### cloneBlock

Given a block object, returns a copy of the block object,
optionally merging new attributes and/or replacing its inner blocks.
optionally merging new attributes, replacing its inner blocks, and/or
filtering out attributes which do not have copy support.

_Parameters_

- _block_ `Object`: Block instance.
- _mergeAttributes_ `Object`: Block attributes.
- _newInnerBlocks_ `?Array`: Nested blocks.
- _\_\_experimentalOptions_ `?WPBlockCloneOptions`: Cloning options.

_Returns_

Expand Down
115 changes: 70 additions & 45 deletions packages/blocks/src/api/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
isFunction,
isEmpty,
map,
reduce,
omit,
} from 'lodash';

/**
Expand All @@ -32,7 +34,7 @@ import {
} from './registration';
import {
normalizeBlockType,
__experimentalSanitizeBlockAttributes,
__experimentalFilterBlockAttributes,
} from './utils';

/**
Expand All @@ -45,9 +47,37 @@ import {
* @return {Object} Block object.
*/
export function createBlock( name, attributes = {}, innerBlocks = [] ) {
const sanitizedAttributes = __experimentalSanitizeBlockAttributes(
name,
attributes
// Get the type definition associated with a registered block.
const blockType = getBlockType( name );

if ( undefined === blockType ) {
throw new Error( `Block type '${ name }' is not registered.` );
}

const sanitizedAttributes = reduce(
blockType.attributes,
( accumulator, schema, key ) => {
const value = attributes[ key ];

if ( undefined !== value ) {
accumulator[ key ] = value;
} else if ( schema.hasOwnProperty( 'default' ) ) {
accumulator[ key ] = schema.default;
}

if ( [ 'node', 'children' ].indexOf( schema.source ) !== -1 ) {
// Ensure value passed is always an array, which we're expecting in
// the RichText component to handle the deprecated value.
if ( typeof accumulator[ key ] === 'string' ) {
accumulator[ key ] = [ accumulator[ key ] ];
} else if ( ! Array.isArray( accumulator[ key ] ) ) {
accumulator[ key ] = [];
}
}

return accumulator;
},
{}
);

const clientId = uuid();
Expand Down Expand Up @@ -94,62 +124,57 @@ export function createBlocksFromInnerBlocksTemplate(
}

/**
* Given a block object, returns a copy of the block object while sanitizing its attributes,
* optionally merging new attributes and/or replacing its inner blocks.
* @typedef {Object} WPBlockCloneOptions Cloning Options.
*
* @property {Array} __experimentalRequiredAttributeSupports Attributes missing these support keys will be excluded from the cloned block.
*/

/**
* Given a block object, returns a copy of the block object,
* optionally merging new attributes, replacing its inner blocks, and/or
* filtering out attributes which do not have copy support.
*
* @param {Object} block Block instance.
* @param {Object} mergeAttributes Block attributes.
* @param {?Array} newInnerBlocks Nested blocks.
* @param {Object} block Block instance.
* @param {Object} mergeAttributes Block attributes.
* @param {?Array} newInnerBlocks Nested blocks.
* @param {?WPBlockCloneOptions} __experimentalOptions Cloning options.
*
* @return {Object} A cloned block.
*/
export function __experimentalCloneSanitizedBlock(
export function cloneBlock(
block,
mergeAttributes = {},
newInnerBlocks
newInnerBlocks,
__experimentalOptions = {}
) {
const {
__experimentalRequiredAttributeSupports: requiredSupportKeys,
} = __experimentalOptions;
const clientId = uuid();

const sanitizedAttributes = __experimentalSanitizeBlockAttributes(
block.name,
{
...block.attributes,
...mergeAttributes,
}
);

return {
...block,
clientId,
attributes: sanitizedAttributes,
innerBlocks:
newInnerBlocks ||
block.innerBlocks.map( ( innerBlock ) =>
__experimentalCloneSanitizedBlock( innerBlock )
),
let attributes = {
...block.attributes,
...mergeAttributes,
};
}

/**
* Given a block object, returns a copy of the block object,
* optionally merging new attributes and/or replacing its inner blocks.
*
* @param {Object} block Block instance.
* @param {Object} mergeAttributes Block attributes.
* @param {?Array} newInnerBlocks Nested blocks.
*
* @return {Object} A cloned block.
*/
export function cloneBlock( block, mergeAttributes = {}, newInnerBlocks ) {
const clientId = uuid();
if ( requiredSupportKeys ) {
// Exclude attributes that do not have the required supports.
const attributesFilter = {
__experimentalSupports: requiredSupportKeys.reduce(
( acc, supportKey ) => ( { ...acc, [ supportKey ]: false } ),
{}
),
};
attributes = omit(
attributes,
__experimentalFilterBlockAttributes( block.name, attributesFilter )
);
}

return {
...block,
clientId,
attributes: {
...block.attributes,
...mergeAttributes,
},
attributes,
innerBlocks:
newInnerBlocks ||
block.innerBlocks.map( ( innerBlock ) => cloneBlock( innerBlock ) ),
Expand Down
4 changes: 1 addition & 3 deletions packages/blocks/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export {
createBlock,
createBlocksFromInnerBlocksTemplate,
cloneBlock,
__experimentalCloneSanitizedBlock,
getPossibleBlockTransformations,
switchToBlockType,
getBlockTransforms,
Expand Down Expand Up @@ -142,8 +141,7 @@ export {
isValidIcon,
getBlockLabel as __experimentalGetBlockLabel,
getAccessibleBlockLabel as __experimentalGetAccessibleBlockLabel,
__experimentalSanitizeBlockAttributes,
__experimentalGetBlockAttributesNamesByRole,
__experimentalFilterBlockAttributes,
} from './utils';

// Templates are, in a general sense, a basic collection of block nodes with any
Expand Down
1 change: 1 addition & 0 deletions packages/blocks/src/api/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ export function serializeBlock( block, { isInnerBlocks = false } = {} ) {

const blockType = getBlockType( blockName );
const saveAttributes = getCommentAttributes( blockType, block.attributes );

return getCommentDelimitedContent( blockName, saveAttributes, saveContent );
}

Expand Down
Loading