Vanessa 2026-01-08 23:47:09 +08:00
parent 251e73b845
commit 338c359013
9 changed files with 183 additions and 153 deletions

View file

@ -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]})`);
});
});
});
});

View file

@ -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;

View file

@ -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})) + "<br>";
}
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
});
}
});
});
};

View file

@ -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);
}

View file

@ -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}", `<b>${tip.substring(0, tip.length - 2)}</b>`));
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})) + "<br>";
}
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}", `<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++) {
@ -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})) + "<br>";
}
}
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);
});
};

View file

@ -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", {});

View file

@ -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);

View file

@ -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;

View file

@ -286,12 +286,17 @@ interface Window {
destroyTheme(): Promise<void>;
}
interface ILocalFiles {
path: string,
size: number
}
interface IClipboardData {
textHTML?: string,
textPlain?: string,
siyuanHTML?: string,
files?: File[],
localFiles?: string[]
localFiles?: ILocalFiles[],
}
interface IRefDefs {