2022-05-26 15:18:53 +08:00
|
|
|
|
import {getEventName, isCtrl, updateHotkeyTip} from "../protyle/util/compatibility";
|
|
|
|
|
|
import {setPosition} from "../util/setPosition";
|
|
|
|
|
|
import {hasClosestByClassName} from "../protyle/util/hasClosest";
|
2022-07-14 09:53:37 +08:00
|
|
|
|
import {isMobile} from "../util/functions";
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
|
|
export class Menu {
|
|
|
|
|
|
public element: HTMLElement;
|
2023-02-02 18:26:15 +08:00
|
|
|
|
public removeCB: () => void;
|
2022-08-03 09:36:45 +08:00
|
|
|
|
private wheelEvent: string;
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
|
|
constructor() {
|
2022-08-03 09:36:45 +08:00
|
|
|
|
this.wheelEvent = "onwheel" in document.createElement("div") ? "wheel" : "mousewheel";
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
this.element = document.getElementById("commonMenu");
|
2023-03-06 11:04:39 +08:00
|
|
|
|
this.element.addEventListener(isMobile() ? "click" : "mouseover", (event) => {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
const target = event.target as Element;
|
2023-03-06 11:04:39 +08:00
|
|
|
|
if (isMobile()) {
|
|
|
|
|
|
const titleElement = hasClosestByClassName(target, "b3-menu__title");
|
2023-03-08 10:36:33 +08:00
|
|
|
|
if (titleElement || (typeof event.detail === "string" && event.detail === "back")) {
|
2023-03-06 11:04:39 +08:00
|
|
|
|
const lastShowElements = this.element.querySelectorAll(".b3-menu__item--show");
|
|
|
|
|
|
if (lastShowElements.length > 0) {
|
|
|
|
|
|
lastShowElements[lastShowElements.length - 1].classList.remove("b3-menu__item--show");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.remove();
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
const itemElement = hasClosestByClassName(target, "b3-menu__item");
|
|
|
|
|
|
if (!itemElement) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (itemElement.classList.contains("b3-menu__item--readonly")) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2023-01-14 22:38:20 +08:00
|
|
|
|
const subMenuElement = itemElement.querySelector(".b3-menu__submenu") as HTMLElement;
|
2022-05-26 15:18:53 +08:00
|
|
|
|
this.element.querySelectorAll(".b3-menu__item--show").forEach((item) => {
|
2023-01-14 22:38:20 +08:00
|
|
|
|
if (!item.contains(itemElement) && !item.isSameNode(itemElement) && !itemElement.contains(item)) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
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");
|
2023-03-06 11:04:39 +08:00
|
|
|
|
if (!this.element.classList.contains("b3-menu--fullscreen")) {
|
|
|
|
|
|
this.showSubMenu(subMenuElement);
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-14 22:38:20 +08:00
|
|
|
|
public showSubMenu(subMenuElement: HTMLElement) {
|
|
|
|
|
|
const parentRect = subMenuElement.parentElement.getBoundingClientRect();
|
|
|
|
|
|
subMenuElement.style.top = (parentRect.top - 8) + "px";
|
|
|
|
|
|
subMenuElement.style.left = (parentRect.right + 8) + "px";
|
|
|
|
|
|
subMenuElement.style.bottom = "auto";
|
2022-08-15 09:29:34 +08:00
|
|
|
|
const rect = subMenuElement.getBoundingClientRect();
|
2023-01-14 22:38:20 +08:00
|
|
|
|
if (rect.right > window.innerWidth) {
|
|
|
|
|
|
if (parentRect.left - 8 > rect.width) {
|
|
|
|
|
|
subMenuElement.style.left = (parentRect.left - 8 - rect.width) + "px";
|
2022-08-15 09:29:34 +08:00
|
|
|
|
} else {
|
2023-01-14 22:38:20 +08:00
|
|
|
|
subMenuElement.style.left = (window.innerWidth - rect.width) + "px";
|
2022-08-15 09:29:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (rect.bottom > window.innerHeight) {
|
2023-01-14 22:38:20 +08:00
|
|
|
|
subMenuElement.style.top = "auto";
|
|
|
|
|
|
subMenuElement.style.bottom = "8px";
|
2022-08-15 09:29:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-03 09:36:45 +08:00
|
|
|
|
private preventDefault(event: KeyboardEvent) {
|
2023-03-08 10:07:29 +08:00
|
|
|
|
if (!hasClosestByClassName(event.target as Element, "b3-menu") &&
|
|
|
|
|
|
// 移动端底部键盘菜单
|
|
|
|
|
|
!hasClosestByClassName(event.target as Element, "keyboard__bar")) {
|
2022-08-13 23:43:03 +08:00
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
}
|
2022-08-03 09:36:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
public remove() {
|
2023-02-02 18:26:15 +08:00
|
|
|
|
if (window.siyuan.menus.menu.removeCB) {
|
|
|
|
|
|
window.siyuan.menus.menu.removeCB();
|
|
|
|
|
|
window.siyuan.menus.menu.removeCB = undefined;
|
|
|
|
|
|
}
|
2022-08-03 09:36:45 +08:00
|
|
|
|
if (isMobile()) {
|
|
|
|
|
|
window.removeEventListener("touchmove", this.preventDefault, false);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
window.removeEventListener(this.wheelEvent, this.preventDefault, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
this.element.innerHTML = "";
|
|
|
|
|
|
this.element.classList.add("fn__none");
|
2023-03-06 13:14:50 +08:00
|
|
|
|
this.element.classList.remove("b3-menu--list", "b3-menu--fullscreen");
|
2022-11-02 15:53:59 +08:00
|
|
|
|
this.element.removeAttribute("style"); // zIndex
|
2023-01-12 17:50:18 +08:00
|
|
|
|
window.siyuan.menus.menu.element.removeAttribute("data-name"); // 标识再次点击不消失
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public append(element?: HTMLElement) {
|
|
|
|
|
|
if (!element) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.element.append(element);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-14 22:38:20 +08:00
|
|
|
|
public popup(options: { x: number, y: number, h?: number, w?: number }, isLeft = false) {
|
2022-10-07 11:56:04 +08:00
|
|
|
|
if (this.element.innerHTML === "") {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2022-08-03 09:36:45 +08:00
|
|
|
|
if (isMobile()) {
|
|
|
|
|
|
window.addEventListener("touchmove", this.preventDefault, {passive: false});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
window.addEventListener(this.wheelEvent, this.preventDefault, {passive: false});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
this.element.classList.remove("fn__none");
|
2022-11-02 15:41:47 +08:00
|
|
|
|
setPosition(this.element, options.x - (isLeft ? window.siyuan.menus.menu.element.clientWidth : 0), options.y, options.h, options.w);
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
2023-03-06 11:04:39 +08:00
|
|
|
|
|
2023-04-02 11:26:04 +08:00
|
|
|
|
public fullscreen(position: "bottom" | "all" = "all") {
|
2023-03-06 11:04:39 +08:00
|
|
|
|
this.element.classList.add("b3-menu--fullscreen");
|
|
|
|
|
|
this.element.insertAdjacentHTML("afterbegin", `<div class="b3-menu__title">
|
2023-03-06 13:14:50 +08:00
|
|
|
|
<svg class="b3-menu__icon"><use xlink:href="#iconLeft"></use></svg>
|
|
|
|
|
|
<span class="b3-menu__label">${window.siyuan.languages.back}</span>
|
2023-03-16 19:40:45 +08:00
|
|
|
|
</div><button class="b3-menu__separator"></button>`);
|
2023-04-09 09:43:20 +08:00
|
|
|
|
if (isMobile()) {
|
|
|
|
|
|
window.addEventListener("touchmove", this.preventDefault, {passive: false});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
window.addEventListener(this.wheelEvent, this.preventDefault, {passive: false});
|
|
|
|
|
|
}
|
|
|
|
|
|
this.element.classList.remove("fn__none");
|
|
|
|
|
|
this.element.style.left = "0";
|
|
|
|
|
|
|
2023-04-02 12:03:32 +08:00
|
|
|
|
if (position === "bottom") {
|
|
|
|
|
|
this.element.querySelectorAll(".b3-menu__submenu").forEach((item: HTMLElement) => {
|
2023-04-09 09:43:20 +08:00
|
|
|
|
item.style.top = "calc(50vh + 48.5px)";
|
2023-04-02 11:40:53 +08:00
|
|
|
|
});
|
2023-04-09 09:43:20 +08:00
|
|
|
|
this.element.style.top = "50vh";
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.element.style.top = "0";
|
2023-04-02 11:26:04 +08:00
|
|
|
|
}
|
2023-04-02 12:03:32 +08:00
|
|
|
|
this.element.scrollTop = 0;
|
2023-03-06 11:04:39 +08:00
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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) {
|
2022-06-03 16:15:57 +08:00
|
|
|
|
this.element.classList.add("b3-menu__item--selected");
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (options.click) {
|
2022-10-04 10:53:39 +08:00
|
|
|
|
// 需使用 click,否则移动端无法滚动
|
|
|
|
|
|
this.element.addEventListener("click", (event) => {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if (this.element.getAttribute("disabled")) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
options.click(this.element);
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
event.stopImmediatePropagation();
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
window.siyuan.menus.menu.remove();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2022-09-04 22:17:08 +08:00
|
|
|
|
let html = `<span class="b3-menu__label">${options.label}</span>`;
|
|
|
|
|
|
if (options.iconHTML) {
|
|
|
|
|
|
html = options.iconHTML + html;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
html = `<svg class="b3-menu__icon${["HTML (SiYuan)", window.siyuan.languages.template].includes(options.label) ? " ft__error" : ""}" style="${options.icon === "iconClose" ? "height:10px;" : ""}"><use xlink:href="#${options.icon || ""}"></use></svg>${html}`;
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if (options.accelerator) {
|
|
|
|
|
|
html += `<span class="b3-menu__accelerator">${updateHotkeyTip(options.accelerator)}</span>`;
|
|
|
|
|
|
}
|
2022-09-20 22:30:41 +08:00
|
|
|
|
if (options.action) {
|
|
|
|
|
|
html += `<svg class="b3-menu__action"><use xlink:href="#${options.action}"></use></svg>`;
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
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) {
|
2022-10-01 17:34:49 +08:00
|
|
|
|
// 主题 rem craft 需要使用 b3-menu__item--custom 来区分自定义菜单 by 281261361
|
|
|
|
|
|
this.element.classList.add("b3-menu__item--custom");
|
2022-05-26 15:18:53 +08:00
|
|
|
|
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", '<svg class="b3-menu__icon b3-menu__icon--arrow"><use xlink:href="#iconRight"></use></svg>');
|
|
|
|
|
|
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;
|
|
|
|
|
|
};
|
2022-12-01 00:52:39 +08:00
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
export const bindMenuKeydown = (event: KeyboardEvent) => {
|
|
|
|
|
|
if (window.siyuan.menus.menu.element.classList.contains("fn__none") || event.altKey || event.shiftKey || isCtrl(event)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2023-01-14 22:38:20 +08:00
|
|
|
|
if (event.code === "ArrowDown" || event.code === "ArrowUp") {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
const currentElement = window.siyuan.menus.menu.element.querySelector(".b3-menu__item--current");
|
|
|
|
|
|
let actionMenuElement;
|
|
|
|
|
|
if (!currentElement) {
|
2023-01-14 22:38:20 +08:00
|
|
|
|
if (event.code === "ArrowUp") {
|
|
|
|
|
|
actionMenuElement = getActionMenu(window.siyuan.menus.menu.element.lastElementChild, false);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
actionMenuElement = getActionMenu(window.siyuan.menus.menu.element.firstElementChild, true);
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
currentElement.classList.remove("b3-menu__item--current", "b3-menu__item--show");
|
2023-01-14 22:38:20 +08:00
|
|
|
|
if (event.code === "ArrowUp") {
|
|
|
|
|
|
actionMenuElement = getActionMenu(currentElement.previousElementSibling, false);
|
|
|
|
|
|
if (!actionMenuElement) {
|
|
|
|
|
|
actionMenuElement = getActionMenu(currentElement.parentElement.lastElementChild, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
actionMenuElement = getActionMenu(currentElement.nextElementSibling, true);
|
|
|
|
|
|
if (!actionMenuElement) {
|
|
|
|
|
|
actionMenuElement = getActionMenu(currentElement.parentElement.firstElementChild, true);
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (actionMenuElement) {
|
|
|
|
|
|
actionMenuElement.classList.add("b3-menu__item--current");
|
|
|
|
|
|
actionMenuElement.classList.remove("b3-menu__item--show");
|
2023-01-14 22:38:20 +08:00
|
|
|
|
|
|
|
|
|
|
const parentRect = actionMenuElement.parentElement.getBoundingClientRect();
|
|
|
|
|
|
const actionMenuRect = actionMenuElement.getBoundingClientRect();
|
|
|
|
|
|
if (parentRect.top > actionMenuRect.top || parentRect.bottom < actionMenuRect.bottom) {
|
|
|
|
|
|
actionMenuElement.scrollIntoView(parentRect.top > actionMenuRect.top);
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
2023-01-14 22:38:20 +08:00
|
|
|
|
return true;
|
2022-05-26 15:18:53 +08:00
|
|
|
|
} else if (event.code === "ArrowRight") {
|
|
|
|
|
|
const currentElement = window.siyuan.menus.menu.element.querySelector(".b3-menu__item--current");
|
|
|
|
|
|
if (!currentElement) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2023-01-14 22:38:20 +08:00
|
|
|
|
const subMenuElement = currentElement.querySelector(".b3-menu__submenu") as HTMLElement;
|
2022-05-26 15:18:53 +08:00
|
|
|
|
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");
|
|
|
|
|
|
}
|
2023-01-14 22:38:20 +08:00
|
|
|
|
window.siyuan.menus.menu.showSubMenu(subMenuElement);
|
2022-05-26 15:18:53 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|