Skip to content

Commit

Permalink
Rich text core: remove dependency on block client ID and other block …
Browse files Browse the repository at this point in the history
…specific behaviour (#31752)
  • Loading branch information
ellatrix authored May 12, 2021
1 parent 4b0f0f0 commit f0d6fb8
Show file tree
Hide file tree
Showing 8 changed files with 615 additions and 416 deletions.
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

0 comments on commit f0d6fb8

Please sign in to comment.