diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index 8583d890..035dc188 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -36,6 +36,7 @@ import { getBookDirFromLanguage, getBookDirFromWritingMode } from '@/utils/book' import { useUICSS } from '@/hooks/useUICSS'; import { handleKeydown, + handleKeyup, handleMousedown, handleMouseup, handleClick, @@ -224,6 +225,7 @@ const FoliateViewer: React.FC<{ // and more gesture events can be detected in the iframeEventHandlers detail.doc.isEventListenersAdded = true; detail.doc.addEventListener('keydown', handleKeydown.bind(null, bookKey)); + detail.doc.addEventListener('keyup', handleKeyup.bind(null, bookKey)); detail.doc.addEventListener('mousedown', handleMousedown.bind(null, bookKey)); detail.doc.addEventListener('mouseup', handleMouseup.bind(null, bookKey)); detail.doc.addEventListener('click', handleClick.bind(null, bookKey, doubleClickDisabled)); diff --git a/apps/readest-app/src/app/reader/hooks/useBookShortcuts.ts b/apps/readest-app/src/app/reader/hooks/useBookShortcuts.ts index 1c159995..3ae6666d 100644 --- a/apps/readest-app/src/app/reader/hooks/useBookShortcuts.ts +++ b/apps/readest-app/src/app/reader/hooks/useBookShortcuts.ts @@ -1,3 +1,4 @@ +import { useEffect } from 'react'; import { useReaderStore } from '@/store/readerStore'; import { useNotebookStore } from '@/store/notebookStore'; import { isTauriAppPlatform } from '@/services/environment'; @@ -171,20 +172,38 @@ const useBookShortcuts = ({ sideBarBookKey, bookKeys }: UseBookShortcutsProps) = } }; - const zoomIn = () => { + const zoomInFactor = (factor = 1.0) => { if (!sideBarBookKey) return; const viewSettings = getViewSettings(sideBarBookKey)!; - const zoomLevel = viewSettings!.zoomLevel + ZOOM_STEP; + const zoomLevel = viewSettings!.zoomLevel + ZOOM_STEP * factor; applyZoomLevel(Math.min(zoomLevel, MAX_ZOOM_LEVEL)); }; - const zoomOut = () => { + const zoomOutFactor = (factor = 1.0) => { if (!sideBarBookKey) return; const viewSettings = getViewSettings(sideBarBookKey)!; - const zoomLevel = viewSettings!.zoomLevel - ZOOM_STEP; + const zoomLevel = viewSettings!.zoomLevel - ZOOM_STEP * factor; applyZoomLevel(Math.max(zoomLevel, MIN_ZOOM_LEVEL)); }; + const zoomIn = () => { + zoomInFactor(); + }; + + const zoomOut = () => { + zoomOutFactor(); + }; + + const handleZoomIn = (event: CustomEvent) => { + const factor = event.detail?.factor || 1.0; + zoomInFactor(factor); + }; + + const handleZoomOut = (event: CustomEvent) => { + const factor = event.detail?.factor || 1.0; + zoomOutFactor(factor); + }; + const resetZoom = () => { if (!sideBarBookKey) return; applyZoomLevel(100); @@ -202,6 +221,18 @@ const useBookShortcuts = ({ sideBarBookKey, bookKeys }: UseBookShortcutsProps) = eventDispatcher.dispatch('toggle-bookmark', { bookKey: sideBarBookKey }); }; + useEffect(() => { + eventDispatcher.on('zoom-in', handleZoomIn); + eventDispatcher.on('zoom-out', handleZoomOut); + eventDispatcher.on('reset-zoom', resetZoom); + return () => { + eventDispatcher.off('zoom-in', handleZoomIn); + eventDispatcher.off('zoom-out', handleZoomOut); + eventDispatcher.off('reset-zoom', resetZoom); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sideBarBookKey]); + useShortcuts( { onSwitchSideBar: switchSideBar, diff --git a/apps/readest-app/src/app/reader/hooks/useIframeEvents.ts b/apps/readest-app/src/app/reader/hooks/useIframeEvents.ts index 0bbd23bc..68196028 100644 --- a/apps/readest-app/src/app/reader/hooks/useIframeEvents.ts +++ b/apps/readest-app/src/app/reader/hooks/useIframeEvents.ts @@ -3,6 +3,7 @@ import { useReaderStore } from '@/store/readerStore'; import { useBookDataStore } from '@/store/bookDataStore'; import { debounce } from '@/utils/debounce'; import { ScrollSource } from './usePagination'; +import { eventDispatcher } from '@/utils/event'; export const useMouseEvent = ( bookKey: string, @@ -19,7 +20,15 @@ export const useMouseEvent = ( debounceScroll('mouse', -msg.data.deltaY, 0); } if (msg.data.type === 'iframe-wheel') { - debounceFlip(msg); + if (msg.data.ctrlKey) { + if (msg.data.deltaY > 0) { + eventDispatcher.dispatch('zoom-out', { factor: Math.abs(msg.data.deltaY) / 100 }); + } else if (msg.data.deltaY < 0) { + eventDispatcher.dispatch('zoom-in', { factor: Math.abs(msg.data.deltaY) / 100 }); + } + } else { + debounceFlip(msg); + } } else { handlePageFlip(msg); } diff --git a/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts b/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts index b85ca106..29507be6 100644 --- a/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts +++ b/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts @@ -4,7 +4,41 @@ import { eventDispatcher } from '@/utils/event'; let lastClickTime = 0; let longHoldTimeout: ReturnType | null = null; +let keyboardState = { + key: '', + code: '', + ctrlKey: false, + shiftKey: false, + altKey: false, + metaKey: false, +}; + +const getKeyStatus = (event?: MouseEvent | WheelEvent | TouchEvent) => { + if (event && 'ctrlKey' in event) { + return { + key: keyboardState.key, + code: keyboardState.code, + ctrlKey: event.ctrlKey, + shiftKey: event.shiftKey, + altKey: event.altKey, + metaKey: event.metaKey, + }; + } + return { + ...keyboardState, + }; +}; + export const handleKeydown = (bookKey: string, event: KeyboardEvent) => { + keyboardState = { + key: event.key, + code: event.code, + ctrlKey: event.ctrlKey, + shiftKey: event.shiftKey, + altKey: event.altKey, + metaKey: event.metaKey, + }; + if (['Backspace'].includes(event.key)) { event.preventDefault(); } @@ -23,6 +57,31 @@ export const handleKeydown = (bookKey: string, event: KeyboardEvent) => { ); }; +export const handleKeyup = (bookKey: string, event: KeyboardEvent) => { + keyboardState = { + key: '', + code: '', + ctrlKey: event.ctrlKey, + shiftKey: event.shiftKey, + altKey: event.altKey, + metaKey: event.metaKey, + }; + + window.postMessage( + { + type: 'iframe-keyup', + bookKey, + key: event.key, + code: event.code, + ctrlKey: event.ctrlKey, + shiftKey: event.shiftKey, + altKey: event.altKey, + metaKey: event.metaKey, + }, + '*', + ); +}; + export const handleMousedown = (bookKey: string, event: MouseEvent) => { longHoldTimeout = setTimeout(() => { longHoldTimeout = null; @@ -39,6 +98,7 @@ export const handleMousedown = (bookKey: string, event: MouseEvent) => { clientY: event.clientY, offsetX: event.offsetX, offsetY: event.offsetY, + ...getKeyStatus(event), }, '*', ); @@ -60,6 +120,7 @@ export const handleMouseup = (bookKey: string, event: MouseEvent) => { clientY: event.clientY, offsetX: event.offsetX, offsetY: event.offsetY, + ...getKeyStatus(event), }, '*', ); @@ -80,6 +141,7 @@ export const handleWheel = (bookKey: string, event: WheelEvent) => { clientY: event.clientY, offsetX: event.offsetX, offsetY: event.offsetY, + ...getKeyStatus(event), }, '*', ); @@ -104,6 +166,7 @@ export const handleClick = ( clientY: event.clientY, offsetX: event.offsetX, offsetY: event.offsetY, + ...getKeyStatus(event), }, '*', ); @@ -151,6 +214,7 @@ export const handleClick = ( clientY: event.clientY, offsetX: event.offsetX, offsetY: event.offsetY, + ...getKeyStatus(event), }, '*', ); @@ -183,6 +247,7 @@ const handleTouchEv = (bookKey: string, event: TouchEvent, type: string) => { bookKey, timeStamp: Date.now(), targetTouches: touches, + ...getKeyStatus(event), }, '*', ); diff --git a/packages/foliate-js b/packages/foliate-js index 1b601ae5..a97db3e9 160000 --- a/packages/foliate-js +++ b/packages/foliate-js @@ -1 +1 @@ -Subproject commit 1b601ae548ae9b3e04c3c044fc7c6488bb479fbb +Subproject commit a97db3e9cf350ec0bb9ff3eb106d067f9811bf5f