Sym - 一个用 Java 实现的现代化社区平台 • 源码 • 注册

Pipe - 小而美的开源博客平台 • 体验 • 皮肤
Solo - 一个用 Java 实现的博客系统,为你或你的团队创建个博客吧! • 源码下载 
Wide - 一个基于 Web 的 Go 语言 IDE • 教程试用

Vditor - 浏览器端的 Markdown 编辑器

Vditor
下一代的 Markdown 编辑器,为未来而构建

npm bundle size


💡 简介

Vditor 是一款浏览器端的 Markdown 编辑器,使用 TypeScript 实现。支持原生 JavaScript、Vue、React、Angular。

📽️ 背景

我们在开发 Sym 的初期是直接使用 WYSIWYG 富文本编辑器的。那时候基于 HTML 的编辑器非常流行,项目中引用起来也很方便,也符合用户当时的使用习惯。

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

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

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

于是,Vditor 就这样诞生了。

✨ 特性

  • 所见即所得编辑模式
  • 支持任务列表、at、图表、流程图、甘特图、时序图、五线谱、多媒体、语音阅读、标题锚点渲染
  • 支持自定义 duplicate、delete 快捷键操作
  • 支持 Markdown 格式化, Markdown 语法树实时渲染
  • 表情自动补全,设置常用表情,支持表情自定义
  • 自定义工具栏按钮、提示、插入字符、快捷键,支持工具栏添加按钮
  • 可使用拖拽、剪切板粘贴上传,显示实时上传进度,支持 CORS 跨域上传
  • 实时保存内容,防止意外丢失
  • 录音支持,用户可直接发布语音
  • 粘贴 HTML 自动转换为 Markdown,如粘贴中包含外链图片可通过指定接口上传到服务器
  • 提供实时预览、滚动同步定位
  • 支持主窗口大小拖拽、字符计数
  • 多主题支持、内置黑白两套
  • 多语言支持、内置中英文
  • 支持主流浏览器和移动端

demo
render

🗃 案例

  • Sym:一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)平台
  • Starfire:一个分布式的内容分享讨论社区,通过点对点超媒体协议让社区更快、更安全也更开放
  • Solo:一款小而美的博客系统,使用 Java 实现
  • Pipe:一款小而美的博客平台,使用 Go 实现

🛠️ 使用文档

CommonJS

  • 安装依赖
npm install vditor --save
  • 在代码中引入并初始化对象,可参考 index.js
import Vditor from 'vditor'
import "~vditor/src/assets/scss/classic" // 或者使用 dark

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

HTML script

  • 在 HTML 中插入 CSS 和 js,可参考 static.html
<!-- ⚠️生产环境请指定版本号,如 https://cdn.jsdelivr.net/npm/vditor@x.x.x/dist... -->
<!-- 可使用 index.dark.css 或 index.classic.css -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vditor/dist/index.classic.css" />
<script src="https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js" defer></script>

示例代码

主题

  • 支持黑白两套主题:classic/dark
  • 参考现有样式后使用自己开发的 scss/css 进行样式的完全自定制
  • 可通过修改 classic.scss/dark.scss 中的变量对主题颜色进行定制
  • 在内容显示元素上添加 class="vditor-reset" 属性可对内容进行更为友好的展示

API

options

说明 默认值
after 编辑器异步渲染完成后的回调方法 undefined
cache 是否使用 localStorage 进行缓存 true
height 编辑器总高度 'auto'
width 编辑器总宽度,支持 % 'auto'
placeholder 输入区域为空时的提示 ''
lang 多语言:en_US, zh_CN 'zh_CN'
counter 计数器 0
input 输入后触发 (value: string, previewElement?: HTMLElement): void -
focus 聚焦后触发 (value: string): void -
blur 失焦后触发 (value: string): void -
esc %3Ckbd %3E esc %3C %2Fkbd %3E 按下后触发 (value: string): void -
ctrlEnter %3Ckbd %3E ⌘/ctrl+enter %3C %2Fkbd %3E 按下后触发 (value: string): void -
select 编辑器中选中文字后触发 (value: string): void -
tab tab 键操作字符串,支持 \t 及任意字符串 -
typewriterMode 是否启用打字机模式 false
cdn 配置自建 CDN 地址 https://cdn.jsdelivr.net/npm/vditor@${VDITOR_VERSION}
mode 可选模式:"wysiwyg-show", "markdown-show", "wysiwyg-only", "markdown-only 'wysiwyg-show'
debugger 是否显示日志 false

