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

Allow custom rich text formats to replace controlled text rather than just wrapping it in an element #19756

Closed
SeanDS opened this issue Jan 19, 2020 · 15 comments
Labels
[Feature] Rich Text Related to the Rich Text component that allows developers to render a contenteditable [Package] Format library /packages/format-library [Type] Enhancement A suggestion for improvement.

Comments

@SeanDS
Copy link
Contributor

SeanDS commented Jan 19, 2020

Is your feature request related to a problem? Please describe.
Currently the rich text formatting API wraps selected text in an element defined by each format; the functionality of the API is therefore limited to formatting that can be applied by wrapping HTML elements around selected text and prevents for example certain advanced formatting from being used such as replacing text (e.g. TeX code) with rendered mathematics.

Describe the solution you'd like
The formatting API should optionally allow a custom defined format to entirely control what the selected text is replaced with, including replacing it with something completely different, not just the current behaviour of wrapping the text in some tag. Further, it should be possible to disable editing of the content of whatever replaces the text - to avoid, for instance, a user editing rendered mathematics (which would otherwise be possible because of the contenteditable="true" property of an ancestor in the editor).

Describe alternatives you've considered
The MathML block provides the ability to display inline mathematics, but it does this in a somewhat clunky way: it wraps the selected text in <mathml> tags then waits 100 ms before calling MathJax to render the contents. MathJax replaces the text in the <mathml> tags with a bunch of <span> elements containing rendered mathematics. As a side-effect it relies on Gutenberg not using the transformed contents to untransform the formatted text back to its unformatted version (e.g. by removing the wrapped element added by the format). The user is, however, still able to edit the rendered mathematics which very easily breaks it: they are effectively able to edit/delete the underlying <span> elements MathJax uses to lay out the mathematics.

Care would need to be given to how to handle nested formats.

I believe having full control over the formatted version of text in the formatting API would allow for advanced formats to be implemented in a much more user friendly way.

@jorgefilipecosta jorgefilipecosta added [Feature] Rich Text Related to the Rich Text component that allows developers to render a contenteditable [Package] Format library /packages/format-library [Type] Enhancement A suggestion for improvement. labels Jan 23, 2020
@makc
Copy link

makc commented Feb 13, 2020

I have found that I can change the value.text property as long as the length is the same, larger string was cut off. I feel like there already is some method to do this, but not as simple as dirct value.text assignment.

@makc
Copy link

makc commented Feb 13, 2020

So if I use this hack instead of changing the value

setTimeout(replaceSelectedText, 0, replacementText);

where replaceSelectedText function is from https://stackoverflow.com/a/3997896

the text is inserted as expected, but I get tons of these:

TypeError: can't access property "nativeEvent", e is undefined

