Something went wrong. Try again.
A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353import {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 !BROWSERimport {openNewWindowById} from "../window/openNewWindow";/// #endif/// #if !MOBILEimport {moveResize} from "../dialog/moveResize";import {openFileById} from "../editor/util";/// #endifimport {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, scrollPosition: "start" }); }); 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, title: response.data.rootID === this.refDefs[index].refID, // 如果块是文档,显示文档标题 }, 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 (menuLevel && menuLevel >= level && window.siyuan.menus.menu.element.dataset.from?.includes("popover")) { // 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 = `<span data-type="stickTab" class="block__icon block__icon--show b3-tooltips b3-tooltips__sw" aria-label="${window.siyuan.languages.openInNewTab}${updateHotkeyAfterTip(window.siyuan.config.keymap.editor.general.openInNewTab.custom)}"><svg><use xlink:href="#iconOpen"></use></svg></span><span class="fn__space"></span>`; /// #if !BROWSER openHTML += `<span data-type="open" class="block__icon block__icon--show b3-tooltips b3-tooltips__sw" aria-label="${window.siyuan.languages.openByNewWindow}"><svg><use xlink:href="#iconOpenWindow"></use></svg></span><span class="fn__space"></span>`; /// #endif } let html = `<div class="block__icons block__icons--menu"> <span class="fn__space fn__flex-1 resize__move"></span>${openHTML} <span data-type="pin" class="block__icon block__icon--show b3-tooltips b3-tooltips__sw" aria-label="${window.siyuan.languages.pin}"><svg><use xlink:href="#iconPin"></use></svg></span> <span class="fn__space"></span> <span data-type="close" class="block__icon block__icon--show b3-tooltips b3-tooltips__sw" aria-label="${window.siyuan.languages.close}${updateHotkeyAfterTip(window.siyuan.config.keymap.general.closeTab.custom)}"><svg style="width: 12px;margin: 0 1px;"><use xlink:href="#iconClose"></use></svg></span></div><div class="block__content">`; if (this.refDefs.length === 0) { html += `<div class="ft__smaller ft__smaller ft__secondary b3-form__space--small" contenteditable="false">${window.siyuan.languages.refExpired}</div>`; } else { this.refDefs.forEach((item, index) => { html += `<div class="block__edit fn__flex-1 protyle" data-index="${index}"></div>`; }); } if (html) { html += '</div><div class="resize__rd"></div><div class="resize__ld"></div><div class="resize__lt"></div><div class="resize__rt"></div><div class="resize__r"></div><div class="resize__d"></div><div class="resize__t"></div><div class="resize__l"></div>'; } this.element.innerHTML = html; let resizeTimeout: number; this.observerResize = new ResizeObserver(() => { clearTimeout(resizeTimeout); resizeTimeout = window.setTimeout(() => { this.editors.forEach(item => { resize(item.protyle); }); }, Constants.TIMEOUT_TRANSITION); }); this.observerResize.observe(this.element); this.observerLoad = 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 ? () => { if (!document.contains(this.element)) { return; } 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 { this.observerLoad.observe(item); } }); if (this.targetElement) { this.targetElement.style.cursor = ""; }
this.element.querySelector(".block__content").addEventListener("scroll", () => { this.editors.forEach(item => { hideElements(["gutter"], item.protyle); }); }); }}