options.toolbar

  • 工具栏,可使用 name 进行简写: toolbar: ['emoji', 'br', 'bold', '|', 'line'] 。默认值参见 src/ts/util/Options.ts
  • name 可枚举为: emoji , headings , bold , italic , strike , | , line , quote , list , ordered-list , check , code , inline-code , undo , redo , upload , link , table , record , both , preview , format , fullscreen , devtools , info , help , br
  • name 不在枚举中时,可以添加自定义按钮,格式如下:
{  
 hotkey: '⌘-⇧-f',  
 name: 'format',  
 tipPosition: 'ne',  
 tip: 'format',  
 icon: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="768" height="768" viewBox="0 0 768 768"><path d="M342 426v-84h426v84h-426zM342 256v-86h426v86h-426zM0 0h768v86h-768v-86zM342 598v-86h426v86h-426zM0 214l170 170-170 170v-340zM0 768v-86h768v86h-768z"></path></svg>',  
 click: () => {  
   alert('custom toolbar')  
 },  
}
说明 默认值
name 唯一标示 -
icon svg 图标 -
tip 提示 -
tipPosition 提示位置:ne, nw -
hotkey 快捷键,支持 ⌘/ctrl-key 或 ⌘/ctrl-⇧/shif-key 格式的配置,不支持 wysiwyg 模式 -
suffix 插入编辑器中的后缀 -
prefix 插入编辑器中的前缀 -
click 自定义按钮点击时触发的事件 ():viod -

options.preview

说明 默认值
delay 预览 debounce 毫秒间隔 1000
mode 显示模式:'both', 'editor', 'preview' 'both'
url md 解析请求 -
inlineMathDigit 内联数学公式起始 $ 后是否允许数字 false
parse 预览回调 (element: HTMLElement): void -
transform 渲染之前回调(html: string): string -

options.preview.hljs

说明 默认值
enable 是否启用代码高亮 true
style 可选值参见 Chroma GitHub
lineNumber 是否启用行号 false

options.hint

说明 默认值
delay 提示 debounce 毫秒间隔 200
emoji 默认表情,可从 lute/emoji_map 中选取,也可自定义 { '+1': '👍', '-1': '👎', 'heart': '❤️ ', 'cold_sweat': '😰' }
emojiTail 常用表情提示 -
emojiPath 表情图片地址 https://cdn.jsdelivr.net/npm/vditor@${VDITOR_VERSION}/dist/images/emoji
at @用户回调,(value: string): Array ,需同步返回数组[{value: '', html: ''}] -

options.upload

  • 后端返回的数据结构不一致时,可使用 format 进行转换。文件上传的数据结构如下:
// 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 可将剪贴板中的站外图片地址传到服务器端进行保存处理,其数据结构如下:
// POST data  
xhr.send(JSON.stringify({url: src})); // src 为站外图片地址  
// return data  
{  
 msg: '',  
 code: 0,  
 data : {  
   originalURL: '',  
   url: ''  
 }  
}
说明 默认值
url 上传 url ''
max 上传文件最大 Byte 10 * 1024 * 1024
linkToImgUrl 剪切板中包含图片地址时,使用此 url 重新上传 ''
success 上传成功回调 (editor: HTMLPreElement, msg: string): void -
error 上传失败回调 (msg: string): void -
token CORS 上传验证,头为 X-Upload-Token -
withCredentials 跨站点访问控制 false
headers 请求头设置 -
filename 文件名安全处理 (name: string): string name => name.replace(/\W/g, '')
accept 文件上传类型,同 input accept -
validate 校验,成功时返回 true 否则返回错误信息 (files: File[]) => string boolean
handler 自定义上传,当发生错误时返回错误信息 (files: File[]) => string null
format 对服务端返回的数据进行转换,以满足内置的数据结构 (files: File[], responseText: string): string -

options.resize

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

options.classes

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

options.keymap

说明 默认值
deleteLine 删除光标所在行或选中的行 ⌘-Backspace
duplicate 复制当前行或选中的内容 ⌘-d

methods

说明
getValue() 获取编辑器内容
getHTML() 获取预览区内容。该方法需使用异步编程
insertValue(value: string) 在焦点处插入内容
focus() 聚焦到编辑器
blur() 让编辑器失焦
disabled() 禁用编辑器
enable() 解除编辑器禁用
setSelection(start: number, end: number) 选中从 start 开始到 end 结束的字符串,不支持 wysiwyg 模式
getSelection():string 返回选中的字符串
setValue(markdown: string) 设置编辑器内容
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: string) 设置预览模式。mode: 'both', 'editor', 'preview'

