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.

  1. $ npm install @toast-ui/editor
  2. $ npm install @toast-ui/editor@<version>

Usages

  1. const Editor = require('@toast-ui/editor'); /* CommonJS */
  2. 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.

  1. import { EditorCore } from '@toast-ui/editor'; /* ES6 Module */
  2. const editorCore = new EditorCore({
  3. el
  4. // ...
  5. });
  6. const { mdEditor, mdPreview, wwEditor } = editorCore.getEditorElements();
  7. // ...

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.

  1. - dist/
  2. ├─ cdn/...
  3. ├─ i18n/...
  4. ├─ esm/
  5. ├─ index.js
  6. └─ index.js.map
  7. ├─ theme/
  8. └─ toastui-editor-dark.css
  9. ├─ toastui-editor-only.css
  10. ├─ toastui-editor-viewer.css
  11. ├─ toastui-editor.css
  12. ├─ toastui-editor.js
  13. └─ 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.

  1. {
  2. "main": "dist/toastui-editor.js",
  3. "module": "dist/esm/",
  4. "exports": {
  5. ".": {
  6. "import": "./dist/esm/index.js",
  7. "require": "./dist/toastui-editor.js"
  8. },
  9. "./viewer": {
  10. "import": "./dist/esm/indexViewer.js",
  11. "require": "./dist/toastui-editor-viewer.js"
  12. }
  13. }
  14. }

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.

  1. import Editor from '@toast-ui/editor';
  2. import '@toast-ui/editor/dist/toastui-editor.css';
  3. import '@toast-ui/editor/dist/theme/toastui-editor-dark.css';
  4. const editor = new Editor({
  5. el: document.querySelector('#editor'),
  6. previewStyle: 'vertical',
  7. height: '500px',
  8. initialValue: content,
  9. theme: 'dark',
  10. });

image

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

  1. <head>
  2. ...
  3. <link
  4. rel="stylesheet"
  5. href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.4/codemirror.min.css"
  6. />
  7. <link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.css" />
  8. ...
  9. </head>
  10. <body>
  11. ...
  12. <script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
  13. ...
  14. </body>

v3.0

  1. <head>
  2. ...
  3. <link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.css" />
  4. ...
  5. </head>
  6. <body>
  7. ...
  8. <script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
  9. ...
  10. </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

  1. const editor = new Editor({
  2. el: document.querySelector('#editor'),
  3. toolbarItems: [
  4. 'heading',
  5. 'bold',
  6. 'italic',
  7. 'strike',
  8. // The divider element had to be added to differentiate different groups.
  9. 'divider',
  10. 'hr',
  11. 'quote',
  12. 'divider',
  13. // ...
  14. ],
  15. // ...
  16. });

v3.0

  1. const editor = new Editor({
  2. el: document.querySelector('#editor'),
  3. toolbarItems: [
  4. ['heading', 'bold', 'italic', 'strike'],
  5. ['hr', 'quote'],
  6. // ...
  7. ],
  8. // ...
  9. });

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

  1. const popup = editor.getUI().createPopup({
  2. header: false,
  3. title: null,
  4. content: colorPickerContainer,
  5. className: 'tui-popup-color',
  6. target: editor.getUI().getToolbar().el,
  7. css: {
  8. width: 'auto',
  9. position: 'absolute'
  10. }
  11. });
  12. editor.eventManager.listen('focus', () => {
  13. popup.hide();
  14. // ...
  15. });
  16. editor.eventManager.listen('colorButtonClicked', () => {
  17. // ...
  18. });
  19. editor.eventManager.listen('closeAllPopup', () => {
  20. // ...
  21. });

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

  1. const popup = {
  2. name: 'color',
  3. tooltip: 'Text color',
  4. className: 'toastui-editor-toolbar-icons color',
  5. popup: {
  6. className: 'toastui-editor-popup-color',
  7. body: colorPickerContainer,
  8. style: { width: 'auto' },
  9. },
  10. };

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.

image

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.

  1. return {
  2. markdownCommands: {
  3. myCommand: (payload, state, dispatch) => {
  4. // ...
  5. },
  6. },
  7. wysiwygCommands: {
  8. myCommand: (payload, state, dispatch) => {
  9. // ...
  10. },
  11. },
  12. };

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.

  1. return {
  2. toHTMLRenderers: {
  3. // ...
  4. tableCell(node: MergedTableCellMdNode, { entering, origin }) {
  5. const result = origin!();
  6. // ...
  7. return result;
  8. },
  9. },
  10. toMarkdownRenderers: {
  11. // ...
  12. tableHead(nodeInfo) {
  13. const row = (nodeInfo.node as ProsemirrorNode).firstChild;
  14. let delim = '';
  15. if (row) {
  16. row.forEach(({ textContent, attrs }) => {
  17. const headDelim = createTableHeadDelim(textContent, attrs.align);
  18. delim += `| ${headDelim} `;
  19. // ...
  20. });
  21. }
  22. return { delim };
  23. },
  24. },
  25. };

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.

  1. return {
  2. // ...
  3. toolbarItems: [
  4. {
  5. groupIndex: 0,
  6. itemIndex: 3,
  7. item: toolbarItem,
  8. },
  9. ],
  10. };

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

  1. interface TextObject {
  2. setRange(range): void;
  3. setEndBeforeRange(range): void;
  4. expandStartOffset(): void;
  5. expandEndOffset(): void;
  6. getTextContent(): string;
  7. replaceContent(content) : void;
  8. deleteContent(): void;
  9. peekStartBeforeOffset(offset): Range;
  10. }

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.

  1. // Markdown's Position Information
  2. type EditorPos = [line: number, charactorOffset: number];
  3. // WYSIWYG's Position Information
  4. type EditorPos = number; // 오프셋
  5. // 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

  1. const editor = new Editor(/* */);
  2. console.log(editor.getCodeMirror()); // CodeMirror Instance
  3. console.log(editor.getSquire()); // squire Instance

v3.0

  1. const editor = new Editor(/* */);
  2. console.log(editor.getCodeMirror()); // Uncaught TypeError
  3. 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}