siyuan/app/src/protyle/wysiwyg/transaction.ts

900 lines
42 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {fetchPost} from "../../util/fetch";
import {focusBlock, focusByRange, focusByWbr, focusSideBlock, getEditorRange} from "../util/selection";
import {getTopAloneElement} from "./getBlock";
import {Constants} from "../../constants";
import {blockRender} from "../markdown/blockRender";
import {processRender} from "../util/processCode";
import {highlightRender} from "../markdown/highlightRender";
import {hasClosestBlock, hasClosestByAttribute} from "../util/hasClosest";
import {lockFile} from "../../dialog/processSystem";
import {setFold} from "../../menus/protyle";
import {onGet} from "../util/onGet";
/// #if !MOBILE
import {getAllModels} from "../../layout/getAll";
/// #endif
import {removeFoldHeading} from "../util/heading";
import {genEmptyElement, genSBElement} from "../../block/util";
import {hideElements} from "../ui/hideElements";
import {reloadProtyle} from "../util/reload";
import {countBlockWord} from "../../layout/status";
const removeTopElement = (updateElement: Element, protyle: IProtyle) => {
// 移动到其他文档中,该块需移除
// TODO 文档没有打开时,需要通过后台获取 getTopAloneElement
const topAloneElement = getTopAloneElement(updateElement);
const doOperations: IOperation[] = [];
if (!topAloneElement.isSameNode(updateElement)) {
updateElement.remove();
doOperations.push({
action: "delete",
id: topAloneElement.getAttribute("data-node-id")
});
}
topAloneElement.remove();
if (protyle.block.rootID === protyle.block.id && protyle.wysiwyg.element.childElementCount === 0) {
const newId = Lute.NewNodeID();
const newElement = genEmptyElement(false, false, newId);
doOperations.push({
action: "insert",
data: newElement.outerHTML,
id: newId,
parentID: protyle.block.parentID
});
protyle.wysiwyg.element.innerHTML = newElement.outerHTML;
}
if (doOperations.length > 0) {
transaction(protyle, doOperations, []);
}
};
// 用于执行操作,外加处理当前编辑器中引用块、嵌入块的更新
const promiseTransaction = () => {
const protyle = window.siyuan.transactions[0].protyle;
const doOperations = window.siyuan.transactions[0].doOperations;
const undoOperations = window.siyuan.transactions[0].undoOperations;
// 1. * ;2. * ;3. a
// 第一步请求没有返回前在 transaction 中会合并1、2步此时第一步请求返回将被以下代码删除在输入a时就会出现 block not found因此以下代码不能放入请求回掉中
window.siyuan.transactions.splice(0, 1);
fetchPost("/api/transactions", {
session: protyle.id,
app: Constants.SIYUAN_APPID,
transactions: [{
doOperations,
undoOperations // 目前用于 ws 推送更新大纲
}]
}, (response) => {
if (window.siyuan.transactions.length === 0) {
promiseTransactions();
} else {
promiseTransaction();
}
if (response.code === 1) {
lockFile(protyle.block.rootID);
return;
}
countBlockWord([], protyle.block.rootID);
if (doOperations.length === 1 && (doOperations[0].action === "unfoldHeading" || doOperations[0].action === "foldHeading" || doOperations[0].action === "setAttrs")) {
const gutterFoldElement = protyle.gutter.element.querySelector('[data-type="fold"]');
if (gutterFoldElement) {
gutterFoldElement.removeAttribute("disabled");
}
if (doOperations[0].action === "unfoldHeading") {
const scrollTop = protyle.contentElement.scrollTop;
protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${doOperations[0].id}"]`).forEach(item => {
if (!item.lastElementChild.classList.contains("protyle-attr")) {
item.lastElementChild.remove();
}
removeUnfoldRepeatBlock(response.data[0].doOperations[0].retData, protyle);
item.insertAdjacentHTML("afterend", response.data[0].doOperations[0].retData);
if (doOperations[0].data === "remove") {
// https://github.com/siyuan-note/siyuan/issues/2188
const selection = getSelection();
if (selection.rangeCount > 0 && item.contains(selection.getRangeAt(0).startContainer)) {
focusBlock(item.nextElementSibling, undefined, true);
}
item.remove();
}
});
processRender(protyle.wysiwyg.element);
highlightRender(protyle.wysiwyg.element);
blockRender(protyle, protyle.wysiwyg.element);
protyle.contentElement.scrollTop = scrollTop;
protyle.scroll.lastScrollTop = scrollTop;
} else if (doOperations[0].action === "foldHeading") {
// 折叠标题后未触发动态加载 https://github.com/siyuan-note/siyuan/issues/4168
if (protyle.wysiwyg.element.lastElementChild.getAttribute("data-eof") !== "true" &&
!protyle.scroll.element.classList.contains("fn__none")) {
fetchPost("/api/filetree/getDoc", {
id: protyle.wysiwyg.element.lastElementChild.getAttribute("data-node-id"),
mode: 2,
k: protyle.options.key || "",
size: Constants.SIZE_GET,
}, getResponse => {
onGet(getResponse, protyle, [Constants.CB_GET_APPEND, Constants.CB_GET_UNCHANGEID]);
});
}
}
return;
}
let range: Range;
if (getSelection().rangeCount > 0) {
range = getSelection().getRangeAt(0);
}
doOperations.forEach(operation => {
if (operation.action === "update") {
if (protyle.options.backlinkData) {
// 反链中有多个相同块的情况
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`)).forEach(item => {
if (item.getAttribute("data-type") === "NodeBlockQueryEmbed" ||
!hasClosestByAttribute(item, "data-type", "NodeBlockQueryEmbed")) {
if (range && (item.isSameNode(range.startContainer) || item.contains(range.startContainer))) {
// 正在编辑的块不能进行更新
} else {
item.outerHTML = operation.data.replace("<wbr>", "");
}
}
});
processRender(protyle.wysiwyg.element);
highlightRender(protyle.wysiwyg.element);
blockRender(protyle, protyle.wysiwyg.element);
}
// 当前编辑器中更新嵌入块
updateEmbed(protyle, operation);
// 更新引用块
updateRef(protyle, operation.id);
return;
}
if (operation.action === "delete" || operation.action === "append") {
if (protyle.options.backlinkData) {
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`)).forEach(item => {
if (!hasClosestByAttribute(item, "data-type", "NodeBlockQueryEmbed")) {
item.remove();
}
});
}
// 更新嵌入块
protyle.wysiwyg.element.querySelectorAll('[data-type="NodeBlockQueryEmbed"]').forEach((item) => {
if (item.querySelector(`[data-node-id="${operation.id}"]`)) {
item.removeAttribute("data-render");
blockRender(protyle, item);
}
});
// 更新引用块
protyle.wysiwyg.element.querySelectorAll(`[data-type~="block-ref"][data-id="${operation.id}"]`).forEach(item => {
if (item.getAttribute("data-subtype") === "d") {
item.textContent = "block not found";
}
});
return;
}
if (operation.action === "move") {
if (protyle.options.backlinkData) {
const updateElements: Element[] = [];
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`)).forEach(item => {
if (item.getAttribute("data-type") === "NodeBlockQueryEmbed" || !hasClosestByAttribute(item, "data-type", "NodeBlockQueryEmbed")) {
updateElements.push(item);
return;
}
});
let hasFind = false;
if (operation.previousID && updateElements.length > 0) {
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.previousID}"]`)).forEach(item => {
if (item.getAttribute("data-type") === "NodeBlockQueryEmbed" || !hasClosestByAttribute(item.parentElement, "data-type", "NodeBlockQueryEmbed")) {
item.after(updateElements[0].cloneNode(true));
hasFind = true;
}
});
} else if (updateElements.length > 0) {
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.parentID}"]`)).forEach(item => {
if (item.getAttribute("data-type") === "NodeBlockQueryEmbed" || !hasClosestByAttribute(item.parentElement, "data-type", "NodeBlockQueryEmbed")) {
// 列表特殊处理
if (item.firstElementChild?.classList.contains("protyle-action")) {
item.firstElementChild.after(updateElements[0].cloneNode(true));
} else {
item.prepend(updateElements[0].cloneNode(true));
}
hasFind = true;
}
});
}
updateElements.forEach(item => {
if (hasFind) {
item.remove();
} else if (!hasFind && item.parentElement) {
removeTopElement(item, protyle);
}
});
}
// 更新嵌入块
protyle.wysiwyg.element.querySelectorAll('[data-type="NodeBlockQueryEmbed"]').forEach((item) => {
if (item.querySelector(`[data-node-id="${operation.id}"]`)) {
item.removeAttribute("data-render");
blockRender(protyle, item);
}
});
return;
}
if (operation.action === "insert") {
// insert
if (protyle.options.backlinkData) {
const cursorElements: Element[] = [];
if (operation.previousID) {
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.previousID}"]`)).forEach(item => {
if (item.nextElementSibling?.getAttribute("data-node-id") !== operation.id &&
!hasClosestByAttribute(item, "data-node-id", operation.id) && // 段落转列表会在段落后插入新列表
(item.getAttribute("data-type") === "NodeBlockQueryEmbed" || !hasClosestByAttribute(item.parentElement, "data-type", "NodeBlockQueryEmbed"))) {
item.insertAdjacentHTML("afterend", operation.data);
cursorElements.push(item.nextElementSibling);
}
});
} else {
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.parentID}"]`)).forEach(item => {
if (item.getAttribute("data-type") === "NodeBlockQueryEmbed" || !hasClosestByAttribute(item.parentElement, "data-type", "NodeBlockQueryEmbed")) {
// 列表特殊处理
if (item.firstElementChild && item.firstElementChild.classList.contains("protyle-action") &&
item.firstElementChild.nextElementSibling.getAttribute("data-node-id") !== operation.id) {
item.firstElementChild.insertAdjacentHTML("afterend", operation.data);
cursorElements.push(item.firstElementChild.nextElementSibling);
} else if (item.firstElementChild && item.firstElementChild.getAttribute("data-node-id") !== operation.id) {
item.insertAdjacentHTML("afterbegin", operation.data);
cursorElements.push(item.firstElementChild);
}
}
});
}
// https://github.com/siyuan-note/siyuan/issues/4420
protyle.wysiwyg.element.querySelectorAll('[data-type="NodeHeading"]').forEach(item => {
if (item.lastElementChild.getAttribute("spin") === "1") {
item.lastElementChild.remove();
}
});
cursorElements.forEach(item => {
processRender(item);
highlightRender(item);
blockRender(protyle, item);
const wbrElement = item.querySelector("wbr");
if (wbrElement) {
wbrElement.remove();
}
});
}
// 不更新嵌入块:在快速删除时重新渲染嵌入块会导致滚动条产生滚动从而触发 getDoc 请求,此时删除的块还没有写库,会把已删除的块 append 到文档底部,最终导致查询块失败、光标丢失
// protyle.wysiwyg.element.querySelectorAll('[data-type="NodeBlockQueryEmbed"]').forEach((item) => {
// if (item.getAttribute("data-node-id") === operation.id) {
// item.removeAttribute("data-render");
// blockRender(protyle, item);
// }
// });
// 更新引用块
updateRef(protyle, operation.id);
}
});
});
};
const updateEmbed = (protyle: IProtyle, operation: IOperation) => {
let updatedEmbed = false;
protyle.wysiwyg.element.querySelectorAll(`[data-type="NodeBlockQueryEmbed"] [data-node-id="${operation.id}"]`).forEach((item) => {
const tempElement = document.createElement("div");
tempElement.innerHTML = operation.data;
tempElement.querySelectorAll('[contenteditable="true"]').forEach(editItem => {
editItem.setAttribute("contenteditable", "false");
});
const wbrElement = tempElement.querySelector("wbr");
if (wbrElement) {
wbrElement.remove();
}
item.outerHTML = tempElement.innerHTML;
updatedEmbed = true;
});
if (updatedEmbed) {
processRender(protyle.wysiwyg.element);
highlightRender(protyle.wysiwyg.element);
}
};
export const promiseTransactions = () => {
window.siyuan.transactionsTimeout = window.setInterval(() => {
if (window.siyuan.transactions.length === 0) {
return;
}
window.clearInterval(window.siyuan.transactionsTimeout);
promiseTransaction();
}, Constants.TIMEOUT_INPUT * 2);
};
// 用于推送和撤销
export const onTransaction = (protyle: IProtyle, operation: IOperation, focus: boolean) => {
const updateElements: Element[] = [];
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`)).forEach(item => {
if (!hasClosestByAttribute(item.parentElement, "data-type", "NodeBlockQueryEmbed")) {
updateElements.push(item);
}
});
if (operation.action === "setAttrs") {
protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`).forEach(item => {
if (JSON.parse(operation.data).fold === "1") {
item.setAttribute("fold", "1");
} else {
item.removeAttribute("fold");
}
});
return;
}
if (operation.action === "unfoldHeading") {
const scrollTop = protyle.contentElement.scrollTop;
protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`).forEach(item => {
if (operation.retData) { // undo 的时候没有 retData
removeUnfoldRepeatBlock(operation.retData, protyle);
item.insertAdjacentHTML("afterend", operation.retData);
}
item.removeAttribute("fold");
if (operation.data === "remove") {
item.remove();
}
});
if (operation.retData) {
processRender(protyle.wysiwyg.element);
highlightRender(protyle.wysiwyg.element);
blockRender(protyle, protyle.wysiwyg.element);
protyle.contentElement.scrollTop = scrollTop;
protyle.scroll.lastScrollTop = scrollTop;
}
return;
}
if (operation.action === "foldHeading") {
protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`).forEach(item => {
item.setAttribute("fold", "1");
if (!operation.retData) {
removeFoldHeading(item);
}
});
if (operation.retData) {
operation.retData.forEach((item: string) => {
protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${item}"]`).forEach(item => {
item.remove();
});
});
}
return;
}
if (operation.action === "delete") {
if (updateElements.length > 0) {
if (focus) {
focusSideBlock(updateElements[0]);
}
updateElements.forEach(item => {
item.remove();
});
}
// 更新 ws 嵌入块
protyle.wysiwyg.element.querySelectorAll('[data-type="NodeBlockQueryEmbed"]').forEach((item) => {
if (item.querySelector(`[data-node-id="${operation.id}"]`)) {
item.removeAttribute("data-render");
blockRender(protyle, item);
}
});
// 更新 ws 引用块
protyle.wysiwyg.element.querySelectorAll(`[data-type~="block-ref"][data-id="${operation.id}"]`).forEach(item => {
if (item.getAttribute("data-subtype") === "d") {
item.textContent = "block not found";
}
});
return;
}
if (operation.action === "update") {
if (updateElements.length > 0) {
updateElements.forEach(item => {
item.outerHTML = operation.data;
});
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`)).find(item => {
if (item.getAttribute("data-type") === "NodeBlockQueryEmbed" // 引用转换为块嵌入undo、redo 后也需要更新 updateElement
|| !hasClosestByAttribute(item, "data-type", "NodeBlockQueryEmbed")) {
updateElements[0] = item;
return true;
}
});
const wbrElement = updateElements[0].querySelector("wbr");
if (focus) {
const range = getEditorRange(updateElements[0]);
if (wbrElement) {
focusByWbr(updateElements[0], range);
} else {
focusBlock(updateElements[0]);
}
} else if (wbrElement) {
wbrElement.remove();
}
processRender(updateElements.length === 1 ? updateElements[0] : protyle.wysiwyg.element);
highlightRender(updateElements.length === 1 ? updateElements[0] : protyle.wysiwyg.element);
blockRender(protyle, updateElements.length === 1 ? updateElements[0] : protyle.wysiwyg.element);
}
// 更新 ws 嵌入块
updateEmbed(protyle, operation);
// 更新 ws 引用块
updateRef(protyle, operation.id);
return;
}
if (operation.action === "updateAttrs") { // 调用接口才推送
let nodeAttrHTML = "";
const data = operation.data as any;
const attrsResult: IObject = {};
Object.keys(data.new).forEach(key => {
attrsResult[key] = data.new[key];
const escapeHTML = data.new[key];
if (key === "bookmark") {
nodeAttrHTML += `<div class="protyle-attr--bookmark">${escapeHTML}</div>`;
} else if (key === "name") {
nodeAttrHTML += `<div class="protyle-attr--name"><svg><use xlink:href="#iconN"></use></svg>${escapeHTML}</div>`;
} else if (key === "alias") {
nodeAttrHTML += `<div class="protyle-attr--alias"><svg><use xlink:href="#iconA"></use></svg>${escapeHTML}</div>`;
} else if (key === "memo") {
nodeAttrHTML += `<div class="protyle-attr--memo b3-tooltips b3-tooltips__sw" aria-label="${escapeHTML}"><svg><use xlink:href="#iconM"></use></svg></div>`;
}
});
if (protyle.block.rootID === operation.id && protyle.title) {
// 文档
const refElement = protyle.title.element.querySelector(".protyle-attr--refcount");
if (refElement) {
nodeAttrHTML += refElement.outerHTML;
}
protyle.title.element.querySelector(".protyle-attr").innerHTML = nodeAttrHTML;
protyle.wysiwyg.renderCustom(attrsResult);
return;
}
protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`).forEach(item => {
Object.keys(data.old).forEach(key => {
item.removeAttribute(key);
});
Object.keys(data.new).forEach(key => {
item.setAttribute(key, data.new[key]);
});
const refElement = item.lastElementChild.querySelector(".protyle-attr--refcount");
if (refElement) {
nodeAttrHTML += refElement.outerHTML;
}
item.lastElementChild.innerHTML = nodeAttrHTML + Constants.ZWSP;
});
return;
}
if (operation.action === "move") {
let cloneRange;
let range;
if (focus && getSelection().rangeCount > 0) {
range = getSelection().getRangeAt(0);
cloneRange = {
startContainer: range.startContainer,
startOffset: range.startOffset,
endContainer: range.endContainer,
endOffset: range.endOffset,
};
}
/// #if !MOBILE
if (updateElements.length === 0) {
// 打开两个相同的文档 A、A1从 A 拖拽块 B 到 A1在后续 ws 处理中,无法获取到拖拽出去的 B
getAllModels().editor.forEach(editor => {
const updateCloneElement = editor.editor.protyle.wysiwyg.element.querySelector(`[data-node-id="${operation.id}"]`);
if (updateCloneElement) {
updateElements.push(updateCloneElement.cloneNode(true) as Element);
}
});
}
/// #endif
let hasFind = false;
if (operation.previousID && updateElements.length > 0) {
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.previousID}"]`)).forEach(item => {
if (!hasClosestByAttribute(item.parentElement, "data-type", "NodeBlockQueryEmbed")) {
item.after(updateElements[0].cloneNode(true));
hasFind = true;
}
});
} else if (updateElements.length > 0) {
if (!protyle.options.backlinkData && operation.parentID === protyle.block.parentID) {
protyle.wysiwyg.element.prepend(updateElements[0].cloneNode(true));
hasFind = true;
} else {
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.parentID}"]`)).forEach(item => {
if (!hasClosestByAttribute(item.parentElement, "data-type", "NodeBlockQueryEmbed")) {
// 列表特殊处理
if (item.firstElementChild?.classList.contains("protyle-action")) {
item.firstElementChild.after(updateElements[0].cloneNode(true));
} else {
item.prepend(updateElements[0].cloneNode(true));
}
hasFind = true;
}
});
}
}
updateElements.forEach(item => {
if (hasFind) {
item.remove();
} else if (!hasFind && item.parentElement) {
removeTopElement(item, protyle);
}
});
if (focus && cloneRange && range) {
if (operation.data === "focus") {
focusBlock(updateElements[0]);
} else {
range.setStart(cloneRange.startContainer, cloneRange.startOffset);
range.setEnd(cloneRange.endContainer, cloneRange.endOffset);
focusByRange(range);
}
}
// 更新 ws 嵌入块
protyle.wysiwyg.element.querySelectorAll('[data-type="NodeBlockQueryEmbed"]').forEach((item) => {
if (item.querySelector(`[data-node-id="${operation.id}"]`)) {
item.removeAttribute("data-render");
blockRender(protyle, item);
}
});
return;
}
if (operation.action === "insert") {
const cursorElements = [];
if (operation.previousID) {
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.previousID}"]`)).forEach(item => {
const embedElement = hasClosestByAttribute(item.parentElement, "data-type", "NodeBlockQueryEmbed");
if (embedElement) {
// https://github.com/siyuan-note/siyuan/issues/5524
embedElement.removeAttribute("data-render");
blockRender(protyle, embedElement);
} else {
item.insertAdjacentHTML("afterend", operation.data);
cursorElements.push(item.nextElementSibling);
}
});
} else {
if (!protyle.options.backlinkData && operation.parentID === protyle.block.parentID) {
protyle.wysiwyg.element.insertAdjacentHTML("afterbegin", operation.data);
cursorElements.push(protyle.wysiwyg.element.firstElementChild);
} else {
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.parentID}"]`)).forEach(item => {
if (!hasClosestByAttribute(item.parentElement, "data-type", "NodeBlockQueryEmbed")) {
// 列表特殊处理
if (item.firstElementChild?.classList.contains("protyle-action")) {
item.firstElementChild.insertAdjacentHTML("afterend", operation.data);
cursorElements.push(item.firstElementChild.nextElementSibling);
} else {
item.insertAdjacentHTML("afterbegin", operation.data);
cursorElements.push(item.firstElementChild);
}
}
});
}
}
// https://github.com/siyuan-note/siyuan/issues/4420
protyle.wysiwyg.element.querySelectorAll('[data-type="NodeHeading"]').forEach(item => {
if (item.lastElementChild.getAttribute("spin") === "1") {
item.lastElementChild.remove();
}
});
if (cursorElements.length === 0) {
return;
}
cursorElements.forEach(item => {
processRender(item);
highlightRender(item);
blockRender(protyle, item);
const wbrElement = item.querySelector("wbr");
if (focus) {
const range = getEditorRange(item);
if (wbrElement) {
focusByWbr(item, range);
} else {
focusBlock(item);
}
} else if (wbrElement) {
wbrElement.remove();
}
});
// 更新 ws 嵌入块
protyle.wysiwyg.element.querySelectorAll(`[data-type="NodeBlockQueryEmbed"][data-node-id="${operation.id}"]`).forEach((item) => {
item.removeAttribute("data-render");
blockRender(protyle, item);
});
protyle.wysiwyg.element.querySelectorAll('[data-type="NodeBlockQueryEmbed"]').forEach((item) => {
if (item.querySelector(`[data-node-id="${operation.id}"]`)) {
item.removeAttribute("data-render");
blockRender(protyle, item);
}
});
// 更新 ws 引用块
updateRef(protyle, operation.id);
return;
}
if (operation.action === "append") {
reloadProtyle(protyle);
}
};
export const turnsIntoOneTransaction = (options: { protyle: IProtyle, selectsElement: Element[], type: string, level?: string }) => {
let parentElement: Element;
const id = Lute.NewNodeID();
if (options.type === "BlocksMergeSuperBlock") {
parentElement = genSBElement(options.level, id);
} else if (options.type === "Blocks2Blockquote") {
parentElement = document.createElement("div");
parentElement.classList.add("bq");
parentElement.setAttribute("data-node-id", id);
parentElement.setAttribute("data-type", "NodeBlockquote");
parentElement.innerHTML = '<div class="protyle-attr" contenteditable="false"></div>';
} else if (options.type.endsWith("Ls")) {
parentElement = document.createElement("div");
parentElement.classList.add("list");
parentElement.setAttribute("data-node-id", id);
parentElement.setAttribute("data-type", "NodeList");
if (options.type === "Blocks2ULs") {
parentElement.setAttribute("data-subtype", "u");
} else if (options.type === "Blocks2OLs") {
parentElement.setAttribute("data-subtype", "o");
} else {
parentElement.setAttribute("data-subtype", "t");
}
let html = "";
options.selectsElement.forEach((item, index) => {
if (options.type === "Blocks2ULs") {
html += `<div data-marker="*" data-subtype="u" data-node-id="${Lute.NewNodeID()}" data-type="NodeListItem" class="li"><div class="protyle-action" draggable="true"><svg><use xlink:href="#iconDot"></use></svg></div><div class="protyle-attr" contenteditable="false"></div></div>`;
} else if (options.type === "Blocks2OLs") {
html += `<div data-marker="${index + 1}." data-subtype="o" data-node-id="${Lute.NewNodeID()}" data-type="NodeListItem" class="li"><div class="protyle-action protyle-action--order" contenteditable="false" draggable="true">${index + 1}.</div><div class="protyle-attr" contenteditable="false"></div></div>`;
} else {
html += `<div data-marker="*" data-subtype="t" data-node-id="${Lute.NewNodeID()}" data-type="NodeListItem" class="li"><div class="protyle-action protyle-action--task" draggable="true"><svg><use xlink:href="#iconUncheck"></use></svg></div><div class="protyle-attr" contenteditable="false"></div></div>`;
}
});
parentElement.innerHTML = html + '<div class="protyle-attr" contenteditable="false"></div>';
}
const previousId = options.selectsElement[0].getAttribute("data-node-id");
const parentId = options.selectsElement[0].parentElement.getAttribute("data-node-id") || options.protyle.block.parentID;
const doOperations: IOperation[] = [{
action: "insert",
id,
data: parentElement.outerHTML,
previousID: previousId,
parentID: parentId
}];
const undoOperations: IOperation[] = [];
if (options.selectsElement[0].previousElementSibling) {
options.selectsElement[0].before(parentElement);
} else {
options.selectsElement[0].parentElement.prepend(parentElement);
}
let itemPreviousId: string;
options.selectsElement.forEach((item, index) => {
item.classList.remove("protyle-wysiwyg--select");
const itemId = item.getAttribute("data-node-id");
undoOperations.push({
action: "move",
id: itemId,
previousID: itemPreviousId || id,
parentID: parentId
});
if (options.type.endsWith("Ls")) {
doOperations.push({
action: "move",
id: itemId,
parentID: parentElement.children[index].getAttribute("data-node-id")
});
parentElement.children[index].firstElementChild.after(item);
} else {
doOperations.push({
action: "move",
id: itemId,
previousID: itemPreviousId,
parentID: id
});
parentElement.lastElementChild.before(item);
}
itemPreviousId = item.getAttribute("data-node-id");
if (index === options.selectsElement.length - 1) {
undoOperations.push({
action: "delete",
id,
});
}
});
transaction(options.protyle, doOperations, undoOperations);
focusBlock(options.protyle.wysiwyg.element.querySelector(`[data-node-id="${options.selectsElement[0].getAttribute("data-node-id")}"]`));
hideElements(["gutter"], options.protyle);
};
const removeUnfoldRepeatBlock = (html: string, protyle: IProtyle) => {
const temp = document.createElement("template");
temp.innerHTML = html;
Array.from(temp.content.children).forEach(item => {
protyle.wysiwyg.element.querySelector(`:scope > [data-node-id="${item.getAttribute("data-node-id")}"]`)?.remove();
});
};
export const turnsIntoTransaction = (options: {
protyle: IProtyle,
selectsElement?: Element[],
nodeElement?: Element,
type: string,
level?: number | string,
isContinue?: boolean,
}) => {
let selectsElement: Element[] = options.selectsElement;
let range: Range;
// 通过快捷键触发
if (options.nodeElement) {
range = getSelection().getRangeAt(0);
range.insertNode(document.createElement("wbr"));
selectsElement = Array.from(options.protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select"));
if (selectsElement.length === 0) {
selectsElement = [options.nodeElement];
}
let isContinue = false;
let hasEmbedBlock = false;
let isList = false;
selectsElement.find((item, index) => {
if (item.classList.contains("li")) {
isList = true;
return true;
}
if (item.classList.contains("bq") || item.classList.contains("sb") || item.classList.contains("p")) {
hasEmbedBlock = true;
}
if (item.nextElementSibling && selectsElement[index + 1] &&
item.nextElementSibling.isSameNode(selectsElement[index + 1])) {
isContinue = true;
} else if (index !== selectsElement.length - 1) {
isContinue = false;
return true;
}
});
if (isList || (hasEmbedBlock && options.type === "Blocks2Ps")) {
return;
}
if (selectsElement.length === 1 && options.type === "Blocks2Hs" &&
selectsElement[0].getAttribute("data-type") === "NodeHeading" &&
options.level === parseInt(selectsElement[0].getAttribute("data-subtype").substr(1))) {
// 快捷键同级转换,消除标题
options.type = "Blocks2Ps";
}
options.isContinue = isContinue;
}
let html = "";
const doOperations: IOperation[] = [];
const undoOperations: IOperation[] = [];
selectsElement.forEach((item, index) => {
if ((options.type === "Blocks2Ps" || options.type === "Blocks2Hs") &&
item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") {
setFold(options.protyle, item);
}
item.classList.remove("protyle-wysiwyg--select");
html += item.outerHTML;
const id = item.getAttribute("data-node-id");
undoOperations.push({
action: "update",
id,
data: item.outerHTML
});
if ((options.type === "Blocks2Ps" || options.type === "Blocks2Hs") && !options.isContinue) {
// @ts-ignore
item.outerHTML = options.protyle.lute[options.type](item.outerHTML, options.level);
} else {
if (index === selectsElement.length - 1) {
const tempElement = document.createElement("div");
// @ts-ignore
tempElement.innerHTML = options.protyle.lute[options.type](html, options.level);
item.outerHTML = tempElement.innerHTML;
} else {
item.remove();
}
}
});
undoOperations.forEach(item => {
const nodeElement = options.protyle.wysiwyg.element.querySelector(`[data-node-id="${item.id}"]`);
doOperations.push({
action: "update",
id: item.id,
data: nodeElement.outerHTML
});
});
transaction(options.protyle, doOperations, undoOperations);
processRender(options.protyle.wysiwyg.element);
highlightRender(options.protyle.wysiwyg.element);
blockRender(options.protyle, options.protyle.wysiwyg.element);
if (range) {
focusByWbr(options.protyle.wysiwyg.element, range);
} else {
focusBlock(options.protyle.wysiwyg.element.querySelector(`[data-node-id="${selectsElement[0].getAttribute("data-node-id")}"]`));
}
hideElements(["gutter"], options.protyle);
};
const updateRef = (protyle: IProtyle, id: string, index = 0) => {
if (index > 6) {
return;
}
protyle.wysiwyg.element.querySelectorAll(`[data-type~="block-ref"][data-id="${id}"]`).forEach(item => {
if (item.getAttribute("data-subtype") === "d") {
fetchPost("/api/block/getRefText", {id: id}, (response) => {
item.innerHTML = response.data;
const blockElement = hasClosestBlock(item);
if (blockElement) {
updateRef(protyle, blockElement.getAttribute("data-node-id"), index + 1);
}
});
}
});
};
export const transaction = (protyle: IProtyle, doOperations: IOperation[], undoOperations?: IOperation[]) => {
const lastTransaction = window.siyuan.transactions[window.siyuan.transactions.length - 1];
let needDebounce = false;
const time = new Date().getTime();
if (lastTransaction && lastTransaction.doOperations.length === 1 && lastTransaction.doOperations[0].action === "update" &&
doOperations.length === 1 && doOperations[0].action === "update" &&
lastTransaction.doOperations[0].id === doOperations[0].id &&
protyle.transactionTime - time < Constants.TIMEOUT_INPUT) {
needDebounce = true;
}
protyle.wysiwyg.lastHTMLs = {};
if (undoOperations) {
if (window.siyuan.config.fileTree.openFilesUseCurrentTab && protyle.model) {
protyle.model.headElement.classList.remove("item--unupdate");
}
protyle.updated = true;
if (needDebounce) {
protyle.undo.replace(doOperations);
} else {
protyle.undo.add(doOperations, undoOperations);
}
}
if (needDebounce) {
// 不能覆盖 undoOperations https://github.com/siyuan-note/siyuan/issues/3727
window.siyuan.transactions[window.siyuan.transactions.length - 1].protyle = protyle;
window.siyuan.transactions[window.siyuan.transactions.length - 1].doOperations = doOperations;
} else {
window.siyuan.transactions.push({
protyle,
doOperations,
undoOperations
});
}
protyle.transactionTime = time;
};
export const updateTransaction = (protyle: IProtyle, id: string, newHTML: string, html: string) => {
if (newHTML === html) {
return;
}
transaction(protyle, [{
id,
data: newHTML,
action: "update"
}], [{
id,
data: html,
action: "update"
}]);
};
export const updateBatchTransaction = (nodeElements: Element[], protyle: IProtyle, cb: (e: HTMLElement) => void) => {
const operations: IOperation[] = [];
const undoOperations: IOperation[] = [];
nodeElements.forEach((element) => {
const id = element.getAttribute("data-node-id");
element.classList.remove("protyle-wysiwyg--select");
undoOperations.push({
action: "update",
id,
data: element.outerHTML
});
cb(element as HTMLElement);
operations.push({
action: "update",
id,
data: element.outerHTML
});
});
transaction(protyle, operations, undoOperations);
};