A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
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};