diff --git a/apps/reader/src/components/Reader.tsx b/apps/reader/src/components/Reader.tsx index ae5364a0..52536f65 100644 --- a/apps/reader/src/components/Reader.tsx +++ b/apps/reader/src/components/Reader.tsx @@ -20,7 +20,6 @@ import { navbarState } from '@flow/reader/state' import { db } from '../db' import { handleFiles } from '../file' import { - hasSelection, useBackground, useColorScheme, useDisablePinchZooming, @@ -28,6 +27,7 @@ import { useSync, useTranslation, useTypography, + useTouchEvent, } from '../hooks' import { BookTab, reader, useReaderSnapshot } from '../models' import { isTouchScreen } from '../platform' @@ -336,49 +336,7 @@ function BookPane({ tab, onMouseDown }: BookPaneProps) { useEventListener(iframe, 'keydown', handleKeyDown(tab)) - useEventListener(iframe, 'touchstart', (e) => { - const x0 = e.targetTouches[0]?.clientX ?? 0 - const y0 = e.targetTouches[0]?.clientY ?? 0 - const t0 = Date.now() - - if (!iframe) return - - // When selecting text with long tap, `touchend` is not fired, - // so instead of use `addEventlistener`, we should use `on*` - // to remove the previous listener. - iframe.ontouchend = function handleTouchEnd(e: TouchEvent) { - iframe.ontouchend = undefined - const selection = iframe.getSelection() - if (hasSelection(selection)) return - - const x1 = e.changedTouches[0]?.clientX ?? 0 - const y1 = e.changedTouches[0]?.clientY ?? 0 - const t1 = Date.now() - - const deltaX = x1 - x0 - const deltaY = y1 - y0 - const deltaT = t1 - t0 - - const absX = Math.abs(deltaX) - const absY = Math.abs(deltaY) - - if (absX < 10) return - - if (absY / absX > 2) { - if (deltaT > 100 || absX < 30) { - return - } - } - - if (deltaX > 0) { - tab.prev() - } - - if (deltaX < 0) { - tab.next() - } - } - }) + useTouchEvent({iframe, tab}); useDisablePinchZooming(iframe) diff --git a/apps/reader/src/file.ts b/apps/reader/src/file.ts index f4f73cc7..7117370d 100644 --- a/apps/reader/src/file.ts +++ b/apps/reader/src/file.ts @@ -87,7 +87,7 @@ async function toDataUrl(url: string) { } export async function fetchBook(url: string) { - const filename = /\/([^/]*\.epub)$/i.exec(url)?.[1] ?? '' + const filename = decodeURIComponent(/\/([^/]*\.epub)$/i.exec(url)?.[1] ?? '') const books = await db?.books.toArray() const book = books?.find((b) => b.name === filename) diff --git a/apps/reader/src/hooks/index.ts b/apps/reader/src/hooks/index.ts index a55e52ca..e52bf596 100644 --- a/apps/reader/src/hooks/index.ts +++ b/apps/reader/src/hooks/index.ts @@ -11,3 +11,4 @@ export * from './useMobile' export * from './useTextSelection' export * from './useTranslation' export * from './useTypography' +export * from './useTouchEvent' diff --git a/apps/reader/src/hooks/useTouchEvent.ts b/apps/reader/src/hooks/useTouchEvent.ts new file mode 100644 index 00000000..098305fd --- /dev/null +++ b/apps/reader/src/hooks/useTouchEvent.ts @@ -0,0 +1,75 @@ +import { useEventListener } from '@literal-ui/hooks' +import { useCallback, useRef } from 'react' + +import { AsRef, BookTab } from '../models/reader' + +import { hasSelection } from './useTextSelection' + +export function useTouchEvent(props: { + iframe?: Window & AsRef + tab: BookTab +}) { + const { iframe, tab } = props + const params = useRef({ x: -1, y: -1, t: 0, expired: false }) + + useEventListener(iframe, 'touchstart', (e) => { + const x0 = e.targetTouches[0]?.clientX ?? 0 + const y0 = e.targetTouches[0]?.clientY ?? 0 + const t0 = Date.now() + + params.current = { x: x0, y: y0, t: t0, expired: false } + + if (!iframe) return + + // When selecting text with long tap, `touchend` is not fired, + // so instead of use `addEventlistener`, we should use `on*` + // to remove the previous listener. + iframe.ontouchend = handleTouchEnd + }) + + const handleTouchEnd = useCallback( + function (e: TouchEvent) { + if (!iframe) return + iframe.ontouchend = undefined + console.log('params:', params.current) + const selection = iframe.getSelection() + + if (hasSelection(selection)) return + if (params.current.expired) return + + params.current.expired = true + + const x1 = e.changedTouches[0]?.clientX ?? 0 + const y1 = e.changedTouches[0]?.clientY ?? 0 + const t1 = Date.now() + + const { x, y, t } = params.current + + const deltaX = x1 - x + const deltaY = y1 - y + const deltaT = t1 - t + + const absX = Math.abs(deltaX) + const absY = Math.abs(deltaY) + + if (absX < 10) return + + if (absY / absX > 2) { + if (deltaT > 100 || absX < 30) { + return + } + } + + if (deltaX > 0) { + tab.prev() + } + + if (deltaX < 0) { + tab.next() + } + }, + [tab, iframe], + ) + + useEventListener(iframe, 'touchend', handleTouchEnd) +} diff --git a/apps/reader/src/models/reader.ts b/apps/reader/src/models/reader.ts index b15c68e3..4380335e 100644 --- a/apps/reader/src/models/reader.ts +++ b/apps/reader/src/models/reader.ts @@ -74,7 +74,7 @@ class BaseTab { } // https://github.com/pmndrs/valtio/blob/92f3311f7f1a9fe2a22096cd30f9174b860488ed/src/vanilla.ts#L6 -type AsRef = { $$valtioRef: true } +export type AsRef = { $$valtioRef: true } export class BookTab extends BaseTab { epub?: Book