import {getEventName, isCtrl, updateHotkeyTip} from "../protyle/util/compatibility"; import {setPosition} from "../util/setPosition"; import {hasClosestByClassName} from "../protyle/util/hasClosest"; import {isMobile} from "../util/functions"; export class Menu { public element: HTMLElement; private wheelEvent: string; constructor() { this.wheelEvent = "onwheel" in document.createElement("div") ? "wheel" : "mousewheel"; this.element = document.getElementById("commonMenu"); this.element.addEventListener(isMobile() ? getEventName() : "mouseover", (event) => { const target = event.target as Element; const itemElement = hasClosestByClassName(target, "b3-menu__item"); if (!itemElement) { return; } if (itemElement.classList.contains("b3-menu__item--readonly")) { return; } const subMenuElement = itemElement.querySelector(".b3-menu__submenu"); this.element.querySelectorAll(".b3-menu__item--show").forEach((item) => { if (!item.contains(itemElement)) { item.classList.remove("b3-menu__item--show"); } }); this.element.querySelectorAll(".b3-menu__item--current").forEach((item) => { item.classList.remove("b3-menu__item--current"); }); itemElement.classList.add("b3-menu__item--current"); if (!subMenuElement) { return; } itemElement.classList.add("b3-menu__item--show"); this.showSubMenu(subMenuElement); }); } public showSubMenu(subMenuElement: Element) { const rect = subMenuElement.getBoundingClientRect(); let style = ""; const leftPosition = rect.left - this.element.clientWidth - rect.width; if (rect.right > window.innerWidth && ( leftPosition > 0 || Math.abs(leftPosition) < (rect.right - window.innerWidth))) { if (leftPosition >= 0) { style = "left:auto;right:calc(100% + 8px);"; } else { style = `z-index:1;mix-blend-mode: normal;left:-${this.element.style.left};`; } } else if (rect.right > window.innerWidth) { style = `z-index:1;mix-blend-mode: normal;left:${window.innerWidth - rect.width - this.element.offsetLeft}px;`; } if (rect.bottom > window.innerHeight) { style += `top: auto;bottom:-5px;max-height:${Math.min(rect.top, window.innerHeight * 0.4)}px`; } if (style) { subMenuElement.setAttribute("style", style); } } private preventDefault(event: KeyboardEvent) { if (!hasClosestByClassName(event.target as Element, "b3-menu")) { event.preventDefault(); } } public remove() { if (isMobile()) { window.removeEventListener("touchmove", this.preventDefault, false); } else { window.removeEventListener(this.wheelEvent, this.preventDefault, false); } this.element.innerHTML = ""; this.element.removeAttribute("data-filetreeid"); // 用于标记文档数右键所选文档 https://ld246.com/article/1667311960383 this.element.classList.add("fn__none"); this.element.removeAttribute("style") } public append(element?: HTMLElement) { if (!element) { return; } this.element.append(element); } public popup(options: { x: number, y: number, h?: number }, isLeft = false) { if (this.element.innerHTML === "") { return; } if (isMobile()) { window.addEventListener("touchmove", this.preventDefault, {passive: false}); } else { window.addEventListener(this.wheelEvent, this.preventDefault, {passive: false}); } this.element.classList.remove("fn__none"); setPosition(this.element, options.x - (isLeft ? window.siyuan.menus.menu.element.clientWidth : 0), options.y, options.h); } } export class MenuItem { public element: HTMLElement; constructor(options: IMenu) { this.element = document.createElement("button"); if (options.disabled) { this.element.setAttribute("disabled", "disabled"); } if (options.type === "separator") { this.element.classList.add("b3-menu__separator"); return; } this.element.classList.add("b3-menu__item"); if (options.current) { this.element.classList.add("b3-menu__item--selected"); } if (options.click) { // 需使用 click,否则移动端无法滚动 this.element.addEventListener("click", (event) => { if (this.element.getAttribute("disabled")) { return; } options.click(this.element); this.element.parentElement.classList.add("fn__none"); this.element.parentElement.innerHTML = ""; event.preventDefault(); event.stopImmediatePropagation(); event.stopPropagation(); window.siyuan.menus.menu.remove(); }); } let html = `${options.label}`; if (options.iconHTML) { html = options.iconHTML + html; } else { html = `${html}`; } if (options.accelerator) { html += `${updateHotkeyTip(options.accelerator)}`; } if (options.action) { html += ``; } if (options.id) { this.element.setAttribute("data-id", options.id); } if (options.type === "readonly") { this.element.classList.add("b3-menu__item--readonly"); } this.element.innerHTML = html; if (options.bind) { // 主题 rem craft 需要使用 b3-menu__item--custom 来区分自定义菜单 by 281261361 this.element.classList.add("b3-menu__item--custom"); options.bind(this.element); } if (options.submenu) { const submenuElement = document.createElement("div"); submenuElement.classList.add("b3-menu__submenu"); options.submenu.forEach((item) => { submenuElement.append(new MenuItem(item).element); }); this.element.insertAdjacentHTML("beforeend", ''); this.element.append(submenuElement); } } } const getActionMenu = (element: Element, next: boolean) => { let actionMenuElement = element; while (actionMenuElement && (actionMenuElement.classList.contains("b3-menu__separator") || actionMenuElement.classList.contains("b3-menu__item--readonly"))) { if (next) { actionMenuElement = actionMenuElement.nextElementSibling; } else { actionMenuElement = actionMenuElement.previousElementSibling; } } return actionMenuElement; }; export const bindMenuKeydown = (event: KeyboardEvent) => { if (window.siyuan.menus.menu.element.classList.contains("fn__none") || event.altKey || event.shiftKey || isCtrl(event)) { return false; } if (event.code === "ArrowDown") { const currentElement = window.siyuan.menus.menu.element.querySelector(".b3-menu__item--current"); let actionMenuElement; if (!currentElement) { actionMenuElement = getActionMenu(window.siyuan.menus.menu.element.firstElementChild, true); } else { currentElement.classList.remove("b3-menu__item--current", "b3-menu__item--show"); actionMenuElement = getActionMenu(currentElement.nextElementSibling, true); if (!actionMenuElement) { actionMenuElement = getActionMenu(currentElement.parentElement.firstElementChild, true); } } if (actionMenuElement) { actionMenuElement.classList.add("b3-menu__item--current"); actionMenuElement.classList.remove("b3-menu__item--show"); } } else if (event.code === "ArrowUp") { const currentElement = window.siyuan.menus.menu.element.querySelector(".b3-menu__item--current"); let actionMenuElement; if (!currentElement) { actionMenuElement = getActionMenu(window.siyuan.menus.menu.element.lastElementChild, false); } else { currentElement.classList.remove("b3-menu__item--current", "b3-menu__item--show"); actionMenuElement = getActionMenu(currentElement.previousElementSibling, false); if (!actionMenuElement) { actionMenuElement = getActionMenu(currentElement.parentElement.lastElementChild, false); } } if (actionMenuElement) { actionMenuElement.classList.add("b3-menu__item--current"); actionMenuElement.classList.remove("b3-menu__item--show"); } } else if (event.code === "ArrowRight") { const currentElement = window.siyuan.menus.menu.element.querySelector(".b3-menu__item--current"); if (!currentElement) { return true; } const subMenuElement = currentElement.querySelector(".b3-menu__submenu"); if (!subMenuElement) { return true; } currentElement.classList.remove("b3-menu__item--current"); currentElement.classList.add("b3-menu__item--show"); const actionMenuElement = getActionMenu(subMenuElement.firstElementChild, true); if (actionMenuElement) { actionMenuElement.classList.add("b3-menu__item--current"); } const rect = subMenuElement.getBoundingClientRect(); let style = ""; if (rect.right > window.innerWidth && (rect.left - subMenuElement.clientWidth - rect.width > 0 || Math.abs(rect.left - subMenuElement.clientWidth - rect.width) < (rect.right - window.innerWidth))) { style = "left:auto;right:calc(100% + 8px);"; } if (rect.bottom > window.innerHeight) { style += `top: auto;bottom:-5px;max-height:${Math.min(rect.top, window.innerHeight * 0.4)}px`; } if (style) { subMenuElement.setAttribute("style", style); } return true; } else if (event.code === "ArrowLeft") { const currentElement = window.siyuan.menus.menu.element.querySelector(".b3-menu__submenu .b3-menu__item--current"); if (!currentElement) { return true; } currentElement.parentElement.parentElement.classList.remove("b3-menu__item--show"); currentElement.parentElement.parentElement.classList.add("b3-menu__item--current"); currentElement.classList.remove("b3-menu__item--current"); return true; } else if (event.code === "Enter") { const currentElement = window.siyuan.menus.menu.element.querySelector(".b3-menu__item--current"); if (!currentElement) { return false; } else { const textElement = currentElement.querySelector(".b3-text-field") as HTMLInputElement; const checkElement = currentElement.querySelector(".b3-switch") as HTMLInputElement; if (textElement) { textElement.focus(); return true; } else if (checkElement) { checkElement.click(); } else { currentElement.dispatchEvent(new CustomEvent(getEventName())); } window.siyuan.menus.menu.remove(); } return true; } // submenu scroll if (event.code === "ArrowUp" || event.code === "ArrowDown") { const currentMenuElement = window.siyuan.menus.menu.element.querySelector(".b3-menu__item--current") as HTMLElement; const currentParentElement = currentMenuElement.parentElement; if (currentParentElement.classList.contains("b3-menu__submenu")) { if (currentMenuElement.offsetTop + currentMenuElement.clientHeight > currentParentElement.scrollTop + currentParentElement.clientHeight) { currentParentElement.scrollTop = currentMenuElement.offsetTop + currentMenuElement.clientHeight - currentParentElement.clientHeight; } else if (currentMenuElement.offsetTop < currentParentElement.scrollTop) { currentParentElement.scrollTop = currentMenuElement.offsetTop; } } return true; } };