TOAST UI Editor `3.0.0-alpha.0` is released!
See original GitHub issueTOAST UI Editor new alpha version is released!
TOAST UI Editor 3.0.0-alpha.0 is released. As previously shared on the roadmap, v3.0 has improved many things, including model abstraction, custom syntax support, and plug-in systems. To this end, all new structural changes were made, and it took quite a long time. In this process, we wrote all new code using typescript to gain the advantage of static type checking and prevent potential bugs.
Here is the summary of some of the biggest changes in v3.0.👇
Model Abstraction(with ProseMirror)
we select a Prosmirror for Model Abstraction. Because Prosmirror provides the schema to customize and provides DOM manipulation as a high-level API, it has greatly aided the maintenance of editor code and the possibility of various feature extensions.
- Based on markdown, internal node schema were created and managed, and structurally
command,keymap, and so on, making it easier to understand features and rendering structures. - We could remove a large amount of pre/post processing code for the DOM manipulation in the WYSIWYG editor.
- This structural integration of core module has also eliminated many dependencies, resulting in a significant reduction in bundle capacity.(approximately 100 kb)
- It is possible to support syntax such as custom blocks, which in turn allows plugin system to be provided as a more concise option.
Below is an example of ListItem node. We define nodes in the form of abstracted model, and we can easily read related commands and keymaps in same domain.
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 {
// ...
return [
'li',
{
class: classNames.join(' '),
'data-task': task,
...(checked && { 'data-task-checked': checked }),
},
0,
];
},
};
}
commands(): EditorCommand {
// ...
}
keymaps() {
return {
Enter: this.commands()(),
};
}
}
Plugin Improvement
The previous method of creating plugins by accessing the editor’s internal properties or by directly manipulating DOM complicates the code and creates high level of coupling between the editor and the plugin. We removed all of these dependencies and changed the plugin to work with only a few specific information of the editor. In doing so, the plugin separated to operate with minimal information about the editor. In particular, codes that customize the toolbar or manipulate WYSIWYG’s nodes can be defined much more concisely in plug-ins. If you look at the newly changed color picker or merge table plugin, you can see that it is much simpler and more readable.
Below is an example of a color picker plugin. The option format is more concise and declarative than before.
export default function colorSyntaxPlugin(
context: PluginContext,
options: PluginOptions = {}
): PluginInfo {
// ...
return {
markdownCommands: {
color: ({ selectedColor }, { tr, selection, schema }, dispatch) => {
// ...
},
},
wysiwygCommands: {
color: ({ selectedColor }, { tr, selection, schema }, dispatch) => {
// ...
},
},
toolbarItems: [
{
groupIndex: 0,
itemIndex: 3,
item: toolbarItem,
},
],
toHTMLRenderers: {
htmlInline: {
span(node: MdLikeNode, { entering }: Context) {
return entering
? { type: 'openTag', tagName: 'span', attributes: node.attrs }
: { type: 'closeTag', tagName: 'span' };
},
},
},
};
}
UI structure
The editor’s UI was often directly exposed to the outside, and internally it was very difficult to manage due to unnecessary inheritance structures. To improve this, we introduce a declarative UI that is being adopted by modern front-end frameworks like Vue, React. The spaghetti codes inside have been reduced much more concisely due to our declarative codes based on very small virtual DOM. I and my colleagues, who are already somewhat familiar with Vue, React, were able to easily expand the UI in this structure, which gave us quite a good experience.
Below is an example of a Link popup UI.
export class LinkPopupBody extends Component<Props> {
// ...
mounted() {
this.initialize();
}
render() {
return html`
<div>
<label for="toastuiLinkUrlInput">${i18n.get('URL')}</label>
<input
id="toastuiLinkUrlInput"
type="text"
ref=${(el: HTMLInputElement) => (this.refs.url = el)}
/>
<label for="toastuiLinkTextInput">${i18n.get('Link text')}</label>
<input
id="toastuiLinkTextInput"
type="text"
ref=${(el: HTMLInputElement) => (this.refs.text = el)}
/>
<div class="${cls('button-container')}">
<button type="button" class="${cls('close-button')}" onClick=${this.props.hidePopup}>
${i18n.get('Cancel')}
</button>
<button type="button" class="${cls('ok-button')}" onClick=${this.execCommand}>
${i18n.get('OK')}
</button>
</div>
</div>
`;
}
}
Document
Currently, documents have not yet been prepared for our new alpha version. We will update the migration guide and tutorial guide sequentially. First, a description of custom block and widget nodes can be seen briefly as below.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:25
- Comments:11 (5 by maintainers)

Top Related StackOverflow Question
Is both editor and viewer available in Next.js(ssr) with this update? I’m really looking forward to it.
@stfenjobs @ats1999 React wrapper will probably be out on official release(maybe next week or week after next). Thanks!