UNPKG

quill-uploader

Version:

Quill custom Uploader, supports images, videos, and attachments, with customizable loading indicators.

318 lines (317 loc) 13.7 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import Loading from './formats/loading'; import CustomLink from './formats/custom-link'; import { dataURLtoFile, fileToBase64, getVideoInfo, randomString } from './util'; import _Quill, { Delta } from 'quill'; const Quill = window.Quill || _Quill; class CustomUploader { static findType(mime, accepts = CustomUploader.Accepts) { const types = Object.keys(accepts); if (types.includes(mime)) return mime; return types.find((k) => accepts[k].includes(mime)) || 'attachment'; } static register() { Quill.register(Loading, true); Quill.register(CustomLink, true); } constructor(quill, options) { this.quill = quill; this.quill.customUploader = this; this.options = Object.assign({ handler: null, upload: null, accepts: this.constructor.Accepts, imagePreload: true, focusOnDone: false }, options); // 更新uploader的mime配置,使其可以拖拽上传 const mimes = Object.keys(this.options.accepts || {}).reduce((m, key) => m.concat(this.options.accepts[key]), []); // @ts-ignore if (this.quill.uploader && this.quill.uploader.options) { // @ts-ignore this.quill.uploader.options.mimetypes = mimes; } if (typeof this.options.upload !== 'function') { // eslint-disable-next-line no-console console.error('[Missing config] upload function that returns a promise is required'); } const toolbar = this.quill.getModule('toolbar'); if (toolbar) { Object.keys(this.options.accepts || {}).forEach((key) => { toolbar.addHandler(key, () => this.pickFile(key)); }); } this.clipboardInit(); } clipboardInit() { let pasteFiles = []; let pasteTimeout; const uploadPasteFiles = () => { pasteFiles.forEach((file) => { const placeholderRange = this.getLoadingDomRange(file.name); this.uploadFiles(placeholderRange, [file]); }); pasteFiles = []; }; const pasteUploadHandler = (file) => { pasteFiles.push(file); if (pasteTimeout) window.clearTimeout(pasteTimeout); pasteTimeout = window.setTimeout(uploadPasteFiles, 100); }; // 粘贴base64图片 this.quill.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => { const tag = node.nodeName.toLowerCase(); // 上传base64图片 if (tag === 'img' && node.src.startsWith('data:')) { const imgFile = dataURLtoFile(node.src, ''); imgFile.base64 = node.src; const placeholder = new Delta().insert({ [Loading.blotName]: { name: imgFile.name, url: imgFile.base64, key: imgFile.name } }); pasteUploadHandler(imgFile); return placeholder; } return delta; }); } pickFile(type) { var _a; let fileInput = this.quill.container.querySelector('input.ql-uploader[type=file]'); if (fileInput == null) { fileInput = document.createElement('input'); fileInput.setAttribute('type', 'file'); fileInput.classList.add('ql-uploader'); fileInput.setAttribute('style', 'visibility:hidden'); fileInput.addEventListener('change', () => { const range = this.quill.getSelection(true); const files = fileInput.files; if (files == null || files.length === 0) return; this.uploadFiles(range, files); fileInput.value = ''; }); this.quill.container.appendChild(fileInput); } const accept = ((_a = (this.options.accepts && this.options.accepts[type])) === null || _a === void 0 ? void 0 : _a.join(', ')) || 'image/*'; fileInput.setAttribute('accept', accept); fileInput.click(); } insertPlaceholder(range, url) { const i = range.index; range.index++; this.quill.history.ignoreChange = true; if (range.length) { this.quill.deleteText(i, range.length, 'api'); } const res = this.quill.insertEmbed(i, Loading.blotName, url, 'api'); this.quill.history.ignoreChange = false; return res; } insertFilePlaceholder(range, file) { const type = this.constructor.findType(file.type); if (!file.key) file.key = randomString(); const key = file.key; const placeholder = this.getLoadingDomRange(key); if (placeholder.delta) return Promise.resolve(placeholder.delta); return new Promise((resolve) => { var _a, _b, _c, _d; if (type === 'attachment') { // 附件,直接插入文本 resolve(this.insertPlaceholder(range, { label: file.name, url: this.constructor.DefaultAttachmentPlaceholder, key })); } else if (file.thumb || file.base64) { // 自定义处理的文件,thumb作为显示的缩略图 resolve(this.insertPlaceholder(range, { name: file.name, url: file.thumb || file.base64, key })); } else if ((_b = (_a = this.options.accepts) === null || _a === void 0 ? void 0 : _a.image) === null || _b === void 0 ? void 0 : _b.includes(file.type)) { // 图片,读取自身base64显示 fileToBase64(file).then((base64) => { resolve(this.insertPlaceholder(range, { name: file.name, url: base64, key })); }); } else if ((_d = (_c = this.options.accepts) === null || _c === void 0 ? void 0 : _c.video) === null || _d === void 0 ? void 0 : _d.includes(file.type)) { // 读取视频信息 getVideoInfo(file).then((videoInfo) => { resolve(this.insertPlaceholder(range, { name: file.name, url: videoInfo.thumb, key })); }); } else { // 其他文件,插入默认占位符 resolve(this.insertPlaceholder(range, { url: this.constructor.DefaultFilePlaceholder, key })); } }); } // The length of the insert delta from insertBase64Image can vary depending on what part of the line the insert occurs calculatePlaceholderInsertLength(delta) { return delta.ops.reduce((accumulator, deltaOperation) => { if (Object.prototype.hasOwnProperty.call(deltaOperation, 'insert')) accumulator++; return accumulator; }, 0); } getLoadingDomRange(key) { const loadingEl = this.quill.container.querySelector(`.ql-uploading[data-key="${key}"]`); if (!loadingEl) return Object.assign({ index: 0, length: 0 }, this.quill.getSelection()); const blot = Quill.find(loadingEl); const index = this.quill.getIndex(blot); const delta = this.quill.getContents(index, 1); const lengthToDelete = this.calculatePlaceholderInsertLength(delta); return { blot, delta, index, length: lengthToDelete }; } loadImage(res) { return new Promise((resolve) => { var _a; const info = this.getLoadingDomRange(res.key); const img = ((_a = info.blot) === null || _a === void 0 ? void 0 : _a.domNode).querySelector('img'); if (!img) { resolve(info); return; } img.addEventListener('load', () => resolve(info)); img.src = res.url; }); } insertToEditor(res) { return __awaiter(this, void 0, void 0, function* () { const type = this.constructor.findType(res.type); // 图片等加载完之后再插入编辑器 if (type === 'image' && this.options.imagePreload) { yield this.loadImage(res); } const info = this.getLoadingDomRange(res.key); let len = info.length; // 删除占位符 this.quill.history.ignoreChange = true; this.quill.deleteText(info.index, len, 'api'); this.quill.history.ignoreChange = false; // 插入内容 if (type === 'attachment') { len = res.name.length + 1; this.quill.insertText(info.index, res.name, { link: Object.assign({ key: res.key, download: res.name, url: res.url, ext: res.name.slice(res.name.lastIndexOf('.') + 1) }, res.delta) }, 'user'); this.quill.insertText(info.index + len - 1, ' ', { link: null }, 'user'); } else { this.quill.insertEmbed(info.index, type, res.delta || res.url, 'user'); } this.quill.setSelection(info.index + len, 'silent'); return Object.assign(Object.assign({}, info), { length: len }); }); } removePlaceholder(res) { this.quill.history.ignoreChange = true; const info = this.getLoadingDomRange(res.key); this.quill.deleteText(info.index, info.length, 'api'); this.quill.history.ignoreChange = false; } uploadFiles(range, filesArg) { return __awaiter(this, void 0, void 0, function* () { let files = Array.from(filesArg); if (this.options.handler) files = yield this.options.handler(files); files = files.filter((f) => f && f.size > 0); if (!files.length) return; this.quill.emitter.emit(this.constructor.Events.start, files); yield Promise.all(files.map((file) => this.insertFilePlaceholder(range, file))); let lastRange; const results = yield this.options.upload(range, files, (error, result) => __awaiter(this, void 0, void 0, function* () { if (!error) { lastRange = yield this.insertToEditor(result); } else { this.removePlaceholder(result); } })); if (!results) { this.done(lastRange, results); return; } yield Promise.allSettled(results.map((result) => __awaiter(this, void 0, void 0, function* () { if (result.url) { lastRange = yield this.insertToEditor(result); } else { this.removePlaceholder(result); } }))); this.done(lastRange, results); }); } done(range, results) { if (range && this.options.focusOnDone) { this.quill.setSelection(range.index + range.length, 'user'); setTimeout(() => this.quill.focus(), 16); } this.quill.emitter.emit(this.constructor.Events.done, results); } } CustomUploader.DefaultFilePlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAC0AQMAAADfKmdSAAAAA1BMVEX19fWwaZ+KAAAAHklEQVRYw+3BMQEAAADCIPuntsYOYAAAAAAAAABhBxzUAAEYBjd2AAAAAElFTkSuQmCC'; CustomUploader.DefaultAttachmentPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYAQMAAADaua+7AAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAAtJREFUCNdjoDEAAABgAAGVQESKAAAAAElFTkSuQmCC'; CustomUploader.Accepts = { image: ['image/jpeg', 'image/png', 'image/gif'], video: ['video/mp4'], attachment: [ 'text/plain', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/zip', 'application/x-zip-compressed', 'application/vnd.rar', 'application/x-7z-compressed' ] }; CustomUploader.Events = { start: 'uploader-start', done: 'uploader-done' }; export default CustomUploader; if (window.Quill) { ; window.Quill.register('modules/customUploader', CustomUploader); }