preface

At the end of 2018, the author received a development task — to build an open platform for AI projects, in which the product documents were converted into MARKdown documents in HTML format. The document information is in the form of an Ajax interface for real-time updates. Therefore, the management background only needs to convert the content of the TextaREA form into HTML format through the Markdown parser, and then save both the Markdown content and the transformed HTML document to the database.

Once the basic requirements are complete, consider adding common editing capabilities for a better user experience. The improved version not only supports the common text editing functions, but also realizes the CONFIGURATION of the UI interface and the custom syntax parser. In order to benefit the public and accumulate some open source experience, the author packaged the React component, React-Markdown-editor-Lite, and released it to the open source community.

preview

Online experience harrychen0506. Making. IO/react – markd…

The characteristics of

  • Lightweight, React based
  • The UI can be configured, such as showing only the edit area or preview area
  • Support custom Markdown syntax parser, syntax highlighting, etc
  • Support common Markdown editing features such as bold, italic, etc…
  • Support edit area and preview area synchronous scrolling

Develop insight

  • Text editing

    Most common editors, including rich text editors, take advantage of the contenteditable property of elements such as divs and implement rich text editing with apis such as Selection, range, and execCommand. The implementation here is a bit more complicated, so “Why do rich text editors always say sinkholes?” This statement.

    While markdown editor, the core processing content is simple grammar plain text, relatively low complexity, and input tag has its own onSelect event, it is very convenient to obtain selection information (select starting position and select text value), so to achieve editing function, only need to change the content of text conversion. And then we reassemble the ends, and we’re done.

  • Markdown parsing

    I examined several popular markdown parsers in the community. The popular ones were markdown, markdown-it, marked, and so on. Considering extensibility and stability, I recommend markdown-it as the lexical parser for Markdown, because it has many plug-ins and supports syntax highlighting.

  • Synchronous rolling

    When selecting column edit, scroll the edit area on the left, and the preview area on the right will automatically scroll to the corresponding area. React-based Markdown input with 100 lines of code It only needs to calculate the proportion value of the maximum scroll range between the input box container element and the preview box container element first, and then make the corresponding proportion conversion according to the scrollTop of the active scrolling element itself, so as to know the scrollTop value of the other area.

  • About the UI

    • Font Awesome style was selected for the project’s Font library, and only the ICONS needed for the project were selected.
    • The editor’s entire CSS can be customized in the form of global coverage. For now, only grey themes are supported.
    • The editor display area includes the menu bar, editor, preview area, toolbar, and the default display area can be selected by configuring the component’s Config property.

Install

npm install react-markdown-editor-lite --save
Copy the code

Props

Property Description Type default Remarks
value markdown content String ‘ ‘ required
style component container style Object {height: ‘100%’} not required
config component config Object {view: {… }, logger: {… }} not required
config.view component UI Object {menu: true, md: true, html: true}
config.htmlClass Html section class attribute String <Empty string>
config.markdownClass Markdown section class attribute String <Empty string>
config.imageUrl default image url String ‘ ‘
config.linkUrl default link url String ‘ ‘
config.table table maximum value of row and column Object {maxRow: 4, maxCol: 6}
config.logger logger in order to undo or redo Object {interval: 3000}
config.synchScroll Does it support synch scroll? Boolean true
config.imageAccept Accept image extensions, such as .jpg,.png String <Empty string>
onChange emitting when editor has changed Function ({html, md}) => {} not required
onImageUpload when image uploaded, callback emitting will get image markdown text (file: File, callback: (url: string) => void) => void; ({file, callback}) => {} not required
renderHTML Render markdown text to HTML. You can return either string, function or Promise (text: string) => string|function|Promise none required

Example

'use strict';
import React from 'react'
import ReactDOM from 'react-dom'
import MdEditor from 'react-markdown-editor-lite'
import MarkdownIt from 'markdown-it'
import emoji from 'markdown-it-emoji'
import subscript from 'markdown-it-sub'
import superscript from 'markdown-it-sup'
import footnote from 'markdown-it-footnote'
import deflist from 'markdown-it-deflist'
import abbreviation from 'markdown-it-abbr'
import insert from 'markdown-it-ins'
import mark from 'markdown-it-mark'
import tasklists from 'markdown-it-task-lists'
import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-light.css'
// import 'highlight.js/styles/github.css'
import './index.less';

const MOCK_DATA = "Hello.\n\n * This is markdown.\n * It is fun\n * Love it or leave it."
export default class Demo extends React.Component {
  mdEditor = null
  mdParser = null
  constructor(props) {
    super(props)
    // initial a parser
    this.mdParser = new MarkdownIt({
      html: true.linkify: true.typographer: true.highlight: function (str, lang) {
        if (lang && hljs.getLanguage(lang)) {
          try {
            return hljs.highlight(lang, str).value
          } catch(__) {}}return ' ' // use external default escaping
      }
    })
    .use(emoji)
    .use(subscript)
    .use(superscript)
    .use(footnote)
    .use(deflist)
    .use(abbreviation)
    .use(insert)
    .use(mark)
    .use(tasklists, { enabled: this.taskLists })
    this.renderHTML = this.renderHTML.bind(this)
  }
  handleEditorChange({html, md}) {
    console.log('handleEditorChange', html, md)
  }
  handleImageUpload(file, callback) {
    const reader = new FileReader()
    reader.onload = (a)= > {      
      const convertBase64UrlToBlob = (urlData) = > {  
        let arr = urlData.split(', '), mime = arr[0].match(/ : (. *?) ; /) [1]
        let bstr = atob(arr[1])
        let n = bstr.length
        let u8arr = new Uint8Array(n)
        while (n--) {
          u8arr[n] = bstr.charCodeAt(n)
        }
        return new Blob([u8arr], {type:mime})
      }
      const blob = convertBase64UrlToBlob(reader.result)
      setTimeout((a)= > {
        // setTimeout simulates asynchronous uploading of images
        // When the image address is uploaded asynchronously, the calback callback (imageUrl string) is executed to write the image address to markdown
        callback('https://avatars0.githubusercontent.com/u/21263805?s=40&v=4')},1000)
    }
    reader.readAsDataURL(file)
  }
  renderHTML(text) {
    // Simulate asynchronous rendering of Markdown
    return new Promise((resolve) = > {
      setTimeout((a)= > {
        resolve(this.mdParser.render(text))
      }, 1000)
    })
  }
  handleGetMdValue = (a)= > {   
    this.mdEditor && alert(this.mdEditor.getMdValue())      
  }
  handleGetHtmlValue = (a)= > {    
    this.mdEditor && alert(this.mdEditor.getHtmlValue())
  }
  render() {
    return (      
      <div>
        <nav>
          <button onClick={this.handleGetMdValue} >getMdValue</button>  
          <button onClick={this.handleGetHtmlValue} >getHtmlValue</button>  
        </nav>
        <section style="height: 500px">
          <MdEditor 
            ref={node= > this.mdEditor = node}
            value={MOCK_DATA}
            style={{height: '400px'}}
            renderHTML={this.renderHTML}
            config={{
              view: {
                menu: true,
                md: true,
                html: true
              },
              imageUrl: 'https://octodex.github.com/images/minion.png'
            }}
            onChange={this.handleEditorChange} 
            onImageUpload={this.handleImageUpload}
          />
        </section>                        
      </div>)}}Copy the code

The last

Welcome to use and feedback, project address (github.com/HarryChen05…) , your thumbs-up will be my great motivation 😊