## 1. Project Overview & Quickstart (Vanessa219/vditor)

## File: README.md

🔥 欢迎观摩我们的另一个开源项目 [思源笔记](https://github.com/siyuan-note/siyuan)


## 💡 简介

[Vditor](https://b3log.org/vditor) 是一款浏览器端的 Markdown 编辑器，支持所见即所得、即时渲染（类似 Typora）和分屏预览模式。它使用 TypeScript 实现，支持原生 JavaScript 以及 Vue、React、Angular 和 Svelte 等框架。

欢迎到 [Vditor 官方讨论区](https://ld246.com/tag/vditor)了解更多。同时也欢迎关注 B3log 开源社区微信公众号 `B3log开源`：



## 🗺️ 背景

随着 Markdown 排版方式的普及，越来越多的应用开始集成 Markdown 编辑器。目前主流可集成的 Markdown 编辑器现状如下：

* 有的仅支持分屏预览，即编辑区和预览区分离
* 有的同时支持所见即所得和分屏预览，但所见即所得模式下不能完整支持 Markdown 语法排版
* 几乎没有类似 Typora 的即时渲染

而这三点恰好对应了三种应用场景：

* 分屏预览：适配传统的 Markdown 使用场景，适合大屏下编辑排版
* 所见即所得：对不熟悉 Markdown 的用户友好，熟悉 Markdown 的用户也可以无缝使用
* 即时渲染：理论上这是最为优雅的 Markdown 编辑方式，让熟悉 Markdown 的用户能够更专注于内容创作

所以，一个能够**适配应用场景**的 Markdown 编辑器至关重要，它需要考虑到：

* 传统 Markdown 用户的使用场景，提供分屏预览
* 富文本编辑用户的使用场景，提供所见即所得
* 高阶 Markdown 用户的使用场景，提供即时渲染

Vditor 在这些方面做了努力，希望能为现代化的通用 Markdown 编辑领域做出一些贡献。

## ✨  特性

* 支持三种编辑模式：所见即所得（wysiwyg）、即时渲染（ir）、分屏预览（sv）
* 支持大纲、数学公式、脑图、图表、流程图、甘特图、时序图、数字波形图、五线谱、[多媒体](https://ld246.com/article/1589813914768)、语音阅读、标题锚点、代码高亮及复制、graphviz 渲染、[plantuml](https://plantuml.com)UML图
* 导出、图片懒加载、任务列表、多平台预览、多主题切换、复制到微信公众号/知乎功能
* 实现 CommonMark 和 GFM 规范，可对 Markdown 进行格式化和语法树查看，并支持[10+项](https://ld246.com/article/1549638745630#options-preview-markdown)配置
* 工具栏包含 36+ 项操作，除支持扩展外还可对每一项中的[快捷键](https://ld246.com/article/1582778815353)、提示、提示位置、图标、点击事件、类名、子工具栏进行自定义
* 表情/at/话题等自动补全扩展
* 可使用拖拽、剪切板粘贴上传，显示实时上传进度，支持 CORS 跨域上传
* 实时保存内容，防止意外丢失
* 录音支持，用户可直接发布语音
* 粘贴 HTML 自动转换为 Markdown，如粘贴中包含外链图片可通过指定接口上传到服务器
* 支持主窗口大小拖拽、字符计数
* 多主题支持，内置黑白绿三套主题
* 多语言支持，内置中、英、韩文本地化
* 支持主流浏览器，对移动端友好





## 🔮 编辑模式

### 所见即所得（WYSIWYG）

*所见即所得*模式对不熟悉 Markdown 的用户较为友好，熟悉 Markdown 的话也可以无缝使用。



### 即时渲染（IR）

*即时渲染*模式对熟悉 Typora 的用户应该不会感到陌生，理论上这是最优雅的 Markdown 编辑方式。



### 分屏预览（SV）

传统的*分屏预览*模式适合大屏下的 Markdown 编辑。



## 🍱 语法支持

* 所有 CommonMark 语法：分隔线、ATX 标题、Setext 标题、缩进代码块、围栏代码块、HTML 块、链接引用定义、段落、块引用、列表、反斜杠转义、HTML 实体、行级代码、强调、加粗、链接、图片、行级 HTML、硬换行、软换行和纯文本。
* 所有 GFM 语法：表格、任务列表项、删除线、自动链接、XSS 过滤
* 常用 Markdown 扩展语法：脚注、ToC、自定义标题 ID
* 图表语法
  * 流程图、时序图、甘特图，通过 Mermaid 支持
  * Graphviz
  * 数字波形图，通过 WaveDrom 支持
  * 折线图、饼图、脑图等，通过 ECharts 支持
* 五线谱：通过 abc.js 支持
* 数学公式：数学公式块、行级数学公式，通过 MathJax 和 KaTeX 支持
* YAML Front Matter
* 中文语境优化
  * 中西文之间插入空格
  * 术语拼写修正
  * 中文后跟英文逗号句号等标点替换为中文对应标点

以上大部分特性可以通过开关配置是否启用，开发者可根据自己的应用场景选择搭配。

## 🗃 案例

* [Sym](https://github.com/88250/symphony) 一款用 Java 实现的现代化社区（论坛/BBS/社交网络/博客）平台
* [Solo](https://github.com/88250/solo) & [Pipe](https://github.com/88250/pipe) B3log 分布式社区的博客端节点，欢迎加入下一代社区网络
* [Tditor](https://tditor.com) 基于React、Vditor、Springboot，一款打造极致文字创作体验的在线Markdown编辑平台
* [Arya](https://github.com/nicejade/markdown-online-editor) 基于 Vue、Vditor，所构建的在线 Markdown 编辑器
* [更多案例](https://github.com/Vanessa219/vditor/network/dependents?package_id=UGFja2FnZS0zMTY2Mzg4MzE%3D)

## 🛠️ 使用文档

### CommonJS

* 安装依赖

```shell
npm install vditor --save
```

* 在代码中引入并初始化对象，可参考 [index.js](https://github.com/Vanessa219/vditor/blob/master/demo/index.js)

```ts
import Vditor from 'vditor'
import "~vditor/src/assets/less/index"

const vditor = new Vditor(id, {options...})
```

### HTML script

* 在 HTML 中插入 CSS 和 JavaScript，可参考 [demo](https://b3log.org/vditor/demo/index.html)

```html

<link rel="stylesheet" href="https://unpkg.com/vditor/dist/index.css" />
<script src="https://unpkg.com/vditor/dist/index.min.js"></script>
```

### 示例代码

* [官方示例](https://b3log.org/vditor/demo/index.html) / [示例源码](https://github.com/Vanessa219/b3log-index/tree/master/src/vditor)
* [CommonJS Editor](https://github.com/Vanessa219/vditor/blob/master/demo/index.js)
* [CommonJS Render](https://github.com/Vanessa219/vditor/blob/master/demo/render.js)
* [在Svelte中使用](https://github.com/HerbertHe/svelte-vditor-demo)

### 主题

#### 编辑器主题

编辑器所展现的外观。内置classic，dark 2 套主题。

* 编辑器初始化时可通过 `options.theme` 设置内置主题
* 初始化完成后可通过 `setTheme` 更新编辑器主题
* 可通过修改 [index.less](https://github.com/Vanessa219/vditor/blob/master/src/assets/less/index.less) 中的变量对主题颜色进行定制
* 可参考现有结构和类名在原有基础上进行修改

#### 内容主题

Markdown 输出的 HTML 所展现的外观。内置 ant-design, light，dark，wechat 4 套主题。支持内容主题扩展接口。

* 需在显示元素上添加 `class="vditor-reset"`
* 编辑器初始化时可通过 `options.preview.theme` 设置内置或自己开发的主题列表
* 内容渲染初始化时可通过 `IPreviewOptions.theme` 设置内置或自己开发的主题
* 初始化完成后可通过 `setTheme` 或 `setContentTheme` 更新内容主题

#### 代码主题

代码块所展现的外观。内置 github 等 36 套主题。

* 编辑器初始化时可通过 `options.preview.hljs` 对代码块样式、行号、是否启用进行设置
* 内容渲染初始化时可通过 `IPreviewOptions.hljs` 对代码块样式、行号、是否启用进行设置
* 初始化完成后可通过 `setTheme` 或 `setCodeTheme` 更新代码主题

### API

#### id

可填入元素 `id` 或元素自身 `HTMLElement`

⚠️：当填入元素自身的 `HTMLElement` 时需设置 `options.cache.id` 或将 `options.cache.enable` 设置为 `false`

#### options

|   | 说明 | 默认值 |
| - | - | - |
| i18n | 多语言，参见 ITips | - |
| undoDelay | 历史记录间隔 | - |
| after | 编辑器异步渲染完成后的回调方法 | - |
| height | 编辑器总高度 | 'auto' |
| minHeight | 编辑区域最小高度 | - |
| width | 编辑器总宽度，支持 % | 'auto' |
| placeholder | 输入区域为空时的提示 | '' |
| lang | 语言种类：de_DE, en_US, es_ES, fr_FR, ja_JP, ko_KR, pt_BR, ru_RU, sv_SE, vi_VN, zh_CN, zh_TW | 'zh_CN' |
| input(value: string) | 输入后触发  | - |
| focus(value: string) | 聚焦后触发 | - |
| blur(value: string) | 失焦后触发 | - |
| keydown(event: KeyboardEvent) | 按下后触发 | - |
| esc(value: string) | <kbd>esc</kbd> 按下后触发 | - |
| ctrlEnter(value: string) | <kbd>⌘/ctrl+enter</kbd> 按下后触发 | - |
| select(value: string) | 编辑器中选中文字后触发 | - |
| unSelect() | 编辑器中未选中文字后触发 | - |
| tab | <kbd>tab</kbd> 键操作字符串，支持 `\t` 及任意字符串 | - |
| typewriterMode | 是否启用打字机模式 | false |
| cdn | 配置自建 CDN 地址 | `https://unpkg.com/vditor@${VDITOR_VERSION}` |
| mode | 可选模式：sv, ir, wysiwyg | 'ir' |
| debugger | 是否显示日志 | false |
| value | 编辑器初始化值 | '' |
| theme | 主题：classic, dark | 'classic' |
| icon | 图标风格：ant, material | 'ant' |
| customRenders: {language: string, render: (element: HTMLElement, vditor: IVditor) => void}[] | 自定义渲染器 | [] |
| customWysiwygToolbar(type: TWYSISYGToolbar, element: HTMLElement): void | 对 wysiwyg 模式下的工具栏进行自定义 | - |
| customWysiwygMobileToolbar(type: TWYSISYGToolbar, element: HTMLElement): void | 对移动端 wysiwyg 模式下的工具栏进行自定义，未添加菜单项时不显示入口 | - |

#### options.toolbar

* 工具栏，可使用 name 进行简写： `toolbar: ['emoji', 'br', 'bold', '|', 'line']` 。默认值参见 [src/ts/util/Options.ts](https://github.com/Vanessa219/vditor/blob/master/src/ts/util/Options.ts)
* name 可枚举为： `emoji`，`headings`，`bold`，`italic`，`strike`，`|`，`line`，`quote`，`list`，`ordered-list`，`check` ,`outdent` ,`indent`，`code`，`inline-code`，`insert-after`，`insert-before` ,`undo`，`redo`，`upload`，`link`，`table`，`record`，`edit-mode`，`both`，`preview`，`fullscreen`，`outline`，`code-theme`，`content-theme`，`export`, `devtools`，`info`，`help`，`br`
* 当 `name` 不在枚举中时，可以添加自定义按钮，格式如下：

```js
new Vditor('vditor', {
  toolbar: [
    {
      hotkey: '⇧⌘S',
      name: 'sponsor',
      tipPosition: 's',
      tip: '成为赞助者',
      className: 'right',
      icon: '<svg t="1589994565028" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2808" width="32" height="32"></svg>',
      click () {alert('捐赠地址：https://ld246.com/sponsor')},
    }],
})
```

|   | 说明 | 默认值 |
| - | - | - |
| name | 唯一标示 | - |
| icon | svg 图标 | - |
| tip | 提示 | - |
| tipPosition | 提示位置：'n', 'ne', 'nw', 's', 'se', 'sw', 'w', 'e' | - |
| hotkey | 快捷键，格式为<kbd>⇧⌘</kbd>/<kbd>⌘</kbd>/<kbd>⌥⌘</kbd>| - |
| suffix | 插入编辑器中的后缀 | - |
| prefix | 插入编辑器中的前缀 | - |
| click(event: Event, vditor: IVditor) | 自定义按钮点击时触发的事件 | - |
| className | 样式名 | '' |
| toolbar?: Array<options.toolbar> | 子菜单 | - |

#### options.toolbarConfig

|   | 说明 | 默认值 |
| - | - | - |
| hide | 是否隐藏工具栏 | false |
| pin | 是否固定工具栏 | false |

#### options.counter

|   | 说明 | 默认值 |
| - | - | - |
| enable | 是否启用计数器 | false |
| after(length: number, counter: options.counter): void | 字数统计回调 | - |
| max | 允许输入的最大值 | - |
| type | 统计类型：'markdown', 'text' | 'markdown' |

#### options.cache

|   | 说明 | 默认值 |
| - | - | - |
| enable | 是否使用 localStorage 进行缓存 | true |
| id | 缓存 key，第一个参数为元素且启用缓存时**必填** | - |
| after(html: string): string | 缓存后的回调 | - |

#### options.comment

⚠️：仅支持 wysiwyg 模式

|   | 说明 | 默认值 |
| - | - | - |
| enable | 是否启用评论模式 | false |
| add(id: string, text: string, commentsData: ICommentsData[]) | 添加评论回调 | - |
| remove(ids: string[]) | 删除评论回调 | - |
| scroll(top: number) | 滚动回调 | - |
| adjustTop(commentsData: ICommentsData[]) | 文档修改时，适配评论高度 | - |

#### options.preview

|   | 说明 | 默认值 |
| - | - | - |
| delay | 预览 debounce 毫秒间隔 | 1000 |
| maxWidth | 预览区域最大宽度 | 800 |
| mode | 显示模式：both, editor | 'both' |
| url | md 解析请求 | - |
| parse(element: HTMLElement) | 预览回调 | - |
| transform(html: string): string | 渲染之前回调 | - |

#### options.preview.hljs

|   | 说明 | 默认值 |
| - | - | - |
| defaultLang | 未指定语言时默认使用该语言 | '' |
| enable | 是否启用代码高亮 | true |
| style | 可选值参见[Chroma](https://xyproto.github.io/splash/docs/longer/all.html) | `github` |
| lineNumber | 是否启用行号 | false |
| langs | 自定义指定语言 | [CODE_LANGUAGES](https://github.com/Vanessa219/vditor/blob/53ca8f9a0e511b37b5dae7c6b15eb933e9e02ccd/src/ts/constants.ts#L20) |
| renderMenu(code: HTMLElement, copy: HTMLElement) | 渲染菜单按钮 | - |

#### options.preview.markdown

|   | 说明 | 默认值 |
| - | - | - |
| autoSpace | 自动空格 | false |
| callout | Callout 块 | true |
| gfmAutoLink | 自动链接 | true |
| fixTermTypo | 自动矫正术语 | false |
| toc | 插入目录 | false |
| footnotes | 脚注 | true |
| codeBlockPreview | wysiwyg 和 ir 模式下是否对代码块进行渲染 | true |
| mathBlockPreview | wysiwyg 和 ir 模式下是否对数学公式进行渲染 | true |
| paragraphBeginningSpace | 段落开头空两个 | false |
| sanitize | 是否启用过滤 XSS | true |
| listStyle | 为列表添加 data-style 属性 | false |
| linkBase | 链接相对路径前缀 | '' |
| linkPrefix | 链接强制前缀 | '' |
| mark | 启用 mark 标记 | false |
| sup | 上标 | false |
| sub | 下标 | false |

#### options.preview.theme

|   | 说明 | 默认值 |
| - | - | - |
| current | 当前主题 | "light" |
| list | 可选主题列表 | { "ant-design": "Ant Design", dark: "Dark", light: "Light", wechat: "WeChat" } |
| path | 主题样式地址 | `https://unpkg.com/vditor@${VDITOR_VERSION}/dist/css/content-theme` |

#### options.preview.math

|   | 说明 | 默认值 |
| - | - | - |
| inlineDigit | 内联数学公式起始 $ 后是否允许数字 | false |
| macros | 使用 MathJax 渲染时传入的宏定义 | {} |
| engine | 数学公式渲染引擎：KaTeX, MathJax | 'KaTeX' |
| mathJaxOptions | 数学公式渲染引擎为 MathJax 时的参数 | - |

#### options.preview.actions?: Array

默认值为 ["desktop", "tablet", "mobile", "mp-wechat", "zhihu"]。
可从默认值中挑选进行配置，也可使用以下字段进行自定制开发。

|   | 说明 | 默认值 |
| - | - | - |
| key | 按钮唯一标识，不能为空 | - |
| text | 按钮文字 | - |
| tooltip | 提示 | - |
| className | 按钮类名 | - |
| click(key: string) | 按钮点击回调事件 | - |

#### options.preview.render.media

|        | 说明        | 默认值  |
|--------|-----------|------|
| enable | 是否启用多媒体渲染 | true |

#### options.image

|   | 说明 | 默认值 |
| - | - | - |
| isPreview | 是否预览图片 | true |
| preview(bom: Element) => void | 图片预览处理 | - |

#### options.link

|   | 说明 | 默认值 |
| - | - | - |
| isOpen | 是否打开链接地址 | true |
| click(bom: Element) => void | 点击链接事件 | - |

#### options.hint

|   | 说明 | 默认值 |
| - | - | - |
| parse | 是否进行 md 解析 | true |
| delay | 提示 debounce 毫秒间隔 | 200 |
| emoji | 默认表情，可从[lute/emoji_map](https://github.com/88250/lute/blob/master/parse/emoji_map.go) 中选取，也可自定义 | { '+1': '👍', '-1': '👎', 'heart': '❤️', 'cold_sweat': '😰' } |
| emojiTail | 常用表情提示 | - |
| emojiPath | 表情图片地址 | `https://unpkg.com/vditor@${VDITOR_VERSION}/dist/images/emoji` |
| extend: IHintExtend[] | 对 @/话题等关键字自动补全的扩展 | [] |

```ts
interface IHintData {
  html: string;
  value: string;
}

interface IHintExtend {
    key: string;

    hint?(value: string): IHintData[] | Promise;
}
```

#### options.upload

* 文件上传的数据结构如下。后端返回的数据结构不一致时，可使用 `format` 进行转换。

```js
// POST data
xhr.send(formData);  // formData = FormData.append("file[]", File)
// return data
{
 "msg": "",
 "code": 0,
 "data": {
 "errFiles": ['filename', 'filename2'],
 "succMap": {
   "filename3": "filepath3",
   "filename3": "filepath3"
   }
 }
}
```

* 为了防止站外图片失效，`linkToImgUrl` 可将剪贴板中的站外图片地址传到服务器端进行保存处理，其数据结构如下：

```js
// POST data
xhr.send(JSON.stringify({url: src})); // src 为站外图片地址
// return data
{
 msg: '',
 code: 0,
 data : {
   originalURL: '',
   url: ''
 }
}
```

* `success`，`format`，`error` 不会同时触发，具体调用情况如下：

```js
if (xhr.status === 200) {
    if (vditor.options.upload.success) {
        vditor.options.upload.success(editorElement, xhr.responseText);
    } else {
        let responseText = xhr.responseText;
        if (vditor.options.upload.format) {
            responseText = vditor.options.upload.format(files as File [], xhr.responseText);
        }
        genUploadedLabel(responseText, vditor);
    }
} else {
    if (vditor.options.upload.error) {
        vditor.options.upload.error(xhr.responseText);
    } else {
        vditor.tip.show(xhr.responseText);
    }
}
```

|   | 说明 | 默认值 |
| - | - | - |
| xhr | 上传时使用的 XMLHttpRequest | - |
| url | 上传 url，为空则不会触发上传相关事件 | '' |
| max | 上传文件最大 Byte | 10 * 1024 * 1024 |
| linkToImgUrl | 剪切板中包含图片地址时，使用此 url 重新上传 | '' |
| linkToImgCallback(responseText: string) | 图片地址上传回调 | - |
| linkToImgFormat(responseText: string): string | 对图片地址上传的返回值进行格式化 | - |
| success(editor: HTMLPreElement, msg: string) | 上传成功回调 | - |
| error(msg: string) | 上传失败回调 | - |
| token | CORS 上传验证，头为 X-Upload-Token | - |
| withCredentials | 跨站点访问控制 | false |
| headers | 请求头设置 | - |
| filename(name: string): string | 文件名安全处理 | name => name.replace(/\W/g, '') |
| accept | 文件上传类型，同[input accept](https://www.w3schools.com/tags/att_input_accept.asp) | - |
| validate(files: File[]) => string \| boolean | 校验，成功时返回 true 否则返回错误信息 | - |
| handler(files: File[]) => string \| null \| Promise<string> \| Promise<null> | 自定义上传，当发生错误时返回错误信息 | - |
| format(files: File[], responseText: string): string | 对服务端返回的数据进行转换，以满足内置的数据结构 | - |
| file(files: File[]): File[] \| Promise<File[]> | 将上传的文件处理后再返回 | - |
| cancel(files: File[]): void | 取消正在上传的文件 | - |
| setHeaders(): { [key: string]: string } | 上传前使用返回值设置头 | - |
| extraData: { [key: string]: string \| Blob } | 为 FormData 添加额外的参数 | - |
| multiple | 上传文件是否为多个 | true |
| fieldName | 上传字段名称 | 'file[]' |
| renderLinkDest?(vditor: IVditor, node: ILuteNode, entering: boolean): [string, number] | 处理剪贴板中的图片地址 | '' |

#### options.resize

|   | 说明 | 默认值 |
| - | - | - |
| enable | 是否支持大小拖拽 | false |
| position | 拖拽栏位置：'top', 'bottom' | 'bottom' |
| after(height: number) | 拖拽结束的回调 | - |

#### options.classes

|   | 说明 | 默认值 |
| - | - | - |
| preview | 预览元素上的 className | '' |

#### options.fullscreen

|   | 说明 | 默认值 |
| - | - | - |
| index | 全屏层级 | 90 |

#### options.outline

|   | 说明 | 默认值 |
| - | - | - |
| enable | 初始化是否展现大纲 | false |
| position | 大纲位置：'left', 'right' | 'left' |

#### methods

|   | 说明 |
| - | - |
| exportJSON(markdown: string) | 根据 Markdown 获取对应 JSON |
| getValue() | 获取 Markdown 内容 |
| getHTML() | 获取 HTML 内容 |
| insertValue(value: string, render = true) | 在焦点处插入内容，并默认进行 Markdown 渲染 |
| focus() | 聚焦到编辑器 |
| blur() | 让编辑器失焦 |
| disabled() | 禁用编辑器 |
| enable() | 解除编辑器禁用 |
| getSelection(): string | 返回选中的字符串 |
| setValue(markdown: string, clearStack = false) | 设置编辑器内容且选中清空历史栈 |
| clearStack() | 清空撤销和重做记录栈|
| renderPreview(value?: string) | 设置预览区域内容 |
| getCursorPosition():{top: number, left: number} | 获取焦点位置 |
| deleteValue() | 删除选中内容 |
| updateValue(value: string) | 更新选中内容 |
| isUploading() | 上传是否还在进行中 |
| clearCache() | 清除缓存 |
| disabledCache() | 禁用缓存 |
| enableCache() | 启用缓存 |
| html2md(value: string) | HTML 转 md |
| tip(text: string, time: number) | 消息提示。time 为 0 将一直显示 |
| setPreviewMode(mode: "both" \| "editor") | 设置预览模式 |
| setTheme(theme: "dark" \| "classic", contentTheme?: string, codeTheme?: string, contentThemePath?: string) | 设置主题、内容主题及代码块风格 |
| getCurrentMode(): string | 获取编辑器当前编辑模式 |
| destroy() | 销毁编辑器 |
| getCommentIds(): {id: string, top: number}[] | 获取所有评论 |
| hlCommentIds(ids: string[]) | 高亮评论 |
| unHlCommentIds(ids: string[]) | 取消评论高亮 |
| removeCommentIds(removeIds: string[]) | 删除评论 |
| updateToolbarConfig(config: {hide?: boolean, pin?: boolean}) | 更新工具栏配置 |
| insertEmptyBlock(position: InsertPosition) | 插入空快 |

#### static methods

* 不需要进行编辑操作时，仅需引入 [`method.min.js`](https://unpkg.com/vditor/dist/) 后如下直接调用

```js
Vditor.mermaidRender(document)
```

```js
import VditorPreview from 'vditor/dist/method.min'
VditorPreview.mermaidRender(document)
```

* 需要对页面中的 Markdown 进行渲染时可直接调用 `preview` 方法，参数如下：

```ts
previewElement: HTMLDivElement,   // 使用该元素进行渲染
markdown: string,  // 需要渲染的 markdown 原文
options?: IPreviewOptions {
  mode: "dark" | "light";
  anchor?: number;  // 为标题添加锚点 0：不渲染；1：渲染于标题前；2：渲染于标题后，默认 0
  customEmoji?: { [key: string]: string };    // 自定义 emoji，默认为 {}
  lang?: (keyof II18nLang);    // 语言，默认为 'zh_CN'
  emojiPath?: string;    // 表情图片路径
  hljs?: IHljs; // 参见 options.preview.hljs
  speech?: {  // 对选中后的内容进行阅读
    enable?: boolean,
  };
  math?: IMath; // 数学公式渲染配置
  cdn?: string; // 自建 CDN 地址
  transform?(html: string): string; // 在渲染前进行的回调方法
  after?(); // 渲染完成后的回调
  lazyLoadImage?: string; // 设置为 Loading 图片地址后将启用图片的懒加载
  markdown?: options.preview.markdown;
  theme?: options.preview.theme;
  render?: options.preview.render;
  renderers?: ILuteRender; // 自定义渲染 https://ld246.com/article/1588412297062
}
```

* ⚠️ `method.min.js`  和 `index.min.js` 不可同时引入

|   | 说明 |
| - | - |
| previewImage(oldImgElement: HTMLImageElement, lang: keyof II18n = "zh_CN", theme = "classic") | 点击图片预览 |
| mermaidRender(element: HTMLElement, cdn = options.cdn, theme = options.theme) | 流程图/时序图/甘特图 |
| SMILESRender(element: HTMLElement, cdn = options.cdn, theme = options.theme) | 化学物质结构 |
| markmapRender(element: HTMLElement, cdn = options.cdn) | markdown 思维导图 |
| flowchartRender(element: HTMLElement, cdn = options.cdn) | flowchart 渲染 |
| codeRender(element: HTMLElement, option?: IHljs) | 为 element 中的代码块添加复制按钮 |
| chartRender(element: (HTMLElement \| Document) = document, cdn = options.cdn, theme = options.theme) | 图表渲染 |
| mindmapRender(element: (HTMLElement \| Document) = document, cdn = options.cdn, theme = options.theme) | 脑图渲染 |
| plantumlRender(element: (HTMLElement \| Document) = document, cdn = options.cdn) | plantuml 渲染 |
| abcRender(element: (HTMLElement \| Document) = document, cdn = options.cdn) | 五线谱渲染 |
| md2html(mdText: string, options?: IPreviewOptions): Promise\<string> | Markdown 文本转换为 HTML，该方法需使用[异步编程](https://ld246.com/article/1546828434083?r=Vanessa#toc_h3_1) |
| preview(previewElement: HTMLDivElement, markdown: string, options?: IPreviewOptions) | 页面 Markdown 文章渲染 |
| highlightRender(hljsOption?: IHljs, element?: HTMLElement \| Document, cdn = options.cdn) | 为 element 中的代码块进行高亮渲染 |
| mediaRender(element: HTMLElement) | 为[特定链接](https://ld246.com/article/1589813914768)分别渲染为视频、音频、嵌入的 iframe |
| mathRender(element: HTMLElement, options?: {cdn?: string, math?: IMath}) | 对数学公式进行渲染 |
| speechRender(element: HTMLElement, lang?: (keyof II18nLang)) | 对选中的文字进行阅读 |
| graphvizRender(element: HTMLElement, cdn?: string) | 对 graphviz 进行渲染 |
| wavedromRender(element: (HTMLElement \| Document) = document, cdn = options.cdn) | 对数字波形图进行渲染 |
| outlineRender(contentElement: HTMLElement, targetElement: Element) | 对大纲进行渲染 |
| lazyLoadImageRender(element: (HTMLElement \| Document) = document) | 对启用懒加载的图片进行渲染 |
| setCodeTheme(codeTheme: string, cdn = options.cdn) | 设置代码主题，codeTheme 参见 options.preview.hljs.style |
| setContentTheme(contentTheme: string, path: string) | 设置内容主题，contentTheme 参见 options.preview.theme.list |

## 🏗 开发文档

### 原理相关

* [关于所见即所得 Markdown 编辑器的讨论](https://ld246.com/article/1579414663700)
* [Vditor 实现 Markdown 所见即所得](https://ld246.com/article/1577370404903)
* [Lute 一款对中文语境优化的 Markdown 引擎，支持 Go 和 JavaScript](https://ld246.com/article/1567047822949)

### 环境

1. 安装 [node](https://nodejs.org/) LTS 版本
2. [下载](https://github.com/Vanessa219/vditor/archive/master.zip)最新代码并解压
3. 根目录运行 `npm install`
4. `npm run start` 启动本地服务器，打开 http://localhost:9000
5. 修改代码
6. `npm run build` 打包代码到 dist 目录

### CDN 切换

由于使用了按需加载的机制，默认 CDN 为 [https://unpkg.com/vditor](https://unpkg.com/vditor)@版本号

如果代码有修改或需要使用自建 CDN 的话，可按以下步骤进行操作：

* 初始化时，需对 `options` 及 `IPreviewOptions` 中的 `cdn`，`emojiPath`, `themes` 进行配置
* `highlightRender`，`mathRender`，`abcRender`，`chartRender`，`mermaidRender`，`SMILESRender`，`markmapRender`，`flowchartRender`，`mindmapRender`，`plantumlRender`，`graphvizRender`，`setCodeTheme`，`setContentTheme` 方法中需添加 cdn 参数
* 将 build 成功的 dist 目录或 [jsDelivr](https://www.jsdelivr.com/package/npm/vditor?path=dist) 中的 dist 目录拷贝至正确的位置

### 升级

版本升级时请**仔细阅读** [CHANGELOG](https://github.com/Vanessa219/vditor/blob/master/CHANGELOG.md) 中的**升级**部分

## Ⓜ️ Markdown 使用指南

* [基础语法](https://ld246.com/article/1583129520165)
* [扩展语法](https://ld246.com/article/1583305480675)
* [速查手册](https://ld246.com/article/1583308420519)

## 🏘️ 社区

* [官网](https://b3log.org/vditor)
* [讨论区](https://ld246.com/tag/vditor)
* [报告问题](https://github.com/Vanessa219/vditor/issues/new)

## 📄 授权

Vditor 使用 [MIT](https://opensource.org/licenses/MIT) 开源协议。

## 🙏 鸣谢

* [Lute](https://github.com/88250/lute)：🎼 一款结构化的 Markdown 引擎，支持 Go 和 JavaScript
* [highlight.js](https://github.com/highlightjs/highlight.js)：JavaScript syntax highlighter
* [mermaid](https://github.com/knsv/mermaid)：Generation of diagram and flowchart from text in a similar manner as Markdown
* [WaveDrom](https://github.com/wavedrom/wavedrom)：Digital timing diagram rendering engine
* [incubator-echarts](https://github.com/apache/incubator-echarts)：A powerful, interactive charting and visualization library for browser
* [abcjs](https://github.com/paulrosen/abcjs)：JavaScript library for rendering standard music notation in a browser

## 📽️ 历史

我们在开发 [Sym](https://github.com/88250/symphony) 的初期是直接使用 WYSIWYG 富文本编辑器的。那时候基于 HTML 的编辑器非常流行，项目中引用起来也很方便，也符合用户当时的使用习惯。

后来，Markdown 的崛起逐步改变了大家的排版方式。再加上我们其他几个项目都是面向程序员用户的，所以迁移到 md 上也是大势所趋。我们选择了 [CodeMirror](https://github.com/codemirror/CodeMirror)，这是一款优秀的编辑器，它对开发者提供了丰富的编程接口，对各种浏览器的兼容性也比较好。

再后来，随着我们项目业务需求方面的沉淀，使用 CodeMirror 有时候会感到比较“笨重”。比如要实现 @自动完成用户名列表、插入 Emoji、上传文件等就需要比较深入的二次开发，而这些业务需求恰恰是很多项目场景共有且必备的。

终于，我们决定开始在 Sym 中自己实现编辑器。随着几个版本的迭代，Sym 的编辑器也日趋成熟。在我们运营的社区[链滴](https://ld246.com)上陆续有人问我们是否能将编辑器单独抽离出来提供给大家使用。与此同时，我们的前端主程 [V](https://ld246.com/member/Vanessa) 同学对于维护分散在各个项目中的编辑器也感到有点力不从心，外加对 TypeScript 的好感，所以就决定使用 ts 来实现一个全新的浏览器端 md 编辑器。

于是，Vditor 就这样诞生了。

---

## File: README_en_US.md

🔥 Welcome to check out our other open source project [SiYuan](https://github.com/siyuan-note/siyuan)


## 💡 Introduction

[Vditor](https://b3log.org/vditor) is a browser-side Markdown editor, Support WYSIWYG, instant rendering (similar to Typora) and split-screen preview mode. It is implemented using TypeScript and supports native JavaScript and frameworks such as Vue, React, Angular, and Svelte..

Welcome to [Vditor Official Site](https://b3log.org/vditor) to learn more.

## 🗺️ Background

With the popularization of Markdown typesetting methods, more and more applications begin to integrate the Markdown editor. The current status of mainstream integrable Markdown editors is as follows:

* Some only support split-screen preview, that is, the editing area and preview area are separated
* Some support WYSIWYG and split-screen previews at the same time, but they cannot fully support Markdown syntax typesetting in WYSIWYG mode
* There is almost no instant rendering similar to Typora

These three points correspond to three application scenarios:

* Split screen preview: adapt to the traditional Markdown usage scene, suitable for editing and typesetting on a large screen
* WYSIWYG: Friendly to users who are not familiar with Markdown, users who are familiar with Markdown can also use it seamlessly
* Instant rendering: In theory, this is the most elegant Markdown editing method, allowing users familiar with Markdown to focus more on content creation

Therefore, a Markdown editor that can **adapt to application scenarios** is essential, it needs to consider:

* Use scenes of traditional Markdown users, providing split screen preview
* Rich text editing users' usage scenarios, providing WYSIWYG
* High-end Markdown users' use scenes, providing instant rendering

Vditor has made efforts in these areas, hoping to make some contributions to the modern general Markdown editing field.

## ✨  Features

* Support three editing modes: WYSIWYG(wysiwyg), Instant Rendering(ir) and Split View(sv)
* Support outline, mathematical formulas, mind maps, charts, flowcharts, Gantt charts, sequence diagrams, digital timing diagrams, staffs, [multimedia](https://ld246.com/article/1589813914768), voice reading, heading anchors, code highlighting and copying, graphviz rendering
* Export, image lazy loading, task list, multi-platform preview, multi-theme switching, copy to WeChat/zhihu function
* Implementation of CommonMark and GFM specifications, formatting and syntax tree viewing of Markdown, and support for [10+ configurations](https://ld246.com/article/1549638745630#options-preview-markdown)
* The toolbar contains 36+ items of operations. In addition to support for expansion, the [shortcut keys](https://ld246.com/article/1582778815353), tip, tip positions, icons, click events, class names, and sub-toolbars can be customized
* Extend auto-complete for emoji/@/# and so on
* You can use drag and drop, paste and paste to upload, display real-time upload progress, support CORS cross-domain upload
* Save content in real time to prevent accidental loss
* Recording support, users can directly publish voice
* The markup HTML is automatically converted to Markdown, if the paste contains images of external links, it can be uploaded to the server through the specified interface
* Support main window size drag and drop, character counting
* Multi-theme support, built-in black, white and green three sets of themes
* Multi-language support, built-in Chinese, English, Korean text localization
* Support mainstream browsers, mobile friendly





## 🔮 Editing Modes

### WYSIWYG

*WYSIWYG* mode is more friendly to users who are not familiar with Markdown, and you can use it seamlessly if you are familiar with Markdown.



### Instant Rendering

*Instant Rendering* mode should not be unfamiliar to users who are familiar with Typora. In theory, this is the most elegant Markdown editing method.



### Split View

The traditional *Split View* mode is suitable for Markdown editing on a large screen.



## 🍱 Syntax support

* All CommonMark syntax: Thematic breaks, ATX headings, Setext headings, Indented code blocks, Fenced code blocks, HTML blocks, Link reference definitions, Paragraphs, Block quotes, Lists, Backslash escapes, Entity and numeric character references, Code spans, Emphasis and strong emphasis, Links, Images, Raw HTML, Hard line breaks, Soft line breaks, and Textual content.
* All GFM syntax: Tables, Task list items, Strikethrough, Autolinks, XSS filtering
* Common Markdown extended syntax: Footnotes, ToC, Custom Heading ID
* Chart syntax
  * Flow chart, sequence diagram, Gantt chart, supported by Mermaid
  * Graphviz
  * Digital timing diagrams, supported by WaveDrom
  * Line chart, pie chart, brain chart, etc., supported by ECharts
* Stave: supported by abc.js
* Math formulas: Math formula blocks, row-level math formulas, supported by MathJax and KaTeX
* YAML Front Matter
* Chinese context optimization
  * Insert space between Chinese and Western
  * Terminology spelling correction
  * Chinese followed by English comma period and other punctuation are replaced with Chinese corresponding punctuation

Most of the above features can be enabled or disabled through the switch configuration, developers can choose to match according to their own application scenarios.

## 🗃 Showcases

* [Sym](https://github.com/88250/symphony) A modern community (forum/BBS/SNS/blog) platform implemented in Java
* [Solo](https://github.com/88250/solo) & [Pipe](https://github.com/88250/pipe) B3log distributed community blog end node, welcome to join the next generation community network
* [Arya](https://github.com/nicejade/markdown-online-editor) Based on Vue, Vditor, built online Markdown editor
* [More cases](https://github.com/Vanessa219/vditor/network/dependents?package_id=UGFja2FnZS0zMTY2Mzg4MzE%3D)

## 🛠️ User Guide

### CommonJS

* Install dependencies

```shell
npm install vditor --save
```

* Introduce and initialize objects in the code, you can refer to [index.js](https://github.com/Vanessa219/vditor/blob/master/demo/index.js)

```ts
import Vditor from 'vditor'
import "~vditor/src/assets/less/index" // Or use dark

const vditor = new Vditor(id, {options...})
```

### HTML script

* Insert CSS and js in HTML, you can refer to [demo](https://b3log.org/vditor/demo/index.html)

```html

<link rel="stylesheet" href="https://unpkg.com/vditor/dist/index.css" />
<script src="https://unpkg.com/vditor/dist/index.min.js"></script>
```

### Demo code

* [Demo](https://b3log.org/vditor/demo/index.html)
* [CommonJS Editor](https://github.com/Vanessa219/vditor/blob/master/demo/index.js)
* [CommonJS Render](https://github.com/Vanessa219/vditor/blob/master/demo/render.js)
* [Use in Svelte](https://github.com/HerbertHe/svelte-vditor-demo)

### Themes

* Support four sets of black and white themes: and-design, classic, dark, wechat
* Use the less/css developed by yourself to fully customize the style after referring to the existing style
* Theme colors can be customized by modifying variables in [index.less](https://github.com/Vanessa219/vditor/blob/master/src/assets/less/index.less)
* Adding `class="vditor-reset"` (classic theme) or `class="vditor-reset vditor-reset--dark"` (black theme) attribute on the content display element can display the content more friendly

### API

#### id

Can be filled with element `id` or element itself` HTMLElement`

⚠️: When filling in the element's `HTMLElement`, you need to set `options.cache.id` or set `options.cache.enable` to `false`

#### options

|   | Explanation | Default |
| - | - | - |
| i18n | I18n, more details see ITips | - |
| undoDelay | Undo interval | - |
| after | Callback method after editor asynchronous rendering is completed | - |
| height | Total editor height | 'auto' |
| minHeight | Editing area minimum height | - |
| width | Total editor width, supports % | 'auto' |
| placeholder | Tips when the input area is empty | '' |
| lang | I18n type: de_DE, en_US, es_ES, fr_FR, ja_JP, ko_KR, pt_BR, ru_RU, sv_SE, vi_VN, zh_CN, zh_TW | 'zh_CN' |
| input(value: string) | Trigger after input  | - |
| focus(value: string) | Trigger after focusing | - |
| blur(value: string) | Trigger after out of focus | - |
| keydown(event: KeyboardEvent) | Trigger after keydown | - |
| esc(value: string) | Trigger after pressing | - |
| ctrlEnter(value: string) | Trigger after pressing <kbd>⌘/ctrl+enter</kbd> | - |
| select(value: string) | Triggered after selecting text in the editor | - |
| unSelect() | Triggered after un selecting text in the editor | - |
| tab | <kbd>tab</kbd> key operation string, support `\ t` and any string | - |
| typewriterMode | Whether to enable typewriter mode | false |
| cdn | Configure self-built CDN address | `https://unpkg.com/vditor@${VDITOR_VERSION}` |
| mode | Editing mode: sv, ir, wysiwyg | 'ir' |
| debugger | Whether to display the log | false |
| value | Editor initialization value | '' |
| theme | Theme: classic, dark | 'classic' |
| icon | icon theme: ant, material | 'ant' |
| customRenders: {language: string, render: (element: HTMLElement, vditor: IVditor) => void}[] | Custom render | [] |
| customWysiwygToolbar(type: TWYSISYGToolbar, element: HTMLElement): void | Customizing the toolbar in wysiwyg mode | - |
| customWysiwygMobileToolbar(type: TWYSISYGToolbar, element: HTMLElement): void | Customize the toolbar in mobile WYSIWYG mode; the entry is hidden when no menu item is added | - |

#### options.toolbar

* Toolbar, you can use name for shorthand: `toolbar: ['emoji', 'br', 'bold', '|', 'line']`. See default [src/ts/util/Options.ts](https://github.com/Vanessa219/vditor/blob/master/src/ts/util/Options.ts)
* name can be enumerated as: `emoji` , `headings` , `bold` , `italic` , `strike` , `|` , `line` , `quote` , `list` , `ordered-list` , `check` ,`outdent` ,`indent` , `code` , `inline-code`, `insert-after`, `insert-before`, `code-theme`, `content-theme`, `export`, `undo` , `redo` , `upload` , `link` , `table` , `record` , `edit-mode` , `both` , `preview` , `fullscreen` , `outline` , `devtools` , `info` , `help` , `br`
* When `name` is not in the enumeration, you can add a custom button in the following format:

```js
new Vditor('vditor', {
  toolbar: [
    {
      hotkey: '⇧⌘S',
      name: 'sponsor',
      tipPosition: 's',
      tip: '成为赞助者',
      className: 'right',
      icon: '<svg t="1589994565028" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2808" width="32" height="32"></svg>',
      click () {alert('捐赠地址：https://ld246.com/sponsor')},
    }],
})
```

|   | Explanation | Default |
| - | - | - |
| name | Unique label | - |
| icon | svg icon | - |
| tip | Prompt | - |
| tipPosition | Prompt location: 'n', 'ne', 'nw', 's', 'se', 'sw', 'w', 'e' | - |
| hotkey | Shortcut keys, support 为<kbd>⇧⌘</kbd>/<kbd>⌘</kbd>/<kbd>⌥⌘</kbd> format configuration | - |
| suffix | Insert the suffix in the editor | - |
| prefix | Insert the prefix in the editor | - |
| click(event: Event, vditor: IVditor) | Custom event triggered when button is clicked | - |
| className | Style name | '' |
| toolbar?: Array<options.toolbar> | sub menu | - |

#### options.toolbarConfig

|   | Explanation | Default |
| - | - | - |
| hide | Whether to hide the toolbar | false |
| pin | Whether to pin the toolbar | false |

#### options.counter

|   | Explanation | Default |
| - | - | - |
| enable | Whether to use counter | false |
| after(length: number, counter: options.counter): void | After count callback | - |
| max | max counter | - |
| type | counter type: 'markdown', 'text' | 'markdown' |

#### options.cache

|   | Explanation | Default |
| - | - | - |
| enable | Whether to use localStorage for caching | true |
| id | Cache key, the first parameter is an element and when caching is enabled **required** | - |
| after | cache callback (markdown: string) | - |

#### options.comment

⚠️: Only supports wysiwyg mode

|   | Explanation | Default |
| - | - | - |
| enable | Whether to enable comment mode | false |
| add(id: string, text: string, commentsData: ICommentsData[]) | Add comment callback | - |
| remove(ids: string[]) | delete comment callback | - |
| scroll(top: number) | Scroll callback | - |
| adjustTop(commentsData: ICommentsData[]) | Adapt the comment height | - |

#### options.preview

|   | Explanation | Default |
| - | - | - |
| delay | Preview debounce millisecond interval | 1000 |
| maxWidth | Preview area maximum width | 800 |
| mode | Display mode: both, editor | 'both' |
| url | md parsing request | - |
| parse | Preview callback (element: HTMLElement) | - |
| transform | Callback before rendering (html: string): string | - |

#### options.preview.theme

|   | Explanation | Default |
| - | - | - |
| current | current Markdown Theme | "light" |
| list | Choose Markdown Theme List | { "ant-design": "Ant Design", dark: "Dark", light: "Light", wechat: "WeChat" } |
| path | CSS Path | `https://unpkg.com/vditor@${VDITOR_VERSION}/dist/css/content-theme` |

#### options.preview.hljs

|   | Explanation | Default |
| - | - | - |
| defaultLang | The language is used by default when no language is specified | '' |
| enable | Whether to enable code syntax highlighting | true |
| style | For optional values, see [Chroma](https://xyproto.github.io/splash/docs/longer/all.html) | `github` |
| lineNumber | Whether to enable line number | false |
| langs | Custom languages | [CODE_LANGUAGES](https://github.com/Vanessa219/vditor/blob/53ca8f9a0e511b37b5dae7c6b15eb933e9e02ccd/src/ts/constants.ts#L20) |
| renderMenu(code: HTMLElement, copy: HTMLElement) | render menu button | - |

#### options.preview.markdown

|   | Explanation | Default |
| - | - | - |
| autoSpace | Autospace | false |
| gfmAutoLink | Automatic link | true |
| fixTermTypo | Automatically correct terminology | false |
| toc | Insert Table of Contents | false |
| footnotes | Footnotes | true |
| codeBlockPreview | Whether to render code blocks in wysiwyg and ir modes | true |
| mathBlockPreview | Whether to render math blocks in wysiwyg and ir modes | true |
| paragraphBeginningSpace | Two spaces before the paragraph | false |
| sanitize | Use XSS | true |
| listStyle | add data-style attribute | false |
| linkBase | link relative path prefix | '' |
| linkPrefix | link prefix | '' |
| mark | enable mark tag | false |
| sup | superscript | false |
| sub | subscript | false |

#### options.preview.math

|   | Explanation | Default |
| - | - | - |
| inlineDigit | Whether numbers are allowed after the inline math formula starting with $ | false |
| macros | Macro definition passed in when rendering with MathJax | {} |
| engine | Math formula rendering engine: KaTeX, MathJax | 'KaTeX' |
| mathJaxOptions | Parameters when the math formula rendering engine is MathJax | - |

#### options.preview.actions

Default: ["desktop", "tablet", "mobile", "mp-wechat", "zhihu"]

|   | Explanation | Default |
| - | - | - |
| key | Custom action ID, not Empty. | - |
| tooltip | Tooltip | - |
| text | Button Text | - |
| className | Button Class | - |
| click(key: string) | Click Event | - |

#### options.preview.render.media

|        | Explanation        | Default  |
|--------|-----------|------|
| enable | Whether to enable multimedia render | true |

#### options.image

|   | Explanation | Default |
| - | - | - |
| isPreview | Whether to preview the picture | true |
| preview(bom: Element) => void | Image preview processing | - |

#### options.link

|   | Explanation | Default |
| - | - | - |
| isOpen | Whether to open the link address | true |
| click(bom: Element) => void | Click link event | - |

#### options.hint

|   | Explanation | Default |
| - | - | - |
| parse | Whether to perform md parsing | true |
| delay | Tip debounce millisecond interval | 200 |
| emoji | The default emoji can be selected from [lute/emoji_map](https://github.com/88250/lute/blob/master/parse/emoji_map.go), or can be customized | { '+1': '👍', '-1': '👎', 'heart': '❤️', 'cold_sweat': '😰' } |
| emojiTail | Common emoji | - |
| emojiPath | Emoji path | `https://unpkg.com/vditor@${VDITOR_VERSION}/dist/images/emoji` |
| extend: IHintExtend[] | @/# and other keyword auto-completion expansion | [] |

```ts
interface IHintData {
  html: string;
  value: string;
}

interface IHintExtend {
  key: string;

  hint?(value: string): IHintData[] | Promise;
}
```

#### options.upload

* The data structure of the file upload is as follows. When the data structure returned by the backend is inconsistent, you can use `format` for conversion.

```js
// POST data
xhr.send(formData);  // formData = FormData.append("file[]", File)
// return data
{
 "msg": "",
 "code": 0,
 "data": {
 "errFiles": ['filename', 'filename2'],
 "succMap": {
   "filename3": "filepath3",
   "filename3": "filepath3"
   }
 }
}
```

* In order to prevent the off-site pictures from being invalid, `linkToImgUrl` can transfer the off-site picture addresses in the clipboard to the server for saving and processing. The data structure is as follows:

```js
// POST data
xhr.send(JSON.stringify({url: src})); // src is the address of the image outside the station
// return data
{
 msg: '',
 code: 0,
 data : {
   originalURL: '',
   url: ''
 }
}
```

|   | Explanation | Default |
| - | - | - |
| xhr | XMLHttpRequest used when uploading | - |
| url | Upload url, empty will not trigger upload related events | '' |
| max | The largest upload file Byte | 10 * 1024 * 1024 |
| linkToImgUrl | When the clipboard contains the image address, use this url to re-upload | '' |
| linkToImgCallback | Callback when uploading picture address | - |
| linkToImgFormat | Transform the data returned by the server to meet the built-in data structure (responseText: string): string | - |
| success | Upload success callback (editor: HTMLPreElement, msg: string) | - |
| error | Upload failure callback (msg: string) | - |
| token | CORS upload verification, header is X-Upload-Token | - |
| withCredentials | Cross-site access control | false |
| headers | Request header settings | - |
| filename | Sanitizing file names (name: string): string \| name => name.replace(/\W/g, '') |
| accept | File upload type, same as [input accept](https://www.w3schools.com/tags/att_input_accept.asp) | - |
| validate | Check, return true if successful, otherwise return error message (files: File[]) => string \| boolean | - |
| handler(files: File[]) => string \| null \| Promise<string> \| Promise<null> | Custom upload, return error message when an error occurs | - |
| format | Transform the data returned by the server to meet the built-in data structure (files: File[], responseText: string): string | - |
| file(files: File[]): File[] \| Promise<File[]> | Process the uploaded file before return. | - |
| cancel(files: File[]): void | Cancel uploading a file. | - |
| setHeaders | Use the return value to set the header before uploading (): { [key: string]: string } | - |
| extraData | Append data to FormData { [key: string]: string | Blob } | - |
| multiple | Allow multiple file uploads | true |
| fieldName | The key of field name | file[] |
| renderLinkDest?(vditor: IVditor, node: ILuteNode, entering: boolean): [string, number] | Process the image address in the clipboard | '' |

#### options.resize

|   | Explanation | Default |
| - | - | - |
| enable | Whether to support size drag | false |
| position | Drag column position: 'top', 'bottom' | 'bottom' |
| after | Callback when dragging ends (height: number) | - |

#### options.classes

|   | Explanation | Default |
| - | - | - |
| preview | Preview on the element className | '' |

#### options.fullscreen

|   | Explanation | Default |
| - | - | - |
| index | fullscreen index | 90 |

#### options.outline

|   | Explanation | Default |
| - | - | - |
| enable | Initialize whether to show outline | false |
| position | Outline location: 'left', 'right' | 'left' |

#### methods

|   | Explanation |
| - | - |
| exportJSON(markdown: string) | Get JSON by markdown |
| getValue() | Get editor content |
| getHTML() | Get preview area content |
| insertValue(value: string, render = true) | Insert content at the focus and markdown rendering by default |
| focus() | Focus on the editor |
| blur() | Make the editor out of focus |
| disabled() | Disable editor |
| enable() | Unedit editor |
| getSelection(): string | Returns the selected string |
| setValue(markdown: string, clearStack = false) | Set editor content |
| clearStack() | remove undo and redo stack |
| renderPreview(value?: string) | Set preview area content |
| getCursorPosition():{top: number, left: number} | Get focus position |
| deleteValue() | Delete selected content |
| updateValue(value: string) | Update selected content |
| isUploading() | Whether the upload is still in progress |
| clearCache() | clear cache |
| disabledCache() | Disable cache |
| enableCache() | Enable caching |
| html2md(value: string) | HTML to md |
| tip(text: string, time: number) | notification. time is 0 will always display |
| setPreviewMode(mode: "both" \| "editor") | Set preview mode |
| setTheme(theme: "dark" | "classic", contentTheme?: string, codeTheme?: string, contentThemePath?: string) | Set theme |
| getCurrentMode(): string | Get the editor's current editing mode |
| destroy() | Destroy the vditor |
| getCommentIds(): {id: string, top: number}[] | Get all comments |
| hlCommentIds(ids: string[]) | Highlight comment by Ids |
| unHlCommentIds(ids: string[]) | Cancel highlight comment by Ids |
| removeCommentIds(removeIds: string[]) | Remove comment by Ids |
| updateToolbarConfig(config: {hide?: boolean, pin?: boolean}) | Update toolbar config |
| insertEmptyBlock(position: InsertPosition) | Insert empty block |

#### static methods

* When no editing operation is required, just introduce [`method.min.js`](https://unpkg.com/vditor/dist/) and directly call

```js
Vditor.mermaidRender(document)
```

```js
import VditorPreview from 'vditor/dist/method.min'
VditorPreview.mermaidRender(document)
```

* When you need to render Markdown on the page, you can directly call the `preview` method with the following parameters:

```ts
previewElement: HTMLDivElement,   // Use this element for rendering
markdown: string,  // The original markdown to be rendered
options?: IPreviewOptions {
  mode: "dark" | "light";
  anchor?: number;  // 0: no render, 1: render left, 2: render right
  customEmoji?: { [key: string]: string };    // Custom emoji, default is {}
  lang?: (keyof II18nLang);    // Language, default is 'zh_CN'
  emojiPath?: string;    // Emoji picture path
  hljs?: IHljs; // Refer to options.preview.hljs
  speech?: {  // Read the selected content
    enable?: boolean,
  };
  math?: IMath; // Math formula rendering configuration
  transform?(html: string): string; // Callback method before rendering
  after?(); // Callback method after rendering
  cdn?: string; // Self-built CDN address
  lazyLoadImage?: string; // use "https://unpkg.com/vditor/dist/images/img-loading.svg" to lazy load image
  markdown?: options.preview.markdown;
  render?: options.preview.render;
  renderers?: ILuteRender; // Custom rendering method https://ld246.com/article/1588412297062
}
```

* ⚠️`method.min.js` and` index.min.js` cannot be introduced at the same time

|   | Explanation |
| - | - |
| previewImage(oldImgElement: HTMLImageElement, lang: keyof II18n = "zh_CN", theme = "classic") | Click on the image to preview |
| mermaidRender(element: HTMLElement, cdn = options.cdn, theme = options.theme) | flowchart/sequence diagram/gantt diagram rendering |
| SMILESRender(element: HTMLElement, cdn = options.cdn, theme = options.theme) | the structure of chemical |
| markmapRender(element: HTMLElement, cdn = options.cdn) | markdown Mind Map |
| flowchartRender(element: HTMLElement, cdn = options.cdn) | flowchart.js rendering |
| codeRender(element: HTMLElement, option?: IHljs) | Add a copy button for the code block in element |
| chartRender(element: (HTMLElement\| Document) = document, cdn = options.cdn, theme = options.theme) | Chart rendering |
| plantumlRender(element: (HTMLElement\| Document) = document, cdn = options.cdn) | plantuml rendering |
| abcRender(element: (HTMLElement\| Document) = document, cdn = options.cdn) | Stave rendering |
| outlineRender(contentElement: HTMLElement, targetElement: Element, vditor?: IVditor) | Outline rendering |
| md2html(mdText: string, options?: IPreviewOptions): Promise\<string> | Markdown text is converted to HTML, this method needs to use [asynchronous programming](https://ld246.com/article/1546828434083?r=Vanessa#toc_h3_1) |
| preview(previewElement: HTMLDivElement, markdown: string, options?: IPreviewOptions) | Page Markdown article rendering |
| highlightRender(hljsOption?: IHljs, element?: HTMLElement \| Document, cdn = options.cdn) | Highlight the code block in element |
| mediaRender(element: HTMLElement) | Rendering as [specific link](https://ld246.com/article/1589813914768) as video, audio, embedded iframe |
| mathRender(element: HTMLElement, options?: {cdn?: string, math?: IMath}) | Render math formulas |
| speechRender(element: HTMLElement, lang?: (keyof II18nLang)) | Read the selected text |
| graphvizRender(element: HTMLElement, cdn?: string) | Render graphviz |
| wavedromRender(element: (HTMLElement \| Document) = document, cdn = options.cdn) | Render digital timing diagrams |
| lazyLoadImageRender(element: (HTMLElement \| Document) = document) | Render lazy load image |
| setCodeTheme (codeTheme: string, cdn = options.cdn)  | update code theme |
| setContentTheme (contentTheme: string, path: string)  | update content theme |
| mindmapRender (element: (HTMLElement \| Document) = document, cdn = options.cdn, theme = options.theme)  | Render mind map |

## 🏗 Developer Guide

### Principle related

* [Discussion on WYSIWYG Markdown Editor](https://ld246.com/article/1579414663700)
* [Vditor implements Markdown WYSIWYG](https://ld246.com/article/1577370404903)
* [Lute is a Markdown engine optimized for Chinese context, supports Go and JavaScript](https://ld246.com/article/1567047822949)

### Environment

1. Install [node](https://nodejs.org/) LTS version
2. [Download](https://github.com/Vanessa219/vditor/archive/master.zip) latest code and unzip
3. Run `npm install` in the root directory
4. `npm run start` Start the local server, open http: // localhost: 9000
5. Modify the code
6. `npm run build` package code to dist directory

### CDN switch

Due to the on-demand loading mechanism, the default CDN is [https://unpkg.com/vditor](https://unpkg.com/vditor)@version number

If the code is modified or you need to use a self-built CDN, you can follow the steps below:

* The initial `options` and` IPreviewOptions` need to add `cdn` configuration
* `highlightRender`, `mathRender`, `abcRender`, `chartRender`, `mermaidRender`, `SMILESRender`, `markmapRender`, `flowchartRender`, `mindmapRender`, `plantumlRender`, `graphvizRender`, `setCodeTheme`, `setContentTheme` methods need to add cdn parameter
* Copy the dist directory in the successful build or [jsDelivr](https://www.jsdelivr.com/package/npm/vditor?path=dist) to the correct location

### Upgrade

Please read [CHANGELOG](https://github.com/Vanessa219/vditor/blob/master/CHANGELOG.md) carefully when upgrading the version.

## Ⓜ️ Markdown User Guide

* [Basic syntax](https://ld246.com/article/1583129520165)
* [Extended syntax](https://ld246.com/article/1583305480675)
* [Quick Reference Manual](https://ld246.com/article/1583308420519)

## 🏘️ Community

* [Forum](https://ld246.com/tag/vditor)
* [Issues](https://github.com/Vanessa219/vditor/issues/new)

## 📄 License

Vditor uses the [MIT](https://opensource.org/licenses/MIT) open source license.

## 🙏 Acknowledgement

* [Lute](https://github.com/88250/lute): A structured Markdown engine that supports Go and JavaScript
* [highlight.js](https://github.com/highlightjs/highlight.js): JavaScript syntax highlighter
* [mermaid](https://github.com/knsv/mermaid): Generation of diagram and flowchart from text in a similar manner as Markdown
* [WaveDrom](https://github.com/wavedrom/wavedrom): Digital timing diagram rendering engine
* [incubator-echarts](https://github.com/apache/incubator-echarts): A powerful, interactive charting and visualization library for browser
* [abcjs](https://github.com/paulrosen/abcjs): JavaScript library for rendering standard music notation in a browser
* [IntelliJ IDEA](https://www.jetbrains.com/?from=Vditor): Family of capable and ergonomic development environments

## 📽️ History

In the initial stage of developing [Sym](https://github.com/88250/symphony), we directly used WYSIWYG rich text editor. At that time, HTML-based editors were very popular, and it was very convenient to quote them in the project, which also conformed to the usage habits of users at that time.

Later, the rise of Markdown gradually changed everyone's typography. In addition, several of our other projects are for programmer users, so it is also a general trend to migrate to md. We chose [CodeMirror](https://github.com/codemirror/CodeMirror), which is an excellent editor, it provides a rich programming interface for developers, and is also compatible with various browsers. it is good.

Later, as the business needs of our projects have precipitated, using CodeMirror sometimes feels more "cumbersome." For example, to implement @automatically complete the user name list, insert Emoji, upload files, etc., it requires more in-depth secondary development, and these business requirements are precisely common and necessary in many project scenarios.

Finally, we decided to start implementing the editor ourselves in Sym. With the iterations of several versions, Sym's editor has matured. In the community [LianDi](https://ld246.com) that we operate, people have asked us if we can separate the editor for everyone to use. At the same time, our front-end main programmer [V](https://ld246.com/member/Vanessa) also felt a little bit overwhelmed with maintaining the editors scattered in various projects, plus a good impression of TypeScript, so I decided Use ts to implement a new browser-side md editor.

So, Vditor was born.

## 2. Official Technical Reference & Guides (Vanessa219/docs)

## File: README.md

# TenDB Cluster
TenDB Cluster is a MySQL distributed relational database solution developed and maintained by Tencent Game CROS DBA team. It consists of three core components: TSpider, TenDB and Tdbctl.   

TSpider is the proxy layer of TenDB Cluster. It is a customized version developed by Tencent Game CROS DBA based on MariaDB 10.3.7. It mainly improves and customizes the distributed MySQL storage engine of Spider; TSpider [github page](https://github.com/Tencent/TenDBCluster-TSpider)

TenDB is the data layer of TenDB Cluster. It is a customized MySQL branch developed by Tencent Game CROS DBA based on Percona Server 5.7.20. It provides some great features internally for business operations; TenDB [github page](https://github.com/Tencent/TenDBCluster-TenDB)

Tdbctl is the control layer of TenDB Cluster. It is developed by Tencent Game CROS DBA team based on TenDB (mainly reuse TenDB's own SQL parsing capabilities and MGR capabilities). It provides cluster routing management, cluster DDL operating, cluster monitoring and some other cluster capabilities. Tdbctl [github page](https://github.com/Tencent/TenDBCluster-Tdbctl)

## Introduction to TenDB Cluster
TenDB Cluster is a MySQL distributed relational database solution developed and maintained by Tencent Game DBA team. The main features include: transparent database/table sharding, high-availability, online scaling. With these features, developers can focus on the development and operation of their product without caring about data sharding logic. Also, in the case of massive concurrency, there is no need to care about the load pressure of the DB storage layer.

## Quick Start
For more detail, see Chapter: [Quick Deployment With Docker Compose](Documentation/op-guide/docker-compose-tendbcluster-en.md)


## [Documentation](http://tendbcluster.com/)
For details, see [Reference Manual](http://tendbcluster.com/)

## Flexible Deployment
It is recommended to use TSpider + TenDB + Tdbctl to deploy TenDB Cluster.   
However, developers can also flexibly deploy TenDB Cluster according to their own online situation. Currently, companies use TSpider + Tencent Cloud RDS or TSpider + AWS RDS to deploy TenDB Cluster clusters.

## Contact us
For any questions or discussions about TenDB Cluster, please feel free to give us feedback via issues:     
TSpider:  https://github.com/Tencent/TenDBCluster-TSpider/issues   
TenDB:    https://github.com/Tencent/TenDBCluster-TenDB/issues   
Tdbctl:   https://github.com/Tencent/TenDBCluster-Tdbctl/issues 


## Roadmap
For details, see [chapter Roadmap](Documentation/roadmap-en.md)


## License
TenDBCluster is licensed under the GNU General Public License Version 2, except for the third-party components listed below. Copyright and license information can be found in the file TenDBCluster-License.


## Acknowledgements
Thanks to MariaDB and Percona for their outstanding contributions to the MySQL open source ecosystem, it was on the shoulders of the predecessors that TenDB Cluster was born.    
In addition, special thanks to the outstanding developers, contributors. For details, see [chapter acknowledgements](Documentation/acknowledgements-en.md)

---

## File: Documentation/com-problem/multi-unique-key-adjust.md

# 多唯一键调整
基于TSpider分区键[选取规则](../re-book/ddl-syntax.md#shard_key)，如果建表是出现多唯一键，则需要进行调整。本文提供一些多唯一键调整经验。

## 同时存在主键与唯一键
若表结构中包含有两个及以上的唯一键（主键是唯一键的一种），且无共同组成部分。要求只保留一个唯一键，其它唯一键变更为普通索引，应用层来保证其它字段的唯一性  


原表：
```
MariaDB [tendb_test]> CREATE TABLE `guild_user` 
    -> (  
    ->   `GID` bigint(20) NOT NULL DEFAULT '0',  
    ->   `UserId` bigint(20) NOT NULL DEFAULT '0',  
    ->    `GuildJob` tinyint(1) unsigned NOT NULL DEFAULT '0',  
    ->   `Contribution` int(10) unsigned DEFAULT '0',  
    ->   `Contribution_Total` int(10) unsigned DEFAULT '0', 
    ->   PRIMARY KEY `userid_index` (`UserId`),  
    ->   UNIQUE KEY `GID_index` (`GID`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ERROR 4151 (HY000): Failed to execute ddl, Error code: 12021, Detail Error Messages: CREATE TABLE ERROR: ERROR: too more unique key with the different pre key
```


修改后：需要应用层来保证 GID的唯一性  
```
MariaDB [tendb_test]> CREATE TABLE `guild_user` 
    -> (  
    ->   `GID` bigint(20) NOT NULL DEFAULT '0',  
    ->   `UserId` bigint(20) NOT NULL DEFAULT '0',  
    ->    `GuildJob` tinyint(1) unsigned NOT NULL DEFAULT '0',  
    ->   `Contribution` int(10) unsigned DEFAULT '0',  
    ->   `Contribution_Total` int(10) unsigned DEFAULT '0', 
    ->   PRIMARY KEY `userid_index` (`UserId`),  
    ->  KEY `GID_index` (`GID`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.07 sec)
```





若auto_increment列为主键，而唯一键为其他字段，可以将auto_increment列修改为普通index。自增列作为普通Key, 也能保证唯一。

原表:
```
MariaDB [tendb_test]> CREATE TABLE `guild_list` 
    -> (  
    ->      `idx` bigint(20) unsigned NOT NULL AUTO_INCREMENT,  
    ->      `GID` bigint(20) NOT NULL DEFAULT '0',  
    ->      `GName` varchar(64) NOT NULL DEFAULT '',  
    ->      `CreateTime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',  
    ->       `Data` blob,  
    ->       PRIMARY  KEY (`idx`),  
    ->       UNIQUE KEY `GName_index` (`GName`)
    -> ) 
    -> ;
ERROR 4151 (HY000): Failed to execute ddl, Error code: 12021, Detail Error Messages: CREATE TABLE ERROR: ERROR: too more unique key with the different pre key
```
修改后，将自增列作为普通Key, 也能保证唯一。
```
MariaDB [tendb_test]> CREATE TABLE `guild_list` 
    -> (  
    ->      `idx` bigint(20) unsigned NOT NULL AUTO_INCREMENT,  
    ->      `GID` bigint(20) NOT NULL DEFAULT '0',  
    ->      `GName` varchar(64) NOT NULL DEFAULT '',  
    ->      `CreateTime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',  
    ->       `Data` blob,  
    ->       KEY (`idx`),  
    ->       PRIMARY KEY `GName_index` (`GName`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.04 sec)

```




注意   
>业务中不允许表的唯一键超过一个，除非各个唯一键有共同部分，且指定该共同部分为分区键

---

## File: Documentation/com-problem/multi-unique-key-adjust-en.md

# Multi-unique Key Adjustment
Based on the TSpider [Shard Key selection rules](../re-book/ddl-syntax-en.md#shard_key), if the TABLE has multiple unique keys, you need to do some adjustment. This article provides some experiences with multiple unique keys adjustments.

## More than One Unique Keys
If the TABLE has two or more unique keys (the primary key is a kind of unique key too), only one unique key is required to be kept, other unique keys should be changed to ordinary indexes, and the application layer should ensures the uniqueness of them  


Original TABLE:

```
MariaDB [tendb_test]> CREATE TABLE `guild_user` 
    -> (  
    ->   `GID` bigint(20) NOT NULL DEFAULT '0',  
    ->   `UserId` bigint(20) NOT NULL DEFAULT '0',  
    ->    `GuildJob` tinyint(1) unsigned NOT NULL DEFAULT '0',  
    ->   `Contribution` int(10) unsigned DEFAULT '0',  
    ->   `Contribution_Total` int(10) unsigned DEFAULT '0', 
    ->   PRIMARY KEY `userid_index` (`UserId`),  
    ->   UNIQUE KEY `GID_index` (`GID`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ERROR 4151 (HY000): Failed to execute ddl, Error code: 12021, Detail Error Messages: CREATE TABLE ERROR: ERROR: too more unique key with the different pre key
```


After Adjustment: 
The application layer is required to guarantee the uniqueness of GID  

```
MariaDB [tendb_test]> CREATE TABLE `guild_user` 
    -> (  
    ->   `GID` bigint(20) NOT NULL DEFAULT '0',  
    ->   `UserId` bigint(20) NOT NULL DEFAULT '0',  
    ->    `GuildJob` tinyint(1) unsigned NOT NULL DEFAULT '0',  
    ->   `Contribution` int(10) unsigned DEFAULT '0',  
    ->   `Contribution_Total` int(10) unsigned DEFAULT '0', 
    ->   PRIMARY KEY `userid_index` (`UserId`),  
    ->  KEY `GID_index` (`GID`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.07 sec)
```


If the auto_increment column is the primary key and the unique key is another column, you can modify the auto_increment column to an ordinary index. As an ordinary key, the auto-inc column will be guaranteed to be unique by TenDB Cluster.

Original TABLE:

```
MariaDB [tendb_test]> CREATE TABLE `guild_list` 
    -> (  
    ->      `idx` bigint(20) unsigned NOT NULL AUTO_INCREMENT,  
    ->      `GID` bigint(20) NOT NULL DEFAULT '0',  
    ->      `GName` varchar(64) NOT NULL DEFAULT '',  
    ->      `CreateTime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',  
    ->       `Data` blob,  
    ->       PRIMARY  KEY (`idx`),  
    ->       UNIQUE KEY `GName_index` (`GName`)
    -> ) 
    -> ;
ERROR 4151 (HY000): Failed to execute ddl, Error code: 12021, Detail Error Messages: CREATE TABLE ERROR: ERROR: too more unique key with the different pre key
```

After Adjustment, the auto-inc column `idx` will be guaranteed to be unique by TenDB Cluster.

```
MariaDB [tendb_test]> CREATE TABLE `guild_list` 
    -> (  
    ->      `idx` bigint(20) unsigned NOT NULL AUTO_INCREMENT,  
    ->      `GID` bigint(20) NOT NULL DEFAULT '0',  
    ->      `GName` varchar(64) NOT NULL DEFAULT '',  
    ->      `CreateTime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',  
    ->       `Data` blob,  
    ->       KEY (`idx`),  
    ->       PRIMARY KEY `GName_index` (`GName`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.04 sec)

```



## Note   
TenDB Cluster do not allow more than one unique keys in a TABLE, unless each unique key has the same common part, and the common part is designated as the Shard Key

---

## File: Documentation/com-problem/problem-analysis.md

# FAQ

下面对TenDB Cluster集群使用中，用户的常见问题进行说明。

## 1. 常见问题

### 1.1 ERROR 12701
> ERROR 12701 (HY000): Remote MySQL server has gone away

12701错误有些也会伴随有错误1159，因网络导致的异常。   
接入层TSpider通过连接池与和存储层交互； 在存储实例单方便把连接断开时接入层TSpider没有感知到这个信息； 当前新请求过来TSpider使用到该异常的连接时就会报12701的错误。以下几种方式可以TSpider侧的异常连接快速释放：
1. 设置spider_idle_conn_recycle_interval=8（最小值），则8秒后所有空闲连接都会释放
2. 在TSpider上重复发送请求，新的请求即可正常执行。（旧的连接因为执行出错会被回收）
3. 重启TSpider节点，则一次性释放所有连接

### 1.2 ERROR 12723
> ERROR 12723 (HY000): Too many connections between spider and remote

接入层TSpider通过连接池与存储实例交互，TSpider和每个存储实例最多同时维护spider_max_connections个数的连接，在应用层瞬间新请求过多会造成spider_max_connections不够用的情况。  因此可通过如下方法解决此类问题：
1. 适当调大spider_max_connections到能够满足要求
2. 针对应用请求进行优化，如果SQL执行够快，那么占用的连接资源就会更少

### 1.3 ERROR 1477
> ERROR 1477 (HY000): The foreign server name you are trying to reference does not exist. Data source error:  SPT0

建表使用的server(SPT0)并不存在，可以检查mysql.servers表是否真的不存在； 如果mysql.servers表中存在SPT0却还报错，说明该server没有执行flush privileges生效

### 1.4 ERROR 1429
> MySQL error code 1429 (ER_CONNECT_TO_FOREIGN_DATA_SOURCE): Unable to connect to foreign data source: %.64s

某个后端存储实例故障，导致对应的server不可用；需要进行主备切换或者修复故障实例

### 1.5 Out of sync错误
非预期的TSpider向存储实例请求数据时出现异常，设置spider_idle_conn_recycle_interval为8（快速释放掉空闲连接），执行flush tables可解决。


### 1.6 自增列
TSpider只保证自增列的唯一性， 有空洞、且不保证自增字段的有序；更多信息参见[自增列](../re-book/auto-increase.md)章节

### 1.7 SQL请求慢
1. 可以打开general_log、 spider_general_log，查看当前请求的分发请求是否符合预期；分发到存储实例执行的请求是否命中索引   
2. 可以设置spider_bgs_mode=1让读操作并行
3. 对涉及多个shard的update/delete/insert操作，在打开spider_bgs_mode前提下打开spider_bgs_dml，让update/delete/insert操作并行
4. 关注存储实例和TSpider间的网络延迟   

### 1.8 OOM
`TSpider OOM`   
用户一次拉取的数据量过大； 尽量避免在TSpider一次性拉取过量数据，非要这样用避免OOM的办法是设置spider_quick_mode=3   
`TenDB OOM`   
通常是buffer pool配置过大，且瞬间连接过多导致；   解决方法是调低buffer pool，使用连接池处理连接问题； TSpider侧也可以调下spider_max_connections；

### 1.9 多唯一键错误
TSpider的唯一键约束规则和MySQL分区表规则相同，多唯一键问题详见[多唯一键调整](multi-unique-key-adjust.md)章节


### 1.10 调整shard key
调整shard key详见 [shard key选取](shard-key-choose.md)章节

### 1.11 TSpider出入流量相差大
一般是TSpider的入流量比出流量大，导致这个原因是部分请求需要拉取大量数据到TSpider层进行计算，TSpider只返回给应用计算后的结果； 比如全表排序取最大的记录，会就导致入流量大于出流量。

### 1.12 存储实例负载不均
考虑shard_key是否配置合理，导致数据及请求分布不均

### 1.13 查询超时
执行复杂查询，可能返回结果超时，一般是因为spider_net_read_timeout/spider_net_write_timeout配置过小

### 1.14 统计信息
通过全局参数spider_get_sts_or_crd开启统计信息，统计信息存储在系统表`mysql`.`spider_table_status`中；   `spider_modify_status_interval`为刷新统计信息的间隔时间。



## 2. 与MySQL的差异性
详见章节[与MySQL的差异](../re-book/mysql-compatibility.md/#jump)
## 3. 架构
详见章节[架构](../architecture.md)
## 4. 事务
详见章节[事务](../re-book/transaction.md)
## 5. SQL语法
详见章节[SQL语法](../re-book/sql-grammar.md)

---

## File: Documentation/com-problem/problem-analysis-en.md

# TenDB Cluster Common Problem(FAQ)

The following describes common problems of using TenDB cluster.

## 1. Client Error Code
###  1.1 ERROR 12701
> ERROR 12701 (HY000): Remote MySQL server has gone away
	 
Usually, Error Code 12701 come with Error Code 1159, it maybe caused by Network partition error.
TSpider Node use connection pool to interactive with all the TenDB Node, some network partition error may cause TenDB Node unilateral close the connection from the TSpider Node. When the TSpider Node accept new query from the client, it will reuse the problematic connection so it will return Error Code 12701 to the client. that is three ways to quickly release the problematic connection:

a. Set the global variables on TSpider Nodes, spider_idle_conn_recycle_interval = 8 which is the minimum value, it means that after 8 seconds all the idled connections will be released by Spider Node.   
b. The Client resend the query to TSpider Node, and the problematic connection will be released immediately and reconnect to TenDB Node again.   
c. Restart TSpider Node, so all the connection will be Re-established.
	  
### 1.2 ERROR 12723
> ERROR 12723 (HY000): Too many connections between TSpider Node and TenDB Node
	
TSpider Node use connection pool to interactive with all the TenDB Node, each TSpider Node will establish spider_max_connections connections with each TenDB Node, when 
a large number of new connections flooded in an instant to TSpider Node, it may encounter no enough connnection to use and so Client will receive Error Code 12723. That is two ways to solve this problem:  

a. set a bigger `spider_max_connections`.

b. optimizate the SQL, when the SQL execute more quickly, it will occupy the connection less time.

### 1.3 ERROR 1477

> ERROR 1477 (HY000): The foreign server name you are trying to reference does not exist. Data source error: SPT0
	
We store the TenDB Instances info and partition info in mysql.servers in TSpider Node , when you receive this Error Code, It seems that the configuration table mysql.servers not exists or you have the table but when you create the TenDB Cluster but you forget to execute FLUSH PRIVILEGES to make the router table available.

### 1.4 ERROR 1429

> MySQL error code 1429 (ER_CONNECT_TO_FOREIGN_DATA_SOURCE): Unable to connect to foreign data source: %.64s
	
When you receive this Error Code, it means that some TenDB master is unavailable, you need to switch to Slave or fix the problem on master quickly.

## 2. Other problem

### 2.1 Out of Sync Error

Unexpected exception occurs when TSpider Node query to the TenDB Node, execute "set global spider_idle_conn_recycle_interval=8"(free idle conn quickly)  and "FLUSH TABLES" on TSpider Node may resolve it

### 2.2 Auto-Increment

TenDB Cluster will ensure the Auto-Increment Column to be unique in the cluster scope, but TenDB Cluster cannot ensure the Auto-Inc column increase 1 by 1, It also will miss some value and the smaller value will be generated later.

for example, follow the timeline, the generated auto-inc ID will be like this: 

      2 6 10 3 7 11 1 5 9 etc.
      
You should know this detail and make sure your program should not relie the Auto-Inc feature.     
For details see [chapter Autoincrement](../re-book/auto-increase-en.md)


### 2.3 Analyse the SlOW QUERY
There is some steps to solve the TenDB Cluster SLOW QUERY:

1. Set global general_log = on and set global spider_general_log = on, and use pt-query-digest to analyse the SQL.
2. Set global spider_bgs_mode=1, which TSpider Node will make read operation executed in parallel on all the TenDB Nodes.
3. When update/delete/insert SQL involve seraval TenDB Nodes, you can `set global spider_bgs_mode=1` and `set global spider_bgs_dml = 1`, which TSpider Node will make  update/delete/insert SQL executed in parallel on all the involved TenDB Nodes
4. Check the network latancy between TSpider Node and TenDB Node, you can ping from TSpider Node and TenDB Node to check the ttl time between the two Nodes.

### 2.4 OOM Problem

#### TSpider Node OOM
This will happen when Client fetch large data from TSpider Node, also you can Set spider_quick_mode=3 to avoid this problem, bug we suggest you don't use TSpider in this way.

#### TenDB Node OOM
This will happen when you config the larger innodb_buffer_pool_size on TenDB Node, and a large number of new connections flooded in an instant, you can lower down the buffer pool size and use connection pool between client and TSpider Node.

### 2.5 Network Flow_In and Flow_Out In TSpider Node is much different
Commonly, network Flow In on TSpider Node will be larger than Flow Out, because TSpider Node need to fetch more data from TenDB Node and calculate the result and return to Client. for example, TSpider Node may execute `SELECT max(COLUMN) from TBALE`.

### 2.6 TenDB Nodes may have different work load
It may happen that you use the unsuitable Shard Key, so it result that different TenDB Node will have different data and request.

### 2.7 Query TimeOut
When you execute complicated QUERY, it will result in query TimeOut, you can set global spider_net_read_timeout/spider_net_write_timeout to avoid this TimeOut problem.

### 2.8 Statistics Information
Set global spider_get_sts_or_crd = on on TSpider Node to collect table statistics information from TenDB Nodes, and TSpider Node will store these information into mysql.spider_table_status table. Because this operation will produce some work load and you can use spider_modify_status_interval variable to control the interval time.

---

## File: Documentation/com-problem/shard-key-choose.md

# Shard Key选取
TSpider会按照表分区键来进行数据分片,分区键的选择尤为重要。本文将提供分区键选取的规则，以及建议。

## Shard Key选取规则
选取规则如下：
*   如果只有一个唯一键（含主键),不指定shard_key, 默认会用唯一键的第一个字段作为分区key；
* 如果多个唯一健（含主键),shard_key只能是其中的共同部分；否则无法建表；   
* 如果只有一个普通索引,不指定shard_key, 默认会用索引的第一个字段作为分区key；  
* 如果包含多个普通索引，则必须指定shard_key
* 如果指定shard_key，shard_key必须是索引的一部分；

其中普通索引和shard_key关系的约束，是通过中控节点Tdbctl建表时的限制。 在不依赖中控节点，直接在TSpider上建表时则不会被约束。 但通常更建议开发者基于索引来访问存储实例。
  
选取规则更详细内容可以参考[TenDB Cluster DDL](../re-book/ddl-syntax.md)


## 选取建议
*   分片的字段，最好是作为经常查询的条件，这样可以提高查询效率；  
*   使用key作为shard_key，数据分布应该均匀，避免数据集中在一个分片上;  


## Note
1. 可通过打开general log来分析应用的各个Table的请求模式，来进行Shard Key的调整，尽量减小跨分片扫描。
2. 另外也可以设置log_sql_use_mutil_partition为true将跨分区扫描的SQL打印到慢查询日志中进行分析。
3. 可以通过查看QUERY_RESPONSE_TIME table来展示TSpider上查询和跨分片扫描的信息。
    ```
     mysql> show QUERY_RESPONSE_TIME;
     mysql> SELECT * FROM information_schema.QUERY_RESPONSE_TIME;
    ```

---

## File: Documentation/com-problem/shard-key-choose-en.md

## Choose The Right Shard Key For TABLE
TSpider Node use the Shard Key to distributed the data to every TenDB Node, It's very important to choose the right Shard Key, you can follow some rules and suggestions here.

## The Rules of Choosing Shard Key
+ If the TABLE just has one unique key, and you don't specify the Shard Key, TSpider Node will use the first column of in unique key as Shard Key.
+ If the TABLE has several unique keys, the specified Shard Key should be the common column of these unique keys, otherwise you can not create table.
+ If the TABLE has multiple unique keys, without specify Shard Key, the first column of the unique key will be used as the Shard Key by default. But it is necessary to ensure that the Shard Key is the first column of each unique key, otherwise the table can not be created.
+ If the TABLE just has one key/index, and you don't specify the Shard Key, TSpider Node will use the first column of the key/index as Shard Key.
+ If the TABLE has several keys/indexes, you must specify one Shard Key.
+ If you specify the Shard Key, it should be part of key/Index of the TABLE.

Among them, the constraints of the relationship between the ordinary index and the shard_key are the limitations when creating tables through the  node Tdbctl. Without relying on the central control node, it will not be constrained when building tables directly on TSpider.  However, it is generally recommended that developers access storage instances based on indexes.

More Rules of choosing Shard Key can founded at [TenDB Cluster DDL](../re-book/ddl-syntax-en.md) 

## The Suggestions of Choosing Shard Key
+  The Shard Key column is best used as conditions for frequent queries, which can improve query efficiency.
+  Use the Column as the Shard Key, the data distribution should be average, to avoid the data concentrated on one or partial Shards.

## Note
1. You can open the general log to analyze the request mode of each TABLE to adjust the Shard Key to minimize Cross-Shard scanning.
2. In addition, you can also set log_sql_use_mutil_partition to true to print the SQL which scans across multiple Shards to the slow query log for analysis.
3. You can find Cross-Shard scanning information on TSpider Node by viewing the QUERY_RESPONSE_TIME table.

	```
	mysql>show QUERY_RESPONSE_TIME; 
	mysql>SELECT * FROM information_schema.QUERY_RESPONSE_TIME;
	```

---

## File: Documentation/contribution/concat.md

# 联系我们

您可以通过仓库提交历史，找到我们的社区成员：   
TSpider:  https://github.com/Tencent/TenDBCluster-TSpider/issues   
TenDB:    https://github.com/Tencent/TenDBCluster-TenDB/issues   
Tdbctl:   https://github.com/Tencent/TenDBCluster-Tdbctl/issues

---

## File: Documentation/contribution/concat-en.md

# Contact Us


You can find our community members through commit history logs of github repository:
   
TSpider:  https://github.com/Tencent/TenDBCluster-TSpider   
TenDB:    https://github.com/Tencent/TenDBCluster-TenDB  
Tdbctl:   https://github.com/Tencent/TenDBCluster-Tdbctl

---

## File: Documentation/contribution/contribute.md

## 源码贡献

欢迎成为TenDB Cluster的贡献者， 一旦您提交给 TSpider/TenDB/Tdbctl项目的PR (pull request) 被合并，你就成为TenDB Cluster的贡献者。
TenDB Cluster的三个核心组件的githu地址分别如下：   
TSpider: https://github.com/TencentDBA/TendbCluster3   
TenDB: https://github.com/TencentDBA/TenDB3   
Tdbctl: https://github.com/TencentDBA/Tdbctl


<a id="jump2"></a>

## 问题反馈

对TenDB Cluster的任何问题或者意见，欢迎通过提issue的方式反馈：   
TSpider:  https://github.com/TencentDBA/TendbCluster3/issues   
TenDB:    https://github.com/TencentDBA/TenDB3/issues   
Tdbctl:   https://github.com/TencentDBA/Tdbctl/issues