import {hasClosestByClassName} from "../protyle/util/hasClosest"; import {Protyle} from "../protyle"; import {genUUID} from "../util/genID"; import {setPosition} from "../util/setPosition"; import {hideElements} from "../protyle/ui/hideElements"; import {Constants} from "../constants"; /// #if !BROWSER import {openNewWindowById} from "../window/openNewWindow"; /// #endif /// #if !MOBILE import {moveResize} from "../dialog/moveResize"; /// #endif import {fetchPost} from "../util/fetch"; import {showMessage} from "../dialog/message"; import {App} from "../index"; import {isMobile} from "../util/functions"; import {resize} from "../protyle/util/resize"; 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"); 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); 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) => { if (this.element && window.siyuan.blockPanels.length > 1) { this.element.style.zIndex = (++window.siyuan.zIndex).toString(); } 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; } }); /// #if !MOBILE moveResize(this.element, (type: string) => { if (type !== "move") { this.editors.forEach(item => { resize(item.protyle); }); } 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"); }); /// #endif this.render(); } private initProtyle(editorElement: HTMLElement, afterCB?: () => void) { 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); } else { action.push(Constants.CB_GET_CONTEXT); action.push(Constants.CB_GET_HL); } 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) => { editorElement.addEventListener("mouseleave", () => { hideElements(["gutter"], editor.protyle); }); if (response.data.rootID !== this.nodeIds[index]) { editor.protyle.breadcrumb.element.parentElement.lastElementChild.classList.remove("fn__none"); } if (afterCB) { afterCB(); } // https://ld246.com/article/1653639418266 if (editor.protyle.element.nextElementSibling || editor.protyle.element.previousElementSibling) { editor.protyle.element.style.minHeight = Math.min(30 + editor.protyle.wysiwyg.element.clientHeight, window.innerHeight / 3) + "px"; } // 由于 afterCB 中高度的设定,需在之后再进行设定 // 49 = 16(上图标)+16(下图标)+8(padding)+9(底部距离) editor.protyle.scroll.element.parentElement.setAttribute("style", `--b3-dynamicscroll-width:${Math.min(editor.protyle.contentElement.clientHeight - 49, 200)}px;${isMobile() ? "" : "right:10px"}`); } }); 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, index === 0 ? () => { 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; } } // 单击嵌入块悬浮窗的位置最好是覆盖嵌入块 // 防止图片撑高后悬浮窗显示不下,只能设置高度 this.element.style.height = Math.min(window.innerHeight - Constants.SIZE_TOOLBAR_HEIGHT, targetRect.height + 42) + "px"; setPosition(this.element, targetRect.left, Math.max(top - 42, Constants.SIZE_TOOLBAR_HEIGHT), -42, 0); } else if (this.targetElement) { if (this.targetElement.classList.contains("pdf__rect")) { targetRect = this.targetElement.firstElementChild.getBoundingClientRect(); } else { targetRect = this.targetElement.getBoundingClientRect(); } // 下部位置大的话就置于下部 https://ld246.com/article/1690333302147 if (window.innerHeight - targetRect.bottom - 4 > targetRect.top + 12) { this.element.style.maxHeight = Math.floor(window.innerHeight - targetRect.bottom - 12) + "px"; } // 靠边不宜拖拽 https://github.com/siyuan-note/siyuan/issues/2937 setPosition(this.element, targetRect.left, targetRect.bottom + 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 = Math.floor(window.innerHeight - Math.max(this.y, Constants.SIZE_TOOLBAR_HEIGHT) - 12) + "px"; } const elementRect = this.element.getBoundingClientRect(); if (this.targetElement && !this.targetElement.classList.contains("protyle-wysiwyg__embed")) { if (elementRect.top < targetRect.top) { this.element.style.maxHeight = Math.floor(targetRect.top - elementRect.top - 8) + "px"; } else { this.element.style.maxHeight = Math.floor(window.innerHeight - elementRect.top - 8) + "px"; } } this.element.classList.add("block__popover--open"); this.element.style.zIndex = (++window.siyuan.zIndex).toString(); } : undefined); } else { observer.observe(item); } }); if (this.targetElement) { this.targetElement.style.cursor = ""; } } }