diff --git a/packages/block-editor/src/components/rich-text/format-toolbar/index.js b/packages/block-editor/src/components/rich-text/format-toolbar/index.js index 7817fc284ae52..445cd38b4cc1d 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar/index.js +++ b/packages/block-editor/src/components/rich-text/format-toolbar/index.js @@ -23,7 +23,7 @@ const POPOVER_PROPS = { const FormatToolbar = () => { return ( <> - { [ 'bold', 'italic', 'link' ].map( ( format ) => ( + { [ 'bold', 'italic', 'link', 'unknown' ].map( ( format ) => ( { + return formats.some( ( format ) => format.type === name ); + } ); + + if ( ! isActive && ! hasUnknownFormats ) { + return null; + } + + return ( + + ); + }, +}; diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index b9b31e1476257..7fdcf8adba762 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -43,7 +43,7 @@ function createEmptyValue() { }; } -function toFormat( { type, attributes } ) { +function toFormat( { tagName, attributes } ) { let formatType; if ( attributes && attributes.class ) { @@ -65,11 +65,11 @@ function toFormat( { type, attributes } ) { if ( ! formatType ) { formatType = - select( richTextStore ).getFormatTypeForBareElement( type ); + select( richTextStore ).getFormatTypeForBareElement( tagName ); } if ( ! formatType ) { - return attributes ? { type, attributes } : { type }; + return attributes ? { type: tagName, attributes } : { type: tagName }; } if ( @@ -80,7 +80,7 @@ function toFormat( { type, attributes } ) { } if ( ! attributes ) { - return { type: formatType.name }; + return { type: formatType.name, tagName }; } const registeredAttributes = {}; @@ -115,6 +115,7 @@ function toFormat( { type, attributes } ) { return { type: formatType.name, + tagName, attributes: registeredAttributes, unregisteredAttributes, }; @@ -368,7 +369,7 @@ function createFromElement( { // Optimise for speed. for ( let index = 0; index < length; index++ ) { const node = element.childNodes[ index ]; - const type = node.nodeName.toLowerCase(); + const tagName = node.nodeName.toLowerCase(); if ( node.nodeType === node.TEXT_NODE ) { let filter = removeReservedCharacters; @@ -398,19 +399,19 @@ function createFromElement( { // Ignore any placeholders. ( node.getAttribute( 'data-rich-text-placeholder' ) || // Ignore any line breaks that are not inserted by us. - ( type === 'br' && + ( tagName === 'br' && ! node.getAttribute( 'data-rich-text-line-break' ) ) ) ) { accumulateSelection( accumulator, node, range, createEmptyValue() ); continue; } - if ( type === 'script' ) { + if ( tagName === 'script' ) { const value = { formats: [ , ], replacements: [ { - type, + type: tagName, attributes: { 'data-rich-text-script': node.getAttribute( 'data-rich-text-script' ) || @@ -425,20 +426,20 @@ function createFromElement( { continue; } - if ( type === 'br' ) { + if ( tagName === 'br' ) { accumulateSelection( accumulator, node, range, createEmptyValue() ); mergePair( accumulator, create( { text: '\n' } ) ); continue; } const format = toFormat( { - type, + tagName, attributes: getAttributes( { element: node } ), } ); if ( multilineWrapperTags && - multilineWrapperTags.indexOf( type ) !== -1 + multilineWrapperTags.indexOf( tagName ) !== -1 ) { const value = createFromMultilineElement( { element: node, diff --git a/packages/rich-text/src/remove-unregistered-formatting.js b/packages/rich-text/src/remove-unregistered-formatting.js new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/rich-text/src/store/selectors.js b/packages/rich-text/src/store/selectors.js index 32a50cf89020d..52aa4dead99bf 100644 --- a/packages/rich-text/src/store/selectors.js +++ b/packages/rich-text/src/store/selectors.js @@ -37,9 +37,15 @@ export function getFormatType( state, name ) { * @return {?Object} Format type. */ export function getFormatTypeForBareElement( state, bareElementTagName ) { - return getFormatTypes( state ).find( ( { className, tagName } ) => { - return className === null && bareElementTagName === tagName; - } ); + const formatTypes = getFormatTypes( state ); + return ( + formatTypes.find( ( { className, tagName } ) => { + return className === null && bareElementTagName === tagName; + } ) || + formatTypes.find( ( { className, tagName } ) => { + return className === null && '*' === tagName; + } ) + ); } /** diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index 96090ea64b51a..15aa032978c66 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -784,6 +784,7 @@ export const specWithRegistration = [ [ { type: 'my-plugin/link', + tagName: 'a', attributes: {}, unregisteredAttributes: {}, }, @@ -808,6 +809,7 @@ export const specWithRegistration = [ [ { type: 'my-plugin/link', + tagName: 'a', attributes: {}, unregisteredAttributes: { class: 'test', @@ -834,6 +836,7 @@ export const specWithRegistration = [ [ { type: 'core/link', + tagName: 'a', attributes: {}, unregisteredAttributes: { class: 'custom-format', @@ -899,6 +902,7 @@ export const specWithRegistration = [ [ { type: 'my-plugin/link', + tagName: 'a', attributes: {}, unregisteredAttributes: {}, }, diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index 8b2397013de4d..74cc08581e83c 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -35,6 +35,7 @@ function restoreOnAttributes( attributes, isEditableTree ) { * * @param {Object} $1 Named parameters. * @param {string} $1.type The format type. + * @param {string} $1.tagName The tag name. * @param {Object} $1.attributes The format attributes. * @param {Object} $1.unregisteredAttributes The unregistered format * attributes. @@ -48,6 +49,7 @@ function restoreOnAttributes( attributes, isEditableTree ) { */ function fromFormat( { type, + tagName, attributes, unregisteredAttributes, object, @@ -100,7 +102,7 @@ function fromFormat( { } return { - type: formatType.tagName, + type: formatType.tagName === '*' ? tagName : formatType.tagName, object: formatType.object, attributes: restoreOnAttributes( elementAttributes, isEditableTree ), }; @@ -241,7 +243,8 @@ export function toTree( { return; } - const { type, attributes, unregisteredAttributes } = format; + const { type, tagName, attributes, unregisteredAttributes } = + format; const boundaryClass = isEditableTree && @@ -253,6 +256,7 @@ export function toTree( { parent, fromFormat( { type, + tagName, attributes, unregisteredAttributes, boundaryClass, diff --git a/test/e2e/specs/editor/plugins/format-api.spec.js b/test/e2e/specs/editor/plugins/format-api.spec.js index a294a2034f055..1b1d3b3d4173f 100644 --- a/test/e2e/specs/editor/plugins/format-api.spec.js +++ b/test/e2e/specs/editor/plugins/format-api.spec.js @@ -34,6 +34,28 @@ test.describe( 'Using Format API', () => { expect( content ).toBe( `

First paragraph

+` + ); + } ); + + test( 'should show unknow formatting button', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { content: 'test' }, + } ); + expect( await editor.getEditedPostContent() ).toBe( + ` +

test

+` + ); + await page.keyboard.press( 'ArrowRight' ); + await editor.clickBlockToolbarButton( 'Clear Unknown Formatting' ); + expect( await editor.getEditedPostContent() ).toBe( + ` +

test

` ); } );