:(

Type errors stop once you edit the text, and the editor updates itself (before that you cant even save the post because it does not notice the change).

@makc
Copy link

makc commented Feb 13, 2020

ok, Ive finally found what seemed to be clean way to do this here:

return window.wp.richText.replace(value, selectedText, replacementText);

However, it replaces 1st occurence of the text in the value and not actually selected one 😔

@makc
Copy link

makc commented Feb 13, 2020

A-ha! insert does just what I want, thanks for listening 😎

@SeanDS
Copy link
Contributor Author

SeanDS commented Jul 19, 2020

@makc can you clarify the problem you solved above? Your solution might work for me too - as explained in my first post, I want a way to replace some text using the format API instead of wrapping it in HTML tags (in my case, I want to render inline LaTeX markup from code). If you have any snippets to share that would be very helpful.

@SeanDS
Copy link
Contributor Author

SeanDS commented Aug 25, 2020

Does anyone from the Gutenberg development team have a comment to make on this? Is the format API even the correct approach for this kind of thing? Is this a completely unsupported paradigm?

@SeanDS
Copy link
Contributor Author

SeanDS commented Mar 4, 2021

It would be good to get some input on this from a Gutenberg developer, please!

@SeanDS
Copy link
Contributor Author

SeanDS commented Mar 18, 2021

#29962 describes a more concrete fix for this problem based on discussion in the issue mentioned immediately above, so I'm going to close this.

@SeanDS SeanDS closed this as completed Mar 18, 2021
@makc
Copy link

makc commented Mar 20, 2021

@SeanDS

can you clarify the problem you solved above?

I needed a way to replace the selected text with another text, basically. Here is complete code I wrote back then:

(function() {

	var __ = window.wp.i18n.__;
	var insert = window.wp.richText.insert;
	var registerFormatType = window.wp.richText.registerFormatType;
	var unregisterFormatType = window.wp.richText.unregisterFormatType;
	var createElement = window.wp.element.createElement;
	var RichTextToolbarButton = window.wp.blockEditor.RichTextToolbarButton;

	//unregisterFormatType( 'designhubz/button' );

	registerFormatType( 'designhubz/button', {
		title: __( 'DesignHubz', 'designhubz' ),
		tagName: 'span',
		className: 'designhubz',
		edit: function ( arg ) {
			var value = arg.value;
			var isActive = arg.isActive;
			var onChange = arg.onChange;
			return createElement(RichTextToolbarButton, {
				title: __( 'DesignHubz', 'designhubz' ),
				onClick: function () {
					return onChange( doTheJob( value ) )
				},
				icon: createElement('img', { src: 'https://3d-vr.designhubz.com/favicon.ico', style: { margin: '4px 6px 0 4px' } }),
				isActive: isActive,
				//shortcutType: 'primary',
				//shortcutCharacter: '.',
				className: 'toolbar-button-with-text designhubz'
			})
		}
	} );

	function doTheJob( value ) {

		var selectedText = value.text.substring( value.start, value.end ) || prompt ('Enter the URL') || '';
		if (selectedText.length) {
			var replacementText = "[designhubz src=\"" + selectedText + "\" width=\"100%\" height=\"500\"]";
			value = insert(value, replacementText);
		}

		return value;
	}

})();

@SeanDS
Copy link
Contributor Author

SeanDS commented Mar 21, 2021

Thanks for posting! I think doing that is now easier thanks to the object: true setting. See the inline image format for an example. This lets you replace the text with another element entirely. It almost does what I need, but I need to be able to embed a whole tree of elements and not just a single one, which is not possible yet (see #29962).

@mysignisleo
Copy link

@SeanDS

can you clarify the problem you solved above?

I needed a way to replace the selected text with another text, basically. Here is complete code I wrote back then:

(function() {

	var __ = window.wp.i18n.__;
	var insert = window.wp.richText.insert;
	var registerFormatType = window.wp.richText.registerFormatType;
	var unregisterFormatType = window.wp.richText.unregisterFormatType;
	var createElement = window.wp.element.createElement;
	var RichTextToolbarButton = window.wp.blockEditor.RichTextToolbarButton;

	//unregisterFormatType( 'designhubz/button' );

	registerFormatType( 'designhubz/button', {
		title: __( 'DesignHubz', 'designhubz' ),
		tagName: 'span',
		className: 'designhubz',
		edit: function ( arg ) {
			var value = arg.value;
			var isActive = arg.isActive;
			var onChange = arg.onChange;
			return createElement(RichTextToolbarButton, {
				title: __( 'DesignHubz', 'designhubz' ),
				onClick: function () {
					return onChange( doTheJob( value ) )
				},
				icon: createElement('img', { src: 'https://3d-vr.designhubz.com/favicon.ico', style: { margin: '4px 6px 0 4px' } }),
				isActive: isActive,
				//shortcutType: 'primary',
				//shortcutCharacter: '.',
				className: 'toolbar-button-with-text designhubz'
			})
		}
	} );

	function doTheJob( value ) {

		var selectedText = value.text.substring( value.start, value.end ) || prompt ('Enter the URL') || '';
		if (selectedText.length) {
			var replacementText = "[designhubz src=\"" + selectedText + "\" width=\"100%\" height=\"500\"]";
			value = insert(value, replacementText);
		}

		return value;
	}

})();

It works fine .
If i want to replace html not text, how to do it ?

@x029a
Copy link

x029a commented Jun 7, 2021

Thanks for posting! I think doing that is now easier thanks to the object: true setting. See the inline image format for an example. This lets you replace the text with another element entirely. It almost does what I need, but I need to be able to embed a whole tree of elements and not just a single one, which is not possible yet (see #29962).

Any chance you have an example of this? I'm attempting to replace formatted text in gutenberg with a react component. I can get your method working with core/image but nothing else. It seems insertObject simply takes a value reference and will insert a format, and not actually an arbitrary object?

function insertObject(value: Value, formatToInsert: Format): Value;

@SeanDS
Copy link
Contributor Author

SeanDS commented Jun 8, 2021

Unfortunately Gutenberg only currently has the ability to replace text with single elements (e.g. an <img>) when object: true is set, not full DOM trees. I would also like this behaviour so I can implement inline TeX rendering. The relevant issue is #29962. I believe @ellatrix planned to look at implementing this at some point.

@makc
Copy link

makc commented Jun 8, 2021

@SeanDS cant you like make an svg and put that into <img> src attribute

@jerryjee I do not remember enough to answer that

@SeanDS
Copy link
Contributor Author

SeanDS commented Jun 24, 2021

@makc that's an interesting idea, thanks. Not as good as having full control over the DOM but might work until support for this is added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Rich Text Related to the Rich Text component that allows developers to render a contenteditable [Package] Format library /packages/format-library [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

No branches or pull requests

5 participants