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

Keyboard & Right-Click Menu Copy + Paste #3083

Merged
merged 113 commits into from
Sep 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
95f6d9d
Start adding pasteHandler.
miina Aug 23, 2019
9876f47
Add PropTypes and missing props.
miina Aug 23, 2019
cdbe412
Add placeholder for ensuring allowed blocks.
miina Aug 23, 2019
ebebf5a
Merge remote-tracking branch 'origin/develop' into add/2996-keyboard-…
miina Aug 27, 2019
101e54a
Allow copying only allowed blocks.
miina Aug 27, 2019
3b525b4
Ensure only allowed child blocks can be pasted.
miina Aug 28, 2019
c9a72dd
Start adding right click menu.
miina Aug 28, 2019
f78df6b
Add missing file.
miina Aug 28, 2019
b2d2050
Add duplicating block.
miina Aug 28, 2019
786fbc6
Refactor code.
miina Aug 28, 2019
b8485e4
Improve style.
miina Aug 28, 2019
25e64a4
Adjust popover position.
miina Aug 28, 2019
f59c566
Add Copy button.
miina Aug 29, 2019
b7024d2
Add Cut button.
miina Aug 29, 2019
6261728
Add PropTypes.
miina Aug 29, 2019
c49bfd1
Add workaround for Paste button.
miina Aug 29, 2019
390ddb2
Use store instead of state for storing copied markup.
miina Aug 29, 2019
25e3c48
Ask permissions for pasting from clipboard.
miina Aug 29, 2019
fdb5c79
Merge develop
miina Aug 29, 2019
48f5192
Make sure navigator.clipboard exists before using it.
miina Aug 29, 2019
c429067
Calculate better menu position.
miina Aug 29, 2019
d8277e7
Add check for clicking outside of Popover.
miina Aug 29, 2019
cc9091a
Focus on the first element when opening the menu.
miina Aug 29, 2019
9a9c306
Adjust Popover style.
miina Aug 29, 2019
0ade232
Merge remote-tracking branch 'origin/develop' into add/2996-keyboard-…
miina Aug 30, 2019
7485cd7
Add skeleton for tests.
miina Aug 30, 2019
6fd93ff
Merge remote-tracking branch 'origin/develop' into add/2996-keyboard-…
miina Aug 30, 2019
0615d6f
Replace using bind.
miina Aug 30, 2019
10e1ad3
Add copyHandler to a separate component.
miina Aug 30, 2019
02fb9a9
Move onPaste to CopyPasteHandler.
miina Aug 30, 2019
9bc38db
Remove redundant cut handler.
miina Aug 30, 2019
ca104e2
Add tests for reducer.
miina Aug 30, 2019
b859204
Remove redundant functions.
miina Aug 30, 2019
de01a79
Merge branch 'develop' into add/2996-keyboard-copy-paste
swissspidy Aug 30, 2019
c22e103
Start adding e2e tests.
miina Aug 30, 2019
65e3bb8
Merge branch 'add/2996-keyboard-copy-paste' of github.com:ampproject/…
miina Aug 30, 2019
3581c54
Remove unused property.
miina Aug 30, 2019
1685efc
Add e2e tests.
miina Aug 30, 2019
b678cc2
Move outside click checker separately.
miina Aug 30, 2019
a037424
Add test for allowed blocks to paste.
miina Aug 30, 2019
ab4f03a
Clear store's 'clipboard' after pasting.
miina Aug 30, 2019
c9fac8e
Merge remote-tracking branch 'origin/develop' into add/2996-keyboard-…
miina Sep 2, 2019
6ba8639
Remove unused proptypes.
miina Sep 2, 2019
e00868f
Merge branch 'develop' into add/2996-keyboard-copy-paste
swissspidy Sep 2, 2019
c247c19
Access navigator via window global
swissspidy Sep 2, 2019
dd31fae
Merge remote-tracking branch 'origin/develop' into add/2996-keyboard-…
miina Sep 2, 2019
2f57c67
Merge branch 'add/2996-keyboard-copy-paste' of github.com:ampproject/…
miina Sep 2, 2019
51d28c0
Improve handling the right click.
miina Sep 2, 2019
cd37782
Merge remote-tracking branch 'origin/develop' into add/2996-keyboard-…
miina Sep 2, 2019
3c1f7c4
Merge branch 'develop' into add/2996-keyboard-copy-paste
swissspidy Sep 2, 2019
e14d673
Merge remote-tracking branch 'origin/develop' into add/2996-keyboard-…
miina Sep 3, 2019
66504ea
Resolve conflicts.
miina Sep 3, 2019
0573366
Make Media Inserter test more reliable.
miina Sep 3, 2019
1b1cfae
Ensure readText exists before using it.
miina Sep 3, 2019
d5f4250
Allow blocks to be copied many times.
spacedmonkey Sep 9, 2019
e51712c
Merge branch 'develop' into add/2996-keyboard-copy-paste
swissspidy Sep 9, 2019
0381a47
Fix pasting text elements will now paste them when the cusor is.
spacedmonkey Sep 10, 2019
1dc9448
Use templates
spacedmonkey Sep 10, 2019
e308d19
Change variable names.
spacedmonkey Sep 10, 2019
eafabb2
Merge pull request #3220 from ampproject/fix/paste-custor-position
spacedmonkey Sep 10, 2019
c32d70d
Start adding pasteHandler.
miina Aug 23, 2019
c912501
Add PropTypes and missing props.
miina Aug 23, 2019
22337c3
Add placeholder for ensuring allowed blocks.
miina Aug 23, 2019
48edf46
Allow copying only allowed blocks.
miina Aug 27, 2019
332c6e9
Ensure only allowed child blocks can be pasted.
miina Aug 28, 2019
3fe5db9
Start adding right click menu.
miina Aug 28, 2019
4dc041f
Add missing file.
miina Aug 28, 2019
bbb396b
Add duplicating block.
miina Aug 28, 2019
c1caaa0
Refactor code.
miina Aug 28, 2019
2881bd7
Improve style.
miina Aug 28, 2019
5659a11
Adjust popover position.
miina Aug 28, 2019
8f05779
Add Copy button.
miina Aug 29, 2019
92d45f6
Add Cut button.
miina Aug 29, 2019
80caef9
Add PropTypes.
miina Aug 29, 2019
85a2a35
Add workaround for Paste button.
miina Aug 29, 2019
ee15cad
Use store instead of state for storing copied markup.
miina Aug 29, 2019
a2b1824
Ask permissions for pasting from clipboard.
miina Aug 29, 2019
8a34659
Make sure navigator.clipboard exists before using it.
miina Aug 29, 2019
c0d4347
Calculate better menu position.
miina Aug 29, 2019
9aad346
Add check for clicking outside of Popover.
miina Aug 29, 2019
3a2cb18
Focus on the first element when opening the menu.
miina Aug 29, 2019
45db452
Adjust Popover style.
miina Aug 29, 2019
907657b
Add skeleton for tests.
miina Aug 30, 2019
009808e
Replace using bind.
miina Aug 30, 2019
3790202
Add copyHandler to a separate component.
miina Aug 30, 2019
5223f01
Move onPaste to CopyPasteHandler.
miina Aug 30, 2019
daaac00
Remove redundant cut handler.
miina Aug 30, 2019
7847551
Add tests for reducer.
miina Aug 30, 2019
e789fc5
Remove redundant functions.
miina Aug 30, 2019
d445ada
Start adding e2e tests.
miina Aug 30, 2019
8e7c227
Add e2e tests.
miina Aug 30, 2019
8cdf513
Move outside click checker separately.
miina Aug 30, 2019
2717f10
Add test for allowed blocks to paste.
miina Aug 30, 2019
bf95136
Clear store's 'clipboard' after pasting.
miina Aug 30, 2019
de69cea
Remove unused proptypes.
miina Sep 2, 2019
7072446
Access navigator via window global
swissspidy Sep 2, 2019
7232136
Improve handling the right click.
miina Sep 2, 2019
2a32e87
Make Media Inserter test more reliable.
miina Sep 3, 2019
eda171b
Ensure readText exists before using it.
miina Sep 3, 2019
ddce164
Allow blocks to be copied many times.
spacedmonkey Sep 9, 2019
66953f1
Fix pasting text elements will now paste them when the cusor is.
spacedmonkey Sep 10, 2019
15c7731
Use templates
spacedmonkey Sep 10, 2019
c4f3b2b
Change variable names.
spacedmonkey Sep 10, 2019
3cff1e2
Merge remote-tracking branch 'origin/add/2996-keyboard-copy-paste' in…
spacedmonkey Sep 10, 2019
a5eac06
Remove repeated helper function.
spacedmonkey Sep 10, 2019
8dbf87b
Only place blocks that are movable next to cusor.
spacedmonkey Sep 10, 2019
32573e7
Use class prop
spacedmonkey Sep 11, 2019
a425d0b
Uncommnet out code.
spacedmonkey Sep 11, 2019
6152350
Merge branch 'develop' into add/2996-keyboard-copy-paste
spacedmonkey Sep 11, 2019
593086a
Return tests.
spacedmonkey Sep 11, 2019
77c7a7d
Wait for image and video blocks to be inserted.
spacedmonkey Sep 12, 2019
a07e10e
Merge branch 'develop' into add/2996-keyboard-copy-paste
spacedmonkey Sep 12, 2019
d9aee22
Fix lint
spacedmonkey Sep 12, 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
128 changes: 128 additions & 0 deletions assets/src/stories-editor/blocks/amp-story-page/copy-paste-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';

