This commit is contained in:
Vanessa 2025-10-04 15:34:00 +08:00
parent db31c47760
commit 2b0e8feee2
3 changed files with 43 additions and 80 deletions

View file

@ -2433,18 +2433,19 @@ export const tableMenu = (protyle: IProtyle, nodeElement: Element, cellElement:
return {menus, removeMenus, insertMenus, otherMenus, other2Menus}; return {menus, removeMenus, insertMenus, otherMenus, other2Menus};
}; };
export const setFold = (protyle: IProtyle, nodeElement: Element, isOpen?: boolean, isRemove?: boolean, addLoading = true) => { export const setFold = (protyle: IProtyle, nodeElement: Element, isOpen?: boolean,
isRemove?: boolean, addLoading = true, getOperations = false) => {
if (nodeElement.getAttribute("data-type") === "NodeListItem" && nodeElement.childElementCount < 4) { if (nodeElement.getAttribute("data-type") === "NodeListItem" && nodeElement.childElementCount < 4) {
// 没有子列表或多个块的列表项不进行折叠 // 没有子列表或多个块的列表项不进行折叠
return -1; return {fold: -1};
} }
if (nodeElement.getAttribute("data-type") === "NodeThematicBreak") { if (nodeElement.getAttribute("data-type") === "NodeThematicBreak") {
return -1; return {fold: -1};
} }
const hasFold = nodeElement.getAttribute("fold") === "1"; const hasFold = nodeElement.getAttribute("fold") === "1";
if (hasFold) { if (hasFold) {
if (typeof isOpen === "boolean" && !isOpen) { if (typeof isOpen === "boolean" && !isOpen) {
return -1; return {fold: -1};
} }
nodeElement.removeAttribute("fold"); nodeElement.removeAttribute("fold");
// https://github.com/siyuan-note/siyuan/issues/4411 // https://github.com/siyuan-note/siyuan/issues/4411
@ -2453,7 +2454,7 @@ export const setFold = (protyle: IProtyle, nodeElement: Element, isOpen?: boolea
}); });
} else { } else {
if (typeof isOpen === "boolean" && isOpen) { if (typeof isOpen === "boolean" && isOpen) {
return -1; return {fold: -1};
} }
nodeElement.setAttribute("fold", "1"); nodeElement.setAttribute("fold", "1");
// 光标在子列表中,再次 focus 段尾的时候不会变 https://ld246.com/article/1647099132461 // 光标在子列表中,再次 focus 段尾的时候不会变 https://ld246.com/article/1647099132461
@ -2468,41 +2469,49 @@ export const setFold = (protyle: IProtyle, nodeElement: Element, isOpen?: boolea
clearSelect(["img", "av"], nodeElement); clearSelect(["img", "av"], nodeElement);
} }
const id = nodeElement.getAttribute("data-node-id"); const id = nodeElement.getAttribute("data-node-id");
const doOperations: IOperation[] = [];
const undoOperations: IOperation[] = [];
if (nodeElement.getAttribute("data-type") === "NodeHeading") { if (nodeElement.getAttribute("data-type") === "NodeHeading") {
if (hasFold) { if (hasFold) {
if (addLoading) { if (addLoading) {
nodeElement.insertAdjacentHTML("beforeend", '<div spin="1" style="text-align: center"><img width="24px" height="24px" src="/stage/loading-pure.svg"></div>'); nodeElement.insertAdjacentHTML("beforeend", '<div spin="1" style="text-align: center"><img width="24px" height="24px" src="/stage/loading-pure.svg"></div>');
} }
transaction(protyle, [{ doOperations.push({
action: "unfoldHeading", action: "unfoldHeading",
id, id,
data: isRemove ? "remove" : undefined, data: isRemove ? "remove" : undefined,
}], [{ });
undoOperations.push({
action: "foldHeading", action: "foldHeading",
id id
}]); });
} else { } else {
transaction(protyle, [{ doOperations.push({
action: "foldHeading", action: "foldHeading",
id id
}], [{ });
undoOperations.push({
action: "unfoldHeading", action: "unfoldHeading",
id id
}]); });
removeFoldHeading(nodeElement); removeFoldHeading(nodeElement);
} }
} else { } else {
transaction(protyle, [{ doOperations.push({
action: "setAttrs", action: "setAttrs",
id, id,
data: JSON.stringify({fold: hasFold ? "" : "1"}) data: JSON.stringify({fold: hasFold ? "" : "1"})
}], [{ });
undoOperations.push({
action: "setAttrs", action: "setAttrs",
id, id,
data: JSON.stringify({fold: hasFold ? "1" : ""}) data: JSON.stringify({fold: hasFold ? "1" : ""})
}]); });
}
if (!getOperations) {
transaction(protyle, doOperations, undoOperations);
} }
// 折叠后,防止滚动条滚动后调用 get 请求 https://github.com/siyuan-note/siyuan/issues/2248 // 折叠后,防止滚动条滚动后调用 get 请求 https://github.com/siyuan-note/siyuan/issues/2248
preventScroll(protyle); preventScroll(protyle);
return !hasFold ? 1 : 0; return {fold: !hasFold ? 1 : 0, undoOperations, doOperations};
}; };

