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') }) }