/**
* WordPress dependencies
*/
import { pasteHandler, serialize } from '@wordpress/blocks';
import { documentHasSelection } from '@wordpress/dom';
import { withDispatch, useSelect, useDispatch } from '@wordpress/data';

/**
* Internal dependencies
*/
import { ensureAllowedBlocksOnPaste } from '../../helpers';

function CopyPasteHandler( { children, onCopy, clientId, isSelected } ) {
const {
isFirstPage,
canUserUseUnfilteredHTML,
} = useSelect(
( select ) => {
const {
getBlockOrder,
getSettings,
} = select( 'core/block-editor' );
const { __experimentalCanUserUseUnfilteredHTML } = getSettings();
return {
isFirstPage: getBlockOrder().indexOf( clientId ) === 0,
canUserUseUnfilteredHTML: __experimentalCanUserUseUnfilteredHTML,
};
}, [ clientId ]
);

const { insertBlocks } = useDispatch( 'core/block-editor' );

const onPaste = ( event ) => {
// Ignore if the Page is not the selected page.
if ( ! isSelected ) {
return;
}
const clipboardData = event.clipboardData;

let plainText = '';
let html = '';

// IE11 only supports `Text` as an argument for `getData` and will
// otherwise throw an invalid argument error, so we try the standard
// arguments first, then fallback to `Text` if they fail.
try {
plainText = clipboardData.getData( 'text/plain' );
html = clipboardData.getData( 'text/html' );
} catch ( error1 ) {
try {
html = clipboardData.getData( 'Text' );
} catch ( error2 ) {
// Some browsers like UC Browser paste plain text by default and
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this error state, return a message to the user to explain that something went wrong?

// don't support clipboardData at all, so allow default
// behaviour.
return;
}
}

event.preventDefault();

const mode = 'BLOCKS';

const content = pasteHandler( {
HTML: html,
plainText,
mode,
canUserUseUnfilteredHTML,
} );

if ( content.length > 0 ) {
insertBlocks( ensureAllowedBlocksOnPaste( content, clientId, isFirstPage ), null, clientId );
}
};

return (
<div onCopy={ onCopy } onPaste={ onPaste }>
{ children }
</div>
);
}

