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

属性

nametypedefaultdescription
valueString-输入框内容
placeholderString开始编辑… 占位文本
lineNumBooleantrue是否显示行号
styleObject-编辑器样式
heightString600px编辑器高度
previewBooleanfalse预览模式
expandBooleanfalse全屏模式
subfieldBooleanfalse双栏模式(预览模式激活下有效)
languageStringzh-CN语言(支持 zh-CN:中文简体, en:英文)
toolbarObject如下自定义工具栏
*
  默认工具栏按钮全部开启, 传入自定义对象
  例如: {
    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, // 单双栏模式
}

事件

nameparams 参数defaultdescription
onChangeString: value-内容改变时回调
onSaveString: value-保存时回调
addImgFile: 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/12131.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

UE5.1.1创建C++工程失败解决办法

闲来无事&#xff0c;更新了一下UE5.1.1&#xff0c;妈蛋创建C项目居然失败&#xff0c; 错误截图如下&#xff1a; 妈蛋&#xff0c;后面一堆乱码&#xff0c;鬼知道是啥错误&#xff01; 咋解决&#xff1f;步步高打火机&#xff0c;直接复制第一段的Running后面的代码到cmd…

【Linux系统管理进程,运行,挂起,杀死进程和crontab计划任务表的使用以及实验的心得体会】

实验 &#xff08;1&#xff09;显示本用户的进程&#xff0c;重定向到file1 top命令如果不加限制&#xff0c;默认是查看所有用户的进程情况top -u [用户名] 可以查看该用户名的所有进程 &#xff08;2&#xff09;显示本用户所有进程&#xff0c;重定向到file2 top命令如果…

打造智慧医疗新生态:互联网医院系统源码分析

在数字化时代&#xff0c;医疗行业也在不断地探索新的模式和方法&#xff0c;以更好地服务于人民群众。互联网医院系统作为一种新型医疗服务模式&#xff0c;受到了广泛的关注和热议。下文&#xff0c;小编将为大家介绍互联网医院系统的概念、特点以及如何利用互联网医院系统源…

【JAVAEE】网络原理之网络发展史

目录 &#x1f381;1. 独立模式 &#x1f383;2. 网络互连 &#x1f388;2.1 局域网 LAN ✨2.1.1 基于网线直连 &#x1f451;2.2.2 基于集线器组建 &#x1f48b;2.2.3 基于交换机组建 &#x1f457;2.2.4 基于交换机与路由器组建 &#x1f388;2.2 广域网 21世纪是一…

香橙派4LTS和树莓派4B构建K8S集群实践之一:K8S安装

目录 1. 说明 1.1 软硬件环境 1.2 设计目标 2 实现 2.1 准备工作 - 香橙派 (k8s-master-1) - 树莓派 (k8s-node-1) - 两派都要干的事 2.2 containerd 安装与设置 2.3 安装 3 遇到的问题 3.1 k8s-master-1 3.2 k8s-node-1 4 相关命令 5 Tips 6 参考 1. 说明 …

反向代理自建教程:你懂的

一、为什么需要自建反代 OpenAI提供了两种访问方式&#xff0c;一种是直接在ChatGPT网页端使用的Access Token方式&#xff0c;这种方式可以免费使用GPT-3.5模型&#xff0c;只需要登录即可使用。但缺点是不稳定&#xff0c;且无法扩展。另一种是使用API&#xff0c;注册用户可…

SpringBoot自动装配原理(附面试快速答法)

文章目录SpringBoot自动装配原理1. 从调用SpringApplication构造器方法开始2. 解析启动类4.按需装配4.1 分析dubbo自动装配5. 如果定义自己的starter6. 面试答法SpringBoot自动装配原理 之前面试被问到这个题目&#xff0c;只会答一些spi、AutoConfigration注解、Import之类的&…

询问ChatGPT的高质量答案艺术——提示工程指南(更新中……)

目录前言一、提示工程简介二、提示技巧2-1、生成法律文件2-2、添加提示技巧三、角色扮演3-1、智能手机产品描述3-2、添加角色扮演四、标准提示4-1、写一篇有关于新智能手机的评论4-2、添加标准提示、角色提示、种子词提示等等五、示例很少、或者没有示例5-1、生成一个手机配置六…

机器学习中的数学原理——过拟合、正则化与惩罚函数