static methods

  • 不需要进行编辑操作时,仅需引入 method.min.js 后如下直接调用
Vditor.mermaidRender(document)
import VditorPreview from 'vditor/dist/method.min'  
VditorPreview.mermaidRender(document)
  • 需要对页面中的 Markdown 进行渲染时可直接调用 preview 方法,参数如下:
previewElement: HTMLDivElement,   // 使用该元素进行渲染
markdown: string,  // 需要渲染的 markdown 原文
options?: IPreviewOptions {  
 anchor?: boolean;  // 为标题添加锚点
 className?: string;  // 渲染元素的 class,默认为 'vditor-reset'
 customEmoji?: { [key: string]: string };    // 自定义 emoji,默认为 {}  
 lang?: (keyof II18nLang);    // 语言,默认为 'zh_CN'  
 emojiPath?: string;    // 表情图片路径 
 hljs?: IHljs // 参见 options.preview.hljs 
 speech?: {  // 对选中后的内容进行阅读
  enable?: boolean
 }
 inlineMathDigit?: boolean // 内联数学公式起始 $ 后是否允许数字
 transform?(html: string): string // 在渲染前进行的回调方法
 cdn?: string // 自建 CDN 地址
}
  • ⚠️ method.min.jsindex.min.js 不可同时引入
说明
mathRender(element: HTMLElement, cdn = options.cdn) 转换 element 中的文本为数学公式
mermaidRender(element: HTMLElement, className = ".language-mermaid", cdn = options.cdn) 转换 element 中 class 为 className 的元素为流程图/时序图/甘特图
codeRender(element: HTMLElement, lang: (keyof II18nLang) = "zh_CN") 为 element 中的代码块添加复制按钮
chartRender(element: (HTMLElement Document) = document, cdn = options.cdn)
abcRender(element: (HTMLElement Document) = document, cdn = options.cdn)
md2html(mdText: string, options?: IPreviewOptions): string Markdown 文本转换为 HTML,该方法需使用异步编程
preview(previewElement: HTMLDivElement, markdown: string, options?: IPreviewOptions) 页面 Markdown 文章渲染
highlightRender(hljsOption?:IHljs, element?: HTMLElement Document, cdn = options.cdn)
mediaRender(element: HTMLElement) 特定链接分别渲染为视频、音频、嵌入的 iframe
mathRenderByLute(element: HTMLElement, cdn = options.cdn) 对使用 Lute 渲染的数学公式进行渲染
speechRender(element: HTMLElement, lang?: (keyof II18nLang)) 对选中的文字进行阅读

升级

版本升级时请仔细阅读 CHANGELOG 中的升级部分

🏗 开发文档

环境

  1. 安装 node LTS 版本
  2. 下载 最新代码并解压
  3. 根目录运行 npm install
  4. npm run start 启动本地服务器,打开 http://localhost:9000
  5. 修改代码
  6. npm run build 打包代码到 dist 目录

CDN 切换

由于使用了按需加载的机制,默认 CDN 为 https://cdn.jsdelivr.net/npm/vditor@版本号

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

  • 初始化的 optionsIPreviewOptions 中需添加 cdn 配置
  • highlightRender , mathRenderByLute , mathRender , abcRender , chartRender , mermaidRender 方法中需添加 cdn 参数
  • 将 build 成功的 dist 目录或 jsDelivr 中的 dist 目录拷贝至正确的位置

🏘️ 社区

📄 授权

Vditor 使用 MIT 开源协议。

🙏 鸣谢

  • Lute:🎼 一款结构化的 Markdown 引擎,支持 Go 和 JavaScript
  • highlight.js:JavaScript syntax highlighter
  • Turndown:🛏 An HTML to Markdown converter written in JavaScript
  • mermaid:Generation of diagram and flowchart from text in a similar manner as Markdown
  • incubator-echarts:A powerful, interactive charting and visualization library for browser
  • abcjs:JavaScript library for rendering standard music notation in a browser

欢迎注册黑客派社区,开启你的博客之旅。让学习和分享成为一种习惯!

162 评论
547176052 • 2019-11-09
回复 删除

其他的基本搞定了就差 页面内跳转的锚点 有没什么解决的方案

547176052 • 2019-11-09
回复 删除

编辑器编辑 md 代码 >>>> 转为 HTML 并设置好需要的样式 >>>> 文章入库直接 html>>>> 显示文章只需要引入 CSS 就好 >>>> 编辑文章。把 HTML 转换为 md 代码然后编辑