CopyPasteHandler.propTypes = {
children: PropTypes.object.isRequired,
clientId: PropTypes.string.isRequired,
isSelected: PropTypes.bool.isRequired,
onCopy: PropTypes.func.isRequired,
};

export default withDispatch( ( dispatch, ownProps, { select } ) => {
const {
getBlocksByClientId,
getSelectedBlockClientIds,
hasMultiSelection,
} = select( 'core/block-editor' );
const { clearCopiedMarkup, setCopiedMarkup } = dispatch( 'amp/story' );

/**
* Copy handler for ensuring that the store's copiedMarkup is in sync with what's actually in clipBoard.
* If it's not a block that's being copied, let's clear the copiedMarkup.
* Otherwise, let's set the copied markup.
*/
const onCopy = () => {
const selectedBlockClientIds = getSelectedBlockClientIds();

if ( selectedBlockClientIds.length === 0 ) {
clearCopiedMarkup();
return;
}

// Let native copy behaviour take over in input fields.
if ( ! hasMultiSelection() && documentHasSelection() ) {
clearCopiedMarkup();
return;
}
const serialized = serialize( getBlocksByClientId( selectedBlockClientIds ) );
setCopiedMarkup( serialized );
};

return {
onCopy,
};
} )( CopyPasteHandler );
54 changes: 36 additions & 18 deletions assets/src/stories-editor/blocks/amp-story-page/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
import {
withSelect,
withDispatch,
dispatch,
} from '@wordpress/data';
import { compose } from '@wordpress/compose';

Expand All @@ -52,6 +51,8 @@ import {
isVideoSizeExcessive,
} from '../../../common/helpers';

import CopyPasteHandler from './copy-paste-handler';

