import { hasClosestByAttribute, hasClosestByClassName, hasTopClosestByClassName, } from "../../protyle/util/hasClosest"; import {closeModel, closePanel} from "./closePanel"; import {popMenu} from "../menu"; import {activeBlur, hideKeyboardToolbar} from "./keyboardToolbar"; import {isIPhone} from "../../protyle/util/compatibility"; import {App} from "../../index"; import {globalTouchEnd, globalTouchStart} from "../../boot/globalEvent/touch"; let clientX: number; let clientY: number; let xDiff: number; let yDiff: number; let time: number; let firstDirection: "toLeft" | "toRight"; let lastClientX: number; // 和起始方向不一致时,记录最后一次的 clientX let scrollBlock: boolean; const popSide = (render = true) => { if (render) { document.getElementById("toolbarFile").dispatchEvent(new CustomEvent("click")); } else { hideKeyboardToolbar(); activeBlur(); document.getElementById("sidebar").style.transform = "translateX(0px)"; } }; export const handleTouchEnd = (event: TouchEvent, app: App) => { if (isIPhone() && globalTouchEnd(event, yDiff, time, app)) { event.stopImmediatePropagation(); event.preventDefault(); return; } const target = event.target as HTMLElement; if (!clientX || !clientY || typeof yDiff === "undefined" || target.tagName === "AUDIO" || hasClosestByClassName(target, "b3-dialog", true) || (window.siyuan.mobile.editor && !window.siyuan.mobile.editor.protyle.toolbar.subElement.classList.contains("fn__none")) || hasClosestByClassName(target, "viewer-container") || hasClosestByClassName(target, "keyboard") || hasClosestByAttribute(target, "id", "commonMenu") ) { return; } if (window.siyuan.mobile.editor) { window.siyuan.mobile.editor.protyle.contentElement.style.overflow = ""; } // 有些事件不经过 touchstart 和 touchmove,因此需设置为 null 不再继续执行 clientX = null; // 有些事件不经过 touchmove if (scrollBlock) { return; } let scrollEnable = false; if (new Date().getTime() - time < 1000) { scrollEnable = true; } else if (Math.abs(xDiff) > window.innerWidth / 3) { scrollEnable = true; } const isXScroll = Math.abs(xDiff) > Math.abs(yDiff); const modelElement = hasClosestByAttribute(target, "id", "model"); if (modelElement) { if (isXScroll && firstDirection === "toRight" && !lastClientX) { closeModel(); } return; } const menuElement = hasClosestByAttribute(target, "id", "menu"); if (menuElement) { if (isXScroll) { if (firstDirection === "toRight") { if (lastClientX) { popMenu(); } else { closePanel(); } } else { if (lastClientX) { closePanel(); } else { popMenu(); } } } else { popMenu(); } return; } const sideElement = hasClosestByAttribute(target, "id", "sidebar"); if (sideElement) { if (isXScroll) { if (firstDirection === "toLeft") { if (lastClientX) { popSide(false); } else { closePanel(); } } else { if (lastClientX) { closePanel(); } else { popSide(false); } } } else { popSide(false); } return; } if (!scrollEnable || !isXScroll) { closePanel(); return; } if (xDiff > 0) { if (lastClientX) { closePanel(); } else { popMenu(); } } else { if (lastClientX) { closePanel(); } else { popSide(); } } }; export const handleTouchStart = (event: TouchEvent) => { if (globalTouchStart(event)) { return; } firstDirection = null; xDiff = undefined; yDiff = undefined; lastClientX = undefined; if (isIPhone() || (event.touches[0].clientX > 8 && event.touches[0].clientX < window.innerWidth - 8)) { clientX = event.touches[0].clientX; clientY = event.touches[0].clientY; time = new Date().getTime(); } else { clientX = null; clientY = null; time = 0; event.stopImmediatePropagation(); } scrollBlock = false }; let previousClientX: number; export const handleTouchMove = (event: TouchEvent) => { const target = event.target as HTMLElement; if (!clientX || !clientY || target.tagName === "AUDIO" || hasClosestByClassName(target, "b3-dialog", true) || (window.siyuan.mobile.editor && !window.siyuan.mobile.editor.protyle.toolbar.subElement.classList.contains("fn__none")) || hasClosestByClassName(target, "keyboard") || hasClosestByClassName(target, "viewer-container") || hasClosestByAttribute(target, "id", "commonMenu") ) { return; } if (getSelection().rangeCount > 0) { // 选中后扩选的情况 const range = getSelection().getRangeAt(0); if (range.toString() !== "" && window.siyuan.mobile.editor.protyle.wysiwyg.element.contains(range.startContainer)) { return; } } xDiff = Math.floor(clientX - event.touches[0].clientX); yDiff = Math.floor(clientY - event.touches[0].clientY); if (!firstDirection) { firstDirection = xDiff > 0 ? "toLeft" : "toRight"; } if (previousClientX) { if (firstDirection === "toRight") { if (previousClientX > event.touches[0].clientX) { lastClientX = event.touches[0].clientX; } else { lastClientX = undefined; } } else if (firstDirection === "toLeft") { if (previousClientX < event.touches[0].clientX) { lastClientX = event.touches[0].clientX; } else { lastClientX = undefined; } } } previousClientX = event.touches[0].clientX; if (Math.abs(xDiff) > Math.abs(yDiff)) { if (hasClosestByAttribute(target, "id", "model", true)) { return; } let scrollElement = hasClosestByAttribute(target, "data-type", "NodeCodeBlock") || hasClosestByAttribute(target, "data-type", "NodeAttributeView") || hasClosestByAttribute(target, "data-type", "NodeMathBlock") || hasClosestByAttribute(target, "data-type", "NodeTable") || hasTopClosestByClassName(target, "list"); if (scrollElement) { if (scrollElement.classList.contains("table")) { scrollElement = scrollElement.firstElementChild as HTMLElement; } else if (scrollElement.classList.contains("code-block")) { scrollElement = scrollElement.firstElementChild.nextElementSibling as HTMLElement; } else if (scrollElement.classList.contains("av")) { scrollElement = hasClosestByClassName(target, "layout-tab-bar") || hasClosestByClassName(target, "av__scroll"); } else if (scrollElement.dataset.type === "NodeMathBlock") { scrollElement = target; while (scrollElement && scrollElement.dataset.type !== "NodeMathBlock") { if (scrollElement.nodeType === 1 && scrollElement.scrollWidth > scrollElement.clientWidth) { break; } scrollElement = scrollElement.parentElement; } } if (scrollElement && ( (xDiff < 0 && scrollElement.scrollLeft > 0) || (xDiff > 0 && scrollElement.clientWidth + scrollElement.scrollLeft < scrollElement.scrollWidth) )) { scrollBlock = true return; } if (scrollBlock) { return; } } const windowWidth = window.innerWidth; const menuElement = hasClosestByAttribute(target, "id", "menu"); if (menuElement) { if (xDiff < 0) { menuElement.style.transform = `translateX(${-xDiff}px)`; transformMask(-xDiff / windowWidth); } else { menuElement.style.transform = "translateX(0px)"; transformMask(0); } return; } const sideElement = hasClosestByAttribute(target, "id", "sidebar"); if (sideElement) { if (xDiff > 0) { sideElement.style.transform = `translateX(${-xDiff}px)`; transformMask(xDiff / windowWidth); } else { sideElement.style.transform = "translateX(0px)"; transformMask(0); } return; } if (firstDirection === "toRight") { document.getElementById("sidebar").style.transform = `translateX(${-xDiff - windowWidth}px)`; transformMask((windowWidth + xDiff) / windowWidth); } else { document.getElementById("menu").style.transform = `translateX(${windowWidth - xDiff}px)`; transformMask((windowWidth - xDiff) / windowWidth); } activeBlur(); hideKeyboardToolbar(); if (window.siyuan.mobile.editor) { window.siyuan.mobile.editor.protyle.contentElement.style.overflow = "hidden"; } } }; const transformMask = (opacity: number) => { const maskElement = document.querySelector(".side-mask") as HTMLElement; maskElement.classList.remove("fn__none"); maskElement.style.opacity = Math.min((1 - opacity), 0.68).toString(); };