Skip to content

Commit

Permalink
Try selecting closest editable block when clicking on a disabled block (
Browse files Browse the repository at this point in the history
WordPress#60016)

* Try selecting closest editable block when clicking on a disabled block

* Remove 'Edit template' notification

* Select nearest *inner block* of Post Content

* Move to own hook in @wordpress/editor

* Revert use-block-props/index.js

* Don't select elements that are too far away

Co-authored-by: noisysocks <[email protected]>
Co-authored-by: jasmussen <[email protected]>
Co-authored-by: andrewserong <[email protected]>
Co-authored-by: jameskoster <[email protected]>
Co-authored-by: richtabor <[email protected]>
Co-authored-by: SaxonF <[email protected]>
  • Loading branch information
7 people authored and cbravobernal committed Apr 9, 2024
1 parent b80cfa8 commit eb435ea
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/**
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { useEffect, useState, useRef } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';
import { useSelect } from '@wordpress/data';
import { useEffect, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components';

Expand Down Expand Up @@ -38,73 +37,22 @@ export default function EditTemplateBlocksNotification( { contentRef } ) {
};
}, [] );

const { getNotices } = useSelect( noticesStore );

const { createInfoNotice, removeNotice } = useDispatch( noticesStore );

const [ isDialogOpen, setIsDialogOpen ] = useState( false );

const lastNoticeId = useRef( 0 );

useEffect( () => {
const handleClick = async ( event ) => {
if ( ! event.target.classList.contains( 'is-root-container' ) ) {
return;
}

const isNoticeAlreadyShowing = getNotices().some(
( notice ) => notice.id === lastNoticeId.current
);
if ( isNoticeAlreadyShowing ) {
return;
}

const { notice } = await createInfoNotice(
__( 'Edit your template to edit this block.' ),
{
isDismissible: true,
type: 'snackbar',
actions: [
{
label: __( 'Edit template' ),
onClick: () =>
onNavigateToEntityRecord( {
postId: templateId,
postType: 'wp_template',
} ),
},
],
}
);
lastNoticeId.current = notice.id;
};

const handleDblClick = ( event ) => {
if ( ! event.target.classList.contains( 'is-root-container' ) ) {
return;
}
if ( lastNoticeId.current ) {
removeNotice( lastNoticeId.current );
}
setIsDialogOpen( true );
};

const canvas = contentRef.current;
canvas?.addEventListener( 'click', handleClick );
canvas?.addEventListener( 'dblclick', handleDblClick );
return () => {
canvas?.removeEventListener( 'click', handleClick );
canvas?.removeEventListener( 'dblclick', handleDblClick );
};
}, [
lastNoticeId,
contentRef,
getNotices,
createInfoNotice,
onNavigateToEntityRecord,
templateId,
removeNotice,
] );
}, [ contentRef ] );

return (
<ConfirmDialog
Expand All @@ -119,7 +67,9 @@ export default function EditTemplateBlocksNotification( { contentRef } ) {
} }
onCancel={ () => setIsDialogOpen( false ) }
>
{ __( 'Edit your template to edit this block.' ) }
{ __(
'You’ve tried to select a block that is part of a template, which may be used on other posts and pages.'
) }
</ConfirmDialog>
);
}
4 changes: 4 additions & 0 deletions packages/editor/src/components/editor-canvas/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import PostTitle from '../post-title';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
import EditTemplateBlocksNotification from './edit-template-blocks-notification';
import useSelectNearestEditableBlock from '../../hooks/use-select-nearest-editable-block';

const {
LayoutStyle,
Expand Down Expand Up @@ -313,6 +314,9 @@ function EditorCanvas( {
useFlashEditableBlocks( {
isEnabled: renderingMode === 'template-locked',
} ),
useSelectNearestEditableBlock( {
isEnabled: renderingMode === 'template-locked',
} ),
] );

return (
Expand Down
95 changes: 95 additions & 0 deletions packages/editor/src/hooks/use-select-nearest-editable-block.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* WordPress dependencies
*/
import { useRefEffect } from '@wordpress/compose';
import { useSelect, useDispatch } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';

/**
* Internal dependencies
*/
import { unlock } from '../lock-unlock';

const DISTANCE_THRESHOLD = 500;

function clamp( value, min, max ) {
return Math.min( Math.max( value, min ), max );
}

function distanceFromRect( x, y, rect ) {
const dx = x - clamp( x, rect.left, rect.right );
const dy = y - clamp( y, rect.top, rect.bottom );
return Math.sqrt( dx * dx + dy * dy );
}

export default function useSelectNearestEditableBlock( {
isEnabled = true,
} = {} ) {
const { getEnabledClientIdsTree, getBlockName, getBlockOrder } = unlock(
useSelect( blockEditorStore )
);
const { selectBlock } = useDispatch( blockEditorStore );

return useRefEffect(
( element ) => {
if ( ! isEnabled ) {
return;
}

const selectNearestEditableBlock = ( x, y ) => {
const editableBlockClientIds =
getEnabledClientIdsTree().flatMap( ( { clientId } ) => {
const blockName = getBlockName( clientId );
if ( blockName === 'core/template-part' ) {
return [];
}
if ( blockName === 'core/post-content' ) {
const innerBlocks = getBlockOrder( clientId );
if ( innerBlocks.length ) {
return innerBlocks;
}
}
return [ clientId ];
} );

let nearestDistance = Infinity,
nearestClientId = null;

for ( const clientId of editableBlockClientIds ) {
const block = element.querySelector(
`[data-block="${ clientId }"]`
);
if ( ! block ) {
continue;
}
const rect = block.getBoundingClientRect();
const distance = distanceFromRect( x, y, rect );
if (
distance < nearestDistance &&
distance < DISTANCE_THRESHOLD
) {
nearestDistance = distance;
nearestClientId = clientId;
}
}

if ( nearestClientId ) {
selectBlock( nearestClientId );
}
};

const handleClick = ( event ) => {
const shouldSelect =
event.target === element ||
event.target.classList.contains( 'is-root-container' );
if ( shouldSelect ) {
selectNearestEditableBlock( event.clientX, event.clientY );
}
};

element.addEventListener( 'click', handleClick );
return () => element.removeEventListener( 'click', handleClick );
},
[ isEnabled ]
);
}

0 comments on commit eb435ea

Please sign in to comment.