A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 755 lines 32 kB view raw
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