Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rich text: try BITS with block binding API #60735

Draft
wants to merge 5 commits into
base: add/support-for-editing-in-block-bindings
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions lib/init.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,58 @@
);
}
add_action( 'admin_menu', 'gutenberg_menu', 9 );

register_meta(
'post',
'isbn',
array(
'object_subtype' => 'post',

Check warning on line 65 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Array double arrow not aligned correctly; expected 4 space(s) between "'object_subtype'" and double arrow, but found 1.
'show_in_rest' => true,

Check warning on line 66 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Array double arrow not aligned correctly; expected 6 space(s) between "'show_in_rest'" and double arrow, but found 1.
'single' => true,

Check warning on line 67 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Array double arrow not aligned correctly; expected 12 space(s) between "'single'" and double arrow, but found 7.
'type' => 'string',

Check warning on line 68 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Array double arrow not aligned correctly; expected 14 space(s) between "'type'" and double arrow, but found 9.
'revisions_enabled' => true

Check failure on line 69 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

There should be a comma after the last array item in a multi-line array.
)
);

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]);

Check failure on line 81 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Expected 1 spaces after opening parenthesis; 0 found

Check failure on line 81 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Expected 1 spaces before closing parenthesis; 0 found
$key = array_shift($attributes);

Check warning on line 82 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

Check failure on line 82 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Expected 1 spaces after opening parenthesis; 0 found

Check failure on line 82 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Expected 1 spaces before closing parenthesis; 0 found
if ( ! isset( $sources["core/$key"] ) ) {

Check failure on line 83 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Array keys must be surrounded by spaces unless they contain a string or an integer.
return '';
}
$attributes = array_reduce(
$attributes,
function ( $carry, $item ) {
$parts = explode('=', $item);

Check warning on line 89 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Equals sign not aligned with surrounding assignments; expected 14 spaces but found 1 space

Check failure on line 89 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Expected 1 spaces after opening parenthesis; 0 found

Check failure on line 89 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Expected 1 spaces before closing parenthesis; 0 found
$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(

Check failure on line 97 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Array keys must be surrounded by spaces unless they contain a string or an integer.
$attributes,
new WP_Block(
array(
'blockName' => 'core/paragraph',
),
array(
'postId' => get_the_ID(),

Check warning on line 104 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Array double arrow not aligned correctly; expected 3 space(s) between "'postId'" and double arrow, but found 1.
'postType' => get_post_type(),
)
),
''
);
},
$content
);
},

Check failure on line 113 in lib/init.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Trailing comma's are not allowed in function calls in PHP 7.2 or earlier
);
48 changes: 48 additions & 0 deletions packages/block-editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -321,6 +324,7 @@ export function RichTextWrapper(
getValue,
onChange,
ref: richTextRef,
replacementRefs,
} = useRichText( {
value: adjustedValue,
onChange( html, { __unstableFormats, __unstableText } ) {
Expand Down Expand Up @@ -464,10 +468,54 @@ export function RichTextWrapper(
}
data-wp-block-attribute-key={ identifier }
/>
{ replacementRefs.map( ( ref ) => {
return (
ref &&
createPortal(
<Binding
content={ ref.getAttribute(
'data-rich-text-comment'
) }
/>,
ref
)
);
} ) }
</>
);
}

function Binding( { content } ) {
const context = useContext( BlockContext );
const blockBindingsSources = useSelect( ( select ) => {
return unlock( select( blocksStore ) ).getAllBlockBindingsSources();
} );

if ( ! content.startsWith( '/wp:' ) ) {
return null;
}

const fakeHTML = '<' + content.slice( 4 ) + '>';
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() || value.placeholder;
}

// This is the private API for the RichText component.
// It allows access to all props, not just the public ones.
export const PrivateRichText = withDeprecations(
Expand Down
11 changes: 11 additions & 0 deletions packages/rich-text/src/component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function useRichText( {
const registry = useRegistry();
const [ , forceRender ] = useReducer( () => ( {} ) );
const ref = useRef();
const replacementRefs = useRef( [] );

function createRecord() {
const {
Expand All @@ -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.
Expand Down Expand Up @@ -218,6 +228,7 @@ export function useRichText( {
getValue: () => record.current,
onChange: handleChange,
ref: mergedRefs,
replacementRefs: replacementRefs.current,
};
}

Expand Down
33 changes: 29 additions & 4 deletions packages/rich-text/src/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -469,6 +466,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;
}
Expand Down
16 changes: 12 additions & 4 deletions packages/rich-text/src/to-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ] );
}
}
}

Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions packages/rich-text/src/to-html-string.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ function remove( object ) {
}

function createElementHTML( { type, attributes, object, children } ) {
if ( type === '#comment' ) {
return `</${ attributes[ 'data-rich-text-comment' ] }>`;
}

let attributeString = '';

for ( const key in attributes ) {
Expand Down
12 changes: 11 additions & 1 deletion packages/rich-text/src/to-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -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( {
Expand Down
Loading