引言
大家好,我是程序员 K.N, 一个试图用代码和世界重新打结的前端小白~
先叠个甲,byd = ByteMD,小小的标题党一下,各位看官老爷轻喷。
前段时间,我们团队做了个面试刷题工具——面试鸭,而我也作为一名前端开发参与了该项目的开发。
在这个工具中,有一个流畅、简洁的 Markdown 编辑器,该编辑器所见即所得,支持代码高亮、代码块复制,标签解析、数学公式、流程图、对齐方式、图片自定义大小(仿语雀)、图片放大预览等功能。
今天,我将手把手带大家揭秘,这个 Markdown 编辑器是如何实现的,助你也能打造同款功能!
一、ByteMD 是啥?
其实这款编辑器是基于 ByteMD 实现的,它是字节开源的一款轻量编辑器,是使用 Svelte 构建的 Markdown 编辑器组件,它也可以用于其他框架,例如 React、Vue 和 Angular。
具有以下特性:
1)轻量级且与框架无关
2)易于扩展:ByteMD 有一个插件系统来扩展基本的 Markdown 语法,
3)默认安全:ByteMD 正确处理跨站点脚本(XSS) 攻击,例如 <script>
和 <img onerror>
。无需引入额外的 DOM 清理步骤。
4)SSR 兼容:ByteMD 可以在服务器端渲染(SSR) 环境中使用,无需额外配置。
相关链接:
ByteMD 开源地址:https://github.com/pd4d10/bytemd
demo 示例:https://bytemd.js.org/playground/
二、快速集成 ByteMD
1、环境准备:
Node.js 16 及以上
2、基本使用
安装 ByteMD 相关依赖
npm install bytemd
npm instal @bytemd/react
安装 gfm(表格支持)插件、highlight 代码高亮插件
npm install @bytemd/plugin-gfm @bytemd/plugin-highlight
引入 ByteMD 汉化包
# 引入中文包
import zhHans from 'bytemd/locales/zh_Hans.json
用法
ByteMD 有两个组件:Editor
和 Viewer
。Editor 是 Markdown 编辑器; View
通常用于显示呈现的 Markdown 结果,无需编辑。在使用组件之前,还要导入CSS文件以确保样式正确:
import 'bytemd/dist/index.css'
封装自定义的 Editor 和 Viewer 组件
接下来需要对官方的 Editor 和 Viewer 进行封装, 以提高组件的通用性。
新建 MdEditor 组件,示例写法如下:
import type { FC } from "react";
import { Editor } from "@bytemd/react";
import gfm from "@bytemd/plugin-gfm";
import gfmLocale from "@bytemd/plugin-gfm/locales/zh_Hans.json";
import highlight from "@bytemd/plugin-highlight";
import locale from "bytemd/locales/zh_Hans.json";
import "bytemd/dist/index.css";
import "./index.css";
interface Props {
value?: string;
onChange?: (v: string) => void;
placeholder?: string;
}
const plugins = [
gfm({
locale: gfmLocale,
}),
highlight(),
];
/**
* Markdown 编辑器
*/
const MdEditor: FC<Props> = (props) => {
const { value = "", onChange, placeholder } = props;
return (
<div className="md-editor">
<Editor
value={value || ""}
placeholder={placeholder}
editorConfig={
{
// 不显示行数
lineNumbers: false,
autofocus: false,
}}
mode="split"
locale={locale}
plugins={plugins}
onChange={onChange}
/>
</div>
);
};
export default MdEditor;
页面中使用
import "./App.css";
import MdEditor from "@/components/MdEditor";
import { useState } from "react";
function App() {
const [value, setValue] = useState<string>("");
return (
<>
<MdEditor value={value} onChange={setValue} />
</>
);
}
export default App;
这样,就能得到一个基本的编辑器了,大家可以在光标中输入看看有没有实现所见即所得呢?但是右上角多了一个 GitHub 的图标,咱们把它隐藏起来,主打的就是一个简洁~
/*隐藏 github 图标*/
.bytemd-toolbar-icon.bytemd-tippy.bytemd-tippy-right:last-child {
display: none;
}
3、ByteMD 插件配置
安装插件
官方支持的插件已经有不少,但对于一款体验良好的编辑器来说,我觉得还不够,除了使用以下列表中的插件外,我们还需要拓展其他插件,且听我娓娓道来 ~
官方插件列表如下:
插件名 | 插件功能 |
---|---|
@bytemd/plugin-breaks | 默认md渲染时硬换行需要双空格或者双回车, 该插件确保正常回车即可硬换行 |
@bytemd/plugin-frontmatter | 解析元数据 |
@bytemd/plugin-gemoji | 解析gemoji表情 |
@bytemd/plugin-gfm | 支持GFM(自动链接文字、删除、表格、任务列表) |
@bytemd/plugin-highlight | 代码高亮 |
@bytemd/plugin-highlight-ssr | 代码高亮ssr版本 |
@bytemd/plugin-math | 支持数学公式 |
@bytemd/plugin-math-ssr | 支持数学公式ssr版本 |
@bytemd/plugin-medium-zoom | 支持点击图片放大预览 |
@bytemd/plugin-mermaid | 支持流程图 |
我们把几个常用的插件都安装上,在 plugins 中导入我们所需的插件:
import type { FC } from "react";
import { Editor } from "@bytemd/react";
import gfm from "@bytemd/plugin-gfm";
import gfmLocale from "@bytemd/plugin-gfm/locales/zh_Hans.json";
import gemoji from "@bytemd/plugin-gemoji";
import highlight from "@bytemd/plugin-highlight";
import math from "@bytemd/plugin-math";
import mathLocale from "@bytemd/plugin-math/locales/zh_Hans.json";
import mermaid from "@bytemd/plugin-mermaid";
import mermaidLocale from "@bytemd/plugin-mermaid/locales/zh_Hans.json";
import mediumZoom from "@bytemd/plugin-medium-zoom";
import locale from "bytemd/locales/zh_Hans.json";
import "bytemd/dist/index.css";
import "highlight.js/styles/vs.css";
import "github-markdown-css/github-markdown-light.css";
import "./index.css";
const plugins = [
gfm({
locale: gfmLocale,
}),
gemoji(),
highlight(),
math({
locale: mathLocale,
}),
mermaid({
locale: mermaidLocale,
}),
mediumZoom(),
];
自定义插件
ByteMD 使用 remark 和 rehype 生态系统来处理 Markdown. 完整流程如下:
- Markdown 文本被解析为AST
- Markdown AST 可以通过多种注释插件进行操作
- Markdown AST 转换为 HTML AST
- 出于安全原因,HTML AST 已被清理
- HTML AST 可以被多个rehype 插件操纵
- HTML AST 被字符串化为 HTML
- HTML 渲染后的一些额外 DOM 操作
这里借用下官方描述的流程图:
2、5、7步骤是通过 ByteMD 插件 API 进行用户定制的。官方文档中用了一个 plugin-math 插件作为例子解释了我们该如何编写插件,接下来,我带大家来自定义实现几个实用的插件,包括:居中插件、标签解析插件、代码块复制插件等。
添加对齐方式插件:
给输入的文本、图片、链接等进行对齐方式设置,实现原理就是通过给某个元素包裹上一个 p
标签,并通过 align
属性设置其在文本框内的位置。
代码如下:
1)给这三个对齐方式设置对应的 icon ,这里可以直接套用我的 svg。
export const ALIGN_CENTER = `
<svg t="1719248469954" class="icon-symbol" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4377">
<path d="M96 128h832v96H96zM96 576h832v96H96zM224 352h576v96H224zM224 800h576v96H224z" p-id="4378"></path>
</svg>`;
export const ALIGN_LEFT = `
<svg width="24" height="24" t="1719248152373" class="icon-symbol" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4230">
<path d="M96 128h832v96H96zM96 576h832v96H96zM96 352h576v96H96zM96 800h576v96H96z" p-id="4231"></path>
</svg>`;
export const ALIGN_RIGHT = `
<svg t="1719248528798" class="icon-symbol" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4524">
<path d="M96 128h832v96H96zM96 576h832v96H96zM352 352h576v96H352zM352 800h576v96H352z" p-id="4525"></path>
</svg>`;
2)编写插件代码
import type {
BytemdPlugin } from 'bytemd';
import zh_Hans from './localels/zh_Hans.json';
import {
ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT } from './icon';
export interface AlignPluginOptions {
locale?: Record<string, string>;
}
/**
* 对齐方式插件
*/
export default function alignPlugin(options?: AlignPluginOptions): BytemdPlugin {
const locale = {
...zh_Hans, ...options?.locale } as typeof zh_Hans;
return {
actions: [
{
title: locale.alignType,
icon: ALIGN_CENTER,
handler: {
type