siyuan/app/src/mobile/util/touch.ts

326 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {hasClosestByAttribute, hasClosestByClassName, hasTopClosestByClassName,} from "../../protyle/util/hasClosest";
import {closeModel, closePanel} from "./closePanel";
import {popMenu} from "../menu";
import {activeBlur} 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 firstXY: "x" | "y";
let lastClientX: number; // 和起始方向不一致时,记录最后一次的 clientX
let scrollBlock: boolean;
let isFirstMove = true;
const popSide = (render = true) => {
if (render) {
document.getElementById("toolbarFile").dispatchEvent(new CustomEvent("click"));
} else {
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;
}
isFirstMove = true;
const target = event.target as HTMLElement;
if (!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) {
closePanel();
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", true);
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 (0 < event.touches.length && ((event.touches[0].target as HTMLElement).tagName === "VIDEO" || (event.touches[0].target as HTMLElement).tagName === "AUDIO")) {
// https://github.com/siyuan-note/siyuan/issues/14569
activeBlur();
return;
}
if (globalTouchStart(event)) {
return;
}
firstDirection = null;
xDiff = undefined;
yDiff = undefined;
lastClientX = undefined;
firstXY = 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();
}
isFirstMove = true;
scrollBlock = false;
};
let previousClientX: number;
const sideMaskElement = document.querySelector(".side-mask") as HTMLElement;
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") || firstXY === "y"
) {
return;
}
// 正在编辑时禁止滑动
if (!document.querySelector("#keyboardToolbar").classList.contains("fn__none")) {
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 (!firstXY) {
if (Math.abs(xDiff) > Math.abs(yDiff)) {
firstXY = "x";
} else {
firstXY = "y";
}
if (firstXY === "x") {
if ((hasClosestByAttribute(target, "id", "menu") && firstDirection === "toLeft") ||
(hasClosestByAttribute(target, "id", "sidebar") && firstDirection === "toRight")) {
firstXY = "y";
yDiff = undefined;
}
}
}
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") ||
hasTopClosestByClassName(target, "protyle-breadcrumb__bar--nowrap");
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.scrollLeft > 0) {
break;
}
scrollElement = scrollElement.parentElement;
}
}
let noScroll = false;
if (scrollElement && scrollElement.scrollLeft === 0) {
scrollElement.scrollLeft = 1;
if (scrollElement.scrollLeft === 0) {
noScroll = true;
} else {
scrollElement.scrollLeft = 0;
}
}
if (!noScroll) {
if (scrollElement && (
(xDiff < 0 && scrollElement.scrollLeft > 0) ||
(xDiff > 0 && scrollElement.clientWidth + scrollElement.scrollLeft < scrollElement.scrollWidth)
)) {
scrollBlock = true;
return;
}
if (scrollBlock) {
return;
}
}
}
if (isFirstMove) {
sideMaskElement.style.zIndex = (++window.siyuan.zIndex).toString();
document.getElementById("sidebar").style.zIndex = (++window.siyuan.zIndex).toString();
document.getElementById("menu").style.zIndex = (++window.siyuan.zIndex).toString();
isFirstMove = false;
}
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(${Math.min(-xDiff - windowWidth, 0)}px)`;
transformMask((windowWidth + xDiff) / windowWidth);
} else {
document.getElementById("menu").style.transform = `translateX(${Math.max(windowWidth - xDiff, 0)}px)`;
transformMask((windowWidth - xDiff) / windowWidth);
}
activeBlur();
if (window.siyuan.mobile.editor) {
window.siyuan.mobile.editor.protyle.contentElement.style.overflow = "hidden";
}
}
};
const transformMask = (opacity: number) => {
sideMaskElement.classList.remove("fn__none");
sideMaskElement.style.opacity = Math.min((1 - opacity), 0.68).toString();
};