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

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

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

npm bundle size



English  |  Demo

💡 简介

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

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

b3logos.jpg

🗺️ 背景

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

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

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

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

✨ 特性

editor.png

preview.png

🔮 编辑模式

所见即所得(WYSIWYG)

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

vditor-wysiwyg

即时渲染(IR)

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

vditor-ir

分屏预览(SV)

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

vditor-sv

🍱 语法支持

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

🗃 案例

🛠️ 使用文档

CommonJS

npm install vditor --save
import Vditor from 'vditor'
import "~vditor/src/assets/scss/index"

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

HTML script

<!-- ⚠️生产环境请指定版本号,如 https://cdn.jsdelivr.net/npm/vditor@x.x.x/dist... -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vditor/dist/index.css" />
<script src="https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js"></script>

示例代码

主题

编辑器主题

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

内容主题

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

代码主题

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

API

id

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

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

options

说明 默认值
undoDelay 历史记录间隔 -
after 编辑器异步渲染完成后的回调方法 -
height 编辑器总高度 'auto'
minHeight 编辑区域最小高度 -
width 编辑器总宽度,支持 % 'auto'
placeholder 输入区域为空时的提示 ''
lang 多语言:en_US, ja_JP, ko_KR, zh_CN 'zh_CN'
input(value: string) 输入后触发 -
focus(value: string) 聚焦后触发 -
blur(value: string) 失焦后触发 -
esc(value: string) esc 按下后触发 -
ctrlEnter(value: string) ⌘/ctrl+enter 按下后触发 -
select(value: string) 编辑器中选中文字后触发 -
tab tab 键操作字符串,支持 \t 及任意字符串 -
typewriterMode 是否启用打字机模式 false
cdn 配置自建 CDN 地址 https://cdn.jsdelivr.net/npm/vditor@${VDITOR_VERSION}
mode 可选模式:sv, ir, wysiwyg 'ir'
debugger 是否显示日志 false
value 编辑器初始化值 ''
theme 主题:classic, dark 'classic'
icon 图标风格:ant, material 'ant'

options.toolbar

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
tipPosition(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

说明 默认值
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 可选主题列表 { dark: "Dark", light: "Light", wechat: "WeChat" }
path 主题样式地址 https://cdn.jsdelivr.net/npm/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.hint

说明 默认值
parse 是否进行 md 解析 true
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
extend: IHintExtend[] 对 @/话题等关键字自动补全的扩展 []
interface IHintExtend {
    key: string;

    hint?(value: string): Array<{
        html: string;
        value: string;
    }>;
}

options.upload

// POST data  
xhr.send(formData);  // formData = FormData.append("file[]", File)  
// return data  
{  
 "msg": "",  
 "code": 0,  
 "data": {  
 "errFiles": ['filename', 'filename2'],  
 "succMap": {  
   "filename3": "filepath3",  
   "filename3": "filepath3"  
   }  
 }  
}
// POST data  
xhr.send(JSON.stringify({url: src})); // src 为站外图片地址  
// return data  
{  
 msg: '',  
 code: 0,  
 data : {  
   originalURL: '',  
   url: ''  
 }  
}
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 自定义上传,当发生错误时返回错误信息 -
format(files: File[], responseText: string): string 对服务端返回的数据进行转换,以满足内置的数据结构 -
file(files: File[]): 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

说明
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

Vditor.mermaidRender(document)
import VditorPreview from 'vditor/dist/method.min'  
VditorPreview.mermaidRender(document)
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
}
说明
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, lang: (keyof II18nLang) = "zh_CN") 为 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://cdn.jsdelivr.net/npm/vditor@版本号

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

升级

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

Ⓜ️ Markdown 使用指南

🏘️ 社区

📄 授权

Vditor 使用 MIT 开源协议。

🙏 鸣谢

📽️ 历史

我们在开发 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

欢迎来到这里!

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

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

    哈哈,不好意思。返回要返回绝对路径,我返回了相对路径,已经成功上传图片。

    image.png

    有遇见的同学,可以把这个坑跳过去了。。

  • 其他回帖
  • 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
  • sweeter

    v 姐 localhost:9000 可以访问了 我想把这个作为自己博客页面的编辑器 但是不知道怎么集成进去

    刚开始用了这种

    Title
    上面这段代码无法显示出编辑器

    接着我又试了另一种,把 dist 目录拷贝到项目的静态文件夹里,然后也不行 空白

    Snipaste20200905114812.jpg

    实在没办法,editor.md 的教程倒是不少,但是我觉得没有 vditor 好用,无奈关于 vditor 的教程又太少,v 姐能给个页面嵌入 vditor 的例子吗

  • gztrljh 1 评论

    image.png

    官网的默认的完整渲染示例,渲染大纲会出现如上错误

    就是这个代码

      const initOutline = () => {
        const headingElements = []
        Array.from(document.getElementById('preview').children).forEach((item) => {
          if (item.tagName.length === 2 && item.tagName !== 'HR' && item.tagName.indexOf('H') === 0) {
            headingElements.push(item)
          }
        })
    
        let toc = []
        window.addEventListener('scroll', () => {
          const scrollTop = window.scrollY
          toc = []
          headingElements.forEach((item) => {
            toc.push({
              id: item.id,
              offsetTop: item.offsetTop,
            })
          })
    
          const currentElement = document.querySelector('.vditor-outline__item--current')
          for (let i = 0, iMax = toc.length; i < iMax; i++) {
            if (scrollTop < toc[i].offsetTop - 30) {
              if (currentElement) {
                currentElement.classList.remove('vditor-outline__item--current')
              }
              let index = i > 0 ? i - 1 : 0
              document.querySelector('div[data-id="' + toc[index].id + '"]').classList.add('vditor-outline__item--current')
              break
            }
          }
        })
      }
    

    把渲染大纲部分给注释掉则不会报错了

    image.png

    image.png

    image.png

    请问 V 姐该怎么解决呢?

    多谢,官方稍后会更新一波
    Vanessa
  • 查看全部回帖