筆者在18年年底的時候接到一個開發任務——搭建一個AI項目的開放平臺,其中的產品文檔爲轉化爲HTML格式的markdown文檔。考慮到文檔的即時更新,將文檔信息作成了Ajax接口的形式。所以管理後臺只需將textarea表單的內容經過markdown解析器進行HTML格式轉化,而後將markdown內容和經轉化的HTML文檔都保存到數據庫便可。css
基本需求完成後,爲了更好的用戶體驗,考慮將經常使用的編輯功能添加進來。改進版不只支持了經常使用的文本編輯功能,還實現的UI界面的配置化。本着造福伸手黨的目的,以及積累些開源經驗,筆者將該react 組件 react-markdown-editor-lite 進行了封裝改造,而且發佈到了開源社區。html
在線體驗 https://harrychen0506.github.io/react-markdown-editor-lite/node
大多數常見的編輯器,包括富文本編輯器,利用了某些元素如div的contenteditable屬性,配合selection、range、execCommand等API,實現了富文本編輯功能。這裏面的實現比較複雜,因此有了"爲何都說富文本編輯器是天坑?"這個說法。react
而markdown編輯器,核心的處理內容爲簡單語法的純文本,複雜度相對來講比較低,而且input標籤自帶onSelect事件,能夠很方便的獲取選擇信息(選擇起始位置和選擇文本值),所以要想實現編輯功能,只需將要改動的內容進行文本轉換,而後進行從新拼接首尾,大功告成。git
考察了幾個社區流行的markdown解析器,比較流行的有markdown, markdown-it, marked 等等。綜合考慮擴展性以及穩定性,筆者選擇了markdown-it做爲markdown的詞法解析器,結果也比較滿意。github
當選擇分欄編輯的時候,滾動左側的編輯區,右側的預覽區能自動滾動到對應的區域。方案參考了《手把手教你用 100行代碼實現基於 react的 markdown 輸入 + 即時預覽在線編輯器(一)》。只需先計算出輸入框容器元素與預覽框容器元素之間最大scroll範圍的比例值,而後根據主動滾動元素自身的scrollTop作相應的比例換算,便可知道對方區域的scrollTop值。數據庫
關於UInpm
npm install react-markdown-editor-lite --save
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}) => {} |
'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> ) } }
歡迎你們使用和反饋,項目地址 (https://github.com/HarryChen0..., 你的點贊將是我莫大的動力😊markdown