With the release of TOAST UI Editor 2.0, we were able to improve a lot of features including the Editor's markdown parsing accuracy, syntax highlighting feature, scroll sync accuracy, and more. All of these improvements were made possible due to the implementation of our own ToastMark markdown parser. However, because we focused mainly on the markdown editor for the 2.0, there were no further improvements on the UI like toolbars and switch tabs.
We worked on TOAST UI 3.0 with not only markdown on our mind but also improving the overall structure of the editor and the usage. We added ways for our users to extend our editor's functionalities including custom markdown syntax support, widget node insertion, and improved plugin system and we also overhauled the design to be more sleek and modern. π
Let's take a look at the TOAST UI Editor 3.0's new changes.
The original markdown editor had both CodeMirror and ToastMark maintain text information and share changes between the two. However, for the WYSIWYG editor, we used squire to edit and manage the contents.
In other words, because the two editors edited and managed data differently, the controlling structures were also completely different. Therefore, there were issues with internal code consistency and feature extension.
While heading, list, tables, and more are maintained as nodes, internally, the editors manage the nodes as completely different objects, making the code difficult to read.
Granted markdown is a text-based editor and WYSIWYG editor is a DOM node-based editor, so the data model structures must be different. However, if two editors were to use a singular module to manage the nodes, classes and structures of operations that abstractify data or manage data can be used uniformly.
In order to deal with this issue, we used the Prosemirror, a development tool used to build a WYSIWYG editor, for TOAST UI Editor 3.0, and we were able to unify the internal dependencies of the editors into one. The unified dependency allowed us to follow a single, internal structure, and we were able to remove the past dependencies including CodeMirror, squire, and to-mark.
// All nodes from the editor will take the following class structure.
export class ListItem extends NodeSchema {
get name() {
return 'listItem';
}
get schema() {
return {
content: 'paragraph listGroup*',
attrs: {
task: { default: false },
checked: { default: false },
rawHTML: { default: null },
},
defining: true,
parseDOM: [
// ...
],
toDOM({ attrs }: ProsemirrorNode): DOMOutputSpecArray {
// ...
},
};
}
commands(): EditorCommand {
// ...
}
keymaps() {
return {
Enter: this.commands()(),
};
}
}
Furthermore, we reduced the total file size of the bundle by about thirty percent from 602.1KB to 495.6KB.
Our decision to use Prosemirror will be discussed in a separate article.
Since v3.0 also provides a ESM bundle, if you do not need legacy browser support, you can use the ESM bundle to use the editor more efficiently.
TOAST UI Editor primarily follows the CommonMark while supporting GFM, additionally. However, what if you were to render elements like mathematical expressions or charts? TOAST UI Editor 3.0 options allow users to customize markdown syntax.
Using custom syntax, users can use KaTex syntax to represent mathematical expressions as shown in the following example.
Markdown
WYSIWYG
As you can see in the above images, you can enter any text to use custom syntax in the block surrounded by the $$
symbol. Using custom syntax, users can define their own parsing logic to render text that isn't supported by markdown.
TOAST UI Editor 3.0 now comes with newly added widgetRules
option that allows users to display plain text as a designated widget node. With this option, you can display the linked text as mention nodes or as any form of DOM node you choose.
const reWidgetRule = /\[(@\S+)\]\((\S+)\)/;
const editor = new Editor({
el: document.querySelector('#editor'),
widgetRules: [
{
rule: reWidgetRule,
toDOM(text) {
const rule = reWidgetRule;
const matched = text.match(rule);
const span = document.createElement('span');
span.innerHTML = `<a class="widget-anchor" href="${matched[2]}">${matched[1]}</a>`;
return span;
},
},
],
});
As you can see in the example code, the widgetRules
define the rules in an array, and each rule is attributed as rule
and toDOM
properties.
rule
: Must be a RegExp value, and any text that fits the expression is substituted as widget node. toDOM
: Define the DOM node of the widget node that will be rendered.You can also insert the widget node synced with a popup widget as shown below.
TOAST UI Editor offers five plugins out of the box.
Plugin | Description |
---|---|
chart |
Plugin for rendering charts |
code-syntax-highlight |
Plugin for syntax highlighting |
color-syntax |
Plugin for color picker |
table-merged-cell |
Plugin for table merged cells |
uml |
Plugin for UML |
Aside from the five default plugins, users can also define their own plugin functions. Previously in v2.0, there was no clear format for defining plugins, and users had to access the editor instance directly as shown below. The previous method of defining plugins made it difficult for users to understand the code by creating a strong coupling between the editor and the plugin.
v2.0
export default function colorSyntaxPlugin(editor, options = {}) {
// ...
editor.eventManager.listen('convertorAfterMarkdownToHtmlConverted', html => {
// ...
});
editor.eventManager.listen('convertorAfterHtmlToMarkdownConverted', markdown => {
// ...
});
if (!editor.isViewer() && editor.getUI().name === 'default') {
editor.addCommand('markdown', {
name: 'color',
exec(mde, color) {
// Access the CodeMirror instance
const cm = mde.getEditor();
const rangeFrom = cm.getCursor('from');
// ...
}
});
editor.addCommand('wysiwyg', {
name: 'color',
exec(wwe, color) {
if (!color) {
return;
}
// access the squire instance
const sq = wwe.getEditor();
const tableSelectionManager = wwe.componentManager.getManager('tableSelection');
// ...
}
});
}
});
The code above is a little snippet of the color-syntax
plugin code from the v2.0. Without a predefined format, there was a lot of editor API dependent code, and there are even codes that have to access CodeMirror and squire directly to control the internal states.
TOAST UI Editor 3.0 has removed all of the previous foundational structures and has made it so that users can define the plugins in a predefined format. Furthermore, the plugin is now separated to function even with a minimal access to the editor.
v3.0
export default function colorSyntaxPlugin(context, options) {
// ...
return {
markdownCommands: {
color: ({ selectedColor }, { tr, selection, schema }, dispatch) => {
if (selectedColor) {
// ...
return true;
}
return false;
},
},
wysiwygCommands: {
color: ({ selectedColor }, { tr, selection, schema }, dispatch) => {
if (selectedColor) {
// ...
return true;
}
return false;
},
},
toolbarItems: [
{
groupIndex: 0,
itemIndex: 3,
item: toolbarItem,
},
],
// ...
};
}
Newly defined color-syntax
plugin code is comparatively simpler and easier to read than the previous code. The object returned from the plugin function has properties (markdownCommands
, wysiwygCommands
, and toolbarItems
) that clearly illustrate their responsibilities and controls. Moreover, the properties in this process are not dependent on the editor API or the editor's properties.
The improved plugin system makes it easier for the users to add the v3.0's new custom markdown syntax or to register commands. Currently, the editor ships with five default plugins, but we plan on adding more plugins that support summary
and details
tags as well as autocomplete popup plugins.
To apply the newly improved plugin system, check out the migration guide!
TOAST UI Editor 3.0 has completely reworked the designs. We have increased the sizes of UI elements like toolbars and tabs in order to increase readability, and we have also rounded out the borders to give a smoother feel.
v2.0
v3.0
Furthermore, the Dark Theme has been added.
// ...
import '@toast-ui/editor/dist/toastui-editor.css';
import '@toast-ui/editor/dist/theme/toastui-editor-dark.css';
const editor = new Editor({
// ...
theme: 'dark'
});
There are still more considerable changes to the TOAST UI Editor 3.0 than we have mentioned above. With the substantial changes a lot of usages were changed as well. To help you along the way, we have prepared a migration guide.
Migration Guide(πΊπΈ): https://github.com/nhn/tui.editor/blob/master/docs/v3.0-migration-guide.md
The features mentioned above are explained in greater detail in the following guides.
Aside from the newly introduced features, there are additional, internal maintenance related changes.
The main characteristic of TOAST UI Editor is that it is a markdown based editor that support WYSIWYG editor simultaneously. For the development of TOAST UI Editor 3.0, we focused on highlighting this characteristic and strove to improve compatibility between the structures of the two editors as well as the editors themselves. As a result, we were able to remove a bunch of limiting conditions that existed previously during feature extension.
For now, we are planning on following updates in the future. πͺ
We are planning on researching and developing synchronous editing, which was not possible before, as well as extending default plugins and customizable options. Furthermore, the compatibility between two editors will be enhanced, and the performance dealing with large documents will be optimized.
The TOAST UI Editor's Github is always open! Look forward to our future updates! π