diff --git a/package-lock.json b/package-lock.json
index 0e27a7383cf08..16b1a4037b3d6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3732,6 +3732,7 @@
"@wordpress/components": "file:packages/components",
"@wordpress/compose": "file:packages/compose",
"@wordpress/data": "file:packages/data",
+ "@wordpress/deprecated": "file:packages/deprecated",
"@wordpress/dom": "file:packages/dom",
"@wordpress/element": "file:packages/element",
"@wordpress/hooks": "file:packages/hooks",
diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md
index 2d97dd70be0c2..67c7e2a9725f7 100644
--- a/packages/block-editor/CHANGELOG.md
+++ b/packages/block-editor/CHANGELOG.md
@@ -1,5 +1,9 @@
## Master
+### New Features
+
+- Added a new `allowedFormats` prop to `RichText` to fine tune allowed formats. Deprecated the `formattingControls` prop in favour of this. Also added a `withoutInteractiveFormatting` to specifically disable format types that would insert interactive elements, which can not be nested.
+
### Breaking Changes
- `BlockEditorProvider` no longer renders a wrapping `SlotFillProvider` or `DropZoneProvider` (from `@wordpress/components`). For custom block editors, you should render your own as wrapping the `BlockEditorProvider`. A future release will include a new `BlockEditor` component for simple, standard usage. `BlockEditorProvider` will serve the simple purpose of establishing its own context for block editors.
diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json
index 5add09430524e..61c49f9d9a088 100644
--- a/packages/block-editor/package.json
+++ b/packages/block-editor/package.json
@@ -29,6 +29,7 @@
"@wordpress/components": "file:../components",
"@wordpress/compose": "file:../compose",
"@wordpress/data": "file:../data",
+ "@wordpress/deprecated": "file:../deprecated",
"@wordpress/dom": "file:../dom",
"@wordpress/element": "file:../element",
"@wordpress/hooks": "file:../hooks",
diff --git a/packages/block-editor/src/components/rich-text/README.md b/packages/block-editor/src/components/rich-text/README.md
index cca45bce3ec04..9cc0b3e93fccd 100644
--- a/packages/block-editor/src/components/rich-text/README.md
+++ b/packages/block-editor/src/components/rich-text/README.md
@@ -41,9 +41,13 @@ Render a rich [`contenteditable` input](https://developer.mozilla.org/en-US/docs
*Optional.* Called when the block can be removed. `forward` is true when the selection is expected to move to the next block, false to the previous block.
-### `formattingControls: Array`
+### `allowedFormats: Array`
-*Optional.* By default, all formatting controls are present. This setting can be used to fine-tune formatting controls. Possible items: `[ 'bold', 'italic', 'strikethrough', 'link' ]`.
+*Optional.* By default, all registered formats are allowed. This setting can be used to fine-tune the allowed formats. Example: `[ 'core/bold', 'core/link' ]`.
+
+### `withoutInteractiveFormatting: Boolean`
+
+*Optional.* By default, all formatting controls are present. This setting can be used to remove formatting controls that would make content [interactive](https://html.spec.whatwg.org/multipage/dom.html#interactive-content). This is useful if you want to make content that is already interactive editable.
### `isSelected: Boolean`
diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js
index 2c46ea36d61f7..8a3ef0550045b 100644
--- a/packages/block-editor/src/components/rich-text/index.js
+++ b/packages/block-editor/src/components/rich-text/index.js
@@ -27,6 +27,7 @@ import {
} from '@wordpress/rich-text';
import { withFilters, IsolatedEventContainer } from '@wordpress/components';
import { createBlobURL } from '@wordpress/blob';
+import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
@@ -253,6 +254,24 @@ class RichTextWrapper extends Component {
onReplace( [ block ] );
}
+ getAllowedFormats() {
+ const { allowedFormats, formattingControls } = this.props;
+
+ if ( ! allowedFormats && ! formattingControls ) {
+ return;
+ }
+
+ if ( allowedFormats ) {
+ return allowedFormats;
+ }
+
+ deprecated( 'wp.blockEditor.RichText formattingControls prop', {
+ alternative: 'allowedFormats',
+ } );
+
+ return formattingControls.map( ( name ) => `core/${ name }` );
+ }
+
render() {
const {
tagName,
@@ -276,6 +295,9 @@ class RichTextWrapper extends Component {
placeholder,
keepPlaceholderOnFocus,
// eslint-disable-next-line no-unused-vars
+ allowedFormats,
+ withoutInteractiveFormatting,
+ // eslint-disable-next-line no-unused-vars
onRemove,
// eslint-disable-next-line no-unused-vars
onMerge,
@@ -293,6 +315,8 @@ class RichTextWrapper extends Component {
...experimentalProps
} = this.props;
+ const adjustedAllowedFormats = this.getAllowedFormats();
+ const hasFormats = ! adjustedAllowedFormats || adjustedAllowedFormats.length > 0;
let adjustedValue = originalValue;
let adjustedOnChange = originalOnChange;
@@ -317,6 +341,8 @@ class RichTextWrapper extends Component {
className={ classnames( classes, className ) }
placeholder={ placeholder }
keepPlaceholderOnFocus={ keepPlaceholderOnFocus }
+ allowedFormats={ adjustedAllowedFormats }
+ withoutInteractiveFormatting={ withoutInteractiveFormatting }
onEnter={ this.onEnter }
onDelete={ this.onDelete }
onPaste={ this.onPaste }
@@ -341,12 +367,12 @@ class RichTextWrapper extends Component {
onChange={ onChange }
/>
) }
- { isSelected && ! inlineToolbar && (
+ { isSelected && ! inlineToolbar && hasFormats && (
) }
- { isSelected && inlineToolbar && (
+ { isSelected && inlineToolbar && hasFormats && (
diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js
index 4e8f0dfedc326..a5f70204d6557 100644
--- a/packages/block-library/src/button/edit.js
+++ b/packages/block-library/src/button/edit.js
@@ -114,7 +114,7 @@ class ButtonEdit extends Component {
placeholder={ __( 'Add text…' ) }
value={ text }
onChange={ ( value ) => setAttributes( { text: value } ) }
- formattingControls={ [ 'bold', 'italic', 'strikethrough' ] }
+ withoutInteractiveFormatting
className={ classnames(
'wp-block-button__link', {
'has-background': backgroundColor.color,
diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js
index 2ab0baaa08f8b..616092ac82203 100644
--- a/packages/block-library/src/file/edit.js
+++ b/packages/block-library/src/file/edit.js
@@ -213,7 +213,7 @@ class FileEdit extends Component {
value={ fileName }
placeholder={ __( 'Write file name…' ) }
keepPlaceholderOnFocus
- formattingControls={ [] } // disable controls
+ withoutInteractiveFormatting
onChange={ ( text ) => setAttributes( { fileName: text } ) }
/>
{ showDownloadButton &&
@@ -223,7 +223,7 @@ class FileEdit extends Component {
tagName="div" // must be block-level or else cursor disappears
className={ 'wp-block-file__button' }
value={ downloadButtonText }
- formattingControls={ [] } // disable controls
+ withoutInteractiveFormatting
placeholder={ __( 'Add text…' ) }
keepPlaceholderOnFocus
onChange={ ( text ) => setAttributes( { downloadButtonText: text } ) }
diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js
index 6525f5a82408f..40834ab63f3ab 100644
--- a/packages/block-library/src/search/edit.js
+++ b/packages/block-library/src/search/edit.js
@@ -14,7 +14,7 @@ export default function SearchEdit( { className, attributes, setAttributes } ) {
aria-label={ __( 'Label text' ) }
placeholder={ __( 'Add label…' ) }
keepPlaceholderOnFocus
- formattingControls={ [] }
+ withoutInteractiveFormatting
value={ label }
onChange={ ( html ) => setAttributes( { label: html } ) }
/>
@@ -34,7 +34,7 @@ export default function SearchEdit( { className, attributes, setAttributes } ) {
aria-label={ __( 'Button text' ) }
placeholder={ __( 'Add button text…' ) }
keepPlaceholderOnFocus
- formattingControls={ [] }
+ withoutInteractiveFormatting
value={ buttonText }
onChange={ ( html ) => setAttributes( { buttonText: html } ) }
/>
diff --git a/packages/rich-text/src/component/format-edit.js b/packages/rich-text/src/component/format-edit.js
index 9a9d1945ba884..2bee3de123782 100644
--- a/packages/rich-text/src/component/format-edit.js
+++ b/packages/rich-text/src/component/format-edit.js
@@ -9,45 +9,74 @@ import { withSelect } from '@wordpress/data';
import { getActiveFormat } from '../get-active-format';
import { getActiveObject } from '../get-active-object';
-const FormatEdit = ( { formatTypes, onChange, value } ) => {
- return (
- <>
- { formatTypes.map( ( { name, edit: Edit } ) => {
- if ( ! Edit ) {
- return null;
+/**
+ * Set of all interactive content tags.
+ *
+ * @see https://html.spec.whatwg.org/multipage/dom.html#interactive-content
+ */
+const interactiveContentTags = new Set( [
+ 'a',
+ 'audio',
+ 'button',
+ 'details',
+ 'embed',
+ 'iframe',
+ 'input',
+ 'label',
+ 'select',
+ 'textarea',
+ 'video',
+] );
+
+const FormatEdit = ( {
+ formatTypes,
+ onChange,
+ value,
+ allowedFormats,
+ withoutInteractiveFormatting,
+} ) =>
+ formatTypes.map( ( {
+ name,
+ edit: Edit,
+ tagName,
+ } ) => {
+ if ( ! Edit ) {
+ return null;
+ }
+
+ if ( allowedFormats && allowedFormats.indexOf( name ) === -1 ) {
+ return null;
+ }
+
+ if (
+ withoutInteractiveFormatting &&
+ interactiveContentTags.has( tagName )
+ ) {
+ return null;
+ }
+
+ const activeFormat = getActiveFormat( value, name );
+ const isActive = activeFormat !== undefined;
+ const activeObject = getActiveObject( value );
+ const isObjectActive = activeObject !== undefined;
+
+ return (
+
+ );
+ } );
- const activeFormat = getActiveFormat( value, name );
- const isActive = activeFormat !== undefined;
- const activeObject = getActiveObject( value );
- const isObjectActive = activeObject !== undefined;
-
- return (
-
- );
- } ) }
- >
- );
-};
-
-export default withSelect(
- ( select ) => {
- const { getFormatTypes } = select( 'core/rich-text' );
-
- return {
- formatTypes: getFormatTypes(),
- };
- }
-)( FormatEdit );
+export default withSelect( ( select ) => ( {
+ formatTypes: select( 'core/rich-text' ).getFormatTypes(),
+} ) )( FormatEdit );
diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js
index c2f76c4a22c29..34792ad1d2b3a 100644
--- a/packages/rich-text/src/component/index.js
+++ b/packages/rich-text/src/component/index.js
@@ -864,6 +864,8 @@ class RichText extends Component {
__unstableAutocompleters: autocompleters,
__unstableAutocomplete: Autocomplete = ( { children: ch } ) => ch( {} ),
__unstableOnReplace: onReplace,
+ allowedFormats,
+ withoutInteractiveFormatting,
} = this.props;
// Generating a key that includes `tagName` ensures that if the tag
@@ -908,7 +910,12 @@ class RichText extends Component {
{ MultilineTag ? { placeholder } : placeholder }
}
- { isSelected && }
+ { isSelected && }
>
);