Skip to content

cherry-markdown

cherry-markdown 是腾讯推出的在线 markdown 编辑器

安装

pnpm add cherry-markdown

文档

示例

react 组件

下面定义 react 组件,方便于在项目中复用

定义类型

因为 cherry 包没有 ts类型,所以我们要先定义模块的 typescript,修改 vite-env.d.ts 文件添加如下内容。如果你使用其他方法,请自行修改该文件。

declare module 'cherry-markdown' {
	export default class Cherry {
		constructor(options: any)
		destroy: () => void
		setMarkdown: (markdown: string) => void
		getMarkdown: () => string
	}
}

定义组件

下面定义组件 components/Markdown.tsx

props 说明

prop说明
height编辑器调试
value初始内容
onChange内容更改时的回调函数
onBlur失去焦点时的触发函数

下面 react 组件的代码

import { useUplaodImageMutation } from '@/services/image'
import Cherry from 'cherry-markdown'
import 'cherry-markdown/dist/cherry-markdown.css'
import React, { memo, useEffect, useImperativeHandle, useRef } from 'react'

interface Props {
  height: string
  value: string
  onChange: (value: string) => void
  onBlur?: ({ e, cherry }: { e: MouseEvent; cherry: Cherry }) => void
}
export interface MarkdownRef {
  editor: React.MutableRefObject<Cherry | null>
}
export const MarkdownEditor = memo(
  React.forwardRef<MarkdownRef, Props>((props, ref) => {
    const uploadImageMutation = useUplaodImageMutation()
    const markdownEditorRef = useRef<Cherry | null>(null)
    useImperativeHandle(ref, () => {
      return {
        editor: markdownEditorRef,
      }
    })
    useEffect(() => {
      markdownEditorRef.current = new Cherry({
        id: 'markdown-container',
        value: props.value,
        fileUpload: (file: File, callback: (url: string) => void) => {
          uploadImageMutation.mutate(file, {
            onSuccess: (data: { url: string }) => {
              callback(data.url)
            },
          })
        },
        editor: {
          //编辑器样式
          // edit&preview: 双栏编辑预览模式
          // editOnly: 纯编辑模式(没有预览,可通过toolbar切换成双栏或预览模式)
          // previewOnly: 预览模式(没有编辑框,toolbar只显示“返回编辑”按钮,可通过toolbar切换成编辑模式)
          defaultModel: 'edit&preview',
          codemirror: {
            // 是否自动focus 默认为true
            autofocus: false,
          },
        },
        event: {
          //内容更改后的事件
          afterChange: (text: string) => props.onChange(text),
          //失焦时的事件
          blur: ({ e, cherry }: { e: MouseEvent; cherry: Cherry }) => {
            props.onBlur?.({ e, cherry })
          },
        },

        toolbars: {
          // 定义顶部工具栏
          toolbar: [
            'bold',
            'italic',
            'strikethrough',
            '|',
            'color',
            'header',
            'ruby',
            '|',
            'list',
            'panel',
            'settings',
            '|',
            'code',
            'image',
          ],
          // 定义侧边栏,默认为空
          // sidebar: ['mobilePreview', 'copy', 'theme'],
          // sidebar: false,
          // 定义顶部右侧工具栏,默认为空
          toolbarRight: ['fullScreen'],
          //编辑时,右侧显示目录
          toc: false,
          // 定义选中文字时弹出的“悬浮工具栏”,默认为 ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', '|', 'size', 'color']
          bubble: false,
          // 定义光标出现在行首位置时出现的“提示工具栏”,默认为 ['h1', 'h2', 'h3', '|', 'checklist', 'quote', 'table', 'code']
          float: false,
        },
        previewer: {
          // 自定义markdown预览区域class
          // className: 'markdown'
          floatWhenClosePreviewer: false,
          enablePreviewerBubble: false,
        },
        themeSettings: {
          // 主题列表,用于切换主题
          themeList: [
            { className: 'default', label: '默认' },
            { className: 'dark', label: '黑' },
            { className: 'light', label: '白' },
            { className: 'green', label: '绿' },
            { className: 'red', label: '粉' },
            { className: 'violet', label: '紫' },
            { className: 'blue', label: '蓝' },
          ],
          // 目前应用的主题
          mainTheme: 'light',
          // 目前应用的代码块主题
          codeBlockTheme: 'one dark',
        },
      })
      //组件卸载时删除编辑器
      return () => markdownEditorRef.current?.destroy()
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])
    return <div id='markdown-container' style={{ height: props.height, width: '100%' }}></div>
  }),
)

使用组件

现在可以使用编辑器组件了

<MarkdownEditor
  ref={markdownRef}
  {...props}
  height='300px'
  value={field.state.value}
  onChange={() => removeError(name)}
  onBlur={({ cherry }) => field.handleChange(cherry.getMarkdown())}
/>

markdownRef 可以获取编辑器对象

markdownRef.current?.editor.current?.setMarkdown('')