quill-image-extend-module.js 9.32 KB
/**
 *@description 观察者模式 全局监听富文本编辑器
 */
export const QuillWatch = {
    watcher: {},  // 登记编辑器信息
    active: null,  // 当前触发的编辑器
    on: function (imageExtendId, ImageExtend) {  // 登记注册使用了ImageEXtend的编辑器
        if (!this.watcher[imageExtendId]) {
            this.watcher[imageExtendId] = ImageExtend
        }
    },
    emit: function (activeId, type = 1) {  // 事件发射触发
        this.active = this.watcher[activeId]
        if (type === 1) {
            imgHandler()
        }
    }
}

/**
 * @description 图片功能拓展: 增加上传 拖动 复制
 */
export class ImageExtend {
    /**
     * @param quill {Quill}富文本实例
     * @param config {Object} options
     * config  keys: action, headers, editForm start end error  size response
     */
    constructor(quill, config = {}) {
        this.id = Math.random()
        this.quill = quill
        this.quill.id = this.id
        this.config = config
        this.file = ''  // 要上传的图片
        this.imgURL = ''  // 图片地址
        quill.root.addEventListener('paste', this.pasteHandle.bind(this), false)
        quill.root.addEventListener('drop', this.dropHandle.bind(this), false)
        quill.root.addEventListener('dropover', function (e) {
            e.preventDefault()
        }, false)
        this.cursorIndex = 0
        QuillWatch.on(this.id, this)
    }

    /**
     * @description 粘贴
     * @param e
     */
    pasteHandle(e) {
        // e.preventDefault()
        QuillWatch.emit(this.quill.id, 0)
        let clipboardData = e.clipboardData
        let i = 0
        let items, item, types

        if (clipboardData) {
            items = clipboardData.items;

            if (!items) {
                return;
            }
            item = items[0];
            types = clipboardData.types || [];

            for (; i < types.length; i++) {
                if (types[i] === 'Files') {
                    item = items[i];
                    break;
                }
            }
            if (item && item.kind === 'file' && item.type.match(/^image\//i)) {
                this.file = item.getAsFile()
                let self = this
                // 如果图片限制大小
                if (self.config.size && self.file.size >= self.config.size * 1024 * 1024) {
                    if (self.config.sizeError) {
                        self.config.sizeError()
                    }
                    return
                }
                if (this.config.action) {
                    this.uploadImg()
                } else {
                    QuillWatch.active.uploading();
                    QuillWatch.active.uploadSuccess();
                    this.toBase64()
                }
            }
        }
    }

    /**
     * 拖拽
     * @param e
     */
    dropHandle(e) {
        QuillWatch.emit(this.quill.id, 0)
        const self = this
        e.preventDefault()
        // 如果图片限制大小
        if (self.config.size && self.file.size >= self.config.size * 1024 * 1024) {
            if (self.config.sizeError) {
                self.config.sizeError()
            }
            return
        }
        self.file = e.dataTransfer.files[0]; // 获取到第一个上传的文件对象
        if (this.config.action) {
            self.uploadImg()
        } else {
            self.toBase64()
        }
    }

    /**
     * @description 将图片转为base4
     */
    toBase64() {
        const self = this
        const reader = new FileReader()
        reader.onload = (e) => {
            // 返回base64
            self.imgURL = e.target.result
            self.insertImg()
        }
        reader.readAsDataURL(self.file)
    }

    /**
     * @description 上传图片到服务器
     */
    uploadImg() {
        const self = this
        let quillLoading = self.quillLoading
        let config = self.config
        // 构造表单
        let formData = new FormData()
        formData.append(config.name, self.file)
        // 自定义修改表单
        if (config.editForm) {
            config.editForm(formData)
        }
        // 创建ajax请求
        let xhr = new XMLHttpRequest()
        xhr.open('post', config.action, true)
        // 如果有设置请求头
        if (config.headers) {
            config.headers(xhr)
        }
        if (config.change) {
            config.change(xhr, formData)
        }
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    //success
                    // let res = JSON.parse(xhr.responseText)
                    let res = xhr.responseText
                    self.imgURL = config.response(res)
                    QuillWatch.active.uploadSuccess()
                    self.insertImg()
                    if (self.config.success) {
                        self.config.success()
                    }
                } else {
                    //error
                    if (self.config.error) {
                        self.config.error()
                    }
                    QuillWatch.active.uploadError()
                }
            }
        }
        // 开始上传数据
        xhr.upload.onloadstart = function (e) {
            QuillWatch.active.uploading()
            // let length = (self.quill.getSelection() || {}).index || self.quill.getLength()
            // self.quill.insertText(length, '[uploading...]', { 'color': 'red'}, true)
            if (config.start) {
                config.start()
            }
        }
        // 上传过程
        xhr.upload.onprogress = function (e) {
            let complete = (e.loaded / e.total * 100 | 0) + '%'
            QuillWatch.active.progress(complete)
        }
        // 当发生网络异常的时候会触发,如果上传数据的过程还未结束
        xhr.upload.onerror = function (e) {
            QuillWatch.active.uploadError()
            if (config.error) {
                config.error()
            }
        }
        // 上传数据完成(成功或者失败)时会触发
        xhr.upload.onloadend = function (e) {
            if (config.end) {
                config.end()
            }
        }
        xhr.send(formData)
    }

    /**
     * @description 往富文本编辑器插入图片
     */
    insertImg() {
        const self = QuillWatch.active
        if (!this.config.timeline) {
            self.quill.insertEmbed(QuillWatch.active.cursorIndex, 'image', self.imgURL)
            self.quill.update()
        }
        // self.quill.blur()
        self.quill.setSelection(self.cursorIndex);
    }

    /**
     * @description 显示上传的进度
     */
    progress(pro) {
        pro = '[' + 'uploading' + pro + ']'
        QuillWatch.active.quill.root.innerHTML
            = QuillWatch.active.quill.root.innerHTML.replace(/\[uploading.*?\]/, pro)
    }

    /**
     * 开始上传
     */
    uploading() {
        let length = (QuillWatch.active.quill.getSelection() || {}).index || QuillWatch.active.quill.getLength()
        QuillWatch.active.cursorIndex = length
        if (this.config.timeline) {
            QuillWatch.active.quill.insertText(QuillWatch.active.cursorIndex - 1, '[uploading...]', {'color': 'red'}, true)
        } else {
            QuillWatch.active.quill.insertText(QuillWatch.active.cursorIndex, '[uploading...]', {'color': 'red'}, true)
        }
    }

    /**
     * 上传失败
     */
    uploadError() {
        QuillWatch.active.quill.root.innerHTML
            = QuillWatch.active.quill.root.innerHTML.replace(/\[uploading.*?\]/, '[upload error]')
    }

    uploadSuccess() {
        QuillWatch.active.quill.root.innerHTML
            = QuillWatch.active.quill.root.innerHTML.replace(/\[uploading.*?\]/, '')
    }
}

