开发背景
这所有都源于在开发 vue3-admin 的 demo 内容时,没有适合的编辑器组件。
阐明
仅反对 vue3 我的项目下应用,应用 jsx 语法开发,反对在 tsx 我的项目应用。为了减小插入,没有应用 less.modifyVars
办法来切换主题,而采纳了替换 class 名称的形式。
代码仓库:md-editor-v3
性能一览
- 快捷插入内容工具栏、编辑器浏览器全屏、页面内全屏等;
- 内置的红色主题和暗黑主题,反对绑定切换;
- 反对快捷键插入内容;
- 反对应用 prettier 格式化内容(应用 CDN 形式引入,只反对格式化 md 内容,可在代码内设置敞开);
- 反对多语言,反对自行扩大语言;
- 反对复制粘贴上传图片;
- …
更多功能待后续更新,若有想要的性能未开发,请留言~
预览图
默认模式下:
暗黑模式下:
在线预览
主题追随返回 vue3-admin 尝试~~
编辑器主页演示没开发完~~~
apis
留神在 jsx 下需应用 modelValue 配合 onChange 办法实现数据绑定,在 vue 模板语法下,能够间接应用 v-modal 指令~
props
名称 | 类型 | 默认值 | 阐明 |
---|---|---|---|
modelValue | String | ” | md 编辑内容,vue 模板反对双向绑定(v-model=”value”) |
editorClass | String | ” | 编辑器最外层款式 |
hljs | Object | null | 我的项目中应用到了 highlight,可将实例间接传递,生产环境则不会申请 CDN,须要手动导入反对的高亮代码款式 |
highlightJs | String | highlight.js | highlightJs CDN |
highlightCss | String | atom-one-dark | 预览高亮代码款式 |
historyLength | Number | 10 | 最大记录操作数(太大会占用内存) |
pageFullScreen | Boolean | false | 浏览器内全屏 |
preview | Boolean | true | 预览模式 |
htmlPreview | Boolean | false | html 预览 |
language | String | ‘zh-CN’ | 内置中英文(‘zh-CN’,’en-US’),可自行扩大其余语言,同时可笼罩内置的中英文 |
languageUserDefined | Array | [{key: StaticTextDefaultValue}] | 通过这里扩大语言,批改 language 值为扩大 key 即可,类型申明可手动导入 |
toolbars | Array | [all] | 选择性展现工具栏,可选内容如下[toolbars] |
prettier | Boolean | true | 是否启用 prettier 优化 md 内容 |
prettierCDN | String | standalone | |
prettierMDCDN | String | parser-markdown | |
editorName | String | ‘editor’ | 当在同一页面搁置了多个编辑器,最好提供该属性以区别某些带有 ID 的内容 |
[toolbars]
[
'bold',
'underline',
'italic',
'strikeThrough',
'title',
'sub',
'sup',
'quote',
'unorderedList',
'orderedList',
'codeRow',
'code',
'link',
'image',
'table',
'revoke',
'next',
'save',
'pageFullscreen',
'fullscreen',
'preview',
'htmlPreview',
'github'
];
自定义语言,须要替换的内容如下(某些字段若不被动提供,可能会造成页面不美观):
[StaticTextDefaultValue]
export interface StaticTextDefaultValue {
toolbarTips?: ToolbarTips;
titleItem?: {
h1?: string;
h2?: string;
h3?: string;
h4?: string;
h5?: string;
h6?: string;
};
linkModalTips?: {
title?: string;
descLable?: string;
descLablePlaceHolder?: string;
urlLable?: string;
UrlLablePlaceHolder?: string;
buttonOK?: string;
buttonUpload?: string;
};
}
事件绑定
名称 | 入参 | 阐明 |
---|---|---|
onChange | v:String | 内容变动事件(以后与 textare 的oninput 事件绑定,每输出一个单字即会触发) |
onSave | v:String | 保留事件,快捷键与保留按钮均会触发 |
onUploadImg | files:FileList, callback:Function | 上传图片事件,弹窗会期待上传后果,务必将上传后的 urls 作为 callback 入参回传 |
快捷键
次要以 CTRL
搭配对应性能英文单词首字母,抵触项增加SHIFT
,再抵触替换为ALT
。
键位 | 性能 | 阐明 | 开发标记 | ||
---|---|---|---|---|---|
CTRL + S | 保留 | 触发编辑器的 onSave 回调 |
√ | ||
CTRL + B | 加粗 | ** 加粗 ** |
√ | ||
CTRL + U | 下划线 | <u> 下划线 </u> |
√ | ||
CTRL + I | 斜体 | * 斜体 * |
√ | ||
CTRL + 1-6 | 1-6 级题目 | # 题目 |
√ | ||
CTRL + ↑ | 上角标 | <sup> 上角标 </sup> |
√ | ||
CTRL + ↓ | 下角标 | <sub> 下角标 </sub> |
√ | ||
CTRL + Q | 援用 | > 援用 |
√ | ||
CTRL + O | 有序列表 | 1. 有序列表 |
√ | ||
CTRL + L | 链接 | [链接](https://imbf.cc) |
√ | ||
CTRL + T | 表格 | `\ | 表格 \ | ` 放弃开发(无奈实现) | x |
CTRL + Z | 撤回 | 触发编辑器内内容撤回,与零碎无关 | √ | ||
CTRL + SHIFT + S | 删除线 | ~ 删除线~ |
√ | ||
CTRL + SHIFT + U | 无序列表 | - 无序列表 |
√ | ||
CTRL + SHIFT + C | 块级代码 | 多行代码块 | √ | ||
CTRL + SHIFT + I | 图片链接 | ![图片](https://imbf.cc) |
√ | ||
CTRL + SHIFT + Z | 前进一步 | 触发编辑器内内容后退,与零碎无关 | √ | ||
CTRL + SHIFT + F | 丑化内容 | √ | |||
CTRL + ALT + C | 行内代码 | 行内代码块 | √ |
演示
yarn add md-editor-v3
jsx 语法我的项目
import {defineComponent, reactive} from 'vue';
import Editor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
import hljs from 'highlight.js';
import 'highlight.js/styles/atom-one-dark.css';
export default defineComponent({setup() {
const md = reactive({text: 'default markdown content'});
return () => (<Editor hljs={hljs} modelValue={md.text} onChange={(value) => (md.text = value)} />
);
}
});
vue 模板我的项目
<template>
<editor v-model="text" pageFullScreen></editor>
</template>
<script>
import {defineComponent} from 'vue';
import Editor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
export default defineComponent({
name: 'VueTemplateDemo',
components: {Editor},
data() {
return {text: '默认值'};
}
});
</script>
上传图片
默认能够抉择多张图片,反对粘贴板上传图片。
留神:粘贴板上传时,如果是网页上的 gif 图,无奈正确上传为 gif 格局!
async onUploadImg(files: FileList, callback: (urls: string[]) => void) {
const res = await Promise.all(Array.from(files).map((file) => {return new Promise((rev, rej) => {const form = new FormData();
form.append('file', file);
axios
.post('/api/img/upload', form, {
headers: {'Content-Type': 'multipart/form-data'}
})
.then((res) => rev(res))
.catch((error) => rej(error));
});
})
);
callback(res.map((item: any) => item.data.url));
}
结尾
该我的项目目前只生存了 3 周,应用中有 bug 期待你能留言给我,有想要的性能期待理解哦。另外审美个别,轻喷。