Application 2017-10-01

React+marked+highlight

Integrate React markdown editor with marked and highlight.js for syntax-highlighted code block rendering.

Read in: ja
React+marked+highlight

I created a Markdown editor in React instead of a WYSIWYG editor.

Most of the source code is based on Introduction to React.

Here is a rough GIF sample (:3」∠) markdown.gif

Environment

Preparation

Install marked and highlight.js using bower

bower install marked bower install highlightjs

Please install them in your own environment and set the paths accordingly. Make sure to use highlightjs, not highlight. They are different, and I got stuck for about an hour because of this mistake... (cry)

Implementation

The HTML looks like this↓

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link href="path/to/monokai.css" rel="stylesheet">
<link href="path/to/style.css" rel="stylesheet">
</head>
    <body>

    <div class="markdown-component">

        <h1>React Markdown Editor</h1>

        <div id="content"></div>

    </div><!-- .component -->


    <!-- scripts -->
    <script src="path/to/react.js"></script>
    <script src="path/to/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
    <script src="path/to/marked.min.js"></script>
    <script src="path/to/highlight.pack.min.js"></script>
    <script type="text/babel" src="path/to/markdown.js"></script>

    </body>
</html>

I like the Monokai color theme for syntax highlighting, so I set the Monokai stylesheet. For Babel, I am using a CDN this time, but you can also install it via bower.

Now, let's create the React components. As mentioned earlier, most of the code is based on Introduction to React, so I recommend reading it first.

I just added the configuration code for highlight.js to the reference source. (Not doing much work, lol)

markdown.js

var App = React.createClass({
    getInitialState: function() {
        return {
            markdown: ""
        };
    },

    updateMarkdown: function(markdown) {
        this.setState({
            markdown: markdown
        });
    },

    render: function() {
        return (
            <div>
                <TextInput onChange={this.updateMarkdown}/>
                <Markdown markdown={this.state.markdown}/>
            </div>
        );
    }
});

var TextInput = React.createClass({
    propTypes: {
        onChange: React.PropTypes.func.isRequired
    },

    _onChange: function(e) {
        this.props.onChange(e.target.value);
    },

    render: function() {
        return (
            <textarea onChange={this._onChange}></textarea>
        );
    }
});

var Markdown = React.createClass({
    componentDidUpdate: function() {
        marked.setOptions({
            highlight: function(code, lang) {
                return hljs.highlightAuto(code, [lang]).value;
            }
        });
    },

    propTypes: {
        markdown: React.PropTypes.string.isRequired
    },

    render: function() {
        var html = marked(this.props.markdown);

        return (
            <div dangerouslySetInnerHTML={{__html: html}}></div>
        );
    }
});

ReactDOM.render(
    <App />,
    document.getElementById("content")
);

The components are divided into three parts: the text input component, the Markdown output component, and the component that integrates them.

The parsing of Markdown is done using the marked function. In the componentDidUpdate section, I set the options to use highlight.js. You can find the method for setting options in the highlight.js README.

dangerouslySetInnerHTML is a property used to sanitize data for XSS prevention.

Thoughts

This is my first time creating an editor, and it’s amazing how quickly it can be done with libraries~ (:3」∠)

ES6 Version

Recently, I studied ES6, so I rewrote it. I wasn’t sure how to handle propTypes, so I omitted that part, lol.

/**
 *
 * Editor
 *
 */

import React from 'react';
import ReactDOM from 'react-dom';

export default class Editor extends React.Component{
  constructor(props) {
    super(props);

    this.state = {
      markdown: ''
    };

    this.updateMarkdown = this.updateMarkdown.bind(this);
  }

  updateMarkdown(markdown) {
    this.setState({
      markdown: markdown
    });
  }

  render() {
    return (
      <div>
        <TextInput onChange={this.updateMarkdown}/>
        <Markdown markdown={this.state.markdown}/>
      </div>
    );
  }
};

class TextInput extends React.Component{
  constructor(props) {
    super(props);

    this._onChange = this._onChange.bind(this);
  }

  _onChange(e) {
    this.props.onChange(e.target.value);
  }

  render() {
    return (
      <textarea onChange={this._onChange}></textarea>
    );
  }
};

class Markdown extends React.Component{
  constructor(props) {
    super(props);
  }

  componentDidUpdate() {
    marked.setOptions({
        highlight: function(code, lang) {
          return hljs.highlightAuto(code, [lang]).value;
        }
    });
  }

  render() {
    var html = marked(this.props.markdown);

    return (
      <div dangerouslySetInnerHTML={{__html: html}}></div>
    );
  }
};
Tags: ES6 React highlightjs markdown marked
Share: 𝕏 Post Facebook Hatena
✏️ View source / Discuss on GitHub
☕ Support

If you enjoy this blog, consider supporting it. Every bit helps keep it running!


Related Articles