From TinyMCE to CKeditor and back

When I wrote the first version of this blogs application in 2008, I initially used TinyMCE for posts and comments, but within a couple of weeks I switched to CKeditor because it handled HTML better and provided server-side support for image uploads. Years have passed since then and the last version of CKeditor I integrated into this application was v2.6, which was written so extraordinarily well, that it continued working for me without any problems for over 10 years.

In the last couple of years, when I started noticing little problems, like Ctrl-B switching back to plain text on its own in Chrome, I decided it was time to upgrade the editor to the latest version. CKeditor worked so well for me over the years that the thought of checking out alternatives didn't even enter my mind.

CKeditor v5 - A beautiful disappointment

The first simple integration test went quite well and about an hour after downloading a custom-built CKeditor package I was able to edit my posts in a brand new and beautifully rendered editor. The only thing remaining was to integrate it fully with the application in the way CKeditor v2.6 was. That seemed like a straightforward task.

Different flavors, different builds

The first surprise for me was that while with CKeditor v2.6 I could configure the same editor package to edit posts with a full set of editing functions and to write comments with a small usable subset of text formatting functionality, with CKeditor v5 I had to download different builds, each configured with a set of plug-ins appropriate for its intended use. This didn't bother me much and I decided to look into this later to see if it's possible to configure the same editor build to serve both purposes.

Our editor, our CSS styles

A much bigger surprise came in the form of a rather large CKeditor-provided CSS style sheet that was supposed to be used for edited HTML. In contrast, CKeditor v2.6 would just accept a user-provided content CSS style sheet.

Writing blog styles is quite tricky in that it has to show the same styles in the editor and when the HTML is rendered on its own as a blog post. It may seem simple at first, but when all styles for the page running the editor, the editor styles and the user-provided content styles are combined, it gets quite tricky to apply styles reliably.

I guess this is exactly what CKeditor team was trying to solve, but their solution was to follow the MS Word HTML editing approach, which saves HTML that looks just like the original Word document, but is unmanageable outside MS Word because of numerous and cryptic styles used throughout the HTML. In the web editor context, it would marry the HTML content to a particular editor, which might be good for the CKeditor brand, but it certainly wasn't what I was looking for.

Our editor, our HTML structure

However, as unhappy as I was with the new way CKeditor handled content styles, it wasn't the final straw that broke my resolve to use CKeditor in my projects.

CKeditor v2.6 handled gracefully p and div elements and kept the structure of the HTML document intact, so it was straightforward to write styles for those documents and apply additional transformations, such as running it through an XSL processor to remove invalid elements (CKeditor v2.6 submitted documents as XHTML).

Traditionally I used div elements to wrap images with titles, so image HTML in a blog post would look like this and was styled within the structure of the outer div.

<div>
<img ...>
<div>Title</div>
</div>

Once I opened a blog post with an image in CKeditor v5 and saved it, all images came back with broken styling because the HTML was converted into this.

<p>
<img ...>
</p>
<p>Title</p>

The same was done against other elements, like blockquote. I naturally assumed this to be a bug and created this issue.

https://github.com/ckeditor/ckeditor5/issues/11154

CKeditor team quickly responded and said that it was working as designed and this HTML structure conforms to their document model. They advised to use the General HTML Support plug-in to retain the original HTML structure.

I tried the advised plug-in and it was keeping some of the outer div elements, but was still injecting p elements in odd places. This was that last straw for me and CKeditor had to go.

TinyMCE - Hello, old friend

I looked at a half a dozen WYSIWYG editors, looking for specific features, which would include using a user-provided content style sheet, source HTML edit, ability to add drop-downs for less-used HTML elements, such as <samp>, customizable toolbars to use the same editor package as a post editor and for post comments, and a few other things that I used over the years in this application and my other projects.

For most editors I looked at, I didn't go past the demo page because they provided very few of those features. Some dropped off my list not far into reading documentation and in the end I had only Quill and TinyMCE on my list.

Quill was quite nice, but TinyMCE had a few more things I wanted, so I made it full circle since 2008 and came back to use TinyMCE for my blogs.

Configure, configure, configure

