Upgrade TinyMCE to v6 in Rails/Stimulus
Tags: javascript, rails, stimulus
Published: 2023-02-11 16:09:55

A couple of years ago, I wrote a post on [how to integrate the text editor TinyMCE into a Rails app using Stimulus](https://dev.to/djchadderton/making-tinymce-work-with-rails-turbolinks-and-stimulus-2ooa).

While most of that post is still relevant, even after upgrading to Rails 7 and replacing Webpack with esbuild, the upgrade from v5 to v6 of TinyMCE did break a few things, so here's how to get it working again.

First of all, add the upgraded version to `package.json` by changing

```javascript
"tinymce": "^5.8.0"
```

to

```javascript
"tinymce": "^6.3.0"
```

and run the `yarn` command to update it.

In the Stimulus controller, as well as importing `tinymce`, you need to import the model, so the start of the controller file will read

```javascript
import { Controller } from '@hotwired/stimulus'

// Import TinyMCE
import tinymce from 'tinymce/tinymce'
import 'tinymce/models/dom/model'
```

Some plugins have been removed or incorporated into the main bundle, so delete any import statements for `bbcode`, `colorpicker`, `contextmenu`, `fullpage`, `hr`, `imagetools`, `legacyoutput`, `noneditable`, `paste`, `print`, `spellchecker`, `tabfocus`, `textcolor`, `textpattern`, `toc`.

Within your toolbar entries, rename `styleselect` to `style`, e.g.

```javascript
toolbar: ['styles | bold italic underline strikethrough superscript | blockquote numlist bullist link | alignleft aligncenter alignright | table']
```

Some other options have changed, so check the documentation on [_Migrating from TinyMCE 5 to TinyMCE 6_](https://www.tiny.cloud/docs/tinymce/6/migration-from-5x/) on the TinyMCE web site if anything isn't working as expected.

My current working Stimulus controller in full is:

```javascript
import { Controller } from '@hotwired/stimulus'

// Import TinyMCE
import tinymce from 'tinymce/tinymce'
import 'tinymce/models/dom/model'

// Import icons
import 'tinymce/icons/default/icons'

// Import theme
import 'tinymce/themes/silver/theme'

// Import plugins

// import 'tinymce/plugins/advlist'
// import 'tinymce/plugins/anchor'
// import 'tinymce/plugins/autolink'
import 'tinymce/plugins/autoresize'
// import 'tinymce/plugins/autosave'
// import 'tinymce/plugins/charmap'
import 'tinymce/plugins/code'
// import 'tinymce/plugins/codesample'
// import 'tinymce/plugins/directionality'
// import 'tinymce/plugins/emoticons'
import 'tinymce/plugins/fullscreen'
import 'tinymce/plugins/help'
// import 'tinymce/plugins/image'
// import 'tinymce/plugins/insertdatetime'
import 'tinymce/plugins/link'
import 'tinymce/plugins/lists'
// import 'tinymce/plugins/media'
// import 'tinymce/plugins/nonbreaking'
// import 'tinymce/plugins/pagebreak'
import 'tinymce/plugins/preview'
// import 'tinymce/plugins/quickbars'
// import 'tinymce/plugins/save'
// import 'tinymce/plugins/searchreplace'
import 'tinymce/plugins/table'
// import 'tinymce/plugins/template'
// import 'tinymce/plugins/visualblocks'
// import 'tinymce/plugins/visualchars'
import 'tinymce/plugins/wordcount'

export default class extends Controller {
  static targets = ['input']

  initialize () {
    this.defaults = {
      autoresize_bottom_margin: 10,
      browser_spellcheck: true,
      content_css: false,
      content_style: `
        html {
          font-family: Roboto, sans-serif; line-height: 1.5;
        }
        h3, h4 {
          font-family: 'Montserrat', sans-serif;
          color: hsla(197, 55%, 26%, 1);
          line-height: 1;
          margin: .9rem 0;
        }
        `,
      invalid_elements: 'span',
      link_context_toolbar: true,
      link_default_target: '_blank',
      link_title: false,
      max_height: 700,
      menubar: false,
      mobile: {
        toolbar: [
          'styles | bold italic underline strikethrough superscript',
          'blockquote numlist bullist link | alignleft aligncenter alignright | table',
          'undo redo | fullscreen preview code help'
        ]
      },
      plugins: 'link lists fullscreen help preview table code autoresize wordcount',
      relative_urls: false,
      skin: false,
      style_formats: [
          { title: 'Heading', format: 'h3' },
          { title: 'Sub Heading', format: 'h4' },
          { title: 'Sub Heading 2', format: 'h5' },
          { title: 'Sub Heading 3', format: 'h6' },
          { title: 'Paragraph', format: 'p'}
      ],
      toolbar: [
        'styles | bold italic underline strikethrough superscript | blockquote numlist bullist link | alignleft aligncenter alignright | table',
        'undo redo | fullscreen preview code help'
              ],
      valid_styles: { '*': 'text-align' }
    }
  }

  connect () {
    // Initialize TinyMCE
    if (!this.preview) {
      let config = Object.assign({ target: this.inputTarget }, this.defaults)
      this.tc = tinymce.init(config)
    }
  }

  disconnect () {
    if (!this.preview) tinymce.remove(this.tc.id)
  }

  get preview () {
    return document.documentElement.hasAttribute('data-turbo-preview')
  }
}
```

## Additional note for upgrading to v7

TinyMCE version 7 has added an extra requirement to specify either the open source GPLv2+ or a commercial licence in the config options object or it will warn in the console that it is running in evaluation mode. Add the following to `this.defaults` to continue using the open source version:

```javascript
license_key: "gpl",
```