import {Layout} from "./index";
import {genUUID} from "../util/genID";
import {
fixWndFlex1,
getInstanceById,
getWndByLayout,
JSONToCenter,
newModelByInitData,
pdfIsLoading,
saveLayout,
setPanelFocus,
switchWnd
} from "./util";
import {Tab} from "./Tab";
import {Model} from "./Model";
import {Editor} from "../editor";
import {Graph} from "./dock/Graph";
import {
hasClosestBlock,
hasClosestByAttribute,
hasClosestByClassName,
isInEmbedBlock
} from "../protyle/util/hasClosest";
import {Constants} from "../constants";
/// #if !BROWSER
import {ipcRenderer, webFrame} from "electron";
import {setModelsHash, setTabPosition} from "../window/setHeader";
/// #endif
import {Search} from "../search";
import {showMessage} from "../dialog/message";
import {openFileById, updatePanelByEditor} from "../editor/util";
import {scrollCenter} from "../util/highlightById";
import {getAllModels} from "./getAll";
import {clearCounter} from "./status";
import {saveScroll} from "../protyle/scroll/saveScroll";
import {Asset} from "../asset";
import {newFile} from "../util/newFile";
import {MenuItem} from "../menus/Menu";
import {escapeHtml} from "../util/escape";
import {getFrontend, isWindow} from "../util/functions";
import {hideAllElements} from "../protyle/ui/hideElements";
import {focusByOffset, getSelectionOffset} from "../protyle/util/selection";
import {Custom} from "./dock/Custom";
import {App} from "../index";
import {unicode2Emoji} from "../emoji";
import {closeWindow} from "../window/closeWin";
import {setTitle} from "../dialog/processSystem";
import {newCenterEmptyTab, resizeTabs} from "./tabUtil";
import {fullscreen} from "../protyle/breadcrumb/action";
import {setPadding} from "../protyle/ui/initUI";
import {setPosition} from "../util/setPosition";
import {clearOBG} from "./dock/util";
import {recordBeforeResizeTop} from "../protyle/util/resize";
export class Wnd {
private app: App;
public id: string;
public parent?: Layout;
public element: HTMLElement;
public headersElement: HTMLElement;
public children: Tab[] = [];
public resize?: Config.TUILayoutDirection;
constructor(app: App, resize?: Config.TUILayoutDirection, parentType?: Config.TUILayoutType) {
this.id = genUUID();
this.app = app;
this.resize = resize;
this.element = document.createElement("div");
this.element.classList.add("fn__flex-1", "fn__flex");
let dragHTML = '
';
if (parentType === "left" || parentType === "right" || parentType === "bottom") {
dragHTML = "";
}
this.element.innerHTML = ``;
this.headersElement = this.element.querySelector(".layout-tab-bar");
const dragElement = this.element.querySelector(".layout-tab-container__drag") as HTMLElement;
if (!dragElement) {
return;
}
this.headersElement.addEventListener("mousedown", (event) => {
// 点击鼠标滚轮关闭
if (event.button !== 1) {
return;
}
let target = event.target as HTMLElement;
while (target && !target.isEqualNode(this.headersElement)) {
if (target.tagName === "LI") {
this.removeTab(target.getAttribute("data-id"));
window.siyuan.menus.menu.remove();
event.stopPropagation();
event.preventDefault();
const frontend = getFrontend();
if ((["desktop", "desktop-window"].includes(frontend) && window.siyuan.config.system.os === "linux") ||
(frontend === "browser-desktop" && navigator.userAgent.indexOf("Linux") !== -1)) {
const activeElement = document.activeElement;
window.addEventListener("paste", this.#preventPast, {
capture: true,
once: true
});
// TODO 保持原有焦点?https://github.com/siyuan-note/siyuan/pull/13395/files#r1877004077
if (activeElement instanceof HTMLElement) {
activeElement.focus();
}
// 如果在短时间内没有 paste 事件发生,移除监听
setTimeout(() => {
window.removeEventListener("paste", this.#preventPast, {
capture: true
});
}, Constants.TIMEOUT_INPUT);
}
break;
}
target = target.parentElement;
}
});
this.headersElement.addEventListener("mousewheel", (event: WheelEvent) => {
this.headersElement.scrollLeft = this.headersElement.scrollLeft + event.deltaY;
}, {passive: true});
this.headersElement.parentElement.addEventListener("click", (event) => {
let target = event.target as HTMLElement;
while (target && !target.isEqualNode(this.headersElement)) {
if (target.classList.contains("block__icon") && target.getAttribute("data-type") === "new") {
setPanelFocus(this.headersElement.parentElement.parentElement);
newFile({
app,
useSavePath: true
});
break;
} else if (target.classList.contains("block__icon") && target.getAttribute("data-type") === "more") {
this.renderTabList(target);
break;
} else if (target.tagName === "LI" && target.getAttribute("data-id") && !pdfIsLoading(this.element)) {
if (target.classList.contains("item--focus")) {
this.switchTab(target, true, true, false, false);
} else {
this.switchTab(target, true);
}
break;
}
target = target.parentElement;
}
});
this.headersElement.parentElement.addEventListener("dblclick", (event) => {
let target = event.target as HTMLElement;
while (target && !target.isEqualNode(this.headersElement)) {
if (window.siyuan.config.fileTree.openFilesUseCurrentTab && target.getAttribute("data-type") === "tab-header") {
target.classList.remove("item--unupdate");
break;
}
target = target.parentElement;
}
});
this.headersElement.parentElement.addEventListener("dragover", function (event: DragEvent & {
target: HTMLElement
}) {
const it = this as HTMLElement;
it.classList.remove("layout-tab-bars--drag");
if (event.dataTransfer.types.includes(Constants.SIYUAN_DROP_FILE)) {
event.preventDefault();
it.classList.add("layout-tab-bars--drag");
return;
}
// 不能使用 !window.siyuan.dragElement,因为移动页签到新窗口后,再把主窗口页签拖拽新窗口页签上时,该值为空
if (!event.dataTransfer.types.includes(Constants.SIYUAN_DROP_TAB)) {
return;
}
event.preventDefault();
let oldTabHeaderElement = window.siyuan.dragElement;
let exitDrag = false;
Array.from(it.firstElementChild.childNodes).find((item: HTMLElement) => {
if (item.style.opacity === "0.1") {
oldTabHeaderElement = item;
exitDrag = true;
return true;
}
});
if (!exitDrag && oldTabHeaderElement) {
if (oldTabHeaderElement.classList.contains("item--pin")) {
return;
}
oldTabHeaderElement = oldTabHeaderElement.cloneNode(true) as HTMLElement;
oldTabHeaderElement.setAttribute("data-clone", "true");
it.firstElementChild.append(oldTabHeaderElement);
return;
} else if (!exitDrag && !oldTabHeaderElement) { // 拖拽到新窗口
oldTabHeaderElement = document.createElement("li");
oldTabHeaderElement.style.opacity = "0.1";
oldTabHeaderElement.innerHTML = '';
oldTabHeaderElement.setAttribute("data-clone", "true");
it.firstElementChild.append(oldTabHeaderElement);
}
const newTabHeaderElement = hasClosestByAttribute(event.target, "data-type", "tab-header");
if (!newTabHeaderElement) {
if (!oldTabHeaderElement.classList.contains("item--pin")) {
it.classList.add("layout-tab-bars--drag");
}
return;
}
if (!newTabHeaderElement.isSameNode(oldTabHeaderElement) &&
((oldTabHeaderElement.classList.contains("item--pin") && newTabHeaderElement.classList.contains("item--pin")) ||
(!oldTabHeaderElement.classList.contains("item--pin") && !newTabHeaderElement.classList.contains("item--pin")))) {
const rect = newTabHeaderElement.getClientRects()[0];
if (event.clientX > rect.left + rect.width / 2) {
newTabHeaderElement.after(oldTabHeaderElement);
} else {
newTabHeaderElement.before(oldTabHeaderElement);
}
}
});
let dragleaveTimeout: number;
let headerDragCounter = 0;
this.headersElement.parentElement.addEventListener("dragleave", (event)=> {
if (!hasClosestByAttribute(event.target as HTMLElement, "data-clone", "true")) {
headerDragCounter--;
}
if (headerDragCounter === 0) {
this.headersElement.parentElement.classList.remove("layout-tab-bars--drag");
clearTimeout(dragleaveTimeout);
// 窗口拖拽到新窗口时,不 drop 无法移除 clone 的元素
dragleaveTimeout = window.setTimeout(() => {
document.querySelectorAll(".layout-tab-bar li[data-clone='true']").forEach(item => {
item.remove();
});
}, 1000);
}
});
this.headersElement.parentElement.addEventListener("dragenter", (event) => {
event.preventDefault();
if (!hasClosestByAttribute(event.target as HTMLElement, "data-clone", "true")) {
headerDragCounter++;
}
});
this.headersElement.parentElement.addEventListener("dragend", (event) => {
this.headersElement.parentElement.classList.remove("layout-tab-bars--drag");
});
this.headersElement.parentElement.addEventListener("drop", (event: DragEvent & {
target: HTMLElement
})=> {
this.headersElement.parentElement.classList.remove("layout-tab-bars--drag");
headerDragCounter = 0;
const it = this.headersElement;
if (event.dataTransfer.types.includes(Constants.SIYUAN_DROP_FILE)) {
// 文档树拖拽
setPanelFocus(it.parentElement);
event.dataTransfer.getData(Constants.SIYUAN_DROP_FILE).split(",").forEach(item => {
if (item) {
openFileById({
app,
id: item,
action: [Constants.CB_GET_FOCUS, Constants.CB_GET_SCROLL]
});
}
});
window.siyuan.dragElement = undefined;
return;
}
const tabData = JSON.parse(event.dataTransfer.getData(Constants.SIYUAN_DROP_TAB));
let oldTab = getInstanceById(tabData.id) as Tab;
const wnd = getInstanceById(it.parentElement.getAttribute("data-id")) as Wnd;
/// #if !BROWSER
if (!oldTab) { // 从主窗口拖拽到页签新窗口
if (wnd instanceof Wnd) {
JSONToCenter(app, tabData, wnd);
oldTab = wnd.children[wnd.children.length - 1];
ipcRenderer.send(Constants.SIYUAN_SEND_WINDOWS, {cmd: "closetab", data: tabData.id});
it.querySelector("li[data-clone='true']").remove();
wnd.switchTab(oldTab.headElement);
ipcRenderer.send(Constants.SIYUAN_CMD, "focus");
}
}
/// #endif
if (!oldTab) {
return;
}
const nextTabHeaderElement = (Array.from(it.firstElementChild.childNodes).find((item: HTMLElement) => {
if (item.style.opacity === "0.1") {
return true;
}
}) as HTMLElement)?.nextElementSibling;
if (!it.contains(oldTab.headElement)) {
// 从其他 Wnd 拖动过来
const cloneTabElement = it.querySelector("[data-clone='true']");
if (!cloneTabElement) {
return;
}
cloneTabElement.before(oldTab.headElement);
cloneTabElement.remove();
// 对象顺序
wnd.moveTab(oldTab, nextTabHeaderElement ? nextTabHeaderElement.getAttribute("data-id") : undefined);
resizeTabs();
return;
}
let tempTab: Tab;
oldTab.parent.children.find((item, index) => {
if (item.id === oldTab.id) {
tempTab = oldTab.parent.children.splice(index, 1)[0];
return true;
}
});
if (nextTabHeaderElement) {
oldTab.parent.children.find((item, index) => {
if (item.id === nextTabHeaderElement.getAttribute("data-id")) {
oldTab.parent.children.splice(index, 0, tempTab);
return true;
}
});
} else {
oldTab.parent.children.push(tempTab);
}
saveLayout();
});
this.element.addEventListener("dragenter", (event: DragEvent & { target: HTMLElement }) => {
if (event.dataTransfer.types.includes(Constants.SIYUAN_DROP_TAB)) {
const tabHeadersElement = hasClosestByClassName(event.target, "layout-tab-bar");
if (tabHeadersElement) {
return;
}
const tabPanelsElement = hasClosestByClassName(event.target, "layout-tab-container", true);
if (tabPanelsElement) {
dragElement.classList.remove("fn__none");
dragElement.setAttribute("style", "height:100%;width:100%;right:auto;bottom:auto");
}
}
});
dragElement.addEventListener("dragover", (event: DragEvent & { layerX: number, layerY: number }) => {
event.preventDefault();
if (!dragElement.nextElementSibling) {
return;
}
const rect = dragElement.parentElement.getBoundingClientRect();
const height = rect.height;
const width = rect.width;
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
if (x <= width / 8 || (x <= width / 3 && x > width / 8 && y >= height / 8 && y <= height * 7 / 8)) {
dragElement.setAttribute("style", "height:100%;width:50%;right:50%;bottom:0;left:0;top:0");
} else if (x >= width * 7 / 8 || (x >= width * 2 / 3 && x < width * 7 / 8 && y >= height / 8 && y <= height * 7 / 8)) {
dragElement.setAttribute("style", "height:100%;width:50%;right:0;bottom:0;left:50%;top:0");
} else if (y <= height / 8) {
dragElement.setAttribute("style", "height:50%;width:100%;right:0;bottom:50%;left:0;top:0");
} else if (y >= height * 7 / 8) {
dragElement.setAttribute("style", "height:50%;width:100%;right:0;bottom:0;left:0;top:50%");
} else {
dragElement.setAttribute("style", "height:100%;width:100%;right:0;bottom:0;top:0;left:0");
}
});
dragElement.addEventListener("dragleave", () => {
dragElement.classList.add("fn__none");
});
dragElement.addEventListener("drop", (event: DragEvent & { target: HTMLElement }) => {
dragElement.classList.add("fn__none");
const targetWndElement = event.target.parentElement.parentElement;
const targetWnd = getInstanceById(targetWndElement.getAttribute("data-id")) as Wnd;
const tabData = JSON.parse(event.dataTransfer.getData(Constants.SIYUAN_DROP_TAB));
let oldTab = getInstanceById(tabData.id) as Tab;
/// #if !BROWSER
if (!oldTab) { // 从主窗口拖拽到页签新窗口
JSONToCenter(app, tabData, this);
oldTab = this.children[this.children.length - 1];
ipcRenderer.send(Constants.SIYUAN_SEND_WINDOWS, {cmd: "closetab", data: tabData.id});
ipcRenderer.send(Constants.SIYUAN_CMD, "focus");
}
/// #endif
if (!oldTab) {
return;
}
if (dragElement.style.height === "50%" || dragElement.style.width === "50%") {
// split
if (dragElement.style.height === "50%") {
// split to bottom
const newWnd = targetWnd.split("tb");
newWnd.headersElement.append(oldTab.headElement);
newWnd.headersElement.parentElement.classList.remove("fn__none");
newWnd.moveTab(oldTab);
if (dragElement.style.bottom === "50%" && newWnd.element.previousElementSibling && targetWnd.element.parentElement) {
// 交换位置
switchWnd(newWnd, targetWnd);
}
} else if (dragElement.style.width === "50%") {
// split to right
const newWnd = targetWnd.split("lr");
newWnd.headersElement.append(oldTab.headElement);
newWnd.headersElement.parentElement.classList.remove("fn__none");
newWnd.moveTab(oldTab);
if (dragElement.style.right === "50%" && newWnd.element.previousElementSibling && targetWnd.element.parentElement) {
// 交换位置
switchWnd(newWnd, targetWnd);
}
}
resizeTabs();
/// #if !BROWSER
setTabPosition();
/// #endif
return;
}
if (targetWndElement.contains(document.querySelector(`[data-id="${tabData.id}"]`))) {
return;
}
if (targetWnd) {
targetWnd.headersElement.append(oldTab.headElement);
targetWnd.headersElement.parentElement.classList.remove("fn__none");
targetWnd.moveTab(oldTab);
resizeTabs();
}
});
}
public showHeading() {
const currentElement = this.headersElement.querySelector(".item--focus") as HTMLElement;
if (!currentElement) {
return;
}
if (currentElement.offsetLeft + currentElement.clientWidth > this.headersElement.scrollLeft + this.headersElement.clientWidth) {
this.headersElement.scrollLeft = currentElement.offsetLeft + currentElement.clientWidth - this.headersElement.clientWidth;
} else if (currentElement.offsetLeft < this.headersElement.scrollLeft) {
this.headersElement.scrollLeft = currentElement.offsetLeft;
}
}
public switchTab(target: HTMLElement, pushBack = false, update = true, resize = true, isSaveLayout = true) {
let currentTab: Tab;
let isInitActive = false;
this.children.forEach((item) => {
if (target === item.headElement) {
if (item.headElement && item.headElement.classList.contains("fn__none")) {
// https://github.com/siyuan-note/siyuan/issues/267
} else {
if (item.headElement) {
item.headElement.classList.add("item--focus");
if (item.headElement.getAttribute("data-init-active") === "true") {
item.headElement.removeAttribute("data-init-active");
isInitActive = true;
} else {
item.headElement.setAttribute("data-activetime", (new Date()).getTime().toString());
}
}
item.panelElement.classList.remove("fn__none");
}
currentTab = item;
} else {
item.headElement?.classList.remove("item--focus");
if (!item.panelElement.classList.contains("fn__none")) {
// 必须现判断,否则会触发 observer.observe(this.element, {attributeFilter: ["class"]}); 导致 https://ld246.com/article/1641198819303
item.panelElement.classList.add("fn__none");
}
}
});
// 在 JSONToLayout 中进行 focus
if (!isInitActive) {
setPanelFocus(this.headersElement.parentElement.parentElement, isSaveLayout);
}
if (currentTab && currentTab.headElement) {
const initData = currentTab.headElement.getAttribute("data-initdata");
if (initData) {
currentTab.addModel(newModelByInitData(this.app, currentTab, JSON.parse(initData)));
currentTab.headElement.removeAttribute("data-initdata");
if (isSaveLayout) {
saveLayout();
}
return;
}
}
if (currentTab && target === currentTab.headElement) {
if (currentTab.model instanceof Graph) {
currentTab.model.onGraph(false);
} else if (currentTab.model instanceof Asset && currentTab.model.pdfObject && currentTab.model.pdfObject.pdfViewer) {
// https://github.com/siyuan-note/siyuan/issues/5655
currentTab.model.pdfObject.pdfViewer.container.focus();
}
}
if (currentTab && currentTab.model instanceof Editor) {
const keepCursorId = currentTab.headElement.getAttribute("keep-cursor");
if (keepCursorId) {
// 在新页签中打开,但不跳转到新页签,但切换到新页签时需调整滚动
let nodeElement: HTMLElement;
Array.from(currentTab.model.editor.protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${keepCursorId}"]`)).find((item: HTMLElement) => {
if (!isInEmbedBlock(item)) {
nodeElement = item;
return true;
}
});
if (nodeElement) {
if (!currentTab.model.editor.protyle.toolbar.range) {
const range = document.createRange();
range.selectNodeContents(nodeElement);
range.collapse();
currentTab.model.editor.protyle.toolbar.range = range;
}
scrollCenter(currentTab.model.editor.protyle, nodeElement, true);
} else {
openFileById({
app: this.app,
id: keepCursorId,
action: [Constants.CB_GET_FOCUS, Constants.CB_GET_SCROLL]
});
}
currentTab.headElement.removeAttribute("keep-cursor");
}
// focusin 触发前,layout__wnd--active 和 tab 已设置,需在调用里面更新
if (update) {
updatePanelByEditor({
protyle: currentTab.model.editor.protyle,
focus: true,
pushBackStack: pushBack,
reload: false,
resize,
});
}
if (window.siyuan.editorIsFullscreen) {
fullscreen(currentTab.model.editor.protyle.element);
setPadding(currentTab.model.editor.protyle);
}
} else {
clearOBG();
}
if (isSaveLayout) {
saveLayout();
}
}
public addTab(tab: Tab, keepCursor = false, isSaveLayout = true, activeTime?: string) {
if (keepCursor) {
tab.headElement?.classList.remove("item--focus");
tab.panelElement.classList.add("fn__none");
}
let oldFocusIndex = 0;
this.children.forEach((item, index) => {
if (item.headElement && item.headElement.classList.contains("item--focus")) {
oldFocusIndex = index;
let nextElement = item.headElement.nextElementSibling;
while (nextElement && nextElement.classList.contains("item--pin")) {
oldFocusIndex++;
nextElement = nextElement.nextElementSibling;
}
}
if (!keepCursor) {
item.headElement?.classList.remove("item--focus");
item.panelElement.classList.add("fn__none");
}
});
this.children.splice(oldFocusIndex + 1, 0, tab);
if (tab.headElement) {
this.headersElement.parentElement.classList.remove("fn__none");
if (this.headersElement.childElementCount === 0) {
this.headersElement.append(tab.headElement);
} else {
this.headersElement.children[oldFocusIndex].after(tab.headElement);
}
tab.headElement.querySelector(".item__close").addEventListener("click", (event) => {
if (tab.headElement.classList.contains("item--pin")) {
tab.unpin();
} else {
tab.parent.removeTab(tab.id);
}
window.siyuan.menus.menu.remove();
event.stopPropagation();
event.preventDefault();
});
tab.headElement.setAttribute("data-activetime", activeTime || (new Date()).getTime().toString());
}
const containerElement = this.element.querySelector(".layout-tab-container");
if (!containerElement.querySelector(".fn__flex-1")) {
// empty center
containerElement.append(tab.panelElement);
} else if (!containerElement.querySelector(".layout-tab-container__drag")) {
// Dock
containerElement.children[oldFocusIndex].after(tab.panelElement);
} else {
containerElement.children[oldFocusIndex + 1].after(tab.panelElement);
}
tab.parent = this;
if (tab.callback) {
tab.callback(tab);
}
// 移除 centerLayout 中的 empty
if (this.parent.type === "center" && this.children.length === 2 && !this.children[0].headElement) {
this.removeTab(this.children[0].id);
} else if (this.children.length > window.siyuan.config.fileTree.maxOpenTabCount) {
this.removeOverCounter(isSaveLayout);
}
/// #if !BROWSER
setTabPosition();
setModelsHash();
/// #endif
if (isSaveLayout) {
saveLayout();
}
}
#preventPast(event: ClipboardEvent) {
event.preventDefault();
event.stopPropagation();
}
private renderTabList(target: HTMLElement) {
if (!window.siyuan.menus.menu.element.classList.contains("fn__none") &&
window.siyuan.menus.menu.element.getAttribute("data-name") === "tabList") {
window.siyuan.menus.menu.remove();
return;
}
window.siyuan.menus.menu.remove();
window.siyuan.menus.menu.element.classList.add("b3-menu--list");
Array.from(this.headersElement.children).forEach((item: HTMLElement) => {
const iconElement = item.querySelector(".item__icon");
const graphicElement = item.querySelector(".item__graphic");
let iconHTML = undefined;
if (iconElement) {
if (iconElement.firstElementChild?.tagName === "IMG") {
// 图标为图片的文档
iconHTML = ``;
} else {
// 有图标的文档
iconHTML = ``;
}
} else if (!graphicElement) {
// 没有图标的文档
iconHTML = unicode2Emoji(window.siyuan.storage[Constants.LOCAL_IMAGES].file, "b3-menu__icon", true);
}
window.siyuan.menus.menu.append(new MenuItem({
label: escapeHtml(item.querySelector(".item__text").textContent),
action: "iconCloseRound",
iconHTML,
icon: graphicElement ? graphicElement.firstElementChild.getAttribute("xlink:href").substring(1) : "",
bind: (element) => {
element.addEventListener("click", (itemEvent) => {
if (hasClosestByClassName(itemEvent.target as Element, "b3-menu__action")) {
this.removeTab(item.getAttribute("data-id"));
if (element.previousElementSibling || element.nextElementSibling) {
element.remove();
setPosition(window.siyuan.menus.menu.element, rect.left + rect.width - window.siyuan.menus.menu.element.clientWidth, rect.top + rect.height);
} else {
window.siyuan.menus.menu.remove();
}
} else {
this.switchTab(item, true);
this.showHeading();
window.siyuan.menus.menu.remove();
}
itemEvent.preventDefault();
itemEvent.stopPropagation();
});
},
current: item.classList.contains("item--focus")
}).element);
});
window.siyuan.menus.menu.element.setAttribute("data-name", "tabList");
const rect = target.getBoundingClientRect();
window.siyuan.menus.menu.popup({
x: rect.left + rect.width,
y: rect.top + rect.height,
isLeft: true
});
}
private removeOverCounter(isSaveLayout = false) {
let removeId: string;
let openTime: string;
let removeCount = 0;
this.children.forEach((item, index) => {
if (item.headElement.classList.contains("item--pin") || item.headElement.classList.contains("item--focus")) {
return;
}
removeCount++;
if (!openTime) {
openTime = item.headElement.getAttribute("data-activetime");
removeId = this.children[index].id;
} else if (item.headElement.getAttribute("data-activetime") < openTime) {
openTime = item.headElement.getAttribute("data-activetime");
removeId = this.children[index].id;
}
});
if (removeId) {
this.removeTab(removeId, false, false, isSaveLayout);
removeCount--;
}
if (removeCount > 0 && this.children.length > window.siyuan.config.fileTree.maxOpenTabCount) {
this.removeOverCounter(isSaveLayout);
}
}
private destroyModel(model: Model) {
if (!model) {
return;
}
if (model instanceof Editor && model.editor) {
window.siyuan.blockPanels.forEach((item) => {
if (item.element && model.editor.protyle.wysiwyg.element.contains(item.element)) {
item.destroy();
}
});
model.editor.destroy();
return;
}
if (model instanceof Search) {
model.editors.edit.destroy();
model.editors.unRefEdit.destroy();
return;
}
if (model instanceof Asset) {
if (model.pdfObject && model.pdfObject.pdfLoadingTask) {
model.pdfObject.pdfLoadingTask.destroy();
}
}
if (model instanceof Custom) {
if (model.destroy) {
model.destroy();
}
}
model.send("closews", {});
}
private removeTabAction = (id: string, closeAll = false, animate = true, isSaveLayout = true) => {
clearCounter();
this.children.find((item, index) => {
if (item.id === id) {
if (item.model instanceof Custom && item.model.beforeDestroy) {
item.model.beforeDestroy();
}
if (item.model instanceof Editor) {
saveScroll(item.model.editor.protyle);
}
if (this.children.length === 1) {
this.destroyModel(this.children[0].model);
this.children = [];
if (["bottom", "left", "right"].includes(this.parent.type)) {
item.panelElement.remove();
} else {
recordBeforeResizeTop();
this.remove();
}
// 关闭分屏页签后光标消失
const editors = getAllModels().editor;
if (editors.length === 0) {
clearOBG();
} else {
editors.forEach(item => {
if (!item.element.classList.contains("fn__none")) {
setPanelFocus(item.parent.parent.headersElement.parentElement.parentElement);
updatePanelByEditor({
protyle: item.editor.protyle,
focus: true,
pushBackStack: true,
reload: false,
resize: true,
});
return;
}
});
}
return;
}
if (item.headElement) {
if (item.headElement.classList.contains("item--focus")) {
let latestHeadElement: HTMLElement;
Array.from(item.headElement.parentElement.children).forEach((headItem: HTMLElement) => {
if (!headItem.isSameNode(item.headElement) &&
headItem.style.maxWidth !== "0px" // 不对比已移除但还在动画效果中的元素 https://github.com/siyuan-note/siyuan/issues/7878
) {
if (!latestHeadElement) {
latestHeadElement = headItem;
} else if (headItem.getAttribute("data-activetime") > latestHeadElement.getAttribute("data-activetime")) {
latestHeadElement = headItem;
}
}
});
if (latestHeadElement && !closeAll) {
this.switchTab(latestHeadElement, true, true, false, false);
this.showHeading();
}
}
if (animate) {
item.headElement.setAttribute("style", "max-width: 0px;");
setTimeout(() => {
item.headElement.remove();
}, 200);
} else {
item.headElement.remove();
}
}
item.panelElement.remove();
this.destroyModel(item.model);
this.children.splice(index, 1);
resizeTabs(false);
return true;
}
});
// 初始化移除窗口,但 centerLayout 还没有赋值 https://ld246.com/article/1658718634416
if (window.siyuan.layout.centerLayout) {
const wnd = getWndByLayout(window.siyuan.layout.centerLayout);
if (!wnd) {
/// #if !BROWSER
if (isWindow()) {
closeWindow(this.app);
return;
}
/// #endif
const wnd = new Wnd(this.app);
window.siyuan.layout.centerLayout.addWnd(wnd);
wnd.addTab(newCenterEmptyTab(this.app), false, false);
setTitle(window.siyuan.languages.siyuanNote);
}
}
if (isSaveLayout) {
saveLayout();
}
/// #if !BROWSER
webFrame.clearCache();
ipcRenderer.send(Constants.SIYUAN_CMD, "clearCache");
setTabPosition();
setModelsHash();
/// #endif
};
public removeTab(id: string, closeAll = false, animate = true, isSaveLayout = true) {
for (let index = 0; index < this.children.length; index++) {
const item = this.children[index];
if (item.id === id) {
if ((item.model instanceof Editor) && item.model.editor?.protyle) {
if (item.model.editor.protyle.upload.isUploading) {
showMessage(window.siyuan.languages.uploading);
return;
}
this.removeTabAction(id, closeAll, animate, isSaveLayout);
} else {
this.removeTabAction(id, closeAll, animate, isSaveLayout);
}
return;
}
}
}
public moveTab(tab: Tab, nextId?: string) {
let rangeData: {
id: string,
start: number,
end: number
};
if (tab.model instanceof Editor && tab.model.editor.protyle.toolbar.range) {
const blockElement = hasClosestBlock(tab.model.editor.protyle.toolbar.range.startContainer);
if (blockElement) {
const startEnd = getSelectionOffset(blockElement, undefined, tab.model.editor.protyle.toolbar.range);
rangeData = {
id: blockElement.getAttribute("data-node-id"),
start: startEnd.start,
end: startEnd.end
};
}
}
this.element.querySelector(".layout-tab-container").append(tab.panelElement);
if (rangeData && tab.model instanceof Editor) {
// DOM 移动后 range 会变化
const range = focusByOffset(tab.model.editor.protyle.wysiwyg.element.querySelector(`[data-node-id="${rangeData.id}"]`), rangeData.start, rangeData.end);
if (range) {
tab.model.editor.protyle.toolbar.range = range;
}
}
if (nextId) {
// 只能用 find https://github.com/siyuan-note/siyuan/issues/3455
this.children.find((item, index) => {
if (item.id === nextId) {
this.children.splice(index, 0, tab);
return true;
}
});
} else {
this.children.push(tab);
}
if (this.children.length > window.siyuan.config.fileTree.maxOpenTabCount) {
this.removeOverCounter();
}
const oldWnd = tab.parent;
if (oldWnd.children.length === 1) {
oldWnd.children = [];
oldWnd.remove();
} else {
oldWnd.children.find((item, index) => {
if (item.id === tab.id) {
oldWnd.children.splice(index, 1);
resizeTabs();
return true;
}
});
if (!oldWnd.headersElement.querySelector(".item--focus")) {
let latestHeadElement: HTMLElement;
Array.from(oldWnd.headersElement.children).forEach((headItem: HTMLElement) => {
if (!latestHeadElement) {
latestHeadElement = headItem;
} else if (headItem.getAttribute("data-activetime") > latestHeadElement.getAttribute("data-activetime")) {
latestHeadElement = headItem;
}
});
if (latestHeadElement) {
oldWnd.switchTab(latestHeadElement, true);
}
}
}
// https://github.com/siyuan-note/siyuan/issues/13551
this.switchTab(tab.headElement);
tab.parent = this;
hideAllElements(["toolbar"]);
/// #if !BROWSER
setTabPosition();
/// #endif
}
public split(direction: Config.TUILayoutDirection) {
if (this.children.length === 1 && !this.children[0].headElement) {
// 场景:没有打开的文档,点击标签面板打开
return this;
}
const wnd = new Wnd(this.app, direction);
if (direction === this.parent.direction) {
this.parent.addWnd(wnd, this.id);
} else if (this.parent.children.length === 1) {
// layout 仅含一个时,只需更新 direction
this.parent.direction = direction;
if (direction === "tb") {
this.parent.element.classList.add("fn__flex-column");
this.parent.element.classList.remove("fn__flex");
} else {
this.parent.element.classList.remove("fn__flex-column");
this.parent.element.classList.add("fn__flex");
}
this.parent.addWnd(wnd, this.id);
} else {
this.parent.children.find((item, index) => {
if (item.id === this.id) {
const layout = new Layout({
resize: item.resize,
direction,
});
this.parent.addLayout(layout, item.id);
const movedWnd = this.parent.children.splice(index, 1)[0];
if (movedWnd.resize) {
movedWnd.element.previousElementSibling.remove();
movedWnd.resize = undefined;
}
layout.addWnd.call(layout, movedWnd);
layout.addWnd.call(layout, wnd);
if (direction === "tb" && movedWnd.element.style.width) {
layout.element.style.width = movedWnd.element.style.width;
layout.element.classList.remove("fn__flex-1");
movedWnd.element.style.width = "";
movedWnd.element.classList.add("fn__flex-1");
} else if (direction === "lr" && movedWnd.element.style.height) {
layout.element.style.height = movedWnd.element.style.height;
layout.element.classList.remove("fn__flex-1");
movedWnd.element.style.height = "";
movedWnd.element.classList.add("fn__flex-1");
}
return true;
}
});
}
return wnd;
}
private remove() {
let layout = this.parent;
let element = this.element;
let id = this.id;
while (layout && layout.children.length === 1 && "center" !== layout.type) {
id = layout.id;
element = layout.element;
layout = layout.parent;
}
layout.children.find((item, index) => {
if (item.id === id) {
if (layout.children.length > 1) {
let previous = layout.children[index - 1];
if (index === 0) {
previous = layout.children[1];
}
if (layout.children.length === 2) {
if (layout.direction === "lr") {
previous.element.style.width = "";
} else {
previous.element.style.height = "";
}
previous.resize = undefined;
previous.element.classList.add("fn__flex-1");
}
// https://github.com/siyuan-note/siyuan/issues/5844
if (layout.children.length > 2 && index === 0) {
layout.children[1].resize = undefined;
}
}
layout.children.splice(index, 1);
return true;
}
});
if (element.previousElementSibling && element.previousElementSibling.classList.contains("layout__resize")) {
element.previousElementSibling.remove();
} else if (element.nextElementSibling && element.nextElementSibling.classList.contains("layout__resize")) {
element.nextElementSibling.remove();
}
element.remove();
fixWndFlex1(layout);
resizeTabs();
}
}