diff --git a/app/src/assets/scss/component/_menu.scss b/app/src/assets/scss/component/_menu.scss index 60de8f4a0f3..d471b559ade 100644 --- a/app/src/assets/scss/component/_menu.scss +++ b/app/src/assets/scss/component/_menu.scss @@ -169,7 +169,6 @@ } &__items { - max-height: 80vh; overflow: auto; padding: 0 8px; } @@ -367,7 +366,6 @@ &__submenu { overflow: auto; display: none; - max-height: 80vh; border: 1px solid var(--b3-theme-surface-lighter); border-radius: var(--b3-border-radius-b); background-color: var(--b3-menu-background); diff --git a/app/src/boot/onGetConfig.ts b/app/src/boot/onGetConfig.ts index decd7a88e64..37a6ad8e1fd 100644 --- a/app/src/boot/onGetConfig.ts +++ b/app/src/boot/onGetConfig.ts @@ -84,6 +84,7 @@ export const onGetConfig = (isStart: boolean, app: App) => { adjustLayout(); resizeTabs(); resizeTopBar(); + window.siyuan.menus.menu.resetPosition(); firstResize = true; }, 200); }); diff --git a/app/src/menus/Menu.ts b/app/src/menus/Menu.ts index 209c91cc7d5..a982ccac989 100644 --- a/app/src/menus/Menu.ts +++ b/app/src/menus/Menu.ts @@ -60,23 +60,47 @@ export class Menu { }); } - public showSubMenu(subMenuElement: HTMLElement) { + public showSubMenu(subMenuElement: HTMLElement | null) { + if (!subMenuElement) { + return; + } + const itemsMenuElement = subMenuElement.lastElementChild as HTMLElement; + if (itemsMenuElement) { + itemsMenuElement.style.maxHeight = ""; + } const itemRect = subMenuElement.parentElement.getBoundingClientRect(); - subMenuElement.style.top = (itemRect.top - 8) + "px"; - subMenuElement.style.left = (itemRect.right + 8) + "px"; - subMenuElement.style.bottom = "auto"; - const rect = subMenuElement.getBoundingClientRect(); - if (rect.right > window.innerWidth) { - if (itemRect.left - 8 > rect.width) { - subMenuElement.style.left = (itemRect.left - 8 - rect.width) + "px"; + const subMenuRect = subMenuElement.getBoundingClientRect(); + + // 垂直方向位置调整 + // 减 9px 是为了尽量对齐菜单选项(b3-menu__submenu 的默认 padding-top 加上子菜单首个 b3-menu__item 的默认 margin-top) + // 减 1px 是为了避免在特定情况下渲染出不应存在的滚动条而做的兼容处理 + const top = Math.min(itemRect.top - 9, window.innerHeight - subMenuRect.height - 1); + subMenuElement.style.top = Math.max(Constants.SIZE_TOOLBAR_HEIGHT, top) + "px"; + + // 水平方向位置调整 + if (subMenuRect.right <= window.innerWidth) { + // 8px 是 b3-menu__items 的默认 padding-right + subMenuElement.style.left = (itemRect.right + 8) + "px"; + } else { + if (itemRect.left - 8 > subMenuRect.width) { + subMenuElement.style.left = (itemRect.left - 8 - subMenuRect.width) + "px"; } else { - subMenuElement.style.left = (window.innerWidth - rect.width) + "px"; + subMenuElement.style.left = (window.innerWidth - subMenuRect.width) + "px"; } } - if (rect.bottom > window.innerHeight) { - subMenuElement.style.top = "auto"; - subMenuElement.style.bottom = "8px"; + + this.updateMaxHeight(subMenuElement, itemsMenuElement); + } + + private updateMaxHeight(menuElement: HTMLElement, itemsMenuElement: HTMLElement) { + if (!menuElement || !itemsMenuElement) { + return; } + const menuRect = menuElement.getBoundingClientRect(); + const itemsMenuRect = itemsMenuElement.getBoundingClientRect(); + // 加 1px 是为了避免在特定情况下渲染出不应存在的滚动条而做的兼容处理 + const availableHeight = (window.innerHeight - menuRect.top) - (menuRect.height - itemsMenuRect.height) + 1; + itemsMenuElement.style.maxHeight = Math.max(availableHeight, 0) + "px"; } private preventDefault(event: KeyboardEvent) { @@ -146,6 +170,20 @@ export class Menu { this.element.style.zIndex = (++window.siyuan.zIndex).toString(); this.element.classList.remove("fn__none"); setPosition(this.element, options.x - (options.isLeft ? this.element.clientWidth : 0), options.y, options.h, options.w); + this.updateMaxHeight(this.element, this.element.lastElementChild as HTMLElement); + } + + public resetPosition() { + if (this.element.classList.contains("fn__none")) { + return; + } + setPosition(this.element, parseFloat(this.element.style.left), parseFloat(this.element.style.top), 0, 0); // 如果不存在 left 或 top,则得到 NaN + this.updateMaxHeight(this.element, this.element.lastElementChild as HTMLElement); + const subMenuElements = this.element.querySelectorAll(".b3-menu__item--show .b3-menu__submenu") as NodeListOf; + subMenuElements.forEach((subMenuElement) => { + // 可能有多层子菜单,都要重新定位 + this.showSubMenu(subMenuElement); + }); } public fullscreen(position: "bottom" | "all" = "all") { diff --git a/app/src/util/setPosition.ts b/app/src/util/setPosition.ts index e0871fd5f81..8854439d840 100644 --- a/app/src/util/setPosition.ts +++ b/app/src/util/setPosition.ts @@ -1,28 +1,39 @@ import {Constants} from "../constants"; -export const setPosition = (element: HTMLElement, x: number, y: number, targetHeight = 0, targetLeft = 0) => { - element.style.top = y + "px"; - element.style.left = x + "px"; +export const setPosition = (element: HTMLElement, left: number, top: number, targetHeight = 0, targetLeft = 0) => { + const isTopValid = !isNaN(top); // 存在 top 时调整垂直方向位置 + const isLeftValid = !isNaN(left); // 存在 left 时调整水平方向位置 + if (isTopValid) { + element.style.top = top + "px"; + } + if (isLeftValid) { + element.style.left = left + "px"; + } const rect = element.getBoundingClientRect(); - // 上下超出屏幕 - if (rect.bottom > window.innerHeight || rect.top < Constants.SIZE_TOOLBAR_HEIGHT) { - const top = y - rect.height - targetHeight; - if (top > Constants.SIZE_TOOLBAR_HEIGHT && (top + rect.height) < window.innerHeight) { - // 上部 - element.style.top = top + "px"; - } else if (top <= Constants.SIZE_TOOLBAR_HEIGHT) { - // 位置超越到屏幕上方外时,需移动到屏幕顶部。eg:光标在第一个块,然后滚动到上方看不见的位置,按 ctrl+a + + if (isTopValid) { + if (rect.top < Constants.SIZE_TOOLBAR_HEIGHT) { + // 如果元素接触顶栏,向下移 element.style.top = Constants.SIZE_TOOLBAR_HEIGHT + "px"; - } else { - // 依旧展现在下部,只是位置上移 - element.style.top = Math.max(Constants.SIZE_TOOLBAR_HEIGHT, window.innerHeight - rect.height) + "px"; + } else if (rect.bottom > window.innerHeight) { + // 如果元素底部超出窗口(下方空间不够),向上移 + if (top - Constants.SIZE_TOOLBAR_HEIGHT >= rect.height) { + // 如果上方空间足够,向上移 + element.style.top = (top - rect.height - targetHeight) + "px"; + } else { + // 如果上下空间都不够,向上移,但尽量靠底部 + element.style.top = Math.max(Constants.SIZE_TOOLBAR_HEIGHT, window.innerHeight - rect.height) + "px"; + } } } - if (rect.right > window.innerWidth) { - // 展现在左侧 - element.style.left = `${window.innerWidth - rect.width - targetLeft}px`; - } else if (rect.left < 0) { - // 依旧展现在左侧,只是位置右移 - element.style.left = "0"; + + if (isLeftValid) { + if (rect.right > window.innerWidth) { + // 展现在左侧 + element.style.left = window.innerWidth - rect.width - targetLeft + "px"; + } else if (rect.left < 0) { + // 依旧展现在左侧,只是位置右移 + element.style.left = "0"; + } } }; diff --git a/app/src/window/init.ts b/app/src/window/init.ts index 442a24b7df3..7e92d8d06c6 100644 --- a/app/src/window/init.ts +++ b/app/src/window/init.ts @@ -61,6 +61,7 @@ export const init = (app: App) => { resizeTimeout = window.setTimeout(() => { adjustLayout(window.siyuan.layout.centerLayout); resizeTabs(); + window.siyuan.menus.menu.resetPosition(); }, 200); }); };