好像目前不能实现这样的流程

547176052 • 2019-11-10
回复 删除
<td class="t_f" id="postmessage_345">
<hook>[ad thread/a_pr/3/0]</hook>## Guide<br>
<br>
这是一篇讲解如何正确使用 **Markdown** 的排版示例学会这个很有必要能让你的文章有更佳清晰的排版<br>
<br>
&gt; 引用文本Markdown is a text formatting syntax inspired<br>
<br>
## 语法指导<br>
<br>
### 普通内容<br>
<br>
这段内容展示了在内容里面一些排版格式比如<br>
<br>
- **加粗** - `**加粗**`<br>
- *倾斜* - `*倾斜*`<br>
- ~~删除线~~ - `~~删除线~~`<br>
- `Code 标记` - `` `Code 标记` ``<br>
- [超级链接](<a href="https://hacpai.com" target="_blank">https://hacpai.com</a>) - `[超级链接](<a href="https://hacpai.com" target="_blank">https://hacpai.com</a>)`<br>
- [<a href="mailto:username@gmail.com">username@gmail.com</a>](mailto:username@gmail.com) - `[<a href="mailto:username@gmail.com">username@gmail.com</a>](mailto:username@gmail.com)`<br>
<br>
### 提及用户<br>
<br>
@Vanessa 通过 `@User` 可以在内容中提及用户被提及的用户将会收到系统通知<br>
<br>
&gt; NOTE:<br>
&gt;<br>
&gt; 1. @用户名之后需要有一个空格<br>
&gt; 2. 新手没有艾特的功能权限<br>
<br>
### 表情符号 Emoji<br>
<br>
支持大部分标准的表情符号可使用输入法直接输入也可手动输入字符格式通过输入 `:` 触发自动完成可在个人设置中[设置常用表情](<a href="https://hacpai.com/settings/function" target="_blank">https://hacpai.com/settings/function</a>)。<br>
<br>
#### 一些表情例子<br>
<br>
:smile: :laughing: :dizzy_face: :sob: :cold_sweat: :sweat_smile:&nbsp;&nbsp;:cry: :triumph: :heart_eyes: :relieved:<br>
:+1: :-1: :100: :clap: :bell: :gift: :question: :bomb: :heart: :coffee: :cyclone: :bow: <img src="static/image/smiley/default/kiss.gif" smilieid="22" border="0" alt=""> :pray: :anger:<br>
<br>
### 大标题 - Heading 3<br>
<br>
你可以选择使用 H1  H6使用 ##(N) 打头建议帖子或回帖中的顶级标题使用 Heading 3不要使用 1  2因为 1 是系统站点级2 是帖子标题级<br>
<br>
&gt; NOTE: 别忘了 # 后面需要有空格<br>
<br>
#### Heading 4<br>
<br>
##### Heading 5<br>
<br>
###### Heading 6<br>
<br>
### 图片<br>
<br>
```<br>
![alt 文本](<a href="http://image-path.png" target="_blank">http://image-path.png</a>)<br>
![alt 文本](<a href="http://image-path.png" target="_blank">http://image-path.png</a> "图片 Title 值")<br>
```<br>
<br>
支持复制粘贴直接上传<br>
<br>
### 代码块<br>
<br>
#### 普通<br>
<br>
```<br>
*emphasize*&nbsp; &nbsp; **strong**<br>
_emphasize_&nbsp; &nbsp; __strong__<br>
var a = 1<br>
```<br>
<br>
#### 语法高亮支持<br>
<br>
如果在 ``` 后面跟随语言名称,可以有语法高亮的效果哦,比如:<br>
<br>
##### 演示 Go 代码高亮<br>
<br>
```go<br>
package main<br>
<br>
import "fmt"<br>
<br>
func main() {<br>
&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("Hello, 世界")<br>
}<br>
```<br>
<br>
##### 演示 Java 高亮<br>
<br>
```java<br>
public class HelloWorld {<br>
<br>
&nbsp; &nbsp; public static void main(String[] args) {<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;System.out.println("Hello World!");<br>
&nbsp; &nbsp; }<br>
<br>
}<br>
```<br>
<br>
&gt; Tip: 语言名称支持下面这些: `ruby`, `python`, `js`, `html`, `erb`, `css`, `coffee`, `bash`, `json`, `yml`, `xml` ...<br>
<br>
### 有序无序任务列表<br>
<br>
#### 无序列表<br>
<br>
- Java<br>
&nbsp;&nbsp;- Spring<br>
&nbsp; &nbsp; - IoC<br>
&nbsp; &nbsp; - AOP<br>
- Go<br>
&nbsp;&nbsp;- gofmt<br>
&nbsp;&nbsp;- Wide<br>
- Node.js<br>
&nbsp;&nbsp;- Koa<br>
&nbsp;&nbsp;- Express<br>
<br>
#### 有序列表<br>
<br>
1. Node.js<br>
&nbsp; &nbsp;1.1. Express<br>
&nbsp; &nbsp;1.2. Koa<br>
&nbsp; &nbsp;1.3. Sails<br>
2. Go<br>
&nbsp; &nbsp;2.1. gofmt<br>
&nbsp; &nbsp;2.2. Wide<br>
3. Java<br>
&nbsp; &nbsp;3.1. Latke<br>
&nbsp; &nbsp;3.2. IDEA<br>
<br>
#### 任务列表<br>
<br>
- [x] 发布 Sym<br>
- [X] 发布 Solo<br>
- [ ] 预约牙医<br>
<br>
### 表格<br>
<br>
如果需要展示数据什么的可以选择使用表格<br>
<br>
| header 1 | header 3 |<br>
| -------- | -------- |<br>
| cell 1&nbsp; &nbsp;| cell 2&nbsp; &nbsp;|<br>
| cell 3&nbsp; &nbsp;| cell 4&nbsp; &nbsp;|<br>
| cell 5&nbsp; &nbsp;| cell 6&nbsp; &nbsp;|<br>
<br>
### 隐藏细节<br>
<br>
&lt;details&gt;<br>
&lt;summary&gt;这里是摘要部分&lt;/summary&gt;<br>
这里是细节部分<br>
&lt;/details&gt;<br>
<br>
### 段落<br>
<br>
空行可以将内容进行分段便于阅读。(这是第一段<br>
<br>
使用空行在 Markdown 排版中相当重要。(这是第二段<br>
<br>
### 数学公式<br>
<br>
多行公式块<br>
<br>
$$<br>
\frac{1}{<br>
&nbsp;&nbsp;\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{<br>
&nbsp;&nbsp;\frac25 \pi}} = 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {<br>
&nbsp; &nbsp; 1+\frac{e^{-6\pi}}<br>
&nbsp; &nbsp; {1+\frac{e^{-8\pi}}{1+\cdots}}<br>
&nbsp;&nbsp;}<br>
}<br>
$$<br>
<br>
行内公式<br>
<br>



原文入库后的效果

547176052 • 2019-11-10
回复 删除

差不多
但是官方的文章目录会更好一点
image.png

haaid • 2019-11-11
回复 删除

能不能将 Lute 改为依赖形式,或者可以传递 Lute 地址链接,现在 192.168.0.107:9090 是写死的地址

haaid • 2019-11-11
回复 删除

发现了一个 bug,preview 在不传递 options 参数时报错 Cannot read property 'anchor' of undefined

547176052 • 2019-11-13
回复 删除

md 编辑器 文字不能设置颜色吗??

huaxie2017 • 2019-11-18
回复 删除

1574051573111111.jpg
图片上传成功了编辑器还是未显示 什么情况呢 编辑器内置上传的

huaxie2017 • 2019-11-18
回复 删除

1574051573111111 看图王.jpg
上传成功 编辑器内还是未显示 什么原因呢

huaxie2017 • 2019-11-20
回复 删除

21211.jpg
你好 请问下 我在页面调用静态方法 preview 图表 甘特图等不能渲染出来

huaxie2017 • 2019-11-20
回复 删除

21211.jpg
请问下 调用静态方法 preview 不能渲染出图表 甘特图等内容

huaxie2017 • 2019-11-20
回复 删除

image.png
你好 请问下 文本内的图标 甘特图不能渲染出来 调用的静态 preview

547176052 • 2019-11-21
回复 删除

如何准确的吧这个替换掉

![图片.png](上传中...1)
![图片.png](上传中...2)
haaid • 2019-12-09
回复 删除

我更新了最新的 1.10.7,发现了两个问题,「上传」「编辑 & 预览」「预览」三个按钮会触发 submit,不知道什么原因,另外,语法高亮当中如果存在 <textarea></textarea>,复制代码按钮错位

错误:
image.png
正确:
image.png

haaid • 2019-12-09
回复 删除

「录音」「全屏」「开发」三个按钮点击了以后,tooltips 在不点击其他区域时,不会消失