
Research
Security News
Lazarus Strikes npm Again with New Wave of Malicious Packages
The Socket Research Team has discovered six new malicious npm packages linked to North Korea’s Lazarus Group, designed to steal credentials and deploy backdoors.
♏ An In-browser Markdown editor, support WYSIWYG, Instant Rendering (Typora-like) and Split View modes.
易于使用的 Markdown 编辑器,为适配不同的应用场景而生
Vditor 是一款浏览器端的 Markdown 编辑器,支持所见即所得、即时渲染(类似 Typora)和分屏预览模式。它使用 TypeScript 实现,支持原生 JavaScript、Vue、React、Angular,提供桌面版。
欢迎到 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/scss/index"
const vditor = new Vditor(id, {options...})
<!-- ⚠️生产环境请指定版本号,如 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" defer></script>
class="vditor-reset"
(经典主题) 或 class="vditor-reset vditor-reset--dark"
(黑色主题) 属性可对内容进行更为友好的展示可填入元素 id
或元素自身 HTMLElement
⚠️:当填入元素自身的 HTMLElement
时需设置 options.cache.id
或将 options.cache.enable
设置为 false
说明 | 默认值 | |
---|---|---|
after | 编辑器异步渲染完成后的回调方法 | - |
height | 编辑器总高度 | 'auto' |
minHeight | 编辑区域最小高度 | - |
width | 编辑器总宽度,支持 % | 'auto' |
placeholder | 输入区域为空时的提示 | '' |
lang | 多语言:en_US, ko_KR, zh_CN | 'zh_CN' |
input | 输入后触发 (value: string, previewElement?: HTMLElement): void | - |
focus | 聚焦后触发 (value: string): void | - |
blur | 失焦后触发 (value: string): void | - |
esc | esc 按下后触发 (value: string): void | - |
ctrlEnter | ⌘/ctrl+enter 按下后触发 (value: string): void | - |
select | 编辑器中选中文字后触发 (value: string): void | - |
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' |
outline | 是否展现大纲 | false |
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
, 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://hacpai.com/sponsor')},
}],
})
说明 | 默认值 | |
---|---|---|
name | 唯一标示 | - |
icon | svg 图标 | - |
tip | 提示 | - |
tipPosition | 提示位置:ne, nw | - |
hotkey | 快捷键,格式为⌘/ctrl-key 或 ⌘/ctrl-⇧/shift-key | - |
suffix | 插入编辑器中的后缀 | - |
prefix | 插入编辑器中的前缀 | - |
click | 自定义按钮点击时触发的事件 (): void | - |
className | 样式名 | '' |
toolbar?: Array<options.toolbar> | 子菜单 | - |
说明 | 默认值 | |
---|---|---|
hide | 是否隐藏工具栏 | false |
pin | 是否固定工具栏 | false |
说明 | 默认值 | |
---|---|---|
enable | 是否启用计数器 | false |
max | 允许输入的最大值 | - |
type | 统计类型:md,text | 'md' |
说明 | 默认值 | |
---|---|---|
enable | 是否使用 localStorage 进行缓存 | true |
id | 缓存 key,第一个参数为元素且启用缓存时必填 | - |
after | 缓存后的回调 (html: string): string | - |
说明 | 默认值 | |
---|---|---|
delay | 预览 debounce 毫秒间隔 | 1000 |
maxWidth | 预览区域最大宽度 | 800 |
mode | 显示模式:both, editor | 'both' |
url | md 解析请求 | - |
parse | 预览回调 (element: HTMLElement): void | - |
transform | 渲染之前回调 (html: string): string | - |
说明 | 默认值 | |
---|---|---|
enable | 是否启用代码高亮 | true |
style | 可选值参见Chroma | github |
lineNumber | 是否启用行号 | false |
说明 | 默认值 | |
---|---|---|
autoSpace | 自动空格 | false |
fixTermTypo | 自动矫正术语 | false |
chinesePunct | 自动矫正标点 | false |
toc | 插入目录 | false |
footnotes | 脚注 | true |
codeBlockPreview | wysiwyg 和 ir 模式下是否对代码块进行渲染 | true |
setext | 是否解析 setext 标题 | true |
paragraphBeginningSpace | 段落开头空两个 | false |
sanitize | 是否启用过滤 XSS | true |
listStyle | 为列表添加 data-style 属性 | false |
linkBase | 链接前缀 | '' |
说明 | 默认值 | |
---|---|---|
current | 当前主题 | "light" |
list | 可选主题列表 | { dark: "Dark", light: "Light", wechat: "WeChat" } |
path | 主题样式地址 | https://cdn.jsdelivr.net/npm/vditor@${VDITOR_VERSION}/dist/css/content-theme |
说明 | 默认值 | |
---|---|---|
inlineDigit | 内联数学公式起始 $ 后是否允许数字 | false |
macros | 使用 MathJax 渲染时传入的宏定义 | {} |
engine | 数学公式渲染引擎:KaTeX, MathJax | 'KaTeX' |
默认值为 ["desktop", "tablet", "mobile", "mp-wechat", "zhihu"]。 可从默认值中挑选进行配置,也可使用以下字段进行自定制开发。
说明 | 默认值 | |
---|---|---|
key | 按钮唯一标识,不能为空 | - |
text | 按钮文字 | - |
className | 按钮类名 | - |
click: (key: string) => void; | 按钮点击回调事件 | - |
说明 | 默认值 | |
---|---|---|
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<any>,需同步返回数组 [{value: '', html: ''}] | - |
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 重新上传 | '' |
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 | - |
file | 将上传的文件处理后再返回 (files: File[]): File[] | - |
setHeaders | 上传前使用返回值设置头 (): { [key: string]: string } | - |
extraData | 为 FormData 添加额外的参数 { [key: string]: string | Blob } | - |
multiple | 上传文件是否为多个 | true |
fieldName | 上传字段名称 | 'file[]' |
说明 | 默认值 | |
---|---|---|
enable | 是否支持大小拖拽 | false |
position | 拖拽栏位置:top, bottom | 'bottom' |
after | 拖拽结束的回调 (height: number): void | - |
说明 | 默认值 | |
---|---|---|
preview | 预览元素上的 className | '' |
说明 | |
---|---|
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() | 销毁编辑器 |
method.min.js
后如下直接调用Vditor.mermaidRender(document)
import VditorPreview from 'vditor/dist/method.min'
VditorPreview.mermaidRender(document)
preview
方法,参数如下:previewElement: HTMLDivElement, // 使用该元素进行渲染
markdown: string, // 需要渲染的 markdown 原文
options?: IPreviewOptions {
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?(): void; // 渲染完成后的回调
lazyLoadImage?: string; // 设置为 Loading 图片地址后将启用图片的懒加载
markdown?: options.preview.markdown;
theme?: options.preview.theme;
renderers?: ILuteRender; // 自定义渲染 https://hacpai.com/article/1588412297062
}
method.min.js
和 index.min.js
不可同时引入说明 | |
---|---|
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) | 图表渲染 |
mindmapRender(element: (HTMLElement | Document) = document, cdn = options.cdn) | 脑图渲染 |
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 install
npm run start
启动本地服务器,打开 http://localhost:9000npm run build
打包代码到 dist 目录由于使用了按需加载的机制,默认 CDN 为 https://cdn.jsdelivr.net/npm/vditor@版本号
如果代码有修改或需要使用自建 CDN 的话,可按以下步骤进行操作:
options
及 IPreviewOptions
中的 cdn
,emojiPath
, themes
进行配置highlightRender
, mathRender
, abcRender
, chartRender
, mermaidRender
,mindmapRender
,graphvizRender
,setCodeTheme
,setContentTheme
方法中需添加 cdn 参数版本升级时请仔细阅读 CHANGELOG 中的升级部分
Vditor 使用 MIT 开源协议。
我们在开发 Sym 的初期是直接使用 WYSIWYG 富文本编辑器的。那时候基于 HTML 的编辑器非常流行,项目中引用起来也很方便,也符合用户当时的使用习惯。
后来,Markdown 的崛起逐步改变了大家的排版方式。再加上我们其他几个项目都是面向程序员用户的,所以迁移到 md 上也是大势所趋。我们选择了 CodeMirror,这是一款优秀的编辑器,它对开发者提供了丰富的编程接口,对各种浏览器的兼容性也比较好。
再后来,随着我们项目业务需求方面的沉淀,使用 CodeMirror 有时候会感到比较“笨重”。比如要实现 @自动完成用户名列表、插入 Emoji、上传文件等就需要比较深入的二次开发,而这些业务需求恰恰是很多项目场景共有且必备的。
终于,我们决定开始在 Sym 中自己实现编辑器。随着几个版本的迭代,Sym 的编辑器也日趋成熟。在我们运营的社区黑客派上陆续有人问我们是否能将编辑器单独抽离出来提供给大家使用。与此同时,我们的前端主程 V 同学对于维护分散在各个项目中的编辑器也感到有点力不从心,外加对 TypeScript 的好感,所以就决定使用 ts 来实现一个全新的浏览器端 md 编辑器。
于是,Vditor 就这样诞生了。
FAQs
♏ 易于使用的 Markdown 编辑器,为适配不同的应用场景而生
The npm package vditor receives a total of 30,613 weekly downloads. As such, vditor popularity was classified as popular.
We found that vditor demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
The Socket Research Team has discovered six new malicious npm packages linked to North Korea’s Lazarus Group, designed to steal credentials and deploy backdoors.
Security News
Socket CEO Feross Aboukhadijeh discusses the open web, open source security, and how Socket tackles software supply chain attacks on The Pair Program podcast.
Security News
Opengrep continues building momentum with the alpha release of its Playground tool, demonstrating the project's rapid evolution just two months after its initial launch.