UNPKG

@rtdui/editor

Version:

React rich text editor based on tiptap

181 lines (177 loc) 4.87 kB
'use client'; 'use strict'; var core = require('@tiptap/core'); var react = require('@tiptap/react'); var ImageResizableComponent = require('./ImageResizableComponent.cjs'); 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]; } 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("\u65E0\u6CD5\u52A0\u8F7D\u56FE\u50CF"); }; 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; const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, newWidth, newHeight); canvas.toBlob( (blob) => { const newFile = new File([blob], file.name, { type: file.type }); cb(newFile, newWidth, newHeight); }, file.type, quality ); }; } const inputRegex = /(?:^|\s)(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/; const UploadImageWithResizable = 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: 0.7, HTMLAttributes: {} }; }, // node特性 addAttributes() { return { src: { default: null }, alt: { default: null }, title: { default: null }, width: { default: null }, height: { default: null } }; }, parseHTML() { return [ { tag: "img[src]" // 没有src特性的img标签, 由于没有任何Node可以接收它. 因此会被编辑器忽略. } ]; }, renderHTML({ HTMLAttributes }) { return [ "img", core.mergeAttributes(this.options.HTMLAttributes, HTMLAttributes) ]; }, addNodeView() { return react.ReactNodeViewRenderer(ImageResizableComponent); }, 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 res = await fetch(this.options.url, { method: this.options.method, body: formData }); const result = await res.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; } }; }, // 支持 MarkDown 图片语法输入规则 addInputRules() { return [ core.nodeInputRule({ find: inputRegex, type: this.type, getAttributes: (match) => { const [, , alt, src, title] = match; return { src, alt, title }; } }) ]; } }); exports.UploadImageWithResizable = UploadImageWithResizable; exports.inputRegex = inputRegex; //# sourceMappingURL=uploadImageWithResizable.cjs.map