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

283 lines
9.8 KiB
TypeScript
Raw Normal View History

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)";
}
2023-04-02 10:42:42 +08:00
};
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")
) {
2022-08-01 10:20:35 +08:00
return;
}
2023-04-12 18:22:54 +08:00
if (window.siyuan.mobile.editor) {
window.siyuan.mobile.editor.protyle.contentElement.style.overflow = "";
}
2023-04-06 19:14:23 +08:00
// 有些事件不经过 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;
}
2023-03-31 10:48:16 +08:00
const isXScroll = Math.abs(xDiff) > Math.abs(yDiff);
const modelElement = hasClosestByAttribute(target, "id", "model");
if (modelElement) {
if (isXScroll && firstDirection === "toRight" && !lastClientX) {
closeModel();
}
return;
}
2023-03-30 18:15:20 +08:00
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;
}
2023-03-30 18:15:20 +08:00
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;
}
2023-03-31 10:48:16 +08:00
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;
}
2023-04-02 22:23:12 +08:00
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 {
2023-03-31 10:48:16 +08:00
lastClientX = undefined;
}
} else if (firstDirection === "toLeft") {
if (previousClientX < event.touches[0].clientX) {
lastClientX = event.touches[0].clientX;
} else {
2023-03-31 10:48:16 +08:00
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")) {
2023-04-06 22:32:33 +08:00
scrollElement = scrollElement.firstElementChild as HTMLElement;
} else if (scrollElement.classList.contains("code-block")) {
2023-04-06 22:32:33 +08:00
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;
2023-03-30 18:15:20 +08:00
const menuElement = hasClosestByAttribute(target, "id", "menu");
if (menuElement) {
if (xDiff < 0) {
2023-04-06 19:14:23 +08:00
menuElement.style.transform = `translateX(${-xDiff}px)`;
transformMask(-xDiff / windowWidth);
} else {
menuElement.style.transform = "translateX(0px)";
transformMask(0);
}
return;
}
2023-03-30 18:15:20 +08:00
const sideElement = hasClosestByAttribute(target, "id", "sidebar");
if (sideElement) {
if (xDiff > 0) {
2023-04-06 19:14:23 +08:00
sideElement.style.transform = `translateX(${-xDiff}px)`;
transformMask(xDiff / windowWidth);
} else {
sideElement.style.transform = "translateX(0px)";
transformMask(0);
}
return;
}
if (firstDirection === "toRight") {
2023-04-06 19:14:23 +08:00
document.getElementById("sidebar").style.transform = `translateX(${-xDiff - windowWidth}px)`;
transformMask((windowWidth + xDiff) / windowWidth);
} else {
2023-04-06 19:14:23 +08:00
document.getElementById("menu").style.transform = `translateX(${windowWidth - xDiff}px)`;
transformMask((windowWidth - xDiff) / windowWidth);
}
activeBlur();
hideKeyboardToolbar();
2023-04-12 18:22:54 +08:00
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();
2023-04-02 10:42:42 +08:00
};