dynamicLoadScript
let callbacks = []
function loadedTinymce() {
return window.tinymce
}
const dynamicLoadScript = (src, callback) = > {
const existingScript = document.getElementById(src)
const cb = callback || function() {}
if(! existingScript) {const script = document.createElement('script')
script.src = src // src url for the third-party library being loaded.
script.id = src
document.body.appendChild(script)
callbacks.push(cb)
const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
onEnd(script)
}
if (existingScript && cb) {
if (loadedTinymce()) {
cb(null, existingScript)
} else {
callbacks.push(cb)
}
}
function stdOnEnd(script) {
script.onload = function() {
// this.onload = null here is necessary
// because even IE9 works not like others
this.onerror = this.onload = null
for (const cb of callbacks) {
cb(null, script)
}
callbacks = null
}
script.onerror = function() {
this.onerror = this.onload = null
cb(new Error('Failed to load ' + src), script)
}
}
function ieOnEnd(script) {
script.onreadystatechange = function() {
if (this.readyState ! = ='complete' && this.readyState ! = ='loaded') return
this.onreadystatechange = null
for (const cb of callbacks) {
cb(null, script) // there is no way to catch loading errors in IE8
}
callbacks = null}}}export default dynamicLoadScript
Copy the code
plugins
const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']
export default plugins
Copy the code
toolbar
const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample'.'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
export default toolbar
Copy the code
//EditorImage
<template>
<div class="upload-container">
<el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">
upload
</el-button>
<el-dialog :visible.sync="dialogVisible">
<el-upload
:multiple="true"
:file-list="fileList"
:show-file-list="true"
:on-remove="handleRemove"
:on-success="handleSuccess"
:before-upload="beforeUpload"
class="editor-slide-upload"
action="https://httpbin.org/post"
list-type="picture-card"
>
<el-button size="small" type="primary">
Click upload
</el-button>
</el-upload>
<el-button @click="dialogVisible = false">
Cancel
</el-button>
<el-button type="primary" @click="handleSubmit">
Confirm
</el-button>
</el-dialog>
</div>
</template>
<script>
// import { getToken } from 'api/qiniu'
export default {
name: 'EditorSlideUpload'.props: {
color: {
type: String.default: '#1890ff'}},data() {
return {
dialogVisible: false.listObj: {},
fileList: []}},methods: {
checkAllSuccess() {
return Object.keys(this.listObj).every(item= > this.listObj[item].hasSuccess)
},
handleSubmit() {
const arr = Object.keys(this.listObj).map(v= > this.listObj[v])
if (!this.checkAllSuccess()) {
this.$message('Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again! ')
return
}
this.$emit('successCBK', arr)
this.listObj = {}
this.fileList = []
this.dialogVisible = false
},
handleSuccess(response, file) {
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
this.listObj[objKeyArr[i]].url = response.files.file
this.listObj[objKeyArr[i]].hasSuccess = true
return}}},handleRemove(file) {
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
delete this.listObj[objKeyArr[i]]
return}}},beforeUpload(file) {
const _self = this
const _URL = window.URL || window.webkitURL
const fileName = file.uid
this.listObj[fileName] = {}
return new Promise((resolve, reject) = > {
const img = new Image()
img.src = _URL.createObjectURL(file)
img.onload = function() {
_self.listObj[fileName] = { hasSuccess: false.uid: file.uid, width: this.width, height: this.height }
}
resolve(true)})}}}</script>
<style lang="scss" scoped>
.editor-slide-upload {
margin-bottom: 20px;
::v-deep .el-upload--picture-card {
width: 100%; }}</style>
Copy the code
//Tinymce
<template>
<div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
<textarea :id="tinymceId" class="tinymce-textarea" />
<div class="editor-custom-btn-container">
<editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
</div>
</div>
</template>
<script>
import editorImage from './components/EditorImage'
import plugins from './plugins'
import toolbar from './toolbar'
import load from './dynamicLoadScript'
const tinymceCDN = 'https://cdn.jsdelivr.net/npm/[email protected]/tinymce.min.js'
export default {
name: 'Tinymce'.components: { editorImage },
props: {
id: {
type: String.default: function() {
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + ' ')}},value: {
type: String.default: ' '
},
toolbar: {
type: Array.required: false.default() {
return[]}},menubar: {
type: String.default: 'file edit insert view format table'
},
height: {
type: [Number.String].required: false.default: 360
},
width: {
type: [Number.String].required: false.default: 'auto'}},data() {
return {
hasChange: false.hasInit: false.tinymceId: this.id,
fullscreen: false.languageTypeList: {
'en': 'en'.'zh': 'zh_CN'.'es': 'es_MX'.'ja': 'ja'}}},computed: {
language() {
return this.languageTypeList[this.$store.getters.language]
},
containerWidth() {
const width = this.width
if (/^[\d]+(\.[\d]+)? $/.test(width)) { // matches `100`, `'100'`
return `${width}px`
}
return width
}
},
watch: {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() = >
window.tinymce.get(this.tinymceId).setContent(val || ' '}})),language() {
this.destroyTinymce()
this.$nextTick(() = > this.initTinymce())
}
},
mounted() {
this.init()
},
activated() {
if (window.tinymce) {
this.initTinymce()
}
},
deactivated() {
this.destroyTinymce()
},
destroyed() {
this.destroyTinymce()
},
methods: {
init() {
// dynamic load tinymce from cdn
load(tinymceCDN, (err) = > {
if (err) {
this.$message.error(err.message)
return
}
this.initTinymce()
})
},
initTinymce() {
const _this = this
window.tinymce.init({
language: this.language,
selector: ` #The ${this.tinymceId}`.height: this.height,
body_class: 'panel-body '.object_resizing: false.toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
menubar: this.menubar,
plugins: plugins,
end_container_on_empty_block: true.powerpaste_word_import: 'clean'.code_dialog_height: 450.code_dialog_width: 1000.advlist_bullet_styles: 'square'.advlist_number_styles: 'default'.imagetools_cors_hosts: ['www.tinymce.com'.'codepen.io'].default_link_target: '_blank'.link_title: false.nonbreaking_force_tab: true.// inserting nonbreaking space need Nonbreaking Space Plugin
init_instance_callback: editor= > {
if (_this.value) {
editor.setContent(_this.value)
}
_this.hasInit = true
editor.on('NodeChange Change KeyUp SetContent'.() = > {
this.hasChange = true
this.$emit('input', editor.getContent())
})
},
setup(editor) {
editor.on('FullscreenStateChanged'.(e) = > {
_this.fullscreen = e.state
})
},
// it will try to keep these URLs intact
// https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
// https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
convert_urls: false
// integrate qiniu upload
// images_dataimg_filter(img) {
// setTimeout(() => {
// const $image = $(img);
// $image.removeAttr('width');
// $image.removeAttr('height');
// if ($image[0].height && $image[0].width) {
// $image.attr('data-wscntype', 'image');
// $image.attr('data-wscnh', $image[0].height);
// $image.attr('data-wscnw', $image[0].width);
// $image.addClass('wscnph');
/ /}
/ /}, 0);
// return img
// },
// images_upload_handler(blobInfo, success, failure, progress) {
// progress(0);
// const token = _this.$store.getters.token;
// getToken(token).then(response => {
// const url = response.data.qiniu_url;
// const formData = new FormData();
// formData.append('token', response.data.qiniu_token);
// formData.append('key', response.data.qiniu_key);
// formData.append('file', blobInfo.blob(), url);
// upload(formData).then(() => {
// success(url);
// progress(100);
/ /})
// }).catch(err => {
// failure(' unknown problem, refresh page, or contact programmer ')
// console.log(err);
/ /});
// },})},destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId)
if (this.fullscreen) {
tinymce.execCommand('mceFullScreen')}if (tinymce) {
tinymce.destroy()
}
},
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value)
},
getContent() {
window.tinymce.get(this.tinymceId).getContent()
},
imageSuccessCBK(arr) {
const _this = this
arr.forEach(v= > {
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)})}}}</script>
<style lang="scss" scoped>
.tinymce-container {
position: relative;
line-height: normal;
}
.tinymce-container {
::v-deep {
.mce-fullscreen {
z-index: 10000; }}}.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
.editor-custom-btn-container {
position: absolute;
right: 4px;
top: 4px;
/*z-index: 2005; * /
}
.fullscreen .editor-custom-btn-container {
z-index: 10000;
position: fixed;
}
.editor-upload-btn {
display: inline-block;
}
</style>
Copy the code
<template>
<div class="components-container">
<aside>
{{ $t('components.tinymceTips') }}
<a target="_blank" class="link-type" href=""> {{ $t('components.documentation') }}</a>
</aside>
<div>
<tinymce v-model="content" :height="300" />
</div>
<div class="editor-content" v-html="content" />
</div>
</template>
<script></script>
<style scoped>
.editor-content{
margin-top: 20px;
}
</style>
Copy the code