UNPKG

@rtdui/editor

Version:

React rich text editor based on tiptap

155 lines (152 loc) 4.18 kB
const require_ImageResizableComponent = require('./ImageResizableComponent.cjs'); let _tiptap_react = require("@tiptap/react"); let _tiptap_core = require("@tiptap/core"); //#region packages/editor/src/RichTextEditor/tiptap_extensions/extension-image-upload/uploadImageWithResizable.ts /** * 根据约束条件计算并返回新的宽度和高度元组 * @param img * @param maxWidth * @param maxHeight * @returns */ function calculateSize(img, maxWidth, maxHeight) { let width = img.naturalWidth; let height = img.naturalHeight; if (width > height) { if (width > maxWidth) { height = Math.round(height * maxWidth / width); width = maxWidth; } } else if (height > maxHeight) { width = Math.round(width * maxHeight / height); height = maxHeight; } return [width, height]; } /** * 压缩图像尺寸 * @param file 原始文件 * @param options 扩展配置选项 */ function processImage(file, options, cb) { const { maxWidth, maxHeight, quality } = options; const fileObjectURL = URL.createObjectURL(file); const img = new Image(); img.src = fileObjectURL; img.onerror = () => { URL.revokeObjectURL(fileObjectURL); console.log("无法加载图像"); }; img.onload = () => { URL.revokeObjectURL(fileObjectURL); const [newWidth, newHeight] = calculateSize(img, maxWidth, maxHeight); if (img.width === newWidth && img.height === newHeight) { cb(file, newWidth, newHeight); return; } const canvas = document.createElement("canvas"); canvas.width = newWidth; canvas.height = newHeight; canvas.getContext("2d").drawImage(img, 0, 0, newWidth, newHeight); canvas.toBlob((blob) => { cb(new File([blob], file.name, { type: file.type }), newWidth, newHeight); }, file.type, quality); }; } const inputRegex = /(?:^|\s)(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/; const UploadImageWithResizable = _tiptap_core.Node.create({ name: "image", inline() { return this.options.inline; }, group() { return this.options.inline ? "inline" : "block"; }, draggable: true, addOptions() { return { inline: false, resizable: true, url: "", method: "post", maxWidth: 320, maxHeight: 180, quality: .7, HTMLAttributes: {} }; }, addAttributes() { return { src: { default: null }, alt: { default: null }, title: { default: null }, width: { default: null }, height: { default: null } }; }, parseHTML() { return [{ tag: "img[src]" }]; }, renderHTML({ HTMLAttributes }) { return ["img", (0, _tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes)]; }, addNodeView() { return (0, _tiptap_react.ReactNodeViewRenderer)(require_ImageResizableComponent.default); }, addCommands() { return { setImage: (options) => ({ commands }) => { return commands.insertContent({ type: this.name, attrs: options }); }, uploadImage: (options) => ({ chain }) => { const input = document.createElement("input"); input.type = "file"; input.accept = "image/*"; input.onchange = async (ev) => { const file = ev.target.files[0]; if (file) processImage(file, this.options, async (newFile, newWidth, newHeight) => { const formData = new FormData(); formData.append("upload", newFile); if (!this.options.url) throw new Error("no configure 'url' for UploadImage extension"); const result = await (await fetch(this.options.url, { method: this.options.method, body: formData })).json(); this.editor.chain().focus().insertContent({ type: this.name, attrs: { src: result.imageUrl, alt: options.alt ?? newFile.name, title: options.title ?? newFile.name, width: newWidth, height: newHeight } }).run(); }); }; input.click(); return true; } }; }, addInputRules() { return [(0, _tiptap_core.nodeInputRule)({ find: inputRegex, type: this.type, getAttributes: (match) => { const [, , alt, src, title] = match; return { src, alt, title }; } })]; } }); //#endregion exports.UploadImageWithResizable = UploadImageWithResizable; exports.inputRegex = inputRegex;