易於使用的Markdown 編輯器,為適配不同的應用場景而生
English | Demo
Vditor 是一款瀏覽器端的Markdown 編輯器,支持所見即所得、即時渲染(類似Typora)和分屏預覽模式。它使用TypeScript 實現,支持原生JavaScript 以及Vue、React、Angular 和Svelte 等框架。
歡迎到Vditor 官方討論區了解更多。同時也歡迎關注B3log 開源社區微信公眾號B3log开源:
隨著Markdown 排版方式的普及,越來越多的應用開始集成Markdown 編輯器。目前主流可集成的Markdown 編輯器現狀如下:
而這三點恰好對應了三種應用場景:
所以,一個能夠適配應用場景的Markdown 編輯器至關重要,它需要考慮到:
Vditor 在這些方面做了努力,希望能為現代化的通用Markdown 編輯領域做出一些貢獻。
所見即所得模式對不熟悉Markdown 的用戶較為友好,熟悉Markdown 的話也可以無縫使用。
即時渲染模式對熟悉Typora 的用戶應該不會感到陌生,理論上這是最優雅的Markdown 編輯方式。
傳統的分屏預覽模式適合大屏下的Markdown 編輯。
以上大部分特性可以通過開關配置是否啟用,開發者可根據自己的應用場景選擇搭配。
npm install vditor --save import Vditor from 'vditor'
import "~vditor/src/assets/less/index"
const vditor = new Vditor ( id , { options... } ) <!-- ️生产环境请指定版本号,如 https://unpkg.com/[email protected]/dist... -->
< link rel =" stylesheet " href =" https://unpkg.com/vditor/dist/index.css " />
< script src =" https://unpkg.com/vditor/dist/index.min.js " > </ script >編輯器所展現的外觀。內置classic,dark 2 套主題。
options.theme設置內置主題setTheme更新編輯器主題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更新代碼主題可填入元素id或元素自身HTMLElement
HTMLElement時需設置options.cache.id或將options.cache.enable設置為false
| 說明 | 預設值 | |
|---|---|---|
| i18n | 多語言,參見ITips | - |
| undoDelay | 歷史記錄間隔 | - |
| after | 編輯器異步渲染完成後的回調方法 | - |
| height | 編輯器總高度 | 'auto' |
| minHeight | 編輯區域最小高度 | - |
| width | 編輯器總寬度,支持% | 'auto' |
| placeholder | 輸入區域為空時的提示 | '' |
| lang | 語言種類:en_US, fr_FR, pt_BR, ja_JP, ko_KR, ru_RU, sv_SE, zh_CN, zh_TW | 'zh_CN' |
| input(value: string) | 輸入後觸發 | - |
| focus(value: string) | 聚焦後觸發 | - |
| blur(value: string) | 失焦後觸發 | - |
| keydown(event: KeyboardEvent) | 按下後觸發 | - |
| esc(value: string) | esc按下後觸發 | - |
| ctrlEnter(value: string) | ⌘/ctrl+enter按下後觸發 | - |
| select(value: string) | 編輯器中選中文字後觸發 | - |
| tab | tab鍵操作字符串,支持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}[] | 自定義渲染器 | [] |
toolbar: ['emoji', 'br', 'bold', '|', 'line'] 。默認值參見src/ts/util/Options.tsemoji , 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 , brname不在枚舉中時,可以添加自定義按鈕,格式如下: 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"><path d="M506.6 423.6m-29.8 0a29.8 29.8 0 1 0 59.6 0 29.8 29.8 0 1 0-59.6 0Z" fill="#0F0F0F" p-id="2809"></path><path d="M717.8 114.5c-83.5 0-158.4 65.4-211.2 122-52.7-56.6-127.7-122-211.2-122-159.5 0-273.9 129.3-273.9 288.9C21.5 562.9 429.3 913 506.6 913s485.1-350.1 485.1-509.7c0.1-159.5-114.4-288.8-273.9-288.8z" fill="#FAFCFB" p-id="2810"></path><path d="M506.6 926c-22 0-61-20.1-116-59.6-51.5-37-109.9-86.4-164.6-139-65.4-63-217.5-220.6-217.5-324 0-81.4 28.6-157.1 80.6-213.1 53.2-57.2 126.4-88.8 206.3-88.8 40 0 81.8 14.1 124.2 41.9 28.1 18.4 56.6 42.8 86.9 74.2 30.3-31.5 58.9-55.8 86.9-74.2 42.5-27.8 84.3-41.9 124.2-41.9 79.9 0 153.2 31.5 206.3 88.8 52 56 80.6 131.7 80.6 213.1 0 103.4-152.1 261-217.5 324-54.6 52.6-113.1 102-164.6 139-54.8 39.5-93.8 59.6-115.8 59.6zM295.4 127.5c-72.6 0-139.1 28.6-187.3 80.4-47.5 51.2-73.7 120.6-73.7 195.4 0 64.8 78.3 178.9 209.6 305.3 53.8 51.8 111.2 100.3 161.7 136.6 56.1 40.4 88.9 54.8 100.9 54.8s44.7-14.4 100.9-54.8c50.5-36.3 108-84.9 161.7-136.6 131.2-126.4 209.6-240.5 209.6-305.3 0-74.9-26.2-144.2-73.7-195.4-48.2-51.9-114.7-80.4-187.3-80.4-61.8 0-127.8 38.5-201.7 117.9-2.5 2.6-5.9 4.1-9.5 4.1s-7.1-1.5-9.5-4.1C423.2 166 357.2 127.5 295.4 127.5z" fill="#141414" p-id="2811"></path><path d="M353.9 415.6m-33.8 0a33.8 33.8 0 1 0 67.6 0 33.8 33.8 0 1 0-67.6 0Z" fill="#0F0F0F" p-id="2812"></path><path d="M659.3 415.6m-33.8 0a33.8 33.8 0 1 0 67.6 0 33.8 33.8 0 1 0-67.6 0Z" fill="#0F0F0F" p-id="2813"></path><path d="M411.6 538.5c0 52.3 42.8 95 95 95 52.3 0 95-42.8 95-95v-31.7h-190v31.7z" fill="#5B5143" p-id="2814"></path><path d="M506.6 646.5c-59.6 0-108-48.5-108-108v-31.7c0-7.2 5.8-13 13-13h190.1c7.2 0 13 5.8 13 13v31.7c0 59.5-48.5 108-108.1 108z m-82-126.7v18.7c0 45.2 36.8 82 82 82s82-36.8 82-82v-18.7h-164z" fill="#141414" p-id="2815"></path><path d="M450.4 578.9a54.7 27.5 0 1 0 109.4 0 54.7 27.5 0 1 0-109.4 0Z" fill="#EA64F9" p-id="2816"></path><path d="M256 502.7a32.1 27.5 0 1 0 64.2 0 32.1 27.5 0 1 0-64.2 0Z" fill="#EFAFF9" p-id="2817"></path><path d="M703.3 502.7a32.1 27.5 0 1 0 64.2 0 32.1 27.5 0 1 0-64.2 0Z" fill="#EFAFF9" p-id="2818"></path></svg>' ,
click ( ) { alert ( '捐赠地址:https://ld246.com/sponsor' ) } ,
} ] ,
} )| 說明 | 預設值 | |
|---|---|---|
| name | 唯一標示 | - |
| icon | svg 圖標 | - |
| tip | 提示 | - |
| tipPosition | 提示位置:'n', 'ne', 'nw', 's', 'se', 'sw', 'w', 'e' | - |
| hotkey | 快捷鍵,格式為⇧⌘ / ⌘ / ⌥⌘ | - |
| suffix | 插入編輯器中的後綴 | - |
| prefix | 插入編輯器中的前綴 | - |
| click(event: Event, vditor: IVditor) | 自定義按鈕點擊時觸發的事件 | - |
| className | 樣式名 | '' |
| toolbar?: Array<options.toolbar> | 子菜單 | - |
| 說明 | 預設值 | |
|---|---|---|
| hide | 是否隱藏工具欄 | false |
| pin | 是否固定工具欄 | false |
| 說明 | 預設值 | |
|---|---|---|
| enable | 是否啟用計數器 | false |
| after(length: number, counter: options.counter): void | 字數統計回調 | - |
| max | 允許輸入的最大值 | - |
| type | 統計類型:'markdown', 'text' | 'markdown' |
| 說明 | 預設值 | |
|---|---|---|
| enable | 是否使用localStorage 進行緩存 | true |
| id | 緩存key,第一個參數為元素且啟用緩存時必填 | - |
| after(html: string): string | 緩存後的回調 | - |
| 說明 | 預設值 | |
|---|---|---|
| enable | 是否啟用評論模式 | false |
| add(id: string, text: string, commentsData: ICommentsData[]) | 添加評論回調 | - |
| remove(ids: string[]) | 刪除評論回調 | - |
| scroll(top: number) | 滾動回調 | - |
| adjustTop(commentsData: ICommentsData[]) | 文檔修改時,適配評論高度 | - |
| 說明 | 預設值 | |
|---|---|---|
| delay | 預覽debounce 毫秒間隔 | 1000 |
| maxWidth | 預覽區域最大寬度 | 800 |
| mode | 顯示模式:both, editor | 'both' |
| url | md 解析請求 | - |
| parse(element: HTMLElement) | 預覽回調 | - |
| transform(html: string): string | 渲染之前回調 | - |
| 說明 | 預設值 | |
|---|---|---|
| defaultLang | 未指定語言時默認使用該語言 | '' |
| enable | 是否啟用代碼高亮 | true |
| style | 可選值參見Chroma | github |
| lineNumber | 是否啟用行號 | false |
| langs | 自定義指定語言 | CODE_LANGUAGES |
| renderMenu(code: HTMLElement, copy: HTMLElement) | 渲染菜單按鈕 | - |
| 說明 | 預設值 | |
|---|---|---|
| autoSpace | 自動空格 | false |
| 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 |
| 說明 | 預設值 | |
|---|---|---|
| current | 當前主題 | "light" |
| list | 可選主題列表 | { "ant-design": "Ant Design", dark: "Dark", light: "Light", wechat: "WeChat" } |
| path | 主題樣式地址 | https://unpkg.com/vditor@${VDITOR_VERSION}/dist/css/content-theme |
| 說明 | 預設值 | |
|---|---|---|
| inlineDigit | 內聯數學公式起始$ 後是否允許數字 | false |
| macros | 使用MathJax 渲染時傳入的宏定義 | {} |
| engine | 數學公式渲染引擎:KaTeX, MathJax | 'KaTeX' |
| mathJaxOptions | 數學公式渲染引擎為MathJax 時的參數 | - |
默認值為["desktop", "tablet", "mobile", "mp-wechat", "zhihu"]。 可從默認值中挑選進行配置,也可使用以下字段進行自定制開發。
| 說明 | 預設值 | |
|---|---|---|
| key | 按鈕唯一標識,不能為空 | - |
| text | 按鈕文字 | - |
| tooltip | 提示 | - |
| className | 按鈕類名 | - |
| click(key: string) | 按鈕點擊回調事件 | - |
| 說明 | 預設值 | |
|---|---|---|
| enable | 是否啟用多媒體渲染 | true |
| 說明 | 預設值 | |
|---|---|---|
| isPreview | 是否預覽圖片 | true |
| preview(bom: Element) => void | 圖片預覽處理 | - |
| 說明 | 預設值 | |
|---|---|---|
| isOpen | 是否打開鏈接地址 | true |
| click(bom: Element) => void | 點擊鏈接事件 | - |
| 說明 | 預設值 | |
|---|---|---|
| parse | 是否進行md 解析 | true |
| delay | 提示debounce 毫秒間隔 | 200 |
| emoji | 默認表情,可從lute/emoji_map 中選取,也可自定義 | { '+1': '?', '-1': '?', 'heart': '❤️', 'cold_sweat': '?' } |
| emojiTail | 常用表情提示 | - |
| emojiPath | 表情圖片地址 | https://unpkg.com/vditor@${VDITOR_VERSION}/dist/images/emoji |
| extend: IHintExtend[] | 對@/話題等關鍵字自動補全的擴展 | [] |
interface IHintData {
html : string ;
value : string ;
}
interface IHintExtend {
key : string ;
hint ? ( value : string ) : IHintData [ ] | Promise < IHintData [ ] > ;
} 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 : ''
}
}success , format , error不會同時觸發,具體調用情況如下: 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 ) ;
}
}| 說明 | 預設值 | |
|---|---|---|
| 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 | - |
| validate(files: File[]) => string | boolean | 校驗,成功時返回true 否則返回錯誤信息 | - |
| handler(files: File[]) => string | null | Promise | Promise | 自定義上傳,當發生錯誤時返回錯誤信息 | - |
| format(files: File[], responseText: string): string | 對服務端返回的數據進行轉換,以滿足內置的數據結構 | - |
| file(files: File[]): File[] | Promise<File[]> | 將上傳的文件處理後再返回 | - |
| setHeaders(): { [key: string]: string } | 上傳前使用返回值設置頭 | - |
| extraData: { [key: string]: string | Blob } | 為FormData 添加額外的參數 | - |
| multiple | 上傳文件是否為多個 | true |
| fieldName | 上傳字段名稱 | 'file[]' |
| renderLinkDest?(vditor: IVditor, node: ILuteNode, entering: boolean): [string, number] | 處理剪貼板中的圖片地址 | '' |
| 說明 | 預設值 | |
|---|---|---|
| enable | 是否支持大小拖拽 | false |
| position | 拖拽欄位置:'top', 'bottom' | 'bottom' |
| after(height: number) | 拖拽結束的回調 | - |
| 說明 | 預設值 | |
|---|---|---|
| preview | 預覽元素上的className | '' |
| 說明 | 預設值 | |
|---|---|---|
| index | 全屏層級 | 90 |
| 說明 | 預設值 | |
|---|---|---|
| enable | 初始化是否展現大綱 | false |
| position | 大綱位置:'left', 'right' | 'left' |
| 說明 | |
|---|---|
| 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}) | 更新工具欄配置 |
method.min.js後如下直接調用 Vditor . mermaidRender ( document ) import VditorPreview from 'vditor/dist/method.min'
VditorPreview . mermaidRender ( document )preview方法,參數如下: 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,該方法需使用異步編程 |
| preview(previewElement: HTMLDivElement, markdown: string, options?: IPreviewOptions) | 頁面Markdown 文章渲染 |
| highlightRender(hljsOption?: IHljs, element?: HTMLElement | Document, cdn = options.cdn) | 為element 中的代碼塊進行高亮渲染 |
| mediaRender(element: HTMLElement) | 為特定鏈接分別渲染為視頻、音頻、嵌入的iframe |
| mathRender(element: HTMLElement, options?: {cdn?: string, math?: IMath}) | 對數學公式進行渲染 |
| speechRender(element: HTMLElement, lang?: (keyof II18nLang)) | 對選中的文字進行閱讀 |
| graphvizRender(element: HTMLElement, cdn?: string) | 對graphviz 進行渲染 |
| 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 |
npm installnpm run start啟動本地服務器,打開http://localhost:9000npm run build打包代碼到dist 目錄由於使用了按需加載的機制,默認CDN 為https://unpkg.com/vditor@版本號
如果代碼有修改或需要使用自建CDN 的話,可按以下步驟進行操作:
options及IPreviewOptions中的cdn , emojiPath , themes進行配置highlightRender , mathRender , abcRender , chartRender , mermaidRender , SMILESRender , markmapRender , flowchartRender , mindmapRender , plantumlRender , graphvizRender , setCodeTheme , setContentTheme方法中需添加cdn 參數版本升級時請仔細閱讀CHANGELOG 中的升級部分
Vditor 使用MIT 開源協議。
我們在開發Sym 的初期是直接使用WYSIWYG 富文本編輯器的。那時候基於HTML 的編輯器非常流行,項目中引用起來也很方便,也符合用戶當時的使用習慣。
後來,Markdown 的崛起逐步改變了大家的排版方式。再加上我們其他幾個項目都是面向程序員用戶的,所以遷移到md 上也是大勢所趨。我們選擇了CodeMirror,這是一款優秀的編輯器,它對開發者提供了豐富的編程接口,對各種瀏覽器的兼容性也比較好。
再後來,隨著我們項目業務需求方面的沉澱,使用CodeMirror 有時候會感到比較“笨重”。比如要實現@自動完成用戶名列表、插入Emoji、上傳文件等就需要比較深入的二次開發,而這些業務需求恰恰是很多項目場景共有且必備的。
終於,我們決定開始在Sym 中自己實現編輯器。隨著幾個版本的迭代,Sym 的編輯器也日趨成熟。在我們運營的社區鏈滴上陸續有人問我們是否能將編輯器單獨抽離出來提供給大家使用。與此同時,我們的前端主程V 同學對於維護分散在各個項目中的編輯器也感到有點力不從心,外加對TypeScript 的好感,所以就決定使用ts 來實現一個全新的瀏覽器端md 編輯器。
於是,Vditor 就這樣誕生了。