通过这篇博客&#xff0c;你将清晰的明白什么是过拟合、正则化、惩罚函数。这个专栏名为白话机器学习中数学学习笔记&#xff0c;主要是用来分享一下我在 机器学习中的学习笔记及一些感悟&#xff0c;也希望对你的学习有帮助哦&#xff01;感兴趣的小伙伴欢迎私信或者评论区留言…

Docker系列 基于OpenAI API自建ChatGPT

转自我的博客文章https://blognas.hwb0307.com/linux/docker/4201&#xff0c;内容更新仅在个人博客可见。欢迎关注&#xff01; 前言 我用帐号/密码使用chatGPT已经有一段时间。但是&#xff0c;我有几个私交较密的朋友&#xff0c;他们并不具备使用chatGPT的条件&#xff1b…

【无功优化】基于多目标差分进化算法的含DG配电网无功优化模型【IEEE33节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

java遍历字符串的方法

在 java中&#xff0c;我们需要遍历字符串&#xff0c;如何遍历呢&#xff1f;首先我们先了解一下遍历的概念&#xff1a; 在我们的计算机中&#xff0c;存储的都是二进制数据&#xff0c;为了方便存储和管理&#xff0c;我们把一段数据分成多个字符串。在 java中&#xff0c;遍…

网络IO(non-blocking io)基础

BIO&#xff08;blocking io&#xff09; 传统的网络io模式&#xff0c;面向流&#xff0c;一个线程对接一个会话&#xff0c;因此高并发时会因线程阻塞而性能低效 Java代码&#xff1a; public class BIO implements Connector {private Integer port 8080;Overridepublic v…

SAP Business Technology Platform (BTP)的架构理解

查资料看到的&#xff0c;转一下&#xff0c;附上链接&#xff1a; SAP Business Technology Platform (BTP)的架构理解 长期以来&#xff0c;我在与客户和伙伴的沟通交流中发现大家依然对SAP业务技术平台 – SAP Business Technology Platform (以下简称BTP)纯有各种疑惑&…

Web 攻防之业务安全:密码找回流程绕过测试.(利用链接跳到后面去)

Web 攻防之业务安全&#xff1a;密码找回流程绕过测试 业务安全是指保护业务系统免受安全威胁的措施或手段。广义的业务安全应包括业务运行的软硬件平台&#xff08;操作系统、数据库&#xff0c;中间件等&#xff09;、业务系统自身&#xff08;软件或设备&#xff09;、业务所…

13.vue-cli

单页面应用程序&#xff1a;所有的功能只在index.html中完成 vue-cli是vue版的webpack 目录 1 安装vue-cli 2 创建项目 3 使用预设 4 删除预设 5 开启项目 6 项目文件内容 6.1 node_moduls 中是项目依赖的库 6.2 public 6.2.1 favicon.ico 是浏览器页签内部…

Android屏幕适配dp、px两套解决办法

最新最全文章(2018-08-25)&#xff1a;Android dp方式的屏幕适配-原理(后期补充完整讲解)_手机dp输出是横屏还是竖屏_android阿杜的博客-CSDN博客 “又是屏幕适配&#xff0c;这类文章网上不是很多了吗&#xff1f;” 我也很遗憾&#xff0c;确实又是老问题。但本文重点对网上…

MYSQL学习 - DDL数据库操作

前言 从今天开始, 健哥就带各位小伙伴学习数据库技术。数据库技术是Java开发中必不可少的一部分知识内容。也是非常重要的技术。本系列教程由浅入深, 全面讲解数据库体系。 非常适合零基础的小伙伴来学习。 ------------------------------前戏已做完&#xff0c;精彩即开始---…

基于springboot和ajax的简单项目 06 日志界面的delete功能(根据选择的checkbox)

01.这次后台开始&#xff1b; 顺序依次是dao->xml->service->serviceimpl->controller->html 02.dao接口 public int doDeleteObjects(Param("ids") Integer... ids);03.xml文件 <update id"doDeleteObjects" >delete from sys_lo…

用友U8 cloud,信创云ERP的数智先锋

编辑&#xff1a;阿冒设计&#xff1a;沐由从来未曾有过一个春天&#xff0c;能够像当下这般被如此由衷地期待。经历了漫长的1000多个日日夜夜之后&#xff0c;我们的工作与生活终于回到正轨。自2023年以来&#xff0c;中国市场迎来“开门红”&#xff0c;消费市场加速回暖&…
最新文章