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"; 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 += `
  • ${window.siyuan.languages.nameEmpty}
  • `; validate = false; } if (file.size > protyle.options.upload.max) { errorTip += `
  • ${file.name} ${window.siyuan.languages.over} ${protyle.options.upload.max / 1024 / 1024}M
  • `; 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 += `
  • ${file.name} ${window.siyuan.languages.fileTypeError}
  • `; validate = false; } } if (validate) { uploadFileList.push(file); uploadingStr += `
  • ${filename} ${window.siyuan.languages.uploading}
  • `; } } let msgId; if (errorTip !== "" || uploadingStr !== "") { msgId = showMessage(``, -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 = `"; } 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 succFileText = ""; const keys = Object.keys(response.data.succMap); const avAssets: IAVCellAssetValue[] = []; 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); avAssets.push({ type: Constants.SIYUAN_ASSETS_IMAGE.includes(type) ? "image" : "file", content: path, name: name }); succFileText += 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")) { succFileText += "
    "; } else if (insertBlock) { succFileText += "\n\n"; } else { succFileText += "\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[] = []; 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(succFileText, protyle, insertBlock); }; 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}", `${tip.substring(0, tip.length - 2)}`)); } 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); };