A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 1520 lines 82 kB view raw
1import {focusBlock, focusByRange, getRangeByPoint} from "./selection"; 2import { 3 hasClosestBlock, 4 hasClosestByAttribute, 5 hasClosestByClassName, 6 hasClosestByTag, 7 hasTopClosestByAttribute, 8 isInEmbedBlock 9} from "./hasClosest"; 10import {Constants} from "../../constants"; 11import {paste} from "./paste"; 12import {cancelSB, genEmptyElement, genSBElement, insertEmptyBlock} from "../../block/util"; 13import {transaction, turnsIntoOneTransaction} from "../wysiwyg/transaction"; 14import {getTopAloneElement} from "../wysiwyg/getBlock"; 15import {updateListOrder} from "../wysiwyg/list"; 16import {fetchPost, fetchSyncPost} from "../../util/fetch"; 17import {onGet} from "./onGet"; 18/// #if !MOBILE 19import {getAllEditor} from "../../layout/getAll"; 20import {updatePanelByEditor} from "../../editor/util"; 21/// #endif 22import {blockRender} from "../render/blockRender"; 23import {uploadLocalFiles} from "../upload"; 24import {insertHTML} from "./insertHTML"; 25import {isBrowser} from "../../util/functions"; 26import {hideElements} from "../ui/hideElements"; 27import {insertAttrViewBlockAnimation} from "../render/av/row"; 28import {dragUpload} from "../render/av/asset"; 29import * as dayjs from "dayjs"; 30import {setFold, zoomOut} from "../../menus/protyle"; 31/// #if !BROWSER 32import {webUtils} from "electron"; 33/// #endif 34import {addDragFill, getTypeByCellElement} from "../render/av/cell"; 35import {processClonePHElement} from "../render/util"; 36import {insertGalleryItemAnimation} from "../render/av/gallery/item"; 37import {clearSelect} from "./clearSelect"; 38import {dragoverTab} from "../render/av/view"; 39 40// position: afterbegin 为拖拽成超级块; "afterend", "beforebegin" 一般拖拽 41const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElement: Element, 42 isSameDoc: boolean, position: InsertPosition, isCopy: boolean) => { 43 const doOperations: IOperation[] = []; 44 const undoOperations: IOperation[] = []; 45 const copyFoldHeadingIds: { newId: string, oldId: string }[] = []; 46 const targetId = targetElement.getAttribute("data-node-id"); 47 const newSourceElements: Element[] = []; 48 let tempTargetElement = targetElement; 49 let isSameLi = true; 50 sourceElements.find(item => { 51 if (!item.classList.contains("li") || 52 targetElement.getAttribute("data-subtype") !== item.getAttribute("data-subtype")) { 53 isSameLi = false; 54 return true; 55 } 56 }); 57 let newListElement: Element; 58 let newListId: string; 59 const orderListElements: { [key: string]: Element } = {}; 60 for (let index = sourceElements.length - 1; index >= 0; index--) { 61 const item = sourceElements[index]; 62 const id = item.getAttribute("data-node-id"); 63 const parentID = item.parentElement.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID; 64 if (item.getAttribute("data-type") === "NodeListItem" && !newListId && !isSameLi) { 65 newListId = Lute.NewNodeID(); 66 newListElement = document.createElement("div"); 67 newListElement.innerHTML = `<div data-subtype="${item.getAttribute("data-subtype")}" data-node-id="${newListId}" data-type="NodeList" class="list"><div class="protyle-attr" contenteditable="false">${Constants.ZWSP}</div></div>`; 68 newListElement = newListElement.firstElementChild; 69 doOperations.push({ 70 action: "insert", 71 data: newListElement.outerHTML, 72 id: newListId, 73 previousID: position === "afterbegin" ? null : (position === "afterend" ? targetId : tempTargetElement.previousElementSibling?.getAttribute("data-node-id")), 74 parentID: position === "afterbegin" ? targetId : (tempTargetElement.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID), 75 }); 76 undoOperations.push({ 77 action: "delete", 78 id: newListId 79 }); 80 tempTargetElement.insertAdjacentElement(position, newListElement); 81 newSourceElements.push(newListElement); 82 } 83 const copyNewId = Lute.NewNodeID(); 84 if (isCopy && item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") { 85 copyFoldHeadingIds.push({ 86 newId: copyNewId, 87 oldId: id 88 }); 89 } 90 91 let copyElement; 92 if (isCopy) { 93 undoOperations.push({ 94 action: "delete", 95 id: copyNewId, 96 }); 97 } else { 98 undoOperations.push({ 99 action: "move", 100 id, 101 previousID: item.previousElementSibling?.getAttribute("data-node-id"), 102 parentID, 103 }); 104 } 105 if (!isSameDoc && !isCopy) { 106 // 打开两个相同的文档 107 const sameElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${id}"]`); 108 if (sameElement) { 109 sameElement.remove(); 110 } 111 } 112 if (isCopy) { 113 copyElement = item.cloneNode(true) as HTMLElement; 114 copyElement.setAttribute("data-node-id", copyNewId); 115 copyElement.querySelectorAll("[data-node-id]").forEach((e) => { 116 const newId = Lute.NewNodeID(); 117 e.setAttribute("data-node-id", newId); 118 e.setAttribute("updated", newId.split("-")[0]); 119 }); 120 if (newListId) { 121 newListElement.insertAdjacentElement("afterbegin", copyElement); 122 doOperations.push({ 123 action: "insert", 124 id: copyNewId, 125 data: copyElement.outerHTML, 126 parentID: newListId, 127 }); 128 } else { 129 tempTargetElement.insertAdjacentElement(position, copyElement); 130 doOperations.push({ 131 action: "insert", 132 id: copyNewId, 133 data: copyElement.outerHTML, 134 previousID: position === "afterbegin" ? null : (position === "afterend" ? targetId : copyElement.previousElementSibling?.getAttribute("data-node-id")), // 不能使用常量,移动后会被修改 135 parentID: position === "afterbegin" ? targetId : (copyElement.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID), 136 }); 137 newSourceElements.push(copyElement); 138 } 139 } else { 140 const topSourceElement = getTopAloneElement(item); 141 const oldSourceParentElement = item.parentElement; 142 if (item.classList.contains("li") && item.getAttribute("data-subtype") === "o") { 143 orderListElements[item.parentElement.getAttribute("data-node-id")] = item.parentElement; 144 } 145 if (newListId) { 146 newListElement.insertAdjacentElement("afterbegin", item); 147 doOperations.push({ 148 action: "move", 149 id, 150 parentID: newListId, 151 }); 152 } else { 153 tempTargetElement.insertAdjacentElement(position, item); 154 doOperations.push({ 155 action: "move", 156 id, 157 previousID: position === "afterbegin" ? null : (position === "afterend" ? targetId : item.previousElementSibling?.getAttribute("data-node-id")), // 不能使用常量,移动后会被修改 158 parentID: position === "afterbegin" ? targetId : (item.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID), 159 }); 160 newSourceElements.push(item); 161 } 162 163 if (topSourceElement !== item) { 164 // 删除空元素 165 doOperations.push({ 166 action: "delete", 167 id: topSourceElement.getAttribute("data-node-id"), 168 }); 169 undoOperations.push({ 170 action: "insert", 171 data: topSourceElement.outerHTML, 172 id: topSourceElement.getAttribute("data-node-id"), 173 previousID: topSourceElement.previousElementSibling?.getAttribute("data-node-id"), 174 parentID: topSourceElement.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID 175 }); 176 topSourceElement.remove(); 177 if (!isSameDoc) { 178 // 打开两个相同的文档 179 const sameElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${topSourceElement.getAttribute("data-node-id")}"]`); 180 if (sameElement) { 181 sameElement.remove(); 182 } 183 } 184 } else if (oldSourceParentElement.classList.contains("sb") && oldSourceParentElement.childElementCount === 2) { 185 // 拖拽后,sb 只剩下一个元素 186 if (isSameDoc) { 187 const sbData = await cancelSB(protyle, oldSourceParentElement); 188 doOperations.push(sbData.doOperations[0], sbData.doOperations[1]); 189 undoOperations.push(sbData.undoOperations[1], sbData.undoOperations[0]); 190 } else { 191 /// #if !MOBILE 192 const allEditor = getAllEditor(); 193 for (let i = 0; i < allEditor.length; i++) { 194 if (allEditor[i].protyle.element.contains(oldSourceParentElement)) { 195 const otherSbData = await cancelSB(allEditor[i].protyle, oldSourceParentElement); 196 doOperations.push(otherSbData.doOperations[0], otherSbData.doOperations[1]); 197 undoOperations.push(otherSbData.undoOperations[1], otherSbData.undoOperations[0]); 198 // 需清空操作栈,否则撤销到移动出去的块的操作会抛异常 199 allEditor[i].protyle.undo.clear(); 200 break; 201 } 202 } 203 /// #endif 204 } 205 } else if (oldSourceParentElement.classList.contains("protyle-wysiwyg") && oldSourceParentElement.childElementCount === 0) { 206 /// #if !MOBILE 207 // 拖拽后,根文档原内容为空 208 getAllEditor().find(item => { 209 if (item.protyle.element.contains(oldSourceParentElement)) { 210 if (!item.protyle.block.showAll) { 211 const newId = Lute.NewNodeID(); 212 doOperations.splice(0, 0, { 213 action: "insert", 214 id: newId, 215 data: genEmptyElement(false, false, newId).outerHTML, 216 parentID: item.protyle.block.parentID 217 }); 218 undoOperations.splice(0, 0, { 219 action: "delete", 220 id: newId, 221 }); 222 } else { 223 zoomOut({protyle: item.protyle, id: item.protyle.block.rootID}); 224 } 225 return true; 226 } 227 }); 228 /// #endif 229 } 230 } 231 232 if (newListId && (index === 0|| 233 sourceElements[index - 1].getAttribute("data-type") !== "NodeListItem" || 234 sourceElements[index - 1].getAttribute("data-subtype") !== item.getAttribute("data-subtype")) 235 ) { 236 if (position === "beforebegin") { 237 tempTargetElement = newListElement; 238 } 239 newListId = null; 240 } else if (position === "beforebegin") { 241 tempTargetElement = isCopy ? copyElement : item; 242 } 243 } 244 Object.keys(orderListElements).forEach(key => { 245 Array.from(orderListElements[key].children).forEach((item) => { 246 if (item.classList.contains("protyle-attr")) { 247 return; 248 } 249 undoOperations.push({ 250 action: "update", 251 id: item.getAttribute("data-node-id"), 252 data: item.outerHTML 253 }); 254 }); 255 updateListOrder(orderListElements[key], 1); 256 Array.from(orderListElements[key].children).forEach((item) => { 257 if (item.classList.contains("protyle-attr")) { 258 return; 259 } 260 doOperations.push({ 261 action: "update", 262 id: item.getAttribute("data-node-id"), 263 data: item.outerHTML 264 }); 265 }); 266 }); 267 undoOperations.reverse(); 268 for (let j = 0; j < copyFoldHeadingIds.length; j++) { 269 const childrenItem = copyFoldHeadingIds[j]; 270 const responseTransaction = await fetchSyncPost("/api/block/getHeadingInsertTransaction", {id: childrenItem.oldId}); 271 responseTransaction.data.doOperations.splice(0, 1); 272 responseTransaction.data.doOperations[0].previousID = childrenItem.newId; 273 responseTransaction.data.undoOperations.splice(0, 1); 274 doOperations.push(...responseTransaction.data.doOperations); 275 undoOperations.push(...responseTransaction.data.undoOperations); 276 } 277 return { 278 doOperations, 279 undoOperations, 280 newSourceElements 281 }; 282}; 283 284const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElement: Element, isBottom: boolean, 285 direct: "col" | "row", isCopy: boolean) => { 286 const isSameDoc = protyle.element.contains(sourceElements[0]); 287 const undoOperations: IOperation[] = []; 288 const targetMoveUndo: IOperation = { 289 action: "move", 290 id: targetElement.getAttribute("data-node-id"), 291 previousID: targetElement.previousElementSibling?.getAttribute("data-node-id"), 292 parentID: targetElement.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID 293 }; 294 const sbElement = genSBElement(direct); 295 targetElement.parentElement.replaceChild(sbElement, targetElement); 296 const doOperations: IOperation[] = [{ 297 action: "insert", 298 data: sbElement.outerHTML, 299 id: sbElement.getAttribute("data-node-id"), 300 nextID: sbElement.nextElementSibling?.getAttribute("data-node-id"), 301 previousID: sbElement.previousElementSibling?.getAttribute("data-node-id"), 302 parentID: sbElement.parentElement.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID 303 }]; 304 const moveToResult = await moveTo(protyle, sourceElements, sbElement, isSameDoc, "afterbegin", isCopy); 305 doOperations.push(...moveToResult.doOperations); 306 undoOperations.push(...moveToResult.undoOperations); 307 const newSourceParentElement = moveToResult.newSourceElements; 308 if (isBottom) { 309 // 拖拽到超级块 col 下方, 其他块右侧 310 sbElement.insertAdjacentElement("afterbegin", targetElement); 311 doOperations.push({ 312 action: "move", 313 id: targetElement.getAttribute("data-node-id"), 314 parentID: sbElement.getAttribute("data-node-id") 315 }); 316 } else { 317 sbElement.lastElementChild.insertAdjacentElement("beforebegin", targetElement); 318 doOperations.push({ 319 action: "move", 320 id: targetElement.getAttribute("data-node-id"), 321 previousID: newSourceParentElement[0].getAttribute("data-node-id"), 322 }); 323 } 324 undoOperations.push(targetMoveUndo); 325 undoOperations.push({ 326 action: "delete", 327 id: sbElement.getAttribute("data-node-id"), 328 }); 329 let hasFoldHeading = false; 330 newSourceParentElement.forEach(item => { 331 if (item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") { 332 hasFoldHeading = true; 333 if (item.nextElementSibling && ( 334 item.nextElementSibling.getAttribute("data-type") !== "NodeHeading" || 335 item.nextElementSibling.getAttribute("data-subtype") > item.getAttribute("data-subtype") 336 )) { 337 const foldOperations = setFold(protyle, item, true, false, false, true); 338 doOperations.push(...foldOperations.doOperations); 339 // 不折叠,否则无法撤销 undoOperations.push(...foldOperations.undoOperations); 340 } 341 return true; 342 } 343 }); 344 if (isSameDoc || isCopy) { 345 transaction(protyle, doOperations, undoOperations); 346 } else { 347 // 跨文档或插入折叠标题下不支持撤销 348 transaction(protyle, doOperations); 349 } 350 if ((newSourceParentElement.length > 1 || hasFoldHeading) && direct === "col") { 351 turnsIntoOneTransaction({ 352 protyle, 353 selectsElement: newSourceParentElement.reverse(), 354 type: "BlocksMergeSuperBlock", 355 level: "row" 356 }); 357 } 358 if (document.contains(sourceElements[0])) { 359 focusBlock(sourceElements[0]); 360 } else { 361 focusBlock(targetElement); 362 } 363}; 364 365const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElement: Element, isBottom: boolean, isCopy: boolean) => { 366 const isSameDoc = protyle.element.contains(sourceElements[0]); 367 const doOperations: IOperation[] = []; 368 const undoOperations: IOperation[] = []; 369 370 const moveToResult = await moveTo(protyle, sourceElements, targetElement, isSameDoc, isBottom ? "afterend" : "beforebegin", isCopy); 371 doOperations.push(...moveToResult.doOperations); 372 undoOperations.push(...moveToResult.undoOperations); 373 const newSourceParentElement = moveToResult.newSourceElements; 374 let foldData; 375 if (isBottom && 376 targetElement.getAttribute("data-type") === "NodeHeading" && 377 targetElement.getAttribute("fold") === "1") { 378 foldData = setFold(protyle, targetElement, true, false, false, true); 379 } else if (!isBottom && targetElement.previousElementSibling && 380 targetElement.previousElementSibling.getAttribute("data-type") === "NodeHeading" && 381 targetElement.previousElementSibling.getAttribute("fold") === "1") { 382 foldData = setFold(protyle, targetElement.previousElementSibling, true, false, false, true); 383 } 384 if (foldData) { 385 foldData.doOperations[0].context = { 386 focusId: sourceElements[0].getAttribute("data-node-id"), 387 }; 388 doOperations.push(...foldData.doOperations); 389 undoOperations.push(...foldData.undoOperations); 390 } 391 if (targetElement.getAttribute("data-type") === "NodeListItem" && 392 targetElement.getAttribute("data-subtype") === "o") { 393 // https://github.com/siyuan-note/insider/issues/536 394 Array.from(targetElement.parentElement.children).forEach((item) => { 395 if (item.classList.contains("protyle-attr")) { 396 return; 397 } 398 undoOperations.splice(0, 0, { 399 action: "update", 400 id: item.getAttribute("data-node-id"), 401 data: item.outerHTML 402 }); 403 }); 404 updateListOrder(targetElement.parentElement, 1); 405 Array.from(targetElement.parentElement.children).forEach((item) => { 406 if (item.classList.contains("protyle-attr")) { 407 return; 408 } 409 doOperations.push({ 410 action: "update", 411 id: item.getAttribute("data-node-id"), 412 data: item.outerHTML 413 }); 414 }); 415 } 416 let hasFoldHeading = false; 417 newSourceParentElement.forEach(item => { 418 if (item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") { 419 hasFoldHeading = true; 420 if (item.nextElementSibling && ( 421 item.nextElementSibling.getAttribute("data-type") !== "NodeHeading" || 422 item.nextElementSibling.getAttribute("data-subtype") > item.getAttribute("data-subtype") 423 )) { 424 const foldOperations = setFold(protyle, item, true, false, false, true); 425 doOperations.push(...foldOperations.doOperations); 426 // 不折叠,否则无法撤销 undoOperations.push(...foldOperations.undoOperations); 427 } 428 return true; 429 } 430 }); 431 if (isSameDoc || isCopy) { 432 transaction(protyle, doOperations, undoOperations); 433 } else { 434 // 跨文档或插入折叠标题下不支持撤销 435 transaction(protyle, doOperations); 436 } 437 if ((newSourceParentElement.length > 1 || hasFoldHeading) && 438 newSourceParentElement[0].parentElement.classList.contains("sb") && 439 newSourceParentElement[0].parentElement.getAttribute("data-sb-layout") === "col") { 440 turnsIntoOneTransaction({ 441 protyle, 442 selectsElement: newSourceParentElement.reverse(), 443 type: "BlocksMergeSuperBlock", 444 level: "row" 445 }); 446 } 447 if (document.contains(sourceElements[0])) { 448 focusBlock(sourceElements[0]); 449 } else { 450 focusBlock(targetElement); 451 } 452}; 453 454export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => { 455 editorElement.addEventListener("dragstart", (event) => { 456 if (protyle.disabled) { 457 event.preventDefault(); 458 event.stopPropagation(); 459 return; 460 } 461 let target = event.target as HTMLElement; 462 if (target.classList?.contains("av__gallery-img")) { 463 target = hasClosestByClassName(target, "av__gallery-item") as HTMLElement; 464 } 465 if (!target) { 466 return; 467 } 468 if (target.tagName === "IMG") { 469 window.siyuan.dragElement = undefined; 470 event.preventDefault(); 471 return; 472 } 473 474 if (target.classList) { 475 if (hasClosestByClassName(target, "protyle-wysiwyg__embed")) { 476 window.siyuan.dragElement = undefined; 477 event.preventDefault(); 478 } else if (target.parentElement.parentElement.classList.contains("av__views")) { 479 window.siyuan.dragElement = target; 480 target.style.width = target.clientWidth + "px"; 481 target.style.opacity = ".36"; 482 event.dataTransfer.setData(`${Constants.SIYUAN_DROP_GUTTER}NodeAttributeView${Constants.ZWSP}ViewTab${Constants.ZWSP}${[target.previousElementSibling?.getAttribute("data-id")]}`, 483 target.outerHTML); 484 return; 485 } else if (target.classList.contains("protyle-action")) { 486 target.parentElement.classList.add("protyle-wysiwyg--select"); 487 const ghostElement = document.createElement("div"); 488 ghostElement.className = protyle.wysiwyg.element.className; 489 ghostElement.append(processClonePHElement(target.parentElement.cloneNode(true) as Element)); 490 ghostElement.setAttribute("style", `position:fixed;opacity:.1;width:${target.parentElement.clientWidth}px;padding:0;`); 491 document.body.append(ghostElement); 492 event.dataTransfer.setDragImage(ghostElement, 0, 0); 493 setTimeout(() => { 494 ghostElement.remove(); 495 }); 496 497 window.siyuan.dragElement = protyle.wysiwyg.element; 498 event.dataTransfer.setData(`${Constants.SIYUAN_DROP_GUTTER}NodeListItem${Constants.ZWSP}${target.parentElement.getAttribute("data-subtype")}${Constants.ZWSP}${[target.parentElement.getAttribute("data-node-id")]}`, 499 protyle.wysiwyg.element.innerHTML); 500 return; 501 } else if (target.classList.contains("av__cell--header")) { 502 window.siyuan.dragElement = target; 503 event.dataTransfer.setData(`${Constants.SIYUAN_DROP_GUTTER}NodeAttributeView${Constants.ZWSP}Col${Constants.ZWSP}${[target.getAttribute("data-col-id")]}`, 504 target.outerHTML); 505 return; 506 } else if (target.classList.contains("av__gallery-item")) { 507 const blockElement = hasClosestBlock(target); 508 if (blockElement) { 509 if (blockElement.querySelector('.block__icon[data-type="av-sort"]')?.classList.contains("block__icon--active")) { 510 const bodyElements = blockElement.querySelectorAll(".av__body"); 511 if (bodyElements.length === 1) { 512 event.preventDefault(); 513 event.stopPropagation(); 514 return; 515 } else if (["template", "created", "updated"].includes(bodyElements[0].getAttribute("data-dtype"))) { 516 event.preventDefault(); 517 event.stopPropagation(); 518 return; 519 } 520 } 521 if (!target.classList.contains("av__gallery-item--select")) { 522 blockElement.querySelectorAll(".av__gallery-item--select").forEach(item => { 523 item.classList.remove("av__gallery-item--select"); 524 }); 525 target.classList.add("av__gallery-item--select"); 526 } 527 const ghostElement = document.createElement("div"); 528 ghostElement.className = "protyle-wysiwyg protyle-wysiwyg--attr"; 529 const selectElements = blockElement.querySelectorAll(".av__gallery-item--select"); 530 let galleryElement: HTMLElement; 531 let cloneGalleryElement = document.createElement("div"); 532 selectElements.forEach(item => { 533 if (!galleryElement || !galleryElement.contains(item)) { 534 galleryElement = item.parentElement; 535 cloneGalleryElement = document.createElement("div"); 536 cloneGalleryElement.classList.add("av__gallery"); 537 cloneGalleryElement.setAttribute("style", `width: 100vw;margin-bottom: 16px;grid-template-columns: repeat(auto-fill, ${selectElements[0].clientWidth}px);`); 538 ghostElement.appendChild(cloneGalleryElement); 539 } 540 const cloneItem = processClonePHElement(item.cloneNode(true) as Element); 541 cloneItem.setAttribute("style", `height:${item.clientHeight}px;`); 542 cloneItem.querySelector(".av__gallery-fields").setAttribute("style", "background-color: var(--b3-theme-background)"); 543 cloneGalleryElement.appendChild(cloneItem); 544 }); 545 ghostElement.setAttribute("style", "top:100vh;position:fixed;opacity:.1;padding:0;z-index: 8"); 546 document.body.append(ghostElement); 547 event.dataTransfer.setDragImage(ghostElement, -10, -10); 548 setTimeout(() => { 549 ghostElement.remove(); 550 }); 551 window.siyuan.dragElement = target; 552 const selectIds: string[] = []; 553 blockElement.querySelectorAll(".av__gallery-item--select").forEach(item => { 554 const bodyElement = hasClosestByClassName(item, "av__body") as HTMLElement; 555 const groupId = bodyElement.getAttribute("data-group-id"); 556 selectIds.push(item.getAttribute("data-id") + (groupId ? `@${groupId}` : "")); 557 }); 558 event.dataTransfer.setData(`${Constants.SIYUAN_DROP_GUTTER}NodeAttributeView${Constants.ZWSP}GalleryItem${Constants.ZWSP}${selectIds}`, 559 ghostElement.outerHTML); 560 } 561 return; 562 } 563 } 564 // 选中编辑器中的文字进行拖拽 565 event.dataTransfer.setData(Constants.SIYUAN_DROP_EDITOR, Constants.SIYUAN_DROP_EDITOR); 566 protyle.element.style.userSelect = "auto"; 567 document.onmousemove = null; 568 document.onmouseup = null; 569 }); 570 editorElement.addEventListener("drop", async (event: DragEvent & { target: HTMLElement }) => { 571 counter = 0; 572 if (protyle.disabled || event.dataTransfer.getData(Constants.SIYUAN_DROP_EDITOR)) { 573 // 只读模式/编辑器内选中文字拖拽 574 event.preventDefault(); 575 event.stopPropagation(); 576 return; 577 } 578 let gutterType = ""; 579 for (const item of event.dataTransfer.items) { 580 if (item.type.startsWith(Constants.SIYUAN_DROP_GUTTER)) { 581 gutterType = item.type; 582 } 583 } 584 if (gutterType.startsWith(`${Constants.SIYUAN_DROP_GUTTER}NodeAttributeView${Constants.ZWSP}ViewTab${Constants.ZWSP}`.toLowerCase())) { 585 const blockElement = hasClosestBlock(window.siyuan.dragElement); 586 if (blockElement) { 587 const avID = blockElement.getAttribute("data-av-id"); 588 const blockID = blockElement.getAttribute("data-node-id"); 589 const id = window.siyuan.dragElement.getAttribute("data-id"); 590 transaction(protyle, [{ 591 action: "sortAttrViewView", 592 avID, 593 blockID, 594 id, 595 previousID: window.siyuan.dragElement.previousElementSibling?.getAttribute("data-id"), 596 data: "unRefresh" // 不需要重新渲染 597 }], [{ 598 action: "sortAttrViewView", 599 avID, 600 blockID, 601 id, 602 previousID: gutterType.split(Constants.ZWSP).pop() 603 }]); 604 } 605 return; 606 } 607 const targetElement = editorElement.querySelector(".dragover__left, .dragover__right, .dragover__bottom, .dragover__top"); 608 if (targetElement) { 609 targetElement.classList.remove("dragover"); 610 targetElement.removeAttribute("select-start"); 611 targetElement.removeAttribute("select-end"); 612 } 613 if (gutterType) { 614 // gutter 或反链面板拖拽 615 const sourceElements: Element[] = []; 616 const gutterTypes = gutterType.replace(Constants.SIYUAN_DROP_GUTTER, "").split(Constants.ZWSP); 617 const selectedIds = gutterTypes[2].split(","); 618 if (event.altKey || event.shiftKey) { 619 if (event.y > protyle.wysiwyg.element.lastElementChild.getBoundingClientRect().bottom) { 620 insertEmptyBlock(protyle, "afterend", protyle.wysiwyg.element.lastElementChild.getAttribute("data-node-id")); 621 } else { 622 const range = getRangeByPoint(event.clientX, event.clientY); 623 if (hasClosestByAttribute(range.startContainer, "data-type", "NodeBlockQueryEmbed")) { 624 return; 625 } else { 626 focusByRange(range); 627 } 628 } 629 } 630 if (event.altKey) { 631 let html = ""; 632 for (let i = 0; i < selectedIds.length; i++) { 633 const response = await fetchSyncPost("/api/block/getRefText", {id: selectedIds[i]}); 634 html += protyle.lute.Md2BlockDOM(`((${selectedIds[i]} '${response.data}'))`); 635 } 636 insertHTML(html, protyle); 637 } else if (event.shiftKey) { 638 let html = ""; 639 selectedIds.forEach(item => { 640 html += `{{select * from blocks where id='${item}'}}\n`; 641 }); 642 insertHTML(protyle.lute.SpinBlockDOM(html), protyle, true); 643 blockRender(protyle, protyle.wysiwyg.element); 644 } else if (targetElement && targetElement.className.indexOf("dragover__") > -1) { 645 let queryClass = ""; 646 selectedIds.forEach(item => { 647 queryClass += `[data-node-id="${item}"],`; 648 }); 649 if (window.siyuan.dragElement) { 650 window.siyuan.dragElement.querySelectorAll(queryClass.substring(0, queryClass.length - 1)).forEach(elementItem => { 651 if (!isInEmbedBlock(elementItem)) { 652 sourceElements.push(elementItem); 653 } 654 }); 655 } else if (window.siyuan.config.system.workspaceDir.toLowerCase() === gutterTypes[3]) { 656 // 跨窗口拖拽 657 // 不能跨工作区域拖拽 https://github.com/siyuan-note/siyuan/issues/13582 658 const targetProtyleElement = document.createElement("template"); 659 targetProtyleElement.innerHTML = `<div>${event.dataTransfer.getData(gutterType)}</div>`; 660 targetProtyleElement.content.querySelectorAll(queryClass.substring(0, queryClass.length - 1)).forEach(elementItem => { 661 if (!isInEmbedBlock(elementItem)) { 662 sourceElements.push(elementItem); 663 } 664 }); 665 } 666 667 const sourceIds: string [] = []; 668 const srcs: IOperationSrcs[] = []; 669 sourceElements.forEach(item => { 670 item.classList.remove("protyle-wysiwyg--hl"); 671 item.removeAttribute("select-start"); 672 item.removeAttribute("select-end"); 673 // 反链提及有高亮,如果拖拽到正文的话,应移除 674 item.querySelectorAll('[data-type="search-mark"]').forEach(markItem => { 675 markItem.outerHTML = markItem.innerHTML; 676 }); 677 const id = item.getAttribute("data-node-id"); 678 sourceIds.push(id); 679 srcs.push({ 680 itemID: Lute.NewNodeID(), 681 id, 682 isDetached: false, 683 }); 684 }); 685 686 hideElements(["gutter"], protyle); 687 688 const targetClass = targetElement.className.split(" "); 689 targetElement.classList.remove("dragover__bottom", "dragover__top", "dragover__left", "dragover__right"); 690 691 if (targetElement.classList.contains("av__cell")) { 692 const blockElement = hasClosestBlock(targetElement); 693 if (blockElement) { 694 const avID = blockElement.getAttribute("data-av-id"); 695 let previousID = ""; 696 if (targetClass.includes("dragover__left")) { 697 if (targetElement.previousElementSibling) { 698 if (targetElement.previousElementSibling.classList.contains("av__colsticky")) { 699 previousID = targetElement.previousElementSibling.lastElementChild.getAttribute("data-col-id"); 700 } else { 701 previousID = targetElement.previousElementSibling.getAttribute("data-col-id"); 702 } 703 } 704 } else { 705 previousID = targetElement.getAttribute("data-col-id"); 706 } 707 let oldPreviousID = ""; 708 const rowElement = hasClosestByClassName(targetElement, "av__row"); 709 if (rowElement) { 710 const oldPreviousElement = rowElement.querySelector(`[data-col-id="${gutterTypes[2]}"`)?.previousElementSibling; 711 if (oldPreviousElement) { 712 if (oldPreviousElement.classList.contains("av__colsticky")) { 713 oldPreviousID = oldPreviousElement.lastElementChild.getAttribute("data-col-id"); 714 } else { 715 oldPreviousID = oldPreviousElement.getAttribute("data-col-id"); 716 } 717 } 718 } 719 if (previousID !== oldPreviousID && previousID !== gutterTypes[2]) { 720 transaction(protyle, [{ 721 action: "sortAttrViewCol", 722 avID, 723 previousID, 724 id: gutterTypes[2], 725 blockID: blockElement.dataset.nodeId, 726 }], [{ 727 action: "sortAttrViewCol", 728 avID, 729 previousID: oldPreviousID, 730 id: gutterTypes[2], 731 blockID: blockElement.dataset.nodeId, 732 }]); 733 } 734 } 735 } else if (targetElement.classList.contains("av__row")) { 736 // 拖拽到属性视图 table 内 737 const blockElement = hasClosestBlock(targetElement); 738 if (blockElement) { 739 let previousID = ""; 740 if (targetClass.includes("dragover__bottom")) { 741 previousID = targetElement.getAttribute("data-id") || ""; 742 } else { 743 previousID = targetElement.previousElementSibling?.getAttribute("data-id") || ""; 744 } 745 const avID = blockElement.getAttribute("data-av-id"); 746 if (gutterTypes[0] === "nodeattributeviewrowmenu") { 747 // 行内拖拽 748 const doOperations: IOperation[] = []; 749 const undoOperations: IOperation[] = []; 750 const targetGroupID = targetElement.parentElement.getAttribute("data-group-id"); 751 selectedIds.reverse().forEach(item => { 752 const items = item.split("@"); 753 const id = items[0]; 754 const groupID = items[1] || ""; 755 const undoPreviousId = blockElement.querySelector(`.av__body${groupID ? `[data-group-id="${groupID}"]` : ""} .av__row[data-id="${id}"]`).previousElementSibling?.getAttribute("data-id") || ""; 756 if (previousID !== id && undoPreviousId !== previousID || ( 757 (undoPreviousId === "" && previousID === "" && targetGroupID !== groupID) 758 )) { 759 doOperations.push({ 760 action: "sortAttrViewRow", 761 avID, 762 previousID, 763 id, 764 blockID: blockElement.dataset.nodeId, 765 groupID, 766 targetGroupID, 767 }); 768 undoOperations.push({ 769 action: "sortAttrViewRow", 770 avID, 771 previousID: undoPreviousId, 772 id, 773 blockID: blockElement.dataset.nodeId, 774 groupID: targetGroupID, 775 targetGroupID: groupID, 776 }); 777 } 778 }); 779 transaction(protyle, doOperations, undoOperations); 780 } else { 781 const newUpdated = dayjs().format("YYYYMMDDHHmmss"); 782 const bodyElement = hasClosestByClassName(targetElement, "av__body"); 783 const groupID = bodyElement && bodyElement.getAttribute("data-group-id"); 784 transaction(protyle, [{ 785 action: "insertAttrViewBlock", 786 avID, 787 previousID, 788 srcs, 789 blockID: blockElement.dataset.nodeId, 790 groupID 791 }, { 792 action: "doUpdateUpdated", 793 id: blockElement.dataset.nodeId, 794 data: newUpdated, 795 }], [{ 796 action: "removeAttrViewBlock", 797 srcIDs: sourceIds, 798 avID, 799 }, { 800 action: "doUpdateUpdated", 801 id: blockElement.dataset.nodeId, 802 data: blockElement.getAttribute("updated") 803 }]); 804 blockElement.setAttribute("updated", newUpdated); 805 insertAttrViewBlockAnimation({ 806 protyle, 807 blockElement, 808 srcIDs: sourceIds, 809 previousId: previousID, 810 groupID 811 }); 812 } 813 } 814 } else if (targetElement.classList.contains("av__gallery-item") || targetElement.classList.contains("av__gallery-add")) { 815 // 拖拽到属性视图 gallery 内 816 const blockElement = hasClosestBlock(targetElement); 817 if (blockElement) { 818 let previousID = ""; 819 if (targetClass.includes("dragover__right")) { 820 previousID = targetElement.getAttribute("data-id") || ""; 821 } else { 822 previousID = targetElement.previousElementSibling?.getAttribute("data-id") || ""; 823 } 824 const avID = blockElement.getAttribute("data-av-id"); 825 if (gutterTypes[1] === "galleryitem" && gutterTypes[0] === "nodeattributeview") { 826 // gallery item 内部拖拽 827 const doOperations: IOperation[] = []; 828 const undoOperations: IOperation[] = []; 829 const targetGroupID = targetElement.parentElement.parentElement.getAttribute("data-group-id"); 830 selectedIds.reverse().forEach(item => { 831 const items = item.split("@"); 832 const id = items[0]; 833 const groupID = items[1] || ""; 834 const undoPreviousId = blockElement.querySelector(`.av__body[data-group-id="${groupID}"] .av__gallery-item[data-id="${id}"]`).previousElementSibling?.getAttribute("data-id") || ""; 835 if (previousID !== item && undoPreviousId !== previousID || ( 836 (undoPreviousId === "" && previousID === "" && targetGroupID !== groupID) 837 )) { 838 doOperations.push({ 839 action: "sortAttrViewRow", 840 avID, 841 previousID, 842 id, 843 blockID: blockElement.dataset.nodeId, 844 groupID, 845 targetGroupID, 846 }); 847 undoOperations.push({ 848 action: "sortAttrViewRow", 849 avID, 850 previousID: undoPreviousId, 851 id, 852 blockID: blockElement.dataset.nodeId, 853 groupID: targetGroupID, 854 targetGroupID: groupID, 855 }); 856 } 857 }); 858 transaction(protyle, doOperations, undoOperations); 859 } else { 860 const newUpdated = dayjs().format("YYYYMMDDHHmmss"); 861 const bodyElement = hasClosestByClassName(targetElement, "av__body"); 862 transaction(protyle, [{ 863 action: "insertAttrViewBlock", 864 avID, 865 previousID, 866 srcs, 867 blockID: blockElement.dataset.nodeId, 868 groupID: bodyElement && bodyElement.getAttribute("data-group-id") 869 }, { 870 action: "doUpdateUpdated", 871 id: blockElement.dataset.nodeId, 872 data: newUpdated, 873 }], [{ 874 action: "removeAttrViewBlock", 875 srcIDs: sourceIds, 876 avID, 877 }, { 878 action: "doUpdateUpdated", 879 id: blockElement.dataset.nodeId, 880 data: blockElement.getAttribute("updated") 881 }]); 882 blockElement.setAttribute("updated", newUpdated); 883 insertGalleryItemAnimation({ 884 protyle, 885 blockElement, 886 srcIDs: sourceIds, 887 previousId: previousID, 888 groupID: targetElement.parentElement.getAttribute("data-group-id") 889 }); 890 } 891 } 892 } else if (sourceElements.length > 0) { 893 if (targetElement.parentElement.getAttribute("data-type") === "NodeSuperBlock" && 894 targetElement.parentElement.getAttribute("data-sb-layout") === "col") { 895 if (targetClass.includes("dragover__left") || targetClass.includes("dragover__right")) { 896 // Mac 上 ⌘ 无法进行拖拽 897 dragSame(protyle, sourceElements, targetElement, targetClass.includes("dragover__right"), event.ctrlKey); 898 } else { 899 dragSb(protyle, sourceElements, targetElement, targetClass.includes("dragover__bottom"), "row", event.ctrlKey); 900 } 901 } else { 902 if (targetClass.includes("dragover__left") || targetClass.includes("dragover__right")) { 903 dragSb(protyle, sourceElements, targetElement, targetClass.includes("dragover__right"), "col", event.ctrlKey); 904 } else { 905 dragSame(protyle, sourceElements, targetElement, targetClass.includes("dragover__bottom"), event.ctrlKey); 906 } 907 } 908 909 // https://github.com/siyuan-note/siyuan/issues/10528#issuecomment-2205165824 910 editorElement.querySelectorAll(".protyle-wysiwyg--empty").forEach(item => { 911 item.classList.remove("protyle-wysiwyg--empty"); 912 }); 913 914 // 需重新渲染 https://github.com/siyuan-note/siyuan/issues/7574 915 protyle.wysiwyg.element.querySelectorAll('[data-type="NodeBlockQueryEmbed"]').forEach(item => { 916 item.removeAttribute("data-render"); 917 blockRender(protyle, item); 918 }); 919 } 920 dragoverElement = undefined; 921 } 922 } else if (event.dataTransfer.getData(Constants.SIYUAN_DROP_FILE)?.split("-").length > 1) { 923 // 文件树拖拽 924 const ids = event.dataTransfer.getData(Constants.SIYUAN_DROP_FILE).split(","); 925 if (!event.altKey && (!targetElement || ( 926 !targetElement.classList.contains("av__row") && !targetElement.classList.contains("av__gallery-item") && 927 !targetElement.classList.contains("av__gallery-add") 928 ))) { 929 if (event.y > protyle.wysiwyg.element.lastElementChild.getBoundingClientRect().bottom) { 930 insertEmptyBlock(protyle, "afterend", protyle.wysiwyg.element.lastElementChild.getAttribute("data-node-id")); 931 } else { 932 const range = getRangeByPoint(event.clientX, event.clientY); 933 if (hasClosestByAttribute(range.startContainer, "data-type", "NodeBlockQueryEmbed")) { 934 return; 935 } else { 936 focusByRange(range); 937 } 938 } 939 let html = ""; 940 for (let i = 0; i < ids.length; i++) { 941 if (ids.length > 1) { 942 html += "- "; 943 } 944 const response = await fetchSyncPost("/api/block/getRefText", {id: ids[i]}); 945 html += `((${ids[i]} '${response.data}'))`; 946 if (ids.length > 1 && i !== ids.length - 1) { 947 html += "\n"; 948 } 949 } 950 insertHTML(protyle.lute.Md2BlockDOM(html), protyle); 951 } else if (targetElement && !protyle.options.backlinkData && targetElement.className.indexOf("dragover__") > -1) { 952 const scrollTop = protyle.contentElement.scrollTop; 953 if (targetElement.classList.contains("av__row") || 954 targetElement.classList.contains("av__gallery-item") || 955 targetElement.classList.contains("av__gallery-add")) { 956 // 拖拽到属性视图内 957 const blockElement = hasClosestBlock(targetElement); 958 if (blockElement) { 959 let previousID = ""; 960 if (targetElement.classList.contains("dragover__bottom")) { 961 previousID = targetElement.getAttribute("data-id") || ""; 962 } else if (targetElement.classList.contains("dragover__top")) { 963 previousID = targetElement.previousElementSibling?.getAttribute("data-id") || ""; 964 } else if (targetElement.classList.contains("dragover__left")) { 965 previousID = targetElement.previousElementSibling?.getAttribute("data-id") || ""; 966 } else if (targetElement.classList.contains("dragover__right")) { 967 previousID = targetElement.getAttribute("data-id") || ""; 968 } 969 const avID = blockElement.getAttribute("data-av-id"); 970 const newUpdated = dayjs().format("YYYYMMDDHHmmss"); 971 const srcs: IOperationSrcs[] = []; 972 const bodyElement = hasClosestByClassName(targetElement, "av__body"); 973 const groupID = bodyElement && bodyElement.getAttribute("data-group-id"); 974 ids.forEach(id => { 975 srcs.push({ 976 itemID: Lute.NewNodeID(), 977 id, 978 isDetached: false, 979 }); 980 }); 981 transaction(protyle, [{ 982 action: "insertAttrViewBlock", 983 avID, 984 previousID, 985 srcs, 986 blockID: blockElement.dataset.nodeId, 987 groupID 988 }, { 989 action: "doUpdateUpdated", 990 id: blockElement.dataset.nodeId, 991 data: newUpdated, 992 }], [{ 993 action: "removeAttrViewBlock", 994 srcIDs: ids, 995 avID, 996 }, { 997 action: "doUpdateUpdated", 998 id: blockElement.dataset.nodeId, 999 data: blockElement.getAttribute("updated") 1000 }]); 1001 insertAttrViewBlockAnimation({ 1002 protyle, 1003 blockElement, 1004 srcIDs: ids, 1005 previousId: previousID, 1006 groupID 1007 }); 1008 blockElement.setAttribute("updated", newUpdated); 1009 } 1010 } else { 1011 if (targetElement.classList.contains("dragover__bottom")) { 1012 for (let i = ids.length - 1; i > -1; i--) { 1013 if (ids[i]) { 1014 await fetchSyncPost("/api/filetree/doc2Heading", { 1015 srcID: ids[i], 1016 after: true, 1017 targetID: targetElement.getAttribute("data-node-id"), 1018 }); 1019 } 1020 } 1021 } else { 1022 for (let i = 0; i < ids.length; i++) { 1023 if (ids[i]) { 1024 await fetchSyncPost("/api/filetree/doc2Heading", { 1025 srcID: ids[i], 1026 after: false, 1027 targetID: targetElement.getAttribute("data-node-id"), 1028 }); 1029 } 1030 } 1031 } 1032 1033 fetchPost("/api/filetree/getDoc", { 1034 id: protyle.block.id, 1035 size: window.siyuan.config.editor.dynamicLoadBlocks, 1036 }, getResponse => { 1037 onGet({data: getResponse, protyle}); 1038 /// #if !MOBILE 1039 // 文档标题互转后,需更新大纲 1040 updatePanelByEditor({ 1041 protyle, 1042 focus: false, 1043 pushBackStack: false, 1044 reload: true, 1045 resize: false, 1046 }); 1047 /// #endif 1048 // 文档标题互转后,编辑区会跳转到开头 https://github.com/siyuan-note/siyuan/issues/2939 1049 setTimeout(() => { 1050 protyle.contentElement.scrollTop = scrollTop; 1051 protyle.scroll.lastScrollTop = scrollTop - 1; 1052 }, Constants.TIMEOUT_LOAD); 1053 }); 1054 } 1055 targetElement.classList.remove("dragover__bottom", "dragover__top", "dragover__left", "dragover__right"); 1056 } 1057 } else if (!window.siyuan.dragElement && (event.dataTransfer.types[0] === "Files" || event.dataTransfer.types.includes("text/html"))) { 1058 event.preventDefault(); 1059 // 外部文件拖入编辑器中或者编辑器内选中文字拖拽 1060 // https://github.com/siyuan-note/siyuan/issues/9544 1061 const avElement = hasClosestByClassName(event.target, "av"); 1062 if (!avElement) { 1063 focusByRange(getRangeByPoint(event.clientX, event.clientY)); 1064 if (event.dataTransfer.types[0] === "Files" && !isBrowser()) { 1065 const files: string[] = []; 1066 for (let i = 0; i < event.dataTransfer.files.length; i++) { 1067 files.push(webUtils.getPathForFile(event.dataTransfer.files[i])); 1068 } 1069 uploadLocalFiles(files, protyle, !event.altKey); 1070 } else { 1071 paste(protyle, event); 1072 } 1073 clearSelect(["av", "img"], protyle.wysiwyg.element); 1074 } else { 1075 const cellElement = hasClosestByClassName(event.target, "av__cell"); 1076 if (cellElement) { 1077 if (getTypeByCellElement(cellElement) === "mAsset" && event.dataTransfer.types[0] === "Files" && !isBrowser()) { 1078 const files: string[] = []; 1079 for (let i = 0; i < event.dataTransfer.files.length; i++) { 1080 files.push(webUtils.getPathForFile(event.dataTransfer.files[i])); 1081 } 1082 dragUpload(files, protyle, cellElement); 1083 clearSelect(["cell"], avElement); 1084 } 1085 } 1086 } 1087 } 1088 if (window.siyuan.dragElement) { 1089 window.siyuan.dragElement.style.opacity = ""; 1090 window.siyuan.dragElement = undefined; 1091 } 1092 }); 1093 let dragoverElement: Element; 1094 let disabledPosition: string; 1095 editorElement.addEventListener("dragover", (event: DragEvent & { target: HTMLElement }) => { 1096 if (protyle.disabled || event.dataTransfer.types.includes(Constants.SIYUAN_DROP_EDITOR)) { 1097 event.preventDefault(); 1098 event.stopPropagation(); 1099 event.dataTransfer.dropEffect = "none"; 1100 return; 1101 } 1102 let gutterType = ""; 1103 for (const item of event.dataTransfer.items) { 1104 if (item.type.startsWith(Constants.SIYUAN_DROP_GUTTER)) { 1105 gutterType = item.type; 1106 } 1107 } 1108 if (gutterType.startsWith(`${Constants.SIYUAN_DROP_GUTTER}NodeAttributeView${Constants.ZWSP}ViewTab${Constants.ZWSP}`.toLowerCase())) { 1109 dragoverTab(event); 1110 event.preventDefault(); 1111 return; 1112 } 1113 const contentRect = protyle.contentElement.getBoundingClientRect(); 1114 if (!hasClosestByClassName(event.target, "av__cell") && 1115 (event.clientY < contentRect.top + Constants.SIZE_SCROLL_TB || event.clientY > contentRect.bottom - Constants.SIZE_SCROLL_TB)) { 1116 protyle.contentElement.scroll({ 1117 top: protyle.contentElement.scrollTop + (event.clientY < contentRect.top + Constants.SIZE_SCROLL_TB ? -Constants.SIZE_SCROLL_STEP : Constants.SIZE_SCROLL_STEP), 1118 behavior: "smooth" 1119 }); 1120 } 1121 let targetElement: HTMLElement | false; 1122 // 设置了的话 drop 就无法监听 shift/control event.dataTransfer.dropEffect = "move"; 1123 if (event.dataTransfer.types.includes("Files")) { 1124 targetElement = hasClosestByClassName(event.target, "av__cell"); 1125 if (targetElement && targetElement.getAttribute("data-dtype") === "mAsset" && 1126 !targetElement.classList.contains("av__cell--header")) { 1127 event.preventDefault(); // 不使用导致无法触发 drop 1128 if (dragoverElement && targetElement === dragoverElement) { 1129 return; 1130 } 1131 const blockElement = hasClosestBlock(targetElement); 1132 if (blockElement) { 1133 clearSelect(["cell", "row"], protyle.wysiwyg.element); 1134 targetElement.classList.add("av__cell--select"); 1135 if (blockElement.getAttribute("data-av-type") !== "gallery") { 1136 addDragFill(targetElement); 1137 } 1138 dragoverElement = targetElement; 1139 } 1140 } 1141 // 使用 event.preventDefault(); 会导致无光标 https://github.com/siyuan-note/siyuan/issues/12857 1142 return; 1143 } 1144 1145 if (!gutterType && !window.siyuan.dragElement) { 1146 // https://github.com/siyuan-note/siyuan/issues/6436 1147 event.preventDefault(); 1148 return; 1149 } 1150 const gutterTypes = gutterType ? gutterType.replace(Constants.SIYUAN_DROP_GUTTER, "").split(Constants.ZWSP) : []; 1151 const fileTreeIds = (event.dataTransfer.types.includes(Constants.SIYUAN_DROP_FILE) && window.siyuan.dragElement) ? window.siyuan.dragElement.innerText : ""; 1152 if (event.shiftKey || (event.altKey && fileTreeIds.indexOf("-") === -1)) { 1153 const targetAssetElement = hasClosestBlock(event.target); 1154 if (targetAssetElement) { 1155 targetAssetElement.classList.remove("dragover__top", "protyle-wysiwyg--select", "dragover__bottom", "dragover__left", "dragover__right"); 1156 targetAssetElement.removeAttribute("select-start"); 1157 targetAssetElement.removeAttribute("select-end"); 1158 } else { 1159 // https://github.com/siyuan-note/siyuan/issues/14177 1160 editorElement.querySelectorAll(".dragover__top, .protyle-wysiwyg--select, .dragover__bottom, .dragover__left, .dragover__right").forEach((item: HTMLElement) => { 1161 item.classList.remove("dragover__top", "protyle-wysiwyg--select", "dragover__bottom", "dragover__left", "dragover__right"); 1162 item.removeAttribute("select-start"); 1163 item.removeAttribute("select-end"); 1164 }); 1165 } 1166 event.preventDefault(); 1167 return; 1168 } 1169 // 编辑器内文字拖拽或资源文件拖拽或按住 alt/shift 拖拽反链图标进入编辑器时不能运行 event.preventDefault(), 否则无光标; 需放在 !window.siyuan.dragElement 之后 1170 event.preventDefault(); 1171 targetElement = hasClosestByClassName(event.target, "av__gallery-item") || hasClosestByClassName(event.target, "av__gallery-add") || 1172 hasClosestByClassName(event.target, "av__row") || hasClosestByClassName(event.target, "av__row--util") || 1173 hasClosestBlock(event.target); 1174 if (targetElement && targetElement.getAttribute("data-av-type") === "gallery" && event.target.classList.contains("av__gallery")) { 1175 // 拖拽到属性视图 gallery 内,但没选中 item 1176 return; 1177 } 1178 const point = {x: event.clientX, y: event.clientY, className: ""}; 1179 1180 // 超级块中有a,b两个段落块,移动到 ab 之间的间隙 targetElement 会变为超级块,需修正为 a 1181 if (targetElement && (targetElement.classList.contains("bq") || targetElement.classList.contains("sb") || targetElement.classList.contains("list") || targetElement.classList.contains("li"))) { 1182 let prevElement = hasClosestBlock(document.elementFromPoint(point.x, point.y - 6)); 1183 while (prevElement && targetElement.contains(prevElement)) { 1184 if (prevElement.nextElementSibling?.getAttribute("data-node-id")) { 1185 targetElement = prevElement; 1186 } 1187 prevElement = prevElement.parentElement; 1188 } 1189 } 1190 1191 if (!targetElement) { 1192 if (event.clientY > editorElement.lastElementChild.getBoundingClientRect().bottom) { 1193 // 命中底部 1194 targetElement = editorElement.lastElementChild as HTMLElement; 1195 point.className = "dragover__bottom"; 1196 } else if (event.clientY < editorElement.firstElementChild.getBoundingClientRect().top) { 1197 // 命中顶部 1198 targetElement = editorElement.firstElementChild as HTMLElement; 1199 point.className = "dragover__top"; 1200 } else if (contentRect) { 1201 const editorPosition = { 1202 left: contentRect.left + parseInt(editorElement.style.paddingLeft), 1203 right: contentRect.left + protyle.contentElement.clientWidth - parseInt(editorElement.style.paddingRight) 1204 }; 1205 if (event.clientX < editorPosition.left) { 1206 // 左侧 1207 point.x = editorPosition.left; 1208 point.className = "dragover__left"; 1209 } else if (event.clientX >= editorPosition.right) { 1210 // 右侧 1211 point.x = editorPosition.right - 6; 1212 point.className = "dragover__right"; 1213 } 1214 targetElement = document.elementFromPoint(point.x, point.y) as HTMLElement; 1215 if (targetElement.classList.contains("protyle-wysiwyg")) { 1216 // 命中间隙 1217 targetElement = document.elementFromPoint(point.x, point.y - 6) as HTMLElement; 1218 } 1219 targetElement = hasTopClosestByAttribute(targetElement, "data-node-id", null); 1220 } 1221 } else if (targetElement && targetElement.classList.contains("list")) { 1222 if (gutterTypes[0] !== "nodelistitem") { 1223 targetElement = hasClosestBlock(document.elementFromPoint(event.clientX, event.clientY - 6)); 1224 } else { 1225 targetElement = hasClosestByClassName(document.elementFromPoint(event.clientX, event.clientY - 6), "li"); 1226 } 1227 } 1228 if (gutterType && gutterType.startsWith(`${Constants.SIYUAN_DROP_GUTTER}NodeAttributeView${Constants.ZWSP}Col${Constants.ZWSP}`.toLowerCase())) { 1229 // 表头只能拖拽到当前 av 的表头中 1230 targetElement = hasClosestByClassName(event.target, "av__cell"); 1231 if (targetElement) { 1232 const targetRowElement = hasClosestByClassName(targetElement, "av__row--header"); 1233 const dragRowElement = hasClosestByClassName(window.siyuan.dragElement, "av__row--header"); 1234 if (targetElement === window.siyuan.dragElement || !targetRowElement || !dragRowElement || 1235 (targetRowElement && dragRowElement && targetRowElement !== dragRowElement) 1236 ) { 1237 targetElement = false; 1238 } 1239 } 1240 } else if (targetElement && gutterType && gutterType.startsWith(`${Constants.SIYUAN_DROP_GUTTER}NodeAttributeViewRowMenu${Constants.ZWSP}`.toLowerCase())) { 1241 if ((!targetElement.classList.contains("av__row") && !targetElement.classList.contains("av__row--util")) || 1242 (window.siyuan.dragElement && !window.siyuan.dragElement.contains(targetElement))) { 1243 // 行只能拖拽当前 av 中 1244 targetElement = false; 1245 } else { 1246 const bodyElement = hasClosestByClassName(targetElement, "av__body"); 1247 if (bodyElement) { 1248 const blockElement = hasClosestBlock(bodyElement) as HTMLElement; 1249 const groupID = bodyElement.getAttribute("data-group-id"); 1250 // 模板、创建时间、更新时间 字段作为分组方式时不允许跨分组拖拽 https://github.com/siyuan-note/siyuan/issues/15553 1251 const isTCU = ["template", "created", "updated"].includes(bodyElement.getAttribute("data-dtype")); 1252 // 排序只能夸组拖拽 1253 const hasSort = blockElement.querySelector('.block__icon[data-type="av-sort"]')?.classList.contains("block__icon--active"); 1254 gutterTypes[2].split(",").find(item => { 1255 const sourceGroupID = item ? item.split("@")[1] : ""; 1256 if (sourceGroupID !== groupID && isTCU) { 1257 targetElement = false; 1258 return true; 1259 } 1260 if (sourceGroupID === groupID && hasSort) { 1261 targetElement = false; 1262 return true; 1263 } 1264 }); 1265 } 1266 } 1267 } else if (targetElement && gutterType && gutterType.startsWith(`${Constants.SIYUAN_DROP_GUTTER}NodeAttributeView${Constants.ZWSP}GalleryItem${Constants.ZWSP}`.toLowerCase())) { 1268 const containerElement = hasClosestByClassName(event.target, "av__container"); 1269 if (targetElement.classList.contains("av") || !containerElement || 1270 !containerElement.contains(window.siyuan.dragElement) || targetElement === window.siyuan.dragElement) { 1271 // gallery item 只能拖拽当前 av 中 1272 targetElement = false; 1273 } else { 1274 const bodyElement = hasClosestByClassName(targetElement, "av__body"); 1275 if (bodyElement) { 1276 const blockElement = hasClosestBlock(bodyElement) as HTMLElement; 1277 const groupID = bodyElement.getAttribute("data-group-id"); 1278 // 模板、创建时间、更新时间 字段作为分组方式时不允许跨分组拖拽 https://github.com/siyuan-note/siyuan/issues/15553 1279 const isTCU = ["template", "created", "updated"].includes(bodyElement.getAttribute("data-dtype")); 1280 // 排序只能夸组拖拽 1281 const hasSort = blockElement.querySelector('.block__icon[data-type="av-sort"]')?.classList.contains("block__icon--active"); 1282 gutterTypes[2].split(",").find(item => { 1283 const sourceGroupID = item ? item.split("@")[1] : ""; 1284 if (sourceGroupID !== groupID && isTCU) { 1285 targetElement = false; 1286 return true; 1287 } 1288 if (sourceGroupID === groupID && hasSort) { 1289 targetElement = false; 1290 return true; 1291 } 1292 }); 1293 } 1294 } 1295 } 1296 1297 if (!targetElement) { 1298 editorElement.querySelectorAll(".dragover__bottom, .dragover__top, .dragover, .dragover__left, .dragover__right").forEach((item: HTMLElement) => { 1299 item.classList.remove("dragover__top", "dragover__bottom", "dragover", "dragover__left", "dragover__right"); 1300 }); 1301 return; 1302 } 1303 const isNotAvItem = !targetElement.classList.contains("av__row") && 1304 !targetElement.classList.contains("av__row--util") && 1305 !targetElement.classList.contains("av__gallery-item") && 1306 !targetElement.classList.contains("av__gallery-add"); 1307 if (targetElement && dragoverElement && targetElement === dragoverElement) { 1308 // 性能优化,目标为同一个元素不再进行校验 1309 const nodeRect = targetElement.getBoundingClientRect(); 1310 editorElement.querySelectorAll(".dragover__left, .dragover__right, .dragover__bottom, .dragover__top, .dragover").forEach((item: HTMLElement) => { 1311 item.classList.remove("dragover__top", "dragover__bottom", "dragover__left", "dragover__right", "dragover"); 1312 item.removeAttribute("select-start"); 1313 item.removeAttribute("select-end"); 1314 }); 1315 // 文档树拖拽限制 1316 if (fileTreeIds.indexOf("-") > -1 && isNotAvItem) { 1317 if (!event.altKey) { 1318 return; 1319 } else if (fileTreeIds.split(",").includes(protyle.block.rootID) && event.altKey) { 1320 return; 1321 } 1322 } 1323 if (targetElement.getAttribute("data-type") === "NodeAttributeView" && hasClosestByTag(event.target, "TD")) { 1324 return; 1325 } 1326 if (point.className) { 1327 targetElement.classList.add(point.className); 1328 addDragover(targetElement); 1329 return; 1330 } 1331 // 忘记为什么要限定文档树的拖拽了,先放开 https://github.com/siyuan-note/siyuan/pull/13284#issuecomment-2503853135 1332 if (targetElement.getAttribute("data-type") === "NodeListItem") { 1333 if (event.clientY > nodeRect.top + nodeRect.height / 2) { 1334 targetElement.classList.add("dragover__bottom"); 1335 addDragover(targetElement); 1336 } else if (!targetElement.classList.contains("av__row--header")) { 1337 targetElement.classList.add("dragover__top"); 1338 addDragover(targetElement); 1339 } 1340 return; 1341 } 1342 1343 if (targetElement.classList.contains("av__cell")) { 1344 if (event.clientX < nodeRect.left + nodeRect.width / 2 && event.clientX > nodeRect.left && 1345 !targetElement.classList.contains("av__row") && targetElement.previousElementSibling !== window.siyuan.dragElement) { 1346 targetElement.classList.add("dragover__left"); 1347 } else if (event.clientX > nodeRect.right - nodeRect.width / 2 && event.clientX <= nodeRect.right + 1 && 1348 !targetElement.classList.contains("av__row") && targetElement !== window.siyuan.dragElement.previousElementSibling) { 1349 if (window.siyuan.dragElement.previousElementSibling.classList.contains("av__colsticky") && 1350 targetElement === window.siyuan.dragElement.previousElementSibling.lastElementChild) { 1351 // 拖拽到固定列的最后一个元素 1352 } else { 1353 targetElement.classList.add("dragover__right"); 1354 } 1355 } 1356 return; 1357 } 1358 // gallery 1359 if (targetElement.classList.contains("av__gallery-item")) { 1360 const midLeft = nodeRect.left + nodeRect.width / 2; 1361 if (event.clientX < midLeft && event.clientX > nodeRect.left - 13) { 1362 targetElement.classList.add("dragover__left"); 1363 } else if (event.clientX > midLeft && event.clientX <= nodeRect.right + 13) { 1364 targetElement.classList.add("dragover__right"); 1365 } 1366 return; 1367 } 1368 if (targetElement.classList.contains("av__gallery-add")) { 1369 targetElement.classList.add("dragover__left"); 1370 return; 1371 } 1372 1373 if (event.clientX < nodeRect.left + 32 && event.clientX >= nodeRect.left - 1 && 1374 !targetElement.classList.contains("av__row")) { 1375 targetElement.classList.add("dragover__left"); 1376 addDragover(targetElement); 1377 } else if (event.clientX > nodeRect.right - 32 && event.clientX < nodeRect.right && 1378 !targetElement.classList.contains("av__row")) { 1379 targetElement.classList.add("dragover__right"); 1380 addDragover(targetElement); 1381 } else if (targetElement.classList.contains("av__row--header")) { 1382 targetElement.classList.add("dragover__bottom"); 1383 } else if (targetElement.classList.contains("av__row--util")) { 1384 targetElement.previousElementSibling.classList.add("dragover__bottom"); 1385 } else { 1386 if (event.clientY > nodeRect.top + nodeRect.height / 2 && disabledPosition !== "bottom") { 1387 targetElement.classList.add("dragover__bottom"); 1388 addDragover(targetElement); 1389 } else if (disabledPosition !== "top") { 1390 targetElement.classList.add("dragover__top"); 1391 addDragover(targetElement); 1392 } 1393 } 1394 return; 1395 } 1396 1397 if (fileTreeIds.indexOf("-") > -1) { 1398 if (fileTreeIds.split(",").includes(protyle.block.rootID) && isNotAvItem && event.altKey) { 1399 dragoverElement = undefined; 1400 editorElement.querySelectorAll(".dragover__left, .dragover__right, .dragover__bottom, .dragover__top, .dragover").forEach((item: HTMLElement) => { 1401 item.classList.remove("dragover__top", "dragover__bottom", "dragover__left", "dragover__right", "dragover"); 1402 item.removeAttribute("select-start"); 1403 item.removeAttribute("select-end"); 1404 }); 1405 } else { 1406 dragoverElement = targetElement; 1407 } 1408 return; 1409 } 1410 1411 if (gutterType) { 1412 disabledPosition = ""; 1413 // gutter 文档内拖拽限制 1414 // 排除自己及子孙 1415 if (gutterTypes[0] === "nodeattributeview" && gutterTypes[1] === "col" && targetElement.getAttribute("data-id") === gutterTypes[2]) { 1416 // 表头不能拖到自己上 1417 clearDragoverElement(dragoverElement); 1418 return; 1419 } 1420 if (gutterTypes[0] === "nodeattributeviewrowmenu" && gutterTypes[2].split("@")[0] === targetElement.getAttribute("data-id")) { 1421 // 行不能拖到自己上 1422 clearDragoverElement(dragoverElement); 1423 return; 1424 } 1425 const isSelf = gutterTypes[2].split(",").find((item: string) => { 1426 if (item && hasClosestByAttribute(targetElement as HTMLElement, "data-node-id", item)) { 1427 return true; 1428 } 1429 }); 1430 if (isSelf && "nodeattributeviewrowmenu" !== gutterTypes[0]) { 1431 clearDragoverElement(dragoverElement); 1432 return; 1433 } 1434 if (isInEmbedBlock(targetElement)) { 1435 // 不允许托入嵌入块 1436 clearDragoverElement(dragoverElement); 1437 return; 1438 } 1439 if (gutterTypes[0] === "nodelistitem" && "NodeListItem" === targetElement.getAttribute("data-type")) { 1440 if (gutterTypes[1] !== targetElement.getAttribute("data-subtype")) { 1441 // 排除类型不同的列表项 1442 clearDragoverElement(dragoverElement); 1443 return; 1444 } 1445 // 选中非列表不能拖拽到列表中 https://github.com/siyuan-note/siyuan/issues/13822 1446 const notLiItem = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select")).find((item: HTMLElement) => { 1447 if (!item.classList.contains("li")) { 1448 return true; 1449 } 1450 }); 1451 if (notLiItem) { 1452 clearDragoverElement(dragoverElement); 1453 return; 1454 } 1455 } 1456 if (gutterTypes[0] !== "nodelistitem" && targetElement.getAttribute("data-type") === "NodeListItem") { 1457 // 非列表项不能拖入列表项周围 1458 clearDragoverElement(dragoverElement); 1459 return; 1460 } 1461 if (gutterTypes[0] === "nodelistitem" && targetElement.parentElement.classList.contains("li") && 1462 targetElement.previousElementSibling?.classList.contains("protyle-action")) { 1463 // 列表项不能拖入列表项中第一个元素之上 1464 disabledPosition = "top"; 1465 } 1466 if (gutterTypes[0] === "nodelistitem" && targetElement.nextElementSibling?.classList.contains("list")) { 1467 // 列表项不能拖入列表上方块的下面 1468 disabledPosition = "bottom"; 1469 } 1470 if (targetElement && targetElement.classList.contains("av__row--header")) { 1471 // 块不能拖在表头上 1472 disabledPosition = "top"; 1473 } 1474 dragoverElement = targetElement; 1475 } 1476 }); 1477 let counter = 0; 1478 editorElement.addEventListener("dragleave", (event: DragEvent & { target: HTMLElement }) => { 1479 if (protyle.disabled) { 1480 event.preventDefault(); 1481 event.stopPropagation(); 1482 return; 1483 } 1484 counter--; 1485 if (counter === 0) { 1486 editorElement.querySelectorAll(".dragover__left, .dragover__right, .dragover__bottom, .dragover__top, .dragover").forEach((item: HTMLElement) => { 1487 item.classList.remove("dragover__top", "dragover__bottom", "dragover__left", "dragover__right", "dragover"); 1488 }); 1489 dragoverElement = undefined; 1490 } 1491 }); 1492 editorElement.addEventListener("dragenter", (event) => { 1493 event.preventDefault(); 1494 counter++; 1495 }); 1496 editorElement.addEventListener("dragend", () => { 1497 if (window.siyuan.dragElement) { 1498 window.siyuan.dragElement.style.opacity = ""; 1499 window.siyuan.dragElement = undefined; 1500 document.onmousemove = null; 1501 } 1502 }); 1503}; 1504 1505const addDragover = (element: HTMLElement) => { 1506 if (element.classList.contains("sb") || 1507 element.classList.contains("li") || 1508 element.classList.contains("list") || 1509 element.classList.contains("bq")) { 1510 element.classList.add("dragover"); 1511 } 1512}; 1513 1514// https://github.com/siyuan-note/siyuan/issues/12651 1515const clearDragoverElement = (element: Element) => { 1516 if (element) { 1517 element.classList.remove("dragover__top", "dragover__bottom", "dragover__left", "dragover__right", "dragover"); 1518 element = undefined; 1519 } 1520};