View file

@ -253,7 +253,7 @@ export class Gutter {
transaction(protyle, doOperations, undoOperations); transaction(protyle, doOperations, undoOperations);
buttonElement.removeAttribute("disabled"); buttonElement.removeAttribute("disabled");
} else { } else {
const foldStatus = setFold(protyle, foldElement); const foldStatus = setFold(protyle, foldElement).fold;
if (foldStatus === 1) { if (foldStatus === 1) {
(buttonElement.firstElementChild as HTMLElement).style.transform = ""; (buttonElement.firstElementChild as HTMLElement).style.transform = "";
} else if (foldStatus === 0) { } else if (foldStatus === 0) {
@ -396,7 +396,7 @@ export class Gutter {
}); });
transaction(protyle, doOperations, undoOperations); transaction(protyle, doOperations, undoOperations);
} else { } else {
const hasFold = setFold(protyle, foldElement); const hasFold = setFold(protyle, foldElement).fold;
const foldArrowElement = buttonElement.parentElement.querySelector("[data-type='fold'] > svg") as HTMLElement; const foldArrowElement = buttonElement.parentElement.querySelector("[data-type='fold'] > svg") as HTMLElement;
if (hasFold !== -1 && foldArrowElement) { if (hasFold !== -1 && foldArrowElement) {
foldArrowElement.style.transform = hasFold === 0 ? "rotate(90deg)" : ""; foldArrowElement.style.transform = hasFold === 0 ? "rotate(90deg)" : "";

View file

@ -27,7 +27,7 @@ import {hideElements} from "../ui/hideElements";
import {insertAttrViewBlockAnimation} from "../render/av/row"; import {insertAttrViewBlockAnimation} from "../render/av/row";
import {dragUpload} from "../render/av/asset"; import {dragUpload} from "../render/av/asset";
import * as dayjs from "dayjs"; import * as dayjs from "dayjs";
import {zoomOut} from "../../menus/protyle"; import {setFold, zoomOut} from "../../menus/protyle";
/// #if !BROWSER /// #if !BROWSER
import {webUtils} from "electron"; import {webUtils} from "electron";
/// #endif /// #endif
@ -44,25 +44,20 @@ const moveToNew = (protyle: IProtyle, sourceElements: Element[], targetElement:
const newSourceId = newSourceElement.getAttribute("data-node-id"); const newSourceId = newSourceElement.getAttribute("data-node-id");
const doOperations: IOperation[] = []; const doOperations: IOperation[] = [];
const undoOperations: IOperation[] = []; const undoOperations: IOperation[] = [];
let ignoreInsert = false; let foldData;
if (isBottom && if (isBottom &&
targetElement.getAttribute("data-type") === "NodeHeading" && targetElement.getAttribute("data-type") === "NodeHeading" &&
targetElement.getAttribute("fold") === "1") { targetElement.getAttribute("fold") === "1") {
ignoreInsert = true; foldData = setFold(protyle, targetElement, true, false, false, true);
} else if (!isBottom && targetElement.previousElementSibling && } else if (!isBottom && targetElement.previousElementSibling &&
targetElement.previousElementSibling.getAttribute("data-type") === "NodeHeading" && targetElement.previousElementSibling.getAttribute("data-type") === "NodeHeading" &&
targetElement.previousElementSibling.getAttribute("fold") === "1") { targetElement.previousElementSibling.getAttribute("fold") === "1") {
ignoreInsert = true; foldData = setFold(protyle, targetElement.previousElementSibling, true, false, false, true);
} }
if (!ignoreInsert) {
targetElement.insertAdjacentElement(isBottom ? "afterend" : "beforebegin", newSourceElement); targetElement.insertAdjacentElement(isBottom ? "afterend" : "beforebegin", newSourceElement);
}
if (isBottom) { if (isBottom) {
doOperations.push({ doOperations.push({
action: "insert", action: "insert",
context: {
ignoreProcess: ignoreInsert.toString(),
},
data: newSourceElement.outerHTML, data: newSourceElement.outerHTML,
id: newSourceId, id: newSourceId,
previousID: targetId, previousID: targetId,
@ -70,9 +65,6 @@ const moveToNew = (protyle: IProtyle, sourceElements: Element[], targetElement:
} else { } else {
doOperations.push({ doOperations.push({
action: "insert", action: "insert",
context: {
ignoreProcess: ignoreInsert.toString(),
},
data: newSourceElement.outerHTML, data: newSourceElement.outerHTML,
id: newSourceId, id: newSourceId,
nextID: targetId, nextID: targetId,
@ -156,8 +148,9 @@ const moveToNew = (protyle: IProtyle, sourceElements: Element[], targetElement:
action: "delete", action: "delete",
id: newSourceId, id: newSourceId,
}); });
doOperations.push(...foldData.doOperations);
undoOperations.push(...foldData.undoOperations);
return { return {
ignoreInsert,
doOperations, doOperations,
undoOperations, undoOperations,
topSourceElement, topSourceElement,
@ -172,28 +165,15 @@ const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElemen
const copyFoldHeadingIds: { newId: string, oldId: string }[] = []; const copyFoldHeadingIds: { newId: string, oldId: string }[] = [];
const targetId = targetElement.getAttribute("data-node-id"); const targetId = targetElement.getAttribute("data-node-id");
let tempTargetElement = targetElement; let tempTargetElement = targetElement;
let ignoreInsert = ""; let foldData;
const targetPreviousId = targetElement.previousElementSibling?.getAttribute("data-node-id");
if (position === "afterend" && if (position === "afterend" &&
targetElement.getAttribute("data-type") === "NodeHeading" && targetElement.getAttribute("data-type") === "NodeHeading" &&
targetElement.getAttribute("fold") === "1") { targetElement.getAttribute("fold") === "1") {
ignoreInsert = targetElement.getAttribute("data-subtype")?.replace("h", ""); foldData = setFold(protyle, targetElement, true, false, false, true);
} else if (position === "beforebegin" && targetElement.previousElementSibling && } else if (position === "beforebegin" && targetElement.previousElementSibling &&
targetElement.previousElementSibling.getAttribute("data-type") === "NodeHeading" && targetElement.previousElementSibling.getAttribute("data-type") === "NodeHeading" &&
targetElement.previousElementSibling.getAttribute("fold") === "1") { targetElement.previousElementSibling.getAttribute("fold") === "1") {
ignoreInsert = targetElement.getAttribute("data-subtype")?.replace("h", ""); foldData = setFold(protyle, targetElement.previousElementSibling, true, false, false, true);
}
if (ignoreInsert) {
let breakIgnore = false;
sourceElements.forEach(item => {
if (item.getAttribute("data-type") === "NodeHeading" &&
parseInt(item.getAttribute("data-subtype").replace("h", "")) >= parseInt(ignoreInsert)) {
breakIgnore = true;
}
if (!breakIgnore) {
item.setAttribute("data-remove", "true");
}
});
} }
sourceElements.reverse().forEach((item, index) => { sourceElements.reverse().forEach((item, index) => {
const id = item.getAttribute("data-node-id"); const id = item.getAttribute("data-node-id");
@ -236,11 +216,7 @@ const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElemen
sameElement.remove(); sameElement.remove();
} }
} }
const needInset = !ignoreInsert || (ignoreInsert && !item.hasAttribute("data-remove"));
if (isCopy) { if (isCopy) {
item.removeAttribute("data-remove");
copyElement = item.cloneNode(true) as HTMLElement; copyElement = item.cloneNode(true) as HTMLElement;
copyElement.setAttribute("data-node-id", copyNewId); copyElement.setAttribute("data-node-id", copyNewId);
copyElement.querySelectorAll("[data-node-id]").forEach((e) => { copyElement.querySelectorAll("[data-node-id]").forEach((e) => {
@ -248,30 +224,20 @@ const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElemen
e.setAttribute("data-node-id", newId); e.setAttribute("data-node-id", newId);
e.setAttribute("updated", newId.split("-")[0]); e.setAttribute("updated", newId.split("-")[0]);
}); });
if (needInset) {
tempTargetElement.insertAdjacentElement(position, copyElement); tempTargetElement.insertAdjacentElement(position, copyElement);
}
doOperations.push({ doOperations.push({
action: "insert", action: "insert",
context: {
ignoreProcess: (!needInset).toString(),
},
id: copyNewId, id: copyNewId,
data: copyElement.outerHTML, data: copyElement.outerHTML,
previousID: position === "afterend" ? targetId : (!needInset ? targetPreviousId : copyElement.previousElementSibling?.getAttribute("data-node-id")), // 不能使用常量,移动后会被修改 previousID: position === "afterend" ? targetId : copyElement.previousElementSibling?.getAttribute("data-node-id"), // 不能使用常量,移动后会被修改
parentID: copyElement.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID, parentID: copyElement.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID,
}); });
} else { } else {
if (needInset) {
tempTargetElement.insertAdjacentElement(position, item); tempTargetElement.insertAdjacentElement(position, item);
}
doOperations.push({ doOperations.push({
action: "move", action: "move",
context: {
ignoreProcess: (!needInset).toString(),
},
id, id,
previousID: position === "afterend" ? targetId : (!needInset ? targetPreviousId : item.previousElementSibling?.getAttribute("data-node-id")), // 不能使用常量,移动后会被修改 previousID: position === "afterend" ? targetId : item.previousElementSibling?.getAttribute("data-node-id"), // 不能使用常量,移动后会被修改
parentID: item.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID, parentID: item.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID,
}); });
} }
@ -279,14 +245,6 @@ const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElemen
tempTargetElement = isCopy ? copyElement : item; tempTargetElement = isCopy ? copyElement : item;
} }
}); });
if (ignoreInsert) {
// 不能在上一个循环中移除,否则会影响位置的判断和 tempTargetElement
sourceElements.forEach(item => {
if (item.hasAttribute("data-remove")) {
item.remove();
}
});
}
undoOperations.reverse(); undoOperations.reverse();
for (let j = 0; j < copyFoldHeadingIds.length; j++) { for (let j = 0; j < copyFoldHeadingIds.length; j++) {
const childrenItem = copyFoldHeadingIds[j]; const childrenItem = copyFoldHeadingIds[j];
@ -297,8 +255,9 @@ const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElemen
doOperations.push(...responseTransaction.data.doOperations); doOperations.push(...responseTransaction.data.doOperations);
undoOperations.push(...responseTransaction.data.undoOperations); undoOperations.push(...responseTransaction.data.undoOperations);
} }
doOperations.push(...foldData.doOperations);
undoOperations.push(...foldData.undoOperations);
return { return {
ignoreInsert: ignoreInsert ? true : false,
doOperations, doOperations,
undoOperations, undoOperations,
topSourceElement, topSourceElement,
@ -663,7 +622,6 @@ const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElem
newSourceElement.insertAdjacentHTML("beforeend", `<div class="protyle-attr" contenteditable="false">${Constants.ZWSP}</div>`); newSourceElement.insertAdjacentHTML("beforeend", `<div class="protyle-attr" contenteditable="false">${Constants.ZWSP}</div>`);
} }
let topSourceElement: Element; let topSourceElement: Element;
let ignoreInsert = false;
let oldSourceParentElement = sourceElements[0].parentElement; let oldSourceParentElement = sourceElements[0].parentElement;
if (isBottom) { if (isBottom) {
if (newSourceElement) { if (newSourceElement) {
@ -671,13 +629,11 @@ const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElem
doOperations.push(...moveToResult.doOperations); doOperations.push(...moveToResult.doOperations);
undoOperations.push(...moveToResult.undoOperations); undoOperations.push(...moveToResult.undoOperations);
topSourceElement = moveToResult.topSourceElement; topSourceElement = moveToResult.topSourceElement;
ignoreInsert = moveToResult.ignoreInsert;
} else { } else {
const moveToResult = await moveTo(protyle, sourceElements, targetElement, isSameDoc, "afterend", isCopy); const moveToResult = await moveTo(protyle, sourceElements, targetElement, isSameDoc, "afterend", isCopy);
doOperations.push(...moveToResult.doOperations); doOperations.push(...moveToResult.doOperations);
undoOperations.push(...moveToResult.undoOperations); undoOperations.push(...moveToResult.undoOperations);
topSourceElement = moveToResult.topSourceElement; topSourceElement = moveToResult.topSourceElement;
ignoreInsert = moveToResult.ignoreInsert;
} }
} else { } else {
if (newSourceElement) { if (newSourceElement) {
@ -685,13 +641,11 @@ const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElem
doOperations.push(...moveToResult.doOperations); doOperations.push(...moveToResult.doOperations);
undoOperations.push(...moveToResult.undoOperations); undoOperations.push(...moveToResult.undoOperations);
topSourceElement = moveToResult.topSourceElement; topSourceElement = moveToResult.topSourceElement;
ignoreInsert = moveToResult.ignoreInsert;
} else { } else {
const moveToResult = await moveTo(protyle, sourceElements, targetElement, isSameDoc, "beforebegin", isCopy); const moveToResult = await moveTo(protyle, sourceElements, targetElement, isSameDoc, "beforebegin", isCopy);
doOperations.push(...moveToResult.doOperations); doOperations.push(...moveToResult.doOperations);
undoOperations.push(...moveToResult.undoOperations); undoOperations.push(...moveToResult.undoOperations);
topSourceElement = moveToResult.topSourceElement; topSourceElement = moveToResult.topSourceElement;
ignoreInsert = moveToResult.ignoreInsert;
} }
} }
if (targetElement.getAttribute("data-type") === "NodeListItem" && targetElement.getAttribute("data-subtype") === "o") { if (targetElement.getAttribute("data-type") === "NodeListItem" && targetElement.getAttribute("data-subtype") === "o") {
@ -829,7 +783,7 @@ const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElem
/// #endif /// #endif
} }
if (isSameDoc || isCopy) { if (isSameDoc || isCopy) {
transaction(protyle, doOperations, ignoreInsert ? undefined : undoOperations); transaction(protyle, doOperations, undoOperations);
} else { } else {
// 跨文档或插入折叠标题下不支持撤销 // 跨文档或插入折叠标题下不支持撤销
transaction(protyle, doOperations); transaction(protyle, doOperations);