mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-23 01:50:12 +01:00
❤️ 完整开源界面和内核 https://github.com/siyuan-note/siyuan/issues/5013
This commit is contained in:
parent
e650b8100c
commit
f40ed985e1
1214 changed files with 345766 additions and 9 deletions
448
app/src/protyle/wysiwyg/remove.ts
Normal file
448
app/src/protyle/wysiwyg/remove.ts
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
import {focusBlock, focusSideBlock, focusByWbr, focusByRange} from "../util/selection";
|
||||
import {
|
||||
getContenteditableElement,
|
||||
getLastBlock,
|
||||
getNextBlock,
|
||||
getPreviousBlock,
|
||||
getTopAloneElement,
|
||||
getTopEmptyElement, hasNextSibling
|
||||
} from "./getBlock";
|
||||
import {transaction, updateTransaction} from "./transaction";
|
||||
import {genEmptyElement} from "../../block/util";
|
||||
import {listOutdent, updateListOrder} from "./list";
|
||||
import {setFold} from "../../menus/protyle";
|
||||
import {preventScroll} from "../scroll/preventScroll";
|
||||
import {hideElements} from "../ui/hideElements";
|
||||
|
||||
const removeLi = (protyle: IProtyle, blockElement: Element, range: Range) => {
|
||||
if (!blockElement.parentElement.previousElementSibling && blockElement.parentElement.nextElementSibling && blockElement.parentElement.nextElementSibling.classList.contains("protyle-attr")) {
|
||||
listOutdent(protyle, [blockElement.parentElement], range);
|
||||
return;
|
||||
}
|
||||
// 第一个子列表合并到上一个块的末尾
|
||||
if (!blockElement.parentElement.previousElementSibling && blockElement.parentElement.parentElement.parentElement.classList.contains("list")) {
|
||||
range.insertNode(document.createElement("wbr"));
|
||||
const listElement = blockElement.parentElement.parentElement;
|
||||
const listHTML = listElement.outerHTML;
|
||||
const previousLastElement = blockElement.parentElement.parentElement.previousElementSibling.lastElementChild;
|
||||
const previousHTML = previousLastElement.parentElement.outerHTML;
|
||||
blockElement.parentElement.firstElementChild.remove();
|
||||
blockElement.parentElement.lastElementChild.remove();
|
||||
previousLastElement.insertAdjacentHTML("beforebegin", blockElement.parentElement.innerHTML);
|
||||
blockElement.parentElement.remove();
|
||||
if (listElement.getAttribute("data-subtype") === "o") {
|
||||
updateListOrder(listElement);
|
||||
}
|
||||
transaction(protyle, [{
|
||||
action: "update",
|
||||
id: listElement.getAttribute("data-node-id"),
|
||||
data: listElement.outerHTML
|
||||
}, {
|
||||
action: "update",
|
||||
data: previousLastElement.parentElement.outerHTML,
|
||||
id: previousLastElement.parentElement.getAttribute("data-node-id"),
|
||||
}], [{
|
||||
action: "update",
|
||||
data: previousHTML,
|
||||
id: previousLastElement.parentElement.getAttribute("data-node-id"),
|
||||
}, {
|
||||
action: "update",
|
||||
data: listHTML,
|
||||
id: listElement.getAttribute("data-node-id"),
|
||||
}]);
|
||||
focusByWbr(previousLastElement.parentElement, range);
|
||||
return;
|
||||
}
|
||||
// 顶级列表首行删除变为块
|
||||
if (!blockElement.parentElement.previousElementSibling) {
|
||||
if (blockElement.parentElement.parentElement.classList.contains("protyle-wysiwyg")) {
|
||||
return;
|
||||
}
|
||||
range.insertNode(document.createElement("wbr"));
|
||||
const listElement = blockElement.parentElement.parentElement;
|
||||
const listHTML = listElement.outerHTML;
|
||||
blockElement.parentElement.firstElementChild.remove();
|
||||
blockElement.parentElement.lastElementChild.remove();
|
||||
const tempElement = document.createElement("div");
|
||||
tempElement.innerHTML = blockElement.parentElement.innerHTML;
|
||||
const doOperations: IOperation[] = [];
|
||||
const undoOperations: IOperation[] = [];
|
||||
Array.from(tempElement.children).forEach((item, index) => {
|
||||
doOperations.push({
|
||||
action: "insert",
|
||||
id: item.getAttribute("data-node-id"),
|
||||
data: item.outerHTML,
|
||||
previousID: index === 0 ? listElement.previousElementSibling?.getAttribute("data-node-id") : doOperations[index - 1].id,
|
||||
parentID: listElement.parentElement.getAttribute("data-node-id") || protyle.block.parentID
|
||||
});
|
||||
undoOperations.push({
|
||||
action: "delete",
|
||||
id: item.getAttribute("data-node-id"),
|
||||
});
|
||||
});
|
||||
listElement.insertAdjacentHTML("beforebegin", blockElement.parentElement.innerHTML);
|
||||
blockElement.parentElement.remove();
|
||||
if (listElement.getAttribute("data-subtype") === "o") {
|
||||
updateListOrder(listElement, parseInt(listElement.firstElementChild.getAttribute("data-marker")) - 1);
|
||||
}
|
||||
doOperations.splice(0, 0, {
|
||||
action: "update",
|
||||
id: listElement.getAttribute("data-node-id"),
|
||||
data: listElement.outerHTML
|
||||
});
|
||||
undoOperations.push({
|
||||
action: "update",
|
||||
data: listHTML,
|
||||
id: listElement.getAttribute("data-node-id"),
|
||||
});
|
||||
transaction(protyle, doOperations, undoOperations);
|
||||
focusByWbr(protyle.wysiwyg.element, range);
|
||||
return;
|
||||
}
|
||||
|
||||
// 列表项合并到前一个列表项的最后一个块末尾
|
||||
const listItemElement = blockElement.parentElement;
|
||||
const listItemId = listItemElement.getAttribute("data-node-id");
|
||||
const listElement = listItemElement.parentElement;
|
||||
range.insertNode(document.createElement("wbr"));
|
||||
const html = listElement.outerHTML;
|
||||
const doOperations: IOperation[] = [];
|
||||
const undoOperations: IOperation[] = [{
|
||||
action: "insert",
|
||||
id: listItemId,
|
||||
data: "",
|
||||
previousID: listItemElement.previousElementSibling.getAttribute("data-node-id")
|
||||
}];
|
||||
const previousLastElement = listItemElement.previousElementSibling.lastElementChild;
|
||||
if (listItemElement.previousElementSibling.getAttribute("fold") === "1") {
|
||||
if (getContenteditableElement(blockElement).textContent.trim() === "") {
|
||||
doOperations.push({
|
||||
action: "delete",
|
||||
id: listItemId
|
||||
});
|
||||
undoOperations[0].data = listItemElement.outerHTML;
|
||||
range.selectNodeContents(getContenteditableElement(listItemElement.previousElementSibling));
|
||||
range.collapse(false);
|
||||
listItemElement.remove();
|
||||
} else {
|
||||
range.selectNodeContents(getContenteditableElement(listItemElement.previousElementSibling));
|
||||
range.collapse(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let previousID = previousLastElement.previousElementSibling.getAttribute("data-node-id");
|
||||
Array.from(blockElement.parentElement.children).forEach((item, index) => {
|
||||
if (item.classList.contains("protyle-action") || item.classList.contains("protyle-attr")) {
|
||||
return;
|
||||
}
|
||||
const id = item.getAttribute("data-node-id");
|
||||
doOperations.push({
|
||||
action: "move",
|
||||
id,
|
||||
previousID,
|
||||
});
|
||||
undoOperations.push({
|
||||
action: "move",
|
||||
id,
|
||||
previousID: index === 1 ? undefined : previousID,
|
||||
parentID: listItemId
|
||||
});
|
||||
previousID = id;
|
||||
previousLastElement.before(item);
|
||||
});
|
||||
doOperations.push({
|
||||
action: "delete",
|
||||
id: listItemId
|
||||
});
|
||||
undoOperations[0].data = listItemElement.outerHTML;
|
||||
listItemElement.remove();
|
||||
}
|
||||
|
||||
if (listElement.classList.contains("protyle-wysiwyg")) {
|
||||
transaction(protyle, doOperations, undoOperations);
|
||||
} else {
|
||||
if (listElement.getAttribute("data-subtype") === "o") {
|
||||
updateListOrder(listElement);
|
||||
}
|
||||
updateTransaction(protyle, listElement.getAttribute("data-node-id"), listElement.outerHTML, html);
|
||||
}
|
||||
focusByWbr(previousLastElement.parentElement, range);
|
||||
};
|
||||
|
||||
export const removeBlock = (protyle: IProtyle, blockElement: Element, range: Range) => {
|
||||
// 删除后,防止滚动条滚动后调用 get 请求,因为返回的请求已查找不到内容块了
|
||||
preventScroll(protyle);
|
||||
const selectElements = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select"));
|
||||
if (selectElements?.length > 0) {
|
||||
const deletes: IOperation[] = [];
|
||||
const inserts: IOperation[] = [];
|
||||
let sideElement = selectElements[0].previousElementSibling || selectElements[selectElements.length - 1].nextElementSibling;
|
||||
let listElement: Element;
|
||||
let topElementId: string;
|
||||
selectElements.find((item: HTMLElement) => {
|
||||
item.classList.remove("protyle-wysiwyg--select");
|
||||
const topElement = getTopAloneElement(item);
|
||||
topElementId = topElement.getAttribute("data-node-id");
|
||||
if (topElement.parentElement.classList.contains("protyle-wysiwyg") && topElement.getAttribute("data-type") === "NodeListItem") {
|
||||
// 缩放后列表项不能全选删除
|
||||
return;
|
||||
}
|
||||
const id = topElement.getAttribute("data-node-id");
|
||||
deletes.push({
|
||||
action: "delete",
|
||||
id,
|
||||
});
|
||||
sideElement = getPreviousBlock(topElement) || getNextBlock(topElement) || topElement.parentElement || protyle.wysiwyg.element.firstElementChild;
|
||||
if (topElement.getAttribute("data-type") === "NodeHeading" && topElement.getAttribute("fold") === "1") {
|
||||
// https://github.com/siyuan-note/siyuan/issues/2188
|
||||
setFold(protyle, topElement, undefined, true);
|
||||
inserts.push({
|
||||
action: "insert",
|
||||
data: topElement.outerHTML,
|
||||
id,
|
||||
previousID: selectElements[0].previousElementSibling ? selectElements[0].previousElementSibling.getAttribute("data-node-id") : "",
|
||||
parentID: topElement.parentElement.getAttribute("data-node-id") || protyle.block.parentID
|
||||
});
|
||||
// https://github.com/siyuan-note/siyuan/issues/4422
|
||||
topElement.firstElementChild.removeAttribute("contenteditable");
|
||||
// 在折叠标题后输入文字,然后全选删除再撤销会重建索引。因此不能删除折叠标题后新输入的输入折叠标题下的内容
|
||||
const nextElement = topElement.nextElementSibling;
|
||||
if (nextElement) {
|
||||
const nextType = nextElement.getAttribute("data-type");
|
||||
if (nextType !== "NodeHeading" ||
|
||||
(nextType === "NodeHeading" && nextElement.getAttribute("data-subtype") > topElement.getAttribute("data-subtype"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// https://github.com/siyuan-note/siyuan/issues/4113
|
||||
if (topElement.getAttribute("data-render") === "true" && ["mindmap", "echarts"].includes(topElement.getAttribute("data-subtype"))) {
|
||||
topElement.removeAttribute("data-render");
|
||||
topElement.firstElementChild.outerHTML = '<div spin="1"></div>';
|
||||
}
|
||||
inserts.push({
|
||||
action: "insert",
|
||||
data: topElement.outerHTML,
|
||||
id,
|
||||
previousID: topElement.previousElementSibling ? topElement.previousElementSibling.getAttribute("data-node-id") : "",
|
||||
parentID: topElement.parentElement.getAttribute("data-node-id") || protyle.block.parentID
|
||||
});
|
||||
if (topElement.getAttribute("data-subtype") === "o" && topElement.classList.contains("li")) {
|
||||
listElement = topElement.parentElement;
|
||||
} else {
|
||||
listElement = undefined;
|
||||
}
|
||||
topElement.remove();
|
||||
}
|
||||
});
|
||||
if ((sideElement.classList.contains("protyle-wysiwyg") && protyle.wysiwyg.element.childElementCount === 0) ||
|
||||
((sideElement.classList.contains("bq") || sideElement.classList.contains("sb")) && sideElement.childElementCount === 1)) {
|
||||
const emptyElement = genEmptyElement(false, true, topElementId);
|
||||
sideElement.insertAdjacentElement("afterbegin", emptyElement);
|
||||
deletes.push({
|
||||
action: "insert",
|
||||
data: emptyElement.outerHTML,
|
||||
id: topElementId,
|
||||
parentID: sideElement.getAttribute("data-node-id") || protyle.block.parentID
|
||||
});
|
||||
inserts.push({
|
||||
action: "delete",
|
||||
id: topElementId,
|
||||
});
|
||||
sideElement = undefined;
|
||||
focusByWbr(emptyElement, range);
|
||||
}
|
||||
if (sideElement) {
|
||||
focusBlock(sideElement, undefined, false);
|
||||
if (listElement) {
|
||||
inserts.push({
|
||||
action: "update",
|
||||
id: listElement.getAttribute("data-node-id"),
|
||||
data: listElement.outerHTML
|
||||
});
|
||||
updateListOrder(listElement, 1);
|
||||
deletes.push({
|
||||
action: "update",
|
||||
id: listElement.getAttribute("data-node-id"),
|
||||
data: listElement.outerHTML
|
||||
});
|
||||
}
|
||||
}
|
||||
transaction(protyle, deletes, inserts.reverse());
|
||||
hideElements(["util"], protyle);
|
||||
return;
|
||||
}
|
||||
// 空代码块直接删除
|
||||
if (blockElement.getAttribute("data-type") === "NodeCodeBlock" && getContenteditableElement(blockElement).textContent.trim() === "") {
|
||||
blockElement.classList.add("protyle-wysiwyg--select");
|
||||
removeBlock(protyle, blockElement, range);
|
||||
return;
|
||||
}
|
||||
// 设置 bq 和代码块光标
|
||||
if (blockElement.getAttribute("data-type") === "NodeCodeBlock" ||
|
||||
blockElement.getAttribute("data-type") === "NodeTable") {
|
||||
if (blockElement.previousElementSibling) {
|
||||
focusBlock(blockElement.previousElementSibling, undefined, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (blockElement.getAttribute("data-type") === "NodeHeading") {
|
||||
const id = blockElement.getAttribute("data-node-id");
|
||||
const newEmptyElement = genEmptyElement(false, false, id);
|
||||
if (blockElement.getAttribute("fold") === "1") {
|
||||
setFold(protyle, blockElement);
|
||||
}
|
||||
getContenteditableElement(newEmptyElement).innerHTML = "<wbr>" + getContenteditableElement(blockElement).textContent;
|
||||
const html = blockElement.outerHTML;
|
||||
blockElement.parentElement.replaceChild(newEmptyElement, blockElement);
|
||||
updateTransaction(protyle, id, newEmptyElement.outerHTML, html);
|
||||
focusByWbr(newEmptyElement, range);
|
||||
return;
|
||||
}
|
||||
if (!blockElement.previousElementSibling && blockElement.parentElement.getAttribute("data-type") === "NodeBlockquote") {
|
||||
range.insertNode(document.createElement("wbr"));
|
||||
const blockParentElement = blockElement.parentElement;
|
||||
blockParentElement.insertAdjacentElement("beforebegin", blockElement);
|
||||
if (blockParentElement.childElementCount === 1) {
|
||||
transaction(protyle, [{
|
||||
action: "move",
|
||||
id: blockElement.getAttribute("data-node-id"),
|
||||
previousID: blockElement.previousElementSibling?.getAttribute("data-node-id"),
|
||||
parentID: blockParentElement.parentElement.getAttribute("data-node-id") || protyle.block.parentID
|
||||
}, {
|
||||
action: "delete",
|
||||
id: blockParentElement.getAttribute("data-node-id")
|
||||
}], [{
|
||||
action: "insert",
|
||||
id: blockParentElement.getAttribute("data-node-id"),
|
||||
data: blockParentElement.outerHTML,
|
||||
previousID: blockElement.previousElementSibling?.getAttribute("data-node-id"),
|
||||
parentID: blockParentElement.parentElement.getAttribute("data-node-id") || protyle.block.parentID
|
||||
}, {
|
||||
action: "move",
|
||||
id: blockElement.getAttribute("data-node-id"),
|
||||
parentID: blockParentElement.getAttribute("data-node-id")
|
||||
}]);
|
||||
blockParentElement.remove();
|
||||
} else {
|
||||
transaction(protyle, [{
|
||||
action: "move",
|
||||
id: blockElement.getAttribute("data-node-id"),
|
||||
previousID: blockElement.previousElementSibling?.getAttribute("data-node-id"),
|
||||
parentID: blockParentElement.parentElement.getAttribute("data-node-id") || protyle.block.parentID
|
||||
}], [{
|
||||
action: "move",
|
||||
id: blockElement.getAttribute("data-node-id"),
|
||||
parentID: blockParentElement.getAttribute("data-node-id")
|
||||
}]);
|
||||
}
|
||||
focusByWbr(blockElement, range);
|
||||
return;
|
||||
}
|
||||
|
||||
if (blockElement.parentElement.classList.contains("li") && blockElement.previousElementSibling.classList.contains("protyle-action")) {
|
||||
removeLi(protyle, blockElement, range);
|
||||
return;
|
||||
}
|
||||
|
||||
const previousElement = getPreviousBlock(blockElement) as HTMLElement;
|
||||
if (!previousElement) {
|
||||
if (protyle.wysiwyg.element.childElementCount > 1 && getContenteditableElement(blockElement).textContent === "") {
|
||||
focusBlock(protyle.wysiwyg.element.firstElementChild.nextElementSibling);
|
||||
// 列表项中包含超级块时需要到顶层
|
||||
const topElement = getTopAloneElement(blockElement);
|
||||
transaction(protyle, [{
|
||||
action: "delete",
|
||||
id: topElement.getAttribute("data-node-id"),
|
||||
}], [{
|
||||
action: "insert",
|
||||
data: topElement.outerHTML,
|
||||
id: topElement.getAttribute("data-node-id"),
|
||||
parentID: protyle.block.parentID
|
||||
}]);
|
||||
topElement.remove();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const editableElement = getContenteditableElement(blockElement);
|
||||
const previousLastElement = getLastBlock(previousElement) as HTMLElement;
|
||||
const isSelectNode = previousLastElement && (previousLastElement.classList.contains("table") || previousLastElement.classList.contains("render-node") || previousLastElement.classList.contains("iframe") || previousLastElement.classList.contains("hr") || previousLastElement.classList.contains("code-block"));
|
||||
if (isSelectNode) {
|
||||
if (previousLastElement.classList.contains("code-block")) {
|
||||
focusBlock(previousLastElement, undefined, false);
|
||||
if (editableElement.textContent.trim() === "") {
|
||||
const id = blockElement.getAttribute("data-node-id");
|
||||
transaction(protyle, [{
|
||||
action: "delete",
|
||||
id,
|
||||
}], [{
|
||||
action: "insert",
|
||||
data: blockElement.outerHTML,
|
||||
id: id,
|
||||
previousID: previousLastElement.getAttribute("data-node-id")
|
||||
}]);
|
||||
blockElement.remove();
|
||||
}
|
||||
return;
|
||||
}
|
||||
previousLastElement.classList.add("protyle-wysiwyg--select");
|
||||
if (previousLastElement.getAttribute("data-type") === "NodeBlockQueryEmbed" || editableElement.textContent !== "" || protyle.wysiwyg.element.childElementCount === 2) {
|
||||
focusByRange(range);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const newId = previousLastElement.getAttribute("data-node-id");
|
||||
const removeElement = getTopEmptyElement(blockElement);
|
||||
const removeId = removeElement.getAttribute("data-node-id");
|
||||
range.insertNode(document.createElement("wbr"));
|
||||
const undoOperations: IOperation[] = [{
|
||||
action: "update",
|
||||
data: previousLastElement.outerHTML,
|
||||
id: newId,
|
||||
}, {
|
||||
action: "insert",
|
||||
data: removeElement.outerHTML,
|
||||
id: removeId,
|
||||
// 不能使用 previousLastElement,否则在超级块下的元素前删除撤销错误
|
||||
previousID: previousElement.getAttribute("data-node-id"),
|
||||
}];
|
||||
|
||||
if (isSelectNode) {
|
||||
// 需先移除 removeElement,否则 side 会选中 removeElement
|
||||
removeElement.remove();
|
||||
focusSideBlock(previousElement);
|
||||
} else {
|
||||
const previousLastEditElement = getContenteditableElement(previousLastElement);
|
||||
if (editableElement.textContent !== "") {
|
||||
// 非空块
|
||||
range.setEndAfter(editableElement.lastChild);
|
||||
// 数学公式会车后再删除 https://github.com/siyuan-note/siyuan/issues/3850
|
||||
if (previousLastEditElement?.lastElementChild?.getAttribute("data-type") === "inline-math") {
|
||||
const lastSibling = hasNextSibling(previousLastEditElement?.lastElementChild);
|
||||
if (lastSibling && lastSibling.textContent === "\n") {
|
||||
lastSibling.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
const scroll = protyle.contentElement.scrollTop;
|
||||
const leftNodes = range.extractContents();
|
||||
range.selectNodeContents(previousLastEditElement);
|
||||
range.collapse(false);
|
||||
range.insertNode(leftNodes);
|
||||
removeElement.remove();
|
||||
// extractContents 内容过多时需要进行滚动条重置,否则位置会错位
|
||||
protyle.contentElement.scrollTop = scroll;
|
||||
protyle.scroll.lastScrollTop = scroll - 1;
|
||||
}
|
||||
transaction(protyle, [{
|
||||
action: "delete",
|
||||
id: removeId,
|
||||
}, {
|
||||
action: "update",
|
||||
data: previousLastElement.outerHTML,
|
||||
id: newId,
|
||||
}], undoOperations);
|
||||
focusByWbr(previousLastElement, range);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue