A couple of years ago, I wrote a post on how to integrate the text editor TinyMCE into a Rails app using Stimulus.
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
"tinymce": "^5.8.0"
to
"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
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.
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 on the TinyMCE web site if anything isn't working as expected.
My current working Stimulus controller in full is:
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:
license_key: "gpl",
Top comments (4)
I highly recommend using
tinymce.remove(this.inputTarget)
instead of justtinymce.remove()
. Using it without an argument will destroy all the instances visible on the current page, not just the one binded to this specific controller instance.Yes, you're correct. Thanks. I have implemented something like this since I wrote this article, but at first I found that if I went to another page and used the back button, the text box would either be uneditable or duplicated, suggesting it wasn't being removed properly. I ended up storing the initialised instance on connect and then removing it by passing its ID on disconnect. I'll edit the article to reflect this.
This isn't working for me. I have everything connected, and it gets to the
tinymce.init(config)
line and passes with no errors but nothing is on the page! It's quite strange.I end up with this:
We can see the box is hidden. But even manually changing that CSS doesn't give the right display.
That's what I get as well as the code TinyMCE replaces the
textarea
tag with, so it looks like the javascript is running. I'm not sure what to suggest, other than going through all of the different options and making sure they're correctly set, especially any that have changed their names (likestyleselect
tostyle
), the ones I've mentioned above and others in the link to the official documentation.In fact, I may start by disabling all but the most basic options to see if that works and then re-enabling them gradually to see where it breaks.