import {
ALLOWED_CHILD_BLOCKS,
ALLOWED_MOVABLE_BLOCKS,
Expand Down Expand Up @@ -210,7 +211,16 @@ class PageEdit extends Component {
}

render() { // eslint-disable-line complexity
const { attributes, media, setAttributes, totalAnimationDuration, allowedBlocks, allowedBackgroundMediaTypes } = this.props;
const {
attributes,
clientId,
isSelected,
media,
setAttributes,
totalAnimationDuration,
allowedBlocks,
allowedBackgroundMediaTypes,
} = this.props;

const {
mediaId,
Expand Down Expand Up @@ -441,20 +451,24 @@ class PageEdit extends Component {
</PanelBody>
<AnimationSettings clientId={ this.props.clientId } />
</InspectorControls>
<div style={ style }>
{ /* todo: show poster image as background-image instead */ }
{ VIDEO_BACKGROUND_TYPE === mediaType && media && (
<div className="editor-amp-story-page-video-wrap">
<video autoPlay muted loop className="editor-amp-story-page-video" poster={ poster } ref={ this.videoPlayer }>
<source src={ mediaUrl } type={ media.mime_type } />
</video>
</div>
) }
{ backgroundColors.length > 0 && (
<div style={ overlayStyle } />
) }
<InnerBlocks allowedBlocks={ allowedBlocks } />
</div>
<CopyPasteHandler clientId={ clientId } isSelected={ isSelected }>
<div
style={ style }
>
{ /* todo: show poster image as background-image instead */ }
{ VIDEO_BACKGROUND_TYPE === mediaType && media && (
<div className="editor-amp-story-page-video-wrap">
<video autoPlay muted loop className="editor-amp-story-page-video" poster={ poster } ref={ this.videoPlayer }>
<source src={ mediaUrl } type={ media.mime_type } />
</video>
</div>
) }
{ backgroundColors.length > 0 && (
<div style={ overlayStyle } />
) }
<InnerBlocks allowedBlocks={ allowedBlocks } />
</div>
</CopyPasteHandler>
</>
);
}
Expand All @@ -478,6 +492,7 @@ PageEdit.propTypes = {
autoAdvanceAfterDuration: PropTypes.number,
mediaAlt: PropTypes.string,
} ).isRequired,
isSelected: PropTypes.bool.isRequired,
setAttributes: PropTypes.func.isRequired,
media: PropTypes.object,
allowedBlocks: PropTypes.arrayOf( PropTypes.string ).isRequired,
Expand All @@ -491,15 +506,18 @@ PageEdit.propTypes = {
};

export default compose(
withDispatch( () => {
withDispatch( ( dispatch ) => {
const { moveBlockToPosition } = dispatch( 'core/block-editor' );
return {
moveBlockToPosition,
};
} ),
withSelect( ( select, { clientId, attributes } ) => {
const { getMedia } = select( 'core' );
const { getBlockOrder, getBlockRootClientId } = select( 'core/block-editor' );
const {
getBlockOrder,
getBlockRootClientId,
} = select( 'core/block-editor' );
const { getAnimatedBlocks, getSettings } = select( 'amp/story' );

const isFirstPage = getBlockOrder().indexOf( clientId ) === 0;
Expand Down
4 changes: 2 additions & 2 deletions assets/src/stories-editor/components/block-mover/draggable.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ import {
STORY_PAGE_INNER_HEIGHT,
} from '../../constants';

const { Image } = window;
const { Image, navigator } = window;

const cloneWrapperClass = 'components-draggable__clone';

const isChromeUA = ( ) => /Chrome/i.test( window.navigator.userAgent );
const isChromeUA = ( ) => /Chrome/i.test( navigator.userAgent );
const documentHasIframes = ( ) => [ ...document.getElementById( 'editor' ).querySelectorAll( 'iframe' ) ].length > 0;

class Draggable extends Component {
Expand Down
2 changes: 1 addition & 1 deletion assets/src/stories-editor/components/draggable-text.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ DraggableText.propTypes = {
isSelected: PropTypes.bool.isRequired,
toggleIsEditing: PropTypes.func.isRequired,
toggleOverlay: PropTypes.func.isRequired,
text: PropTypes.string.isRequired,
text: PropTypes.string,
textStyle: PropTypes.shape( {
color: PropTypes.string,
fontSize: PropTypes.string,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* WordPress dependencies
*/
import { withSelect } from '@wordpress/data';
import { createHigherOrderComponent } from '@wordpress/compose';
import { render } from '@wordpress/element';
/**
* Internal dependencies
*/
import { ALLOWED_CHILD_BLOCKS } from '../../constants';
import { getBlockDOMNode, getPercentageFromPixels } from '../../helpers';
import { RightClickMenu } from '../';

const applyWithSelect = withSelect( ( select, props ) => {
const { isReordering, getCopiedMarkup, getCurrentPage } = select( 'amp/story' );
const {
getSelectedBlockClientIds,
hasMultiSelection,
} = select( 'core/block-editor' );

const { name } = props;

const onContextMenu = ( event ) => {
const selectedBlockClientIds = getSelectedBlockClientIds();

if ( selectedBlockClientIds.length === 0 ) {
return;
}
// If nothing is in the saved markup, use the default behavior.
if ( 'amp/amp-story-page' === name && ! getCopiedMarkup().length ) {
return;
}

// Let's ignore if some text has been selected.
const selectedText = window.getSelection().toString();
// Let's ignore multi-selection for now.
if ( hasMultiSelection() || selectedText.length ) {
return;
}

const editLayout = document.querySelector( '.edit-post-layout' );
if ( ! document.getElementById( 'amp-story-right-click-menu' ) ) {
const menuWrapper = document.createElement( 'div' );
menuWrapper.id = 'amp-story-right-click-menu';

editLayout.appendChild( menuWrapper );
}

// Calculate the position to display the right click menu.
const wrapperDimensions = editLayout.getBoundingClientRect();
const toolBar = document.querySelector( '.edit-post-header' );

// If Toolbar is available then consider that as well.
let toolBarHeight = 0;
if ( toolBar ) {
toolBarHeight = toolBar.clientHeight;
}
const relativePositionX = event.clientX - wrapperDimensions.left;
const relativePositionY = event.clientY - wrapperDimensions.top - toolBarHeight;
const clientId = getCurrentPage();

let insidePercentageY = 0;
let insidePercentageX = 0;

const page = getBlockDOMNode( clientId );
if ( page ) {
const pagePostions = page.getBoundingClientRect();
const insideY = event.clientY - pagePostions.top;
const insideX = event.clientX - pagePostions.left;
insidePercentageY = getPercentageFromPixels( 'y', insideY );
insidePercentageX = getPercentageFromPixels( 'x', insideX );
}

render(
<RightClickMenu clientIds={ selectedBlockClientIds } clientX={ relativePositionX } clientY={ relativePositionY } insidePercentageX={ insidePercentageX } insidePercentageY={ insidePercentageY } />,
document.getElementById( 'amp-story-right-click-menu' )
);

event.preventDefault();
};

return {
onContextMenu,
isReordering: isReordering(),
};
} );

/**
* Higher-order component that adds right click handler to each inner block.
*
* @return {Function} Higher-order component.
*/
export default createHigherOrderComponent(
( BlockEdit ) => {
return applyWithSelect( ( props ) => {
const { name, onContextMenu, isReordering } = props;
const isPageBlock = 'amp/amp-story-page' === name;

// Add for page block and inner blocks.
if ( ! isPageBlock && ! ALLOWED_CHILD_BLOCKS.includes( name ) ) {
return <BlockEdit { ...props } />;
}

// Not relevant for reordering.
if ( isReordering ) {
return <BlockEdit { ...props } />;
}

return (
<div onContextMenu={ onContextMenu }>
<BlockEdit { ...props } />
</div>
);
} );
},
'withRightClickHandler'
);
2 changes: 2 additions & 0 deletions assets/src/stories-editor/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export { default as StoryBlockMover } from './block-mover';
export { default as PageInserter } from './page-inserter';
export { default as FontFamilyPicker } from './font-family-picker';
export { default as RotatableBox } from './rotatable-box';
export { default as RightClickMenu } from './right-click-menu';
export { default as PreviewPicker } from './preview-picker';
export { default as Inserter } from './inserter';
export { default as MediaInserter } from './media-inserter';
Expand All @@ -25,6 +26,7 @@ export { default as withAttributes } from './higher-order/with-attributes';
export { default as withBlockName } from './higher-order/with-block-name';
export { default as withHasSelectedInnerBlock } from './higher-order/with-has-selected-inner-block';
export { default as withPageNumber } from './higher-order/with-page-number';
export { default as withRightClickHandler } from './higher-order/with-right-click-handler';
export { default as withStoryFeaturedImageNotice } from './higher-order/with-story-featured-image-notice';
export { default as withEditFeaturedImage } from './with-edit-featured-image';
export { default as withCustomVideoBlockEdit } from './with-custom-video-block-edit';
Expand Down
Loading