spohlenz / tinymce-rails

Integration of TinyMCE with the Rails asset pipeline
Other
813 stars 256 forks source link

Turbo + TinyMCE — doesn't reinit the editor after a Turbo interaction #308

Open jasonfb opened 8 months ago

jasonfb commented 8 months ago

Symptom: Although TinyMCE inits once after a page load, if you make a Tubro interaction (either a whole page render or a frame replacement), the TinyMCE widget does not reinitialize to the textarea after your first Turbo interaction.
tinymce-with-turbo2

Note: there are various suggestions on setting up TinyMCE, but I have the most basic in my <head>

<head>
<%= tinymce_assets %>

    <script>
      TinyMCERails.configuration.default = {
        selector: "textarea.tinymce",
        cache_suffix: "?v=6.7.0",
        menubar: "insert view format table tools",
        toolbar: ["bold italic | link | undo redo | forecolor backcolor | bullist numlist outdent indent | table | uploadimage | code"],
        plugins: "table,fullscreen,image,code,searchreplace,wordcount,visualblocks,visualchars,link,charmap,directionality,nonbreaking,media,advlist,autolink,lists",
        images_upload_url: "/uploader/image"
      };
    </script>
<!-- more head content -->
</head>

Then you of course attach class tinymce to your textareas. This works for the 1st pickup, but after navigating with Turbo interactions, TinyMCE stops working.

jasonfb commented 8 months ago

Ok, what's going on here is a little tricky, important to grok before moving forward: • TinyMCE doesn't seem to like to be re-inited after it exists in memory, therefore, it must be unloaded before the turbo interaction and reloaded after the turbo interaction.

To do this, here's what I've done

I am using a JSBundling app with the Sprockets pipeline, so my app/application/javascript.js contains this:

import "./tinymce_init"

Then I have a file app/application/tinymce_init.js with this content:

const reinitTiny = () => {
  console.log("connecting tiny...")
  console.log(tinymce);

  tinymce.init({
    selector: 'textarea.tinymce', // Add the appropriate selector for your textareas
    // Other TinyMCE configuration options
  });
}

window.addEventListener('turbo:before-fetch-response', () => {
  tinymce.remove();
  tinymce.init({selector:'textarea.tinymce'});
})
window.addEventListener('turbo:frame-render', reinitTiny)
window.addEventListener('turbo:render', reinitTiny)

(As this is all global code, you can easily put it into the <head> of your DOM, but I have done it this way just to keep the head clean)

Please note that the above code works with page re-renders and frame renders, I have not tested stream renders.

After this fix, TinyMCE re-initializes appropriately between Turbo interactions

tinymce-with-turbo4

wvengen commented 8 months ago

As a sidenote: with Turbo, you really need to put the Javascript in the head. Putting it in the body gives strange problems (where Turbo inserts the scripts tags on navigation).

dgm commented 1 week ago

I just took the initialization code and put it into a stimulus controller connect() method, and added the tinymce.remove(); at the beginning. This forces a reload whenever the stimulus controller is loaded, only in pages that use it.

yjchieng commented 1 week ago

I just took the initialization code and put it into a stimulus controller connect() method, and added the tinymce.remove(); at the beginning. This forces a reload whenever the stimulus controller is loaded, only in pages that use it.

I am doing something like the below. Seems to work, but there is some 2-3 sec loading delay once in a while when navigate away from the page that has editor. Still trying to find a better way.

export default class extends Controller {
    connect() {
        tinymce.init({...});
    }
    disconnect() {
        tinymce.remove();
    }
}