feat(tts): add shortcuts to navigate and play/pause in TTS mode, closes #3620 (#3651)

This commit is contained in:
Huang Xin
2026-03-27 13:39:17 +08:00
committed by GitHub
parent 26fec924fc
commit 5f897f648f
4 changed files with 124 additions and 1 deletions
@@ -0,0 +1,41 @@
import { describe, it, expect } from 'vitest';
// Use dynamic import to avoid module resolution issues in test
const getDefaults = async () => {
const mod = await import('../../helpers/shortcuts');
return mod.loadShortcuts();
};
describe('TTS play/pause shortcut', () => {
it('should have onTTSPlayPause shortcut with space', async () => {
const shortcuts = await getDefaults();
expect(shortcuts.onTTSPlayPause).toEqual([' ']);
});
it('should also have space in onGoRight as fallback', async () => {
const shortcuts = await getDefaults();
expect(shortcuts.onGoRight).toContain(' ');
});
});
describe('TTS navigation shortcuts', () => {
it('should have onTTSGoNextSentence shortcut with ctrl+] and cmd+]', async () => {
const shortcuts = await getDefaults();
expect(shortcuts.onTTSGoNextSentence).toEqual(['ctrl+]', 'cmd+]']);
});
it('should have onTTSGoPreviousSentence shortcut with ctrl+[ and cmd+[', async () => {
const shortcuts = await getDefaults();
expect(shortcuts.onTTSGoPreviousSentence).toEqual(['ctrl+[', 'cmd+[']);
});
it('should have onTTSGoNextParagraph shortcut with ctrl+shift+} and cmd+shift+}', async () => {
const shortcuts = await getDefaults();
expect(shortcuts.onTTSGoNextParagraph).toEqual(['ctrl+shift+}', 'cmd+shift+}']);
});
it('should have onTTSGoPreviousParagraph shortcut with ctrl+shift+{ and cmd+shift+{', async () => {
const shortcuts = await getDefaults();
expect(shortcuts.onTTSGoPreviousParagraph).toEqual(['ctrl+shift+{', 'cmd+shift+{']);
});
});
@@ -230,6 +230,34 @@ const useBookShortcuts = ({ sideBarBookKey, bookKeys }: UseBookShortcutsProps) =
eventDispatcher.dispatch(viewState?.ttsEnabled ? 'tts-stop' : 'tts-speak', { bookKey });
};
const ttsPlayPause = () => {
if (!sideBarBookKey) return false;
const viewState = getViewState(sideBarBookKey);
if (!viewState?.ttsEnabled) return false;
eventDispatcher.dispatch('tts-toggle-play', { bookKey: sideBarBookKey });
return true;
};
const ttsGoNextSentence = () => {
if (!sideBarBookKey) return;
eventDispatcher.dispatch('tts-forward', { bookKey: sideBarBookKey, byMark: true });
};
const ttsGoPreviousSentence = () => {
if (!sideBarBookKey) return;
eventDispatcher.dispatch('tts-backward', { bookKey: sideBarBookKey, byMark: true });
};
const ttsGoNextParagraph = () => {
if (!sideBarBookKey) return;
eventDispatcher.dispatch('tts-forward', { bookKey: sideBarBookKey, byMark: false });
};
const ttsGoPreviousParagraph = () => {
if (!sideBarBookKey) return;
eventDispatcher.dispatch('tts-backward', { bookKey: sideBarBookKey, byMark: false });
};
const toggleBookmark = () => {
if (!sideBarBookKey) return;
eventDispatcher.dispatch('toggle-bookmark', { bookKey: sideBarBookKey });
@@ -280,6 +308,11 @@ const useBookShortcuts = ({ sideBarBookKey, bookKeys }: UseBookShortcutsProps) =
onShowSearchBar: showSearchBar,
onToggleFullscreen: toggleFullscreen,
onToggleTTS: toggleTTS,
onTTSPlayPause: ttsPlayPause,
onTTSGoNextSentence: ttsGoNextSentence,
onTTSGoPreviousSentence: ttsGoPreviousSentence,
onTTSGoNextParagraph: ttsGoNextParagraph,
onTTSGoPreviousParagraph: ttsGoPreviousParagraph,
onReloadPage: reloadPage,
onCloseWindow: closeWindow,
onQuitApp: quitApp,
@@ -59,12 +59,56 @@ export const useTTSControl = ({ bookKey, onRequestHidePanel }: UseTTSControlProp
deinitMediaSession,
} = useTTSMediaSession({ bookKey });
const handleTTSForward = async (event: CustomEvent) => {
const detail = event.detail as { bookKey: string; byMark?: boolean } | undefined;
if (detail?.bookKey !== bookKey) return;
const ttsController = ttsControllerRef.current;
if (ttsController) {
await ttsController.forward(detail?.byMark ?? false);
}
};
const handleTTSBackward = async (event: CustomEvent) => {
const detail = event.detail as { bookKey: string; byMark?: boolean } | undefined;
if (detail?.bookKey !== bookKey) return;
const ttsController = ttsControllerRef.current;
if (ttsController) {
await ttsController.backward(detail?.byMark ?? false);
}
};
const handleTTSTogglePlay = async (event: CustomEvent) => {
const detail = event.detail as { bookKey: string } | undefined;
if (detail?.bookKey !== bookKey) return;
const ttsController = ttsControllerRef.current;
if (!ttsController) return;
if (ttsController.state === 'playing') {
setIsPlaying(false);
setIsPaused(true);
await ttsController.pause();
} else {
setIsPlaying(true);
setIsPaused(false);
if (ttsController.state === 'paused') {
await ttsController.resume();
} else {
await ttsController.start();
}
}
};
useEffect(() => {
eventDispatcher.on('tts-speak', handleTTSSpeak);
eventDispatcher.on('tts-stop', handleTTSStop);
eventDispatcher.on('tts-forward', handleTTSForward);
eventDispatcher.on('tts-backward', handleTTSBackward);
eventDispatcher.on('tts-toggle-play', handleTTSTogglePlay);
return () => {
eventDispatcher.off('tts-speak', handleTTSSpeak);
eventDispatcher.off('tts-stop', handleTTSStop);
eventDispatcher.off('tts-forward', handleTTSForward);
eventDispatcher.off('tts-backward', handleTTSBackward);
eventDispatcher.off('tts-toggle-play', handleTTSTogglePlay);
if (ttsControllerRef.current) {
ttsControllerRef.current.shutdown();
ttsControllerRef.current = null;
+6 -1
View File
@@ -7,6 +7,11 @@ const DEFAULT_SHORTCUTS = {
onToggleSelectMode: ['shift+s'],
onToggleBookmark: ['ctrl+d', 'cmd+d'],
onToggleTTS: ['t'],
onTTSPlayPause: [' '],
onTTSGoNextSentence: ['ctrl+]', 'cmd+]'],
onTTSGoPreviousSentence: ['ctrl+[', 'cmd+['],
onTTSGoNextParagraph: ['ctrl+shift+}', 'cmd+shift+}'],
onTTSGoPreviousParagraph: ['ctrl+shift+{', 'cmd+shift+{'],
onToggleParagraphMode: ['shift+p'],
onHighlightSelection: ['ctrl+h', 'cmd+h'],
onUnderlineSelection: ['ctrl+u', 'cmd+u'],
@@ -19,7 +24,7 @@ const DEFAULT_SHORTCUTS = {
onReadAloudSelection: ['ctrl+r', 'cmd+r'],
onProofreadSelection: ['ctrl+p', 'cmd+p'],
onOpenFontLayoutSettings: ['shift+f', 'ctrl+,', 'cmd+,'],
onOpenCommandPalette: ['ctrl+Shift+p', 'cmd+Shift+p'],
onOpenCommandPalette: ['ctrl+shift+p', 'cmd+shift+p'],
onOpenBooks: ['ctrl+o'],
onReloadPage: ['shift+r'],
onToggleFullscreen: ['F11'],