@rtdui/editor
Version:
React rich text editor based on tiptap
181 lines (177 loc) • 4.87 kB
JavaScript
'use client';
;
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