共计 2588 个字符,预计需要花费 7 分钟才能阅读完成。
前言
笔者在 18 年年末的时候接到一个开发任务——搭建一个 AI 项目的开放平台,其中的产品文档为转化为 HTML 格式的 markdown 文档。考虑到文档的即时更新,将文档信息做成了 Ajax 接口的形式。因此管理后台只需将 textarea 表单的内容通过 markdown 解析器进行 HTML 格式转化,然后将 markdown 内容和经转化的 HTML 文档都保存到数据库即可。
基本需求完成后,为了更好的用户体验,考虑将常用的编辑功能添加进来。改进版不仅支持了常用的文本编辑功能,还实现的 UI 界面的配置化。本着造福伸手党的目的,以及积累些开源经验,笔者将该 react 组件 react-markdown-editor-lite 进行了封装改造,并且发布到了开源社区。
预览
在线体验 https://harrychen0506.github.io/react-markdown-editor-lite/
特点
轻量、基于 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>
)
}
}
最后
欢迎大家使用和反馈,项目地址 (https://github.com/HarryChen0…,你的点赞将是我莫大的动力????