作为一个后端 Java 开发,为何、如何自己实现一个 Markdown 编辑器


theme: cyanosis
highlight: xcode

Quiet 项目简介:https://juejin.cn/post/7171222265172852744

Q:读完这篇文章能收获什么?

A:可以自己实现一个简易且跟自己的项目风格融合的 Markdown 编辑器。

上一篇:

Dubbo + Spring Security Oauth2 如何优雅解决服务提供者无法获取当前用户(下)

背景

在做 Quiet 项目中的接口文档管理部分时,接口编辑、预览、运行这三部分的布局均参考了 Yapi,里面有一个接口备注的区域用的是 Markdown 编辑器,看了下 Yapi 用的 Markdown 编辑器已年久失修了,那就换其他的 Markdown 编辑器,找了一圈,找到了很多优秀的 Markdown 编辑器,然而还是没找到自己想要的 Markdown 编辑器。

当然尝试过掘金目前用的 Markdown 编辑器 bytemd,但是 Quiet 项目用的 Arco Design Pro 是支持暗黑和亮色模式的,奈何 bytemd 不支持暗黑模式,在 Quiet 切换到暗黑模式的时候,会出现一块大白色区域,只能无奈放弃,当然可以自己实现 bytemd 的暗黑模式(Github 上有 issue ,这个 issue 早已过周岁,要官方实现,遥遥无期),但是让我一个 Java 后端开发来写这么多 css(我也不知道多不多,先找个借口吧!),着实有点为难~。

那就自己写一个吧!

组件拆分

一个 Markdown 编辑器可以拆分为三个小组件:Toolbar、Editor、Viewer

Toolbar:工具栏,主要提供鼠标点击的快捷操作

Editor:用户输入 Markdown 格式内容的区域

Viewer:渲染用户输入的内容

Toolbar 的按钮相当于在 Editor 区域按照 Markdown 的格式修改或添加文本内容,除此之外,就是控制 Markdown 的样式、展现内容,当然可以包含其他的功能,但是基本都差不多。

三个小组件的实现

在这三个小组件的实现过程中,我尽量去复用 Arco Design 的组件,毕竟操刀 css 那是前端大佬吃饭的手艺活,我可干不来。。。而且如果自己写(估计也写不好)或者换了一种样式,会显得这个 Markdown 编辑器与项目的样式格格不入,那简直太难受了(有蚂蚁在爬~)。

Toolbar 的实现

Toolbar 可以分为两块内容,左边是提供用户快捷输入或修改内容的,比如粗体、斜体、引用等,右边是控制编辑器的展示及其他功能,如目录、仅编辑区、仅预览区、全屏等,在 Arco Design 里,卡片组件的上部分可分为 TitleExtra 与此布局类似:

image.png

而且卡片的下半部分刚好可以用来放 Editor 和 Viewer,那就用卡片来作为整个编辑器的主体,上部分的样式改一下 headerStyle 就可以了,这个我做得来~

其他的样式,比如 Hover 出现提示信息,下拉菜单等,Arco Design 都有合适的组件可以使用,但是 Icon 的选择也是一个麻烦事,因为 Arco Design 的 Icon 不够我用,有的菜单操作找不到合适的组件使用,比如上标、下标、表格、行内公式、块级公式、展示左边区域、展示右边区域这些就没找到合适的 Icon 使用,没有的 Icon 那就得自己搞定了。

寻找合适的 Icon

React Icon 的开源项目还是很多的,但是,还是老问题,适配亮色和暗黑模式,又要跟 Arco Design 风格相近的 Icon 属实难找,甚至尝试了自己编辑一个 SVG(之前也没玩过这玩意)。

我先是找了 iconfont,确实找到了自己想要的 Icon,但是没办法像 Arco Design 的 Icon 自动适配两种模式,在亮色模式还好好的 Icon,一切换到暗黑模式,Icon 就变得乌漆麻黑的,而且从 iconfont 下载的 SVG 代码也是乱七八糟的(可以自行下一个 SVG 看看),看不懂啊,想改也没地方下手。

