@rtdui/editor
Version:
React rich text editor based on tiptap
155 lines (152 loc) • 4.18 kB
JavaScript
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;