diff --git a/app/src/protyle/wysiwyg/index.ts b/app/src/protyle/wysiwyg/index.ts index a91882caf..5477bc5eb 100644 --- a/app/src/protyle/wysiwyg/index.ts +++ b/app/src/protyle/wysiwyg/index.ts @@ -78,7 +78,7 @@ export class WYSIWYG { if (window.siyuan.config.editor.displayBookmarkIcon) { this.element.classList.add("protyle-wysiwyg--attr"); } - this.bindCopyEvent(protyle); + this.bindCommonEvent(protyle); if (protyle.options.action.includes(Constants.CB_GET_HISTORY)) { return; } @@ -181,7 +181,7 @@ export class WYSIWYG { /// #endif } - private bindCopyEvent (protyle: IProtyle) { + private bindCommonEvent (protyle: IProtyle) { this.element.addEventListener("copy", (event: ClipboardEvent & { target: HTMLElement }) => { // https://github.com/siyuan-note/siyuan/issues/4600 if (event.target.tagName === "PROTYLE-HTML") { @@ -255,6 +255,606 @@ export class WYSIWYG { event.clipboardData.setData("text/plain", textPlain || protyle.lute.BlockDOM2StdMd(html).trimEnd()); event.clipboardData.setData("text/html", html + Constants.ZWSP); }); + this.element.addEventListener("mousedown", (event: MouseEvent) => { + if (event.button === 2 || window.siyuan.ctrlIsPressed) { + // 右键 + return; + } + if (!window.siyuan.shiftIsPressed) { + // https://github.com/siyuan-note/siyuan/issues/3026 + hideElements(["select"], protyle); + } + const target = event.target as HTMLElement; + if (hasClosestByClassName(target, "protyle-action")) { + return; + } + const documentSelf = document; + const rect = protyle.element.getBoundingClientRect(); + const mostLeft = rect.left + parseInt(protyle.wysiwyg.element.style.paddingLeft) + 1; + const mostRight = mostLeft + protyle.wysiwyg.element.firstElementChild.clientWidth - 1; + const mostBottom = rect.bottom; + const y = event.clientY; + // 图片、iframe、video 缩放 + if (!protyle.disabled && target.classList.contains("protyle-action__drag")) { + const nodeElement = hasClosestBlock(target); + if (!nodeElement) { + return; + } + let isCenter = true; + if (["NodeIFrame", "NodeWidget", "NodeVideo"].includes(nodeElement.getAttribute("data-type"))) { + nodeElement.classList.add("iframe--drag"); + if (nodeElement.style.textAlign === "left" || nodeElement.style.textAlign === "right") { + isCenter = false; + } + } else if (target.parentElement.parentElement.getAttribute("data-type") === "img") { + target.parentElement.parentElement.classList.add("img--drag"); + } + + const id = nodeElement.getAttribute("data-node-id"); + const html = nodeElement.outerHTML; + const x = event.clientX; + const dragElement = target.previousElementSibling as HTMLElement; + const dragWidth = dragElement.clientWidth; + const dragHeight = dragElement.clientHeight; + documentSelf.onmousemove = (moveEvent: MouseEvent) => { + if (dragElement.tagName === "IMG") { + dragElement.parentElement.parentElement.style.width = ""; + } + if (moveEvent.clientX > x - dragWidth + 8 && moveEvent.clientX < mostRight) { + if ((dragElement.tagName === "IMG" && dragElement.parentElement.parentElement.style.display !== "block") || !isCenter) { + dragElement.style.width = Math.max(17, dragWidth + (moveEvent.clientX - x)) + "px"; + } else { + dragElement.style.width = Math.max(17, dragWidth + (moveEvent.clientX - x) * 2) + "px"; + } + } + if (dragElement.tagName !== "IMG") { + if (moveEvent.clientY > y - dragHeight + 8 && moveEvent.clientY < mostBottom) { + dragElement.style.height = (dragHeight + (moveEvent.clientY - y)) + "px"; + } + } else { + dragElement.parentElement.parentElement.style.maxWidth = (parseInt(dragElement.style.width) + 10) + "px"; + } + }; + + documentSelf.onmouseup = () => { + documentSelf.onmousemove = null; + documentSelf.onmouseup = null; + documentSelf.ondragstart = null; + documentSelf.onselectstart = null; + documentSelf.onselect = null; + if (target.classList.contains("protyle-action__drag") && nodeElement) { + updateTransaction(protyle, id, nodeElement.outerHTML, html); + } + nodeElement.classList.remove("iframe--drag"); + target.parentElement.parentElement.classList.remove("img--drag"); + }; + return; + } + // table cell select + let tableBlockElement: HTMLElement | false; + if (target.tagName === "TH" || target.tagName === "TD" || target.firstElementChild?.tagName === "TABLE" || target.classList.contains("table__resize") || target.classList.contains("table__select")) { + tableBlockElement = hasClosestBlock(target); + if (tableBlockElement) { + tableBlockElement.querySelector("table").classList.remove("select"); + tableBlockElement.querySelector(".table__select").removeAttribute("style"); + window.siyuan.menus.menu.remove(); + event.stopPropagation(); + } + // 后续拖拽操作写在多选节点中 + } + // table col resize + if (!protyle.disabled && target.classList.contains("table__resize")) { + const nodeElement = hasClosestBlock(target); + if (!nodeElement) { + return; + } + const html = nodeElement.outerHTML; + // https://github.com/siyuan-note/siyuan/issues/4455 + if (getSelection().rangeCount > 0) { + getSelection().getRangeAt(0).collapse(false); + } + // @ts-ignore + nodeElement.firstElementChild.style.webkitUserModify = "read-only"; + nodeElement.style.cursor = "col-resize"; + target.removeAttribute("style"); + const id = nodeElement.getAttribute("data-node-id"); + const x = event.clientX; + const colElement = nodeElement.querySelectorAll("table col")[parseInt(target.getAttribute("data-col-index"))] as HTMLElement; + // 兼容 1.8.2 + (nodeElement.querySelectorAll("table th")[parseInt(target.getAttribute("data-col-index"))] as HTMLElement).style.width = ""; + const oldWidth = colElement.clientWidth; + const hasScroll = nodeElement.firstElementChild.clientWidth < nodeElement.firstElementChild.scrollWidth; + documentSelf.onmousemove = (moveEvent: MouseEvent) => { + if (nodeElement.style.textAlign === "center" && !hasScroll) { + colElement.style.width = (oldWidth + (moveEvent.clientX - x) * 2) + "px"; + } else { + colElement.style.width = (oldWidth + (moveEvent.clientX - x)) + "px"; + } + }; + + documentSelf.onmouseup = () => { + // @ts-ignore + nodeElement.firstElementChild.style.webkitUserModify = ""; + nodeElement.style.cursor = ""; + documentSelf.onmousemove = null; + documentSelf.onmouseup = null; + documentSelf.ondragstart = null; + documentSelf.onselectstart = null; + documentSelf.onselect = null; + if (nodeElement) { + updateTransaction(protyle, id, nodeElement.outerHTML, html); + } + }; + return; + } + + // 多选节点 + let x = event.clientX; + if (event.clientX > mostRight) { + x = mostRight; + } else if (event.clientX < mostLeft) { + x = mostLeft; + } + const mostTop = rect.top + (protyle.options.render.breadcrumb?protyle.breadcrumb.element.parentElement.clientHeight:0); + + let mouseElement: Element; + let moveCellElement: HTMLElement; + documentSelf.onmousemove = (moveEvent: MouseEvent) => { + const moveTarget = moveEvent.target as HTMLElement; + // table cell select + if (!protyle.disabled && tableBlockElement && tableBlockElement.contains(moveTarget) && !hasClosestByClassName(tableBlockElement, "protyle-wysiwyg__embed")) { + if ((moveTarget.tagName === "TH" || moveTarget.tagName === "TD") && !moveTarget.isSameNode(target) && (!moveCellElement || !moveCellElement.isSameNode(moveTarget))) { + // @ts-ignore + tableBlockElement.firstElementChild.style.webkitUserModify = "read-only"; + let width = target.offsetLeft + target.clientWidth - moveTarget.offsetLeft; + let left = moveTarget.offsetLeft; + if (target.offsetLeft === moveTarget.offsetLeft) { + width = Math.max(target.clientWidth, moveTarget.clientWidth); + } else if (target.offsetLeft < moveTarget.offsetLeft) { + width = moveTarget.offsetLeft + moveTarget.clientWidth - target.offsetLeft; + left = target.offsetLeft; + } + let height = target.offsetTop + target.clientHeight - moveTarget.offsetTop; + let top = moveTarget.offsetTop; + if (target.offsetTop === moveTarget.offsetTop) { + height = Math.max(target.clientHeight, moveTarget.clientHeight); + } else if (target.offsetTop < moveTarget.offsetTop) { + height = moveTarget.offsetTop + moveTarget.clientHeight - target.offsetTop; + top = target.offsetTop; + } + // https://github.com/siyuan-note/insider/issues/1015 + Array.from(tableBlockElement.querySelectorAll("th, td")).find((item: HTMLElement) => { + const updateWidth = item.offsetLeft < left + width && item.offsetLeft + item.clientWidth > left + width; + const updateWidth2 = item.offsetLeft < left && item.offsetLeft + item.clientWidth > left; + if (item.offsetTop < top && item.offsetTop + item.clientHeight > top) { + if ((item.offsetLeft + 6 > left && item.offsetLeft + item.clientWidth - 6 < left + width) || updateWidth || updateWidth2) { + height = top + height - item.offsetTop; + top = item.offsetTop; + } + if (updateWidth) { + width = item.offsetLeft + item.clientWidth - left; + } + if (updateWidth2) { + width = left + width - item.offsetLeft; + left = item.offsetLeft; + } + } else if (item.offsetTop < top + height && item.offsetTop + item.clientHeight > top + height) { + if ((item.offsetLeft + 6 > left && item.offsetLeft + item.clientWidth - 6 < left + width) || updateWidth || updateWidth2) { + height = item.clientHeight + item.offsetTop - top; + } + if (updateWidth) { + width = item.offsetLeft + item.clientWidth - left; + } + if (updateWidth2) { + width = left + width - item.offsetLeft; + left = item.offsetLeft; + } + } else if (updateWidth2 && item.offsetTop + 6 > top && item.offsetTop + item.clientHeight - 6 < top + height) { + width = left + width - item.offsetLeft; + left = item.offsetLeft; + } else if (updateWidth && item.offsetTop + 6 > top && item.offsetTop + item.clientHeight - 6 < top + height) { + width = item.offsetLeft + item.clientWidth - left; + } + }); + tableBlockElement.querySelector("table").classList.add("select"); + tableBlockElement.querySelector(".table__select").setAttribute("style", `left:${left - tableBlockElement.firstElementChild.scrollLeft}px;top:${top}px;height:${height}px;width:${width + 1}px;`); + moveCellElement = moveTarget; + } + return; + } + protyle.selectElement.classList.remove("fn__none"); + // 向左选择,遇到 gutter 就不会弹出 toolbar + hideElements(["gutter"], protyle); + let newTop = 0; + let newLeft = 0; + let newWidth = 0; + let newHeight = 0; + if (moveEvent.clientX < x) { + if (moveEvent.clientX < mostLeft) { + // 向左越界 + newLeft = mostLeft; + } else { + // 向左 + newLeft = moveEvent.clientX; + } + newWidth = x - newLeft; + } else { + if (moveEvent.clientX > mostRight) { + // 向右越界 + newLeft = x; + newWidth = mostRight - newLeft; + } else { + // 向右 + newLeft = x; + newWidth = moveEvent.clientX - x; + } + } + + if (moveEvent.clientY > y) { + if (moveEvent.clientY > mostBottom) { + // 向下越界 + newTop = y; + newHeight = mostBottom - y; + } else { + // 向下 + newTop = y; + newHeight = moveEvent.clientY - y; + } + } else { + if (moveEvent.clientY < mostTop) { + // 向上越界 + newTop = mostTop; + } else { + // 向上 + newTop = moveEvent.clientY; + } + newHeight = y - newTop; + } + protyle.selectElement.setAttribute("style", `background-color: ${protyle.selectElement.style.backgroundColor};top:${newTop}px;height:${newHeight}px;left:${newLeft + 2}px;width:${newWidth - 2}px;`); + const newMouseElement = document.elementFromPoint(moveEvent.clientX, moveEvent.clientY); + if (mouseElement && mouseElement.isSameNode(newMouseElement) && !mouseElement.classList.contains("protyle-wysiwyg") && + !mouseElement.classList.contains("list") && !mouseElement.classList.contains("bq") && !mouseElement.classList.contains("sb")) { + // 性能优化,同一个p元素不进行选中计算 + return; + } else { + mouseElement = newMouseElement; + } + hideElements(["select"], protyle); + let firstElement = document.elementFromPoint(newLeft - 1, newTop); + if (!firstElement) { + return; + } + if (firstElement.classList.contains("protyle-wysiwyg") || firstElement.classList.contains("list") || firstElement.classList.contains("sb") || firstElement.classList.contains("bq")) { + firstElement = document.elementFromPoint(newLeft - 1, newTop + 16); + } + if (!firstElement || firstElement.classList.contains("protyle-wysiwyg")) { + return; + } + const firstBlockElement = hasClosestBlock(firstElement); + if (!firstBlockElement) { + return; + } + let selectElements: Element[] = []; + let currentElement: Element | boolean = firstBlockElement; + let hasJump = false; + while (currentElement) { + if (currentElement && !currentElement.classList.contains("protyle-attr")) { + const currentRect = currentElement.getBoundingClientRect(); + if (currentRect.height > 0 && currentRect.top < newTop + newHeight && currentRect.left < newLeft + newWidth) { + if (hasJump) { + // 父节点的下个节点在选中范围内才可使用父节点作为选中节点 + if (currentElement.nextElementSibling && !currentElement.nextElementSibling.classList.contains("protyle-attr")) { + const nextRect = currentElement.nextElementSibling.getBoundingClientRect(); + if (nextRect.top < newTop + newHeight && nextRect.left < newLeft + newWidth) { + selectElements = [currentElement]; + currentElement = currentElement.nextElementSibling; + hasJump = false; + } else if (currentElement.parentElement.classList.contains("sb")) { + currentElement = hasClosestBlock(currentElement.parentElement); + hasJump = true; + } else { + break; + } + } else { + currentElement = hasClosestBlock(currentElement.parentElement); + hasJump = true; + } + } else { + selectElements.push(currentElement); + currentElement = currentElement.nextElementSibling; + } + } else if (currentElement.parentElement.classList.contains("sb")) { + // 跳出超级块横向排版中的未选中元素 + currentElement = hasClosestBlock(currentElement.parentElement); + hasJump = true; + } else if (currentRect.height === 0 && currentRect.width === 0 && currentElement.parentElement.getAttribute("fold") === "1") { + currentElement = currentElement.parentElement; + selectElements = []; + } else { + break; + } + } else { + currentElement = hasClosestBlock(currentElement.parentElement); + hasJump = true; + } + } + if (selectElements.length === 1 && !selectElements[0].classList.contains("list") && !selectElements[0].classList.contains("bq") && !selectElements[0].classList.contains("sb")) { + // 只有一个 p 时不选中 + protyle.selectElement.style.backgroundColor = "transparent"; + } else { + selectElements.forEach(item => { + if (!hasClosestByClassName(item, "protyle-wysiwyg__embed")) { + item.classList.add("protyle-wysiwyg--select"); + } + }); + protyle.selectElement.style.backgroundColor = ""; + } + }; + + documentSelf.onmouseup = (mouseUpEvent) => { + documentSelf.onmousemove = null; + documentSelf.onmouseup = null; + documentSelf.ondragstart = null; + documentSelf.onselectstart = null; + documentSelf.onselect = null; + protyle.selectElement.classList.add("fn__none"); + protyle.selectElement.removeAttribute("style"); + if (!protyle.disabled && tableBlockElement) { + // @ts-ignore + tableBlockElement.firstElementChild.style.webkitUserModify = ""; + const tableSelectElement = tableBlockElement.querySelector(".table__select") as HTMLElement; + if (tableSelectElement.getAttribute("style")) { + if (getSelection().rangeCount > 0) { + getSelection().getRangeAt(0).collapse(false); + } + window.siyuan.menus.menu.remove(); + window.siyuan.menus.menu.append(new MenuItem({ + label: window.siyuan.languages.mergeCell, + click: () => { + if (tableBlockElement) { + const selectCellElements: HTMLTableCellElement[] = []; + const colIndexList: number[] = []; + const colCount = tableBlockElement.querySelectorAll("th").length; + let fnNoneMax = 0; + const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; + let isTHead = false; + let isTBody = false; + tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement, index: number) => { + if (item.classList.contains("fn__none")) { + // 合并的元素中间有 fn__none 的元素 + if (item.previousElementSibling && item.previousElementSibling.isSameNode(selectCellElements[selectCellElements.length - 1])) { + selectCellElements.push(item); + if (!isTHead && item.parentElement.parentElement.tagName === "THEAD") { + isTHead = true; + } else if (!isTBody && item.parentElement.parentElement.tagName === "TBODY") { + isTBody = true; + } + } else { + if (index < fnNoneMax && colIndexList.includes((index + 1) % colCount)) { + selectCellElements.push(item); + if (!isTHead && item.parentElement.parentElement.tagName === "THEAD") { + isTHead = true; + } else if (!isTBody && item.parentElement.parentElement.tagName === "TBODY") { + isTBody = true; + } + } + } + } else { + if (item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && + item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight) { + selectCellElements.push(item); + if (!isTHead && item.parentElement.parentElement.tagName === "THEAD") { + isTHead = true; + } else if (!isTBody && item.parentElement.parentElement.tagName === "TBODY") { + isTBody = true; + } + colIndexList.push((index + 1) % colCount); + // https://github.com/siyuan-note/insider/issues/1014 + fnNoneMax = Math.max((item.rowSpan - 1) * colCount + index + 1, fnNoneMax); + } + } + }); + tableBlockElement.querySelector("table").classList.remove("select"); + tableSelectElement.removeAttribute("style"); + const oldHTML = tableBlockElement.outerHTML; + let cellElement = selectCellElements[0]; + let colSpan = cellElement.colSpan; + let index = 1; + while (cellElement.nextElementSibling && cellElement.nextElementSibling.isSameNode(selectCellElements[index])) { + cellElement = cellElement.nextElementSibling as HTMLTableCellElement; + if (!cellElement.classList.contains("fn__none")) { // https://github.com/siyuan-note/insider/issues/1007#issuecomment-1046195608 + colSpan += cellElement.colSpan; + } + index++; + } + let html = ""; + let rowElement: Element = selectCellElements[0].parentElement; + let rowSpan = selectCellElements[0].rowSpan; + selectCellElements.forEach((item, index) => { + let cellHTML = item.innerHTML.trim(); + if (cellHTML.endsWith("
")) { + cellHTML = cellHTML.substr(0, cellHTML.length - 4); + } + html += cellHTML + ((!cellHTML || index === selectCellElements.length - 1) ? "" : "
"); + if (index !== 0) { + if (!rowElement.isSameNode(item.parentElement)) { + if (!item.classList.contains("fn__none")) { // https://github.com/siyuan-note/insider/issues/1011 + rowSpan += item.rowSpan; + } + rowElement = item.parentElement; + if (selectCellElements[0].parentElement.parentElement.tagName === "THEAD" && item.parentElement.parentElement.tagName !== "THEAD") { + selectCellElements[0].parentElement.parentElement.insertAdjacentElement("beforeend", item.parentElement); + } + } + item.classList.add("fn__none"); + item.innerHTML = ""; + } + }); + + // https://github.com/siyuan-note/insider/issues/1017 + if (isTHead && isTBody) { + rowElement = rowElement.parentElement.nextElementSibling.firstElementChild; + while (rowElement && rowElement.parentElement.tagName !== "THEAD") { + let colSpanCount = 0; + let noneCount = 0; + Array.from(rowElement.children).forEach((item: HTMLTableCellElement) => { + colSpanCount += item.colSpan - 1; + if (item.classList.contains("fn__none")) { + noneCount++; + } + }); + if (colSpanCount !== noneCount) { + selectCellElements[0].parentElement.parentElement.insertAdjacentElement("beforeend", rowElement); + rowElement = rowElement.parentElement.nextElementSibling.firstElementChild; + } else { + break; + } + } + } + + // 合并背景色不会修改,需要等计算完毕 + setTimeout(() => { + if (tableBlockElement) { + selectCellElements[0].innerHTML = html; + selectCellElements[0].colSpan = colSpan; + selectCellElements[0].rowSpan = rowSpan; + updateTransaction(protyle, tableBlockElement.getAttribute("data-node-id"), tableBlockElement.outerHTML, oldHTML); + } + }); + } + } + }).element); + window.siyuan.menus.menu.append(new MenuItem({type: "separator"}).element); + window.siyuan.menus.menu.append(new MenuItem({ + icon: "iconAlignLeft", + accelerator: window.siyuan.config.keymap.editor.general.alignLeft.custom, + label: window.siyuan.languages.alignLeft, + click: () => { + if (tableBlockElement) { + const selectCellElements: HTMLTableCellElement[] = []; + const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; + tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { + if (!item.classList.contains("fn__none") && + item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && + item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight && + (selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) { + selectCellElements.push(item); + } + }); + tableBlockElement.querySelector("table").classList.remove("select"); + tableSelectElement.removeAttribute("style"); + setTableAlign(protyle, selectCellElements, tableBlockElement, "left", getEditorRange(tableBlockElement)); + } + } + }).element); + window.siyuan.menus.menu.append(new MenuItem({ + icon: "iconAlignCenter", + accelerator: window.siyuan.config.keymap.editor.general.alignCenter.custom, + label: window.siyuan.languages.alignCenter, + click: () => { + if (tableBlockElement) { + const selectCellElements: HTMLTableCellElement[] = []; + const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; + tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { + if (!item.classList.contains("fn__none") && + item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && + item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight && + (selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) { + selectCellElements.push(item); + } + }); + tableBlockElement.querySelector("table").classList.remove("select"); + tableSelectElement.removeAttribute("style"); + setTableAlign(protyle, selectCellElements, tableBlockElement, "center", getEditorRange(tableBlockElement)); + } + } + }).element); + window.siyuan.menus.menu.append(new MenuItem({ + icon: "iconAlignRight", + accelerator: window.siyuan.config.keymap.editor.general.alignRight.custom, + label: window.siyuan.languages.alignRight, + click: () => { + if (tableBlockElement) { + const selectCellElements: HTMLTableCellElement[] = []; + const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; + tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { + if (!item.classList.contains("fn__none") && + item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && + item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight && + (selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) { + selectCellElements.push(item); + } + }); + tableBlockElement.querySelector("table").classList.remove("select"); + tableSelectElement.removeAttribute("style"); + setTableAlign(protyle, selectCellElements, tableBlockElement, "right", getEditorRange(tableBlockElement)); + } + } + }).element); + window.siyuan.menus.menu.append(new MenuItem({type: "separator"}).element); + window.siyuan.menus.menu.append(new MenuItem({ + label: window.siyuan.languages.remove, + icon: "iconTrashcan", + click() { + if (tableBlockElement) { + const selectCellElements: HTMLTableCellElement[] = []; + const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; + tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { + if (!item.classList.contains("fn__none") && + item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && + item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight) { + selectCellElements.push(item); + } + }); + tableBlockElement.querySelector("table").classList.remove("select"); + tableSelectElement.removeAttribute("style"); + const oldHTML = tableBlockElement.outerHTML; + selectCellElements.forEach(item => { + item.innerHTML = ""; + }); + updateTransaction(protyle, tableBlockElement.getAttribute("data-node-id"), tableBlockElement.outerHTML, oldHTML); + } + } + }).element); + window.siyuan.menus.menu.popup({x: mouseUpEvent.clientX - 8, y: mouseUpEvent.clientY - 16}); + } + } + + const ids: string[] = []; + const selectElement = protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select"); + selectElement.forEach(item => { + ids.push(item.getAttribute("data-node-id")); + }); + countBlockWord(ids); + // 划选后不能存在跨块的 range https://github.com/siyuan-note/siyuan/issues/4473 + if (getSelection().rangeCount > 0) { + const range = getSelection().getRangeAt(0); + if (range.toString() === "" || + window.siyuan.shiftIsPressed // https://ld246.com/article/1650096678723 + ) { + return; + } + if (selectElement.length > 0) { + range.collapse(true); + return; + } + const startBlockElement = hasClosestBlock(range.startContainer); + let endBlockElement: false | HTMLElement; + if (mouseUpEvent.detail === 3 && range.endContainer.nodeType !== 3 && (range.endContainer as HTMLElement).tagName === "DIV" && range.endOffset === 0) { + // 三击选中段落块时,rangeEnd 会在下一个块 + if ((range.endContainer as HTMLElement).classList.contains("protyle-attr")) { + // 三击在悬浮层中会选择到 attr https://github.com/siyuan-note/siyuan/issues/4636 + range.setEndAfter(range.endContainer.previousSibling.lastChild); + } + } else { + endBlockElement = hasClosestBlock(range.endContainer); + } + if (startBlockElement && endBlockElement && !endBlockElement.isSameNode(startBlockElement)) { + range.collapse(true); + } + } + }; + }); } private bindEvent(protyle: IProtyle) { @@ -595,607 +1195,6 @@ export class WYSIWYG { } }); - this.element.addEventListener("mousedown", (event: MouseEvent) => { - if (event.button === 2 || window.siyuan.ctrlIsPressed) { - // 右键 - return; - } - if (!window.siyuan.shiftIsPressed) { - // https://github.com/siyuan-note/siyuan/issues/3026 - hideElements(["select"], protyle); - } - const target = event.target as HTMLElement; - if (hasClosestByClassName(target, "protyle-action")) { - return; - } - const documentSelf = document; - const rect = protyle.element.getBoundingClientRect(); - const mostLeft = rect.left + parseInt(protyle.wysiwyg.element.style.paddingLeft) + 1; - const mostRight = mostLeft + protyle.wysiwyg.element.firstElementChild.clientWidth - 1; - const mostBottom = rect.bottom; - const y = event.clientY; - // 图片、iframe、video 缩放 - if (target.classList.contains("protyle-action__drag")) { - const nodeElement = hasClosestBlock(target); - if (!nodeElement) { - return; - } - let isCenter = true; - if (["NodeIFrame", "NodeWidget", "NodeVideo"].includes(nodeElement.getAttribute("data-type"))) { - nodeElement.classList.add("iframe--drag"); - if (nodeElement.style.textAlign === "left" || nodeElement.style.textAlign === "right") { - isCenter = false; - } - } else if (target.parentElement.parentElement.getAttribute("data-type") === "img") { - target.parentElement.parentElement.classList.add("img--drag"); - } - - const id = nodeElement.getAttribute("data-node-id"); - const html = nodeElement.outerHTML; - const x = event.clientX; - const dragElement = target.previousElementSibling as HTMLElement; - const dragWidth = dragElement.clientWidth; - const dragHeight = dragElement.clientHeight; - documentSelf.onmousemove = (moveEvent: MouseEvent) => { - if (dragElement.tagName === "IMG") { - dragElement.parentElement.parentElement.style.width = ""; - } - if (moveEvent.clientX > x - dragWidth + 8 && moveEvent.clientX < mostRight) { - if ((dragElement.tagName === "IMG" && dragElement.parentElement.parentElement.style.display !== "block") || !isCenter) { - dragElement.style.width = Math.max(17, dragWidth + (moveEvent.clientX - x)) + "px"; - } else { - dragElement.style.width = Math.max(17, dragWidth + (moveEvent.clientX - x) * 2) + "px"; - } - } - if (dragElement.tagName !== "IMG") { - if (moveEvent.clientY > y - dragHeight + 8 && moveEvent.clientY < mostBottom) { - dragElement.style.height = (dragHeight + (moveEvent.clientY - y)) + "px"; - } - } else { - dragElement.parentElement.parentElement.style.maxWidth = (parseInt(dragElement.style.width) + 10) + "px"; - } - }; - - documentSelf.onmouseup = () => { - documentSelf.onmousemove = null; - documentSelf.onmouseup = null; - documentSelf.ondragstart = null; - documentSelf.onselectstart = null; - documentSelf.onselect = null; - if (target.classList.contains("protyle-action__drag") && nodeElement) { - updateTransaction(protyle, id, nodeElement.outerHTML, html); - } - nodeElement.classList.remove("iframe--drag"); - target.parentElement.parentElement.classList.remove("img--drag"); - }; - return; - } - // table cell select - let tableBlockElement: HTMLElement | false; - if (target.tagName === "TH" || target.tagName === "TD" || target.firstElementChild?.tagName === "TABLE" || target.classList.contains("table__resize") || target.classList.contains("table__select")) { - tableBlockElement = hasClosestBlock(target); - if (tableBlockElement) { - tableBlockElement.querySelector("table").classList.remove("select"); - tableBlockElement.querySelector(".table__select").removeAttribute("style"); - window.siyuan.menus.menu.remove(); - event.stopPropagation(); - } - // 后续拖拽操作写在多选节点中 - } - // table col resize - if (target.classList.contains("table__resize")) { - const nodeElement = hasClosestBlock(target); - if (!nodeElement) { - return; - } - const html = nodeElement.outerHTML; - // https://github.com/siyuan-note/siyuan/issues/4455 - if (getSelection().rangeCount > 0) { - getSelection().getRangeAt(0).collapse(false); - } - // @ts-ignore - nodeElement.firstElementChild.style.webkitUserModify = "read-only"; - nodeElement.style.cursor = "col-resize"; - target.removeAttribute("style"); - const id = nodeElement.getAttribute("data-node-id"); - const x = event.clientX; - const colElement = nodeElement.querySelectorAll("table col")[parseInt(target.getAttribute("data-col-index"))] as HTMLElement; - // 兼容 1.8.2 - (nodeElement.querySelectorAll("table th")[parseInt(target.getAttribute("data-col-index"))] as HTMLElement).style.width = ""; - const oldWidth = colElement.clientWidth; - const hasScroll = nodeElement.firstElementChild.clientWidth < nodeElement.firstElementChild.scrollWidth; - documentSelf.onmousemove = (moveEvent: MouseEvent) => { - if (nodeElement.style.textAlign === "center" && !hasScroll) { - colElement.style.width = (oldWidth + (moveEvent.clientX - x) * 2) + "px"; - } else { - colElement.style.width = (oldWidth + (moveEvent.clientX - x)) + "px"; - } - }; - - documentSelf.onmouseup = () => { - // @ts-ignore - nodeElement.firstElementChild.style.webkitUserModify = ""; - nodeElement.style.cursor = ""; - documentSelf.onmousemove = null; - documentSelf.onmouseup = null; - documentSelf.ondragstart = null; - documentSelf.onselectstart = null; - documentSelf.onselect = null; - if (nodeElement) { - updateTransaction(protyle, id, nodeElement.outerHTML, html); - } - }; - return; - } - - // 多选节点 - let x = event.clientX; - if (event.clientX > mostRight) { - x = mostRight; - } else if (event.clientX < mostLeft) { - x = mostLeft; - } - const mostTop = rect.top + (protyle.options.render.breadcrumb?protyle.breadcrumb.element.parentElement.clientHeight:0); - - let mouseElement: Element; - let moveCellElement: HTMLElement; - documentSelf.onmousemove = (moveEvent: MouseEvent) => { - const moveTarget = moveEvent.target as HTMLElement; - // table cell select - if (tableBlockElement && tableBlockElement.contains(moveTarget) && !hasClosestByClassName(tableBlockElement, "protyle-wysiwyg__embed")) { - if ((moveTarget.tagName === "TH" || moveTarget.tagName === "TD") && !moveTarget.isSameNode(target) && (!moveCellElement || !moveCellElement.isSameNode(moveTarget))) { - // @ts-ignore - tableBlockElement.firstElementChild.style.webkitUserModify = "read-only"; - let width = target.offsetLeft + target.clientWidth - moveTarget.offsetLeft; - let left = moveTarget.offsetLeft; - if (target.offsetLeft === moveTarget.offsetLeft) { - width = Math.max(target.clientWidth, moveTarget.clientWidth); - } else if (target.offsetLeft < moveTarget.offsetLeft) { - width = moveTarget.offsetLeft + moveTarget.clientWidth - target.offsetLeft; - left = target.offsetLeft; - } - let height = target.offsetTop + target.clientHeight - moveTarget.offsetTop; - let top = moveTarget.offsetTop; - if (target.offsetTop === moveTarget.offsetTop) { - height = Math.max(target.clientHeight, moveTarget.clientHeight); - } else if (target.offsetTop < moveTarget.offsetTop) { - height = moveTarget.offsetTop + moveTarget.clientHeight - target.offsetTop; - top = target.offsetTop; - } - // https://github.com/siyuan-note/insider/issues/1015 - Array.from(tableBlockElement.querySelectorAll("th, td")).find((item: HTMLElement) => { - const updateWidth = item.offsetLeft < left + width && item.offsetLeft + item.clientWidth > left + width; - const updateWidth2 = item.offsetLeft < left && item.offsetLeft + item.clientWidth > left; - if (item.offsetTop < top && item.offsetTop + item.clientHeight > top) { - if ((item.offsetLeft + 6 > left && item.offsetLeft + item.clientWidth - 6 < left + width) || updateWidth || updateWidth2) { - height = top + height - item.offsetTop; - top = item.offsetTop; - } - if (updateWidth) { - width = item.offsetLeft + item.clientWidth - left; - } - if (updateWidth2) { - width = left + width - item.offsetLeft; - left = item.offsetLeft; - } - } else if (item.offsetTop < top + height && item.offsetTop + item.clientHeight > top + height) { - if ((item.offsetLeft + 6 > left && item.offsetLeft + item.clientWidth - 6 < left + width) || updateWidth || updateWidth2) { - height = item.clientHeight + item.offsetTop - top; - } - if (updateWidth) { - width = item.offsetLeft + item.clientWidth - left; - } - if (updateWidth2) { - width = left + width - item.offsetLeft; - left = item.offsetLeft; - } - } else if (updateWidth2 && item.offsetTop + 6 > top && item.offsetTop + item.clientHeight - 6 < top + height) { - width = left + width - item.offsetLeft; - left = item.offsetLeft; - } else if (updateWidth && item.offsetTop + 6 > top && item.offsetTop + item.clientHeight - 6 < top + height) { - width = item.offsetLeft + item.clientWidth - left; - } - }); - tableBlockElement.querySelector("table").classList.add("select"); - tableBlockElement.querySelector(".table__select").setAttribute("style", `left:${left - tableBlockElement.firstElementChild.scrollLeft}px;top:${top}px;height:${height}px;width:${width + 1}px;`); - moveCellElement = moveTarget; - } - return; - } - protyle.selectElement.classList.remove("fn__none"); - // 向左选择,遇到 gutter 就不会弹出 toolbar - hideElements(["gutter"], protyle); - let newTop = 0; - let newLeft = 0; - let newWidth = 0; - let newHeight = 0; - if (moveEvent.clientX < x) { - if (moveEvent.clientX < mostLeft) { - // 向左越界 - newLeft = mostLeft; - } else { - // 向左 - newLeft = moveEvent.clientX; - } - newWidth = x - newLeft; - } else { - if (moveEvent.clientX > mostRight) { - // 向右越界 - newLeft = x; - newWidth = mostRight - newLeft; - } else { - // 向右 - newLeft = x; - newWidth = moveEvent.clientX - x; - } - } - - if (moveEvent.clientY > y) { - if (moveEvent.clientY > mostBottom) { - // 向下越界 - newTop = y; - newHeight = mostBottom - y; - } else { - // 向下 - newTop = y; - newHeight = moveEvent.clientY - y; - } - } else { - if (moveEvent.clientY < mostTop) { - // 向上越界 - newTop = mostTop; - } else { - // 向上 - newTop = moveEvent.clientY; - } - newHeight = y - newTop; - } - protyle.selectElement.setAttribute("style", `background-color: ${protyle.selectElement.style.backgroundColor};top:${newTop}px;height:${newHeight}px;left:${newLeft + 2}px;width:${newWidth - 2}px;`); - const newMouseElement = document.elementFromPoint(moveEvent.clientX, moveEvent.clientY); - if (mouseElement && mouseElement.isSameNode(newMouseElement) && !mouseElement.classList.contains("protyle-wysiwyg") && - !mouseElement.classList.contains("list") && !mouseElement.classList.contains("bq") && !mouseElement.classList.contains("sb")) { - // 性能优化,同一个p元素不进行选中计算 - return; - } else { - mouseElement = newMouseElement; - } - hideElements(["select"], protyle); - let firstElement = document.elementFromPoint(newLeft - 1, newTop); - if (!firstElement) { - return; - } - if (firstElement.classList.contains("protyle-wysiwyg") || firstElement.classList.contains("list") || firstElement.classList.contains("sb") || firstElement.classList.contains("bq")) { - firstElement = document.elementFromPoint(newLeft - 1, newTop + 16); - } - if (!firstElement || firstElement.classList.contains("protyle-wysiwyg")) { - return; - } - const firstBlockElement = hasClosestBlock(firstElement); - if (!firstBlockElement) { - return; - } - let selectElements: Element[] = []; - let currentElement: Element | boolean = firstBlockElement; - let hasJump = false; - while (currentElement) { - if (currentElement && !currentElement.classList.contains("protyle-attr")) { - const currentRect = currentElement.getBoundingClientRect(); - if (currentRect.height > 0 && currentRect.top < newTop + newHeight && currentRect.left < newLeft + newWidth) { - if (hasJump) { - // 父节点的下个节点在选中范围内才可使用父节点作为选中节点 - if (currentElement.nextElementSibling && !currentElement.nextElementSibling.classList.contains("protyle-attr")) { - const nextRect = currentElement.nextElementSibling.getBoundingClientRect(); - if (nextRect.top < newTop + newHeight && nextRect.left < newLeft + newWidth) { - selectElements = [currentElement]; - currentElement = currentElement.nextElementSibling; - hasJump = false; - } else if (currentElement.parentElement.classList.contains("sb")) { - currentElement = hasClosestBlock(currentElement.parentElement); - hasJump = true; - } else { - break; - } - } else { - currentElement = hasClosestBlock(currentElement.parentElement); - hasJump = true; - } - } else { - selectElements.push(currentElement); - currentElement = currentElement.nextElementSibling; - } - } else if (currentElement.parentElement.classList.contains("sb")) { - // 跳出超级块横向排版中的未选中元素 - currentElement = hasClosestBlock(currentElement.parentElement); - hasJump = true; - } else if (currentRect.height === 0 && currentRect.width === 0 && currentElement.parentElement.getAttribute("fold") === "1") { - currentElement = currentElement.parentElement; - selectElements = []; - } else { - break; - } - } else { - currentElement = hasClosestBlock(currentElement.parentElement); - hasJump = true; - } - } - if (selectElements.length === 1 && !selectElements[0].classList.contains("list") && !selectElements[0].classList.contains("bq") && !selectElements[0].classList.contains("sb")) { - // 只有一个 p 时不选中 - protyle.selectElement.style.backgroundColor = "transparent"; - } else { - selectElements.forEach(item => { - if (!hasClosestByClassName(item, "protyle-wysiwyg__embed")) { - item.classList.add("protyle-wysiwyg--select"); - } - }); - protyle.selectElement.style.backgroundColor = ""; - } - }; - - documentSelf.onmouseup = (mouseUpEvent) => { - documentSelf.onmousemove = null; - documentSelf.onmouseup = null; - documentSelf.ondragstart = null; - documentSelf.onselectstart = null; - documentSelf.onselect = null; - protyle.selectElement.classList.add("fn__none"); - protyle.selectElement.removeAttribute("style"); - if (tableBlockElement) { - // @ts-ignore - tableBlockElement.firstElementChild.style.webkitUserModify = ""; - const tableSelectElement = tableBlockElement.querySelector(".table__select") as HTMLElement; - if (tableSelectElement.getAttribute("style")) { - if (getSelection().rangeCount > 0) { - getSelection().getRangeAt(0).collapse(false); - } - window.siyuan.menus.menu.remove(); - window.siyuan.menus.menu.append(new MenuItem({ - label: window.siyuan.languages.mergeCell, - click: () => { - if (tableBlockElement) { - const selectCellElements: HTMLTableCellElement[] = []; - const colIndexList: number[] = []; - const colCount = tableBlockElement.querySelectorAll("th").length; - let fnNoneMax = 0; - const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; - let isTHead = false; - let isTBody = false; - tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement, index: number) => { - if (item.classList.contains("fn__none")) { - // 合并的元素中间有 fn__none 的元素 - if (item.previousElementSibling && item.previousElementSibling.isSameNode(selectCellElements[selectCellElements.length - 1])) { - selectCellElements.push(item); - if (!isTHead && item.parentElement.parentElement.tagName === "THEAD") { - isTHead = true; - } else if (!isTBody && item.parentElement.parentElement.tagName === "TBODY") { - isTBody = true; - } - } else { - if (index < fnNoneMax && colIndexList.includes((index + 1) % colCount)) { - selectCellElements.push(item); - if (!isTHead && item.parentElement.parentElement.tagName === "THEAD") { - isTHead = true; - } else if (!isTBody && item.parentElement.parentElement.tagName === "TBODY") { - isTBody = true; - } - } - } - } else { - if (item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && - item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight) { - selectCellElements.push(item); - if (!isTHead && item.parentElement.parentElement.tagName === "THEAD") { - isTHead = true; - } else if (!isTBody && item.parentElement.parentElement.tagName === "TBODY") { - isTBody = true; - } - colIndexList.push((index + 1) % colCount); - // https://github.com/siyuan-note/insider/issues/1014 - fnNoneMax = Math.max((item.rowSpan - 1) * colCount + index + 1, fnNoneMax); - } - } - }); - tableBlockElement.querySelector("table").classList.remove("select"); - tableSelectElement.removeAttribute("style"); - const oldHTML = tableBlockElement.outerHTML; - let cellElement = selectCellElements[0]; - let colSpan = cellElement.colSpan; - let index = 1; - while (cellElement.nextElementSibling && cellElement.nextElementSibling.isSameNode(selectCellElements[index])) { - cellElement = cellElement.nextElementSibling as HTMLTableCellElement; - if (!cellElement.classList.contains("fn__none")) { // https://github.com/siyuan-note/insider/issues/1007#issuecomment-1046195608 - colSpan += cellElement.colSpan; - } - index++; - } - let html = ""; - let rowElement: Element = selectCellElements[0].parentElement; - let rowSpan = selectCellElements[0].rowSpan; - selectCellElements.forEach((item, index) => { - let cellHTML = item.innerHTML.trim(); - if (cellHTML.endsWith("
")) { - cellHTML = cellHTML.substr(0, cellHTML.length - 4); - } - html += cellHTML + ((!cellHTML || index === selectCellElements.length - 1) ? "" : "
"); - if (index !== 0) { - if (!rowElement.isSameNode(item.parentElement)) { - if (!item.classList.contains("fn__none")) { // https://github.com/siyuan-note/insider/issues/1011 - rowSpan += item.rowSpan; - } - rowElement = item.parentElement; - if (selectCellElements[0].parentElement.parentElement.tagName === "THEAD" && item.parentElement.parentElement.tagName !== "THEAD") { - selectCellElements[0].parentElement.parentElement.insertAdjacentElement("beforeend", item.parentElement); - } - } - item.classList.add("fn__none"); - item.innerHTML = ""; - } - }); - - // https://github.com/siyuan-note/insider/issues/1017 - if (isTHead && isTBody) { - rowElement = rowElement.parentElement.nextElementSibling.firstElementChild; - while (rowElement && rowElement.parentElement.tagName !== "THEAD") { - let colSpanCount = 0; - let noneCount = 0; - Array.from(rowElement.children).forEach((item: HTMLTableCellElement) => { - colSpanCount += item.colSpan - 1; - if (item.classList.contains("fn__none")) { - noneCount++; - } - }); - if (colSpanCount !== noneCount) { - selectCellElements[0].parentElement.parentElement.insertAdjacentElement("beforeend", rowElement); - rowElement = rowElement.parentElement.nextElementSibling.firstElementChild; - } else { - break; - } - } - } - - // 合并背景色不会修改,需要等计算完毕 - setTimeout(() => { - if (tableBlockElement) { - selectCellElements[0].innerHTML = html; - selectCellElements[0].colSpan = colSpan; - selectCellElements[0].rowSpan = rowSpan; - updateTransaction(protyle, tableBlockElement.getAttribute("data-node-id"), tableBlockElement.outerHTML, oldHTML); - } - }); - } - } - }).element); - window.siyuan.menus.menu.append(new MenuItem({type: "separator"}).element); - window.siyuan.menus.menu.append(new MenuItem({ - icon: "iconAlignLeft", - accelerator: window.siyuan.config.keymap.editor.general.alignLeft.custom, - label: window.siyuan.languages.alignLeft, - click: () => { - if (tableBlockElement) { - const selectCellElements: HTMLTableCellElement[] = []; - const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; - tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { - if (!item.classList.contains("fn__none") && - item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && - item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight && - (selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) { - selectCellElements.push(item); - } - }); - tableBlockElement.querySelector("table").classList.remove("select"); - tableSelectElement.removeAttribute("style"); - setTableAlign(protyle, selectCellElements, tableBlockElement, "left", getEditorRange(tableBlockElement)); - } - } - }).element); - window.siyuan.menus.menu.append(new MenuItem({ - icon: "iconAlignCenter", - accelerator: window.siyuan.config.keymap.editor.general.alignCenter.custom, - label: window.siyuan.languages.alignCenter, - click: () => { - if (tableBlockElement) { - const selectCellElements: HTMLTableCellElement[] = []; - const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; - tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { - if (!item.classList.contains("fn__none") && - item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && - item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight && - (selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) { - selectCellElements.push(item); - } - }); - tableBlockElement.querySelector("table").classList.remove("select"); - tableSelectElement.removeAttribute("style"); - setTableAlign(protyle, selectCellElements, tableBlockElement, "center", getEditorRange(tableBlockElement)); - } - } - }).element); - window.siyuan.menus.menu.append(new MenuItem({ - icon: "iconAlignRight", - accelerator: window.siyuan.config.keymap.editor.general.alignRight.custom, - label: window.siyuan.languages.alignRight, - click: () => { - if (tableBlockElement) { - const selectCellElements: HTMLTableCellElement[] = []; - const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; - tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { - if (!item.classList.contains("fn__none") && - item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && - item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight && - (selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) { - selectCellElements.push(item); - } - }); - tableBlockElement.querySelector("table").classList.remove("select"); - tableSelectElement.removeAttribute("style"); - setTableAlign(protyle, selectCellElements, tableBlockElement, "right", getEditorRange(tableBlockElement)); - } - } - }).element); - window.siyuan.menus.menu.append(new MenuItem({type: "separator"}).element); - window.siyuan.menus.menu.append(new MenuItem({ - label: window.siyuan.languages.remove, - icon: "iconTrashcan", - click() { - if (tableBlockElement) { - const selectCellElements: HTMLTableCellElement[] = []; - const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; - tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { - if (!item.classList.contains("fn__none") && - item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && - item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight) { - selectCellElements.push(item); - } - }); - tableBlockElement.querySelector("table").classList.remove("select"); - tableSelectElement.removeAttribute("style"); - const oldHTML = tableBlockElement.outerHTML; - selectCellElements.forEach(item => { - item.innerHTML = ""; - }); - updateTransaction(protyle, tableBlockElement.getAttribute("data-node-id"), tableBlockElement.outerHTML, oldHTML); - } - } - }).element); - window.siyuan.menus.menu.popup({x: mouseUpEvent.clientX - 8, y: mouseUpEvent.clientY - 16}); - } - } - - const ids: string[] = []; - const selectElement = protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select"); - selectElement.forEach(item => { - ids.push(item.getAttribute("data-node-id")); - }); - countBlockWord(ids); - // 划选后不能存在跨块的 range https://github.com/siyuan-note/siyuan/issues/4473 - if (getSelection().rangeCount > 0) { - const range = getSelection().getRangeAt(0); - if (range.toString() === "" || - window.siyuan.shiftIsPressed // https://ld246.com/article/1650096678723 - ) { - return; - } - if (selectElement.length > 0) { - range.collapse(true); - return; - } - const startBlockElement = hasClosestBlock(range.startContainer); - let endBlockElement: false | HTMLElement; - if (mouseUpEvent.detail === 3 && range.endContainer.nodeType !== 3 && (range.endContainer as HTMLElement).tagName === "DIV" && range.endOffset === 0) { - // 三击选中段落块时,rangeEnd 会在下一个块 - if ((range.endContainer as HTMLElement).classList.contains("protyle-attr")) { - // 三击在悬浮层中会选择到 attr https://github.com/siyuan-note/siyuan/issues/4636 - range.setEndAfter(range.endContainer.previousSibling.lastChild); - } - } else { - endBlockElement = hasClosestBlock(range.endContainer); - } - if (startBlockElement && endBlockElement && !endBlockElement.isSameNode(startBlockElement)) { - range.collapse(true); - } - } - }; - }); - this.element.addEventListener("paste", (event: ClipboardEvent & { target: HTMLElement }) => { // https://github.com/siyuan-note/siyuan/issues/4600 if (event.target.tagName === "PROTYLE-HTML") {