前言
筆者在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…), 你的點贊將是我莫大的動力?