/**
 * @description 点击图片上传
 */
export function imgHandler() {
    let fileInput = document.querySelector('.quill-image-input');
    if (fileInput === null) {
        fileInput = document.createElement('input');
        fileInput.setAttribute('type', 'file');
        fileInput.classList.add('quill-image-input');
        fileInput.style.display = 'none'
        // 监听选择文件
        fileInput.addEventListener('change', function () {
            let self = QuillWatch.active
            self.file = fileInput.files[0]
            fileInput.value = ''
            // 如果图片限制大小
            if (self.config.size && self.file.size >= self.config.size * 1024 * 1024) {
                if (self.config.sizeError) {
                    self.config.sizeError()
                }
                return
            }
            if (self.config.action) {
                self.uploadImg()
            } else {
                self.toBase64()
            }
        })
        document.body.appendChild(fileInput);
    }
    fileInput.click();
}

/**
 *@description 全部工具栏
 */
export const container = [
    ['bold', 'italic', 'underline', 'strike'],
    ['blockquote', 'code-block'],
    [{'header': 1}, {'header': 2}],
    [{'list': 'ordered'}, {'list': 'bullet'}],
    [{'script': 'sub'}, {'script': 'super'}],
    [{'indent': '-1'}, {'indent': '+1'}],
    [{'direction': 'rtl'}],
    [{'size': ['small', false, 'large', 'huge']}],
    [{'header': [1, 2, 3, 4, 5, 6, false]}],
    [{'color': []}, {'background': []}],
    [{'font': []}],
    [{'align': []}],
    ['clean'],
    ['link', 'image', 'video']
]