import {hasClosestByClassName} from "../protyle/util/hasClosest"; import {Protyle} from "../protyle"; import {genUUID} from "../util/genID"; import {setPadding} from "../protyle/ui/initUI"; import {setPosition} from "../util/setPosition"; import {hideElements} from "../protyle/ui/hideElements"; import {Constants} from "../constants"; /// #if !BROWSER import {openNewWindowById} from "../window/openNewWindow"; /// #endif import {disabledProtyle} from "../protyle/util/onGet"; import {fetchPost} from "../util/fetch"; import {showMessage} from "../dialog/message"; import {App} from "../index"; export class BlockPanel { public element: HTMLElement; public targetElement: HTMLElement; public nodeIds: string[]; public defIds: string[] = []; public id: string; private app: App; public x: number; public y: number; private isBacklink: boolean; public editors: Protyle[] = []; // x,y 和 targetElement 二选一必传 constructor(options: { app: App, targetElement?: HTMLElement, nodeIds?: string[], defIds?: string[], isBacklink: boolean, x?: number, y?: number }) { this.id = genUUID(); this.targetElement = options.targetElement; this.nodeIds = options.nodeIds; this.defIds = options.defIds || []; this.app = options.app; this.x = options.x; this.y = options.y; this.isBacklink = options.isBacklink; this.element = document.createElement("div"); this.element.classList.add("block__popover", "block__popover--move", "block__popover--top"); const parentElement = hasClosestByClassName(this.targetElement, "block__popover", true); let level = 1; if (parentElement) { this.element.setAttribute("data-oid", parentElement.getAttribute("data-oid")); level = parseInt(parentElement.getAttribute("data-level")) + 1; } else { this.element.setAttribute("data-oid", this.nodeIds[0]); } // 移除同层级其他更高级的 block popover this.element.setAttribute("data-level", level.toString()); for (let i = 0; i < window.siyuan.blockPanels.length; i++) { const item = window.siyuan.blockPanels[i]; if (item.element.getAttribute("data-pin") === "false" && item.targetElement && parseInt(item.element.getAttribute("data-level")) >= level) { item.destroy(); i--; } } document.body.insertAdjacentElement("beforeend", this.element); this.element.addEventListener("mousedown", (event: MouseEvent & { target: HTMLElement }) => { document.querySelectorAll(".block__popover--top").forEach(item => { item.classList.remove("block__popover--top"); }); if (this.element && window.siyuan.blockPanels.length > 1) { this.element.classList.add("block__popover--top"); } if (hasClosestByClassName(event.target, "block__icon")) { return; } let iconsElement = hasClosestByClassName(event.target, "block__icons"); let type = "move"; let x = event.clientX - parseInt(this.element.style.left); let y = event.clientY - parseInt(this.element.style.top); const height = this.element.clientHeight; const width = this.element.clientWidth; if (!iconsElement) { x = event.clientX; y = event.clientY; iconsElement = hasClosestByClassName(event.target, "block__rd") || hasClosestByClassName(event.target, "block__r") || hasClosestByClassName(event.target, "block__rt") || hasClosestByClassName(event.target, "block__d") || hasClosestByClassName(event.target, "block__l") || hasClosestByClassName(event.target, "block__ld") || hasClosestByClassName(event.target, "block__lt") || hasClosestByClassName(event.target, "block__t"); if (!iconsElement) { return; } type = iconsElement.className; } const documentSelf = document; this.element.style.userSelect = "none"; documentSelf.ondragstart = () => false; documentSelf.onmousemove = (moveEvent: MouseEvent) => { if (!this.element) { return; } if (type === "move") { let positionX = moveEvent.clientX - x; let positionY = moveEvent.clientY - y; if (positionX > window.innerWidth - width) { positionX = window.innerWidth - width; } if (positionY > window.innerHeight - height) { positionY = window.innerHeight - height; } this.element.style.left = Math.max(positionX, 0) + "px"; this.element.style.top = Math.max(positionY, Constants.SIZE_TOOLBAR_HEIGHT) + "px"; } else { if (type === "block__r" && moveEvent.clientX - x + width > 200 && moveEvent.clientX - x + width < window.innerWidth) { this.element.style.width = moveEvent.clientX - x + width + "px"; } else if (type === "block__d" && moveEvent.clientY - y + height > 160 && moveEvent.clientY - y + height < window.innerHeight - Constants.SIZE_TOOLBAR_HEIGHT) { this.element.style.height = moveEvent.clientY - y + height + "px"; this.element.style.maxHeight = ""; } else if (type === "block__t" && moveEvent.clientY > Constants.SIZE_TOOLBAR_HEIGHT && y - moveEvent.clientY + height > 160) { this.element.style.top = moveEvent.clientY + "px"; this.element.style.maxHeight = ""; this.element.style.height = (y - moveEvent.clientY + height) + "px"; } else if (type === "block__l" && moveEvent.clientX > 0 && x - moveEvent.clientX + width > 200) { this.element.style.left = moveEvent.clientX + "px"; this.element.style.width = (x - moveEvent.clientX + width) + "px"; } else if (type === "block__rd" && moveEvent.clientX - x + width > 200 && moveEvent.clientX - x + width < window.innerWidth && moveEvent.clientY - y + height > 160 && moveEvent.clientY - y + height < window.innerHeight - Constants.SIZE_TOOLBAR_HEIGHT) { this.element.style.height = moveEvent.clientY - y + height + "px"; this.element.style.maxHeight = ""; this.element.style.width = moveEvent.clientX - x + width + "px"; } else if (type === "block__rt" && moveEvent.clientX - x + width > 200 && moveEvent.clientX - x + width < window.innerWidth && moveEvent.clientY > Constants.SIZE_TOOLBAR_HEIGHT && y - moveEvent.clientY + height > 160) { this.element.style.width = moveEvent.clientX - x + width + "px"; this.element.style.top = moveEvent.clientY + "px"; this.element.style.maxHeight = ""; this.element.style.height = (y - moveEvent.clientY + height) + "px"; } else if (type === "block__lt" && moveEvent.clientX > 0 && x - moveEvent.clientX + width > 200 && moveEvent.clientY > Constants.SIZE_TOOLBAR_HEIGHT && y - moveEvent.clientY + height > 160) { this.element.style.left = moveEvent.clientX + "px"; this.element.style.width = (x - moveEvent.clientX + width) + "px"; this.element.style.top = moveEvent.clientY + "px"; this.element.style.maxHeight = ""; this.element.style.height = (y - moveEvent.clientY + height) + "px"; } else if (type === "block__ld" && moveEvent.clientX > 0 && x - moveEvent.clientX + width > 200 && moveEvent.clientY - y + height > 160 && moveEvent.clientY - y + height < window.innerHeight - Constants.SIZE_TOOLBAR_HEIGHT) { this.element.style.left = moveEvent.clientX + "px"; this.element.style.width = (x - moveEvent.clientX + width) + "px"; this.element.style.height = moveEvent.clientY - y + height + "px"; this.element.style.maxHeight = ""; } } }; documentSelf.onmouseup = () => { if (!this.element) { return; } if (window.siyuan.dragElement) { // 反向链接拖拽 https://ld246.com/article/1632915506502 window.siyuan.dragElement.style.opacity = ""; window.siyuan.dragElement = undefined; } this.element.style.userSelect = "auto"; documentSelf.onmousemove = null; documentSelf.onmouseup = null; documentSelf.ondragstart = null; documentSelf.onselectstart = null; documentSelf.onselect = null; if (type !== "move") { this.editors.forEach(item => { setPadding(item.protyle); }); } else { const pinElement = this.element.firstElementChild.querySelector('[data-type="pin"]'); pinElement.classList.add("block__icon--active"); pinElement.setAttribute("aria-label", window.siyuan.languages.unpin); this.element.setAttribute("data-pin", "true"); } }; }); if (this.targetElement) { this.targetElement.style.cursor = "wait"; } this.element.setAttribute("data-pin", "false"); this.element.addEventListener("dblclick", (event) => { const target = event.target as HTMLElement; const iconsElement = hasClosestByClassName(target, "block__icons"); if (iconsElement) { const pingElement = iconsElement.querySelector('[data-type="pin"]'); if (pingElement.classList.contains("block__icon--active")) { pingElement.classList.remove("block__icon--active"); pingElement.setAttribute("aria-label", window.siyuan.languages.pin); this.element.setAttribute("data-pin", "false"); } else { pingElement.classList.add("block__icon--active"); pingElement.setAttribute("aria-label", window.siyuan.languages.unpin); this.element.setAttribute("data-pin", "true"); } event.preventDefault(); event.stopPropagation(); } }); this.element.addEventListener("click", (event) => { let target = event.target as HTMLElement; while (target && !target.isEqualNode(this.element)) { if (target.classList.contains("block__icon") || target.classList.contains("block__logo")) { const type = target.getAttribute("data-type"); if (type === "close") { this.destroy(); } else if (type === "pin") { if (target.classList.contains("block__icon--active")) { target.classList.remove("block__icon--active"); target.setAttribute("aria-label", window.siyuan.languages.pin); this.element.setAttribute("data-pin", "false"); } else { target.classList.add("block__icon--active"); target.setAttribute("aria-label", window.siyuan.languages.unpin); this.element.setAttribute("data-pin", "true"); } } else if (type === "open") { /// #if !BROWSER openNewWindowById(this.nodeIds[0]); /// #endif } event.preventDefault(); event.stopPropagation(); break; } target = target.parentElement; } }); this.render(); } private initProtyle(editorElement: HTMLElement) { const index = parseInt(editorElement.getAttribute("data-index")); fetchPost("/api/block/getBlockInfo", {id: this.nodeIds[index]}, (response) => { if (response.code === 3) { showMessage(response.msg); return; } if (!this.targetElement && typeof this.x === "undefined" && typeof this.y === "undefined") { return; } const action = []; if (response.data.rootID !== this.nodeIds[index]) { action.push(Constants.CB_GET_ALL); } if (this.isBacklink) { action.push(Constants.CB_GET_BACKLINK); } const editor = new Protyle(this.app, editorElement, { blockId: this.nodeIds[index], defId: this.defIds[index] || this.defIds[0] || "", action, render: { scroll: true, gutter: true, breadcrumbDocName: true, }, typewriterMode: false, after: (editor) => { if (window.siyuan.config.readonly || window.siyuan.config.editor.readOnly) { disabledProtyle(editor.protyle); } editorElement.addEventListener("mouseleave", () => { hideElements(["gutter"], editor.protyle); }); if (response.data.rootID !== this.nodeIds[index]) { editor.protyle.breadcrumb.element.parentElement.insertAdjacentHTML("beforeend", `
`); } } }); this.editors.push(editor); }); } public destroy() { window.siyuan.blockPanels.find((item, index) => { if (item.id === this.id) { window.siyuan.blockPanels.splice(index, 1); return true; } }); if (this.editors.length > 0) { this.editors.forEach(item => { // https://github.com/siyuan-note/siyuan/issues/8199 hideElements(["util"], item.protyle); item.destroy(); }); this.editors = []; } this.element.remove(); this.element = undefined; this.targetElement = undefined; // 移除弹出上使用右键菜单 window.siyuan.menus.menu.remove(); } private render() { if (!document.body.contains(this.element)) { this.destroy(); return; } let openHTML = ""; /// #if !BROWSER if (this.nodeIds.length === 1) { openHTML = ` `; } /// #endif let html = `
${openHTML}
`; if (this.nodeIds.length === 0) { html += `
${window.siyuan.languages.refExpired}
`; } else { this.nodeIds.forEach((item, index) => { html += `
`; }); } if (html) { html += '
'; } this.element.innerHTML = html; const observer = new IntersectionObserver((e) => { e.forEach(item => { if (item.isIntersecting && item.target.innerHTML === "") { this.initProtyle(item.target as HTMLElement); } }); }, { threshold: 0, }); this.element.querySelectorAll(".block__edit").forEach((item: HTMLElement, index) => { if (index < 5) { this.initProtyle(item); } else { observer.observe(item); } }); if (this.targetElement) { this.targetElement.style.cursor = ""; } this.element.classList.add("block__popover--open"); let targetRect; if (this.targetElement && this.targetElement.classList.contains("protyle-wysiwyg__embed")) { targetRect = this.targetElement.getBoundingClientRect(); // 嵌入块过长时,单击弹出的悬浮窗位置居下 https://ld246.com/article/1634292738717 let top = targetRect.top; const contentElement = hasClosestByClassName(this.targetElement, "protyle-content", true); if (contentElement) { const contentRectTop = contentElement.getBoundingClientRect().top; if (targetRect.top < contentRectTop) { top = contentRectTop; } } // 单击嵌入块悬浮窗的位置最好是覆盖嵌入块 setPosition(this.element, targetRect.left, Math.max(top - 84, Constants.SIZE_TOOLBAR_HEIGHT), 0, 8); } else if (this.targetElement) { if (this.targetElement.classList.contains("pdf__rect")) { targetRect = this.targetElement.firstElementChild.getBoundingClientRect(); } else { targetRect = this.targetElement.getBoundingClientRect(); } // 靠边不宜拖拽 https://github.com/siyuan-note/siyuan/issues/2937 setPosition(this.element, targetRect.left, targetRect.top + targetRect.height + 4, targetRect.height + 12, 8); } else if (typeof this.x === "number" && typeof this.y === "number") { setPosition(this.element, this.x, this.y); } this.element.style.maxHeight = (window.innerHeight - this.element.getBoundingClientRect().top - 8) + "px"; } }