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"; import {openFileById} from "../editor/util"; /// #endif import {fetchPost} from "../util/fetch"; import {showMessage} from "../dialog/message"; import {App} from "../index"; import {resize} from "../protyle/util/resize"; import {checkFold} from "../util/noRelyPCFunction"; import {updateHotkeyAfterTip} from "../protyle/util/compatibility"; export class BlockPanel { public element: HTMLElement; public targetElement: HTMLElement; public refDefs: IRefDefs[]; public id: string; private app: App; public x: number; public y: number; private isBacklink: boolean; public editors: Protyle[] = []; private observerResize: ResizeObserver; private observerLoad: IntersectionObserver; private originalRefBlockIDs: IObject; // x,y 和 targetElement 二选一必传 constructor(options: { app: App, targetElement?: HTMLElement, refDefs: IRefDefs[] isBacklink: boolean, originalRefBlockIDs?: IObject, // isBacklink 为 true 时有效 x?: number, y?: number, }) { this.id = genUUID(); this.targetElement = options.targetElement; this.refDefs = options.refDefs; this.app = options.app; this.x = options.x; this.y = options.y; this.isBacklink = options.isBacklink; this.originalRefBlockIDs = options.originalRefBlockIDs; 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.refDefs[0].refID); } // 移除同层级其他更高级的 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 (this.element.getAttribute("data-pin") === "true") { pingElement.setAttribute("aria-label", window.siyuan.languages.pin); pingElement.querySelector("use").setAttribute("xlink:href", "#iconPin"); this.element.setAttribute("data-pin", "false"); } else { pingElement.setAttribute("aria-label", window.siyuan.languages.unpin); pingElement.querySelector("use").setAttribute("xlink:href", "#iconUnpin"); 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 (this.element.getAttribute("data-pin") === "true") { target.setAttribute("aria-label", window.siyuan.languages.pin); target.querySelector("use").setAttribute("xlink:href", "#iconPin"); this.element.setAttribute("data-pin", "false"); } else { target.setAttribute("aria-label", window.siyuan.languages.unpin); target.querySelector("use").setAttribute("xlink:href", "#iconUnpin"); this.element.setAttribute("data-pin", "true"); } } else if (type === "open") { /// #if !BROWSER openNewWindowById(this.refDefs[0].refID); /// #endif } else if (type === "stickTab") { checkFold(this.refDefs[0].refID, (zoomIn, action) => { openFileById({ app: options.app, id: this.refDefs[0].refID, action, zoomIn, openNewTab: true }); }); this.destroy(); } event.preventDefault(); event.stopPropagation(); break; } target = target.parentElement; } }); /// #if !MOBILE moveResize(this.element, () => { const pinElement = this.element.firstElementChild.querySelector('[data-type="pin"]'); pinElement.setAttribute("aria-label", window.siyuan.languages.unpin); pinElement.querySelector("use").setAttribute("xlink:href", "#iconUnpin"); 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.refDefs[index].refID}, (response) => { if (response.code === 3) { showMessage(response.msg); return; } if (!this.targetElement && typeof this.x === "undefined" && typeof this.y === "undefined") { return; } const action: TProtyleAction[] = []; if (response.data.rootID !== this.refDefs[index].refID) { action.push(Constants.CB_GET_ALL); } else { action.push(Constants.CB_GET_CONTEXT); // 不需要高亮 https://github.com/siyuan-note/siyuan/issues/11160#issuecomment-2084652764 } if (this.isBacklink) { action.push(Constants.CB_GET_BACKLINK); } const editor = new Protyle(this.app, editorElement, { blockId: this.refDefs[index].refID, defIds: this.refDefs[index].defIDs || [], originalRefBlockIDs: this.isBacklink ? this.originalRefBlockIDs : undefined, action, render: { scroll: true, gutter: true, breadcrumbDocName: true, }, typewriterMode: false, after: (editor) => { if (response.data.rootID !== this.refDefs[index].refID) { 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;`); } }); this.editors.push(editor); }); } public destroy() { this.observerResize?.disconnect(); this.observerLoad?.disconnect(); 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 = []; } const level = parseInt(this.element.dataset.level); this.element.remove(); this.element = undefined; this.targetElement = undefined; // 移除弹出上使用右键菜单 const menuLevel = parseInt(window.siyuan.menus.menu.element.dataset.from); if (window.siyuan.menus.menu.element.dataset.from !== "app" && menuLevel && menuLevel >= level) { // https://github.com/siyuan-note/siyuan/issues/9854 右键菜单不是从浮窗中弹出的则不进行移除 window.siyuan.menus.menu.remove(); } } private render() { if (!document.body.contains(this.element)) { this.destroy(); return; } let openHTML = ""; if (this.refDefs.length === 1) { openHTML = ` `; /// #if !BROWSER openHTML += ` `; /// #endif } let html = `