几经周转找到了 iconpark,一开始用的时候也是跟 iconfont 一样的效果,放弃了,后面知晓了可以通过修改 SVG 的属性,SVG 的颜色会自动使用字体的颜色,才重新尝试了 iconpark。从 iconpark 下载的 SVG 代码很符合我的口味,优雅啊!

<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
    <rect x="6" y="6" width="28" height="36" rx="2" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="butt"
          stroke-linejoin="miter"/>
    <path d="M42 6V42" stroke="currentColor" stroke-width="4" stroke-linecap="butt" stroke-linejoin="miter"/>
</svg>

这是我修改后的 SVG 代码,从 iconpark 下载的原始 SVG 代码中,stroke#333,这是用来配置 SVG 的颜色,知道了 currentColor 可以使用当前该元素所处环境的文字颜色,就尝试了下,嘿!还真行!

编辑区域

看过其他 Markdown 编辑器的编辑区,很多用的是 textarea,属实难看,在找 Markdown 编辑器的时候看中过一个比较可以的 Markdown 编辑器,提了个 issue 看能否支持编辑区高亮,被作者拒绝了,我也无奈~

然后看了 bytemd 的编辑区,诶!codemirror?等等,这个我好像在哪见到过。。。

是的,我在写 Json Schema 可视化编辑器 的时候有了解过,当时因为属实有点丑,就放弃了,用了 Monaco Editor,而且 Semi Design 的代码示例也是用的这个,很漂亮呀!同时也有很多开源的主题可以选择,那就没理由不用它了。

选一个 Monaco Editor 的 React 组件使用:react-monaco-editormonaco-react,两者差别不大。

image.png

Markdown 渲染区域

渲染 Markdown 的 React 开源组件也很多,也有很多基础轮子可以用,比如 remarkmarked 等等很多优秀的开源项目,但是!这些我都放弃了,因为!我用不来,或者说用不好会贴切一点。

最后选择了 react-markdown ,它的 示例 渲染得挺合我口味的(忽略左边的编辑区域),而且之前玩过这个开源项目,它的样式是导入一些主题的 css 文件,那我修改 css 让渲染的结果可以适配暗黑模式和亮色模式即可,这个我也做得来~

实现过程

这部分主要是说明各个组件我是如何实现的,怎么实现的,实现过程中遇到的部分问题是怎么解决的,至于所有源码,在文末 结语 附有说明,感兴趣可以自己去看哈,这里就不贴大量代码了。

Toolbar

在 Toolbar 中,每个图标都是一个可操作的区域,这个区域很小,鼠标移动过去后有样式变化,给用户反馈:这是个可操作的区域

image.png

这个地方用 Button 的话有点大材小用了,而且改样式也很麻烦,那就自己定义一个:

export const Option = styled.div`
  background-color: rgb(var(--gray-2));
  border-radius: var(--border-radius-small);
  color: var(--color-text-1);
  padding: 0 8px;
  line-height: 26px;
  height: 26px;
  font-size: 14px;
  cursor: pointer;

  :hover {
    background-color: rgb(var(--gray-3));
  }
`;

在所有的操作中可以分为三类:

  1. 光标选中的字符前后添加内容,并移动光标位置
  2. 在下一行添加内容,并移动光标位置
  3. 在光标当前行的开头添加内容,并移动光标位置

但是有一个菜单比较特别,那就是标题菜单,标题需要去掉/添加合适的 # 数量来设置/修改当前行的标题级别。

定义四个方法:

  • changeHeading 修改当前行的标题级别
  • setStartAndEndCharacters 在选中的字符前后添加内容
  • addCharactersAtLineStart 在当前行的开头添加内容
  • addBlockValue 在下一行添加内容块,并移动光标位置

