首先,要搞清楚一点,markdown 编辑器与传统的富文本编辑器实际上一点区别都没有!只是可能由于某些原因放到了一个概念,那么是一个什么概念呢?
在传统的富文本时代,我们不需要接触任何的标签,我们只用编辑器自带的功能就行,而markdown多了一层标签的概念,而这一层标签的概念被大家无限放大,感觉markdown 是什么神奇的东西。
在富文本编辑器年代,对于引入一个链接,你只要复制这个链接,然后粘贴过去。而在目前部分markdown编辑器,由于设计者默认你懂了markdown的语法就不搞这么一个工具栏而是让你自己手动加入,由于一些完全不知道markdown是什么东西的人来说,就觉得与平常的东西不一样,就感觉markdown是什么神奇的东西。
实际上markdown 一点都不神奇,也没有一点的与众不同,它与大家认知的富文本编辑器实际都一样的,就是一个html解释器在富文本的时候是一个工具栏,在markdown里面是一些标签语法一样。上面说了一些markdown的概念,现在回到这个问题本身,个人认为这两者是没有比较的意义,因为两者都是html的解析器引擎,富文本编辑器操作上面比较简单,但是,markdown一样能做到,就如同segmentfault的编辑器,就有用到markdown,不过可能加了一个工具栏,可能就觉得这是一个富文本编辑器而不是markdown。
由于项目中的回复问题模块可读性较差,需要增加一个富文本编辑器的功能,项目是用vue做的,最终选择了mavon-editor做富文本编辑器,富文本编辑器集成到项目中效果如下:
首先,要想使用mavon-editor,需要先下载并安装
npm install mavon-editor --save
下载完毕查看package.json中是否显示"mavon-editor": “^2.9.0”,确保下载成功就好
接下来就是在项目中使用了,在页面中如下引用
<template>
<div class="mavonEditor">
<mavon-editor ref="md"
:toolbars="markdownOption"
:autofocus="false"
v-model="markdownStr"
@imgAdd="$imgAdd"
@change="changeEditor">
</mavon-editor>
<input type="file" @change="getFile" id="imgFile" accept="image/*" />
<label for="imgFile" class="nb-img-box fix-icon">
<i class="nb-image"></i>
</label>
<i class="nb-tip fix-icon" @click="markdownTip = true"></i>
<a-modal title="Markdown语法参考"
width="320px"
:footer="null"
:closable="false"
class="common-modal"
:visible.sync="markdownTip">
<Tip></Tip>
<div class="closeDialog" @click="markdownTip = false">我知道了</div>
</a-modal>
</div>
</template>
<script>
import { attachNoticesApi } from '@/apis'
import { mavonEditor } from 'mavon-editor'
import { cbSuccess } from '@/utils'
import Tip from './tip'
export default {
data() {
return {
markdownTip: false,
markdownOption: {
bold: true, // 粗体
header: true, // 标题
underline: true, // 下划线
strikethrough: true, // 中划线
mark: true, // 标记
quote: true, // 引用
ol: true, // 有序列表
ul: true, // 无序列表
link: true, // 链接
// imagelink: true, // 图片链接
code: true, // code
table: true, // 表格
fullscreen: true, // 全屏编辑
alignleft: true, // 左对齐
aligncenter: true, // 居中
alignright: true, // 右对齐
subfield: true, // 单双栏模式
},
markdownStr: '',
markdownHtmlStr: ''
};
},
props: {
value: {
type: String,
default: () => {
return ''
}
}
},
watch: {
value (val) {
this.markdownStr = val
}
},
components: {
mavonEditor,
Tip
},
methods: {
getFile (e) {
if (!e || !window.FileReader) return
let file = e.target.files[0]
this.uploadImg(file)
},
async uploadImg (file) {
let formdata = new FormData()
let $vm = this.$refs.md
formdata.append('file', file)
let { data } = await attachNoticesApi(formdata)
if (data.success) {
let _imgUrl = data.operateCallBackObj
let _text = file.name
let insert_text = {
prefix: `![${_text}](`,
subfix: ')',
str: _imgUrl
}
$vm.insertText($vm.getTextareaDom(), insert_text)
} else {
this.$message.error(data.operateMessage)
}
},
// 绑定@imgAdd event上传图片
async $imgAdd (pos, $file) {
let formdata = new FormData()
formdata.append('file', $file)
let { data } = await attachNoticesApi(formdata)
if (data.success) {
this.$refs.md.$img2Url(pos, data.operateCallBackObj)
} else {
this.$message.error(data.operateMessage)
}
},
changeEditor (mdStr, htmlStr) {
this.$emit('setValue', htmlStr, mdStr, 'markdown')
this.markdownHtmlStr = htmlStr
},
setContent (html) {
let turndownService = new TurndownService()
let _markdown = turndownService.turndown(html)
this.markdownStr = _markdown || ""
}
},
created () {
this.markdownStr = this.value || ""
}
}
</script>
<style lang="stylus">
.closeDialog{
font-size: 16px;
line-height: 20px;
border-top: 1px solid #d9d9d9;
padding: 15px 0 0;
text-align: center;
color: #ec7259;
cursor: pointer;
}
.mavonEditor{
.v-note-wrapper .v-note-op .v-left-item,
.v-note-wrapper .v-note-op .v-right-item{
flex none
}
.op-icon-divider{
display none
}
}
</style>
<style lang="stylus" scoped>
.mavonEditor{
overflow auto
border 1px solid #DCDFE6
position relative
.markdown-body >>> .v-right-item{
max-width 100px
margin-left 60px
}
.fix-icon{
cursor pointer
color #757575
position absolute
text-align center
top 12px
right 70px
z-index 90
width 20px
}
.nb-tip{
left 475px
font-size 19px
}
.nb-img-box{
left 445px
.i{
font-size 19px
}
}
}
</style>
由于项目中不仅仅需要富文本支持,还需要Markdown和富文本切换,所以又做了一个切换的组件。
下面是wEditor组件的内容。
<template>
<div class="w-editor-box">
<div id="wEditor"></div>
</div>
</template>
<script>
import hljs from 'highlight.js'
import { attachNoticesApi } from '@/apis'
export default {
data () {
return {
editor: null,
isHTML: false
}
},
props: {
value: {
type: String,
default: () => {
return ''
}
}
},
methods: {
initEditor () {
let _this = this
const E = window.wangEditor
const { $, BtnMenu } = E
class AlertMenu extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu">
<i class="nb-html"></i>
</div>`
)
super($elem, editor)
}
clickHandler() {
_this.showSource()
this.tryChangeActive()
}
tryChangeActive() {
if (_this.isHTML) this.active()
else this.unActive()
}
}
this.editor = new E('#wEditor')
const editor = this.editor
editor.config.menus = [
'bold', 'head', 'italic', 'underline', 'strikeThrough',
'link', 'list', 'justify', 'quote', 'image', 'table', 'code', 'undo', 'redo',
]
editor.highlight = hljs
editor.config.languageType = [
'SQL', 'Shell Session', 'Java', 'JavaScript', 'JSON', 'Markdown', 'TypeScript', 'Plain text',
'Html', 'CSS', 'Python', 'XML', 'Go', 'Bash', 'C', 'C#', 'C++', 'Kotlin', 'Lua', 'PHP', 'Ruby',
]
// 注册菜单
const menuKey = 'alertMenuKey' // 菜单 key ,各个菜单不能重复
editor.menus.extend('alertMenuKey', AlertMenu)
editor.config.menus = editor.config.menus.concat(menuKey)
editor.config.zIndex = 8
editor.config.customUploadImg = function (resultFiles, insertImgFn) {
let file = resultFiles[0]
let formdata = new FormData()
formdata.append('file', file)
attachNoticesApi(formdata).then(res => {
let imgUrl = res.data.operateCallBackObj
insertImgFn(imgUrl)
})
}
editor.config.onchange = function (newHtml) {
let _text = editor.txt.text()
if (_this.isHTML) {
newHtml = _text.replace(/</ig, "<").replace(/>/ig, ">").replace(/ /ig, " ")
}
_this.$emit('setValue', newHtml, _text, 'richtxt')
}
editor.create()
if (_this.value) _this.setContent(_this.value)
},
setContent (html) {
this.editor.txt.html(html)
},
showSource () {
let _this = this
let _editor = _this.editor
_this.isHTML = !_this.isHTML
let _source = _editor.txt.html()
if (_this.isHTML) {
_source = _source.replace(/</g, "<").replace(/>/g, ">").replace(/ /g, " ")
} else {
_source = _editor.txt.text().replace(/</ig, "<").replace(/>/ig, ">").replace(/ /ig, " ")
}
_editor.txt.html(_source)
}
},
mounted () {
this.initEditor()
}
}
</script>
<style lang="stylus">
.w-e-toolbar {
padding 6px
.w-e-menu{
width 28px
height 28px
margin 0 2px
.nb-html{
font-size 22px
}
i{
color #757575
}
}
}
</style>
引入之后,可以进行富文本和Markdown的切换
<template>
<div class="mw-editor-box">
<div class="editor-switch flex center" @click="switchShow = true">
<i class="nb-qiehuan"></i>
<span>{{isMarkdown ? '富文本' : 'Markdown'}}</span>
</div>
<MavonEditor v-show="isMarkdown"
ref="MavonEditor"
:value="mdValue"
@setValue="setMdValue">
</MavonEditor>
<WEditor v-show="!isMarkdown"
ref="WEditor"
:value="htmlValue"
@setValue="setMdValue">
</WEditor>
<a-modal class="common-modal" title="提示" width="320px" :footer="null" :closable="false" :visible.sync="switchShow">
<div class="tip-box">切换为{{isMarkdown ? '富文本' : 'Markdown'}}编辑器后,保留内容可能会出现不兼容现象,确定要切换吗?</div>
<div class="btn-box flex center">
<div class="nimbus-btn gray mr20" @click="switchShow = false">取消</div>
<div class="nimbus-btn bold" @click="confirmSwitch">确定</div>
</div>
</a-modal>
</div>
</template>
<script>
import MavonEditor from './mavonEditor'
import WEditor from './wEditor'
export default {
data () {
return {
isMarkdown: true,
switchShow: false
}
},
props: {
htmlValue: {
type: String,
default: () => {
return ''
}
},
mdValue: {
type: String,
default: () => {
return ''
}
}
},
components: {
MavonEditor,
WEditor
},
methods: {
setMdValue (html, md, editorType) {
if (this.isMarkdown && editorType === 'markdown' || (!this.isMarkdown && editorType === 'richtxt')) {
this.$emit('setValue', html, md, editorType)
}
},
confirmSwitch () {
let _refs = this.$refs
if (this.isMarkdown) {
let _html = _refs.MavonEditor.markdownHtmlStr
_refs.WEditor.setContent(_html)
this.isMarkdown = false
} else {
let _html = _refs.WEditor.editor.txt.html()
_refs.MavonEditor.setContent(_html)
this.isMarkdown = true
}
this.switchShow = false
}
},
created () {
// 如果md无值,且html有值时,默认采用富文本编辑器
if (this.htmlValue && !this.mdValue) this.isMarkdown = false
}
}
</script>
<style lang="stylus">
.article-mavon-editor{
height calc(100% - 60px)
.mavonEditor{
height 100%
.v-note-wrapper{
height 100%
box-shadow none
margin-bottom 0
}
}
.w-editor-box{
height 100%
#wEditor{
height 100%
.w-e-text-container{
height calc(100% - 42px) !important
}
}
}
}
#reply-editor{
.w-editor-box{
height 100%
#wEditor{
height 100%
.w-e-text-container{
height 256px !important
}
}
}
}
</style>
<style lang="stylus" scoped>
.mw-editor-box{
position relative
}
.editor-switch{
position absolute
padding 0 8px
height: 24px;
border: 1px solid #E8EEFC;
border-radius: 4px;
z-index 99
right 20px
top 10px
background #ffffff
cursor pointer
font-size 12px
color #757575
i{
font-size 13px
color #737373
margin-right 3px
}
}
</style>