Skip to content

Commit

Permalink
Universal edit placeholder
Browse files Browse the repository at this point in the history
  • Loading branch information
jsnajdr committed Mar 6, 2024
1 parent 67862ad commit b007e3c
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 126 deletions.
3 changes: 2 additions & 1 deletion packages/block-editor/src/autocompleters/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ function createBlockCompleter() {
name,
initialAttributes,
innerBlocks,
template,
syncStatus,
content,
} = inserterItem;
Expand All @@ -136,7 +137,7 @@ function createBlockCompleter() {
name,
initialAttributes,
createBlocksFromInnerBlocksTemplate(
innerBlocks
innerBlocks || template
)
),
};
Expand Down
85 changes: 83 additions & 2 deletions packages/block-editor/src/components/block-edit/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ import {
getBlockType,
getBlockEdit,
} from '@wordpress/blocks';
import { useContext, useMemo } from '@wordpress/element';
import { AsyncModeProvider, useDispatch, useSelect } from '@wordpress/data';
import { useContext, useMemo, useRef } from '@wordpress/element';

/**
* Internal dependencies
*/
import BlockContext from '../block-context';
import RichText from '../rich-text';
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../lock-unlock';

/**
* Default value used for blocks which do not define their own context needs,
Expand All @@ -30,6 +34,83 @@ import BlockContext from '../block-context';
*/
const DEFAULT_BLOCK_CONTEXT = {};

function useSelectedChildBlock( clientId ) {
return useSelect(
( select ) => {
const {
getSelectionStart,
isBlockSelected,
hasSelectedInnerBlock,
getBlockAttributes,
getBlockName,
} = select( blockEditorStore );

const isSelected =
isBlockSelected( clientId ) ||
hasSelectedInnerBlock( clientId, true );

if ( ! isSelected ) {
return {
selClientId: null,
selBlockName: null,
selContent: undefined,
};
}

const sClientId = getSelectionStart().clientId;
return {
selClientId: sClientId,
selBlockName: getBlockName( sClientId ),
selContent: getBlockAttributes( sClientId ).content,
};
},
[ clientId ]
);
}
function FallbackRichEdit( { clientId } ) {
const ref = useRef();
const { selClientId, selBlockName, selContent } =
useSelectedChildBlock( clientId );
const { updateBlockAttributes, replaceEdit } = unlock(
useDispatch( blockEditorStore )
);

if ( ! selClientId ) {
return null;
}

const onChange = ( value ) =>
updateBlockAttributes( [ selClientId ], { content: value } );

const onReplace = ( blocks, indexToSelect, initialPos ) =>
replaceEdit( selClientId, blocks, indexToSelect, initialPos );

return (
<div className="block-placeholder">
<RichText
ref={ ref }
identifier="content"
clientId={ selClientId }
blockName={ selBlockName }
value={ selContent }
onChange={ onChange }
onReplace={ onReplace }
__unstableAllowPrefixTransformations={
selBlockName === 'core/paragraph'
}
/>
</div>
);
}

function FallbackEdit( { clientId } ) {
return (
<AsyncModeProvider value={ false }>
<FallbackRichEdit clientId={ clientId } />
</AsyncModeProvider>
);
}

const Edit = ( props ) => {
const { name } = props;
const blockType = getBlockType( name );
Expand All @@ -39,7 +120,7 @@ const Edit = ( props ) => {
return null;
}

return <Component { ...props } />;
return <Component FallbackEdit={ FallbackEdit } { ...props } />;
};

