aboutsummaryrefslogtreecommitdiff
path: root/js/controllers
diff options
context:
space:
mode:
authorHakim El Hattab <hakim.elhattab@gmail.com>2020-03-10 20:28:56 +0100
committerHakim El Hattab <hakim.elhattab@gmail.com>2020-03-10 20:28:56 +0100
commit3d6212378aeaed4ce2f4ffd070b79cecf221977e (patch)
tree2094afa4d5f576cfe8eb4b68959029ad94401d75 /js/controllers
parentddbe06eb7efdaae99fb22cba9844eb6c30b3eb59 (diff)
downloadfosdem-2021-minimalism-presentation-3d6212378aeaed4ce2f4ffd070b79cecf221977e.tar
fosdem-2021-minimalism-presentation-3d6212378aeaed4ce2f4ffd070b79cecf221977e.tar.gz
new keyboard module
Diffstat (limited to 'js/controllers')
-rw-r--r--js/controllers/keyboard.js362
1 files changed, 362 insertions, 0 deletions
diff --git a/js/controllers/keyboard.js b/js/controllers/keyboard.js
new file mode 100644
index 0000000..6658cf9
--- /dev/null
+++ b/js/controllers/keyboard.js
@@ -0,0 +1,362 @@
+import { extend, toArray, enterFullscreen } from '../utils/util.js'
+
+/**
+ *
+ */
+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 );
+
+ }
+
+ refreshSortcuts() {
+
+ // Define our contextual list of keyboard shortcuts
+ if( this.Reveal.getConfig().navigationMode === 'linear' ) {
+ this.shortcuts['&#8594; , &#8595; , SPACE , N , L , J'] = 'Next slide';
+ this.shortcuts['&#8592; , &#8593; , P , H , K'] = 'Previous slide';
+ }
+ else {
+ this.shortcuts['N , SPACE'] = 'Next slide';
+ this.shortcuts['P'] = 'Previous slide';
+ this.shortcuts['&#8592; , H'] = 'Navigate left';
+ this.shortcuts['&#8594; , L'] = 'Navigate right';
+ this.shortcuts['&#8593; , K'] = 'Navigate up';
+ this.shortcuts['&#8595; , J'] = 'Navigate down';
+ }
+
+ this.shortcuts['Home , Shift &#8592;'] = 'First slide';
+ this.shortcuts['End , Shift &#8594;'] = 'Last slide';
+ this.shortcuts['B , .'] = 'Pause';
+ this.shortcuts['F'] = 'Fullscreen';
+ this.shortcuts['ESC, O'] = 'Slide overview';
+
+ }
+
+ bind() {
+
+ document.addEventListener( 'keydown', this.onDocumentKeyDown, false );
+ document.addEventListener( 'keypress', this.onDocumentKeyPress, false );
+
+ }
+
+ 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;
+
+ }
+
+ /**
+ * 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;
+ }
+
+ // 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.contentEditable !== 'inherit';
+ 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 specific modified + keycode combinations
+ let prevSlideShortcut = event.shiftKey && event.keyCode === 32;
+ let firstSlideShortcut = event.shiftKey && keyCode === 37;
+ let lastSlideShortcut = event.shiftKey && keyCode === 39;
+
+ // Prevent all other events when a modifier is pressed
+ let unusedModifier = !prevSlideShortcut && !firstSlideShortcut && !lastSlideShortcut &&
+ ( 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();
+ }
+ // N, PAGE DOWN
+ else if( keyCode === 78 || keyCode === 34 ) {
+ this.Review.next();
+ }
+ // H, LEFT
+ else if( keyCode === 72 || keyCode === 37 ) {
+ if( firstSlideShortcut ) {
+ this.Reveal.slide( 0 );
+ }
+ else if( !this.Reveal.overview.isActive() && useLinearMode ) {
+ this.Reveal.prev();
+ }
+ else {
+ this.Reveal.left();
+ }
+ }
+ // L, RIGHT
+ else if( keyCode === 76 || keyCode === 39 ) {
+ if( lastSlideShortcut ) {
+ this.Reveal.slide( Number.MAX_VALUE );
+ }
+ else if( !this.Reveal.overview.isActive() && useLinearMode ) {
+ this.Review.next();
+ }
+ else {
+ this.Reveal.right();
+ }
+ }
+ // K, UP
+ else if( keyCode === 75 || keyCode === 38 ) {
+ if( !this.Reveal.overview.isActive() && useLinearMode ) {
+ this.Reveal.prev();
+ }
+ else {
+ this.Reveal.up();
+ }
+ }
+ // J, DOWN
+ else if( keyCode === 74 || keyCode === 40 ) {
+ if( !this.Reveal.overview.isActive() && useLinearMode ) {
+ this.Review.next();
+ }
+ else {
+ this.Reveal.down();
+ }
+ }
+ // HOME
+ else if( keyCode === 36 ) {
+ this.Reveal.slide( 0 );
+ }
+ // END
+ else if( keyCode === 35 ) {
+ this.Reveal.slide( Number.MAX_VALUE );
+ }
+ // SPACE
+ else if( keyCode === 32 ) {
+ if( this.Reveal.overview.isActive() ) {
+ this.Reveal.overview.deactivate();
+ }
+ if( event.shiftKey ) {
+ this.Reveal.prev();
+ }
+ else {
+ this.Review.next();
+ }
+ }
+ // 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();
+ }
+ // A
+ else if( keyCode === 65 ) {
+ if ( config.autoSlideStoppable ) {
+ this.Reveal.toggleAutoSlide( autoSlideWasPaused );
+ }
+ }
+ 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();
+
+ }
+
+} \ No newline at end of file