markdown编辑器和富文本编辑器实现切换功能(代码详细)

首先,要搞清楚一点,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(/&lt;/ig, "<").replace(/&gt;/ig, ">").replace(/&nbsp;/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, "&lt;").replace(/>/g, "&gt;").replace(/ /g, "&nbsp;")
      } else {
        _source = _editor.txt.text().replace(/&lt;/ig, "<").replace(/&gt;/ig, ">").replace(/&nbsp;/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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值