🔩 Custom Block Node And HTML Node

The TOAST UI Editor (henceforth referred to as ‘Editor’) follows the CommonMark specification, and also supports the GFM specification. But what if you want to use a specific syntax that is not supported by CommonMark or GFM? For example, you might want to use LaTeX syntax or render elements such as charts in Markdown. The editor provides the option to define a custom block node for this usability.

Custom Block Node

The editor provides the customHTMLRenderer option that can be customized when converting Markdown Abstract Syntax Tree(AST) to HTML text. Using customHTMLRenderer option, rendering results of nodes supported by CommonMark or GFM can be customized like table and heading. Custom block nodes can also be defined using this customHTMLRenderer option.

The following code defines a custom block node that renders math typesetting using KaTeX, a library that supports LaTex syntax.

  1. const editor = new Editor({
  2. el: document.querySelector('#editor'),
  3. customHTMLRenderer: {
  4. latex(node) {
  5. const generator = new latexjs.HtmlGenerator({ hyphenate: false });
  6. const { body } = latexjs.parse(node.literal, { generator }).htmlDocument();
  7. return [
  8. { type: 'openTag', tagName: 'div', outerNewLine: true },
  9. { type: 'html', content: body.innerHTML },
  10. { type: 'closeTag', tagName: 'div', outerNewLine: true }
  11. ];
  12. },
  13. }
  14. });

The latex function property was written in the customHTMLRenderer option, which returns HTML to be rendered in token format. It is easy to use because it configures options in almost the same form as when customizing a markdown node. The code above is rendered in the Markdown Editor as follows.

image

As you can see in the image above, in order to use a custom block node in a markdown editor, text must be entered within a block enclosed by the symbol. Blocks wrapped with symbols are parsed from editor to custom block nodes. In addition, to indicate which custom block node it is, the node name defined by the customHTMLRenderer option must be written next to the $$ symbol.

  1. // The node name must be written next to the $$ symbol.
  2. $$latex
  3. \documentclass{article}
  4. \begin{document}
  5. $
  6. f(x) = \int_{-\infty}^\infty \hat f(\xi)\,e^{2 \pi i \xi x} \, d\xi
  7. $
  8. \end{document}
  9. $$

WYSIWYG

The custom block node in the WYSIWYG Editor works like the image below.

image

In WYSIWYG Editor, the custom block node is rendered in the same result as a markdown preview, and can be changed by clicking on the node and using the edit button that appears when selected. Because the custom block node are eventually parsed based on specific text, editing in the WYSIWYG Editor is also based on text. This operation is different from general WYSIWYG editors, but it is more ideal because the TOAST UI Editor supports WYSIWYG editors based on markdown.

HTML Node

CommonMark uses < and > characters to write nodes that are not supported by default in HTML text. (CommonMark Raw HTML Spec)

Because Markdown Editor also follows these specifications, HTML text are rendered correctly in the Markdown preview.

image

WYSIWYG

Unfortunately, WYSIWYG Editor cannot render HTML nodes properly. The editor internally manages nodes supported by the WYSIWYG Editor as abstracted model object. Nodes that are supported by WYSIWYG Editor are nodes that are supported by CommonMark and GFM (such as heading, list, strike and others) and custom block node.

image

The iframe node in the example image above is not a node supported by WYSIWYG Editor. Therefore, if you want to use iframe node in WYSIWYG Editor, you need to set it up using customHTMLRenderer option.

  1. const editor = new Editor({
  2. el: document.querySelector('#editor'),
  3. customHTMLRenderer: {
  4. htmlBlock: {
  5. iframe(node) {
  6. return [
  7. { type: 'openTag', tagName: 'iframe', outerNewLine: true, attributes: node.attrs },
  8. { type: 'html', content: node.childrenHTML },
  9. { type: 'closeTag', tagName: 'iframe', outerNewLine: true },
  10. ];
  11. },
  12. }
  13. },
  14. });

HTML nodes are defined in the customHTMLRenderer.htmlBlock property. To distinguish it from the custom block nodes described above, it should be configured within the htmlBlock property. If you run the example code, iframe node will be rendered correctly in WYSIWYG as shown in the image below.

image

If you want to use an inline HTML node, it should be configured in the customHTMLRenderer.htmlInline property.

  1. const editor = new Editor({
  2. el: document.querySelector('#editor'),
  3. customHTMLRenderer: {
  4. htmlBlock: {
  5. iframe(node) {
  6. return [
  7. { type: 'openTag', tagName: 'iframe', outerNewLine: true, attributes: node.attrs },
  8. { type: 'html', content: node.childrenHTML },
  9. { type: 'closeTag', tagName: 'iframe', outerNewLine: true },
  10. ];
  11. },
  12. },
  13. htmlInline: {
  14. big(node, { entering }) {
  15. return entering
  16. ? { type: 'openTag', tagName: 'big', attributes: node.attrs }
  17. : { type: 'closeTag', tagName: 'big' };
  18. },
  19. },
  20. },
  21. });