import { getContenteditableElement, getNextBlock, getPreviousBlock, hasPreviousSibling, isNotEditBlock } from "../wysiwyg/getBlock"; import {hasClosestByAttribute, hasClosestByTag} from "./hasClosest"; import {countBlockWord, countSelectWord} from "../../layout/status"; import {hideElements} from "../ui/hideElements"; import {genRenderFrame} from "../render/util"; const selectIsEditor = (editor: Element, range?: Range) => { if (!range) { if (getSelection().rangeCount === 0) { return false; } else { range = getSelection().getRangeAt(0); } } const container = range.commonAncestorContainer; return editor.isEqualNode(container) || editor.contains(container); }; // table 选中处理 export const fixTableRange = (range: Range) => { const tableElement = hasClosestByAttribute(range.startContainer, "data-type", "NodeTable"); if (range.toString() !== "" && tableElement && range.commonAncestorContainer.nodeType !== 3) { const parentTag = (range.commonAncestorContainer as Element).tagName; if (parentTag !== "TH" && parentTag !== "TD") { const startCellElement = hasClosestByTag(range.startContainer, "TD") || hasClosestByTag(range.startContainer, "TH"); const endCellElement = hasClosestByTag(range.endContainer, "TD") || hasClosestByTag(range.endContainer, "TH"); if (!startCellElement && !endCellElement) { const cellElement = tableElement.querySelector("th") || tableElement.querySelector("td"); range.setStart(cellElement.firstChild, 0); range.setEnd(cellElement.lastChild, cellElement.lastChild.textContent.length); } else if (startCellElement && // 不能包含自身元素,否则对 cell 中的部分文字两次高亮后就会选中整个 cell。 https://github.com/siyuan-note/siyuan/issues/3649 第二点 !startCellElement.contains(range.endContainer)) { setLastNodeRange(startCellElement, range, false); } } } }; export const selectAll = (protyle: IProtyle, nodeElement: Element, range: Range) => { const editElement = getContenteditableElement(nodeElement); if (editElement) { let position; if (editElement.tagName === "TABLE") { const cellElement = hasClosestByTag(range.startContainer, "TD") || hasClosestByTag(range.startContainer, "TH"); if (cellElement) { position = getSelectionOffset(cellElement, nodeElement, range); if (position.start !== 0 || position.end !== cellElement.textContent.length) { range.setStart(cellElement.firstChild, 0); range.setEndAfter(cellElement.lastChild); protyle.toolbar.render(protyle, range); countSelectWord(range, protyle.block.rootID); return true; } } } else { position = getSelectionOffset(editElement, nodeElement, range); if (position.start !== 0 || position.end !== editElement.textContent.length) { // 全选后 rang 不对 https://ld246.com/article/1654848722251 let firstChild = editElement.firstChild; while (firstChild) { if (firstChild.nodeType === 3) { if (firstChild.textContent !== "") { range.setStart(firstChild, 0); break; } firstChild = firstChild.nextSibling; } else { if ((firstChild as HTMLElement).classList.contains("render-node") || (firstChild as HTMLElement).classList.contains("img")) { range.setStartBefore(firstChild); break; } firstChild = firstChild.firstChild; } } let lastChild = editElement.lastChild as HTMLElement; while (lastChild) { if (lastChild.nodeType === 3) { if (lastChild.textContent !== "") { range.setEnd(lastChild, lastChild.textContent.length); break; } lastChild = lastChild.previousSibling as HTMLElement; } else { if (lastChild.classList.contains("render-node") || lastChild.classList.contains("img") || lastChild.tagName === "BR") { range.setEndAfter(lastChild); break; } lastChild = lastChild.lastChild as HTMLElement; } } // 列表回车后,左键全选无法选中 focusByRange(range); protyle.toolbar.render(protyle, range); countSelectWord(range, protyle.block.rootID); return true; } } } range.collapse(true); const selectElements = protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select"); if (protyle.wysiwyg.element.childElementCount === selectElements.length && selectElements[0].parentElement.isSameNode(protyle.wysiwyg.element)) { return true; } hideElements(["select"], protyle); const ids: string [] = []; Array.from(protyle.wysiwyg.element.children).forEach(item => { const nodeId = item.getAttribute("data-node-id"); if (nodeId) { item.classList.add("protyle-wysiwyg--select"); ids.push(nodeId); } }); countBlockWord(ids, protyle.block.rootID); }; // https://github.com/siyuan-note/siyuan/issues/8196 export const getRangeByPoint = (x: number, y: number) => { const range = document.caretRangeFromPoint(x, y); const imgElement = hasClosestByAttribute(range.startContainer, "data-type", "img"); if (imgElement) { range.setStart(imgElement.nextSibling, 0); range.collapse(); } return range; }; export const getEditorRange = (element: Element): Range => { let range: Range; if (getSelection().rangeCount > 0) { range = getSelection().getRangeAt(0); if (element.isSameNode(range.startContainer) || element.contains(range.startContainer)) { // 有时候点击编辑器头部需要矫正到第一个块中 if (range.toString() === "" && range.startContainer.nodeType === 1 && range.startOffset === 0 && (range.startContainer as HTMLElement).classList.contains("protyle-wysiwyg")) { const focusRange = focusBlock(range.startContainer.firstChild as Element); if (focusRange) { return focusRange; } } return range; } } if (element.classList.contains("li") || element.classList.contains("list")) { const childElement = element.querySelector("[data-node-id]"); if (childElement) { return getEditorRange(childElement); } } // 代码块过长,在代码块的下一个块前删除,代码块会滚动到顶部,因粗需要 preventScroll (element as HTMLElement).focus({preventScroll: true}); if (!range) { range = document.createRange(); } let targetElement; if (element.classList.contains("table")) { // 当光标不在表格区域中时表格无法被复制 https://ld246.com/article/1650510736504 targetElement = element.querySelector("th") || element.querySelector("td"); } else { targetElement = getContenteditableElement(element); if (!targetElement) { const type = element.getAttribute("data-type"); if (type === "NodeThematicBreak") { targetElement = element.firstElementChild; } else if (type === "NodeBlockQueryEmbed") { targetElement = element.querySelector(".protyle-cursor")?.firstChild; } else if (["NodeMathBlock", "NodeHTMLBlock"].includes(type)) { targetElement = element.lastElementChild.previousElementSibling?.lastElementChild?.firstChild; } else if (type === "NodeVideo") { targetElement = element.firstElementChild.firstChild; } else if (type === "NodeAudio") { targetElement = element.firstElementChild.lastChild; } } else if (targetElement.tagName === "TABLE") { // 文档中开头为表格,获取错误 https://ld246.com/article/1663408335459?r=88250 targetElement = targetElement.querySelector("th") || element.querySelector("td"); } } range.setStart(targetElement || element, 0); range.collapse(true); return range; }; export const getSelectionPosition = (nodeElement: Element, range?: Range) => { if (!range) { range = getEditorRange(nodeElement); } if (!nodeElement.contains(range.startContainer)) { return { left: 0, top: 0, }; } let cursorRect; if (range.getClientRects().length === 0) { if (range.startContainer.nodeType === 3) { // 空行时,会出现没有 br 的情况,需要根据父元素
获取位置信息
const parentRects = range.startContainer.parentElement?.getClientRects();
// 连续粘贴图片时
const previousRects = (range.startContainer as Element).previousElementSibling?.getClientRects();
if (parentRects.length > 0 || previousRects.length > 0) {
if (parentRects.length === 0 || (previousRects &&
previousRects.length > 0 && parentRects[0].top < previousRects[previousRects.length - 1].bottom)) {
cursorRect = {
left: previousRects[previousRects.length - 1].left,
top: previousRects[previousRects.length - 1].bottom,
};
} else {
cursorRect = parentRects[0];
}
} else {
return {
left: 0,
top: 0,
};
}
} else {
const children = (range.startContainer as Element).children;
if (children[range.startOffset] &&
children[range.startOffset].getClientRects().length > 0) {
// markdown 模式回车
cursorRect = children[range.startOffset].getClientRects()[0];
} else if (range.startContainer.childNodes.length > 0) {
// in table or code block
const cloneRange = range.cloneRange();
range.selectNode(range.startContainer.childNodes[Math.max(0, range.startOffset - 1)]);
cursorRect = range.getClientRects()[0];
range.setEnd(cloneRange.endContainer, cloneRange.endOffset);
range.setStart(cloneRange.startContainer, cloneRange.startOffset);
} else {
cursorRect = (range.startContainer as HTMLElement).getClientRects()[0];
}
if (!cursorRect) {
let parentElement = range.startContainer.childNodes[range.startOffset] as HTMLElement;
if (!parentElement) {
parentElement = range.startContainer.childNodes[range.startOffset - 1] as HTMLElement;
}
if (!parentElement) {
cursorRect = range.getBoundingClientRect();
} else {
while (!parentElement.getClientRects || (parentElement.getClientRects && parentElement.getClientRects().length === 0)) {
parentElement = parentElement.parentElement;
}
cursorRect = parentElement.getClientRects()[0];
}
}
}
} else {
const rects = range.getClientRects(); // 由于长度过长折行,光标在行首时有多个 rects https://github.com/siyuan-note/siyuan/issues/6156
if (range.toString()) {
return { // 选中多行不应遮挡第一行 https://github.com/siyuan-note/siyuan/issues/7541
left: rects[rects.length - 1].left,
top: rects[0].top
};
} else {
return { // 代码块首 https://github.com/siyuan-note/siyuan/issues/13113
left: rects[rects.length - 1].left,
top: rects[rects.length - 1].top
};
}
}
return {
left: cursorRect.left,
top: cursorRect.top,
};
};
export const getSelectionOffset = (selectElement: Node, editorElement?: Element, range?: Range) => {
const position = {
end: 0,
start: 0,
};
if (!range) {
if (getSelection().rangeCount === 0) {
return position;
}
range = window.getSelection().getRangeAt(0);
}
if (editorElement && !selectIsEditor(editorElement, range)) {
return position;
}
const preSelectionRange = range.cloneRange();
if (selectElement.childNodes[0] && selectElement.childNodes[0].childNodes[0]) {
preSelectionRange.setStart(selectElement.childNodes[0].childNodes[0], 0);
} else {
preSelectionRange.selectNodeContents(selectElement);
}
preSelectionRange.setEnd(range.startContainer, range.startOffset);
// 需加上表格内软换行 br 的长度
position.start = preSelectionRange.toString().length + preSelectionRange.cloneContents().querySelectorAll("br").length;
position.end = position.start + range.toString().length + range.cloneContents().querySelectorAll("br").length;
return position;
};
function searchNode(
container: Node,
startNode: Node,
predicate: (node: Node) => boolean,
excludeSibling?: boolean,
): boolean {
if (!startNode) {
return false;
}
if (predicate(startNode as Text)) {
return true;
}
for (let i = 0, len = startNode.childNodes.length; i < len; i++) {
if (searchNode(startNode, startNode.childNodes[i], predicate, true)) {
return true;
}
}
if (!excludeSibling) {
let parentNode = startNode;
while (parentNode && parentNode !== container) {
let nextSibling = parentNode.nextSibling;
while (nextSibling) {
if (searchNode(container, nextSibling, predicate, true)) {
return true;
}
nextSibling = nextSibling.nextSibling;
}
parentNode = parentNode.parentNode;
}
}
return false;
}
export const setLastNodeRange = (editElement: Element, range: Range, setStart = true) => {
if (!editElement) {
return range;
}
let lastNode = editElement.lastChild as Element;
while (lastNode && lastNode.nodeType !== 3) {
if (lastNode.nodeType !== 3 && lastNode.tagName === "BR") {
// 防止单元格中 ⇧↓ 全部选中
return range;
}
// https://github.com/siyuan-note/siyuan/issues/12792
if (!lastNode.lastChild) {
break;
}
// 最后一个为多种行内元素嵌套
lastNode = lastNode.lastChild as Element;
}
// https://github.com/siyuan-note/siyuan/issues/12753
if (!lastNode) {
lastNode = editElement;
}
if (setStart) {
if (lastNode.nodeType !== 3 && lastNode.classList.contains("render-node") && lastNode.innerHTML === "") {
range.setStartAfter(lastNode);
} else {
range.setStart(lastNode, lastNode.textContent.length);
}
} else {
if (lastNode.nodeType !== 3 && lastNode.classList.contains("render-node") && lastNode.innerHTML === "") {
range.setStartAfter(lastNode);
} else {
range.setEnd(lastNode, lastNode.textContent.length);
}
}
return range;
};
export const setFirstNodeRange = (editElement: Element, range: Range) => {
if (!editElement) {
return range;
}
let firstChild = editElement.firstChild as HTMLElement;
while (firstChild && firstChild.nodeType !== 3 && !firstChild.classList.contains("render-node")) {
if (firstChild.classList.contains("img")) { // https://ld246.com/article/1665360254842
range.setStartBefore(firstChild);
return range;
}
firstChild = firstChild.firstChild as HTMLElement;
}
if (!firstChild) {
range.selectNodeContents(editElement);
return range;
}
if (firstChild.nodeType !== 3 && firstChild.classList.contains("render-node")) {
range.setStartBefore(firstChild);
} else {
range.setStart(firstChild, 0);
}
return range;
};
export const focusByOffset = (container: Element, start: number, end: number, isFocus = true) => {
if (!container) {
return false;
}
// 空块无法 focus
const editElement = getContenteditableElement(container);
if (editElement) {
container = editElement;
} else if (isFocus && (isNotEditBlock(container) || container.classList.contains("av"))) {
return focusBlock(container);
}
let startNode: Node;
searchNode(container, container.firstChild, node => {
if (node.nodeType === Node.TEXT_NODE) {
const dataLength = (node as Text).data.length;
if (start <= dataLength) {
startNode = node;
return true;
}
start -= dataLength;
end -= dataLength;
return false;
} else if (node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName === "BR") {
if (start <= 1) {
startNode = node;
return true;
}
start -= 1;
end -= 1;
return false;
}
});
let endNode;
if (startNode) {
searchNode(container, startNode, node => {
if (node.nodeType === Node.TEXT_NODE) {
const dataLength = (node as Text).data.length;
if (end <= dataLength) {
endNode = node;
return true;
}
end -= dataLength;
return false;
}
});
}
const range = document.createRange();
if (startNode) {
if (startNode.nodeType === Node.TEXT_NODE && start < (startNode as Text).data.length) {
range.setStart(startNode, start);
} else {
range.setStartAfter(startNode);
}
} else {
if (start === 0) {
range.setStart(container, 0);
} else {
setLastNodeRange(getContenteditableElement(container as Element), range);
}
}
if (endNode) {
if (end < (endNode as Text).data.length) {
range.setEnd(endNode, end);
} else {
range.setEndAfter(endNode);
}
} else {
if (end === 0) {
range.setEnd(container, 0);
} else {
setLastNodeRange(getContenteditableElement(container as Element), range, false);
}
}
if (isFocus) {
focusByRange(range);
}
return range;
};
export const setInsertWbrHTML = (nodeElement: HTMLElement, range: Range, protyle: IProtyle) => {
const offset = getSelectionOffset(nodeElement, nodeElement, range);
const cloneNode = nodeElement.cloneNode(true) as HTMLElement;
const cloneRange = focusByOffset(cloneNode, offset.end, offset.end, false);
if (cloneRange) {
cloneRange.insertNode(document.createElement("wbr"));
}
protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] = cloneNode.outerHTML;
};
export const focusByWbr = (element: Element, range: Range) => {
const wbrElements = element.querySelectorAll("wbr");
if (wbrElements.length === 0) {
return;
}
// 没找到 wbr 产生多个的地方,先顶顶
wbrElements.forEach((item, index) => {
if (index !== 0) {
item.remove();
}
});
const wbrElement = wbrElements[0];
if (!wbrElement.previousElementSibling) {
if (wbrElement.previousSibling) {
// text