WordPress.org

Make WordPress Core

Opened 22 months ago

Closed 6 months ago

Last modified 4 months ago

#35760 closed enhancement (fixed)

Provide API for TinyMCE editor to be dynamically instantiated via JS

Reported by: westonruter Owned by: azaozz
Milestone: 4.8 Priority: normal
Severity: normal Version:
Component: Editor Keywords: has-patch commit has-dev-note
Focuses: javascript Cc:

Description

There is apparently no easy way currently to bootstrap the TinyMCE content editor dynamically via JS, that is to initialize one or more editors after page load. There is a PHP API to embed an editor into a page statically via wp_editor( $content, $editor_id, $settings ) but this does not work with JS (see #26295).

It would be great if there was a proper JS API for spinning up new TinyMCE editors with all of the WordPress extensions and plugins. For example: var editor = new wp.Editor( container, settings );

Ideally the required assets for the editor could be lazy-loaded instead of being loaded all up-front with the initial request.

With an API for initializing an editor with JS, it would then be greatly simplified to do things like:

  • Create a Customizer control for a rich text editor.
  • Add rich text editing to a Text widget (#35243)
  • General content in the Customizer (#34923)
  • General frontend editing.

For more background, see conversation: https://wordpress.slack.com/archives/core/p1454615457001581

Attachments (3)

black-studio-tinymce-widget.png (369.3 KB) - added by westonruter 11 months ago.
35760.patch (28.1 KB) - added by azaozz 8 months ago.
summoned-tinymce.jpg (10.9 KB) - added by mihai2u 7 months ago.

Download all attachments as: .zip

Change History (49)

This ticket was mentioned in Slack in #core-customize by westonruter. View the logs.


14 months ago

This ticket was mentioned in Slack in #core-editor by westonruter. View the logs.


14 months ago

#3 @westonruter
11 months ago

  • Milestone changed from Awaiting Review to Future Release

@azaozz @iseulde thoughts on this?

#4 follow-up: @azaozz
11 months ago

Yeah, it's a good idea to be able to (lazy) load and initialize the editor from JS. The current wp_editor() is quite a few years old, and will need to be rewritten to support that.

There are three main components for TinyMCE: the core or the concatenated core + plugins, the translation, and the init / settings. The last two will have to be generated from PHP so a "pure" JS solution is not possible.

There is also the wplink modal (HTML) that will probably need refactoring to make is easily loadable from JS.

Create a Customizer control for a rich text editor.

I'm not sure it will be possible to use the current TinyMCE in the customizer sidebar. There is simply not enough space in there. Also the user experience will be quite bad, trying to "preview" something in the sidebar? Thinking it will be much better after the customizer UI is fixed/updated and (hopefully) moves away from the current layout.

There is also another option: to make a completely different function/mini-API for loading the editor only for use in JS. The translated strings will still need to be outputted from PHP, but the editor init object can be filterable only from JS. This will not contain any settings from existing plugins, but will be a better option for making editor instances with different configuration, minimal UI, etc.

#5 in reply to: ↑ 4 ; follow-up: @westonruter
11 months ago

Replying to azaozz:

I'm not sure it will be possible to use the current TinyMCE in the customizer sidebar. There is simply not enough space in there. Also the user experience will be quite bad, trying to "preview" something in the sidebar? Thinking it will be much better after the customizer UI is fixed/updated and (hopefully) moves away from the current layout.

I agree. However, I think that a TinyMCE control could be a suitable drop-in replacement for the current textarea in the sidebar *if* the controls were very stripped-down to just have the most essential buttons (like bold, italics, and link). Otherwise the editor would indeed need to be located somewhere else. The Customize Posts plugin addresses this by sliding the TinyMCE editor in from the bottom as a separate pane: https://www.youtube.com/watch?v=QJsEl0gd7dk

One other option would be to consider switching to a “wide widget” configuration for the Text widget and that would allow for more horizontal space, although the widget would expand over the preview (which can block previewing of the element you want to see). This is what can be seen in the Black Studio TinyMCE Widget: black-studio-tinymce-widget.png

The other option to move toward is inline editing within the preview itself. This needs to be explored regardless, but it doesn't really help solve the space constraint problem since text widgets would commonly appear in a theme sidebar location that is no wider than the customizer controls are, so there wouldn't be any extra room for the toolbars.

#6 in reply to: ↑ 5 @azaozz
11 months ago

Replying to westonruter:

The other option to move toward is inline editing within the preview itself. This needs to be explored regardless, but it doesn't really help solve the space constraint problem since text widgets would commonly appear in a theme sidebar location that is no wider than the customizer controls are, so there wouldn't be any extra room for the toolbars.

Thinking this is the best option. It's true the space in the widget may be small/narrow, but that is exactly how it will look after saving. Then if the editor is in inline mode, it will be the "perfect" preview (or rather "no preview needed" as the perception is that the "real thing" is being changed, not that there is an editor and separate preview) :)

There are couple of UI options when the editor is inline even when the actual edited area is quite small. A "pop-out" toolbar or a side or inline toolbar, etc. This is somewhat easier as the toolbar can be wider than the edited area without making it look odd.

Second best imho is the "wide widget" from the above screenshot, if it doesn't cover the actual widget area. Then the editor would not need to be WYSIWYG and can probably have more streamlined UI / toolbars.

TinyMCE control could be a suitable drop-in replacement for the current textarea in the sidebar *if* the controls were very stripped-down to just have the most essential buttons (like bold, italics, and link).

Yeah, that would work for places that need very "slimmed down" editor UI and/or support only few HTML tags. Then the editor configuration will have to be separate/JS only, as running the default PHP filters will add a lot of stuff from plugins that is intended for the Edit Post screen.

Last edited 11 months ago by azaozz (previous) (diff)

#7 @celloexpressions
11 months ago

For most places that a full editor makes sense (in terms of what it does on the front end), inline editing would be the best route for UX I think. Combining that with something like the customize sidebar becomes useful when looking to edit things like custom post meta, taxonomies, etc., as well as for providing a unified place for publishing-type options. I recall working with the "teeny" mode in the past; a modern take on that could be useful for things like widgets (in a sidebar/admin or inline depending on context).

In general, making the editor component more modular and making it instantiable with JS makes a lot of sense to me.

This ticket was mentioned in Slack in #core-customize by westonruter. View the logs.


10 months ago

#9 @westonruter
10 months ago

  • Milestone changed from Future Release to 4.8

#10 @azaozz
10 months ago

Been thinking what would work best. As this is going to be "pure JS" way to load the needed parts for the editor, skipping most (all?) PHP filters that are currently in class-wp-editor.php seems better. This will avoid interference from current plugins.

Instead of the PHP filters, we can fire custom jQuery events before initializing each editor instance, passing the settings. This will allow plugins to modify them similarly to using the PHP filters. To make it easier for the plugins, we would need to add another PHP filter or action to trigger loading of these scripts.

The plan is to make another function similar to wp_editor() which will either output the script tags or return the src and scripts as strings so they can be lazy-loaded (we still have to output the translations and the settings from PHP). Then on the JS side will have a small function to load the scripts and CSS (if not already loaded) then initialize the editor firing the above events.

This ticket was mentioned in Slack in #core-customize by westonruter. View the logs.


9 months ago

@azaozz
8 months ago

#12 @azaozz
8 months ago

In 35760.patch:

  • This is a somewhat raw, "first run" patch. May need some cleanup and/or refinement.
  • The scripts, stylesheets and default settings for the editor are outputted from PHP and need to be "enqueued" before the footer (before admin_print_footer_scripts).
  • It is aware of wp_editor() and won't output the same editor parts twice.

Tried loading from JS "on demand", but that can be quite slow (typically more than 3 sec.). After tinymce.js is loaded it starts to initialize, then has to load the css and maybe additional js, then it finishes initializing. So it takes "double load" time. Also appending another (bigger) stylesheet after the page has finished loading is quite undesirable as it forces the browser to "reflow" everything.

Last edited 8 months ago by azaozz (previous) (diff)

#13 @mihai2u
7 months ago

I've added this under git here such that it's easier to collaborate on:
https://github.com/xwp/wordpress-develop/tree/trac-35760
with the following WIP PR:
https://github.com/xwp/wordpress-develop/pull/223
I attributed the commit to the original poster of the patch file.

#14 @mihai2u
7 months ago

I was able to summon multiple TinyMCE entities on the same page using JS (attached the screenshot). I'll look into the performance front of things and see whether there are any improvements I can make / suggest on that front.

Thank you for your work here!

#15 @dmo7
7 months ago

Hi guys. I see that this enhancement is targeted for 4.8, but I was wondering if anybody has had success dynamically adding the editor using JS in some other clever way. I'm currently doing it, but it appears that the "media" buttons don't show up. I'm basically using this command:

tinymce.execCommand( 'mceAddEditor', true, "my_id" );

The reason we want to dynamically add it is because we have a custom post type with a repeating field (using CMB2) which has a WYSIWYG field in it. If the users repeat that field a bunch of times (let's say 13 or more), the page gets really really slow because there is essentially 13+ instances of the editor in the page. I'm attempting to find a way to simply have 1 editor on the page at any given time. Essentially, each "lazy editor" field has an edit button and when it's clicked, the JS removes the other editors and then loads up TinyMCE in that spot. It's mostly working, with the exception of the add media buttons. Is there something I can do before or after the above line of code so that it loads up correctly with all the plugins and buttons i would expect? Any thoughts?

This ticket was mentioned in Slack in #core-customize by westonruter. View the logs.


7 months ago

#17 @azaozz
7 months ago

In 40476:

Editor: Provide API for the editor to be dynamically instantiated via JS. First run.

See: #35760

#18 @SergeyBiryukov
7 months ago

In 40477:

Editor: Define $suffix before using it in _WP_Editors::print_tinymce_scripts().

Props imath.
Fixes #40479. See #35760.

#19 @azaozz
7 months ago

In 40515:

Editor: fix undefined var notice in editor_js() in class-wp-editor.php.

Props littler.chicken.
Fixes #40501, see #35760.

This ticket was mentioned in Slack in #core-customize by westonruter. View the logs.


7 months ago

#21 @westonruter
7 months ago

Work in progress for integrating new editor JS API for the Text widget: https://github.com/xwp/wp-core-media-widgets/pull/97

#22 @mihai2u
7 months ago

I ran some performance tests onto the merged patch and the first instantiation of a tinyMce takes about 300ms and subsequent ones take less than half of that, in the realm of ±100ms.

On a page that does not have tinyMce vs with a tinyMce instantiated until the page completes loading, the difference in browser rendering + painting is minimal (goes up from 25ms to 75ms), and for processing the JS, it adds up from ±500ms up to ±700ms in scripting time, so a minimal amount as well.

I agree that it doesn't bring much benefit to load on-demand the editor CSS when needed. The most important aspect is the fact that the editor is mainly oriented towards the administrative interface and it won't have any negative performance penalty to regular visitors.

:thumbs-up: and thank you for the addition!
I don't have any improvement suggestions with regards to increasing performance.

Breakdown of browser work on first instantiation:
http://files.urldocs.com/share/first-instantiation/first-instantiation.jpg
And on second instantiation:
http://files.urldocs.com/share/subsequent-instantiation/subsequent-instantiation.jpg

#23 follow-up: @westonruter
7 months ago

@azaozz as noted on #35243:

TinyMCE is initialized when a widget control is expanded. Note there is currently a 100ms delay in initialization since wp.editor.initialize() seems to not enable contenteditable in the editor iframe when the textarea is not fully visible (such as while/before slideDown).

Is this a defect with wp.editor.initialize() or with TinyMCE? Or is it a known limitation that just needs to be worked around?

#24 @mihai2u
7 months ago

@westonruter

According to my tests the wp.editor.initialize function call takes ±100ms to run - both on first initialisation, and on subsequent TinyMCE initialisations just as well.
This seems to match the delay you experienced here with it requiring 100ms of 'visibility' before someone can interact with them.

Correlation != causality... but my guess is that this is the browser's way to protect the computer resources by delaying any scripting that belongs to iframe's that are not visible.

I would recommend hiding it with something else than display: none (maybe position: absolute; left: -999em; instead) ... and see whether the browser behaves differently and allows the scripting to take place even before the user interacts with it and the iframe turns into becoming visible.

#25 @westonruter
7 months ago

@mihai2u thanks for the info. If initialize takes ~100ms to run then the additional delay of 100ms makes sense, since fast animation for slideDown is 200ms.

I'm not sure of the feasibility of changing how the textarea is hidden since it is inside of a display:none container that gets slid-down, like is done for all widgets.

#26 @mihai2u
7 months ago

@westonruter the changing how the textarea is hidden can be a test-bed scenario where one can get a confirmation on whether this assumption I had is indeed the reason behind it.
And if that's it, and we have to abide by the standards of display: none, at least we can pad the initialisation by 100ms extra to cope for slower systems where scripting for editor initialisation can potentially take a longer amount of time.

#27 in reply to: ↑ 23 @azaozz
7 months ago

Replying to westonruter:

...seems to not enable contenteditable in the editor iframe when the textarea is not fully visible (such as while/before slideDown).

Is this a defect with wp.editor.initialize() or with TinyMCE? Or is it a known limitation that just needs to be worked around?

It is a limitation. Ideally wp.editor.initialize() should run after all animations are done. There are couple of things at play:

  • Iframes cannot be moved in the DOM, or rather they are reloaded after they are moved. This is a browser security related limitation that may break TinyMCE.
  • When initializing TinyMCE gets the dimensions and position of the textarea to be able to visually replace it. This doesn't work if the textarea is hidden (no calculated values). There may be a workaround for that, will look further.

In the "main" editor the textarea is shown initially but any text in it is set to color: white; to prevent the initial "flash" of the unstyled content. Can probably look at doing something similar.

Last edited 7 months ago by azaozz (previous) (diff)

#28 follow-up: @westonruter
7 months ago

  • Keywords needs-patch added
  • Owner set to azaozz
  • Status changed from new to assigned

@azaozz I've discovered a defect with Quick Tags, namely they only get initialized for the first editor created. For subsequent editors, the QT container element is empty. Thereafter, I am currently forced to manually call QTags._buttonsInit() which clearly isn't right.

This ticket was mentioned in Slack in #core-customize by westonruter. View the logs.


7 months ago

This ticket was mentioned in Slack in #core by jeffpaul. View the logs.


6 months ago

#31 @azaozz
6 months ago

  • Resolution set to fixed
  • Status changed from assigned to closed

In 40588:

Editor: Add wp.editor.remove() for editors that were dynamically instantiated from JS.

Fixes: #35760

#32 in reply to: ↑ 28 @azaozz
6 months ago

Replying to westonruter:

Yeah, Quicktags is not as easy to work with as TinyMCE :) Need to remove the instance by hand, also the "ref instance" so it rebuilds the buttons. Added wp.editor.remove() to make that easy.

#34 follow-up: @westonruter
6 months ago

Re-opening per 28 and needing a fix for Quicktags. See https://wordpress.slack.com/archives/C0381N237/p1494343040847084

#35 in reply to: ↑ 34 ; follow-up: @azaozz
6 months ago

Replying to westonruter:

Re-opening per 28 and needing a fix for Quicktags.

Sorry but I don't seem to follow. Added wp.editor.remove() in [40588] for exactly this purpose. You don't need to do QTags._buttonsInit(); when using wp.editor.remove().

This seems to be working properly in the current plugin (after merging of https://github.com/xwp/wp-core-media-widgets/pull/158). Tested on the widgets screen, currently the plugin seem to have loading problems in the customizer.

#36 follow-up: @azaozz
6 months ago

...loading problems in the customizer.

WP_Widget_Visual_Text::enqueue_admin_scripts() is called twice in the customizer. Both of the actions run there:

add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) );
add_action( 'customize_controls_print_scripts', array( $this, 'enqueue_admin_scripts' ) );

That interferes with another test plugin doing wp_enqueue_editor() that I used. Looking further into this, but in the meantime it would be good to fix the "double loading" call in WP_Widget_Visual_Text.

#37 in reply to: ↑ 35 @westonruter
6 months ago

Replying to azaozz:

Sorry but I don't seem to follow. Added wp.editor.remove() in [40588] for exactly this purpose. You don't need to do QTags._buttonsInit(); when using wp.editor.remove().

Try commenting out QTags._buttonsInit(); and then load add a second Text widget. You'll see that the QuickTags only get initialized for the first instance: https://cloudup.com/cWQ2hGF6fKm

#38 in reply to: ↑ 36 @westonruter
6 months ago

Replying to azaozz:

it would be good to fix the "double loading" call in WP_Widget_Visual_Text.

Fixed in https://github.com/xwp/wp-core-media-widgets/pull/166

#39 @azaozz
6 months ago

In 40599:

Editor: Update wp.editor.remove() to use the new Quicktags instance removep().

Fixes: #35760

#40 @westonruter
6 months ago

In 40631:

Widgets: Extend the Text widget with TinyMCE.

Introduces rich text formatting: bold, italic, lists, links.

Props westonruter, azaozz, timmydcrawford, obenland, melchoyce.
See #35760.
Fixes #35243.

#41 @westonruter
6 months ago

  • Keywords has-patch commit needs-dev-note added; needs-patch removed

I think this is important to have a dev note written for it.

#43 @westonruter
6 months ago

  • Keywords has-dev-note added; needs-dev-note removed

#44 @theMikeD
5 months ago

  • Keywords dev-feedback 2nd-opinion added
  • Severity changed from normal to major

The decision to update the text widget rather than create a new rich text widget was a very poorly thought through one. In one step you have destroyed the custom code in countless widgets using the existing text widget, and rendered invalid every tutorial on adding custom code into a widget.

This need to be reverted in 4.8.1 and re-introduced as a Rich Text widget.

#45 @westonruter
5 months ago

  • Keywords dev-feedback 2nd-opinion removed
  • Severity changed from major to normal

This ticket is in a completed milestone. There are other follow-up tickets the Text widget for 4.8.1.

This ticket was mentioned in Slack in #core-editor by jnylen. View the logs.


4 months ago

Note: See TracTickets for help on using tickets.