const EditWithFilters = withFilters( 'editor.BlockEdit' )( Edit );
Expand Down
128 changes: 64 additions & 64 deletions packages/block-editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export function RichTextWrapper(
tagName = 'div',
value: adjustedValue = '',
onChange: adjustedOnChange,
clientId: originalClientId,
blockName: originalBlockName,
isSelected: originalIsSelected,
multiline,
inlineToolbar,
Expand Down Expand Up @@ -116,20 +118,18 @@ export function RichTextWrapper(
forwardedRef
) {
props = removeNativeProps( props );

const anchorRef = useRef();
const context = useBlockEditContext();
const { clientId, isSelected: isBlockSelected, name: blockName } = context;
const blockBindings = context[ blockBindingsKey ];
const clientId = originalClientId ?? context.clientId;
const blockName = originalBlockName ?? context.blockName;

const selector = ( select ) => {
// Avoid subscribing to the block editor store if the block is not
// selected.
if ( ! isBlockSelected ) {
const { isBlockSelected, getSelectionStart, getSelectionEnd } =
select( blockEditorStore );

if ( ! isBlockSelected( clientId ) ) {
return { isSelected: false };
}

const { getSelectionStart, getSelectionEnd } =
select( blockEditorStore );
const selectionStart = getSelectionStart();
const selectionEnd = getSelectionEnd();

Expand All @@ -140,8 +140,9 @@ export function RichTextWrapper(
selectionStart.clientId === clientId &&
selectionEnd.clientId === clientId &&
selectionStart.attributeKey === identifier;
} else if ( originalIsSelected ) {
isSelected = selectionStart.clientId === clientId;
} else {
isSelected =
originalIsSelected && selectionStart.clientId === clientId;
}

return {
Expand All @@ -154,9 +155,9 @@ export function RichTextWrapper(
clientId,
identifier,
originalIsSelected,
isBlockSelected,
] );

const blockBindings = context[ blockBindingsKey ];
const disableBoundBlocks = useSelect(
( select ) => {
// Disable Rich Text editing if block bindings specify that.
Expand Down Expand Up @@ -333,11 +334,61 @@ export function RichTextWrapper(

const keyboardShortcuts = useRef( new Set() );
const inputEvents = useRef( new Set() );
const anchorRef = useRef();

function onFocus() {
anchorRef.current?.focus();
}

const textRef = useMergeRefs( [
// Rich text ref must be first because its focus listener
// must be set up before any other ref calls .focus() on
// mount.
richTextRef,
forwardedRef,
autocompleteProps.ref,
props.ref,
useBeforeInputRules( { value, onChange } ),
useInputRules( {
getValue,
onChange,
__unstableAllowPrefixTransformations,
formatTypes,
onReplace,
selectionChange,
} ),
useInsertReplacementText(),
useRemoveBrowserShortcuts(),
useShortcuts( keyboardShortcuts ),
useInputEvents( inputEvents ),
useUndoAutomaticChange(),
usePasteHandler( {
isSelected,
disableFormats,
onChange,
value,
formatTypes,
tagName,
onReplace,
onSplit,
__unstableEmbedURLOnPaste,
pastePlainText,
} ),
useDelete( { value, onMerge, onRemove } ),
useEnter( {
removeEditorOnlyFormats,
value,
onReplace,
onSplit,
onChange,
disableLineBreaks,
onSplitAtEnd,
onSplitAtDoubleLineEnd,
} ),
useFirefoxCompat(),
anchorRef,
] );

const TagName = tagName;
return (
<>
Expand Down Expand Up @@ -373,58 +424,7 @@ export function RichTextWrapper(
aria-readonly={ shouldDisableEditing }
{ ...props }
{ ...autocompleteProps }
ref={ useMergeRefs( [
// Rich text ref must be first because its focus listener
// must be set up before any other ref calls .focus() on
// mount.
richTextRef,
forwardedRef,
autocompleteProps.ref,
props.ref,
useBeforeInputRules( { value, onChange } ),
useInputRules( {
getValue,
onChange,
__unstableAllowPrefixTransformations,
formatTypes,
onReplace,
selectionChange,
} ),
useInsertReplacementText(),
useRemoveBrowserShortcuts(),
useShortcuts( keyboardShortcuts ),
useInputEvents( inputEvents ),
useUndoAutomaticChange(),
usePasteHandler( {
isSelected,
disableFormats,
onChange,
value,
formatTypes,
tagName,
onReplace,
onSplit,
__unstableEmbedURLOnPaste,
pastePlainText,
} ),
useDelete( {
value,
onMerge,
onRemove,
} ),
useEnter( {
removeEditorOnlyFormats,
value,
onReplace,
onSplit,
onChange,
disableLineBreaks,
onSplitAtEnd,
onSplitAtDoubleLineEnd,
} ),
useFirefoxCompat(),
anchorRef,
] ) }
ref={ textRef }
contentEditable={ ! shouldDisableEditing }
suppressContentEditableWarning
className={ classnames(
Expand Down
Loading

0 comments on commit b007e3c

Please sign in to comment.