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

Rich text core: remove dependency on block client ID #31752

Merged
merged 4 commits into from
May 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 44 additions & 0 deletions packages/block-editor/src/components/rich-text/format-edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* WordPress dependencies
*/
import { getActiveFormat, getActiveObject } from '@wordpress/rich-text';

export default function FormatEdit( {
formatTypes,
onChange,
onFocus,
value,
forwardedRef,
} ) {
return formatTypes.map( ( settings ) => {
const { name, edit: Edit } = settings;

if ( ! Edit ) {
return null;
}

const activeFormat = getActiveFormat( value, name );
const isActive = activeFormat !== undefined;
const activeObject = getActiveObject( value );
const isObjectActive =
activeObject !== undefined && activeObject.type === name;

return (
<Edit
key={ name }
isActive={ isActive }
activeAttributes={
isActive ? activeFormat.attributes || {} : {}
}
isObjectActive={ isObjectActive }
activeObjectAttributes={
isObjectActive ? activeObject.attributes || {} : {}
}
value={ value }
onChange={ onChange }
onFocus={ onFocus }
contentRef={ forwardedRef }
/>
);
} );
}
264 changes: 86 additions & 178 deletions packages/block-editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { omit } from 'lodash';
import { RawHTML, useRef, useCallback, forwardRef } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import {
pasteHandler,
children as childrenSource,
getBlockTransforms,
findTransform,
Expand All @@ -23,16 +22,12 @@ import {
__unstableIsEmptyLine as isEmptyLine,
insert,
__unstableInsertLineSeparator as insertLineSeparator,
create,
replace,
split,
__UNSTABLE_LINE_SEPARATOR as LINE_SEPARATOR,
toHTMLString,
slice,
isCollapsed,
removeFormat,
} from '@wordpress/rich-text';
import deprecated from '@wordpress/deprecated';
import { isURL } from '@wordpress/url';
import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes';

/**
Expand All @@ -41,17 +36,15 @@ import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes';
import { useBlockEditorAutocompleteProps } from '../autocomplete';
import { useBlockEditContext } from '../block-edit';
import { RemoveBrowserShortcuts } from './remove-browser-shortcuts';
import { filePasteHandler } from './file-paste-handler';
import FormatToolbarContainer from './format-toolbar-container';
import { store as blockEditorStore } from '../../store';
import { useUndoAutomaticChange } from './use-undo-automatic-change';
import { useCaretInFormat } from './use-caret-in-format';
import {
addActiveFormats,
getMultilineTag,
getAllowedFormats,
isShortcode,
} from './utils';
import { usePasteHandler } from './use-paste-handler';
import { useInputRules } from './use-input-rules';
import { useFormatTypes } from './use-format-types';
import FormatEdit from './format-edit';
import { getMultilineTag, getAllowedFormats } from './utils';

function RichTextWrapper(
{
Expand Down Expand Up @@ -240,192 +233,76 @@ function RichTextWrapper(
[ onReplace, onSplit, multilineTag, onSplitMiddle ]
);

const onPaste = useCallback(
( {
value,
onChange,
html,
plainText,
isInternal,
files,
activeFormats,
} ) => {
// If the data comes from a rich text instance, we can directly use it
// without filtering the data. The filters are only meant for externally
// pasted content and remove inline styles.
if ( isInternal ) {
const pastedValue = create( {
html,
multilineTag,
multilineWrapperTags:
multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined,
preserveWhiteSpace,
} );
addActiveFormats( pastedValue, activeFormats );
onChange( insert( value, pastedValue ) );
return;
}

if ( pastePlainText ) {
onChange( insert( value, create( { text: plainText } ) ) );
return;
}

// Only process file if no HTML is present.
// Note: a pasted file may have the URL as plain text.
if ( files && files.length && ! html ) {
const content = pasteHandler( {
HTML: filePasteHandler( files ),
mode: 'BLOCKS',
tagName,
preserveWhiteSpace,
} );

// Allows us to ask for this information when we get a report.
// eslint-disable-next-line no-console
window.console.log( 'Received items:\n\n', files );

if ( onReplace && isEmpty( value ) ) {
onReplace( content );
} else {
splitValue( value, content );
}

return;
}

let mode = onReplace && onSplit ? 'AUTO' : 'INLINE';

// Force the blocks mode when the user is pasting
// on a new line & the content resembles a shortcode.
// Otherwise it's going to be detected as inline
// and the shortcode won't be replaced.
if (
mode === 'AUTO' &&
isEmpty( value ) &&
isShortcode( plainText )
) {
mode = 'BLOCKS';
}

if (
__unstableEmbedURLOnPaste &&
isEmpty( value ) &&
isURL( plainText.trim() )
) {
mode = 'BLOCKS';
}

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

if ( typeof content === 'string' ) {
let valueToInsert = create( { html: content } );

addActiveFormats( valueToInsert, activeFormats );

// If the content should be multiline, we should process text
// separated by a line break as separate lines.
if ( multilineTag ) {
valueToInsert = replace(
valueToInsert,
/\n+/g,
LINE_SEPARATOR
);
}

onChange( insert( value, valueToInsert ) );
} else if ( content.length > 0 ) {
if ( onReplace && isEmpty( value ) ) {
onReplace( content, content.length - 1, -1 );
} else {
splitValue( value, content );
}
}
},
[
tagName,
onReplace,
onSplit,
splitValue,
__unstableEmbedURLOnPaste,
multilineTag,
preserveWhiteSpace,
pastePlainText,
]
);

const inputRule = useCallback(
( value, valueToFormat ) => {
if ( ! onReplace ) {
return;
}

const { start, text } = value;
const characterBefore = text.slice( start - 1, start );

// The character right before the caret must be a plain space.
if ( characterBefore !== ' ' ) {
return;
}
const {
formatTypes,
prepareHandlers,
valueHandlers,
changeHandlers,
dependencies,
} = useFormatTypes( {
clientId,
identifier,
withoutInteractiveFormatting,
allowedFormats: adjustedAllowedFormats,
} );

const trimmedTextBefore = text.slice( 0, start ).trim();
const prefixTransforms = getBlockTransforms( 'from' ).filter(
( { type } ) => type === 'prefix'
);
const transformation = findTransform(
prefixTransforms,
( { prefix } ) => {
return trimmedTextBefore === prefix;
}
);
function addEditorOnlyFormats( value ) {
return valueHandlers.reduce(
( accumulator, fn ) => fn( accumulator, value.text ),
value.formats
);
}

if ( ! transformation ) {
return;
function removeEditorOnlyFormats( value ) {
formatTypes.forEach( ( formatType ) => {
// Remove formats created by prepareEditableTree, because they are editor only.
if ( formatType.__experimentalCreatePrepareEditableTree ) {
value = removeFormat(
value,
formatType.name,
0,
value.text.length
);
}
} );

const content = valueToFormat( slice( value, start, text.length ) );
const block = transformation.transform( content );
return value.formats;
}

onReplace( [ block ] );
__unstableMarkAutomaticChange();
},
[ onReplace, __unstableMarkAutomaticChange ]
);
function addInvisibleFormats( value ) {
return prepareHandlers.reduce(
( accumulator, fn ) => fn( accumulator, value.text ),
value.formats
);
}

const {
value,
onChange,
onFocus,
ref: richTextRef,
hasActiveFormats,
removeEditorOnlyFormats,
children: richTextChildren,
} = useRichText( {
clientId,
identifier,
value: adjustedValue,
onChange: adjustedOnChange,
onChange( html, { __unstableFormats, __unstableText } ) {
adjustedOnChange( html );
Object.values( changeHandlers ).forEach( ( changeHandler ) => {
changeHandler( __unstableFormats, __unstableText );
} );
},
selectionStart,
selectionEnd,
onSelectionChange,
placeholder,
allowedFormats: adjustedAllowedFormats,
withoutInteractiveFormatting,
onPaste,
__unstableIsSelected: isSelected,
__unstableInputRule: inputRule,
__unstableMultilineTag: multilineTag,
__unstableOnCreateUndoLevel: __unstableMarkLastChangeAsPersistent,
__unstableMarkAutomaticChange,
__unstableDisableFormats: disableFormats,
preserveWhiteSpace,
__unstableAllowPrefixTransformations,
__unstableDependencies: dependencies,
__unstableAfterParse: addEditorOnlyFormats,
__unstableBeforeSerialize: removeEditorOnlyFormats,
__unstableAddInvisibleFormats: addInvisibleFormats,
} );
const autocompleteProps = useBlockEditorAutocompleteProps( {
onReplace,
Expand All @@ -446,7 +323,8 @@ function RichTextWrapper(
if ( event.keyCode === ENTER ) {
event.preventDefault();

const _value = removeEditorOnlyFormats( value );
const _value = { ...value };
_value.formats = removeEditorOnlyFormats( value );
const canSplit = onReplace && onSplit;

if ( onReplace ) {
Expand Down Expand Up @@ -528,7 +406,15 @@ function RichTextWrapper(
{ children && children( { value, onChange, onFocus } ) }
{ isSelected && <RemoveBrowserShortcuts /> }
{ isSelected && autocompleteProps.children }
{ isSelected && richTextChildren }
{ isSelected && (
<FormatEdit
value={ value }
onChange={ onChange }
onFocus={ onFocus }
formatTypes={ formatTypes }
forwardedRef={ anchorRef }
/>
) }
{ isSelected && hasFormats && (
<FormatToolbarContainer
inline={ inlineToolbar }
Expand All @@ -546,7 +432,29 @@ function RichTextWrapper(
autocompleteProps.ref,
props.ref,
richTextRef,
useInputRules( {
value,
onChange,
__unstableAllowPrefixTransformations,
formatTypes,
onReplace,
} ),
useUndoAutomaticChange(),
usePasteHandler( {
isSelected,
disableFormats,
onChange,
value,
formatTypes,
tagName,
onReplace,
onSplit,
splitValue,
__unstableEmbedURLOnPaste,
multilineTag,
preserveWhiteSpace,
pastePlainText,
} ),
anchorRef,
forwardedRef,
] ) }
Expand Down
Loading