siyuan/app/src/block/Panel.ts

314 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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下图标+8padding+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 = `<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}"><svg style="width: 10px"><use xlink:href="#iconClose"></use></svg></span>
</div>
<div class="block__content">`;
if (this.nodeIds.length === 0) {
html += `<div class="ft__smaller ft__smaller ft__secondary b3-form__space--small" contenteditable="false">${window.siyuan.languages.refExpired}</div>`;
} else {
this.nodeIds.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;
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 = "";
}
}
}