手把手教你造一個基於React的markdown編輯器
筆者在18年年末的時候接到一個開發任務——搭建一個AI專案的開放平臺,其中的產品文件為轉化為HTML格式的markdown文件。考慮到文件的即時更新,將文件資訊做成了Ajax介面的形式。因此管理後臺只需將textarea表單的內容通過markdown解析器進行HTML格式轉化,然後將markdown內容和經轉化的HTML文件都儲存到資料庫即可。
基本需求完成後,為了更好的使用者體驗,考慮將常用的編輯功能新增進來。改進版不僅支援了常用的文字編輯功能,還實現的UI介面的配置化。本著造福伸手黨的目的,以及積累些開源經驗,筆者將該react 元件 react-markdown-editor-lite 進行了封裝改造,並且釋出到了開源社群。
預覽
線上體驗 harrychen0506.github.io/react-markd…

特點
- 輕量、基於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… ), 你的點贊將是我莫大的動力:blush: