Code Monkey home page Code Monkey logo

bytemd's Introduction

ByteMD

This is ByteMD v1 repository.

v2 is under development actively, see HashMD.

ByteMD is a Markdown editor component built with Svelte. It could also be used in other libraries/frameworks such as React, Vue and Angular.

Playground here: https://bytemd.js.org/playground/

Features

  1. Lightweight and framework agnostic: ByteMD is built with Svelte. It compiles to vanilla JS DOM manipulation without importing any UI Framework runtime bundle, which makes it lightweight, and easily adapted to other libraries/frameworks.
  2. Easy to extend: ByteMD has a plugin system to extend the basic Markdown syntax, which makes it easy to add additional features such as code syntax highlight, math equation and Mermaid flowcharts. You can also write your own plugin if these ones don't meet your needs.
  3. Secure by default: Cross-site scripting(XSS) attack such as <script> and <img onerror> have been correctly handled by ByteMD. No need to introduce extra DOM sanitize steps.
  4. SSR compatible: ByteMD could be used in the Server-side rendering(SSR) environment without extra config. SSR is widely used in some cases due to its better SEO and fast time-to-content in slow network connection.

Installation

Package Status Description
bytemd npm Svelte/Vanilla JS component
@bytemd/react npm React component
@bytemd/vue npm Vue 2 component
@bytemd/vue-next npm Vue 3 component

Legacy browsers support

The default entry of NPM package only supports modern browsers. To make legacy browsers (IE9+) work, You can compile it with ESNext -> ES5 transpilers, such as Babel or SWC.

The ES5 bundle will no longer be available after version 1.11.0. If you need it, you can use version 1.11.0 or earlier versions

Notice that polyfills are not included, and should be imported manually, see the legacy browser example.

Usage

There are two components: Editor and Viewer. Editor is the Markdown editor, as the name suggests; Viewer is commonly used to display rendered Markdown results without editing.

Before using the component, remember to import CSS file to make styles correct:

import 'bytemd/dist/index.css'

Svelte

<script>
  import { Editor, Viewer } from 'bytemd'
  import gfm from '@bytemd/plugin-gfm'

  let value
  const plugins = [
    gfm(),
    // Add more plugins here
  ]

  function handleChange(e) {
    value = e.detail.value
  }
</script>

<template>
  <Editor {value} {plugins} on:change={handleChange} />
</template>

React

import gfm from '@bytemd/plugin-gfm'
import { Editor, Viewer } from '@bytemd/react'

const plugins = [
  gfm(),
  // Add more plugins here
]

const App = () => {
  const [value, setValue] = useState('')

  return (
    <Editor
      value={value}
      plugins={plugins}
      onChange={(v) => {
        setValue(v)
      }}
    />
  )
}

Vue

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

<script>
import gfm from '@bytemd/plugin-gfm'
import { Editor, Viewer } from '@bytemd/vue'

const plugins = [
  gfm(),
  // Add more plugins here
]

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

Vanilla JS

import gfm from '@bytemd/plugin-gfm'
import { Editor, Viewer } from 'bytemd'

const plugins = [
  gfm(),
  // Add more plugins here
]

const editor = new Editor({
  target: document.body, // DOM to render
  props: {
    value: '',
    plugins,
  },
})

editor.$on('change', (e) => {
  editor.$set({ value: e.detail.value })
})

Options

Viewer

Key Type Description
value string (required) Markdown text
plugins BytemdPlugin[] ByteMD plugin list
sanitize (schema: Schema) => Schema Sanitize strategy
remarkRehype documentation remark-rehype config options

Editor

Editor component also accepts the options of Viewer for preview. Besides that, there are some other options:

Key Type Description
mode split, tab, auto Editor display mode, default: auto
previewDebounce number Debounce time (ms) for preview, default: 300
placeholder string Editor placeholder
editorConfig documentation CodeMirror editor config
locale i18n locale. Available locales could be found at bytemd/locales, default: use en.json
uploadImages function Specify how to upload images. If set, the image icon will appear on the toolbar
maxLength number Maximum length (number of characters) of value

Style customization

Editor

The default height of ByteMD Editor is 300px. It could be overridden by CSS:

.bytemd {
  height: calc(100vh - 200px);
}

The other styles could also be overridden, see the default style.

Viewer

There is no built-in styles for the Viewer. You could use third-party markdown themes, for example juejin-markdown-themes and github-markdown-css.

Plugin System

ByteMD provides a powerful plugin system for customization. There are several official plugins to support features such as code syntax highlight, math equation and Mermaid flowcharts.

If you have more customized needs, you could also write your own plugin to support them.

Official Plugins

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

Technical Overview

ByteMD uses remark and rehype ecosystem to process Markdown. The complete process is as follows:

  1. The markdown text is parsed to an AST
  2. The Markdown AST could be manipulated by several remark plugins
  3. The Markdown AST is transformed to a HTML AST
  4. The HTML AST is sanitized for security reason
  5. The HTML AST could be manipulated by several rehype plugins
  6. The HTML AST is stringified to HTML
  7. Some extra DOM manipulation after the HTML being rendered

It could also be described as a flowchart:

plugin system

The 2,5,7 steps are designed for user customization via ByteMD plugin API.

Authoring a Plugin

We'll take Math formula plugin as an example to walk you through the process.

First of all, scaffold the project according to the BytemdPlugin type signature:

import type { BytemdPlugin } from 'bytemd'

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

Then we look into the requirement more closely: If we want to render syntax like $a+b$ to Math formula, there are several things we need to do:

  • Support $a+b$ syntax as a Math formula in Markdown (step 2)
  • Render these nodes correctly in HTML (step 5 or 7)
  • Additionally, an extra icon in the toolbar would be great for user convenience

For the first thing, luckily, we don't need to implement it with our own because remark-math already did it. The only thing we need to do is to import and use it:

import type { BytemdPlugin } from 'bytemd'
+import remarkMath from 'remark-math'

export default function mathPlugin(): BytemdPlugin {
  return {
-    // to be implement
+    remark: (processor) => processor.use(remarkMath),
  }
}

Then consider the second thing, it would be a little complicated because we have two choices, do it in step 5 or 7. The difference is that step 5 is more friendly with SSR, while step 7 hand over the rendering to the client-side. This is why we have two plugin: @bytemd/plugin-math and @bytemd/plugin-math-ssr.

// if we choose step 5:
import type { BytemdPlugin } from 'bytemd'
import remarkMath from 'remark-math'
+import rehypeKatex from 'rehype-katex'

export default function mathPlugin(): BytemdPlugin {
  return {
    remark: (processor) => processor.use(remarkMath),
+   rehype: (processor) => processor.use(rehypeKatex),
  }
}

// if we choose step 7:
import type { BytemdPlugin } from 'bytemd'
import remarkMath from 'remark-math'
+import rehypeKatex from 'rehype-katex'

export default function mathPlugin(): BytemdPlugin {
  return {
    remark: (processor) => processor.use(remarkMath),
+   viewerEffect({ markdownBody }) {
+     const renderMath = async (selector: string, displayMode: boolean) => {
+       const katex = await import('katex').then((m) => m.default)
+
+       const els = markdownBody.querySelectorAll<HTMLElement>(selector)
+       els.forEach((el) => {
+         katex.render(el.innerText, el, { displayMode })
+       })
+     }
+
+     renderMath('.math.math-inline', false)
+     renderMath('.math.math-display', true)
+   },
  }
}

The last thing is to add an icon to the toolbar. we use the actions prop to implement it:

export default function mathPlugin(): BytemdPlugin {
  return {
    actions: [
      {
        title: 'Insert an math formula',
        icon: '', // 16x16 SVG icon
        handler: {
          type: 'action',
          click(ctx) {
            // to be implement:
            // the `ctx` is an instance of `BytemdEditorContext`, which has
            // several utility methods to help operate the CodeMirror editor state.

            // remember to call `focus` to avoid lost of focus
            editor.focus()
          },
        },
      },
    ],
  }
}

Now we have completed a minimalist version of the plugin! For more details and references please check out the source code.

Contributors

License

MIT

FOSSA Status

bytemd's People

Contributors

0xflotus avatar anzhizhang avatar badlop avatar bonbonboi avatar comradekingu avatar cpunisher avatar filipedeschamps avatar fitojb avatar fossabot avatar freedom-xiao007 avatar ghostchan avatar github-actions[bot] avatar hangcode2001 avatar infi avatar isfk avatar jiaoyin avatar kebukele avatar kovalevartem avatar maronghappy avatar mxite avatar nullorm avatar pd4d10 avatar pml-pml avatar santossi avatar shfshanyue avatar vitoropereira avatar weblate avatar wrz199306 avatar yzn-h avatar zouhirdehbi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

bytemd's Issues

因全量引入"@icon-park/svg"导致打包体积大

现有问题:插件plugin-gfm和plugin-math依赖了“@icon-park/svg”库,在代码引入的地方是全量导入,而非按需引入,导致项目在打包的时候,会将整个icons都打包到项目里
image
image

Where is the dark theme located?

The demo on the playground page displays the preview with a dark theme, and I was wondering where that might be located so I can use it in my own instance.

使用shiki来取代highlight.js来进行代码的高亮

/**
 * ByteMD plugin to show the code block with shiki
 * @param options The options for shiki
 */
export function shikiPlugin(options?: CodeToHastOptions): BytemdPlugin {
  return {
    viewerEffect({ markdownBody }) {
      const els = markdownBody.querySelectorAll<HTMLElement>('pre>code')
      if (els.length === 0)
        return
      els.forEach(async (el) => {
        const lang = el.className.replace('language-', '')
        el.className = `${el.className} shiki-code`
        const codeGet = el.textContent || ''
        const code = await codeToHtml(codeGet, useAssign(options, {
          lang,
          themes: {
            light: 'github-light',
            dark: 'github-dark',
          },
        }))

        if (el.parentElement)
          el.parentElement.innerHTML = code
      },
      )
    },
  }
}

这个是插件的定义方式,我这边使用正常。这个实现非常的简单,希望官方可以作为参考,另外它的深色模式需要选举定义一下css样式来覆盖

html.dark .shiki,
html.dark .shiki span {
  color: var(--shiki-dark) !important;
  background-color: var(--shiki-dark-bg) !important;
  font-style: var(--shiki-dark-font-style) !important;
  font-weight: var(--shiki-dark-font-weight) !important;
  text-decoration: var(--shiki-dark-text-decoration) !important;
}

如果需要我提供pr实现的话,我很乐意去做🫡。

Show wysiwyg toolbar actions in tab mode

Couldn't there be an option to show WYSIWYG actions (bold, heading, italic, ...) when the editor is in tab mode (either through model='tab' or when the screen is small enough) ?

markdown编辑支持快捷键

在编辑页面能否支持快捷键,比如:

  1. Alt+1:一级标题
  2. Alt+2:二级标题
  3. 。。。。。
    这种类似Halo1.6中编辑页面的快捷键

在编辑器里编写html标签添加内敛样式展示问题

编辑器里写的html标签的内敛样式貌似在展示过程中被去掉了,检查元素时没发现自己写的内敛样式。有什么方法解决这个问题吗?或者有其他方式给编辑器里写的html代码添加css样式吗?

import 'bytemd/dist/index.css'

react引入import 'bytemd/dist/index.css' 提示
Failed to resolve import "bytemd/dist/index.css" from "src\components\Content\component\MessageData.jsx". Does the file exist?

gfm插件存在表格无法加载的问题

结果展示

其他对应的md内容都能渲染除了table
image

项目环境

vue3+vite+ts,使用pnpm多次下载结果都一样

pnpm命令执行结果

`pnpm install @bytemd/plugin-gfm --force
 WARN  using --force I sure hope you know what you are doing
 WARN  10 deprecated subdependencies found: @types/[email protected], [email protected], [email protected], [email protected], [email protected], [email protected], sv
[email protected], [email protected], [email protected], [email protected]
Packages: +1415
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 1415, reused 1407, downloaded 8, added 1415, done
 WARN  Issues with peer dependencies found
.
├─┬ stylelint-config-recommended-vue 1.4.0
│ └─┬ stylelint-config-recommended 11.0.0
│ └── ✕ unmet peer stylelint@^15.3.0: found 14.16.1
└─┬ vite-plugin-vue-svg 0.1.0
└── ✕ unmet peer vite@^2.0.0-beta.61: found 3.2.5

Done in 19.2s
`

内嵌图片的问题

内嵌的图片不能显示?

<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhAQAAAAB/n//CAAAAk0lEQVR42mP4DwQNDJjkB6E77A0M3y/Yfm9g+BK2VBxIRu4FkeFXgeT3O3eB4h9EQ4Bq/v82B6r/xvLZvYHhxzMufqCs25LtQJGfZe0NDL+nPgWq/JTPnN/A8LH3IVD9l+dF04FqLhn4A/V+WQ4kPwiWloPsWp4PssshHUgGsz4HknHVIDfcigOa/EFEeDs2d4JJAEyabEVfJQ6QAAAAAElFTkSuQmCC" width="310" /></p>

![PIC](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhAQAAAAB/n//CAAAAk0lEQVR42mP4DwQNDJjkB6E77A0M3y/Yfm9g+BK2VBxIRu4FkeFXgeT3O3eB4h9EQ4Bq/v82B6r/xvLZvYHhxzMufqCs25LtQJGfZe0NDL+nPgWq/JTPnN/A8LH3IVD9l+dF04FqLhn4A/V+WQ4kPwiWloPsWp4PssshHUgGsz4HknHVIDfcigOa/EFEeDs2d4JJAEyabEVfJQ6QAAAAAElFTkSuQmCC)

PIC

Mermaid plugin not producing rendered output

I have included the mermaid plugin in the same way that the example markdown specifies, but when the render occurs the plugin appears to render only an empty svg element.

image

Here is the output HTML from the inspector:

image

vue的版本

大佬你们的vue的环境都是哪些呀,我这边下载mermaid和mathssr都报错,Module parse failed: Unexpected token (6:12333) You may need an appropriate,然后上传图片功能报selectFiles is not a function at Object.click (index.js?3a32:13082)错误,我想问下咱的环境是哪个版本,应该是版本不适配的问题,谢谢啦

Could we avoid blocking the main thread while parsing the markdown?

Do you guys think it would be possible to use a web worker to not block the main thread? The issue with a very long amount of markdown to parse is that the entire UI will be non interactive for the duration of the parsing which is a heavy operation with remarked/unified

url地址解析不正确

比如说输入:"www.baidu.com"是一个字符串。在计算机编程中,字符串是指由零个或多个字符组成的有限序列。它可以包含字母、数字、标点符号等。"www.baidu.com"是由字母、点和斜杠组成的字符串,通常表示一个网站的URL(统一资源定位符)
预览中解析成的a标签如下:
www.baidu.com"是一个字符串。在计算机编程中,字符串是指由零个或多个字符组成的有限序列。它可以包含字母、数字、标点符号等。"www.baidu.com"是由字母、点和斜杠组成的字符串,通常表示一个网站的URL(统一资源定位符)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.