diff --git a/app/src/asset/anno.ts b/app/src/asset/anno.ts index cad1fa580..b70afeaf0 100644 --- a/app/src/asset/anno.ts +++ b/app/src/asset/anno.ts @@ -8,6 +8,8 @@ import {Constants} from "../constants"; import {Dialog} from "../dialog"; import {showMessage} from "../dialog/message"; import {isMobile} from "../util/functions"; +import {confirmDialog} from "../dialog/confirmDialog"; +import prettyBytes from "pretty-bytes"; export const initAnno = (element: HTMLElement, pdf: any) => { getConfig(pdf); @@ -738,13 +740,19 @@ const copyAnno = (idPath: string, fileName: string, pdf: any) => { fetch(imageDataURL).then((response) => { return response.blob(); }).then((blob) => { - const formData = new FormData(); const imageName = content + ".png"; - formData.append("file[]", blob, imageName); - formData.append("skipIfDuplicated", "true"); - fetchPost(Constants.UPLOAD_ADDRESS, formData, (response) => { - writeText(`<<${idPath} "${content}">> + let msg = ""; + if (Constants.SIZE_UPLOAD_TIP_SIZE <= blob.size) { + msg = window.siyuan.languages.uploadFileTooLarge.replace("${x}", imageName).replace("${y}", prettyBytes(blob.size, {binary: true})); + } + confirmDialog(msg ? window.siyuan.languages.upload : "", msg, () => { + const formData = new FormData(); + formData.append("file[]", blob, imageName); + formData.append("skipIfDuplicated", "true"); + fetchPost(Constants.UPLOAD_ADDRESS, formData, (response) => { + writeText(`<<${idPath} "${content}">> ![](${response.data.succMap[imageName]})`); + }); }); }); }); diff --git a/app/src/constants.ts b/app/src/constants.ts index a5154561b..d14997e76 100644 --- a/app/src/constants.ts +++ b/app/src/constants.ts @@ -67,6 +67,7 @@ export abstract class Constants { // size public static readonly SIZE_DATABASE_MAZ_SIZE: number = 102400; + public static readonly SIZE_UPLOAD_TIP_SIZE: number = 256 * 1024 * 1024; public static readonly SIZE_SCROLL_TB: number = 24; public static readonly SIZE_SCROLL_STEP: number = 256; public static readonly SIZE_LINK_TEXT_MAX: number = 64; diff --git a/app/src/protyle/render/av/asset.ts b/app/src/protyle/render/av/asset.ts index db0dd4155..9955b475a 100644 --- a/app/src/protyle/render/av/asset.ts +++ b/app/src/protyle/render/av/asset.ts @@ -22,6 +22,8 @@ import * as dayjs from "dayjs"; import {getColId} from "./col"; import {getFieldIdByCellElement} from "./row"; import {getCompressURL, removeCompressURL} from "../../../util/image"; +import {confirmDialog} from "../../../dialog/confirmDialog"; +import prettyBytes from "pretty-bytes"; export const bindAssetEvent = (options: { protyle: IProtyle, @@ -420,40 +422,51 @@ ${window.siyuan.languages.title} menu.element.querySelector("textarea").focus(); }; -export const dragUpload = (files: string[], protyle: IProtyle, cellElement: HTMLElement) => { - const msgId = showMessage(window.siyuan.languages.uploading, 0); - fetchPost("/api/asset/insertLocalAssets", { - assetPaths: files, - isUpload: true, - id: protyle.block.rootID - }, (response) => { - const blockElement = hasClosestBlock(cellElement); - if (blockElement) { - hideMessage(msgId); - const addValue: IAVCellAssetValue[] = []; - Object.keys(response.data.succMap).forEach(key => { - const type = pathPosix().extname(key).toLowerCase(); - const name = key.substring(0, key.length - type.length); - if (Constants.SIYUAN_ASSETS_IMAGE.includes(type)) { - addValue.push({ - type: "image", - name, - content: response.data.succMap[key], - }); - } else { - addValue.push({ - type: "file", - name, - content: response.data.succMap[key], - }); - } - }); - updateAssetCell({ - protyle, - blockElement, - cellElements: [cellElement], - addValue - }); +export const dragUpload = (files: ILocalFiles[], protyle: IProtyle, cellElement: HTMLElement) => { + let msg = ""; + const assetPaths: string[] = []; + files.forEach(item => { + if (item.size && Constants.SIZE_UPLOAD_TIP_SIZE <= item.size) { + msg += window.siyuan.languages.uploadFileTooLarge.replace("${x}", item.path).replace("${y}", prettyBytes(item.size, {binary: true})) + "
"; } + assetPaths.push(item.path); + }); + + confirmDialog(msg ? window.siyuan.languages.upload : "", msg, () => { + const msgId = showMessage(window.siyuan.languages.uploading, 0); + fetchPost("/api/asset/insertLocalAssets", { + assetPaths, + isUpload: true, + id: protyle.block.rootID + }, (response) => { + const blockElement = hasClosestBlock(cellElement); + if (blockElement) { + hideMessage(msgId); + const addValue: IAVCellAssetValue[] = []; + Object.keys(response.data.succMap).forEach(key => { + const type = pathPosix().extname(key).toLowerCase(); + const name = key.substring(0, key.length - type.length); + if (Constants.SIYUAN_ASSETS_IMAGE.includes(type)) { + addValue.push({ + type: "image", + name, + content: response.data.succMap[key], + }); + } else { + addValue.push({ + type: "file", + name, + content: response.data.succMap[key], + }); + } + }); + updateAssetCell({ + protyle, + blockElement, + cellElements: [cellElement], + addValue + }); + } + }); }); }; diff --git a/app/src/protyle/render/av/blockAttr.ts b/app/src/protyle/render/av/blockAttr.ts index 58920931e..5bc73d0f4 100644 --- a/app/src/protyle/render/av/blockAttr.ts +++ b/app/src/protyle/render/av/blockAttr.ts @@ -275,9 +275,12 @@ class="fn__flex-1 fn__flex${["url", "text", "number", "email", "phone", "block"] const cellElement = element.querySelector(".custom-attr__avvalue--active") as HTMLElement; if (cellElement) { if (event.dataTransfer.types[0] === "Files" && !isBrowser()) { - const files: string[] = []; + const files: ILocalFiles[] = []; for (let i = 0; i < event.dataTransfer.files.length; i++) { - files.push(webUtils.getPathForFile(event.dataTransfer.files[i])); + files.push({ + path: webUtils.getPathForFile(event.dataTransfer.files[i]), + size: event.dataTransfer.files[i].size + }); } dragUpload(files, protyle, cellElement); } diff --git a/app/src/protyle/upload/index.ts b/app/src/protyle/upload/index.ts index 0925c382e..67ab33161 100644 --- a/app/src/protyle/upload/index.ts +++ b/app/src/protyle/upload/index.ts @@ -10,6 +10,8 @@ import {hasClosestBlock} from "../util/hasClosest"; import {getContenteditableElement} from "../wysiwyg/getBlock"; import {getTypeByCellElement, updateCellsValue} from "../render/av/cell"; import {scrollCenter} from "../../util/highlightById"; +import {confirmDialog} from "../../dialog/confirmDialog"; +import prettyBytes from "pretty-bytes"; interface FileWithPath extends File { path: string; @@ -213,53 +215,39 @@ const genUploadedLabel = (responseText: string, protyle: IProtyle) => { }, 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}", `${tip.substring(0, tip.length - 2)}`)); +export const uploadLocalFiles = (files: ILocalFiles[], protyle: IProtyle, isUpload: boolean) => { + let msg = ""; + const assetPaths: string[] = []; + files.forEach(item => { + if (item.size && Constants.SIZE_UPLOAD_TIP_SIZE <= item.size) { + msg += window.siyuan.languages.uploadFileTooLarge.replace("${x}", item.path).replace("${y}", prettyBytes(item.size, {binary: true})) + "
"; } - genUploadedLabel(JSON.stringify(response), protyle); + assetPaths.push(item.path); + }); + + confirmDialog(msg ? window.siyuan.languages.upload : "", msg, () => { + const msgId = showMessage(window.siyuan.languages.uploading, 0); + fetchPost("/api/asset/insertLocalAssets", { + assetPaths, + 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++) { @@ -269,7 +257,7 @@ export const uploadFiles = (protyle: IProtyle, files: FileList | DataTransferIte } if (0 === fileItem.size && "" === fileItem.type && -1 === fileItem.name.indexOf(".")) { // 文件夹 - uploadLocalFiles([(fileItem as FileWithPath).path], protyle, false); + uploadLocalFiles([{path: (fileItem as FileWithPath).path, size: null}], protyle, false); } else { fileList.push(fileItem); } @@ -319,65 +307,71 @@ export const uploadFiles = (protyle: IProtyle, files: FileList | DataTransferIte for (const key of Object.keys(extraData)) { formData.append(key, extraData[key]); } - + let msg = ""; 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; + if (Constants.SIZE_UPLOAD_TIP_SIZE <= validateResult.files[i].size) { + msg += window.siyuan.languages.uploadFileTooLarge.replace("${x}", validateResult.files[i].name).replace("${y}", prettyBytes(validateResult.files[i].size, {binary: true})) + "
"; + } } - protyle.upload.isUploading = true; - xhr.onreadystatechange = () => { - if (xhr.readyState === XMLHttpRequest.DONE) { - protyle.upload.isUploading = false; - if (!document.body.contains(protyle.element)) { - // 网络较慢时,页签已经关闭 - destroy(protyle); + formData.append("id", protyle.block.rootID); + confirmDialog(msg ? window.siyuan.languages.upload : "", msg, () => { + 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; } - 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); + 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); + }); }; diff --git a/app/src/protyle/util/compatibility.ts b/app/src/protyle/util/compatibility.ts index 16d25c6e9..71dfc16ee 100644 --- a/app/src/protyle/util/compatibility.ts +++ b/app/src/protyle/util/compatibility.ts @@ -122,13 +122,13 @@ export const readText = () => { /// #if !BROWSER export const getLocalFiles = async () => { // 不再支持 PC 浏览器 https://github.com/siyuan-note/siyuan/issues/7206 - let localFiles: string[] = []; + let localFiles: ILocalFiles[] = []; if ("darwin" === window.siyuan.config.system.os) { const xmlString = clipboard.read("NSFilenamesPboardType"); const domParser = new DOMParser(); const xmlDom = domParser.parseFromString(xmlString, "application/xml"); Array.from(xmlDom.getElementsByTagName("string")).forEach(item => { - localFiles.push(item.childNodes[0].nodeValue); + localFiles.push({path: item.childNodes[0].nodeValue, size: null}); }); } else { const xmlString = await fetchSyncPost("/api/clipboard/readFilePaths", {}); diff --git a/app/src/protyle/util/editorCommonEvent.ts b/app/src/protyle/util/editorCommonEvent.ts index 1ccc01a61..bf58344cc 100644 --- a/app/src/protyle/util/editorCommonEvent.ts +++ b/app/src/protyle/util/editorCommonEvent.ts @@ -1149,9 +1149,12 @@ export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => { if (!avElement) { focusByRange(getRangeByPoint(event.clientX, event.clientY)); if (event.dataTransfer.types[0] === "Files" && !isBrowser()) { - const files: string[] = []; + const files: ILocalFiles[] = []; for (let i = 0; i < event.dataTransfer.files.length; i++) { - files.push(webUtils.getPathForFile(event.dataTransfer.files[i])); + files.push({ + path: webUtils.getPathForFile(event.dataTransfer.files[i]), + size: event.dataTransfer.files[i].size + }); } uploadLocalFiles(files, protyle, !event.altKey); } else { @@ -1162,9 +1165,12 @@ export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => { const cellElement = hasClosestByClassName(event.target, "av__cell"); if (cellElement) { if (getTypeByCellElement(cellElement) === "mAsset" && event.dataTransfer.types[0] === "Files" && !isBrowser()) { - const files: string[] = []; + const files: ILocalFiles[] = []; for (let i = 0; i < event.dataTransfer.files.length; i++) { - files.push(webUtils.getPathForFile(event.dataTransfer.files[i])); + files.push({ + path: webUtils.getPathForFile(event.dataTransfer.files[i]), + size: event.dataTransfer.files[i].size + }); } dragUpload(files, protyle, cellElement); clearSelect(["cell"], avElement); diff --git a/app/src/protyle/util/paste.ts b/app/src/protyle/util/paste.ts index cb59b8a33..b22a11a8a 100644 --- a/app/src/protyle/util/paste.ts +++ b/app/src/protyle/util/paste.ts @@ -148,7 +148,7 @@ export const pasteEscaped = async (protyle: IProtyle, nodeElement: Element) => { }; export const pasteAsPlainText = async (protyle: IProtyle) => { - let localFiles: string[] = []; + let localFiles: ILocalFiles[] = []; /// #if !BROWSER localFiles = await getLocalFiles(); if (localFiles.length > 0) { @@ -214,24 +214,24 @@ export const restoreLuteMarkdownSyntax = (protyle: IProtyle) => { protyle.lute.SetMark(window.siyuan.config.editor.markdown.inlineMark); }; -const readLocalFile = async (protyle: IProtyle, localFiles: string[]) => { +const readLocalFile = async (protyle: IProtyle, localFiles: ILocalFiles[]) => { if (protyle && protyle.app && protyle.app.plugins) { for (let i = 0; i < protyle.app.plugins.length; i++) { - const response: { files: string[] } = await new Promise((resolve) => { + const response: { localFiles: ILocalFiles[] } = await new Promise((resolve) => { const emitResult = protyle.app.plugins[i].eventBus.emit("paste", { protyle, resolve, textHTML: "", textPlain: "", siyuanHTML: "", - files: localFiles + localFiles }); if (emitResult) { resolve(undefined); } }); - if (response?.files) { - localFiles = response.files; + if (response?.localFiles) { + localFiles = response.localFiles; } } } @@ -277,7 +277,7 @@ export const paste = async (protyle: IProtyle, event: (ClipboardEvent | DragEven /// #if !BROWSER if (!siyuanHTML && !textHTML && !textPlain && ("clipboardData" in event)) { - const localFiles: string[] = await getLocalFiles(); + const localFiles: ILocalFiles[] = await getLocalFiles(); if (localFiles.length > 0) { readLocalFile(protyle, localFiles); return; diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index 19b4af116..f7f9045f0 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -286,12 +286,17 @@ interface Window { destroyTheme(): Promise; } +interface ILocalFiles { + path: string, + size: number +} + interface IClipboardData { textHTML?: string, textPlain?: string, siyuanHTML?: string, files?: File[], - localFiles?: string[] + localFiles?: ILocalFiles[], } interface IRefDefs {