diff options
Diffstat (limited to 'js/reveal.js')
-rw-r--r-- | js/reveal.js | 1088 |
1 files changed, 798 insertions, 290 deletions
diff --git a/js/reveal.js b/js/reveal.js index 2fd07a8..01472a6 100644 --- a/js/reveal.js +++ b/js/reveal.js @@ -5,23 +5,21 @@ * * Copyright (C) 2014 Hakim El Hattab, http://hakim.se */ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { +(function( root, factory ) { + if( typeof define === 'function' && define.amd ) { // AMD. Register as an anonymous module. - define(function () { + define( function() { root.Reveal = factory(); return root.Reveal; - }); - } else if (typeof exports === 'object') { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like enviroments that support module.exports, - // like Node. + } ); + } else if( typeof exports === 'object' ) { + // Node. Does not work with strict CommonJS. module.exports = factory(); } else { - // Browser globals + // Browser globals. root.Reveal = factory(); } -}(this, function(){ +}( this, function() { 'use strict'; @@ -62,6 +60,9 @@ // Enable keyboard shortcuts for navigation keyboard: true, + // Optional function that blocks keyboard events when retuning false + keyboardCondition: null, + // Enable the slide overview mode overview: true, @@ -104,6 +105,12 @@ // Opens links in an iframe preview overlay previewLinks: false, + // Exposes the reveal.js API through window.postMessage + postMessage: true, + + // Dispatches all reveal.js events to the parent window through postMessage + postMessageEvents: false, + // Focuses body when page changes visiblity to ensure keyboard shortcuts work focusBodyOnPageVisiblityChange: true, @@ -111,13 +118,13 @@ theme: null, // Transition style - transition: 'default', // default/cube/page/concave/zoom/linear/fade/none + transition: 'default', // none/fade/slide/convex/concave/zoom // Transition speed transitionSpeed: 'default', // default/fast/slow // Transition style for full page slide backgrounds - backgroundTransition: 'default', // default/linear/none + backgroundTransition: 'default', // none/fade/slide/convex/concave/zoom // Parallax background image parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg" @@ -169,12 +176,6 @@ // Delays updates to the URL due to a Chrome thumbnailer bug writeURLTimeout = 0, - // A delay used to activate the overview mode - activateOverviewTimeout = 0, - - // A delay used to deactivate the overview mode - deactivateOverviewTimeout = 0, - // Flags if the interaction event listeners are bound eventsAreBound = false, @@ -260,7 +261,6 @@ } - /** * Loads the dependencies of reveal.js. Dependencies are * defined via the configuration option 'dependencies' @@ -334,6 +334,9 @@ // Make sure we've got all the DOM elements we need setupDOM(); + // Listen to messages posted to this window + setupPostMessage(); + // Resets all vertical slides so that only the first is visible resetVerticalSlides(); @@ -346,6 +349,12 @@ // Update all backgrounds updateBackground( true ); + // Special setup and config is required when printing to PDF + if( isPrintingPDF() ) { + removeEventListeners(); + setupPDF(); + } + // Notify listeners that the presentation is ready but use a 1ms // timeout to ensure it's not fired synchronously after #initialize() setTimeout( function() { @@ -415,6 +424,72 @@ } /** + * Configures the presentation for printing to a static + * PDF. + */ + function setupPDF() { + + var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight ); + + // Dimensions of the PDF pages + var pageWidth = Math.round( slideSize.width * ( 1 + config.margin ) ), + pageHeight = Math.round( slideSize.height * ( 1 + config.margin ) ); + + // Dimensions of slides within the pages + var slideWidth = slideSize.width, + slideHeight = slideSize.height; + + // Let the browser know what page size we want to print + injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0;}' ); + + document.body.classList.add( 'print-pdf' ); + document.body.style.width = pageWidth + 'px'; + document.body.style.height = pageHeight + 'px'; + + // Slide and slide background layout + toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) { + + // Vertical stacks are not centred since their section + // children will be + if( slide.classList.contains( 'stack' ) === false ) { + // Center the slide inside of the page, giving the slide some margin + var left = ( pageWidth - slideWidth ) / 2, + top = ( pageHeight - slideHeight ) / 2; + + var contentHeight = getAbsoluteHeight( slide ); + var numberOfPages = Math.ceil( contentHeight / pageHeight ); + + // Center slides vertically + if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) { + top = Math.max( ( pageHeight - contentHeight ) / 2, 0 ); + } + + // Position the slide inside of the page + slide.style.left = left + 'px'; + slide.style.top = top + 'px'; + slide.style.width = slideWidth + 'px'; + + // TODO Backgrounds need to be multiplied when the slide + // stretches over multiple pages + var background = slide.querySelector( '.slide-background' ); + if( background ) { + background.style.width = pageWidth + 'px'; + background.style.height = ( pageHeight * numberOfPages ) + 'px'; + background.style.top = -top + 'px'; + background.style.left = -left + 'px'; + } + } + + } ); + + // Show all fragments + toArray( document.querySelectorAll( SLIDES_SELECTOR + ' .fragment' ) ).forEach( function( fragment ) { + fragment.classList.add( 'visible' ); + } ); + + } + + /** * Creates an HTML element and returns a reference to it. * If the element already exists the existing instance will * be returned. @@ -441,81 +516,36 @@ */ function createBackgrounds() { - if( isPrintingPDF() ) { - document.body.classList.add( 'print-pdf' ); - } + var printMode = isPrintingPDF(); // Clear prior backgrounds dom.background.innerHTML = ''; dom.background.classList.add( 'no-transition' ); - // Helper method for creating a background element for the - // given slide - function _createBackground( slide, container ) { - - var data = { - background: slide.getAttribute( 'data-background' ), - backgroundSize: slide.getAttribute( 'data-background-size' ), - backgroundImage: slide.getAttribute( 'data-background-image' ), - backgroundColor: slide.getAttribute( 'data-background-color' ), - backgroundRepeat: slide.getAttribute( 'data-background-repeat' ), - backgroundPosition: slide.getAttribute( 'data-background-position' ), - backgroundTransition: slide.getAttribute( 'data-background-transition' ) - }; - - var element = document.createElement( 'div' ); - element.className = 'slide-background'; - - if( data.background ) { - // Auto-wrap image urls in url(...) - if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) { - element.style.backgroundImage = 'url('+ data.background +')'; - } - else { - element.style.background = data.background; - } - } - - if( data.background || data.backgroundColor || data.backgroundImage ) { - element.setAttribute( 'data-background-hash', data.background + data.backgroundSize + data.backgroundImage + data.backgroundColor + data.backgroundRepeat + data.backgroundPosition + data.backgroundTransition ); - } - - // Additional and optional background properties - if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize; - if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")'; - if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor; - if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat; - if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition; - if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition ); - - container.appendChild( element ); - - return element; - - } - // Iterate over all horizontal slides toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) { var backgroundStack; - if( isPrintingPDF() ) { - backgroundStack = _createBackground( slideh, slideh ); + if( printMode ) { + backgroundStack = createBackground( slideh, slideh ); } else { - backgroundStack = _createBackground( slideh, dom.background ); + backgroundStack = createBackground( slideh, dom.background ); } // Iterate over all vertical slides toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) { - if( isPrintingPDF() ) { - _createBackground( slidev, slidev ); + if( printMode ) { + createBackground( slidev, slidev ); } else { - _createBackground( slidev, backgroundStack ); + createBackground( slidev, backgroundStack ); } + backgroundStack.classList.add( 'stack' ); + } ); } ); @@ -545,6 +575,98 @@ } /** + * Creates a background for the given slide. + * + * @param {HTMLElement} slide + * @param {HTMLElement} container The element that the background + * should be appended to + */ + function createBackground( slide, container ) { + + var data = { + background: slide.getAttribute( 'data-background' ), + backgroundSize: slide.getAttribute( 'data-background-size' ), + backgroundImage: slide.getAttribute( 'data-background-image' ), + backgroundVideo: slide.getAttribute( 'data-background-video' ), + backgroundColor: slide.getAttribute( 'data-background-color' ), + backgroundRepeat: slide.getAttribute( 'data-background-repeat' ), + backgroundPosition: slide.getAttribute( 'data-background-position' ), + backgroundTransition: slide.getAttribute( 'data-background-transition' ) + }; + + var element = document.createElement( 'div' ); + + // Carry over custom classes from the slide to the background + element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' ); + + if( data.background ) { + // Auto-wrap image urls in url(...) + if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) { + slide.setAttribute( 'data-background-image', 'url('+ data.background +')' ); + } + else { + element.style.background = data.background; + } + } + + // Create a hash for this combination of background settings. + // This is used to determine when two slide backgrounds are + // the same. + if( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo ) { + element.setAttribute( 'data-background-hash', data.background + + data.backgroundSize + + data.backgroundImage + + data.backgroundVideo + + data.backgroundColor + + data.backgroundRepeat + + data.backgroundPosition + + data.backgroundTransition ); + } + + // Additional and optional background properties + if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize; + if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor; + if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat; + if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition; + if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition ); + + container.appendChild( element ); + + return element; + + } + + /** + * Registers a listener to postMessage events, this makes it + * possible to call all reveal.js API methods from another + * window. For example: + * + * revealWindow.postMessage( JSON.stringify({ + * method: 'slide', + * args: [ 2 ] + * }), '*' ); + */ + function setupPostMessage() { + + if( config.postMessage ) { + window.addEventListener( 'message', function ( event ) { + var data = event.data; + + // Make sure we're dealing with JSON + if( data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) { + data = JSON.parse( data ); + + // Check if the requested method can be found + if( data.method && typeof Reveal[data.method] === 'function' ) { + Reveal[data.method].apply( Reveal, data.args ); + } + } + }, false ); + } + + } + + /** * Applies the configuration settings from the config * object. May be called multiple times. */ @@ -609,7 +731,13 @@ enablePreviewLinks( '[data-preview-link]' ); } - // Auto-slide playback controls + // Remove existing auto-slide controls + if( autoSlidePlayer ) { + autoSlidePlayer.destroy(); + autoSlidePlayer = null; + } + + // Generate auto-slide controls if needed if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) { autoSlidePlayer = new Playback( dom.wrapper, function() { return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 ); @@ -618,9 +746,13 @@ autoSlidePlayer.on( 'click', onAutoSlidePlayerClick ); autoSlidePaused = false; } - else if( autoSlidePlayer ) { - autoSlidePlayer.destroy(); - autoSlidePlayer = null; + + // When fragments are turned off they should be visible + if( config.fragments === false ) { + toArray( dom.slides.querySelectorAll( '.fragment' ) ).forEach( function( element ) { + element.classList.add( 'visible' ); + element.classList.remove( 'current-fragment' ); + } ); } // Load the theme in the config, if it's not already loaded @@ -655,7 +787,14 @@ dom.wrapper.addEventListener( 'touchend', onTouchEnd, false ); // Support pointer-style touch interaction as well - if( window.navigator.msPointerEnabled ) { + if( window.navigator.pointerEnabled ) { + // IE 11 uses un-prefixed version of pointer events + dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false ); + dom.wrapper.addEventListener( 'pointermove', onPointerMove, false ); + dom.wrapper.addEventListener( 'pointerup', onPointerUp, false ); + } + else if( window.navigator.msPointerEnabled ) { + // IE 10 uses prefixed version of pointer events dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false ); dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false ); dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false ); @@ -714,7 +853,14 @@ dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false ); dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false ); - if( window.navigator.msPointerEnabled ) { + // IE11 + if( window.navigator.pointerEnabled ) { + dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false ); + dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false ); + dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false ); + } + // IE10 + else if( window.navigator.msPointerEnabled ) { dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false ); dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false ); dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false ); @@ -757,6 +903,22 @@ } /** + * Utility for deserializing a value. + */ + function deserialize( value ) { + + if( typeof value === 'string' ) { + if( value === 'null' ) return null; + else if( value === 'true' ) return true; + else if( value === 'false' ) return false; + else if( value.match( /^\d+$/ ) ) return parseFloat( value ); + } + + return value; + + } + + /** * Measures the distance in pixels between point a * and point b. * @@ -786,6 +948,23 @@ } /** + * Injects the given CSS styles into the DOM. + */ + function injectStyleSheet( value ) { + + var tag = document.createElement( 'style' ); + tag.type = 'text/css'; + if( tag.styleSheet ) { + tag.styleSheet.cssText = value; + } + else { + tag.appendChild( document.createTextNode( value ) ); + } + document.getElementsByTagName( 'head' )[0].appendChild( tag ); + + } + + /** * Retrieves the height of the given element by looking * at the position and height of its immediate children. */ @@ -800,7 +979,7 @@ if( typeof child.offsetTop === 'number' && child.style ) { // Count # of abs children - if( child.style.position === 'absolute' ) { + if( window.getComputedStyle( child ).position === 'absolute' ) { absoluteChildren += 1; } @@ -822,40 +1001,26 @@ /** * Returns the remaining height within the parent of the - * target element after subtracting the height of all - * siblings. + * target element. * - * remaining height = [parent height] - [ siblings height] + * remaining height = [ configured parent height ] - [ current parent height ] */ function getRemainingHeight( element, height ) { height = height || 0; if( element ) { - var parent = element.parentNode; - var siblings = parent.childNodes; - - // Subtract the height of each sibling - toArray( siblings ).forEach( function( sibling ) { - - if( typeof sibling.offsetHeight === 'number' && sibling !== element ) { - - var styles = window.getComputedStyle( sibling ), - marginTop = parseInt( styles.marginTop, 10 ), - marginBottom = parseInt( styles.marginBottom, 10 ); - - height -= sibling.offsetHeight + marginTop + marginBottom; - - } + var newHeight, oldHeight = element.style.height; - } ); - - var elementStyles = window.getComputedStyle( element ); + // Change the .stretch element height to 0 in order find the height of all + // the other elements + element.style.height = '0px'; + newHeight = height - element.parentNode.offsetHeight; - // Subtract the margins of the target element - height -= parseInt( elementStyles.marginTop, 10 ) + - parseInt( elementStyles.marginBottom, 10 ); + // Restore the old height, just in case + element.style.height = oldHeight + 'px'; + return newHeight; } return height; @@ -900,13 +1065,19 @@ * Dispatches an event of the specified type from the * reveal DOM element. */ - function dispatchEvent( type, properties ) { + function dispatchEvent( type, args ) { - var event = document.createEvent( "HTMLEvents", 1, 2 ); + var event = document.createEvent( 'HTMLEvents', 1, 2 ); event.initEvent( type, true, true ); - extend( event, properties ); + extend( event, args ); dom.wrapper.dispatchEvent( event ); + // If we're in an iframe, post each reveal.js event to the + // parent window. Used by the notes plugin + if( config.postMessageEvents && window.parent !== window.self ) { + window.parent.postMessage( JSON.stringify({ namespace: 'reveal', eventName: type, state: getState() }), '*' ); + } + } /** @@ -915,7 +1086,7 @@ function enableRollingLinks() { if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) { - var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' ); + var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a' ); for( var i = 0, len = anchors.length; i < len; i++ ) { var anchor = anchors[i]; @@ -1045,50 +1216,34 @@ if( dom.wrapper && !isPrintingPDF() ) { - // Available space to scale within - var availableWidth = dom.wrapper.offsetWidth, - availableHeight = dom.wrapper.offsetHeight; - - // Reduce available space by margin - availableWidth -= ( availableHeight * config.margin ); - availableHeight -= ( availableHeight * config.margin ); + var size = getComputedSlideSize(); - // Dimensions of the content - var slideWidth = config.width, - slideHeight = config.height, - slidePadding = 20; // TODO Dig this out of DOM + var slidePadding = 20; // TODO Dig this out of DOM // Layout the contents of the slides layoutSlideContents( config.width, config.height, slidePadding ); - // Slide width may be a percentage of available width - if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) { - slideWidth = parseInt( slideWidth, 10 ) / 100 * availableWidth; - } - - // Slide height may be a percentage of available height - if( typeof slideHeight === 'string' && /%$/.test( slideHeight ) ) { - slideHeight = parseInt( slideHeight, 10 ) / 100 * availableHeight; - } - - dom.slides.style.width = slideWidth + 'px'; - dom.slides.style.height = slideHeight + 'px'; + dom.slides.style.width = size.width + 'px'; + dom.slides.style.height = size.height + 'px'; // Determine scale of content to fit within available space - scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight ); + scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height ); // Respect max/min scale settings scale = Math.max( scale, config.minScale ); scale = Math.min( scale, config.maxScale ); - // Prefer applying scale via zoom since Chrome blurs scaled content - // with nested transforms - if( typeof dom.slides.style.zoom !== 'undefined' && !navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi ) ) { + // Prefer zooming in WebKit so that content remains crisp + if( /webkit/i.test( navigator.userAgent ) && typeof dom.slides.style.zoom !== 'undefined' ) { dom.slides.style.zoom = scale; } // Apply scale transform as a fallback else { - transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' ); + dom.slides.style.left = '50%'; + dom.slides.style.top = '50%'; + dom.slides.style.bottom = 'auto'; + dom.slides.style.right = 'auto'; + transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +')' ); } // Select all slides, vertical and horizontal @@ -1109,7 +1264,7 @@ slide.style.top = 0; } else { - slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + 'px'; + slide.style.top = Math.max( ( ( size.height - getAbsoluteHeight( slide ) ) / 2 ) - slidePadding, 0 ) + 'px'; } } else { @@ -1135,7 +1290,7 @@ toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) { // Determine how much vertical space we can use - var remainingHeight = getRemainingHeight( element, ( height - ( padding * 2 ) ) ); + var remainingHeight = getRemainingHeight( element, height ); // Consider the aspect ratio of media elements if( /(img|video)/gi.test( element.nodeName ) ) { @@ -1158,6 +1313,41 @@ } /** + * Calculates the computed pixel size of our slides. These + * values are based on the width and height configuration + * options. + */ + function getComputedSlideSize( presentationWidth, presentationHeight ) { + + var size = { + // Slide size + width: config.width, + height: config.height, + + // Presentation size + presentationWidth: presentationWidth || dom.wrapper.offsetWidth, + presentationHeight: presentationHeight || dom.wrapper.offsetHeight + }; + + // Reduce available space by margin + size.presentationWidth -= ( size.presentationHeight * config.margin ); + size.presentationHeight -= ( size.presentationHeight * config.margin ); + + // Slide width may be a percentage of available width + if( typeof size.width === 'string' && /%$/.test( size.width ) ) { + size.width = parseInt( size.width, 10 ) / 100 * size.presentationWidth; + } + + // Slide height may be a percentage of available height + if( typeof size.height === 'string' && /%$/.test( size.height ) ) { + size.height = parseInt( size.height, 10 ) / 100 * size.presentationHeight; + } + + return size; + + } + + /** * Stores the vertical index of a stack so that the same * vertical slide can be selected when navigating to and * from the stack. @@ -1216,67 +1406,57 @@ dom.wrapper.classList.add( 'overview' ); dom.wrapper.classList.remove( 'overview-deactivating' ); - clearTimeout( activateOverviewTimeout ); - clearTimeout( deactivateOverviewTimeout ); + var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ); - // Not the pretties solution, but need to let the overview - // class apply first so that slides are measured accurately - // before we can position them - activateOverviewTimeout = setTimeout( function() { + for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) { + var hslide = horizontalSlides[i], + hoffset = config.rtl ? -105 : 105; - var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ); + hslide.setAttribute( 'data-index-h', i ); - for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) { - var hslide = horizontalSlides[i], - hoffset = config.rtl ? -105 : 105; + // Apply CSS transform + transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' ); - hslide.setAttribute( 'data-index-h', i ); + if( hslide.classList.contains( 'stack' ) ) { - // Apply CSS transform - transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' ); + var verticalSlides = hslide.querySelectorAll( 'section' ); - if( hslide.classList.contains( 'stack' ) ) { + for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) { + var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide ); - var verticalSlides = hslide.querySelectorAll( 'section' ); + var vslide = verticalSlides[j]; - for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) { - var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide ); + vslide.setAttribute( 'data-index-h', i ); + vslide.setAttribute( 'data-index-v', j ); - var vslide = verticalSlides[j]; - - vslide.setAttribute( 'data-index-h', i ); - vslide.setAttribute( 'data-index-v', j ); - - // Apply CSS transform - transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' ); - - // Navigate to this slide on click - vslide.addEventListener( 'click', onOverviewSlideClicked, true ); - } - - } - else { + // Apply CSS transform + transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' ); // Navigate to this slide on click - hslide.addEventListener( 'click', onOverviewSlideClicked, true ); - + vslide.addEventListener( 'click', onOverviewSlideClicked, true ); } - } - updateSlidesVisibility(); + } + else { - layout(); + // Navigate to this slide on click + hslide.addEventListener( 'click', onOverviewSlideClicked, true ); - if( !wasActive ) { - // Notify observers of the overview showing - dispatchEvent( 'overviewshown', { - 'indexh': indexh, - 'indexv': indexv, - 'currentSlide': currentSlide - } ); } + } - }, 10 ); + updateSlidesVisibility(); + + layout(); + + if( !wasActive ) { + // Notify observers of the overview showing + dispatchEvent( 'overviewshown', { + 'indexh': indexh, + 'indexv': indexv, + 'currentSlide': currentSlide + } ); + } } @@ -1291,9 +1471,6 @@ // Only proceed if enabled in config if( config.overview ) { - clearTimeout( activateOverviewTimeout ); - clearTimeout( deactivateOverviewTimeout ); - dom.wrapper.classList.remove( 'overview' ); // Temporarily add a class so that transitions can do different things @@ -1301,7 +1478,7 @@ // moving from slide to slide dom.wrapper.classList.add( 'overview-deactivating' ); - deactivateOverviewTimeout = setTimeout( function () { + setTimeout( function () { dom.wrapper.classList.remove( 'overview-deactivating' ); }, 1 ); @@ -1388,7 +1565,7 @@ element.webkitRequestFullscreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || - element.msRequestFullScreen; + element.msRequestFullscreen; if( requestMethod ) { requestMethod.apply( element ); @@ -1432,13 +1609,13 @@ /** * Toggles the paused mode on and off. */ - function togglePause() { + function togglePause( override ) { - if( isPaused() ) { - resume(); + if( typeof override === 'boolean' ) { + override ? pause() : resume(); } else { - pause(); + isPaused() ? resume() : pause(); } } @@ -1453,6 +1630,34 @@ } /** + * Toggles the auto slide mode on and off. + * + * @param {Boolean} override Optional flag which sets the desired state. + * True means autoplay starts, false means it stops. + */ + + function toggleAutoSlide( override ) { + + if( typeof override === 'boolean' ) { + override ? resumeAutoSlide() : pauseAutoSlide(); + } + + else { + autoSlidePaused ? resumeAutoSlide() : pauseAutoSlide(); + } + + } + + /** + * Checks if the auto slide mode is currently on. + */ + function isAutoSliding() { + + return !!( autoSlide && !autoSlidePaused ); + + } + + /** * Steps from the current point in the presentation to the * slide which matches the specified horizontal and vertical * indices. @@ -1696,6 +1901,8 @@ var slides = toArray( document.querySelectorAll( selector ) ), slidesLength = slides.length; + var printMode = isPrintingPDF(); + if( slidesLength ) { // Should the index loop? @@ -1722,37 +1929,47 @@ // http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute element.setAttribute( 'hidden', '' ); + // If this element contains vertical slides + if( element.querySelector( 'section' ) ) { + element.classList.add( 'stack' ); + } + + // If we're printing static slides, all slides are "present" + if( printMode ) { + element.classList.add( 'present' ); + continue; + } + if( i < index ) { // Any element previous to index is given the 'past' class element.classList.add( reverse ? 'future' : 'past' ); - var pastFragments = toArray( element.querySelectorAll( '.fragment' ) ); + if( config.fragments ) { + var pastFragments = toArray( element.querySelectorAll( '.fragment' ) ); - // Show all fragments on prior slides - while( pastFragments.length ) { - var pastFragment = pastFragments.pop(); - pastFragment.classList.add( 'visible' ); - pastFragment.classList.remove( 'current-fragment' ); + // Show all fragments on prior slides + while( pastFragments.length ) { + var pastFragment = pastFragments.pop(); + pastFragment.classList.add( 'visible' ); + pastFragment.classList.remove( 'current-fragment' ); + } } } else if( i > index ) { // Any element subsequent to index is given the 'future' class element.classList.add( reverse ? 'past' : 'future' ); - var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) ); + if( config.fragments ) { + var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) ); - // No fragments in future slides should be visible ahead of time - while( futureFragments.length ) { - var futureFragment = futureFragments.pop(); - futureFragment.classList.remove( 'visible' ); - futureFragment.classList.remove( 'current-fragment' ); + // No fragments in future slides should be visible ahead of time + while( futureFragments.length ) { + var futureFragment = futureFragments.pop(); + futureFragment.classList.remove( 'visible' ); + futureFragment.classList.remove( 'current-fragment' ); + } } } - - // If this element contains vertical slides - if( element.querySelector( 'section' ) ) { - element.classList.add( 'stack' ); - } } // Mark the current slide as present @@ -1801,6 +2018,11 @@ viewDistance = isOverview() ? 6 : 1; } + // Limit view distance on weaker devices + if( isPrintingPDF() ) { + viewDistance = Number.MAX_VALUE; + } + for( var x = 0; x < horizontalSlidesLength; x++ ) { var horizontalSlide = horizontalSlides[x]; @@ -1811,7 +2033,12 @@ distanceX = Math.abs( ( indexh - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0; // Show the horizontal slide if it's within the view distance - horizontalSlide.style.display = distanceX > viewDistance ? 'none' : 'block'; + if( distanceX < viewDistance ) { + showSlide( horizontalSlide ); + } + else { + hideSlide( horizontalSlide ); + } if( verticalSlidesLength ) { @@ -1822,7 +2049,12 @@ distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy ); - verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block'; + if( distanceX + distanceY < viewDistance ) { + showSlide( verticalSlide ); + } + else { + hideSlide( verticalSlide ); + } } } @@ -1840,42 +2072,7 @@ // Update progress if enabled if( config.progress && dom.progress ) { - var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ); - - // The number of past and total slides - var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length; - var pastCount = 0; - - // Step through all slides and count the past ones - mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) { - - var horizontalSlide = horizontalSlides[i]; - var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ); - - for( var j = 0; j < verticalSlides.length; j++ ) { - - // Stop as soon as we arrive at the present - if( verticalSlides[j].classList.contains( 'present' ) ) { - break mainLoop; - } - - pastCount++; - - } - - // Stop as soon as we arrive at the present - if( horizontalSlide.classList.contains( 'present' ) ) { - break; - } - - // Don't count the wrapping section for vertical slides - if( horizontalSlide.classList.contains( 'stack' ) === false ) { - pastCount++; - } - - } - - dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px'; + dom.progressbar.style.width = getProgress() * window.innerWidth + 'px'; } @@ -1969,30 +2166,38 @@ // states of their slides (past/present/future) toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) { + backgroundh.classList.remove( 'past' ); + backgroundh.classList.remove( 'present' ); + backgroundh.classList.remove( 'future' ); + if( h < indexh ) { - backgroundh.className = 'slide-background ' + horizontalPast; + backgroundh.classList.add( horizontalPast ); } else if ( h > indexh ) { - backgroundh.className = 'slide-background ' + horizontalFuture; + backgroundh.classList.add( horizontalFuture ); } else { - backgroundh.className = 'slide-background present'; + backgroundh.classList.add( 'present' ); // Store a reference to the current background element currentBackground = backgroundh; } if( includeAll || h === indexh ) { - toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) { + toArray( backgroundh.querySelectorAll( '.slide-background' ) ).forEach( function( backgroundv, v ) { + + backgroundv.classList.remove( 'past' ); + backgroundv.classList.remove( 'present' ); + backgroundv.classList.remove( 'future' ); if( v < indexv ) { - backgroundv.className = 'slide-background past'; + backgroundv.classList.add( 'past' ); } else if ( v > indexv ) { - backgroundv.className = 'slide-background future'; + backgroundv.classList.add( 'future' ); } else { - backgroundv.className = 'slide-background present'; + backgroundv.classList.add( 'present' ); // Only if this is the present horizontal and vertical slide if( h === indexh ) currentBackground = backgroundv; @@ -2003,9 +2208,22 @@ } ); - // Don't transition between identical backgrounds. This - // prevents unwanted flicker. + // Stop any currently playing video background + if( previousBackground ) { + + var previousVideo = previousBackground.querySelector( 'video' ); + if( previousVideo ) previousVideo.pause(); + + } + if( currentBackground ) { + + // Start video playback + var currentVideo = currentBackground.querySelector( 'video' ); + if( currentVideo ) currentVideo.play(); + + // Don't transition between identical backgrounds. This + // prevents unwanted flicker. var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null; var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' ); if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) { @@ -2013,6 +2231,7 @@ } previousBackground = currentBackground; + } // Allow the first background to apply without transition @@ -2050,7 +2269,7 @@ var slideHeight = dom.background.offsetHeight; var verticalSlideCount = verticalSlides.length; - var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0; + var verticalOffset = verticalSlideCount > 1 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0; dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px'; @@ -2059,6 +2278,87 @@ } /** + * Called when the given slide is within the configured view + * distance. Shows the slide element and loads any content + * that is set to load lazily (data-src). + */ + function showSlide( slide ) { + + // Show the slide element + slide.style.display = 'block'; + + // Media elements with data-src attributes + toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ) ).forEach( function( element ) { + element.setAttribute( 'src', element.getAttribute( 'data-src' ) ); + element.removeAttribute( 'data-src' ); + } ); + + // Media elements with <source> children + toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( media ) { + var sources = 0; + + toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) { + source.setAttribute( 'src', source.getAttribute( 'data-src' ) ); + source.removeAttribute( 'data-src' ); + sources += 1; + } ); + + // If we rewrote sources for this video/audio element, we need + // to manually tell it to load from its new origin + if( sources > 0 ) { + media.load(); + } + } ); + + + // Show the corresponding background element + var indices = getIndices( slide ); + var background = getSlideBackground( indices.h, indices.v ); + background.style.display = 'block'; + + // If the background contains media, load it + if( background.hasAttribute( 'data-loaded' ) === false ) { + background.setAttribute( 'data-loaded', 'true' ); + + var backgroundImage = slide.getAttribute( 'data-background-image' ); + var backgroundVideo = slide.getAttribute( 'data-background-video' ); + + // Images + if( backgroundImage ) { + background.style.backgroundImage = backgroundImage; + } + // Videos + else if ( backgroundVideo ) { + var video = document.createElement( 'video' ); + + // Support comma separated lists of video sources + backgroundVideo.split( ',' ).forEach( function( source ) { + video.innerHTML += '<source src="'+ source +'">'; + } ); + + background.appendChild( video ); + } + } + + } + + /** + * Called when the given slide is moved outside of the + * configured view distance. + */ + function hideSlide( slide ) { + + // Hide the slide element + slide.style.display = 'none'; + + // Hide the corresponding background element + var indices = getIndices( slide ); + var background = getSlideBackground( indices.h, indices.v ); + background.style.display = 'none'; + + } + + /** * Determine what available routes there are for navigation. * * @return {Object} containing four booleans: left/right/up/down @@ -2168,6 +2468,70 @@ } /** + * Returns a value ranging from 0-1 that represents + * how far into the presentation we have navigated. + */ + function getProgress() { + + var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ); + + // The number of past and total slides + var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length; + var pastCount = 0; + + // Step through all slides and count the past ones + mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) { + + var horizontalSlide = horizontalSlides[i]; + var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ); + + for( var j = 0; j < verticalSlides.length; j++ ) { + + // Stop as soon as we arrive at the present + if( verticalSlides[j].classList.contains( 'present' ) ) { + break mainLoop; + } + + pastCount++; + + } + + // Stop as soon as we arrive at the present + if( horizontalSlide.classList.contains( 'present' ) ) { + break; + } + + // Don't count the wrapping section for vertical slides + if( horizontalSlide.classList.contains( 'stack' ) === false ) { + pastCount++; + } + + } + + if( currentSlide ) { + + var allFragments = currentSlide.querySelectorAll( '.fragment' ); + + // If there are fragments in the current slide those should be + // accounted for in the progress. + if( allFragments.length > 0 ) { + var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' ); + + // This value represents how big a portion of the slide progress + // that is made up by its fragments (0-1) + var fragmentWeight = 0.9; + + // Add fragment progress to the past slide count + pastCount += ( visibleFragments.length / allFragments.length ) * fragmentWeight; + } + + } + + return pastCount / ( totalCount - 1 ); + + } + + /** * Checks if this presentation is running inside of the * speaker notes window. */ @@ -2191,8 +2555,16 @@ // If the first bit is invalid and there is a name we can // assume that this is a named link if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) { - // Find the slide with the specified name - var element = document.querySelector( '#' + name ); + var element; + + try { + // Find the slide with the specified name + element = document.querySelector( '#' + name ); + } + catch( e ) { + // If the ID is an invalid selector a harmless SyntaxError + // may be thrown here. + } if( element ) { // Find the position of the named slide and navigate to it @@ -2237,9 +2609,16 @@ else { var url = '/'; + // Attempt to create a named link based on the slide's ID + var id = currentSlide.getAttribute( 'id' ); + if( id ) { + id = id.toLowerCase(); + id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' ); + } + // If the current slide has an ID, use that as a named link - if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) { - url = '/' + currentSlide.getAttribute( 'id' ); + if( currentSlide && typeof id === 'string' && id.length ) { + url = '/' + id; } // Otherwise use the /h/v index else { @@ -2281,6 +2660,9 @@ // Now that we know which the horizontal slide is, get its index h = Math.max( horizontalSlides.indexOf( slideh ), 0 ); + // Assume we're not vertical + v = 0; + // If this is a vertical slide, grab the vertical index if( isVertical ) { v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 ); @@ -2300,6 +2682,100 @@ } /** + * Retrieves the total number of slides in this presentation. + */ + function getTotalSlides() { + + return document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length; + + } + + /** + * Returns the slide element matching the specified index. + */ + function getSlide( x, y ) { + + var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ]; + var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' ); + + if( verticalSlides && verticalSlides.length && typeof y === 'number' ) { + return verticalSlides ? verticalSlides[ y ] : undefined; + } + + return horizontalSlide; + + } + + /** + * Returns the background element for the given slide. + * All slides, even the ones with no background properties + * defined, have a background element so as long as the + * index is valid an element will be returned. + */ + function getSlideBackground( x, y ) { + + // When printing to PDF the slide backgrounds are nested + // inside of the slides + if( isPrintingPDF() ) { + var slide = getSlide( x, y ); + return slide ? slide.querySelector( '.slide-background' ) : undefined; + } + + var horizontalBackground = document.querySelectorAll( '.backgrounds>.slide-background' )[ x ]; + var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' ); + + if( verticalBackgrounds && verticalBackgrounds.length && typeof y === 'number' ) { + return verticalBackgrounds ? verticalBackgrounds[ y ] : undefined; + } + + return horizontalBackground; + + } + + /** + * Retrieves the current state of the presentation as + * an object. This state can then be restored at any + * time. + */ + function getState() { + + var indices = getIndices(); + + return { + indexh: indices.h, + indexv: indices.v, + indexf: indices.f, + paused: isPaused(), + overview: isOverview() + }; + + } + + /** + * Restores the presentation to the given state. + * + * @param {Object} state As generated by getState() + */ + function setState( state ) { + + if( typeof state === 'object' ) { + slide( deserialize( state.indexh ), deserialize( state.indexv ), deserialize( state.indexf ) ); + + var pausedFlag = deserialize( state.paused ), + overviewFlag = deserialize( state.overview ); + + if( typeof pausedFlag === 'boolean' && pausedFlag !== isPaused() ) { + togglePause( pausedFlag ); + } + + if( typeof overviewFlag === 'boolean' && overviewFlag !== isOverview() ) { + toggleOverview( overviewFlag ); + } + } + + } + + /** * Return a sorted fragments list, ordered by an increasing * "data-fragment-index" attribute. * @@ -2433,6 +2909,7 @@ } updateControls(); + updateProgress(); return !!( fragmentsShown.length || fragmentsHidden.length ); @@ -2477,14 +2954,21 @@ if( currentSlide ) { + var currentFragment = currentSlide.querySelector( '.current-fragment' ); + + var fragmentAutoSlide = currentFragment ? currentFragment.getAttribute( 'data-autoslide' ) : null; var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null; var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' ); // Pick value in the following priority order: - // 1. Current slide's data-autoslide - // 2. Parent slide's data-autoslide - // 3. Global autoSlide setting - if( slideAutoSlide ) { + // 1. Current fragment's data-autoslide + // 2. Current slide's data-autoslide + // 3. Parent slide's data-autoslide + // 4. Global autoSlide setting + if( fragmentAutoSlide ) { + autoSlide = parseInt( fragmentAutoSlide, 10 ); + } + else if( slideAutoSlide ) { autoSlide = parseInt( slideAutoSlide, 10 ); } else if( parentAutoSlide ) { @@ -2536,19 +3020,25 @@ function pauseAutoSlide() { - autoSlidePaused = true; - clearTimeout( autoSlideTimeout ); + if( autoSlide && !autoSlidePaused ) { + autoSlidePaused = true; + dispatchEvent( 'autoslidepaused' ); + clearTimeout( autoSlideTimeout ); - if( autoSlidePlayer ) { - autoSlidePlayer.setPlaying( false ); + if( autoSlidePlayer ) { + autoSlidePlayer.setPlaying( false ); + } } } function resumeAutoSlide() { - autoSlidePaused = false; - cueAutoSlide(); + if( autoSlide && autoSlidePaused ) { + autoSlidePaused = false; + dispatchEvent( 'autoslideresumed' ); + cueAutoSlide(); + } } @@ -2665,6 +3155,15 @@ */ function onDocumentKeyDown( event ) { + // If there's a condition specified and it returns false, + // ignore this event + if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) { + return true; + } + + // Remember if auto-sliding was paused so we can toggle it + var autoSlideWasPaused = autoSlidePaused; + onUserInput( event ); // Check if there's a focused element that could be using @@ -2737,10 +3236,12 @@ case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break; // return case 13: isOverview() ? deactivateOverview() : triggered = false; break; - // b, period, Logitech presenter tools "black screen" button - case 66: case 190: case 191: togglePause(); break; + // two-spot, semicolon, b, period, Logitech presenter tools "black screen" button + case 58: case 59: case 66: case 190: case 191: togglePause(); break; // f case 70: enterFullscreen(); break; + // a + case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break; default: triggered = false; } @@ -2895,7 +3396,7 @@ */ function onPointerDown( event ) { - if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) { + if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) { event.touches = [{ clientX: event.clientX, clientY: event.clientY }]; onTouchStart( event ); } @@ -2907,7 +3408,7 @@ */ function onPointerMove( event ) { - if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) { + if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) { event.touches = [{ clientX: event.clientX, clientY: event.clientY }]; onTouchMove( event ); } @@ -2919,7 +3420,7 @@ */ function onPointerUp( event ) { - if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) { + if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) { event.touches = [{ clientX: event.clientX, clientY: event.clientY }]; onTouchEnd( event ); } @@ -3293,28 +3794,35 @@ // Toggles the "black screen" mode on/off togglePause: togglePause, + // Toggles the auto slide mode on/off + toggleAutoSlide: toggleAutoSlide, + // State checks isOverview: isOverview, isPaused: isPaused, + isAutoSliding: isAutoSliding, // Adds or removes all internal event listeners (such as keyboard) addEventListeners: addEventListeners, removeEventListeners: removeEventListeners, + // Facility for persisting and restoring the presentation state + getState: getState, + setState: setState, + + // Presentation progress on range of 0-1 + getProgress: getProgress, + // Returns the indices of the current, or specified, slide getIndices: getIndices, - // Returns the slide at the specified index, y is optional - getSlide: function( x, y ) { - var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ]; - var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' ); + getTotalSlides: getTotalSlides, - if( typeof y !== 'undefined' ) { - return verticalSlides ? verticalSlides[ y ] : undefined; - } + // Returns the slide element at the specified index + getSlide: getSlide, - return horizontalSlide; - }, + // Returns the slide background element at the specified index + getSlideBackground: getSlideBackground, // Returns the previous slide element, may be null getPreviousSlide: function() { @@ -3348,12 +3856,7 @@ for( var i in query ) { var value = query[ i ]; - query[ i ] = unescape( value ); - - if( value === 'null' ) query[ i ] = null; - else if( value === 'true' ) query[ i ] = true; - else if( value === 'false' ) query[ i ] = false; - else if( value.match( /^\d+$/ ) ) query[ i ] = parseFloat( value ); + query[ i ] = deserialize( unescape( value ) ); } return query; @@ -3394,6 +3897,11 @@ if( 'addEventListener' in window ) { ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture ); } + }, + + // Programatically triggers a keyboard event + triggerKey: function( keyCode ) { + onDocumentKeyDown( { keyCode: keyCode } ); } }; |