likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 23

Quiet 项目简介:juejin.cn/post/717122…

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

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

上一篇:

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

背景

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

那就自己写一个吧!

组件拆分

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

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

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

Viewer:渲染用户输入的内容

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

三个小组件的实现

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

Toolbar 的实现

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

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

而且卡片的下半部分刚好可以用来放 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,两者差别不大。

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

Markdown 渲染区域

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

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

实现过程

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

Toolbar

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

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

这个地方用 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 的颜色常量即可,当然用到的开源轮子也要配置好它的亮色模式和暗色模式。

亮色模式

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

暗色模式

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

结语

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

源码地址:github.com/lin-mt/quie…

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

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

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

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

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

下一篇

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

下下篇

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

转载自:https://juejin.cn/post/7174105780335935524
评论
请登录