Skip to content

Commit

Permalink
List: copy wrapper when multi selecting items (#59460)
Browse files Browse the repository at this point in the history
Co-authored-by: ellatrix <ellatrix@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: annezazu <annezazu@git.wordpress.org>
  • Loading branch information
4 people authored and youknowriad committed Mar 4, 2024
1 parent dda0fec commit 3c4dad4
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 77 deletions.
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { useDispatch, useSelect } from '@wordpress/data';
import { useDispatch, useRegistry, useSelect } from '@wordpress/data';
import { useRefEffect } from '@wordpress/compose';

/**
Expand All @@ -15,6 +15,7 @@ import { getPasteBlocks, setClipboardBlocks } from '../writing-flow/utils';
// This hook borrows from useClipboardHandler in ../writing-flow/use-clipboard-handler.js
// and adds behaviour for the list view, while skipping partial selection.
export default function useClipboardHandler( { selectBlock } ) {
const registry = useRegistry();
const {
getBlockOrder,
getBlockRootClientId,
Expand Down Expand Up @@ -106,7 +107,7 @@ export default function useClipboardHandler( { selectBlock } ) {

notifyCopy( event.type, selectedBlockClientIds );
const blocks = getBlocksByClientId( selectedBlockClientIds );
setClipboardBlocks( event, blocks );
setClipboardBlocks( event, blocks, registry );
}

if ( event.type === 'cut' ) {
Expand Down
Expand Up @@ -5,7 +5,7 @@ import {
documentHasSelection,
documentHasUncollapsedSelection,
} from '@wordpress/dom';
import { useDispatch, useSelect } from '@wordpress/data';
import { useDispatch, useRegistry, useSelect } from '@wordpress/data';
import { useRefEffect } from '@wordpress/compose';

/**
Expand All @@ -16,6 +16,7 @@ import { useNotifyCopy } from '../../utils/use-notify-copy';
import { getPasteBlocks, setClipboardBlocks } from './utils';

export default function useClipboardHandler() {
const registry = useRegistry();
const {
getBlocksByClientId,
getSelectedBlockClientIds,
Expand Down Expand Up @@ -104,7 +105,7 @@ export default function useClipboardHandler() {
blocks = [ head, ...inBetweenBlocks, tail ];
}

setClipboardBlocks( event, blocks );
setClipboardBlocks( event, blocks, registry );
}
}

Expand Down
47 changes: 31 additions & 16 deletions packages/block-editor/src/components/writing-flow/utils.js
Expand Up @@ -8,36 +8,51 @@ import {
pasteHandler,
findTransform,
getBlockTransforms,
store as blocksStore,
} from '@wordpress/blocks';

/**
* Internal dependencies
*/
import { getPasteEventData } from '../../utils/pasting';
import { store as blockEditorStore } from '../../store';

export const requiresWrapperOnCopy = Symbol( 'requiresWrapperOnCopy' );

/**
* Sets the clipboard data for the provided blocks, with both HTML and plain
* text representations.
*
* @param {ClipboardEvent} event Clipboard event.
* @param {WPBlock[]} blocks Blocks to set as clipboard data.
* @param {ClipboardEvent} event Clipboard event.
* @param {WPBlock[]} blocks Blocks to set as clipboard data.
* @param {Object} registry The registry to select from.
*/
export function setClipboardBlocks( event, blocks ) {
export function setClipboardBlocks( event, blocks, registry ) {
let _blocks = blocks;
const wrapperBlockName = event.clipboardData.getData(
'__unstableWrapperBlockName'
);

if ( wrapperBlockName ) {
_blocks = createBlock(
wrapperBlockName,
JSON.parse(
event.clipboardData.getData(
'__unstableWrapperBlockAttributes'
)
),
_blocks
);
const [ firstBlock ] = blocks;

if ( firstBlock ) {
const firstBlockType = registry
.select( blocksStore )
.getBlockType( firstBlock.name );

if ( firstBlockType[ requiresWrapperOnCopy ] ) {
const { getBlockRootClientId, getBlockName, getBlockAttributes } =
registry.select( blockEditorStore );
const wrapperBlockClientId = getBlockRootClientId(
firstBlock.clientId
);
const wrapperBlockName = getBlockName( wrapperBlockClientId );

if ( wrapperBlockName ) {
_blocks = createBlock(
wrapperBlockName,
getBlockAttributes( wrapperBlockClientId ),
_blocks
);
}
}
}

const serialized = serialize( _blocks );
Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/src/private-apis.js
Expand Up @@ -27,6 +27,7 @@ import { ExperimentalBlockCanvas } from './components/block-canvas';
import { getDuotoneFilter } from './components/duotone/utils';
import { useFlashEditableBlocks } from './components/use-flash-editable-blocks';
import { selectBlockPatternsKey } from './store/private-keys';
import { requiresWrapperOnCopy } from './components/writing-flow/utils';
import { PrivateRichText } from './components/rich-text/';

/**
Expand Down Expand Up @@ -59,5 +60,6 @@ lock( privateApis, {
usesContextKey,
useFlashEditableBlocks,
selectBlockPatternsKey,
requiresWrapperOnCopy,
PrivateRichText,
} );
3 changes: 1 addition & 2 deletions packages/block-library/src/list-item/edit.js
Expand Up @@ -29,7 +29,6 @@ import {
useOutdentListItem,
useSplit,
useMerge,
useCopy,
} from './hooks';
import { convertToListItems } from './utils';

Expand Down Expand Up @@ -79,7 +78,7 @@ export default function ListItemEdit( {
mergeBlocks,
} ) {
const { placeholder, content } = attributes;
const blockProps = useBlockProps( { ref: useCopy( clientId ) } );
const blockProps = useBlockProps();
const innerBlocksProps = useInnerBlocksProps( blockProps, {
renderAppender: false,
__unstableDisableDropZone: true,
Expand Down
1 change: 0 additions & 1 deletion packages/block-library/src/list-item/hooks/index.js
Expand Up @@ -4,4 +4,3 @@ export { default as useEnter } from './use-enter';
export { default as useSpace } from './use-space';
export { default as useSplit } from './use-split';
export { default as useMerge } from './use-merge';
export { default as useCopy } from './use-copy';
38 changes: 0 additions & 38 deletions packages/block-library/src/list-item/hooks/use-copy.js

This file was deleted.

3 changes: 3 additions & 0 deletions packages/block-library/src/list-item/index.js
Expand Up @@ -2,6 +2,7 @@
* WordPress dependencies
*/
import { listItem as icon } from '@wordpress/icons';
import { privateApis } from '@wordpress/block-editor';

/**
* Internal dependencies
Expand All @@ -11,6 +12,7 @@ import metadata from './block.json';
import edit from './edit';
import save from './save';
import transforms from './transforms';
import { unlock } from '../lock-unlock';

const { name } = metadata;

Expand All @@ -27,6 +29,7 @@ export const settings = {
};
},
transforms,
[ unlock( privateApis ).requiresWrapperOnCopy ]: true,
};

export const init = () => initBlock( { name, metadata, settings } );
34 changes: 18 additions & 16 deletions packages/e2e-test-utils-playwright/src/page-utils/press-keys.ts
Expand Up @@ -55,18 +55,26 @@ async function emulateClipboard( page: Page, type: 'copy' | 'cut' | 'paste' ) {
const canvasDoc =
// @ts-ignore
document.activeElement?.contentDocument ?? document;
const clipboardDataTransfer = new DataTransfer();
const event = new ClipboardEvent( _type, {
bubbles: true,
cancelable: true,
clipboardData: new DataTransfer(),
} );

if ( ! event.clipboardData ) {
throw new Error( 'ClipboardEvent.clipboardData is null' );
}

if ( _type === 'paste' ) {
clipboardDataTransfer.setData(
event.clipboardData.setData(
'text/plain',
_clipboardData[ 'text/plain' ]
);
clipboardDataTransfer.setData(
event.clipboardData.setData(
'text/html',
_clipboardData[ 'text/html' ]
);
clipboardDataTransfer.setData(
event.clipboardData.setData(
'rich-text',
_clipboardData[ 'rich-text' ]
);
Expand All @@ -85,22 +93,16 @@ async function emulateClipboard( page: Page, type: 'copy' | 'cut' | 'paste' ) {
)
.join( '' );
}
clipboardDataTransfer.setData( 'text/plain', plainText );
clipboardDataTransfer.setData( 'text/html', html );
event.clipboardData.setData( 'text/plain', plainText );
event.clipboardData.setData( 'text/html', html );
}

canvasDoc.activeElement?.dispatchEvent(
new ClipboardEvent( _type, {
bubbles: true,
cancelable: true,
clipboardData: clipboardDataTransfer,
} )
);
canvasDoc.activeElement.dispatchEvent( event );

return {
'text/plain': clipboardDataTransfer.getData( 'text/plain' ),
'text/html': clipboardDataTransfer.getData( 'text/html' ),
'rich-text': clipboardDataTransfer.getData( 'rich-text' ),
'text/plain': event.clipboardData.getData( 'text/plain' ),
'text/html': event.clipboardData.getData( 'text/html' ),
'rich-text': event.clipboardData.getData( 'rich-text' ),
};
},
[ type, clipboardDataHolder ] as const
Expand Down
30 changes: 30 additions & 0 deletions test/e2e/specs/editor/blocks/list.spec.js
Expand Up @@ -8,6 +8,36 @@ test.describe( 'List (@firefox)', () => {
await admin.createNewPost();
} );

test( 'can be copied from multi selection', async ( {
editor,
page,
pageUtils,
} ) => {
await editor.insertBlock( { name: 'core/list' } );
await page.keyboard.type( 'one' );
await page.keyboard.press( 'Enter' );
await page.keyboard.type( 'two' );
await pageUtils.pressKeys( 'primary+a' );
await pageUtils.pressKeys( 'primary+a' );
await pageUtils.pressKeys( 'primary+c' );
await editor.insertBlock( { name: 'core/paragraph' } );
await pageUtils.pressKeys( 'primary+v' );

const copied = `<!-- wp:list -->
<ul><!-- wp:list-item -->
<li>one</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>two</li>
<!-- /wp:list-item --></ul>
<!-- /wp:list -->`;

expect( await editor.getEditedPostContent() ).toBe(
copied + '\n\n' + copied
);
} );

test( 'can be created by using an asterisk at the start of a paragraph block', async ( {
editor,
page,
Expand Down

0 comments on commit 3c4dad4

Please sign in to comment.