掘金同款Markdown编辑器

393 阅读3分钟

掘金同款Markdown编辑器

2021年03月10日 17:01的时候稀土君发表了掘友们的创作利器-新版掘金编辑器上线啦! Markdown 掘金bytemd编辑器开源了。

使用掘金编辑器体验与其他编辑器的区别

个人觉得最大的就是支持复制html-> markdown语法了。 比如我再MDN复制一段api说明介绍, 不用我自己再转成Markdown语法了,会自动转换。

**bytemd编辑器并没有支持html-> markdown**的转换,想要体验的下bytemd 能力的可以去 演武场 自己去试试。

bytemd插件

bytemd编辑器的功能做成插件的形式了,像其他编辑器的话也有一些是配置式,就是那个功能我不需要我搞个配置就好了。插件形式话也是算是配置式,需要那个安装那个,好处是可以自己按照bytemd的插件要求去扩展更多的功能。插件如下:

  • @bytemd/plugin-breaks (Support breaks)
  • @bytemd/plugin-frontmatter (Parse frontmatter)
  • @bytemd/plugin-gemoji (Support Gemoji shortcodes)
  • @bytemd/plugin-gfm (Support GFM (autolink literals, strikethrough, tables, tasklists))
  • @bytemd/plugin-highlight (Highlight code blocks)
  • @bytemd/plugin-highlight-ssr (Highlight code blocks (SSR compatible))
  • @bytemd/plugin-math (Support math formula)
  • @bytemd/plugin-math-ssr (Support math formula (SSR compatible))
  • @bytemd/plugin-medium-zoom (Zoom images like Medium)
  • @bytemd/plugin-mermaid (Support Mermaid diagram)

插件大致能力就是支持图片可点击放大、支持表格、支持表情包、支持Mermaid语法图表、支持math公式等。全都安装给bytemd去使用就好了。

bytemd 实现复制HTML 转成 Markdown 实现方案

1、有直接监听复制事件,完事直接使用turndown转的使用如下:(vue配置bytemd(掘金同款MarkDown插件)文章链接)

核心代码如下:

document.addEventListener('paste', function (event) {
    that.SetHtml(event);
});
//转markdown语法
async SetHtml(e) {
    e.preventDefault();
    const list = e.clipboardData.items;
    for (let i = 0; i < list.length; i++) {
        if (list[i].kind === 'string' && list[i].type.match('^text/html')) {
            const h = await new Promise(t => list[i].getAsString(e => t(e)));
            const ts = new TurndownService()
            this.value = ts.turndown(h)
        }
    }
}

功能实现了,不好的点是:比如我粘贴到的位置不是你的编辑器里面呢!你也都解析了。未加是否再编辑器中的判断!!

2、 开发一款@bytemd/plugin-html2md插件,下面细说!

bytemd插件开发@bytemd/plugin-html2md 初始化(一)

开发一款@bytemd/plugin-html2md插件,他的能力呢就是把复制来的html转成md。插件生命周期如下图:

interface BytemdPlugin {
  /**
   * Customize Markdown parse by remark plugins:
   *
   * https://github.com/remarkjs/remark/blob/main/doc/plugins.md
   */
  remark?: (p: Processor) => Processor
  /**
   * Customize HTML parse by rehype plugins:
   *
   * https://github.com/rehypejs/rehype/blob/main/doc/plugins.md
   */
  rehype?: (p: Processor) => Processor
  /**
   * 在工具栏,备忘单和快捷方式中注册操作
   */
  actions?: BytemdAction[]
  /**
   * 编辑器的副作用,当插件更改时触发
   */
  editorEffect?(ctx: BytemdEditorContext): void | (() => void)
  /**
   * 对查看器的副作用,当查看器道具改变时触发
   */
  viewerEffect?(ctx: BytemdViewerContext): void | (() => void)
}


export default function html2mdPlugin(): BytemdPlugin {
  return {
    // to be implement
  }
}

咱们要更改的是编辑视图用到的是editorEffect了,代码结构如下:

export default function html2mdPlugin(): BytemdPlugin {
  return {
    editorEffect(ctx) {

    },
  }
}

bytemd插件开发@bytemd/plugin-html2md 使用turndown解析HTML(二)

editorEffect去监听编辑时候发生更改做相关处理。使用turndownHTML转成Markdown语法,。

turndown使用如下:

import TurndownService from 'turndown';

const turndownService = new TurndownService({ option: 'value' });
const markdown = turndownService.turndown('<h1>Hello world!</h1>');
OptionValid valuesDefault
headingStylesetext or atxsetext
hrAny Thematic break* * *
bulletListMarker-+, or **
codeBlockStyleindented or fencedindented
fence``` or ~~~```
emDelimiter_ or *_
strongDelimiter** or __**
linkStyleinlined or referencedinlined
linkReferenceStylefullcollapsed, or shortcutfull
preformattedCodefalse or truefalse

bytemd插件开发@bytemd/plugin-html2md (三)

完整开发如下:

import type { Editor, EditorConfiguration } from 'codemirror'
export interface BytemdEditorContext extends EditorUtils {
  codemirror: typeof CodeMirror
  /**
   * CodeMirror editor instance
   */
  editor: Editor
  /**
   * The root element
   */
  root: HTMLElement
}

export default function html2mdPlugin(): BytemdPlugin {
  return {
    editorEffect({ editor }) {
      // editor 详细用法请参考 https://codemirror.net/docs/guide/
      editor.on('parse', ($editor, event) => {
        let itemList: any[] = [];

        if (event instanceof ClipboardEvent) {
          itemList = Array.from(event?.clipboardData?.items) || [];
        } else {
          itemList = Array.from(event?.dataTransfer?.items) || [];
        }

        const HTMLItem = itemList.find((v) => v.type.includes('text/html'))
        if (!HTMLItem) {
          return;
        }

        let HTMLStr;
        switch (HTMLItem.kind) {
          case 'string':
            HTMLStr = await new Promise((res) =>
              HTMLItem.getAsString((e) => {
                res(e);
              }));
            break;
          case 'file':
            HTMLStr = await HTMLItem.getAsFile().text();
            break;
          default:
            throw new Error('粘贴的item.kind有未知的类型,需要手动处理!');
        }
        const markdown = turndownService.turndown(HTMLStr);
        $editor.replaceSelection(markdown);
      });
    },
  }
}

使用

具体使用如下:

<template>
  <Editor :value="value" :plugins="plugins" @change="handleChange" />
</template>

<script>

import { Editor, Viewer } from '@bytemd/vue'

import gfm from '@bytemd/plugin-gfm'
import highlight from '@bytemd/plugin-highlight-ssr';
import mediumZoom from "@bytemd/plugin-medium-zoom";
import gemoji from "@bytemd/plugin-gemoji";

import html2mdPlugin from './html2md-plugin.js'

// 引入中文包
import zhHans from "bytemd/lib/locales/zh_Hans.json";
// 引入基础css
import 'bytemd/dist/index.min.css';
// 引入高亮css
import "highlight.js/styles/vs.css";

const plugins = [
  gfm(),
  highlight(),
  mediumZoom(),
  gemoji(),
  html2mdPlugin(),
  // Add more plugins here
]

export default {
  components: { Editor },
  data() {
    return { value: '', plugins }
  },
  methods: {
    handleChange(v) {
      this.value = v
    },
  },
}
</script>

总结

这个样就可以使bytemd复制HTML到转成Markdown了。