Introduction
This migration guide includes all information regarding changes users must be aware of when updating from TOAST UI Editor 2.x to TOAST UI Editor 3.0.
TOAST UI Editor (hereafter referred to as the ‘Editor’) has removed the original CodeMirror, squire, and to-mark dependencies and has modified the editor to use abstract models through Prosemirror. Since the core module API and plugin usages were changed, it is advised that users consult the migration guide carefully. The table of contents is as follows and refers to the ‘Changes’ in the order enumerated when updating.
Changes
1. Installation and Usages
To use the Editor, use the scoped package to install the @toast-ui/editor
package as you did for the previous v2.x. The following is an example of using the npm command to install the Editor.
$ npm install @toast-ui/editor
$ npm install @toast-ui/editor@<version>
Usages
const Editor = require('@toast-ui/editor'); /* CommonJS */
import Editor from '@toast-ui/editor'; /* ES6 Module */
Furthermore, v3.0 provides an EditorCore
module as named export for those wanting to implement their own unique UI instead of using the default UI. This module will create the markdown editor, markdown preview, and WYSIWYG editor, and the user can use the getEditorElements()
method to add the editor to desired UI. This module does not create external editor UIs like toolbars, toolbar popups, and switch tabs.
import { EditorCore } from '@toast-ui/editor'; /* ES6 Module */
const editorCore = new EditorCore({
el
// ...
});
const { mdEditor, mdPreview, wwEditor } = editorCore.getEditorElements();
// ...
Bundle Structure
Aside from the original v2.x bundle content, two new items were added to v3.0.
In addition to the original legacy support bundle and the cdn bundle, ESM bundle is included. ESM bundle is lightweight due to the fact that there is no complex module compatibility statement, and it also provides the bundle with the added benefit of tree shaking via static analysis.
Secondly, the theme/toastui-editor-dark.css
is added for the dark theme support. The dark theme will be covered more in depth in Added Dark Theme.
The bundle structure for v3.0 is as follows.
- dist/
├─ cdn/...
├─ i18n/...
├─ esm/
│ ├─ index.js
│ └─ index.js.map
├─ theme/
│ └─ toastui-editor-dark.css
│
├─ toastui-editor-only.css
├─ toastui-editor-viewer.css
├─ toastui-editor.css
├─ toastui-editor.js
└─ toastui-editor-viewer.js
Furthermore, the ESM bundle is included in the v3.0, and the package.json file has been updated accordingly. The original UMD bundle file is defined in the main field, and the ESM bundle file is defined in the exports field.
{
"main": "dist/toastui-editor.js",
"module": "dist/esm/",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/toastui-editor.js"
},
"./viewer": {
"import": "./dist/esm/indexViewer.js",
"require": "./dist/toastui-editor-viewer.js"
}
}
}
Added Dark Theme
v3.0 ships with dark theme included. To apply the dark theme, add the theme/toastui-editor-dark.css
and set the editor’s theme
option to be dark
. Currently, in v3.0, only the dark theme is supported, but the theme
option was added to support more diverse combinations of themes in the future.
import Editor from '@toast-ui/editor';
import '@toast-ui/editor/dist/toastui-editor.css';
import '@toast-ui/editor/dist/theme/toastui-editor-dark.css';
const editor = new Editor({
el: document.querySelector('#editor'),
previewStyle: 'vertical',
height: '500px',
initialValue: content,
theme: 'dark',
});
Changes in Dependencies
Editor 3.0 no longer requires some of the dependent modules that were needed for v2.x. If you are using the CDN for development, the CodeMirror dependencies required for v2.x are no longer necessary and should be removed. The v3.0 requires Prosemirror and its related modules, but the change is reflected in the CDN, so there is nothing for the user to add.
v2.0
<head>
...
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.4/codemirror.min.css"
/>
<link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.css" />
...
</head>
<body>
...
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
...
</body>
v3.0
<head>
...
<link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.css" />
...
</head>
<body>
...
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
...
</body>
2. Customizing the Toolbar
The toolbarItems
option has been reworked to be more concise and declarative compared to the v2.x. In v3.0, each toolbar item and toolbar groups are defined as options in 2D array format. This method removes the need to define the divider
for differentiating different groups, making the final code much more intuitive.
v2.0
const editor = new Editor({
el: document.querySelector('#editor'),
toolbarItems: [
'heading',
'bold',
'italic',
'strike',
// The divider element had to be added to differentiate different groups.
'divider',
'hr',
'quote',
'divider',
// ...
],
// ...
});
v3.0
const editor = new Editor({
el: document.querySelector('#editor'),
toolbarItems: [
['heading', 'bold', 'italic', 'strike'],
['hr', 'quote'],
// ...
],
// ...
});
By looking at the example code above, it is clear that the v3.0 code is more concise and that the group separation is made easier.
Customization
The method of customization has changed. In v2.x, when displaying or hiding a popup on toolbar item click, the coupling between the editor’s eventManager
and other UI instances were intricate. This forced the users to be familiar with the editor’s internal implementations when customizing from the user’s or from the plugin’s perspective. It made customization difficult and created unnecessary control codes. In v3.0, the UI control codes have been capsulated internally in order to decrease the level of coupling, and users can now customize the toolbar items just by configuring the options.
v2.0
const popup = editor.getUI().createPopup({
header: false,
title: null,
content: colorPickerContainer,
className: 'tui-popup-color',
target: editor.getUI().getToolbar().el,
css: {
width: 'auto',
position: 'absolute'
}
});
editor.eventManager.listen('focus', () => {
popup.hide();
// ...
});
editor.eventManager.listen('colorButtonClicked', () => {
// ...
});
editor.eventManager.listen('closeAllPopup', () => {
// ...
});
The code above is an example of customizing the toolbar’s color picker in v2.x. In order to customize how the toolbar popup functions, users had to use API that were dependent on the editor’s internal implementations like editor.getUI().createPopup()
and editor.getUI().getToolbar()
. Such internal dependencies make flexible customization difficult. It is not just the API. In order to manipulate the popup, users had to register multiple events to the eventManager
.
v3.0
const popup = {
name: 'color',
tooltip: 'Text color',
className: 'toastui-editor-toolbar-icons color',
popup: {
className: 'toastui-editor-popup-color',
body: colorPickerContainer,
style: { width: 'auto' },
},
};
Few bits of codes have been intentionally left out, but in v3.0, users can create and control the popup UI through a simple option object. Users no longer need to be familiar with the internal UI modules and just have to define the popup
option object’s className
, style
, and body
properties to trigger a popup on click of a toolbar button. For more information regarding customizing the toolbar, refer to this link.
3. Defining Plugins
The biggest change of the v3.0 is in defining plugins. In v2.x, plugins were also incredibly dependent on the editor’s internal modules as seen in the previous toolbar section. For plugins, the users had to be especially familiar with the markdown editor, WYSIWYG editor, converter, and editor’s other internal instances and how they function. In v3.0, in order to address this issue, defining plugins have been reworked to include clearly defined options for customizing each feature. This guide will briefly discuss the options, and for in depth guide on defining plugins can be found here.
Registering Commands
Users can register markdown and WYSIWYG commands through markdownCommands
and wysiwygCommands
options for plugins.
return {
markdownCommands: {
myCommand: (payload, state, dispatch) => {
// ...
},
},
wysiwygCommands: {
myCommand: (payload, state, dispatch) => {
// ...
},
},
};
Each command takes payload
, state
, and dispatch
as inputs, and these three parameters can be used to control the internal functionalities of Prosemirror based editor. This method also requires that users be familiar with Prosemirror. However, the Editor will continue to provide our own basic commands, which will prevent users having to work with the internal implementations directly.
Converting
Users can now change the result of a render that happens when a certain element is converted from markdown to preview or from markdown editor to WYSIWYG editor. The same is true in reverse. Users can now redefine the text of the element that is converted from WYSIWYG editor to markdown editor. The toHTMLRenderers
and toMarkdownRenderers
options can be used to define what happens during the conversion from markdown to WYSIWYG and from WYSIWYG to markdown.
return {
toHTMLRenderers: {
// ...
tableCell(node: MergedTableCellMdNode, { entering, origin }) {
const result = origin!();
// ...
return result;
},
},
toMarkdownRenderers: {
// ...
tableHead(nodeInfo) {
const row = (nodeInfo.node as ProsemirrorNode).firstChild;
let delim = '';
if (row) {
row.forEach(({ textContent, attrs }) => {
const headDelim = createTableHeadDelim(textContent, attrs.align);
delim += `| ${headDelim} `;
// ...
});
}
return { delim };
},
},
};
The above code is an example of merged table plugin. The tableCell
, defined in toHTMLRenderers
, node’s return value is used for the markdown preview and WYSIWYG editor conversion, and the tableHead
, defined in toMarkdownRenderers
node’s text value is used for markdown editor conversion. Any process during each editor’s conversion can be defined per node through options.
Registering Toolbar Items
The method of registering toolbar items in plugins have changed. The options are similar to the previously explained toolbar customization options. The user just needs to configure the index of the to be added group.
return {
// ...
toolbarItems: [
{
groupIndex: 0,
itemIndex: 3,
item: toolbarItem,
},
],
};
As the code above shows, users can configure which item to add to the toolbarItems
array. Each option object has groupIndex
, itemIndex
, and item
properties and serves the following purpose.
groupIndex
: Defines the index of the group that the item will be added to.itemIndex
: Defines the index of the item to be placed in the determined group.item
: Defines the toolbar item to be added.
Following the example code, the toolbarItems
option will make it so that the toolbar item will be added to the first toolbar group’s fourth index.
Additionally, there are options that this document does not cover including registering markdown and WYSIWYG editor’s Prosemirror plugin, using the eventEmitter
for communication between the editor and the plugin. For more information, it is recommended that users consult the Guide to Using Plugins.
4. APIs and Events
The following are the API signatures and event names that have changed as of v3.0.
Commands
The options of the commands to be registered are now passed in as individual inputs instead of an object consisting of the name and the handler. Furthermore, the input format of the method that executes the command has changed as well.
v2.x
Method Signature | Returned Type |
---|---|
addCommand(type: string, props: { name: string; exec: Command } |
void |
exec(name: string, ...args: any[] ) |
void |
v3.0
Method Signature | Returned Type |
---|---|
addCommand(type: string, name: string, command: CommandFn) |
void |
exec(name: string, payload?: Object) |
void |
Text Manipulation API
Originally, the getTextObject()
API was used to insert or change text from the editor. However, in order to use this, users had to be familiar with the structure of the instance returned by the getTextObject()
API. In v3.0, the getTextObject()
API has been replaced with individual APIs that gets, replaces, and deletes text.
v2.x
TextObject
‘s Interface
interface TextObject {
setRange(range): void;
setEndBeforeRange(range): void;
expandStartOffset(): void;
expandEndOffset(): void;
getTextContent(): string;
replaceContent(content) : void;
deleteContent(): void;
peekStartBeforeOffset(offset): Range;
}
v3.0
Method Signature | Returned Type | Notes |
---|---|---|
replaceSelection(text: string, start?: EditorPos, end?: EditorPos) |
void |
Replaces the text at the given range. If the range is not provided, the text present in current editor’s selected range is replaced. |
deleteSelection(start?: EditorPos, end?: EditorPos) |
void |
Deletes the text at the given range. If the range is not provided, the text present in current editor’s selected range is replaced. |
getSelectedText(start?: EditorPos, end?: EditorPos) |
string |
Gets the text at the given range. If the range is not provided, the text present in current editor’s selected range is retrieved. |
The above APIs’ positional information (EditorPos
) differs from markdown editor to WYSIWYG editor, and has the following format. This is because markdown and WYSIWYG have different ways to calculate position. Markdown calculates position based on the line, and the WYSIWYG calculates the offset from the start of the document.
// Markdown's Position Information
type EditorPos = [line: number, charactorOffset: number];
// WYSIWYG's Position Information
type EditorPos = number; // 오프셋
// Offset
Changed Instance Constructing Options and Methods
There were changes in options and methods that were not named properly or that did not clearly indicate its feature.
- Instance Constructing Option
v2 | v3 |
---|---|
linkAttribute |
linkAttributes |
- Instance Methods
v2 | v3 |
---|---|
setHtml |
setHTML |
getHtml |
getHTML |
minHeight |
setMinHeight , getMinHeight |
height |
setHeight , getHeight |
getRange |
getSelection |
remove |
destroy |
Changed Event Names
Some events were renamed to represent their meaning more clearly.
v2 | v3 |
---|---|
stateChange |
caretChange |
convertorAfterMarkdownToHtmlConverted |
beforePreviewRender |
convertorAfterHtmlToMarkdownConverted |
beforeConvertWysiwygToMarkdown |
5. Supported Browsers
From v3.0, only the browsers above Internet Explorer (IE) 11 will be supported. The previous version supported IE 10 and above, but the support range has been changed due to the low browser share and Prosemirror core module support.
Removed Features
1. Removed jQuery Wrapper
From v3.0, the jQuery Wrapper has been removed. To use jQuery, the user must wrap the @toast-ui/editor
package separately.
2. Removed Dependencies
Because original CodeMirror, squire, and to-mark dependencies were all removed, any code that accesses these modules directly or indirectly will no longer work. Most of the required features were added to the editor instance’s API, and it is recommended that users use the according APIs.
v2.x
const editor = new Editor(/* */);
console.log(editor.getCodeMirror()); // CodeMirror Instance
console.log(editor.getSquire()); // squire Instance
v3.0
const editor = new Editor(/* */);
console.log(editor.getCodeMirror()); // Uncaught TypeError
console.log(editor.getSquire()); // Uncaught TypeError
3. Removed APIs
Lastly, the following is a list of APIs removed from the v3.0.
Static Properties
Name | Type |
---|---|
isViewer |
{boolean} |
codeBlockManager |
{CodeBlockManager} |
WwCodeBlockManager |
{Class.<WwCodeBlockManager>} |
WwTableManager |
{Class.<WwTableManager>} |
WwTableSelectionManager |
{Class.<WwTableSelectionManager>} |
CommandManager |
{Class.<CommandManager>} |
Static Methods
Name | Type |
---|---|
getInstances |
{function} |
Instance Constructing Option
Name | Type |
---|---|
useDefaultHTMLSanitizer |
boolean |
Instance Method
Name | Type |
---|---|
setCodeBlockLanguages |
{function} |
afterAddedCommand |
{function} |
getCodeMirror |
{function} |
getSquire |
{function} |
getCurrentModeEditor |
{function} |
getUI |
{function} |