至于如何获取光标位置,如何在 Monaco Editor 中添加内容,在 Monaco Editor 的官网有非常详细的文档,这里就不过多提及了,或者可以自己看下文末的源码。

在实现 Editor 和 Viewer 之前,提供了可拖拽的分割线,让用户可以自行调整 Editor 和 Viewer 两者的占用比例,使用了 ResizeBox 组件实现。

Editor

Editor 的实现主要是调整 Monaco Editor 的属性,属性的设置是结合了上面选的 Monaco Editor React 组件和 Monaco Editor 的文档进行调整。

// https://github.com/lin-mt/quiet-web/tree/master/src/components/QuietEditor
<QuietEditor
  value={value}
  paddingTop={16}
  paddingBottom={16}
  lineNumbers={'off'}
  language={'markdown'}
  minimapEnabled={false}
  lineDecorationsWidth={0}
  onMount={handleEditorDidMount}
  onChange={(val) => {
    setValue(val);
    if (props.onChange) {
      props.onChange(val);
    }
  }}
  style={{
    width: '100%',
    height: '100%',
    borderRadius: 0,
    borderWidth: 0,
  }}
/>

Viewer

Viewer 使用的是 react-markdown,样式部分需要引入具体的 css 文件,为了能适应亮色模式和暗黑模式,我把 css 文件直接复制出来,而不是使用 import 的方式引入,这样更方便修改它的样式。

为了让 Markdown 支持更多的功能,比如数学公式、代码高亮、emoji、Mermaid等等,需要引入相应的插件支持,可使用的插件有 remark pluginsrehype plugins

实现一个 rehype 插件

为了支持 Mermaid 图表,找了很多插件,古老的插件倒是有,但是用不了,找到了一个插件 remark-mermaidjs,目前这个插件还有些 BUG。

然后就想看看有没有 rehype 插件支持 Mermaid,也确实找到了 rehype-mermaid,可以用,但是也存在跟 remark-mermaidjs 类似的 BUG,好在我能看懂部分代码,在组件 MermaidBlock.tsx 中用到了 useEffectdeps 参数是空的,也就是只会渲染一次,内容出现变化的时候,图就消失了,那就在 deps 参数中加上 props.code 试试,诶!还真行~!

到此,Markdown 编辑器的基础功能基本实现了。虽然上面的实现过程描述得云淡风轻,但是实际我在实现的时候还是耗费了大量时间。

效果图

编辑器的亮色模式和暗色模式是如何实现的?这个需要结合 Arco Design Pro 的主题切换,同时在 Markdown 的样式中使用 Arco Design 的颜色变量代替 css 的颜色常量即可,当然用到的开源轮子也要配置好它的亮色模式和暗色模式。

亮色模式

light.gif

暗色模式

dark.gif

结语

前端代码可能写得有点粗糙(javaer 瑟瑟发抖)😄

源码地址:https://github.com/lin-mt/quiet-web/tree/master/src/components/QuietMarkdown

目前编辑器还实现了仅编辑区、仅预览区、网页全屏等功能,所有的代码均在 quiet-web 下的 src/components/QuietMarkdown,目前图片上传的功能还未实现,后端的文件服务还在写,感兴趣的可以关注下~

当然,这个编辑器还有很多需要改进的点,如果要实现通用的编辑器,需要提供插件、国际化、样式适配等扩展性功能。还有一些细节不是很完善,比如在使用无序列表、有序列表的时候,按回车键,下一行会自动添加前缀,无论当前行是否有内容,这个体验跟 bytemd 是不一样的。

好了!这篇文章到这里就结束了!现在多多少少也应该知道了该如何自己实现一个简易的 Markdown 编辑器了吧。

image.png

后面的两篇会写得晚点,因为还在写实现代码。

下一篇

如何结合 Minio 实现一个简单的可嵌入的 Spring Boot Starter 文件服务

下下篇

如何让 Markdown 编辑器实现两种方式的文件上传

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容