Vditor 一款浏览器端的 Markdown 编辑器,支持所见即所得(富文本)、即时渲染(类似 Typora)和分屏预览模式

本贴最后更新于 1118 天前,其中的信息可能已经沧海桑田

Vditor
易于使用的 Markdown 编辑器,为适配不同的应用场景而生

npm bundle size


English  |  Demo

💡 简介

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

欢迎到 Vditor 官方讨论区了解更多。同时也欢迎关注 B3log 开源社区微信公众号 B3log开源

b3logos.jpg

🗺️ 背景

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

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

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

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

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

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

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

✨ 特性

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

editor.png

preview.png

🔮 编辑模式

所见即所得(WYSIWYG)

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

vditor-wysiwyg

即时渲染(IR)

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

vditor-ir

分屏预览(SV)

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

vditor-sv

🍱 语法支持

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

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

🗃 案例

  • Sym 一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)平台
  • Solo & Pipe B3log 分布式社区的博客端节点,欢迎加入下一代社区网络
  • Tditor 基于 React、Vditor、Springboot, 一款打造极致文字创作体验的在线 Markdown 编辑平台
  • Arya 基于 Vue、Vditor,所构建的在线 Markdown 编辑器
  • 更多案例

🛠️ 使用文档

CommonJS

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

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

HTML script

  • 在 HTML 中插入 CSS 和 JavaScript,可参考 demo
<!-- ⚠️生产环境请指定版本号,如 https://unpkg.com/vditor@x.x.x/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 更新编辑器主题
  • 可通过修改 index.less 中的变量对主题颜色进行定制
  • 可参考现有结构和类名在原有基础上进行修改

内容主题

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

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

代码主题

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

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

API

id

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

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

options

说明 默认值
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'

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 ,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 不在枚举中时,可以添加自定义按钮,格式如下:
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> 子菜单 -

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 github
lineNumber 是否启用行号 false

options.preview.markdown

说明 默认值
autoSpace 自动空格 false
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

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'

options.preview.actions?: Array<IPreviewAction | IPreviewActionCustom>

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

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

options.image

说明 默认值
isPreview 是否预览图片 true
preview(bom: Element) => void 图片预览处理 -
说明 默认值
isOpen 是否打开链接地址 true
click(bom: Element) => void 点击链接事件 -

options.hint

说明 默认值
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[]>;
}

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: ''
 }
}
  • successformaterror 不会同时触发,具体调用情况如下:
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[]'

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[]) 删除评论

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 {
  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;
  renderers?: ILuteRender; // 自定义渲染 https://ld246.com/article/1588412297062
}
  • ⚠️ method.min.jsindex.min.js 不可同时引入
说明
previewImage(oldImgElement: HTMLImageElement, lang: keyof II18n = "zh_CN", theme = "classic") 点击图片预览
mermaidRender(element: HTMLElement, cdn = options.cdn, theme = options.theme) 流程图/时序图/甘特图
flowchartRender(element: HTMLElement, cdn = options.cdn) flowchart 渲染
codeRender(element: HTMLElement) 为 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

🏗 开发文档

原理相关

环境

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

CDN 切换

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

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

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

升级

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

Ⓜ️ Markdown 使用指南

🏘️ 社区

📄 授权

Vditor 使用 MIT 开源协议。

🙏 鸣谢

  • Lute:🎼 一款结构化的 Markdown 引擎,支持 Go 和 JavaScript
  • highlight.js:JavaScript syntax highlighter
  • 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

📽️ 历史

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

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

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

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

于是,Vditor 就这样诞生了。

相关帖子