I was very happy with my CKeditor v2.6 layout, so I tried to reproduce the same toolbars in TinyMCE. It took me a few hours to combing through TinyMCE documentation and trying things out and in the end I got most of it working in a quite similar configuration for both editor flavors I needed.

Much of the additional goodness in TinyMCE comes from plug-ins and in the end I had these plug-ins on my list - code, lists, link, anchor, image, table, charmap, searchreplace, help, visualblocks. Each plug-in would have a corresponding button or a set of buttons in a toolbar that needed to be listed explicitly to place them in the expected position.

The toolbar could be configured in multiple ways, including creative ways to handle overflows and could be broken down onto separate toolbars. I opted for the latter to make buttons appear in the expected positions in the toolbar. I was also able to configure most of functionality I needed to be accessible via the toolbar, so I disabled the menu, which made the editor look simpler, but without losing editing capabilities.

Gotchas

There were a few unexpected surprises while I was testing TinyMCE. Some were smaller and some resulted in data loss when I saved edited documents. Those were not easy to spot visually.

The small annoyances are just bugs or design oversights and, hopefully, will be fixed some day. For example, I was able to configure the inline styles drop-down to contain the styles I need with a combination of toolbar: styleselect and the styles drop-down configured this way.

style_formats: [
{ title: "Bold", format: "bold" },
{ title: "Italic", format: "italic" },
{ title: "Underline", format: "underline" },
{ title: "Strikethrough", format: "strikethrough" },
{ title: "Superscript", format: "superscript" },
{ title: "Subscript", format: "subscript" },
{ title: "Code", format: "code" },
{ title: "Sample", format: "samp" }
]

This revealed a bug in how samp is applied, which was as if it was a block style, so the entire paragraph was wrapped in a <samp> element. I created this issue and hopefully it will be fixed at some point.

https://github.com/tinymce/tinymce/issues/7577

There were also other small unexpected issues, like kbd missing from the styles altogether, even though it is listed as a supported inline element. For the time being I use source edit to enter those.

I also stumbled upon a couple of more serious issues that threw away my original content or modified it in the way that would present problems in the future. I sure am glad that I did text comparisons on many saved documents before completely switching to TinyMCE.

The first one was that TinyMCE would silently drop inline event handlers, such as onmouseover and onmouseout. It seems that TinyMCE does this for security reasons, but for trusted content, such as blog posts, this results in a loss of data. It is possible to list explicitly allowed attributes in configuration, but this means that an elaborated list like this needs to be maintained to allow specific inline event handlers.

extended_valid_elements: "span[class|style|alt|title|onmouseover|onmouseout]"

There are also wildcard rules that can be applied to all elements. See this section for more details on this.

https://www.tiny.cloud/docs/configure/content-filtering/#valid_elements

The second issue was in that I notice all my image paths being rewritten using parent paths, so an image referenced via path /media/2/30/img.png would be silently converted to /../../media/2/30/img.png based on the strange idea that the browser will "mess up" URLs, which is described in TinyMCE documentation this way.

URLs are automatically converted (messed up) by default because the built-in browser logic works this way.

https://www.tiny.cloud/docs/configure/url-handling/#convert_urls

Fortunately, there is a way to disable this questionable behavior by setting convert_urls: false, which is what I did, so all my URLs remain as entered. I will rather take the well defined "messed up" browser logic in this specific case.

Final Word

I must admit that TinyMCE exceeded my expectations. It is beautifully rendered and is highly configurable to accommodate most online general text editing needs for projects I worked on in the past and certainly works out very well for this blogs application.

Having said that, many modern WYSIWYG editors try to enforce best practices for document content and while it would be fine if it came with some kind of warning, unfortunately, many development teams take it a step too far and implement these practices as the default silent behavior. This makes it easy to lose some markup just because some developer considered a particular document structure unsafe without accounting for various specific editor integration contexts.

When integrating a new WYSIWYG editor, it is a good idea to maintain document history, so when some data loss is discovered long after the document was saved, it could be recovered without having to retype lost bits and pieces.

Another important point is never rely on content safety measures enforced by a WYSIWYG editor, as those are easily bypassed by a direct form submission or replaying an API request with arbitrary data. All safety rules must be enforced by the server-side code.

Comments: