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 = `