Markdown 是一种轻量级标记语言。
Markdown是一种简单的格式化文本的方法,在任何设备上看起来都很棒。它不会做任何花哨的事情,比如改变字体大小、颜色或类型——只是基本的,使用你已经知道的键盘符号。
它还允许人们使用易读易写的纯文本格式编写文档,然后转换成有效的 XHTML(或者HTML)文档。
这种语言吸收了很多在电子邮件中已有的纯文本标记的特性。
由于 Markdown 的轻量化、易读易写特性,并且对于图片,图表、数学公式都有支持,许多网站都广泛使用 Markdown 来撰写帮助文档或是用于论坛上发表消息。
那么在react中,我们如何使用markdown编辑器来写开发文档?查阅资料及实践后,最终形成以下方案,记录下来以供参阅。
插件依赖及目录
依赖插件:
- 编辑器使用 for-editor,
- 内容预览及展示采用 react-markdown。
- 数学公式支持及语法解析使用 remark-math、rehype-katex,数学公式的样式展示需要 katex.min.css 文件支持,见下文。
- 引入 rehype-raw 解析HTML文本(因为可能仍需解析之前输入的富文本内容)。
依赖安装
yarn add for-editor react-markdown remark-math rehype-katex rehype-raw
package.json:组件涉及的依赖及版本
"dependencies": {
"for-editor": "^0.3.5", // Markdown编辑
"react-markdown": "^8.0.6", // Markdown预览
"rehype-katex": "^6.0.2", // 数学公式katex语法
"rehype-raw": "^6.1.1", // 支持HTML语法解析
"remark-math": "^5.1.1", // 支持数学公式
}
目录结构
├─components
|── MarkdownEditor
├─ Index.js
└─ MarkdownPreview.js
相关代码及说明
Index.js:编辑器的主体代码
for-editor 是一个基于 react 的 markdown 语法编辑器
for-editor官网地址:https://www.npmjs.com/package/for-editor
Api
属性
name | type | default | description |
---|---|---|---|
value | String | - | 输入框内容 |
placeholder | String | 开始编辑… 占位文本 | |
lineNum | Boolean | true | 是否显示行号 |
style | Object | - | 编辑器样式 |
height | String | 600px | 编辑器高度 |
preview | Boolean | false | 预览模式 |
expand | Boolean | false | 全屏模式 |
subfield | Boolean | false | 双栏模式(预览模式激活下有效) |
language | String | zh-CN | 语言(支持 zh-CN:中文简体, en:英文) |
toolbar | Object | 如下 | 自定义工具栏 |
*
默认工具栏按钮全部开启, 传入自定义对象
例如: {
h1: true, // h1
code: true, // 代码块
preview: true, // 预览
}
此时, 仅仅显示此三个功能键
注:传入空对象则不显示工具栏
*/
toolbar: {
h1: true, // h1
h2: true, // h2
h3: true, // h3
h4: true, // h4
img: true, // 图片
link: true, // 链接
code: true, // 代码块
preview: true, // 预览
expand: true, // 全屏
/* v0.0.9 */
undo: true, // 撤销
redo: true, // 重做
save: true, // 保存
/* v0.2.3 */
subfield: true, // 单双栏模式
}
事件
name | params 参数 | default | description |
---|---|---|---|
onChange | String: value | - | 内容改变时回调 |
onSave | String: value | - | 保存时回调 |
addImg | File: file | - | 添加图片时回调 |
图片上传
export default function MarkdownEditor({ value, onChangeContext, readOnly }) {
// 上传图片
const addImg = (_file) => {
console.log(_file)
mdRef.current.$img2Url(_file.name, 'file_url');
};
return (
<div style={{position: "relative"}}>
<ForEditor
placeholder="请输入Markdown文本"
addImg={_file => addImg(_file)}
/>
</div>
);
}
Index.js完整代码如下:
import React, { Fragment, useRef, useState } from "react";
import { Button, Modal } from "antd";
import ForEditor from "for-editor";
import MdPreview from "../MarkdownEditor/MarkdownPreview";
/** https://github.com/kkfor/for-editor
* @param {string} value Markdown文本内容
* @param {() => void} onChange 更改内容方法
* @param {boolean} readOnly 只读状态
*/
export default function MarkdownEditor({ value, onChangeContext, readOnly }) {
const [visible, setVisible] = useState(false); // 预览弹框状态
const mdRef = useRef(null); // 编辑器ref
// 工具栏菜单
const toolbar = {
h1: true, // h1
h2: true, // h2
h3: true, // h3
h4: true, // h4
img: true, // 图片
link: true, // 链接
code: true, // 代码块
// preview: true, // 预览
expand: true, // 全屏
/* v0.0.9 */
undo: true, // 撤销
redo: true, // 重做
save: true, // 保存
// subfield: true, // 单双栏模式
};
// 上传图片
const addImg = (_file) => {
console.log(_file)
mdRef.current.$img2Url(_file.name, 'file_url');
};
const handleChange = (value)=> {
onChangeContext(value)
}
return (
<div style={{position: "relative"}}>
{readOnly ? (
<MdPreview content={value} />
) : (
<Fragment>
<Button style={{position: "absolute",right: "44px", top: "11px"}}
size="small"
onClick={() => setVisible(true)}
>
预览
</Button>
<Modal
title="Markdown内容预览"
width="60%"
okText="关闭"
open={visible}
onOk={() => setVisible(false)}
onCancel={() => setVisible(false)}
cancelButtonProps={{ style: { display: "none" } }}
>
<MdPreview content={value} />
</Modal>
<ForEditor
placeholder="请输入Markdown文本"
height={360}
ref={mdRef}
lineNum={true}
toolbar={toolbar}
value={value}
onChange={handleChange}
addImg={_file => addImg(_file)}
/>
</Fragment>
)}
</div>
);
}
组件最终样式
MarkdownPreview.js:预览的主体代码
github地址:https://github.com/remarkjs/react-markdown
import React from "react";
import ReactMarkdown from "react-markdown";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import rehypeRaw from "rehype-raw";
/**
* @param {string} content Markdown文本内容
* @param {Boolean} escapeHtml 是否转义html语法,参数为true后转义显示html源码
*/
export default function MarkdownPreview({ content }) {
return (
<ReactMarkdown
remarkPlugins={[remarkMath]}
escapeHtml={true} // escapeHtml 是否转义html语法,参数为true后转义显示html源码
rehypePlugins={[rehypeKatex, rehypeRaw]}
>
{content}
</ReactMarkdown>
);
}
使用组件
import React, {forwardRef, useEffect, useState } from "react";
import { Button, Select, Space, Form, Input } from "antd";
// 引入自定义的MarkdownEditor组件
import MarkdownEditor from "./MarkdownEditor/Index";
const NewsAudit = forwardRef(({},ref)=> {
const [context, setcontext] = useState("")
const onChangeContext = (value) => {
console.log(`onChangeContext`);
console.log(value);
setcontext(value)
};
return (
<div>
// 使用自定义的MarkdownEditor组件
<MarkdownEditor onChangeContext = {onChangeContext} readOnly = {false} value= {context}></MarkdownEditor>
</div>
);
})
export default NewsAudit;
参考文档
- https://www.jianshu.com/p/a4746fce6ab3