> 文章列表 > react项目中自定义一个markdown编辑器

react项目中自定义一个markdown编辑器

react项目中自定义一个markdown编辑器

react项目中自定义一个markdown编辑器

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, // h1code: true, // 代码块preview: true, // 预览}此时, 仅仅显示此三个功能键注:传入空对象则不显示工具栏*/toolbar: {h1: true, // h1h2: true, // h2h3: true, // h3h4: true, // h4img: 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"}}> <ForEditorplaceholder="请输入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, // h1h2: true, // h2h3: true, // h3h4: true, // h4img: 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><Modaltitle="Markdown内容预览"width="60%"okText="关闭"open={visible}onOk={() => setVisible(false)}onCancel={() => setVisible(false)}cancelButtonProps={{ style: { display: "none" } }}><MdPreview content={value} /></Modal><ForEditorplaceholder="请输入Markdown文本"height={360}ref={mdRef}lineNum={true}toolbar={toolbar}value={value}onChange={handleChange}addImg={_file => addImg(_file)}/></Fragment>)}</div>);
}

组件最终样式

react项目中自定义一个markdown编辑器

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