200 lines
5.4 KiB
JavaScript
200 lines
5.4 KiB
JavaScript
import './image-uploading.css'
|
||
|
||
export default function (Quill) {
|
||
// const Delta = Quill.import('delta')
|
||
|
||
class ImageExtend {
|
||
constructor(quill, config) {
|
||
this.quill = quill
|
||
this.config = config
|
||
|
||
// 通过监听内容变化判断是否上传图片
|
||
quill.on(Quill.events.TEXT_CHANGE, (delta) => {
|
||
const imageElements = quill.container.querySelectorAll('img[data-local]:not(.uploading)')
|
||
|
||
imageElements.forEach(element => {
|
||
if (element.classList.contains('uploaded')) {
|
||
element.removeAttribute('data-local')
|
||
element.classList.remove('uploaded')
|
||
if(element.classList.length <= 0) {
|
||
element.removeAttribute('class')
|
||
}
|
||
} else {
|
||
this.uploader(element)
|
||
}
|
||
})
|
||
})
|
||
|
||
// 插入粘贴的文件图片
|
||
quill.root.addEventListener('paste', (e) => {
|
||
|
||
const files = Array.from(e.clipboardData.files || []);
|
||
|
||
if (files.length > 0) {
|
||
e.preventDefault();
|
||
setTimeout(() => {
|
||
this.insertFileImage(files[0])
|
||
}, 0)
|
||
}
|
||
}, false)
|
||
|
||
// 插入粘贴的网络图片
|
||
quill.clipboard.addMatcher('IMG', function (node, delta) {
|
||
if (!delta.ops[0].attributes) {
|
||
delta.ops[0].attributes = {}
|
||
}
|
||
|
||
delta.ops[0].attributes['data-local'] = node.src
|
||
|
||
delta.ops[0].attributes = Object.keys(delta.ops[0].attributes).reduce((attributes, key) => {
|
||
if (['data-local', 'alt'].includes(key)) {
|
||
attributes[key] = delta.ops[0].attributes[key]
|
||
}
|
||
|
||
return attributes
|
||
}, {})
|
||
return delta
|
||
});
|
||
}
|
||
|
||
async uploader(element) {
|
||
// 标记元素正在上传
|
||
element.classList.add('uploading')
|
||
|
||
// 检查是否配置了上传方法
|
||
if (typeof this.config.uploadHandler !== 'function') {
|
||
console.warn('没有配置图片上传方法,移除图片:' + element.src)
|
||
Quill.find(element).deleteAt(0)
|
||
return
|
||
}
|
||
|
||
const imageFile = await this.convertImageFile(element.src)
|
||
|
||
// 构建上传元素
|
||
const uploadNode = this.getUploadNode(element.src)
|
||
// 获取位置
|
||
const parent = this.quill.root.parentNode
|
||
|
||
setTimeout(() => {
|
||
|
||
const elementRect = element.getBoundingClientRect()
|
||
const containerRect = parent.getBoundingClientRect()
|
||
|
||
uploadNode.style.left = `${elementRect.left - containerRect.left - 1 + parent.scrollLeft}px`
|
||
uploadNode.style.top = `${elementRect.top - containerRect.top + parent.scrollTop}px`
|
||
uploadNode.style.width = `${elementRect.width}px`
|
||
uploadNode.style.height = `${elementRect.height}px`
|
||
|
||
// 渲染到element上
|
||
parent.appendChild(uploadNode)
|
||
}, 100)
|
||
|
||
// 上传图片 展示进度
|
||
Promise.resolve({
|
||
then: (resolve, reject) => {
|
||
try {
|
||
const handler = this.config.uploadHandler(imageFile, element)
|
||
resolve(handler)
|
||
} catch (e) {
|
||
reject(e)
|
||
}
|
||
}
|
||
}).then((url) => {
|
||
const customData = (element.getAttribute('data-custom') || '').split('&').filter(item => item).reduce((res, item) => {
|
||
const [key, value] = item.split('=')
|
||
res[key] = value
|
||
return res
|
||
}, {})
|
||
|
||
customData.source = url
|
||
|
||
element.src = url
|
||
element.removeAttribute('data-local')
|
||
element.classList.remove('uploading')
|
||
element.setAttribute('data-custom', Object.keys(customData).map(key => `${key}=${customData[key]}`).join('&'))
|
||
|
||
parent.removeChild(uploadNode)
|
||
}).catch((e) => {
|
||
parent.removeChild(uploadNode)
|
||
console.error(e)
|
||
})
|
||
}
|
||
|
||
async convertImageFile(imageUrl) {
|
||
const result = {
|
||
blob: imageUrl,
|
||
ext: 'jpg',
|
||
size: 0,
|
||
}
|
||
|
||
// if (!/^blob:/.test(imageUrl)) {
|
||
const image = await fetch(imageUrl)
|
||
const blob = await image.blob()
|
||
const mimeType = this.getBlobImageMimeType(blob, imageUrl)
|
||
const ext = mimeType.replace('image/', '')
|
||
const filename = `image.${ext}`
|
||
const file = new File([blob], filename, {type: mimeType})
|
||
|
||
result.blob = this.file2blob(file)
|
||
result.ext = ext
|
||
result.size = file.size
|
||
// }
|
||
|
||
return result
|
||
}
|
||
|
||
getBlobImageMimeType(blob, imageUrl) {
|
||
if (blob.type) {
|
||
return blob.type
|
||
}
|
||
if (imageUrl.match(/data:(image\/\w+);base64/)) {
|
||
return imageUrl.match(/data:(image\/\w+);base64/)[1].toLowerCase()
|
||
}
|
||
return 'image/jpeg'
|
||
}
|
||
|
||
getUploadNode(imageUrl) {
|
||
const div = document.createElement('div')
|
||
const img = document.createElement('img')
|
||
|
||
img.src = imageUrl
|
||
img.setAttribute('οndragstart', 'return false')
|
||
div.appendChild(img)
|
||
|
||
div.classList.add('image-uploading')
|
||
|
||
return div
|
||
}
|
||
|
||
file2blob(file) {
|
||
let url
|
||
if (window.createObjectURL !== undefined) {
|
||
url = window.createObjectURL(file);
|
||
} else if (window.URL !== undefined) {
|
||
url = window.URL.createObjectURL(file);
|
||
} else if (window.webkitURL !== undefined) {
|
||
url = window.webkitURL.createObjectURL(file);
|
||
}
|
||
return url
|
||
}
|
||
|
||
insertFileImage(file) {
|
||
const range = this.quill.getSelection(true)
|
||
const url = this.file2blob(file)
|
||
if (range.index > 0 && this.quill.getText(range.index - 1, 1) !== '\n') {
|
||
this.quill.insertText(range.index, '\n', Quill.sources.SILENT)
|
||
range.index += 1
|
||
}
|
||
this.quill.insertEmbed(range.index, 'image', url, Quill.sources.SILENT)
|
||
this.quill.formatText(range.index, 1, 'data-local', url, Quill.sources.SILENT)
|
||
this.quill.insertText(range.index + 1, '\n', Quill.sources.SILENT)
|
||
this.quill.setSelection(range.index + 2, Quill.sources.SILENT)
|
||
this.quill.scrollIntoView()
|
||
}
|
||
}
|
||
|
||
return {
|
||
'modules/ImageExtend': ImageExtend
|
||
}
|
||
}
|