A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1import {
2 getContenteditableElement,
3 getNextBlock,
4 getPreviousBlock,
5 hasPreviousSibling,
6 isNotEditBlock
7} from "../wysiwyg/getBlock";
8import {hasClosestBlock, hasClosestByAttribute, hasClosestByTag} from "./hasClosest";
9import {countBlockWord, countSelectWord} from "../../layout/status";
10import {hideElements} from "../ui/hideElements";
11import {genRenderFrame} from "../render/util";
12
13const selectIsEditor = (editor: Element, range?: Range) => {
14 if (!range) {
15 if (getSelection().rangeCount === 0) {
16 return false;
17 } else {
18 range = getSelection().getRangeAt(0);
19 }
20 }
21 const container = range.commonAncestorContainer;
22
23 return editor.isEqualNode(container) || editor.contains(container);
24};
25
26// table 选中处理
27export const fixTableRange = (range: Range) => {
28 const tableElement = hasClosestByAttribute(range.startContainer, "data-type", "NodeTable");
29 if (range.toString() !== "" && tableElement && range.commonAncestorContainer.nodeType !== 3) {
30 const parentTag = (range.commonAncestorContainer as Element).tagName;
31 if (parentTag !== "TH" && parentTag !== "TD") {
32 const startCellElement = hasClosestByTag(range.startContainer, "TD") || hasClosestByTag(range.startContainer, "TH");
33 const endCellElement = hasClosestByTag(range.endContainer, "TD") || hasClosestByTag(range.endContainer, "TH");
34 if (!startCellElement && !endCellElement) {
35 const cellElement = tableElement.querySelector("th") || tableElement.querySelector("td");
36 range.setStart(cellElement.firstChild, 0);
37 range.setEnd(cellElement.lastChild, cellElement.lastChild.textContent.length);
38 } else if (startCellElement &&
39 // 不能包含自身元素,否则对 cell 中的部分文字两次高亮后就会选中整个 cell。 https://github.com/siyuan-note/siyuan/issues/3649 第二点
40 !startCellElement.contains(range.endContainer)) {
41 setLastNodeRange(startCellElement, range, false);
42 }
43 }
44 }
45};
46
47export const selectAll = (protyle: IProtyle, nodeElement: Element, range: Range) => {
48 const editElement = getContenteditableElement(nodeElement);
49 if (editElement) {
50 let position;
51 if (editElement.tagName === "TABLE") {
52 const cellElement = hasClosestByTag(range.startContainer, "TD") || hasClosestByTag(range.startContainer, "TH");
53 if (cellElement) {
54 position = getSelectionOffset(cellElement, nodeElement, range);
55 if (position.start !== 0 || position.end !== cellElement.textContent.length) {
56 range.setStart(cellElement.firstChild, 0);
57 range.setEndAfter(cellElement.lastChild);
58 protyle.toolbar.render(protyle, range);
59 countSelectWord(range, protyle.block.rootID);
60 return true;
61 }
62 }
63 } else {
64 position = getSelectionOffset(editElement, nodeElement, range);
65 if (position.start !== 0 || position.end !== editElement.textContent.length) {
66 // 全选后 rang 不对 https://ld246.com/article/1654848722251
67 let firstChild = editElement.firstChild;
68 while (firstChild) {
69 if (firstChild.nodeType === 3) {
70 if (firstChild.textContent !== "") {
71 range.setStart(firstChild, 0);
72 break;
73 }
74 firstChild = firstChild.nextSibling;
75 } else {
76 if ((firstChild as HTMLElement).classList.contains("render-node") ||
77 (firstChild as HTMLElement).classList.contains("img")) {
78 range.setStartBefore(firstChild);
79 break;
80 }
81 firstChild = firstChild.firstChild;
82 }
83 }
84 let lastChild = editElement.lastChild as HTMLElement;
85 while (lastChild) {
86 if (lastChild.nodeType === 3) {
87 if (lastChild.textContent !== "") {
88 range.setEnd(lastChild, lastChild.textContent.length);
89 break;
90 }
91 lastChild = lastChild.previousSibling as HTMLElement;
92 } else {
93 if (lastChild.classList.contains("render-node") ||
94 lastChild.classList.contains("img") ||
95 lastChild.tagName === "BR") {
96 range.setEndAfter(lastChild);
97 break;
98 }
99 lastChild = lastChild.lastChild as HTMLElement;
100 }
101 }
102 // 列表回车后,左键全选无法选中
103 focusByRange(range);
104 protyle.toolbar.render(protyle, range);
105 countSelectWord(range, protyle.block.rootID);
106 return true;
107 }
108 }
109 }
110 range.collapse(true);
111 const selectElements = protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select");
112 if (protyle.wysiwyg.element.childElementCount === selectElements.length && (selectElements[0].parentElement === protyle.wysiwyg.element)) {
113 return true;
114 }
115 hideElements(["select"], protyle);
116 const ids: string [] = [];
117 Array.from(protyle.wysiwyg.element.children).forEach(item => {
118 const nodeId = item.getAttribute("data-node-id");
119 if (nodeId) {
120 item.classList.add("protyle-wysiwyg--select");
121 ids.push(nodeId);
122 }
123 });
124 countBlockWord(ids, protyle.block.rootID);
125};
126
127// https://github.com/siyuan-note/siyuan/issues/8196
128export const getRangeByPoint = (x: number, y: number) => {
129 const range = document.caretRangeFromPoint(x, y);
130 const imgElement = hasClosestByAttribute(range.startContainer, "data-type", "img");
131 if (imgElement) {
132 range.setStart(imgElement.nextSibling, 0);
133 range.collapse();
134 }
135 return range;
136};
137
138export const getEditorRange = (element: Element): Range => {
139 let range: Range;
140 if (getSelection().rangeCount > 0) {
141 range = getSelection().getRangeAt(0);
142 if (element === range.startContainer || element.contains(range.startContainer)) {
143 if (range.toString() === "" && range.startContainer.nodeType === 1) {
144 // 有时候点击编辑器头部需要矫正到第一个块中
145 if (range.startOffset === 0 && (range.startContainer as HTMLElement).classList.contains("protyle-wysiwyg")) {
146 const focusRange = focusBlock(range.startContainer.firstChild as Element);
147 if (focusRange) {
148 return focusRange;
149 }
150 }
151 // 移动端获取有偏差 https://github.com/siyuan-note/siyuan/issues/15998
152 if ((range.startContainer as Element).getAttribute("contenteditable") !== "true" &&
153 getContenteditableElement(range.startContainer as Element)) {
154 const blockElement = hasClosestBlock(range.startContainer);
155 if (blockElement) {
156 const focusRange = focusBlock(blockElement);
157 if (focusRange) {
158 return focusRange;
159 }
160 }
161 }
162 }
163 return range;
164 }
165 }
166
167 if (element.classList.contains("li") || element.classList.contains("list")) {
168 const childElement = element.querySelector("[data-node-id]");
169 if (childElement) {
170 return getEditorRange(childElement);
171 }
172 }
173
174 // 代码块过长,在代码块的下一个块前删除,代码块会滚动到顶部,因粗需要 preventScroll
175 (element as HTMLElement).focus({preventScroll: true});
176 if (!range) {
177 range = document.createRange();
178 }
179
180 let targetElement;
181 if (element.classList.contains("table")) {
182 // 当光标不在表格区域中时表格无法被复制 https://ld246.com/article/1650510736504
183 targetElement = element.querySelector("th") || element.querySelector("td");
184 } else {
185 targetElement = getContenteditableElement(element);
186 if (!targetElement) {
187 const type = element.getAttribute("data-type");
188 if (type === "NodeThematicBreak") {
189 targetElement = element.firstElementChild;
190 } else if (type === "NodeBlockQueryEmbed") {
191 targetElement = element.querySelector(".protyle-cursor")?.firstChild;
192 } else if (["NodeMathBlock", "NodeHTMLBlock"].includes(type)) {
193 targetElement = element.lastElementChild.previousElementSibling?.lastElementChild?.firstChild;
194 } else if (type === "NodeVideo") {
195 targetElement = element.firstElementChild.firstChild;
196 } else if (type === "NodeAudio") {
197 targetElement = element.firstElementChild.lastChild;
198 }
199 } else if (targetElement.tagName === "TABLE") {
200 // 文档中开头为表格,获取错误 https://ld246.com/article/1663408335459?r=88250
201 targetElement = targetElement.querySelector("th") || element.querySelector("td");
202 }
203 }
204 range.setStart(targetElement || element, 0);
205 range.collapse(true);
206 return range;
207};
208
209export const getSelectionPosition = (nodeElement: Element, range?: Range, useDirect = false) => {
210 if (!range) {
211 range = getEditorRange(nodeElement);
212 }
213 if (!nodeElement.contains(range.startContainer)) {
214 return {
215 left: 0,
216 top: 0,
217 };
218 }
219 let cursorRect;
220 if (range.getClientRects().length === 0) {
221 if (range.startContainer.nodeType === 3) {
222 // 空行时,会出现没有 br 的情况,需要根据父元素 <p> 获取位置信息
223 const parentRects = range.startContainer.parentElement?.getClientRects();
224 // 连续粘贴图片时
225 const previousRects = (range.startContainer as Element).previousElementSibling?.getClientRects();
226 if (parentRects.length > 0 || previousRects.length > 0) {
227 if (parentRects.length === 0 || (previousRects &&
228 previousRects.length > 0 && parentRects[0].top < previousRects[previousRects.length - 1].bottom)) {
229 cursorRect = {
230 left: previousRects[previousRects.length - 1].left,
231 top: previousRects[previousRects.length - 1].bottom,
232 };
233 } else {
234 cursorRect = parentRects[0];
235 }
236 } else {
237 return {
238 left: 0,
239 top: 0,
240 };
241 }
242 } else {
243 const children = (range.startContainer as Element).children;
244 if (children[range.startOffset] &&
245 children[range.startOffset].getClientRects().length > 0) {
246 // markdown 模式回车
247 cursorRect = children[range.startOffset].getClientRects()[0];
248 } else if (range.startContainer.childNodes.length > 0) {
249 // in table or code block
250 const cloneRange = range.cloneRange();
251 range.selectNode(range.startContainer.childNodes[Math.max(0, range.startOffset - 1)]);
252 cursorRect = range.getClientRects()[0];
253 range.setEnd(cloneRange.endContainer, cloneRange.endOffset);
254 range.setStart(cloneRange.startContainer, cloneRange.startOffset);
255 } else {
256 cursorRect = (range.startContainer as HTMLElement).getClientRects()[0];
257 }
258 if (!cursorRect) {
259 let parentElement = range.startContainer.childNodes[range.startOffset] as HTMLElement;
260 if (!parentElement) {
261 parentElement = range.startContainer.childNodes[range.startOffset - 1] as HTMLElement;
262 }
263 if (!parentElement) {
264 cursorRect = range.getBoundingClientRect();
265 } else {
266 while (!parentElement.getClientRects || (parentElement.getClientRects && parentElement.getClientRects().length === 0)) {
267 parentElement = parentElement.parentElement;
268 }
269 cursorRect = parentElement.getClientRects()[0];
270 }
271 }
272 }
273 } else {
274 const rects = range.getClientRects(); // 由于长度过长折行,光标在行首时有多个 rects https://github.com/siyuan-note/siyuan/issues/6156
275 if (range.toString()) {
276 if (useDirect) {
277 const selection = window.getSelection();
278 // 判断选择方向
279 const isBackward = (selection && "direction" in selection && selection.direction !== "none") ?
280 selection.direction === "backward"
281 : range.startContainer === selection?.focusNode && range.startOffset === selection?.focusOffset;
282 const isBottom = !isBackward && rects[0].top !== rects[rects.length - 1].top;
283 return {
284 // 向左选择:使用第一个矩形的左边界;向右选择:使用最后一个矩形的右边界
285 left: isBackward ? rects[0].left : rects[rects.length - 1].right,
286 // 如果向右选择时有多个垂直位置不同的矩形:使用最后一个矩形的下边界;否则使用第一个矩形的上边界
287 top: isBottom ? rects[rects.length - 1].bottom : rects[0].top,
288 isBottom
289 };
290 } else {
291 return { // 选中多行不应遮挡第一行 https://github.com/siyuan-note/siyuan/issues/7541
292 left: rects[rects.length - 1].left,
293 top: rects[0].top
294 };
295 }
296 } else {
297 return { // 代码块首 https://github.com/siyuan-note/siyuan/issues/13113
298 left: rects[rects.length - 1].left,
299 top: rects[rects.length - 1].top
300 };
301 }
302 }
303
304 return {
305 left: cursorRect.left,
306 top: cursorRect.top,
307 };
308};
309
310export const getSelectionOffset = (selectElement: Node, editorElement?: Element, range?: Range) => {
311 const position = {
312 end: 0,
313 start: 0,
314 };
315
316 if (!range) {
317 if (getSelection().rangeCount === 0) {
318 return position;
319 }
320 range = window.getSelection().getRangeAt(0);
321 }
322
323 if (editorElement && !selectIsEditor(editorElement, range)) {
324 return position;
325 }
326 const preSelectionRange = range.cloneRange();
327 if (selectElement.childNodes[0] && selectElement.childNodes[0].childNodes[0]) {
328 preSelectionRange.setStart(selectElement.childNodes[0].childNodes[0], 0);
329 } else {
330 preSelectionRange.selectNodeContents(selectElement);
331 }
332 preSelectionRange.setEnd(range.startContainer, range.startOffset);
333 // 需加上表格内软换行 br 的长度
334 position.start = preSelectionRange.toString().length + preSelectionRange.cloneContents().querySelectorAll("br").length;
335 position.end = position.start + range.toString().length + range.cloneContents().querySelectorAll("br").length;
336 return position;
337};
338
339function searchNode(
340 container: Node,
341 startNode: Node,
342 predicate: (node: Node) => boolean,
343 excludeSibling?: boolean,
344): boolean {
345 if (!startNode) {
346 return false;
347 }
348
349 if (predicate(startNode as Text)) {
350 return true;
351 }
352
353 for (let i = 0, len = startNode.childNodes.length; i < len; i++) {
354 if (searchNode(startNode, startNode.childNodes[i], predicate, true)) {
355 return true;
356 }
357 }
358
359 if (!excludeSibling) {
360 let parentNode = startNode;
361 while (parentNode && parentNode !== container) {
362 let nextSibling = parentNode.nextSibling;
363 while (nextSibling) {
364 if (searchNode(container, nextSibling, predicate, true)) {
365 return true;
366 }
367 nextSibling = nextSibling.nextSibling;
368 }
369 parentNode = parentNode.parentNode;
370 }
371 }
372
373 return false;
374}
375
376export const setLastNodeRange = (editElement: Element, range: Range, setStart = true) => {
377 if (!editElement) {
378 return range;
379 }
380 let lastNode = editElement.lastChild as Element;
381 while (lastNode && lastNode.nodeType !== 3) {
382 // https://github.com/siyuan-note/siyuan/issues/12792
383 if (!lastNode.lastChild) {
384 break;
385 }
386 // 最后一个为多种行内元素嵌套
387 lastNode = lastNode.lastChild as Element;
388 }
389 // https://github.com/siyuan-note/siyuan/issues/12753
390 if (!lastNode) {
391 lastNode = editElement;
392 }
393 if (setStart) {
394 if (lastNode.nodeType !== 3 && (lastNode.classList.contains("render-node") || lastNode.tagName === "BR") && lastNode.innerHTML === "") {
395 range.setStartAfter(lastNode);
396 } else {
397 range.setStart(lastNode, lastNode.textContent.length);
398 }
399 } else {
400 if (lastNode.nodeType !== 3 && (lastNode.classList.contains("render-node") || lastNode.tagName === "BR") && lastNode.innerHTML === "") {
401 range.setEndAfter(lastNode);
402 } else {
403 range.setEnd(lastNode, lastNode.textContent.length);
404 }
405 }
406 return range;
407};
408
409export const setFirstNodeRange = (editElement: Element, range: Range) => {
410 if (!editElement) {
411 return range;
412 }
413 let firstChild = editElement.firstChild as HTMLElement;
414 while (firstChild && firstChild.nodeType !== 3 && !firstChild.classList.contains("render-node")) {
415 if (firstChild.classList.contains("img")) { // https://ld246.com/article/1665360254842
416 range.setStartBefore(firstChild);
417 return range;
418 }
419 firstChild = firstChild.firstChild as HTMLElement;
420 }
421 if (!firstChild) {
422 range.selectNodeContents(editElement);
423 return range;
424 }
425 if (firstChild.nodeType !== 3 && firstChild.classList.contains("render-node")) {
426 range.setStartBefore(firstChild);
427 } else {
428 range.setStart(firstChild, 0);
429 }
430 return range;
431};
432
433export const focusByOffset = (container: Element, start: number, end: number, isFocus = true) => {
434 if (!container) {
435 return false;
436 }
437 // 空块无法 focus
438 const editElement = getContenteditableElement(container);
439 if (editElement) {
440 container = editElement;
441 } else if (isFocus && (isNotEditBlock(container) || container.classList.contains("av"))) {
442 return focusBlock(container);
443 }
444 let startNode: Node;
445 searchNode(container, container.firstChild, node => {
446 if (node.nodeType === Node.TEXT_NODE) {
447 const dataLength = (node as Text).data.length;
448 if (start <= dataLength) {
449 startNode = node;
450 return true;
451 }
452 start -= dataLength;
453 end -= dataLength;
454 return false;
455 } else if (node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName === "BR") {
456 if (start <= 1) {
457 startNode = node;
458 return true;
459 }
460 start -= 1;
461 end -= 1;
462 return false;
463 }
464 });
465
466 let endNode;
467 if (startNode) {
468 searchNode(container, startNode, node => {
469 if (node.nodeType === Node.TEXT_NODE) {
470 const dataLength = (node as Text).data.length;
471 if (end <= dataLength) {
472 endNode = node;
473 return true;
474 }
475 end -= dataLength;
476 return false;
477 }
478 });
479 }
480
481 const range = document.createRange();
482 if (startNode) {
483 if (startNode.nodeType === Node.TEXT_NODE && start <= (startNode as Text).data.length) {
484 range.setStart(startNode, start);
485 } else {
486 range.setStartAfter(startNode);
487 }
488 } else {
489 if (start === 0) {
490 range.setStart(container, 0);
491 } else {
492 setLastNodeRange(getContenteditableElement(container as Element), range);
493 }
494 }
495
496 if (endNode) {
497 if (end <= (endNode as Text).data.length) {
498 range.setEnd(endNode, end);
499 } else {
500 range.setEndAfter(endNode);
501 }
502 } else {
503 if (end === 0) {
504 range.setEnd(container, 0);
505 } else {
506 setLastNodeRange(getContenteditableElement(container as Element), range, false);
507 }
508 }
509 if (isFocus) {
510 focusByRange(range);
511 }
512 return range;
513};
514
515export const setInsertWbrHTML = (nodeElement: HTMLElement, range: Range, protyle: IProtyle) => {
516 const editElement = getContenteditableElement(nodeElement);
517 if (!editElement) {
518 return;
519 }
520 const offset = getSelectionOffset(editElement, nodeElement, range);
521 const cloneNode = nodeElement.cloneNode(true) as HTMLElement;
522 const cloneRange = focusByOffset(cloneNode, offset.end, offset.end, false);
523 if (cloneRange) {
524 cloneRange.insertNode(document.createElement("wbr"));
525 }
526 protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] = cloneNode.outerHTML;
527};
528
529export const focusByWbr = (element: Element, range: Range) => {
530 const wbrElements = element.querySelectorAll("wbr");
531 if (wbrElements.length === 0) {
532 return;
533 }
534 // 没找到 wbr 产生多个的地方,先顶顶
535 wbrElements.forEach((item, index) => {
536 if (index !== 0) {
537 item.remove();
538 }
539 });
540 const wbrElement = wbrElements[0];
541 if (!wbrElement.previousElementSibling) {
542 if (wbrElement.previousSibling) {
543 // text<wbr>
544 range.setStart(wbrElement.previousSibling, wbrElement.previousSibling.textContent.length);
545 } else if (wbrElement.nextSibling) {
546 if (wbrElement.nextSibling.nodeType === 3) {
547 // <wbr>text
548 range.setStart(wbrElement.nextSibling, 0);
549 } else {
550 // <wbr><span>a</span>
551 range.setStartAfter(wbrElement);
552 }
553 } else {
554 // 内容为空
555 range.setStart(wbrElement.parentElement, 0);
556 }
557 } else {
558 const wbrPreviousSibling = hasPreviousSibling(wbrElement);
559 if (wbrPreviousSibling && wbrElement.previousElementSibling === wbrPreviousSibling) {
560 if (wbrElement.previousElementSibling.lastChild?.nodeType === 3) {
561 // <em>text</em><wbr> 需把光标放在里面,因为 chrome 点击后也是默认在里面
562 range.setStart(wbrElement.previousElementSibling.lastChild, wbrElement.previousElementSibling.lastChild.textContent.length);
563 } else if (wbrPreviousSibling.nodeType !== 3 && (wbrPreviousSibling as HTMLElement).classList.contains("img")) {
564 // <img><wbr>, 删除图片后的唯一的一个字符
565 range.setStartAfter(wbrPreviousSibling);
566 } else {
567 // <span class="hljs-function"><span class="hljs-keyword">fun</span></span>
568 range.setStartBefore(wbrElement);
569 }
570 } else {
571 // <em>text</em>text<wbr>
572 range.setStart(wbrElement.previousSibling, wbrElement.previousSibling.textContent.length);
573 }
574 }
575 range.collapse(true);
576 wbrElement.remove();
577 focusByRange(range);
578};
579
580export const focusByRange = (range: Range) => {
581 if (!range) {
582 return;
583 }
584
585 const startNode = range.startContainer.childNodes[range.startOffset] as HTMLElement;
586 if (startNode && startNode.nodeType !== 3 && ["INPUT", "TEXTAREA"].includes(startNode.tagName)) {
587 startNode.focus();
588 return;
589 }
590 const selection = window.getSelection();
591 selection.removeAllRanges();
592 selection.addRange(range);
593};
594
595export const focusBlock = (element: Element, parentElement?: HTMLElement, toStart = true): false | Range => {
596 if (!element) {
597 return false;
598 }
599
600 // hr、嵌入块、数学公式、iframe、音频、视频、图表渲染块等,删除段落块后,光标位置矫正 https://github.com/siyuan-note/siyuan/issues/4143
601 if (element.classList.contains("render-node") || element.classList.contains("iframe") || element.classList.contains("hr") || element.classList.contains("av")) {
602 const range = document.createRange();
603 const type = element.getAttribute("data-type");
604 let setRange = false;
605 if (type === "NodeThematicBreak") {
606 range.selectNodeContents(element.firstElementChild);
607 setRange = true;
608 } else if (type === "NodeBlockQueryEmbed") {
609 genRenderFrame(element);
610 range.setStart(element.querySelector(".protyle-cursor").firstChild, 0);
611 range.collapse(true);
612 setRange = true;
613 } else if (type === "NodeMathBlock") {
614 genRenderFrame(element);
615 range.setStart(element.firstElementChild.lastElementChild.firstChild, 0);
616 setRange = true;
617 } else if (type === "NodeHTMLBlock") {
618 range.setStart(element.lastElementChild.previousElementSibling.lastElementChild.firstChild, 0);
619 range.collapse(true);
620 setRange = true;
621 } else if (type === "NodeIFrame" || type === "NodeWidget") {
622 range.setStart(element, 0);
623 setRange = true;
624 } else if (type === "NodeVideo") {
625 range.setStart(element.firstElementChild.firstChild, 0);
626 setRange = true;
627 } else if (type === "NodeAudio") {
628 range.setStart(element.firstElementChild.lastChild, 0);
629 setRange = true;
630 } else if (type === "NodeCodeBlock") {
631 range.selectNodeContents(element);
632 range.collapse(true);
633 setRange = true;
634 } else if (type === "NodeAttributeView") {
635 /// #if !MOBILE
636 const cursorElement = element.querySelector(".av__cursor");
637 if (cursorElement) {
638 range.setStart(cursorElement.firstChild, 0);
639 setRange = true;
640 } else {
641 element.setAttribute("data-need-focus", "true");
642 return false;
643 }
644 /// #else
645 return false;
646 /// #endif
647 }
648 if (setRange) {
649 focusByRange(range);
650 return range;
651 } else {
652 focusSideBlock(element);
653 return false;
654 }
655 }
656 let cursorElement;
657 if (toStart) {
658 cursorElement = getContenteditableElement(element);
659 } else {
660 Array.from(element.querySelectorAll('[contenteditable="true"]')).reverse().find(item => {
661 if (item.getBoundingClientRect().width > 0) {
662 cursorElement = item;
663 return true;
664 }
665 });
666 }
667 if (cursorElement) {
668 if (cursorElement.tagName === "TABLE") {
669 if (toStart) {
670 cursorElement = cursorElement.querySelector("th, td");
671 } else {
672 const cellElements = cursorElement.querySelectorAll("th, td");
673 cursorElement = cellElements[cellElements.length - 1];
674 }
675 }
676 let range;
677 if (toStart) {
678 // 需要定位到第一个 child https://github.com/siyuan-note/siyuan/issues/5930
679 range = setFirstNodeRange(cursorElement, getEditorRange(cursorElement));
680 range.collapse(true);
681 } else {
682 let focusHljs = false;
683 // 定位到末尾 https://github.com/siyuan-note/siyuan/issues/5982
684 if (element.getAttribute("data-type") === "NodeCodeBlock") {
685 // 代码块末尾定位需在 /n 之前 https://github.com/siyuan-note/siyuan/issues/9141,https://github.com/siyuan-note/siyuan/issues/9189
686 let lastNode = cursorElement.lastChild;
687 if (!lastNode) {
688 // 粘贴 ``` 报错
689 cursorElement.innerHTML = "\n";
690 lastNode = cursorElement.lastChild;
691 }
692 if (lastNode.textContent === "" && lastNode.nodeType === 3) {
693 lastNode = hasPreviousSibling(cursorElement.lastChild) as HTMLElement;
694 }
695 if (lastNode && lastNode.textContent.endsWith("\n")) {
696 // https://github.com/siyuan-note/siyuan/issues/11362
697 if (lastNode.nodeType === 1) {
698 lastNode = lastNode.lastChild;
699 while (lastNode && lastNode.textContent.indexOf("\n") === -1) {
700 lastNode = lastNode.previousSibling;
701 }
702 }
703 range = getEditorRange(cursorElement);
704 range.setStart(lastNode, lastNode.textContent.length - 1);
705 focusHljs = true;
706 }
707 }
708 if (!focusHljs) {
709 range = setLastNodeRange(cursorElement, getEditorRange(cursorElement));
710 }
711 range.collapse(false);
712 }
713 focusByRange(range);
714 return range;
715 } else if (parentElement) {
716 parentElement.focus();
717 } else {
718 // li 下面为 hr、嵌入块、数学公式、iframe、音频、视频、图表渲染块等时递归处理
719 if (element.classList.contains("li")) {
720 return focusBlock(element.querySelector("[data-node-id]"), parentElement, toStart);
721 }
722 }
723 return false;
724};
725
726export const focusSideBlock = (updateElement: Element) => {
727 if (updateElement.getAttribute("data-node-id")) {
728 let sideBlockElement;
729 let collapse;
730 if (updateElement.nextElementSibling &&
731 !updateElement.nextElementSibling.classList.contains("protyle-attr") // 用例 https://ld246.com/article/1661928364696
732 ) {
733 collapse = true;
734 sideBlockElement = getNextBlock(updateElement) as HTMLElement;
735 } else if (updateElement.previousElementSibling) {
736 collapse = false;
737 sideBlockElement = getPreviousBlock(updateElement) as HTMLElement;
738 }
739 if (!sideBlockElement) {
740 sideBlockElement = updateElement;
741 }
742 focusBlock(sideBlockElement, undefined, collapse);
743 return;
744 }
745 const range = getEditorRange(updateElement);
746 if (updateElement.nextSibling) {
747 range.selectNodeContents(updateElement.nextSibling);
748 range.collapse(true);
749 } else if (updateElement.previousSibling) {
750 range.selectNodeContents(updateElement.previousSibling);
751 range.collapse(false);
752 }
753 focusByRange(range);
754};
755