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 😊