123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- import { enterFullscreen } from '../utils/util.js'
-
- /**
- * Handles all reveal.js keyboard interactions.
- */
- export default class Keyboard {
-
- constructor( Reveal ) {
-
- this.Reveal = Reveal;
-
- // A key:value map of keyboard keys and descriptions of
- // the actions they trigger
- this.shortcuts = {};
-
- // Holds custom key code mappings
- this.bindings = {};
-
- this.onDocumentKeyDown = this.onDocumentKeyDown.bind( this );
- this.onDocumentKeyPress = this.onDocumentKeyPress.bind( this );
-
- }
-
- /**
- * Called when the reveal.js config is updated.
- */
- configure( config, oldConfig ) {
-
- if( config.navigationMode === 'linear' ) {
- this.shortcuts['→ , ↓ , SPACE , N , L , J'] = 'Next slide';
- this.shortcuts['← , ↑ , P , H , K'] = 'Previous slide';
- }
- else {
- this.shortcuts['N , SPACE'] = 'Next slide';
- this.shortcuts['P , Shift SPACE'] = 'Previous slide';
- this.shortcuts['← , H'] = 'Navigate left';
- this.shortcuts['→ , L'] = 'Navigate right';
- this.shortcuts['↑ , K'] = 'Navigate up';
- this.shortcuts['↓ , J'] = 'Navigate down';
- }
-
- this.shortcuts['Alt + ←/↑/→/↓'] = 'Navigate without fragments';
- this.shortcuts['Shift + ←/↑/→/↓'] = 'Jump to first/last slide';
- this.shortcuts['B , .'] = 'Pause';
- this.shortcuts['F'] = 'Fullscreen';
- this.shortcuts['G'] = 'Jump to slide';
- this.shortcuts['ESC, O'] = 'Slide overview';
-
- }
-
- /**
- * Starts listening for keyboard events.
- */
- bind() {
-
- document.addEventListener( 'keydown', this.onDocumentKeyDown, false );
- document.addEventListener( 'keypress', this.onDocumentKeyPress, false );
-
- }
-
- /**
- * Stops listening for keyboard events.
- */
- unbind() {
-
- document.removeEventListener( 'keydown', this.onDocumentKeyDown, false );
- document.removeEventListener( 'keypress', this.onDocumentKeyPress, false );
-
- }
-
- /**
- * Add a custom key binding with optional description to
- * be added to the help screen.
- */
- addKeyBinding( binding, callback ) {
-
- if( typeof binding === 'object' && binding.keyCode ) {
- this.bindings[binding.keyCode] = {
- callback: callback,
- key: binding.key,
- description: binding.description
- };
- }
- else {
- this.bindings[binding] = {
- callback: callback,
- key: null,
- description: null
- };
- }
-
- }
-
- /**
- * Removes the specified custom key binding.
- */
- removeKeyBinding( keyCode ) {
-
- delete this.bindings[keyCode];
-
- }
-
- /**
- * Programmatically triggers a keyboard event
- *
- * @param {int} keyCode
- */
- triggerKey( keyCode ) {
-
- this.onDocumentKeyDown( { keyCode } );
-
- }
-
- /**
- * Registers a new shortcut to include in the help overlay
- *
- * @param {String} key
- * @param {String} value
- */
- registerKeyboardShortcut( key, value ) {
-
- this.shortcuts[key] = value;
-
- }
-
- getShortcuts() {
-
- return this.shortcuts;
-
- }
-
- getBindings() {
-
- return this.bindings;
-
- }
-
- /**
- * Handler for the document level 'keypress' event.
- *
- * @param {object} event
- */
- onDocumentKeyPress( event ) {
-
- // Check if the pressed key is question mark
- if( event.shiftKey && event.charCode === 63 ) {
- this.Reveal.toggleHelp();
- }
-
- }
-
- /**
- * Handler for the document level 'keydown' event.
- *
- * @param {object} event
- */
- onDocumentKeyDown( event ) {
-
- let config = this.Reveal.getConfig();
-
- // If there's a condition specified and it returns false,
- // ignore this event
- if( typeof config.keyboardCondition === 'function' && config.keyboardCondition(event) === false ) {
- return true;
- }
-
- // If keyboardCondition is set, only capture keyboard events
- // for embedded decks when they are focused
- if( config.keyboardCondition === 'focused' && !this.Reveal.isFocused() ) {
- return true;
- }
-
- // Shorthand
- let keyCode = event.keyCode;
-
- // Remember if auto-sliding was paused so we can toggle it
- let autoSlideWasPaused = !this.Reveal.isAutoSliding();
-
- this.Reveal.onUserInput( event );
-
- // Is there a focused element that could be using the keyboard?
- let activeElementIsCE = document.activeElement && document.activeElement.isContentEditable === true;
- let activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
- let activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className);
-
- // Whitelist certain modifiers for slide navigation shortcuts
- let isNavigationKey = [32, 37, 38, 39, 40, 78, 80].indexOf( event.keyCode ) !== -1;
-
- // Prevent all other events when a modifier is pressed
- let unusedModifier = !( isNavigationKey && event.shiftKey || event.altKey ) &&
- ( event.shiftKey || event.altKey || event.ctrlKey || event.metaKey );
-
- // Disregard the event if there's a focused element or a
- // keyboard modifier key is present
- if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || unusedModifier ) return;
-
- // While paused only allow resume keyboard events; 'b', 'v', '.'
- let resumeKeyCodes = [66,86,190,191];
- let key;
-
- // Custom key bindings for togglePause should be able to resume
- if( typeof config.keyboard === 'object' ) {
- for( key in config.keyboard ) {
- if( config.keyboard[key] === 'togglePause' ) {
- resumeKeyCodes.push( parseInt( key, 10 ) );
- }
- }
- }
-
- if( this.Reveal.isPaused() && resumeKeyCodes.indexOf( keyCode ) === -1 ) {
- return false;
- }
-
- // Use linear navigation if we're configured to OR if
- // the presentation is one-dimensional
- let useLinearMode = config.navigationMode === 'linear' || !this.Reveal.hasHorizontalSlides() || !this.Reveal.hasVerticalSlides();
-
- let triggered = false;
-
- // 1. User defined key bindings
- if( typeof config.keyboard === 'object' ) {
-
- for( key in config.keyboard ) {
-
- // Check if this binding matches the pressed key
- if( parseInt( key, 10 ) === keyCode ) {
-
- let value = config.keyboard[ key ];
-
- // Callback function
- if( typeof value === 'function' ) {
- value.apply( null, [ event ] );
- }
- // String shortcuts to reveal.js API
- else if( typeof value === 'string' && typeof this.Reveal[ value ] === 'function' ) {
- this.Reveal[ value ].call();
- }
-
- triggered = true;
-
- }
-
- }
-
- }
-
- // 2. Registered custom key bindings
- if( triggered === false ) {
-
- for( key in this.bindings ) {
-
- // Check if this binding matches the pressed key
- if( parseInt( key, 10 ) === keyCode ) {
-
- let action = this.bindings[ key ].callback;
-
- // Callback function
- if( typeof action === 'function' ) {
- action.apply( null, [ event ] );
- }
- // String shortcuts to reveal.js API
- else if( typeof action === 'string' && typeof this.Reveal[ action ] === 'function' ) {
- this.Reveal[ action ].call();
- }
-
- triggered = true;
- }
- }
- }
-
- // 3. System defined key bindings
- if( triggered === false ) {
-
- // Assume true and try to prove false
- triggered = true;
-
- // P, PAGE UP
- if( keyCode === 80 || keyCode === 33 ) {
- this.Reveal.prev({skipFragments: event.altKey});
- }
- // N, PAGE DOWN
- else if( keyCode === 78 || keyCode === 34 ) {
- this.Reveal.next({skipFragments: event.altKey});
- }
- // H, LEFT
- else if( keyCode === 72 || keyCode === 37 ) {
- if( event.shiftKey ) {
- this.Reveal.slide( 0 );
- }
- else if( !this.Reveal.overview.isActive() && useLinearMode ) {
- this.Reveal.prev({skipFragments: event.altKey});
- }
- else {
- this.Reveal.left({skipFragments: event.altKey});
- }
- }
- // L, RIGHT
- else if( keyCode === 76 || keyCode === 39 ) {
- if( event.shiftKey ) {
- this.Reveal.slide( this.Reveal.getHorizontalSlides().length - 1 );
- }
- else if( !this.Reveal.overview.isActive() && useLinearMode ) {
- this.Reveal.next({skipFragments: event.altKey});
- }
- else {
- this.Reveal.right({skipFragments: event.altKey});
- }
- }
- // K, UP
- else if( keyCode === 75 || keyCode === 38 ) {
- if( event.shiftKey ) {
- this.Reveal.slide( undefined, 0 );
- }
- else if( !this.Reveal.overview.isActive() && useLinearMode ) {
- this.Reveal.prev({skipFragments: event.altKey});
- }
- else {
- this.Reveal.up({skipFragments: event.altKey});
- }
- }
- // J, DOWN
- else if( keyCode === 74 || keyCode === 40 ) {
- if( event.shiftKey ) {
- this.Reveal.slide( undefined, Number.MAX_VALUE );
- }
- else if( !this.Reveal.overview.isActive() && useLinearMode ) {
- this.Reveal.next({skipFragments: event.altKey});
- }
- else {
- this.Reveal.down({skipFragments: event.altKey});
- }
- }
- // HOME
- else if( keyCode === 36 ) {
- this.Reveal.slide( 0 );
- }
- // END
- else if( keyCode === 35 ) {
- this.Reveal.slide( this.Reveal.getHorizontalSlides().length - 1 );
- }
- // SPACE
- else if( keyCode === 32 ) {
- if( this.Reveal.overview.isActive() ) {
- this.Reveal.overview.deactivate();
- }
- if( event.shiftKey ) {
- this.Reveal.prev({skipFragments: event.altKey});
- }
- else {
- this.Reveal.next({skipFragments: event.altKey});
- }
- }
- // TWO-SPOT, SEMICOLON, B, V, PERIOD, LOGITECH PRESENTER TOOLS "BLACK SCREEN" BUTTON
- else if( keyCode === 58 || keyCode === 59 || keyCode === 66 || keyCode === 86 || keyCode === 190 || keyCode === 191 ) {
- this.Reveal.togglePause();
- }
- // F
- else if( keyCode === 70 ) {
- enterFullscreen( config.embedded ? this.Reveal.getViewportElement() : document.documentElement );
- }
- // A
- else if( keyCode === 65 ) {
- if ( config.autoSlideStoppable ) {
- this.Reveal.toggleAutoSlide( autoSlideWasPaused );
- }
- }
- // G
- else if( keyCode === 71 ) {
- if ( config.jumpToSlide ) {
- this.Reveal.toggleJumpToSlide();
- }
- }
- else {
- triggered = false;
- }
-
- }
-
- // If the input resulted in a triggered action we should prevent
- // the browsers default behavior
- if( triggered ) {
- event.preventDefault && event.preventDefault();
- }
- // ESC or O key
- else if( keyCode === 27 || keyCode === 79 ) {
- if( this.Reveal.closeOverlay() === false ) {
- this.Reveal.overview.toggle();
- }
-
- event.preventDefault && event.preventDefault();
- }
-
- // If auto-sliding is enabled we need to cue up
- // another timeout
- this.Reveal.cueAutoSlide();
-
- }
-
- }
|