diff --git a/app/src/protyle/util/editorCommonEvent.ts b/app/src/protyle/util/editorCommonEvent.ts index 8cdd2b285..2c42a201f 100644 --- a/app/src/protyle/util/editorCommonEvent.ts +++ b/app/src/protyle/util/editorCommonEvent.ts @@ -20,6 +20,107 @@ import {insertHTML} from "./insertHTML"; import {isBrowser} from "../../util/functions"; import {hideElements} from "../ui/hideElements"; +const moveToNew = (protyle: IProtyle, sourceElements: Element[], targetElement: Element, newSourceElement: Element, + isSameDoc: boolean, isBottom: boolean, isCopy: boolean) => { + let topSourceElement; + const targetId = targetElement.getAttribute("data-node-id"); + const newSourceId = newSourceElement.getAttribute("data-node-id") + const doOperations: IOperation[] = []; + const undoOperations: IOperation[] = []; + targetElement.insertAdjacentElement(isBottom ? "afterend" : "beforebegin", newSourceElement); + if (isBottom) { + doOperations.push({ + action: "insert", + data: newSourceElement.outerHTML, + id: newSourceId, + previousID: targetId, + }) + } else { + doOperations.push({ + action: "insert", + data: newSourceElement.outerHTML, + id: newSourceId, + nextID: targetId, + }) + } + sourceElements.reverse().forEach((item, index) => { + const itemId = item.getAttribute("data-node-id") + if (index === sourceElements.length - 1) { + topSourceElement = getTopAloneElement(item); + if (topSourceElement.isSameNode(item)) { + topSourceElement = undefined; + } + } + const copyId = Lute.NewNodeID() + if (isCopy) { + undoOperations.push({ + action: "delete", + id: copyId, + }); + } else { + undoOperations.push({ + action: "move", + id: itemId, + previousID: item.previousElementSibling?.getAttribute("data-node-id"), + parentID: item.parentElement.getAttribute("data-node-id") || protyle.block.rootID, + }); + } + if (!isSameDoc && !isCopy) { + // 打开两个相同的文档 + const sameElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${itemId}"]`); + if (sameElement) { + sameElement.remove(); + } + } + if (isCopy) { + const copyElement = item.cloneNode(true) as HTMLElement; + copyElement.setAttribute("data-node-id", copyId); + copyElement.querySelectorAll("[data-node-id]").forEach((e) => { + const newId = Lute.NewNodeID(); + e.setAttribute("data-node-id", newId); + e.setAttribute("updated", newId.split("-")[0]); + }); + newSourceElement.insertAdjacentElement("afterbegin", copyElement); + doOperations.push({ + action: "insert", + id: copyId, + data: copyElement.outerHTML, + parentID: newSourceId, + }); + } else { + newSourceElement.insertAdjacentElement("afterbegin", item); + doOperations.push({ + action: "move", + id: itemId, + parentID: newSourceId, + }); + } + }); + undoOperations.reverse(); + if (newSourceElement.getAttribute("data-subtype") === "o") { + undoOperations.splice(0, 0, { + action: "update", + id: newSourceId, + data: newSourceElement.outerHTML + }); + updateListOrder(newSourceElement, 1); + doOperations.push({ + action: "update", + id: newSourceId, + data: newSourceElement.outerHTML + }); + } + undoOperations.push({ + action: "delete", + id: newSourceId, + }); + return { + doOperations, + undoOperations, + topSourceElement, + }; +} + const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElement: Element, isSameDoc: boolean, position: InsertPosition, isCopy: boolean) => { let topSourceElement; @@ -42,7 +143,9 @@ const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElemen } if (item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") { item.removeAttribute("fold"); - foldHeadingIds.push({id, parentID}); + if (isCopy) { + foldHeadingIds.push({id, parentID}); + } } let copyId let copyElement @@ -126,7 +229,8 @@ const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElemen }; }; -const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElement: Element, isBottom: boolean, direct: "col" | "row") => { +const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElement: Element, isBottom: boolean, + direct: "col" | "row", isCopy: boolean) => { const isSameDoc = protyle.element.contains(sourceElements[0]); let newSourceElement: HTMLElement; @@ -158,6 +262,7 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen parentID: sbElement.parentElement.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID }]; if (newSourceElement) { + const newSourceId = newSourceElement.getAttribute("data-node-id") sbElement.insertAdjacentElement("afterbegin", targetElement); doOperations.push({ action: "move", @@ -169,7 +274,7 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen doOperations.push({ action: "insert", data: newSourceElement.outerHTML, - id: newSourceElement.getAttribute("data-node-id"), + id: newSourceId, previousID: targetElement.getAttribute("data-node-id"), }); } else { @@ -177,7 +282,7 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen doOperations.push({ action: "insert", data: newSourceElement.outerHTML, - id: newSourceElement.getAttribute("data-node-id"), + id: newSourceId, nextID: targetElement.getAttribute("data-node-id"), }); } @@ -188,30 +293,55 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen topSourceElement = undefined; } } - undoOperations.push({ - action: "move", - id: item.getAttribute("data-node-id"), - previousID: item.previousElementSibling?.getAttribute("data-node-id"), - parentID: item.parentElement.getAttribute("data-node-id") || protyle.block.rootID, - }); - if (!isSameDoc) { + const copyId = Lute.NewNodeID() + if (isCopy) { + undoOperations.push({ + action: "delete", + id: copyId + }); + } else { + undoOperations.push({ + action: "move", + id: item.getAttribute("data-node-id"), + previousID: item.previousElementSibling?.getAttribute("data-node-id"), + parentID: item.parentElement.getAttribute("data-node-id") || protyle.block.rootID, + }); + } + if (!isSameDoc && !isCopy) { // 打开两个相同的文档 const sameElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${item.getAttribute("data-node-id")}"]`); if (sameElement) { sameElement.remove(); } } - newSourceElement.insertAdjacentElement("afterbegin", item); - doOperations.push({ - action: "move", - id: item.getAttribute("data-node-id"), - parentID: newSourceElement.getAttribute("data-node-id"), - }); + if (isCopy) { + const copyElement = item.cloneNode(true) as HTMLElement; + copyElement.setAttribute("data-node-id", copyId); + copyElement.querySelectorAll("[data-node-id]").forEach((e) => { + const newId = Lute.NewNodeID(); + e.setAttribute("data-node-id", newId); + e.setAttribute("updated", newId.split("-")[0]); + }); + newSourceElement.insertAdjacentElement("afterbegin", copyElement); + doOperations.push({ + action: "insert", + id: copyId, + data: copyElement.outerHTML, + parentID: newSourceId, + }); + } else { + newSourceElement.insertAdjacentElement("afterbegin", item); + doOperations.push({ + action: "move", + id: item.getAttribute("data-node-id"), + parentID: newSourceId, + }); + } }); undoOperations.reverse(); undoOperations.push({ action: "delete", - id: newSourceElement.getAttribute("data-node-id"), + id: newSourceId, }); } else { const foldHeadingIds: { id: string, parentID: string }[] = []; @@ -225,32 +355,59 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen topSourceElement = undefined; } } + const copyId = Lute.NewNodeID() if (index === 0) { - afterPreviousID = id; + afterPreviousID = isCopy ? copyId : id; } if (item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") { item.removeAttribute("fold"); - foldHeadingIds.push({id, parentID}); + if (isCopy) { + foldHeadingIds.push({id, parentID}); + } } - undoOperations.push({ - action: "move", - id, - previousID: item.previousElementSibling?.getAttribute("data-node-id"), - parentID - }); - if (!isSameDoc) { + if (isCopy) { + undoOperations.push({ + action: "delete", + id: copyId, + }); + } else { + undoOperations.push({ + action: "move", + id, + previousID: item.previousElementSibling?.getAttribute("data-node-id"), + parentID + }); + } + if (!isSameDoc && !isCopy) { // 打开两个相同的文档 const sameElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${id}"]`); if (sameElement) { sameElement.remove(); } } - sbElement.insertAdjacentElement("afterbegin", item); - doOperations.push({ - action: "move", - id, - parentID: sbElement.getAttribute("data-node-id"), - }); + if (isCopy) { + const copyElement = item.cloneNode(true) as HTMLElement; + copyElement.setAttribute("data-node-id", copyId); + copyElement.querySelectorAll("[data-node-id]").forEach((e) => { + const newId = Lute.NewNodeID(); + e.setAttribute("data-node-id", newId); + e.setAttribute("updated", newId.split("-")[0]); + }); + sbElement.insertAdjacentElement("afterbegin", copyElement); + doOperations.push({ + action: "insert", + id: copyId, + data: copyElement.outerHTML, + parentID: sbElement.getAttribute("data-node-id"), + }); + } else { + sbElement.insertAdjacentElement("afterbegin", item); + doOperations.push({ + action: "move", + id, + parentID: sbElement.getAttribute("data-node-id"), + }); + } }); undoOperations.reverse(); for (let j = 0; j < foldHeadingIds.length; j++) { @@ -298,7 +455,7 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen id: sbElement.getAttribute("data-node-id"), }); // https://github.com/siyuan-note/insider/issues/536 - if (oldSourceParentElement && oldSourceParentElement.classList.contains("list") && + if (!isCopy && oldSourceParentElement && oldSourceParentElement.classList.contains("list") && oldSourceParentElement.getAttribute("data-subtype") === "o" && !oldSourceParentElement.isSameNode(sourceElements[0].parentElement) && oldSourceParentElement.childElementCount > 1) { Array.from(oldSourceParentElement.children).forEach((item) => { @@ -325,7 +482,7 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen }); } // 删除空元素 - if (topSourceElement) { + if (!isCopy && topSourceElement) { doOperations.push({ action: "delete", id: topSourceElement.getAttribute("data-node-id"), @@ -347,12 +504,12 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen oldSourceParentElement = topSourceElement.parentElement; topSourceElement.remove(); } - if (oldSourceParentElement && oldSourceParentElement.classList.contains("sb") && oldSourceParentElement.childElementCount === 2) { + if (!isCopy && oldSourceParentElement && oldSourceParentElement.classList.contains("sb") && oldSourceParentElement.childElementCount === 2) { // 拖拽后,sb 只剩下一个元素 const sbData = cancelSB(protyle, oldSourceParentElement); doOperations.push(sbData.doOperations[0], sbData.doOperations[1]); undoOperations.splice(0, 0, sbData.undoOperations[0], sbData.undoOperations[1]); - } else if (oldSourceParentElement && oldSourceParentElement.classList.contains("protyle-wysiwyg") && oldSourceParentElement.innerHTML === "") { + } else if (!isCopy && oldSourceParentElement && oldSourceParentElement.classList.contains("protyle-wysiwyg") && oldSourceParentElement.innerHTML === "") { /// #if !MOBILE // 拖拽后,根文档原内容为空,且不为悬浮窗 const protyleElement = hasClosestByClassName(oldSourceParentElement, "protyle", true); @@ -374,7 +531,7 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen } /// #endif } - if (isSameDoc) { + if (isSameDoc || isCopy) { transaction(protyle, doOperations, undoOperations); } else { // 跨文档不支持撤销 @@ -399,61 +556,12 @@ const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElem } let topSourceElement: Element; let oldSourceParentElement = sourceElements[0].parentElement; - const targetId = targetElement.getAttribute("data-node-id"); if (isBottom) { if (newSourceElement) { - targetElement.insertAdjacentElement("afterend", newSourceElement); - doOperations.push({ - action: "insert", - data: newSourceElement.outerHTML, - id: newSourceElement.getAttribute("data-node-id"), - previousID: targetId, - }); - sourceElements.reverse().forEach((item, index) => { - if (index === sourceElements.length - 1) { - topSourceElement = getTopAloneElement(item); - if (topSourceElement.isSameNode(item)) { - topSourceElement = undefined; - } - } - undoOperations.push({ - action: "move", - id: item.getAttribute("data-node-id"), - previousID: item.previousElementSibling?.getAttribute("data-node-id"), - parentID: item.parentElement.getAttribute("data-node-id") || protyle.block.rootID, - }); - if (!isSameDoc) { - // 打开两个相同的文档 - const sameElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${item.getAttribute("data-node-id")}"]`); - if (sameElement) { - sameElement.remove(); - } - } - newSourceElement.insertAdjacentElement("afterbegin", item); - doOperations.push({ - action: "move", - id: item.getAttribute("data-node-id"), - parentID: newSourceElement.getAttribute("data-node-id"), - }); - }); - undoOperations.reverse(); - if (newSourceElement.getAttribute("data-subtype") === "o") { - undoOperations.splice(0, 0, { - action: "update", - id: newSourceElement.getAttribute("data-node-id"), - data: newSourceElement.outerHTML - }); - updateListOrder(newSourceElement, 1); - doOperations.push({ - action: "update", - id: newSourceElement.getAttribute("data-node-id"), - data: newSourceElement.outerHTML - }); - } - undoOperations.push({ - action: "delete", - id: newSourceElement.getAttribute("data-node-id"), - }); + const moveToResult = moveToNew(protyle, sourceElements, targetElement, newSourceElement, isSameDoc, isBottom, isCopy); + doOperations.push(...moveToResult.doOperations); + undoOperations.push(...moveToResult.undoOperations); + topSourceElement = moveToResult.topSourceElement; } else { const moveToResult = await moveTo(protyle, sourceElements, targetElement, isSameDoc, "afterend", isCopy); doOperations.push(...moveToResult.doOperations); @@ -462,58 +570,10 @@ const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElem } } else { if (newSourceElement) { - targetElement.insertAdjacentElement("beforebegin", newSourceElement); - doOperations.push({ - action: "insert", - data: newSourceElement.outerHTML, - id: newSourceElement.getAttribute("data-node-id"), - nextID: targetId, - }); - sourceElements.reverse().forEach((item, index) => { - if (index === sourceElements.length - 1) { - topSourceElement = getTopAloneElement(item); - if (topSourceElement.isSameNode(item)) { - topSourceElement = undefined; - } - } - undoOperations.push({ - action: "move", - id: item.getAttribute("data-node-id"), - previousID: item.previousElementSibling?.getAttribute("data-node-id"), - parentID: item.parentElement.getAttribute("data-node-id") || protyle.block.rootID, - }); - if (!isSameDoc) { - // 打开两个相同的文档 - const sameElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${item.getAttribute("data-node-id")}"]`); - if (sameElement) { - sameElement.remove(); - } - } - newSourceElement.insertAdjacentElement("afterbegin", item); - doOperations.push({ - action: "move", - id: item.getAttribute("data-node-id"), - parentID: newSourceElement.getAttribute("data-node-id"), - }); - }); - undoOperations.reverse(); - if (newSourceElement.getAttribute("data-subtype") === "o") { - undoOperations.splice(0, 0, { - action: "update", - id: newSourceElement.getAttribute("data-node-id"), - data: newSourceElement.outerHTML - }); - updateListOrder(newSourceElement, 1); - doOperations.push({ - action: "update", - id: newSourceElement.getAttribute("data-node-id"), - data: newSourceElement.outerHTML - }); - } - undoOperations.push({ - action: "delete", - id: newSourceElement.getAttribute("data-node-id"), - }); + const moveToResult = moveToNew(protyle, sourceElements, targetElement, newSourceElement, isSameDoc, isBottom, isCopy); + doOperations.push(...moveToResult.doOperations); + undoOperations.push(...moveToResult.undoOperations); + topSourceElement = moveToResult.topSourceElement; } else { const moveToResult = await moveTo(protyle, sourceElements, targetElement, isSameDoc, "beforebegin", isCopy); doOperations.push(...moveToResult.doOperations); @@ -719,11 +779,11 @@ export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => { if (targetClass.includes("dragover__left") || targetClass.includes("dragover__right")) { dragSame(protyle, sourceElements, targetElement, targetClass.includes("dragover__right"), event.ctrlKey); } else { - dragSb(protyle, sourceElements, targetElement, targetClass.includes("dragover__bottom"), "row"); + dragSb(protyle, sourceElements, targetElement, targetClass.includes("dragover__bottom"), "row", event.ctrlKey); } } else { if (targetClass.includes("dragover__left") || targetClass.includes("dragover__right")) { - dragSb(protyle, sourceElements, targetElement, targetClass.includes("dragover__right"), "col"); + dragSb(protyle, sourceElements, targetElement, targetClass.includes("dragover__right"), "col", event.ctrlKey); } else { dragSame(protyle, sourceElements, targetElement, targetClass.includes("dragover__bottom"), event.ctrlKey); }