Introducing tinymce rich text editor into Vue project

Introduce tinymce rich text editor into Vue project

The rich text editor originally used in the project is wangEditor , this is a very lightweight and concise editor

However, the company's business upgrade requires a more comprehensive editor. I've been looking for it for a long time. At present, the common editors are as follows:

UEditor : Baidu's front-end open source project is powerful and based on jQuery, but it has not been maintained, and the back-end code is limited, which is difficult to modify

bootstrap-wysiwyg : miniature, easy to use, small and beautiful, just Bootstrap + jQuery

kindEditor : powerful, concise code, need to configure the background, and haven't seen the update for a long time

wangEditor : lightweight, simple and easy to use, but upgrade to 3.0 After X, it is not convenient for customized development. However, the author is very diligent. In a broad sense, he and I are a family and make a call

quill : it doesn't have many functions, but it can be extended by itself. The api is also easy to understand. If you can understand English

summernote : no in-depth study. The UI is very beautiful. It is also a small and beautiful editor, but I need a big one

With such reference, I finally chose tinymce This editor that does not engage in black technology and cannot even open the official website (it is simply asking for trouble), mainly because of two points:

1. GitHub has many stars and complete functions;

2. The only editor that can paste from word and maintain most formats;

3. There is no need to find back-end personnel to scan the code and change the interface, and the front and rear ends are separated;

4. What's the agreed two points!

Note: this blog is only applicable to TinyMCE 4 x

In addition to tinymce, CKEditor 5 It is also a large and comprehensive rich text editor

But the CK5 threshold is high. Those who are interested can see this article CKEditor 5 (I) -- building customized engineering projects from scratch

1, Resource download

tinymce officially provides a component for the vue project tinymce-vue

npm install @tinymce/tinymce-vue -S

Running this code on the terminal of vscode and webstorm may report errors. It is best to use the command-line tool of the operating system

If you have purchased tinymce services, you can refer to the description of tinymce Vue and use tinymce directly through API key

If you haven't bought anything like me, you should download tinymce honestly

npm install tinymce -S

If you use vue2 Do not install version 0. You cannot use version @ TinyMCE / TinyMCE Vue above 4 in vue2;

Install a lower version: for example: npm install @tinymce/tinymce-vue@3.0.1 -S

The tinymce version size doesn't matter

After installation, on node_ Find the tinymce/skins directory in modules, and then copy the skins directory to the "static" directory

//If you are using Vue cli 3 The typescript project built by X is placed in the public directory. All static directory related items in this article are handled in this way

tinymce defaults to the English interface, so you need to download a Chinese interface Language pack (remember to visit in a scientific way!)

Then put the language pack under the "static" directory. In order to make the structure clear, I packed a layer of tinymce directory

II. Initialization

Introduce the following files into the page

import tinymce from 'tinymce/tinymce'
import 'tinymce/themes/modern/theme'
import Editor from '@tinymce/tinymce-vue'

Reminded by the comment area, if you can't find 'tinymce/themes/modern/theme'

Can be replaced by 'tinymce/themes/silver/theme'
 

TinyMCE Vue is a component that needs to be registered in components and then used directly

<Editor id="tinymce" v-model="tinymceHtml" :init="editorInit"></Editor>

init here is the tinymce initialization configuration item. We will talk about some key APIs later. You can refer to the complete api Official documents

The editor needs a skin to work properly, so set a skin_ The URL points to the previously copied skin file

editorInit: {
  language_url: '/static/tinymce/zh_CN.js',
  language: 'zh_CN',
  skin_url: '/static/tinymce/skins/lightgray',
  height: 300
}

// vue-cli 3. For the typescript project created by X, remove the static in the URL, that is, skin_url: '/tinymce/skins/lightgray'

At the same time, initialization is also required in mounted:

If the above init object is passed here, it will not take effect, but an error will be reported if no parameters are passed, so an empty object is passed here

Some friends reported that the following errors may appear here

 

This is because the address of the init parameter is wrong. Please check whether several paths in the init parameter are correct

If the parameters are correct, you can delete language first_ URL and} language try again

III. extension

After the above initialization, the editor can run normally, but there are only some basic functions

tinymce by adding plug-ins plugins  To add features

For example, to add a function of uploading pictures, you need to use the image plug-in, and to add hyperlinks, you need to use the link plug-in

At the same time, these plug-ins need to be introduced into the page:

After adding a plug-in, the corresponding function buttons will be added on the toolbar by default, and the toolbar can also be customized

 

Post the complete component code:

<template>
  <div class="tinymce">
    <editor id="tinymce" v-model="Html" :init="tinymceInit" @input="echoEditor"></editor>
    <!-- <div v-html="Html"></div> -->
  </div>
</template>

<script>
import uploadApi from '@/api/uploadApi/upload'
import tinymce from 'tinymce/tinymce'
import 'tinymce/themes/silver/theme'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/icons/default/icons'
import 'tinymce/plugins/image'
import 'tinymce/plugins/link'
import 'tinymce/plugins/code'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/contextmenu'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/colorpicker'
import 'tinymce/plugins/textcolor'
import 'tinymce/plugins/print'
import 'tinymce/plugins/preview'
import 'tinymce/plugins/searchreplace'
import 'tinymce/plugins/autolink'
import 'tinymce/plugins/directionality'
import 'tinymce/plugins/visualblocks'
import 'tinymce/plugins/visualchars'
import 'tinymce/plugins/media'
import 'tinymce/plugins/fullscreen'
import 'tinymce/plugins/template'
import 'tinymce/plugins/codesample'
import 'tinymce/plugins/charmap'
import 'tinymce/plugins/hr'
import 'tinymce/plugins/pagebreak'
import 'tinymce/plugins/nonbreaking'
import 'tinymce/plugins/anchor'
import 'tinymce/plugins/insertdatetime'
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/imagetools'
import 'tinymce/plugins/textpattern'
import 'tinymce/plugins/help'
import 'tinymce/plugins/emoticons'
import 'tinymce/plugins/autosave'
import 'tinymce/plugins/autoresize'
// import 'tinymce/baseURL/icons/custom'
// import '../../../public/tinymce/plugins/emoticons'
import '../../../public/tinymce/plugins/bdmap/plugin'
import '../../../public/tinymce/plugins/indent2em/plugin'
import '../../../public/tinymce/plugins/axupimgs/plugin'
// import 'tinymce/plugins/formatpainter'
export default {
  name: 'Tinymce',
  data() {
    return {
      Html: '',
      tinymceInit: {
        language_url: '../../../tinymce/zh_CN.js',
        language: 'zh_CN',
        skin_url: '../../../tinymce/skins/ui/oxide',
        emoticons_database_url: '../../../tinymce/plugins/emoticons/js/emojis.js',
        plugins:
          'print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template code codesample table charmap hr pagebreak nonbreaking anchor insertdatetime advlist lists wordcount imagetools textpattern help emoticons autosave autoresize bdmap indent2em axupimgs',
        //     toolbar:
        //       'code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | \
        // styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | \
        // table image media charmap emoticons hr pagebreak insertdatetime print preview | fullscreen | bdmap indent2em lineheight axupimgs',
        toolbar: [
          'code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | \
       styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | \
         table image media charmap emoticons hr pagebreak insertdatetime print preview | fullscreen | bdmap indent2em lineheight axupimgs'
        ],
        height: 650, //Editor height
        min_height: 400,
        // icons: 'custom',
        // inline: true,
        // statusbar: false,
        /*content_css: [ //css for content display in the editing area can be set and used with caution
        '/static/reset.css',
        '/static/ax.css',
        '/static/css.css',
    ],*/
        fontsize_formats: '12px 14px 16px 18px 24px 36px 48px 56px 72px',
        font_formats:
          'Microsoft YaHei =Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;Apple apple recipe=PingFang SC,Microsoft YaHei,sans-serif;Song typeface=simsun,serif;Imitation song style=FangSong,serif;Blackbody=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;',
        link_list: [
          { title: 'Preset link 1', value: 'http://www.tinymce.com' },
          { title: 'Preset link 2', value: 'http://tinymce.ax-z.cn' }
        ],
        image_list: [
          { title: 'Preset picture 1', value: 'https://www.tiny.cloud/images/glyph-tinymce@2x.png' },
          { title: 'Preset picture 2', value: 'https://www.baidu.com/img/bd_logo1.png' }
        ],
        image_class_list: [
          { title: 'None', value: '' }
          // { title: 'Some class', value: 'class-name' }
        ],
        importcss_append: true,
        //Customize the callback content of the file selector
        file_picker_callback: function (callback, value, meta) {
          if (meta.filetype === 'file') {
            // //First simulate an input for uploading local files
            var input = document.createElement('input')
            input.setAttribute('type', 'file')
            // You can add the accept attribute to input to limit the types of files uploaded
            // For example: input setAttribute('accept', '.jpg,.png')
            input.setAttribute('accept', '.doc,.docx,.ppt,.pptx,.pdf,.xlsx')
            input.click()
            input.onchange = function () {
              var file = this.files[0]

              uploadApi.getUpload({ bucketName: 'notice', objectName: file.name }).then(res => {
                console.log(res.data.downloadPath)
                uploadApi.uploadFile({ url: res.data.uploadPath, file: file }).then(data => {
                  callback(res.data.downloadPath, { text: file.name })
                })
              })
            }
            // callback('https://www.baidu.com/img/bd_logo1.png', { text: 'My text' })
          }
          if (meta.filetype === 'image') {
            // callback('https://www.baidu.com/img/bd_logo1.png', { alt: 'My alt text' })
            // //First simulate an input for uploading local files
            var input = document.createElement('input')
            input.setAttribute('type', 'file')
            // You can add the accept attribute to input to limit the types of files uploaded
            // For example: input setAttribute('accept', '.jpg,.png')
            input.setAttribute('accept', '.jpg,.png,.jpeg,.svg')
            input.click()
            input.onchange = function () {
              var file = this.files[0]

              uploadApi.getUpload({ bucketName: 'notice', objectName: file.name }).then(res => {
                console.log(res.data.downloadPath)
                uploadApi.uploadFile({ url: res.data.uploadPath, file: file }).then(data => {
                  callback(res.data.downloadPath, { text: file.name })
                })
              })
            }
          }
          if (meta.filetype === 'media') {
            // callback('https://www.baidu.com/img/bd_logo1.png', { alt: 'My alt text' })
            // //First simulate an input for uploading local files
            var input = document.createElement('input')
            input.setAttribute('type', 'file')
            // You can add the accept attribute to input to limit the types of files uploaded
            // For example: input setAttribute('accept', '.jpg,.png')
            input.setAttribute('accept', '.mp4,.mov,.wmv,.flv')
            input.click()
            input.onchange = function () {
              var file = this.files[0]
              uploadApi.getUpload({ bucketName: 'notice', objectName: file.name }).then(res => {
                console.log(res.data.downloadPath)
                uploadApi.uploadFile({ url: res.data.uploadPath, file: file }).then(data => {
                  callback(res.data.downloadPath, { text: file.name })
                })
              })
            }
            // callback('movie.mp4', { source2: 'alt.ogg', poster: 'https://www.baidu.com/img/bd_logo1.png' })
          }
        },
        //The path where multiple pictures are uploaded and displayed in rich text (empty in case of full path)
        images_upload_base_path: '',
        images_upload_handler: function (blobInfo, succFun, failFun) {
          var file = blobInfo.blob() //Convert to an easy to understand file object
          console.log(file)
          // //First simulate an input for uploading local files
          var input = document.createElement('input')
          input.setAttribute('type', 'file')
          // You can add the accept attribute to input to limit the types of files uploaded
          // For example: input setAttribute('accept', '.jpg,.png')

          uploadApi.getUpload({ bucketName: 'notice', objectName: file.name }).then(res => {
            console.log(res.data.downloadPath)
            uploadApi.uploadFile({ url: res.data.uploadPath, file: file }).then(data => {
              succFun(res.data.downloadPath)
            })
          })

          // callback('http
        },
        toolbar_sticky: true,
        autosave_ask_before_unload: false
      }
    }
  },
  mounted() {
    tinymce.init({})
  },
  methods: {
    echoEditor() {
      this.$emit('echoEditor', this.Html)
    }
  },
  props: {
    tinymceHtml: {
      type: String,
      default: ''
    }
  },
  watch: {
    tinymceHtml: {
      handler(val) {
        this.Html = val
      },
      immediate: true,
      deep: true
    }
  },

  components: { Editor }
}
</script>
 

 

4, Upload pictures

tinymce provides images_ upload_ APIs such as URL allow users to configure parameters related to uploading pictures

However, in order to adapt your own projects without bothering the back end, you still have to use {images_upload_handler to customize an upload method

This method will provide three parameters: blobinfo, success and failure

Where , blobinfo , is an object that contains the information of the uploaded file:

Success , and , failure , are functions that pass an image address to success when the upload is successful and an error message to failure when the upload fails

Post my own upload method and use axios to send requests

handleImgUpload (blobInfo, success, failure) {
  let formdata = new FormData()
  formdata.set('upload_file', blobInfo.blob())
  axios.post('/api/upload', formdata).then(res => {
    success(res.data.data.src)
  }).catch(res => {
    failure('error')
  })
}

Keywords: Javascript Front-end Vue.js

Added by karenruth on Mon, 21 Feb 2022 05:26:16 +0200