const isMobileSafari = (() => { let v = navigator.userAgent.match(/Version\/(?\S+) Mobile\/\S+/); if (v && v.groups.version) { // Safari return true; } return false; })(); export class ContextMenuWidget { constructor() { this.menuItems = []; this.menuItemsUpdated = true; this.initDom(); } initDom() { let menuContainer = document.createElement('div'); menuContainer.id = 'iseai-contextmenu'; menuContainer.className = 'oo-ui-toolGroup-tools oo-ui-popupToolGroup-tools oo-ui-listToolGroup-tools oo-ui-toolGroup-enabled-tools oo-ui-popupToolGroup-active-tools'; menuContainer.style.minWidth = 'unset'; menuContainer.style.display = 'none'; this.menuContainer = menuContainer; let menuCover = document.createElement('div'); menuCover.className = 'isekai-contextmenu-cover'; menuCover.style.display = 'none'; menuCover.addEventListener('click', () => { this.hide(); }); this.menuCover = menuCover; document.body.appendChild(menuContainer); document.body.appendChild(menuCover); } setMenuItem(menuItems) { this.menuItems = menuItems; this.menuItemsUpdated = true; } updateMenuItemDom() { if (this.menuItemsUpdated) { this.menuContainer.innerHTML = ''; this.menuItems.sort((a, b) => { return (b.priority ?? 0) - (a.priority ?? 0); }).forEach((menuItem) => { let menuElem = document.createElement('span'); menuElem.className = 'oo-ui-widget oo-ui-widget-enabled oo-ui-tool'; let menuLink = document.createElement('a'); menuLink.className = 'oo-ui-tool-link'; menuLink.tabIndex = 0; menuLink.role = 'button'; menuLink.addEventListener('click', (e) => { e.preventDefault(); menuItem.onClick?.(); this.hide(); }); let menuLabel = document.createElement('span'); menuLabel.className = 'oo-ui-tool-title'; menuLabel.innerText = menuItem.label; menuLink.appendChild(menuLabel); menuElem.appendChild(menuLink); this.menuContainer.appendChild(menuElem); }); this.menuItemsUpdated = false; } } show(...args) { let x = 0; let y = 0; if (args[0] instanceof MouseEvent) { x = args[0].clientX; y = args[0].clientY; } else if (args[0] instanceof Element) { let rect = args[0].getBoundingClientRect(); x = rect.left; y = rect.top; } else if (args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'number') { x = args[0]; y = args[1]; } if (this.menuItemsUpdated) { this.updateMenuItemDom(); } this.menuContainer.style.display = 'block'; this.menuCover.style.display = 'block'; let menuWidth = this.menuContainer.clientWidth; let menuHeight = this.menuContainer.clientHeight; if (x + menuWidth > window.innerWidth) { x -= menuWidth; } if (y + menuHeight > window.innerHeight) { y -= menuHeight; } this.menuContainer.style.left = x + 'px'; this.menuContainer.style.top = y + 'px'; } hide() { this.menuContainer.style.display = 'none'; this.menuCover.style.display = 'none'; } bindToDom(dom) { dom.addEventListener('contextmenu', (e) => { e.preventDefault(); this.show(e); }); if (isMobileSafari) { // Safari下对长按的特殊处理 let longPressTimer; dom.addEventListener('touchstart', (e) => { document.body.classList.add('isekai-contextmenu-ios-longpress'); longPressTimer = setTimeout(() => { e.preventDefault(); this.show({ x: e.pageX - window.scrollX, y: e.pageY - window.scrollY }); }, 200); }); dom.addEventListener('touchend', function (e) { document.body.classList.remove('isekai-contextmenu-ios-longpress'); if (longPressTimer) { clearInterval(longPressTimer); } }); } } }