优质回帖
  • Vanessa 1
    支持者 赞助者 订阅者 作者

    前端是这样传送的

        const formData = new FormData();
        for (let i = 0, iMax = uploadFileList.length; i < iMax; i++) {
            formData.append("file[]", uploadFileList[i]);
        }
         xhr.send(formData);
    
  • haaid 1

    文字错误:接触编辑器禁用,「接触」->「解除」

  • someone9891 1
    捐赠者

    此链接 404

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • 547176052

    我用的是管理员

  • 88250
    订阅者

    依赖库 node-sass 没安装好,试试把 node_modules 整个手动删了,然后再重新执行 npm i

    1 回复
  • buexplain

    image.png

    如上图所示,请问,我这个内容没有渲染是什么原因呢,编辑器图标异常又是要引入什么 css 呢?
    我的代码如下:

    内容容器:
    <div class="vditor-reset" id="j-content">{{.body}}</div>

    编辑器代码:

    //初始化编辑器
    const vEditor = new Vditor('j-content', {
        placeholder:'请输入文章内容',
        cache:false,
        counter: 65535,
        height: $(window).height() - 93,
        tab: '\t',
        upload: {
            linkToImgUrl: '/backend/article/content/upload',
            handler (file) {
                var formData = new FormData();
                formData.append('_token', document.querySelector("input[name=_token]").value);
                for(var i in file) {
                    formData.append('file[]', file[i]);
                }
                var request = new XMLHttpRequest();
                request.open("POST", "/backend/article/content/upload");
                request.onload = function(oEvent) {
                    let currentTarget = oEvent.currentTarget;
                    if(currentTarget.status !== 200) {
                        layer.alert(currentTarget.status+' '+currentTarget.statusText, {icon: 2});
                        return '';
                    }
                    let json = JSON.parse(currentTarget.response);
                    if(json.code === 0) {
                        var show = [".jpg", ".png", ".gif", ".jpeg", ".bmp"];
                        for(var i in json.data) {
                            var tmp = '['+json.data[i].name+']('+json.data[i].path+')';
                            for(var j in show) {
                                if(json.data[i].path.substr(0 - show[j].length).toLocaleLowerCase() === show[j]) {
                                    tmp = '!'+tmp;
                                    break;
                                }
                            }
                            vEditor.insertValue(tmp);
                        }
                    }else {
                        layer.alert(json.message, {icon: 2});
                    }
                };
                request.send(formData);
            },
        },
        preview: {
            mode: 'both',
            parse: () => {
                LazyLoadImage()
            },
        },
    });
    

    引入的代码:

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vditor@latest/dist/index.classic.css" />
    <script src="https://cdn.jsdelivr.net/npm/vditor@latest/dist/index.min.js"></script>
    
    1 回复
  • 547176052

    image.png
    重新安装的结果

    1 回复
  • 88250
    订阅者

    参考这些搜索结果解决一下。

  • Vanessa
    支持者 赞助者 订阅者 作者

    内容已经渲染在右侧了,图标问题的话你需要 F12 看一下
    image.png

    1 回复
  • 547176052

    image.png
    这个渲染对象值能调用一次吗

    1 回复
  • Vanessa
    支持者 赞助者 订阅者 作者

    这个 preview 是根据 HTML 中的 Textarea 内容进行渲染,再次调用的话 textarea 已经不存在了。

    重复调用的话可以参考 https://github.com/b3log/vditor/blob/master/src/ts/markdown/previewRender.ts#L20

  • 547176052

    image.png
    为什么这么多错误

    1 回复
  • Vanessa
    支持者 赞助者 订阅者 作者

    这个端口和请求看上去不是 vditor 发送的,你看一下是什么地方请求的。

  • buexplain

    image.png

    如上图所示,请问,我这个内容没有渲染是什么原因呢,编辑器图标异常又是要引入什么 css 呢?
    我的代码如下:

    内容容器:
    <div class="vditor-reset" id="j-content">{{.body}}</div>

    编辑器代码:

    //初始化编辑器
    const vEditor = new Vditor('j-content', {
        placeholder:'请输入文章内容',
        cache:false,
        counter: 65535,
        height: $(window).height() - 93,
        tab: '\t',
        upload: {
            linkToImgUrl: '/backend/article/content/upload',
            handler (file) {
                var formData = new FormData();
                formData.append('_token', document.querySelector("input[name=_token]").value);
                for(var i in file) {
                    formData.append('file[]', file[i]);
                }
                var request = new XMLHttpRequest();
                request.open("POST", "/backend/article/content/upload");
                request.onload = function(oEvent) {
                    let currentTarget = oEvent.currentTarget;
                    if(currentTarget.status !== 200) {
                        layer.alert(currentTarget.status+' '+currentTarget.statusText, {icon: 2});
                        return '';
                    }
                    let json = JSON.parse(currentTarget.response);
                    if(json.code === 0) {
                        var show = [".jpg", ".png", ".gif", ".jpeg", ".bmp"];
                        for(var i in json.data) {
                            var tmp = '['+json.data[i].name+']('+json.data[i].path+')';
                            for(var j in show) {
                                if(json.data[i].path.substr(0 - show[j].length).toLocaleLowerCase() === show[j]) {
                                    tmp = '!'+tmp;
                                    break;
                                }
                            }
                            vEditor.insertValue(tmp);
                        }
                    }else {
                        layer.alert(json.message, {icon: 2});
                    }
                };
                request.send(formData);
            },
        },
        preview: {
            mode: 'both',
            parse: () => {
                LazyLoadImage()
            },
        },
    });
    

    引入的代码:

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vditor@latest/dist/index.classic.css" />
    <script src="https://cdn.jsdelivr.net/npm/vditor@latest/dist/index.min.js"></script>
    

    我实际输出的代码是这样的:
    image.png
    现在渲染后的结果是把空格都压缩掉了。

    1 回复
  • Vanessa
    支持者 赞助者 订阅者 作者

    初始化的时候会把 div 里面的内容当作 html 而不是 markdown 进行处理。可以调用 vditor.setValue('...')

    1 回复
  • 547176052

    能不能上传一个完整一依赖包

    1 回复
  • Vanessa
    支持者 赞助者 订阅者 作者

    依赖包可以在 npm 中下载,如果嫌慢的话可以使用淘宝的代理

    1 回复
  • 547176052

    image.png
    这个是什么东西
    image.png
    微软浏览器会多出个滚动条

    1 回复
  • Vanessa
    支持者 赞助者 订阅者 作者

    这个是选中后可以阅读,但是看截图中你并未选中。麻烦告知一下操作步骤

    Chrome 和 Firefox 也会出现滚动条么?

    1 回复
  • 547176052

    双击就会出现,怎么关闭那个选中阅读,只有微软浏览器会出现滚动条,谷歌浏览器不会出现滚动条,还有一个严重的问题,就是输入太多字符串后编辑器回非常卡

    1 回复
  • Vanessa
    支持者 赞助者 订阅者 作者
    1. 稍后更新到 1.9.4,IPreviewOptions 增加配置按钮,默认关闭
           className?: string;
           customEmoji?: { [key: string]: string };
           lang?: (keyof II18nLang);
           emojiPath?: string;
           hljs?: {
              lineNumber?: boolean;
              style?: string;
              enable?: boolean;
           };
           speech?: {
               enable?: boolean
           }
    
    1. 我只有 Mac,你可以先看一下官方示例在微软浏览器中是否会出现滚动条

    2. 目前还没有遇到非常卡的问题,希望能提供一下以下数据:

      • 字符达到多少时会出现非常卡的现象
      • 你站点的用户一般输入字符为多少
      • 预览关了还卡么
      • 出现非常卡的原文
    2 回复
  • 547176052

    预览关了不卡

  • 547176052 1
    • 字符达到多少时会出现非常卡的现象 ====== 5000 左右
    • 你站点的用户一般输入字符为多少 ====== =还没布置站点需要的话可以弄一个
    • 预览关了还卡么===================关了不卡
    • 出现非常卡的原文==================官方例子
    1 回复
    1 操作
    547176052 在 2019-11-08 10:55:07 更新了该回帖
  • Vanessa
    支持者 赞助者 订阅者 作者

    非常感谢,请升级到 1.9.5 后再看一下。如果卡的话可以修改 options.preview.delay 参数

    1 回复
    1 操作
    Vanessa 在 2019-11-08 11:18:45 更新了该回帖
  • 547176052

    还有一个问题,能不能设置为每个 h 元素加个 id,我想弄个目录跳转到指定 h 元素
    image.png
    就像这个一样 目录跳转到 h 元素

    1 回复
    1 操作
    547176052 在 2019-11-08 12:14:16 更新了该回帖
  • 547176052
      const vditor = new Vditor('vditor', {
    	//hljsStyle:'dracula',//设置代码高亮颜色
    	preview:{
    		delay:30000,
    		mode:'both',//模式
    		hljs:{
    			style:'native',//设置代码高亮颜色
    		}
    	},
    	
    	
        typewriterMode: false,//打印机模式
    	placeholder:'请使用Markdown语法编写',
    
        counter: 100,
        height: 462,
    
    
    	
    	resize:{
    		enable:true,//是否支持大小拖拽
    	},
    
    
    
    
    
    
      });
    

    这样设置延迟好像不生效

    1 回复
  • Vanessa
    支持者 赞助者 订阅者 作者

    你看一下这个 Issue #21 · b3log/lute 能满足么?如果可以的话请关注这个 issue 即可。

    1 回复
  • Vanessa
    支持者 赞助者 订阅者 作者

    请检查一下引入的版本号是否为 1.9.5

    1 回复
  • 547176052

    更新 1.9.5 然后清除缓存,果然不卡了

  • 547176052

    这个是扩展 md 语法,我的意思是只要把 md 语言渲染后的 h 元素加个 id 就好,渲染完成后我直接遍历所有 h 元素然后加目录

    1 回复
    1 操作
    547176052 在 2019-11-08 16:01:54 更新了该回帖
  • Vanessa
    支持者 赞助者 订阅者 作者
    2 回复
  • 547176052 1 评论

    渲染有点卡

    https://142536.vip/dz/forum.php?mod=viewthread&tid=31&extra=page%3D1

    页面加载完成后你滚动条 滚一下试试看

    我这里很快,没有卡的感觉。你是用的什么浏览器
    Vanessa
  • 547176052 1 评论

    image.png

    你可以点此了解一下社区的行为准则
    Vanessa
  • 547176052 3 评论
    Vditor.preview()
    

    页面中的 Markdown 进行渲染 完成后有没有回调

    preview 处理了大量异步的 render 方法,使用回调需要等待所有的 render 方法结束。如果回调和 render 方法无关,可以直接在后面调用函数
    Vanessa
    @Vanessa 就是需要 preview 处理了异步的 render 完成后回调 因为渲染完成后 我要为所有 h 添加 id 锚点 以及 css 等
    547176052
    @547176052 这个可以不用回调,直接在 preview 方法后执行就可
    Vanessa
  • 547176052 1 评论
        upload: {
          accept: 'image/jpeg',
          url: 'plugin.php?id=qq547176052:1',
          linkToImgUrl: 'plugin.php?id=qq547176052:1',
          filename (name) {
    		console.log(name);
            return name.replace(/[^(a-zA-Z0-9\u4e00-\u9fa5\.)]/g, '').
              replace(/[\?\\/:|<>\*\[\]\(\)\$%\{\}@~]/g, '').
              replace('/\\s/g', '')
          },
    	  success(editor,msg){
    	 	 //上传成功后怎么改连接
    		console.log(editor+'上传成功'+msg);
    		editor.innerHTML=editor.innerHTML + "![image.png](http://localhost/dz/static/image/common/logo.png)";//这样改好像不行
    	  },
    	  error(msg){
    		console.log('上传成失败:'+msg);
    	  },
        },
    
    
    <?php
    //http://localhost/dz/plugin.php?id=qq547176052:1
    //plugin.php?id=qq547176052:1
    header('Content-Type:application/json; charset=utf-8');
    $ml = "source/plugin/qq547176052/photo/";
    	$picData = $_FILES['Filedata'];
        $imgname = "666";//$_FILES['myfile']['name'];
        $tmp = $_FILES['myfile']['tmp_name'];
        $filepath = $ml;
        if(move_uploaded_file($tmp,$filepath.$imgname.".png")){
            $n = "成功";
        }else{
            $n = "失败";
        }
    
    $arr = array(
    	'code'=>1,
    	'msg'=>$n,
    	'data'=>"json type"
    	
    );
    exit(json_encode($arr));
    ?>
    

    php 端怎么接收图片保存

    1 操作
    547176052 在 2019-11-09 11:53:09 更新了该回帖
    可以使用 options.upload.format,具体方法参见文档。PHP 的话我不会
    Vanessa
  • 547176052 1 评论

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

    dev 分支已经为 h 加上了 id 锚点,可以关注 Issue #163 · b3log/vditor
    Vanessa
  • 547176052 2 评论

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

    编辑器编辑 md 代码 >>>> 转为 html 并设置好需要的样式(代码高亮等)>>>> 文章入库直接 html>>>> 显示文章只需要引入 css 就好 >>>> 编辑文章.把 html 转换为 md 代码然后编辑

    3 操作
    547176052 在 2019-11-09 19:12:23 更新了该回帖
    547176052 在 2019-11-09 19:11:58 更新了该回帖
    547176052 在 2019-11-09 19:10:52 更新了该回帖
    入库应该存 md 原文即可
    Vanessa
    @Vanessa 文章 md 原文入库 显示文章时会吧换行符变成 br 标签 并且有很多引号
    547176052
  • 547176052 1 评论
    <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>
    

    原文入库后的效果

    使用 vditor.getValue() 得到的是 \n,不是 <br>。你可以检查一下你入库的代码
    Vanessa
  • 547176052 1 评论

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

    1 回复
    效果是一样的
    Vanessa
  • haaid 1 1 评论

    能不能将 lute 改为依赖形式,或者可以传递 lute 地址链接,现在 192.168.0.107:9090 是写死的地址,是不是开发完忘了改注释了 😏

    WeWorkHelper20191111020637.png

    1 操作
    haaid 在 2019-11-11 14:06:54 更新了该回帖
    非常抱歉,请更新到 1.9.7
    Vanessa
  • haaid 1

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

    // 正常
    Vditor.preview(dom, markdown, {});
    
    // 错误
    Vditor.preview(dom, markdown);
    
    1 操作
    haaid 在 2019-11-11 14:02:16 更新了该回帖
  • 547176052 1 评论

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

    markdown 语法不支持颜色,但可以用数学公式进行扩展支持
    Vanessa
  • huaxie2017 1 评论

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

    返回问号后面多了个空格,匹配不上,替换不了。可参考 https://hacpai.com/article/1549638745630/comment/1574051799158#toc_h4_15
    Vanessa
  • huaxie2017 4 评论

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

    检查一下语法,可参考 https://hacpai.com/guide/markdown
    Vanessa
    @Vanessa 复制教程里的内容 现在是本地测试中 刷新页面几次可以显示正常 没报错 之后又不行了
    huaxie2017
    @huaxie2017 你看一下我们的 Demo 有没有这个问题?
    Vanessa
    @Vanessa 编辑器内是正常 保存后的 md 文本没渲染成功 静态 demo 是正常渲染的
    huaxie2017
  • haaid 2 评论

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

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

    复制按钮错位请关注 Issue #16 · Vanessa219/vditor
    Vanessa
    触发 submit 请关注 Issue #18 · Vanessa219/vditor
    Vanessa
  • haaid 3 评论

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

    这个使用的是浏览器的 css:hover,目前没有找到更好的解决方案。你有空的话,我们可以一起探讨下
    Vanessa
    @Vanessa 测试发现,是因为焦点还在按钮身上导致的,让按钮失焦或让编辑器获取焦点(vditor.editor.element.focus();)即可解决。
    haaid
    @haaid 我试了下,但是点击后貌似监听不到 mouseout 事件
    Vanessa
  • zyk 2 评论
    捐赠者 支持者 订阅者

    V 姐,在 HTML 面中指定版本号

    <script src="https://cdn.jsdelivr.net/npm/vditor@1.10.9/dist/index.min.js"></script>
    

    会报错,markdown 编辑器加载不了。
    image.png

    1 回复
    Vanessa
    @Vanessa 好的
    zyk
  • hellotomoro

    换到 1.10.8 吧,路径打包没改过来吧

    1 回复
  • zyk
    捐赠者 支持者 订阅者

    1 回复
  • Vanessa
    支持者 赞助者 订阅者 作者

    @hellotomoro

    为了更加方便的修改 cdn,做了修改,产生的 bug 现已修复,请更新到 1.10.10

  • haaid 6 评论

    上传图片按钮还是会触发 submit 事件

    只有上传图片按钮,其他按钮不会触发了吧?
    Vanessa
    可以提供一下浏览器信息和操作方式么?我这里重现不了。
    Vanessa
    @Vanessa 对的,只剩上传图片按钮了,是 button 触发,input 不会
    haaid
    @Vanessa 点 input 旁边的 button 身上就会触发,浏览器是 chrome 最新版
    haaid
    @haaid 好的,我知道了
    Vanessa
    麻烦升级到 1.10.11 再试一下
    Vanessa
请输入回帖内容 ...