Fix up rich text editor initial selection and add blocks (#35286)

* Fix double click toolbar behavior

* Fix initial block selection on editor load

* Add placeholder option to RichTextEditor

* Add image and video blocks

* Set toolbar height

* Allow inserter to be shown

* Allow media uploads in rich text editor

* Add changelog entries

* Fix media upload

* Check for existence of selected blocks before checking length

* Pass blocks to avoid race in detecting initially empty blocks
This commit is contained in:
Joshua T Flowers 2022-10-31 14:36:54 -07:00 committed by GitHub
parent 66370c823f
commit e1ebabba29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 90 additions and 39 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: tweak
Fix up initial block selection in RichTextEditor and add media blocks

View File

@ -3,8 +3,8 @@
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { useInstanceId } from '@wordpress/compose';
import { BlockInstance, createBlock } from '@wordpress/blocks';
import { createElement, useEffect } from '@wordpress/element';
import { createBlock } from '@wordpress/blocks';
import {
BlockList,
ObserveTyping,
@ -15,37 +15,50 @@ import {
WritingFlow,
} from '@wordpress/block-editor';
export const EditorWritingFlow: React.VFC = () => {
type EditorWritingFlowProps = {
blocks: BlockInstance[];
onChange: ( changes: BlockInstance[] ) => void;
placeholder?: string;
};
export const EditorWritingFlow = ( {
blocks,
onChange,
placeholder = '',
}: EditorWritingFlowProps ) => {
const instanceId = useInstanceId( EditorWritingFlow );
const firstBlock = blocks[ 0 ];
const isEmpty = ! blocks.length;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This action is available in the block editor data store.
const { insertBlock } = useDispatch( blockEditorStore );
const { isEmpty } = useSelect( ( select ) => {
const blocks = select( 'core/block-editor' ).getBlocks();
const { insertBlock, selectBlock } = useDispatch( blockEditorStore );
const { selectedBlockClientIds } = useSelect( ( select ) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This selector is available in the block editor data store.
const { getSelectedBlockClientIds } = select( blockEditorStore );
return {
isEmpty: blocks.length
? blocks.length <= 1 &&
blocks[ 0 ].attributes?.content?.trim() === ''
: true,
firstBlock: blocks[ 0 ],
selectedBlockClientIds: getSelectedBlockClientIds(),
};
} );
useEffect( () => {
if ( selectedBlockClientIds?.length || ! firstBlock ) {
return;
}
selectBlock( firstBlock.clientId );
}, [ firstBlock, selectedBlockClientIds ] );
useEffect( () => {
if ( isEmpty ) {
const initialBlock = createBlock( 'core/paragraph', {
content: '',
placeholder,
} );
insertBlock( initialBlock );
onChange( [ initialBlock ] );
}
}, [] );
}, [ isEmpty ] );
return (
/* Gutenberg handles the keyboard events when focusing the content editable area. */
@ -60,8 +73,6 @@ export const EditorWritingFlow: React.VFC = () => {
<BlockTools>
<WritingFlow>
<ObserveTyping>
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
{ /* @ts-ignore This action is available in the block editor data store. */ }
<BlockList />
</ObserveTyping>
</WritingFlow>

View File

@ -1,18 +1,14 @@
/**
* External dependencies
*/
import { BaseControl, SlotFillProvider } from '@wordpress/components';
import { BaseControl, Popover, SlotFillProvider } from '@wordpress/components';
import { BlockEditorProvider } from '@wordpress/block-editor';
import { BlockInstance } from '@wordpress/blocks';
import { createElement, useEffect, useState, useRef } from '@wordpress/element';
import { debounce } from 'lodash';
import {
createElement,
useCallback,
useEffect,
useState,
useRef,
} from '@wordpress/element';
import React from 'react';
import { uploadMedia } from '@wordpress/media-utils';
import { useUser } from '@woocommerce/data';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
// eslint-disable-next-line @woocommerce/dependency-group
@ -31,15 +27,17 @@ type RichTextEditorProps = {
label?: string;
onChange: ( changes: BlockInstance[] ) => void;
entryId?: string;
placeholder?: string;
};
export const RichTextEditor: React.VFC< RichTextEditorProps > = ( {
blocks,
label,
onChange,
placeholder = '',
} ) => {
const blocksRef = useRef( blocks );
const { currentUserCan } = useUser();
const [ , setRefresh ] = useState( 0 );
// If there is a props change we need to update the ref and force re-render.
@ -61,6 +59,24 @@ export const RichTextEditor: React.VFC< RichTextEditorProps > = ( {
forceRerender();
}, 200 );
const mediaUpload = currentUserCan( 'upload_files' )
? ( {
onError,
...rest
}: {
onError: ( message: string ) => void;
} ) => {
uploadMedia(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore The upload function passes the remaining required props.
{
onError: ( { message } ) => onError( message ),
...rest,
}
);
}
: undefined;
return (
<div className="woocommerce-rich-text-editor">
{ label && (
@ -75,13 +91,19 @@ export const RichTextEditor: React.VFC< RichTextEditorProps > = ( {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This property was recently added in the block editor data store.
__experimentalClearBlockSelection: false,
mediaUpload,
} }
onInput={ debounceChange }
onChange={ debounceChange }
>
<ShortcutProvider>
<EditorWritingFlow />
<EditorWritingFlow
blocks={ blocksRef.current }
onChange={ onChange }
placeholder={ placeholder }
/>
</ShortcutProvider>
<Popover.Slot />
</BlockEditorProvider>
</SlotFillProvider>
</div>

View File

@ -1,3 +1,5 @@
$toolbar-height: 40px;
.woocommerce-rich-text-editor {
.woocommerce-rich-text-editor__writing-flow {
border: 1px solid $gray-600;
@ -9,21 +11,12 @@
box-sizing: border-box;
}
.block-editor-inserter {
display: none;
}
.block-editor-block-contextual-toolbar.is-fixed,
.block-editor-block-contextual-toolbar.is-fixed .block-editor-block-toolbar .components-toolbar-group,
.block-editor-block-contextual-toolbar.is-fixed .block-editor-block-toolbar .components-toolbar {
border-color: $gray-600;
}
/* Hide rich text placeholder text */
.rich-text [data-rich-text-placeholder] {
display: none !important;
}
/* hide block boundary background styling */
.rich-text:focus *[data-rich-text-format-boundary] {
background: none !important;
@ -39,11 +32,6 @@
outline: none;
}
.block-editor-block-list__empty-block-inserter,
.block-editor-block-list__insertion-point {
display: none !important;
}
.block-editor-writing-flow {
padding: $gap-small;
}
@ -56,11 +44,25 @@
}
.components-accessible-toolbar {
height: $toolbar-height;
width: 100%;
background-color: $white;
border-color: $gray-700;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
.components-button {
height: $toolbar-height;
}
}
.block-editor-block-mover:not(.is-horizontal) .block-editor-block-mover__move-button-container > * {
height: calc( $toolbar-height / 2 );
}
.block-editor-block-contextual-toolbar.is-fixed,
.components-toolbar-group {
min-height: $toolbar-height;
}
.wp-block-quote {

View File

@ -14,6 +14,8 @@ export const HEADING_BLOCK_ID = 'core/heading';
export const LIST_BLOCK_ID = 'core/list';
export const LIST_ITEM_BLOCK_ID = 'core/list-item';
export const QUOTE_BLOCK_ID = 'core/quote';
export const IMAGE_BLOCK_ID = 'core/image';
export const VIDEO_BLOCK_ID = 'core/video';
const ALLOWED_CORE_BLOCKS = [
PARAGRAPH_BLOCK_ID,
@ -21,6 +23,8 @@ const ALLOWED_CORE_BLOCKS = [
LIST_BLOCK_ID,
LIST_ITEM_BLOCK_ID,
QUOTE_BLOCK_ID,
IMAGE_BLOCK_ID,
VIDEO_BLOCK_ID,
];
const registerCoreBlocks = () => {

View File

@ -226,6 +226,10 @@ export const ProductDetailsSection: React.FC = () => {
setDescriptionBlocks( blocks );
setValue( 'description', serialize( blocks ) );
} }
placeholder={ __(
'Describe this product. What makes it unique? What are its most important features?',
'woocommerce'
) }
/>
</CardBody>
</Card>

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add placeholder to description field