From b723c1e3655f279996a53f6b29d5923eca4e18f0 Mon Sep 17 00:00:00 2001 From: Ella Date: Sun, 14 Apr 2024 20:38:41 +0300 Subject: [PATCH 1/5] Rich text: try bit with block binding API --- lib/init.php | 12 +++++ .../src/components/rich-text/index.js | 48 +++++++++++++++++++ packages/rich-text/src/component/index.js | 11 +++++ packages/rich-text/src/create.js | 28 +++++++++++ packages/rich-text/src/to-dom.js | 16 +++++-- packages/rich-text/src/to-html-string.js | 4 ++ packages/rich-text/src/to-tree.js | 12 ++++- 7 files changed, 126 insertions(+), 5 deletions(-) diff --git a/lib/init.php b/lib/init.php index 88dcba4525f6e..f7362ebf6834f 100644 --- a/lib/init.php +++ b/lib/init.php @@ -57,3 +57,15 @@ function gutenberg_menu() { ); } add_action( 'admin_menu', 'gutenberg_menu', 9 ); + +register_meta( + 'post', + 'isbn', + array( + 'object_subtype' => 'post', + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + 'revisions_enabled' => true + ) +); diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index fac1a594b1c95..63f511ebd6cef 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -11,6 +11,8 @@ import { useCallback, forwardRef, createContext, + createPortal, + useContext, } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { useMergeRefs, useInstanceId } from '@wordpress/compose'; @@ -48,6 +50,7 @@ import { Content, valueToHTMLString } from './content'; import { withDeprecations } from './with-deprecations'; import { unlock } from '../../lock-unlock'; import { canBindBlock } from '../../hooks/use-bindings-attributes'; +import BlockContext from '../block-context'; export const keyboardShortcutContext = createContext(); export const inputEventContext = createContext(); @@ -321,6 +324,7 @@ export function RichTextWrapper( getValue, onChange, ref: richTextRef, + replacementRefs, } = useRichText( { value: adjustedValue, onChange( html, { __unstableFormats, __unstableText } ) { @@ -464,10 +468,54 @@ export function RichTextWrapper( } data-wp-block-attribute-key={ identifier } /> + { replacementRefs.map( ( ref ) => { + return ( + ref && + createPortal( + , + ref + ) + ); + } ) } ); } +function Binding( { content } ) { + const context = useContext( BlockContext ); + const blockBindingsSources = useSelect( ( select ) => { + return unlock( select( blocksStore ) ).getAllBlockBindingsSources(); + } ); + + if ( ! content.startsWith( '/' ) ) { + return null; + } + + const fakeHTML = '<' + content.slice( 1 ) + '>'; + const body = document.implementation.createHTMLDocument( '' ).body; + body.innerHTML = fakeHTML; + const element = body.firstElementChild; + const tag = 'core/' + element.tagName.toLowerCase(); + const source = blockBindingsSources[ tag ]; + + if ( ! source ) { + return null; + } + + const value = source.useSource( + { + context, + }, + { key: element.getAttribute( 'key' ) } + ); + + return value.value.toString(); +} + // This is the private API for the RichText component. // It allows access to all props, not just the public ones. export const PrivateRichText = withDeprecations( diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 6767498548a64..cc9e7921921d6 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -38,6 +38,7 @@ export function useRichText( { const registry = useRegistry(); const [ , forceRender ] = useReducer( () => ( {} ) ); const ref = useRef(); + const replacementRefs = useRef( [] ); function createRecord() { const { @@ -62,6 +63,15 @@ export function useRichText( { __unstableDomOnly: domOnly, placeholder, } ); + + ref.current + .querySelectorAll( '[data-rich-text-comment]' ) + .forEach( ( node, i ) => { + if ( replacementRefs.current[ i ] !== node ) { + replacementRefs.current[ i ] = node; + forceRender(); + } + } ); } // Internal values are updated synchronously, unlike props and state. @@ -218,6 +228,7 @@ export function useRichText( { getValue: () => record.current, onChange: handleChange, ref: mergedRefs, + replacementRefs: replacementRefs.current, }; } diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 8ac79799b22ef..a6f71878af48b 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -469,6 +469,34 @@ function createFromElement( { element, range, isEditableTree } ) { continue; } + if ( + node.nodeType === node.COMMENT_NODE || + ( node.nodeType === node.ELEMENT_NODE && + node.tagName === 'SPAN' && + node.hasAttribute( 'data-rich-text-comment' ) ) + ) { + const value = { + formats: [ , ], + replacements: [ + { + type: '#comment', + attributes: { + 'data-rich-text-comment': + node.nodeType === node.COMMENT_NODE + ? node.nodeValue + : node.getAttribute( + 'data-rich-text-comment' + ), + }, + }, + ], + text: OBJECT_REPLACEMENT_CHARACTER, + }; + accumulateSelection( accumulator, node, range, value ); + mergePair( accumulator, value ); + continue; + } + if ( node.nodeType !== node.ELEMENT_NODE ) { continue; } diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index e7288e4ba1633..d8eb3babe6682 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -68,10 +68,16 @@ function append( element, child ) { const { type, attributes } = child; if ( type ) { - child = element.ownerDocument.createElement( type ); + if ( type === '#comment' ) { + child = element.ownerDocument.createComment( + attributes[ 'data-rich-text-comment' ] + ); + } else { + child = element.ownerDocument.createElement( type ); - for ( const key in attributes ) { - child.setAttribute( key, attributes[ key ] ); + for ( const key in attributes ) { + child.setAttribute( key, attributes[ key ] ); + } } } @@ -238,7 +244,9 @@ export function applyValue( future, current ) { } } - applyValue( futureChild, currentChild ); + if ( ! currentChild.hasAttribute( 'data-rich-text-comment' ) ) { + applyValue( futureChild, currentChild ); + } future.removeChild( futureChild ); } } else { diff --git a/packages/rich-text/src/to-html-string.js b/packages/rich-text/src/to-html-string.js index 35089003f0b3f..aa35da97dd341 100644 --- a/packages/rich-text/src/to-html-string.js +++ b/packages/rich-text/src/to-html-string.js @@ -88,6 +88,10 @@ function remove( object ) { } function createElementHTML( { type, attributes, object, children } ) { + if ( type === '#comment' ) { + return ``; + } + let attributeString = ''; for ( const key in attributes ) { diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index c380570db561d..49c6844030b2f 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -227,7 +227,17 @@ export function toTree( { const { type, attributes, innerHTML } = replacement; const formatType = getFormatType( type ); - if ( ! isEditableTree && type === 'script' ) { + if ( isEditableTree && type === '#comment' ) { + pointer = append( getParent( pointer ), { + type: 'span', + attributes: { + contenteditable: 'false', + 'data-rich-text-comment': + attributes[ 'data-rich-text-comment' ], + style: 'background:yellow', + }, + } ); + } else if ( ! isEditableTree && type === 'script' ) { pointer = append( getParent( pointer ), fromFormat( { From 322d206b4ff1a4064b59d192739b0208f578bee5 Mon Sep 17 00:00:00 2001 From: Ella Date: Sun, 14 Apr 2024 21:18:31 +0300 Subject: [PATCH 2/5] Use bit syntax --- packages/rich-text/src/create.js | 5 +---- packages/rich-text/src/to-html-string.js | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index a6f71878af48b..b712fcc6ceae0 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -145,10 +145,7 @@ export class RichTextData { // We could expose `toHTMLElement` at some point as well, but we'd only use // it internally. toHTMLString( { preserveWhiteSpace } = {} ) { - return ( - this.originalHTML || - toHTMLString( { value: this.#value, preserveWhiteSpace } ) - ); + return toHTMLString( { value: this.#value, preserveWhiteSpace } ); } valueOf() { return this.toHTMLString(); diff --git a/packages/rich-text/src/to-html-string.js b/packages/rich-text/src/to-html-string.js index aa35da97dd341..d1f7b0f4d8fa2 100644 --- a/packages/rich-text/src/to-html-string.js +++ b/packages/rich-text/src/to-html-string.js @@ -89,7 +89,7 @@ function remove( object ) { function createElementHTML( { type, attributes, object, children } ) { if ( type === '#comment' ) { - return ``; + return ``; } let attributeString = ''; From b2f16dbc6bdc71dbaf8d11245d39726d1a506e46 Mon Sep 17 00:00:00 2001 From: Ella Date: Tue, 16 Apr 2024 16:59:27 +0300 Subject: [PATCH 3/5] Add placeholder fallback --- packages/block-editor/src/components/rich-text/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 63f511ebd6cef..853e72de2e45c 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -513,7 +513,7 @@ function Binding( { content } ) { { key: element.getAttribute( 'key' ) } ); - return value.value.toString(); + return value.value.toString() || value.placeholder; } // This is the private API for the RichText component. From 60d0fac207225243e619c5f379e8430c2eb49ae0 Mon Sep 17 00:00:00 2001 From: Ella Date: Tue, 16 Apr 2024 18:06:09 +0300 Subject: [PATCH 4/5] Replace with value on front-end --- lib/init.php | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/lib/init.php b/lib/init.php index f7362ebf6834f..f91d169fa4d21 100644 --- a/lib/init.php +++ b/lib/init.php @@ -69,3 +69,46 @@ function gutenberg_menu() { 'revisions_enabled' => true ) ); + +add_filter( + 'the_content', + function ( $content ) { + $sources = get_all_registered_block_bindings_sources(); + // To do: use HTML API. + return preg_replace_callback( + '/<\/\/([^>]*)>/', + function ( $matches ) use ( $sources ) { + $attributes = explode(' ', $matches[1]); + $key = array_shift($attributes); + if ( ! isset( $sources["core/$key"] ) ) { + return ''; + } + $attributes = array_reduce( + $attributes, + function ( $carry, $item ) { + $parts = explode('=', $item); + $carry[ $parts[0] ] = $parts[1]; + return $carry; + }, + array() + ); + // We need to change this function so it doesn't rely on a block + // instance. + return $sources["core/$key"]->get_value( + $attributes, + new WP_Block( + array( + 'blockName' => 'core/paragraph', + ), + array( + 'postId' => get_the_ID(), + 'postType' => get_post_type(), + ) + ), + '' + ); + }, + $content + ); + }, +); From 308d7525e3f8fbac649c514319dab3ac18597b18 Mon Sep 17 00:00:00 2001 From: Ella Date: Tue, 16 Apr 2024 18:32:29 +0300 Subject: [PATCH 5/5] Add wp: prefix --- packages/block-editor/src/components/rich-text/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 853e72de2e45c..d44dcb4b27a66 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -491,11 +491,11 @@ function Binding( { content } ) { return unlock( select( blocksStore ) ).getAllBlockBindingsSources(); } ); - if ( ! content.startsWith( '/' ) ) { + if ( ! content.startsWith( '/wp:' ) ) { return null; } - const fakeHTML = '<' + content.slice( 1 ) + '>'; + const fakeHTML = '<' + content.slice( 4 ) + '>'; const body = document.implementation.createHTMLDocument( '' ).body; body.innerHTML = fakeHTML; const element = body.firstElementChild;