mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-05-02 14:13:39 +08:00
374 lines
14 KiB
TypeScript
374 lines
14 KiB
TypeScript
import {insertHTML} from "../util/insertHTML";
|
|
import {hideMessage, showMessage} from "../../dialog/message";
|
|
import {Constants} from "../../constants";
|
|
import {destroy} from "../util/destroy";
|
|
import {fetchPost} from "../../util/fetch";
|
|
import {getEditorRange} from "../util/selection";
|
|
import {pathPosix} from "../../util/pathName";
|
|
import {genAssetHTML} from "../../asset/renderAssets";
|
|
import {hasClosestBlock} from "../util/hasClosest";
|
|
import {getContenteditableElement} from "../wysiwyg/getBlock";
|
|
import {getTypeByCellElement, updateCellsValue} from "../render/av/cell";
|
|
import {scrollCenter} from "../../util/highlightById";
|
|
|
|
export class Upload {
|
|
public element: HTMLElement;
|
|
public isUploading: boolean;
|
|
|
|
constructor() {
|
|
this.isUploading = false;
|
|
this.element = document.createElement("div");
|
|
this.element.className = "protyle-upload";
|
|
}
|
|
}
|
|
|
|
const validateFile = (protyle: IProtyle, files: File[]) => {
|
|
const uploadFileList = [];
|
|
let errorTip = "";
|
|
let uploadingStr = "";
|
|
|
|
for (let iMax = files.length, i = 0; i < iMax; i++) {
|
|
const file = files[i];
|
|
let validate = true;
|
|
|
|
if (!file.name) {
|
|
errorTip += `<li>${window.siyuan.languages.nameEmpty}</li>`;
|
|
validate = false;
|
|
}
|
|
|
|
if (file.size > protyle.options.upload.max) {
|
|
errorTip += `<li>${file.name} ${window.siyuan.languages.over} ${protyle.options.upload.max / 1024 / 1024}M</li>`;
|
|
validate = false;
|
|
}
|
|
|
|
const lastIndex = file.name.lastIndexOf(".");
|
|
const fileExt = lastIndex === -1 ? "" : file.name.substr(lastIndex);
|
|
const filename = lastIndex === -1 ? file.name : (protyle.options.upload.filename(file.name.substr(0, lastIndex)) + fileExt);
|
|
|
|
if (protyle.options.upload.accept) {
|
|
const isAccept = protyle.options.upload.accept.split(",").some((item) => {
|
|
const type = item.trim();
|
|
if (type.indexOf(".") === 0) {
|
|
if (fileExt.toLowerCase() === type.toLowerCase()) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (file.type.split("/")[0] === type.split("/")[0]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
|
|
if (!isAccept) {
|
|
errorTip += `<li>${file.name} ${window.siyuan.languages.fileTypeError}</li>`;
|
|
validate = false;
|
|
}
|
|
}
|
|
|
|
if (validate) {
|
|
uploadFileList.push(file);
|
|
uploadingStr += `<li>${filename} ${window.siyuan.languages.uploading}</li>`;
|
|
}
|
|
}
|
|
let msgId;
|
|
if (errorTip !== "" || uploadingStr !== "") {
|
|
msgId = showMessage(`<ul>${errorTip}${uploadingStr}</ul>`, -1);
|
|
}
|
|
|
|
return {files: uploadFileList, msgId};
|
|
};
|
|
|
|
const genUploadedLabel = (responseText: string, protyle: IProtyle) => {
|
|
const response = JSON.parse(responseText);
|
|
let errorTip = "";
|
|
|
|
if (response.code === 1) {
|
|
errorTip = `${response.msg}`;
|
|
}
|
|
|
|
if (response.data.errFiles && response.data.errFiles.length > 0) {
|
|
errorTip = `<ul><li>${errorTip}</li>`;
|
|
response.data.errFiles.forEach((data: string) => {
|
|
const lastIndex = data.lastIndexOf(".");
|
|
const filename = lastIndex === -1 ? data : (protyle.options.upload.filename(data.substr(0, lastIndex)) + data.substr(lastIndex));
|
|
errorTip += `<li>${filename} ${window.siyuan.languages.uploadError}</li>`;
|
|
});
|
|
errorTip += "</ul>";
|
|
}
|
|
|
|
if (errorTip) {
|
|
showMessage(errorTip);
|
|
}
|
|
let insertBlock = true;
|
|
const range = getEditorRange(protyle.wysiwyg.element);
|
|
if (range.toString() === "" && range.startContainer.nodeType === 3 && protyle.toolbar.getCurrentType(range).length > 0) {
|
|
// 防止链接插入其他元素中 https://ld246.com/article/1676003478664
|
|
range.setEndAfter(range.startContainer.parentElement);
|
|
range.collapse(false);
|
|
}
|
|
// https://github.com/siyuan-note/siyuan/issues/7624
|
|
const nodeElement = hasClosestBlock(range.startContainer);
|
|
if (nodeElement) {
|
|
if (nodeElement.classList.contains("table")) {
|
|
insertBlock = false;
|
|
} else {
|
|
const editableElement = getContenteditableElement(nodeElement);
|
|
if (editableElement && editableElement.textContent !== "" && nodeElement.classList.contains("p")) {
|
|
insertBlock = false;
|
|
}
|
|
}
|
|
}
|
|
let successFileText = "";
|
|
const keys = Object.keys(response.data.succMap);
|
|
// 插入多个资源文件时按文件名自然升序排列 Use natural ascending order when inserting multiple assets https://github.com/siyuan-note/siyuan/issues/14643
|
|
keys.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
|
|
const avAssets: IAVCellAssetValue[] = [];
|
|
let hasImage = false;
|
|
keys.forEach((key, index) => {
|
|
const path = response.data.succMap[key];
|
|
const type = pathPosix().extname(key).toLowerCase();
|
|
const filename = protyle.options.upload.filename(key);
|
|
const name = filename.substring(0, filename.length - type.length);
|
|
hasImage = Constants.SIYUAN_ASSETS_IMAGE.includes(type);
|
|
avAssets.push({
|
|
type: Constants.SIYUAN_ASSETS_IMAGE.includes(type) ? "image" : "file",
|
|
content: path,
|
|
name: name
|
|
});
|
|
successFileText += genAssetHTML(type, path, name, filename);
|
|
if (!Constants.SIYUAN_ASSETS_AUDIO.includes(type) && !Constants.SIYUAN_ASSETS_VIDEO.includes(type) &&
|
|
keys.length - 1 !== index) {
|
|
if (nodeElement && nodeElement.classList.contains("table")) {
|
|
successFileText += "<br>";
|
|
} else if (insertBlock) {
|
|
successFileText += "\n\n";
|
|
} else {
|
|
successFileText += "\n";
|
|
}
|
|
}
|
|
});
|
|
|
|
if ((nodeElement && nodeElement.classList.contains("av"))) {
|
|
const cellElements: HTMLElement[] = [];
|
|
nodeElement.querySelectorAll(".av__row--select:not(.av__row--header)").forEach(item => {
|
|
item.querySelectorAll(".av__cell").forEach((cellItem: HTMLElement) => {
|
|
if (getTypeByCellElement(cellItem) === "mAsset") {
|
|
cellElements.push(cellItem);
|
|
}
|
|
});
|
|
});
|
|
if (cellElements.length === 0) {
|
|
protyle.wysiwyg.element.querySelectorAll(".av__cell--active").forEach((item: HTMLElement) => {
|
|
if (getTypeByCellElement(item) === "mAsset") {
|
|
cellElements.push(item);
|
|
}
|
|
});
|
|
}
|
|
if (cellElements.length > 0) {
|
|
updateCellsValue(protyle, nodeElement, avAssets, cellElements);
|
|
document.querySelector(".av__panel")?.remove();
|
|
return;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (document.querySelector(".av__panel")) {
|
|
const cellElements: HTMLElement[] = [document.querySelector('.custom-attr__avvalue[data-type="mAsset"][data-active="true"]')];
|
|
if (!cellElements[0]) {
|
|
cellElements.splice(0, 1);
|
|
protyle.wysiwyg.element.querySelectorAll(".av__cell--active").forEach((item: HTMLElement) => {
|
|
if (getTypeByCellElement(item) === "mAsset") {
|
|
cellElements.push(item);
|
|
}
|
|
});
|
|
}
|
|
if (cellElements.length > 0) {
|
|
const blockElement = hasClosestBlock(cellElements[0]);
|
|
if (blockElement) {
|
|
updateCellsValue(protyle, blockElement, avAssets, cellElements);
|
|
document.querySelector(".av__panel")?.remove();
|
|
return;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
// 避免插入代码块中,其次因为都要独立成块 https://github.com/siyuan-note/siyuan/issues/7607
|
|
insertHTML(successFileText, protyle, insertBlock);
|
|
// 粘贴图片后定位不准确 https://github.com/siyuan-note/siyuan/issues/13336
|
|
setTimeout(() => {
|
|
scrollCenter(protyle, undefined, false, "smooth");
|
|
}, hasImage ? 0 : Constants.TIMEOUT_LOAD);
|
|
};
|
|
|
|
export const uploadLocalFiles = (files: string[], protyle: IProtyle, isUpload: boolean) => {
|
|
const msgId = showMessage(window.siyuan.languages.uploading, 0);
|
|
fetchPost("/api/asset/insertLocalAssets", {
|
|
assetPaths: files,
|
|
isUpload,
|
|
id: protyle.block.rootID
|
|
}, (response) => {
|
|
hideMessage(msgId);
|
|
let tip = "";
|
|
Object.keys(response.data.succMap).forEach(name => {
|
|
if (response.data.succMap[name].startsWith("file:")) {
|
|
tip += name + ", ";
|
|
}
|
|
});
|
|
if (tip) {
|
|
showMessage(window.siyuan.languages.dndFolderTip.replace("${x}", `<b>${tip.substring(0, tip.length - 2)}</b>`));
|
|
}
|
|
genUploadedLabel(JSON.stringify(response), protyle);
|
|
});
|
|
};
|
|
|
|
export const uploadFiles = (protyle: IProtyle, files: FileList | DataTransferItemList | File[], element?: HTMLInputElement, successCB?: (res: string) => void) => {
|
|
// 文档书中点开属性->数据库后的变更操作
|
|
if (!protyle) {
|
|
const formData = new FormData();
|
|
for (let i = 0, iMax = files.length; i < iMax; i++) {
|
|
formData.append("file[]", files[i] as File);
|
|
}
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open("POST", Constants.UPLOAD_ADDRESS);
|
|
xhr.onreadystatechange = () => {
|
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
if (xhr.status === 200) {
|
|
successCB(xhr.responseText);
|
|
} else if (xhr.status === 0) {
|
|
showMessage(window.siyuan.languages.fileTypeError);
|
|
} else {
|
|
showMessage(xhr.responseText);
|
|
}
|
|
if (element) {
|
|
element.value = "";
|
|
}
|
|
}
|
|
};
|
|
xhr.send(formData);
|
|
return;
|
|
}
|
|
// FileList | DataTransferItemList | File[] => File[]
|
|
let fileList = [];
|
|
for (let i = 0; i < files.length; i++) {
|
|
let fileItem = files[i];
|
|
if (fileItem instanceof DataTransferItem) {
|
|
fileItem = fileItem.getAsFile();
|
|
}
|
|
if (0 === fileItem.size && "" === fileItem.type && -1 === fileItem.name.indexOf(".")) {
|
|
// 文件夹
|
|
uploadLocalFiles([fileItem.path], protyle, false);
|
|
} else {
|
|
fileList.push(fileItem);
|
|
}
|
|
}
|
|
|
|
if (protyle.options.upload.handler) {
|
|
const isValidate = protyle.options.upload.handler(fileList);
|
|
if (typeof isValidate === "string") {
|
|
showMessage(isValidate);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!protyle.options.upload.url || !protyle.upload) {
|
|
if (element) {
|
|
element.value = "";
|
|
}
|
|
showMessage("please config: options.upload.url");
|
|
return;
|
|
}
|
|
|
|
if (protyle.options.upload.file) {
|
|
fileList = protyle.options.upload.file(fileList);
|
|
}
|
|
|
|
if (protyle.options.upload.validate) {
|
|
const isValidate = protyle.options.upload.validate(fileList);
|
|
if (typeof isValidate === "string") {
|
|
showMessage(isValidate);
|
|
return;
|
|
}
|
|
}
|
|
const editorElement = protyle.wysiwyg.element;
|
|
|
|
const validateResult = validateFile(protyle, fileList);
|
|
if (validateResult.files.length === 0) {
|
|
if (element) {
|
|
element.value = "";
|
|
}
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
|
|
const extraData = protyle.options.upload.extraData;
|
|
for (const key of Object.keys(extraData)) {
|
|
formData.append(key, extraData[key]);
|
|
}
|
|
|
|
for (let i = 0, iMax = validateResult.files.length; i < iMax; i++) {
|
|
formData.append(protyle.options.upload.fieldName, validateResult.files[i]);
|
|
}
|
|
formData.append("id", protyle.block.rootID);
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open("POST", protyle.options.upload.url);
|
|
if (protyle.options.upload.token) {
|
|
xhr.setRequestHeader("X-Upload-Token", protyle.options.upload.token);
|
|
}
|
|
if (protyle.options.upload.withCredentials) {
|
|
xhr.withCredentials = true;
|
|
}
|
|
|
|
protyle.upload.isUploading = true;
|
|
xhr.onreadystatechange = () => {
|
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
protyle.upload.isUploading = false;
|
|
if (!document.body.contains(protyle.element)) {
|
|
// 网络较慢时,页签已经关闭
|
|
destroy(protyle);
|
|
return;
|
|
}
|
|
if (xhr.status === 200) {
|
|
hideMessage(validateResult.msgId);
|
|
if (protyle.options.upload.success) {
|
|
protyle.options.upload.success(editorElement, xhr.responseText);
|
|
} else if (successCB) {
|
|
successCB(xhr.responseText);
|
|
} else {
|
|
let responseText = xhr.responseText;
|
|
if (protyle.options.upload.format) {
|
|
responseText = protyle.options.upload.format(files as File [], xhr.responseText);
|
|
}
|
|
genUploadedLabel(responseText, protyle);
|
|
}
|
|
} else if (xhr.status === 0) {
|
|
showMessage(window.siyuan.languages.fileTypeError);
|
|
} else {
|
|
if (protyle.options.upload.error) {
|
|
protyle.options.upload.error(xhr.responseText);
|
|
} else {
|
|
showMessage(xhr.responseText);
|
|
}
|
|
}
|
|
if (element) {
|
|
element.value = "";
|
|
}
|
|
protyle.upload.element.style.display = "none";
|
|
}
|
|
};
|
|
xhr.upload.onprogress = (event: ProgressEvent) => {
|
|
if (!event.lengthComputable) {
|
|
return;
|
|
}
|
|
const progress = event.loaded / event.total * 100;
|
|
protyle.upload.element.style.display = "block";
|
|
const progressBar = protyle.upload.element;
|
|
progressBar.style.width = progress + "%";
|
|
};
|
|
xhr.send(formData);
|
|
};
|