手把手教你造一個基於React的markdown編輯器

HarryChen0506發表於2019-02-25

前言

筆者在18年年末的時候接到一個開發任務——搭建一個AI專案的開放平臺,其中的產品文件為轉化為HTML格式的markdown文件。考慮到文件的即時更新,將文件資訊做成了Ajax介面的形式。因此管理後臺只需將textarea表單的內容通過markdown解析器進行HTML格式轉化,然後將markdown內容和經轉化的HTML文件都儲存到資料庫即可。

基本需求完成後,為了更好的使用者體驗,考慮將常用的編輯功能新增進來。改進版不僅支援了常用的文字編輯功能,還實現的UI介面的配置化。本著造福伸手黨的目的,以及積累些開源經驗,筆者將該react 元件 react-markdown-editor-lite 進行了封裝改造,並且釋出到了開源社群。

預覽

線上體驗 harrychen0506.github.io/react-markd…

image

特點

  • 輕量、基於React
  • UI可配置, 如只顯示編輯區或預覽區
  • 支援常用的markdown編輯功能,如加粗,斜體等等...
  • 支援編輯區和預覽區同步滾動

開發心得

  • 文字編輯

    大多數常見的編輯器,包括富文字編輯器,利用了某些元素如div的contenteditable屬性,配合selection、range、execCommand等API,實現了富文字編輯功能。這裡面的實現比較複雜,所以有了"為什麼都說富文字編輯器是天坑?"這個說法。

    而markdown編輯器,核心的處理內容為簡單語法的純文字,複雜度相對來說比較低,並且input標籤自帶onSelect事件,可以很方便的獲取選擇資訊(選擇起始位置和選擇文字值),因此要想實現編輯功能,只需將要改動的內容進行文字轉換,然後進行重新拼接首尾,大功告成。

  • markdown解析

    考察了幾個社群流行的markdown解析器,比較流行的有markdown, markdown-it, marked 等等。綜合考慮擴充套件性以及穩定性,筆者選擇了markdown-it作為markdown的詞法解析器,結果也比較滿意。

  • 同步滾動

    當選擇分欄編輯的時候,滾動左側的編輯區,右側的預覽區能自動滾動到對應的區域。方案參考了《手把手教你用 100行程式碼實現基於 react的 markdown 輸入 + 即時預覽線上編輯器(一)》。只需先計算出輸入框容器元素與預覽框容器元素之間最大scroll範圍的比例值,然後根據主動滾動元素自身的scrollTop做相應的比例換算,即可知道對方區域的scrollTop值。

  • 關於UI

    • 專案的字型庫選擇了Font Awesome風格,並且只選取了專案所需要的一些圖示。
    • 編輯器的整體css均可通過全域性覆蓋的形式進行自定義。目前暫時只支援灰色主題。
    • 編輯器的顯示區域包括選單欄,編輯器,預覽區,工具欄,通過配置元件的config屬性,可以選擇預設的展示區域。

Install

npm install react-markdown-editor-lite --save
複製程式碼

Props

Property Description Type default Remarks
value markdown content String ''
style component container style Object {height: '100%'}
config component config Object {view: {...}, logger: {...}}
config.view component UI Object {menu: true, md: true, html: true}
config.imageUrl default image url String ''
config.linkUrl default link url String ''
config.logger logger in order to undo or redo Object {interval: 3000}
onChange emitting when editor has changed Function ({html, md}) => {}

Example

'use strict';
import React from 'react'
import ReactDOM from 'react-dom'
import MdEditor from 'react-markdown-editor-lite'

const mock_content = "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
  handleEditorChange ({html, md}) {    
    console.log('handleEditorChange', html, md)
  }
  handleGetMdValue = () => {   
    this.mdEditor && alert(this.mdEditor.getMdValue())      
  }
  handleGetHtmlValue = () => {    
    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_content}
            style={{height: '400px'}}
            config={{
              view: {
                menu: true,
                md: true,
                html: true
              },
              imageUrl: 'https://octodex.github.com/images/minion.png'
            }}
            onChange={this.handleEditorChange} 
          />
        </section>                        
      </div>      
    )
  }
}
複製程式碼

最後

歡迎大家使用和反饋,專案地址 (github.com/HarryChen05…), 你的點贊將是我莫大的動力?

相關文章