Skip to content

Commit b2bbaef

Browse files
committed
Refactor list handling by introducing getLastChildBlock utility function
1 parent 98a5717 commit b2bbaef

File tree

1 file changed

+45
-22
lines changed

1 file changed

+45
-22
lines changed

app/src/protyle/wysiwyg/list.ts

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,23 @@ import {genEmptyBlock} from "../../block/util";
44
import * as dayjs from "dayjs";
55
import {Constants} from "../../constants";
66
import {moveToPrevious, removeBlock} from "./remove";
7-
import {hasClosestBlock, hasClosestByClassName, isBlockElement} from "../util/hasClosest";
7+
import {hasClosestByClassName, isBlockElement} from "../util/hasClosest";
88
import {setFold} from "../../menus/protyle";
99

10+
const getLastChildBlock = (element: Element): Element | null => {
11+
if (!element || !element.lastElementChild) {
12+
return null;
13+
}
14+
let current = element.lastElementChild.previousElementSibling;
15+
while (current) {
16+
if (isBlockElement(current)) {
17+
return current;
18+
}
19+
current = current.previousElementSibling;
20+
}
21+
return null;
22+
};
23+
1024
export const updateListOrder = (listElement: Element, sIndex?: number) => {
1125
if (listElement.getAttribute("data-subtype") !== "o") {
1226
return;
@@ -73,7 +87,10 @@ export const addSubList = (protyle: IProtyle, nodeElement: Element, range: Range
7387

7488
// 无列表块:在列表项块的最后一个子块后面插入新的列表块
7589
if (!listElement) {
76-
const lastChildBlock = liElement.lastElementChild.previousElementSibling;
90+
const lastChildBlock = getLastChildBlock(liElement);
91+
if (!lastChildBlock) {
92+
return;
93+
}
7794
const subType = liElement.getAttribute("data-subtype") || "u";
7895
const id = Lute.NewNodeID();
7996
const newListItemElement = genListItemElement(liElement, 0, true, 1);
@@ -93,7 +110,7 @@ export const addSubList = (protyle: IProtyle, nodeElement: Element, range: Range
93110
}
94111

95112
// 有列表块:在列表块的最后一个列表项块后插入新的列表项块
96-
const lastSubItem = listElement.lastElementChild.previousElementSibling;
113+
const lastSubItem = getLastChildBlock(listElement);
97114
if (lastSubItem) {
98115
const newListElement = genListItemElement(lastSubItem, 0, true);
99116
const id = newListElement.getAttribute("data-node-id");
@@ -141,15 +158,17 @@ export const listIndent = (protyle: IProtyle, liItemElements: Element[], range:
141158
range.collapse(false);
142159
range.insertNode(document.createElement("wbr"));
143160
const html = previousElement.parentElement.outerHTML;
144-
if (previousElement.lastElementChild.previousElementSibling.getAttribute("data-type") === "NodeList") {
161+
const previousLastBlock = getLastChildBlock(previousElement);
162+
if (previousLastBlock && previousLastBlock.getAttribute("data-type") === "NodeList") {
145163
// 上一个列表的最后一项为子列表
146-
const previousLastListHTML = previousElement.lastElementChild.previousElementSibling.outerHTML;
164+
const previousLastListHTML = previousLastBlock.outerHTML;
147165

148166
const doOperations: IOperation[] = [];
149167
const undoOperations: IOperation[] = [];
150168

151-
const subtype = previousElement.lastElementChild.previousElementSibling.getAttribute("data-subtype");
152-
let previousID = previousElement.lastElementChild.previousElementSibling.lastElementChild.previousElementSibling.getAttribute("data-node-id");
169+
const subtype = previousLastBlock.getAttribute("data-subtype");
170+
const previousLastListLastBlock = getLastChildBlock(previousLastBlock);
171+
let previousID = previousLastListLastBlock ? previousLastListLastBlock.getAttribute("data-node-id") : undefined;
153172
liItemElements.forEach((item, index) => {
154173
doOperations.push({
155174
action: "move",
@@ -167,23 +186,23 @@ export const listIndent = (protyle: IProtyle, liItemElements: Element[], range:
167186
if (subtype === "o") {
168187
actionElement.classList.add("protyle-action--order");
169188
actionElement.classList.remove("protyle-action--task");
170-
previousElement.lastElementChild.previousElementSibling.lastElementChild.before(item);
189+
previousLastBlock.lastElementChild.before(item);
171190
} else if (subtype === "t") {
172191
item.setAttribute("data-marker", "*");
173192
actionElement.innerHTML = `<svg><use xlink:href="#icon${item.classList.contains("protyle-task--done") ? "Check" : "Uncheck"}"></use></svg>`;
174193
actionElement.classList.remove("protyle-action--order");
175194
actionElement.classList.add("protyle-action--task");
176-
previousElement.lastElementChild.previousElementSibling.lastElementChild.before(item);
195+
previousLastBlock.lastElementChild.before(item);
177196
} else {
178197
item.setAttribute("data-marker", "*");
179198
actionElement.innerHTML = '<svg><use xlink:href="#iconDot"></use></svg>';
180199
actionElement.classList.remove("protyle-action--order", "protyle-action--task");
181-
previousElement.lastElementChild.previousElementSibling.lastElementChild.before(item);
200+
previousLastBlock.lastElementChild.before(item);
182201
}
183202
});
184203

185204
if (subtype === "o") {
186-
updateListOrder(previousElement.lastElementChild.previousElementSibling);
205+
updateListOrder(previousLastBlock);
187206
updateListOrder(previousElement.parentElement);
188207
} else if (previousElement.getAttribute("data-subtype") === "o") {
189208
updateListOrder(previousElement.parentElement);
@@ -192,13 +211,13 @@ export const listIndent = (protyle: IProtyle, liItemElements: Element[], range:
192211
if (previousElement.parentElement.classList.contains("protyle-wysiwyg")) {
193212
doOperations.push({
194213
action: "update",
195-
data: previousElement.lastElementChild.previousElementSibling.outerHTML,
196-
id: previousElement.lastElementChild.previousElementSibling.getAttribute("data-node-id")
214+
data: previousLastBlock.outerHTML,
215+
id: previousLastBlock.getAttribute("data-node-id")
197216
});
198217
undoOperations.push({
199218
action: "update",
200219
data: previousLastListHTML,
201-
id: previousElement.lastElementChild.previousElementSibling.getAttribute("data-node-id")
220+
id: previousLastBlock.getAttribute("data-node-id")
202221
});
203222
transaction(protyle, doOperations, undoOperations);
204223
}
@@ -212,11 +231,12 @@ export const listIndent = (protyle: IProtyle, liItemElements: Element[], range:
212231
newListElement.setAttribute("class", "list");
213232
newListElement.setAttribute("data-subtype", subType);
214233
newListElement.innerHTML = '<div class="protyle-attr" contenteditable="false"></div>';
234+
const previousLastBlockForNewList = getLastChildBlock(previousElement);
215235
const doOperations: IOperation[] = [{
216236
action: "insert",
217237
data: newListElement.outerHTML,
218238
id: newListId,
219-
previousID: previousElement.lastElementChild.previousElementSibling.getAttribute("data-node-id")
239+
previousID: previousLastBlockForNewList ? previousLastBlockForNewList.getAttribute("data-node-id") : undefined
220240
}];
221241
previousElement.lastElementChild.before(newListElement);
222242
const undoOperations: IOperation[] = [];
@@ -414,7 +434,7 @@ export const listOutdent = (protyle: IProtyle, liItemElements: Element[], range:
414434
let topPreviousID = liId;
415435
let previousElement: Element = liElement;
416436
let nextElement = liItemElements[liItemElements.length - 1].nextElementSibling;
417-
let lastBlockElement = liItemElements[liItemElements.length - 1].lastElementChild.previousElementSibling;
437+
let lastBlockElement = getLastChildBlock(liItemElements[liItemElements.length - 1]);
418438
liItemElements.forEach(item => {
419439
Array.from(item.children).forEach((blockElement, index) => {
420440
const id = blockElement.getAttribute("data-node-id");
@@ -442,7 +462,7 @@ export const listOutdent = (protyle: IProtyle, liItemElements: Element[], range:
442462
if (!window.siyuan.config.editor.listLogicalOutdent && !nextElement.classList.contains("protyle-attr")) {
443463
// 传统缩进
444464
let newId;
445-
if (lastBlockElement.getAttribute("data-subtype") !== nextElement.getAttribute("data-subtype")) {
465+
if (!lastBlockElement || lastBlockElement.getAttribute("data-subtype") !== nextElement.getAttribute("data-subtype")) {
446466
newId = Lute.NewNodeID();
447467
lastBlockElement = document.createElement("div");
448468
lastBlockElement.classList.add("list");
@@ -461,10 +481,11 @@ export const listOutdent = (protyle: IProtyle, liItemElements: Element[], range:
461481
}
462482
let topOldPreviousID;
463483
while (nextElement && !nextElement.classList.contains("protyle-attr")) {
484+
const lastBlockLastBlock = lastBlockElement ? getLastChildBlock(lastBlockElement) : null;
464485
topDoOperations.push({
465486
action: "move",
466487
id: nextElement.getAttribute("data-node-id"),
467-
previousID: topOldPreviousID || lastBlockElement.lastElementChild.previousElementSibling?.getAttribute("data-node-id"),
488+
previousID: topOldPreviousID || (lastBlockLastBlock ? lastBlockLastBlock.getAttribute("data-node-id") : undefined),
468489
parentID: lastBlockElement.getAttribute("data-node-id")
469490
});
470491
topUndoOperations.push({
@@ -586,7 +607,7 @@ export const listOutdent = (protyle: IProtyle, liItemElements: Element[], range:
586607
}
587608
const html = parentLiItemElement.parentElement.outerHTML;
588609
let nextElement = liItemElements[liItemElements.length - 1].nextElementSibling;
589-
let lastBlockElement = liItemElements[liItemElements.length - 1].lastElementChild.previousElementSibling;
610+
let lastBlockElement = getLastChildBlock(liItemElements[liItemElements.length - 1]);
590611
liItemElements.reverse().forEach(item => {
591612
const itemId = item.getAttribute("data-node-id");
592613
doOperations.push({
@@ -652,7 +673,7 @@ export const listOutdent = (protyle: IProtyle, liItemElements: Element[], range:
652673
if (!window.siyuan.config.editor.listLogicalOutdent && !nextElement.classList.contains("protyle-attr")) {
653674
// 传统缩进
654675
let newId;
655-
if (!lastBlockElement.classList.contains("list")) {
676+
if (!lastBlockElement || !lastBlockElement.classList.contains("list")) {
656677
newId = Lute.NewNodeID();
657678
lastBlockElement = document.createElement("div");
658679
lastBlockElement.classList.add("list");
@@ -661,11 +682,12 @@ export const listOutdent = (protyle: IProtyle, liItemElements: Element[], range:
661682
lastBlockElement.setAttribute("data-type", "NodeList");
662683
lastBlockElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
663684
lastBlockElement.innerHTML = `<div class="protyle-attr" contenteditable="false">${Constants.ZWSP}</div>`;
685+
const firstItemLastBlock = getLastChildBlock(liItemElements[0]);
664686
doOperations.push({
665687
action: "insert",
666688
id: newId,
667689
data: lastBlockElement.outerHTML,
668-
previousID: liItemElements[0].lastElementChild.previousElementSibling.getAttribute("data-node-id"),
690+
previousID: firstItemLastBlock ? firstItemLastBlock.getAttribute("data-node-id") : undefined,
669691
});
670692
liItemElements[0].lastElementChild.before(lastBlockElement);
671693
}
@@ -687,10 +709,11 @@ export const listOutdent = (protyle: IProtyle, liItemElements: Element[], range:
687709
data: nextElement.outerHTML
688710
});
689711
}
712+
const lastBlockLastBlock = getLastChildBlock(lastBlockElement);
690713
doOperations.push({
691714
action: "move",
692715
id: nextId,
693-
previousID: subPreviousID || lastBlockElement.lastElementChild.previousElementSibling?.getAttribute("data-node-id"),
716+
previousID: subPreviousID || (lastBlockLastBlock ? lastBlockLastBlock.getAttribute("data-node-id") : undefined),
694717
parentID: lastBlockElement.getAttribute("data-node-id")
695718
});
696719
undoOperations.push({

0 commit comments

Comments
 (0)