Using tinymce in vue3

Background: There are scenarios where a rich text editor needs to be used in the project. There are currently many rich text editors, and the tinymce rich text editor is used in this case;

Solution: There are two methods used in vue3, but each has advantages and disadvantages:

① Use the official method to introduce, advantages: simple operation; disadvantages: use api-key to request the js file in the cloud, the loading speed is slow, which affects the user experience;

②Using local files to import, advantages: fast loading, can be repackaged into editor components; disadvantages: the introduction of styles and plug-ins is more troublesome, and some plug-in customization methods are easy to step on;

Directory

1. Tinymce official provision method:

1. Install tinymce-vue:

2. Apply for an api-key:

3. Use in the component:

2. Use local files to import:

1. Install tinymce and tinymce-vue

2. Process the file path:

3. Secondarily package the editor component and create a new TinymceEditor.vue:

4. Use in components:

5. Pit:


one. tinymce official provision method:

In this case, the method of tinymce version 5 (tinymce-vue version 4) is used

Official documentation (version 5): Vue integration | Docs | TinyMCE

1. Install tinymce-vue:

npm install --save "@tinymce/tinymce-vue@^4"

//or

yarn add "@tinymce/tinymce-vue@^4"

2. Apply for api-key:

3. Use in component:

<template>
    <div>
        <Editor :init="myTinyInit" api-key="A P I key you applied for" v-model="content" />
    </div>
</template>

<script setup>
import {defineExpose, reactive, ref, defineEmits} from 'vue'
import Editor from '@tinymce/tinymce-vue';

const myTinyInit = ref({
    width:'100%', //Editor width, in tinymce version 6, toolbar_mode must be set to wrap to take effect
    height:500, //Editor height
    branding: false, //Do not display logo
    menubar: false,
    resize: false,
    toolbar_mode: 'wrap',
    skeletonScreen: true, //The editor is lazy loaded, but it does not seem to take effect after setting
    placeholder: 'Please enter notification content',
    plugins: ['lists link image table paste help wordcount'], //Required plugins, you can refer to the official documentation to import the plugins you need
    toolbar: 'undo redo | formatselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | bullet numlist outdent indent |
    fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 26px 36px 48px 56px',
    content_style: 'body, p{font-size: 14px;color:#606266}', //The text style in the editor can also be imported after customizing the content.css file
    //Custom image upload method (tinymce version 5)
    images_upload_handler: async (blobInfo, success, failure) => {
        var formData = new FormData();
        formData.append('file', blobInfo.blob(), blobInfo.filename());
        const res = await tinymceImageUpload(formData); //tinymceImageUpload is an encapsulated image upload method, which can be replaced by your own method
        if (res. code === 1) {
            success(`/image_manipulation/${res.data.filePath}`); //After success, return the image path, where /image_manipulation/ is the prefix of the image path, the specific situation depends on the result returned by the backend
        } else {
            failure('Image upload failed due to a XHR Transport error. Code: ' + res.msg);
        }
    },
})

</script>

2. Use local file to import:

In this case, the method of tinymce version 6 (tinymce-vue version 5) is used

Official documentation: Using the TinyMCE package with the Vue.js framework | TinyMCE Documentation

1. Install tinymce and tinymce-vue

npm install tinymce
npm install tinymce-vue
//or
yarn add tinymce
yarn add tinymce-vue

2. Processing file path:

① Create a new tinymce folder under the public folder (if it is vue2, it will be in the static folder);

②Language pack: The default interface is English. If you need to change to Chinese interface, you need to download a Chinese language pack, create a new langs folder under the tinymce folder, and place the downloaded file in the langs folder;

③Skins style: Create a new tinymce folder under the public folder (if it is vue2, it will be in the static folder), find skins in node_modules, and copy the entire folder to /public/tinymce/;

3. Secondary packaging of the editor component, creating a new TinymceEditor.vue:

<template>
    <div>
        <Editor v-model="content" :init="myTinyInit"></Editor>
    </div>
</template>

<script setup>
import {computed, defineEmits, defineProps, onMounted, reactive, ref, watch} from 'vue'
import tinymce from "tinymce/tinymce";
import Editor from "@tinymce/tinymce-vue";
import "tinymce/icons/default/icons";
import "tinymce/themes/silver";
import "tinymce/models/dom/model";

//Introduce plugins as needed
import "tinymce/plugins/image";
import "tinymce/plugins/table";
import "tinymce/plugins/lists";
import "tinymce/plugins/link";
import "tinymce/plugins/help";
import "tinymce/plugins/wordcount";

import axios from "axios";
import {useStore} from "vuex";
import {ElNotification} from "element-plus";

const props = defineProps({
    modelValue: {
        type: String,
        default: ""
    },
    plugins: {
        type: [String, Array],
        default:'lists link image table help wordcount',
    },
    toolbar: {
        type: [String, Array],
        default: 'undo redo | formatselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | bullet numlist outdent indent | table image | help',
    }
});
const emit = defineEmits(['input']);

const store = useStore();
const myTinyInit = reactive({
    width: '100%',
    height: 500,
    branding: false,
    menubar: false,
    resize: false,
    skin_url: "/tinymce/skins/ui/oxide", //manually introduced
    content_css: '/tinymce/skins/content/default/content.css', //Manually introduced
    toolbar_mode: "wrap",
    plugins: props.plugins,
    toolbar: props.toolbar,
    //Image upload method (Note!!: Use resolve in the promise object to return the image path, otherwise an error will be reported)
    images_upload_handler: (blobInfo) => new Promise((resolve, reject) => {
        let formData = new FormData();
        formData.append('file', blobInfo.blob(), blobInfo.filename());
        axios.post(`/api/backend/upload`, formData, {
            headers: ({
                'Content-Type': 'multipart/form-data',
                'Authorization': "Bearer " + store.state.user.accessToken
            })
        }).then(res => {
            if (res. data. code === 1) {
                resolve(`/image_manipulation${res.data.data.filePath}`)
            } else {
                ElNotification.warning(res.data.msg)
            }
        }).catch(error => {
            reject(error);
        })
    }),
});


const initContent = computed(() => {
    return props. modelValue
});

onMounted(() => {
    tinymce.init({});
})

const content = ref();
watch(initContent, (newVal) => {
    content.value = newVal;
}, {deep: true, immediate: true});

watch(content, (newVal) => {
    emit("input", newVal);
}, {deep: true});
</script>

<style scoped lang="scss">
</style>

4. Use in components:

<template>
   <div>
      <editor v-model="notificationForm. content" @input="(val)=> {notificationForm. content=val}"></editor>
      //The @input method needs to be defined by itself, otherwise the parent component may not get the value of the editor
   </div>
</template>

<script setup>
import {reactive} from 'vue'
import Editor from '@/components/backend/TinymceEditor.vue';
const notificationForm = reactive({
    content: '',
})
</script>

5. Pit:

①Introduction of files: In addition to the need to store language packs and skins locally, errors may occur when importing css or plugin files, just import the corresponding files according to the error status;

②Custom method: You cannot use the packaged axios request, you must use the promise object (see the definition of images_upload_handler method for details);