summaryrefslogtreecommitdiffhomepage
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/reveal.js598
1 files changed, 435 insertions, 163 deletions
diff --git a/js/reveal.js b/js/reveal.js
index c371371..ed9db7b 100644
--- a/js/reveal.js
+++ b/js/reveal.js
@@ -3,7 +3,7 @@
* http://revealjs.com
* MIT licensed
*
- * Copyright (C) 2017 Hakim El Hattab, http://hakim.se
+ * Copyright (C) 2018 Hakim El Hattab, http://hakim.se
*/
(function( root, factory ) {
if( typeof define === 'function' && define.amd ) {
@@ -69,6 +69,10 @@
// Display the page number of the current slide
slideNumber: false,
+ // Use 1 based indexing for # links to match slide number (default is zero
+ // based)
+ hashOneBasedIndex: false,
+
// Determine which displays to show the slide number on
showSlideNumber: 'all',
@@ -84,6 +88,10 @@
// Enable the slide overview mode
overview: true,
+ // Disables the default reveal.js slide layout so that you can use
+ // custom CSS layout
+ disableLayout: false,
+
// Vertical centering of slides
center: true,
@@ -102,6 +110,10 @@
// Turns fragments on and off globally
fragments: true,
+ // Flags whether to include the current fragment in the URL,
+ // so that reloading brings you to the same fragment position
+ fragmentInURL: false,
+
// Flags if the presentation is running in an embedded mode,
// i.e. contained within a limited portion of the screen
embedded: false,
@@ -135,6 +147,11 @@
// Use this method for navigation when auto-sliding (defaults to navigateNext)
autoSlideMethod: null,
+ // Specify the average time in seconds that you think you will spend
+ // presenting each slide. This is used to show a pacing timer in the
+ // speaker view
+ defaultTiming: null,
+
// Enable slide navigation via mouse wheel
mouseWheel: false,
@@ -173,6 +190,12 @@
// Parallax background size
parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
+ // Parallax background repeat
+ parallaxBackgroundRepeat: '', // repeat/repeat-x/repeat-y/no-repeat/initial/inherit
+
+ // Parallax background position
+ parallaxBackgroundPosition: '', // CSS syntax, e.g. "top left"
+
// Amount of pixels to move the parallax background per slide step
parallaxBackgroundHorizontal: null,
parallaxBackgroundVertical: null,
@@ -291,7 +314,10 @@
'B , .': 'Pause',
'F': 'Fullscreen',
'ESC, O': 'Slide overview'
- };
+ },
+
+ // Holds custom key code mappings
+ registeredKeyBindings = {};
/**
* Starts up the presentation if the client is capable.
@@ -393,13 +419,13 @@
}
- /**
- * Loads the dependencies of reveal.js. Dependencies are
- * defined via the configuration option 'dependencies'
- * and will be loaded prior to starting/binding reveal.js.
- * Some dependencies may have an 'async' flag, if so they
- * will load after reveal.js has been started up.
- */
+ /**
+ * Loads the dependencies of reveal.js. Dependencies are
+ * defined via the configuration option 'dependencies'
+ * and will be loaded prior to starting/binding reveal.js.
+ * Some dependencies may have an 'async' flag, if so they
+ * will load after reveal.js has been started up.
+ */
function load() {
var scripts = [],
@@ -417,7 +443,7 @@
}
function loadScript( s ) {
- head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], function() {
+ head.ready( s.src.match( /([\w\d_\-]*)\.?js(\?[\w\d.=&]*)?$|[^\\\/]*$/i )[0], function() {
// Extension may contain callback functions
if( typeof s.callback === 'function' ) {
s.callback.apply( this );
@@ -867,6 +893,8 @@
dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")';
dom.background.style.backgroundSize = config.parallaxBackgroundSize;
+ dom.background.style.backgroundRepeat = config.parallaxBackgroundRepeat;
+ dom.background.style.backgroundPosition = config.parallaxBackgroundPosition;
// Make sure the below properties are set on the element - these properties are
// needed for proper transitions to be set on the element via CSS. To remove
@@ -896,6 +924,57 @@
*/
function createBackground( slide, container ) {
+
+ // Main slide background element
+ var element = document.createElement( 'div' );
+ element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
+
+ // Inner background element that wraps images/videos/iframes
+ var contentElement = document.createElement( 'div' );
+ contentElement.className = 'slide-background-content';
+
+ element.appendChild( contentElement );
+ container.appendChild( element );
+
+ slide.slideBackgroundElement = element;
+ slide.slideBackgroundContentElement = contentElement;
+
+ // Syncs the background to reflect all current background settings
+ syncBackground( slide );
+
+ return element;
+
+ }
+
+ /**
+ * Renders all of the visual properties of a slide background
+ * based on the various background attributes.
+ *
+ * @param {HTMLElement} slide
+ */
+ function syncBackground( slide ) {
+
+ var element = slide.slideBackgroundElement,
+ contentElement = slide.slideBackgroundContentElement;
+
+ // Reset the prior background state in case this is not the
+ // initial sync
+ slide.classList.remove( 'has-dark-background' );
+ slide.classList.remove( 'has-light-background' );
+
+ element.removeAttribute( 'data-loaded' );
+ element.removeAttribute( 'data-background-hash' );
+ element.removeAttribute( 'data-background-size' );
+ element.removeAttribute( 'data-background-transition' );
+ element.style.backgroundColor = '';
+
+ contentElement.style.backgroundSize = '';
+ contentElement.style.backgroundRepeat = '';
+ contentElement.style.backgroundPosition = '';
+ contentElement.style.backgroundImage = '';
+ contentElement.style.opacity = '';
+ contentElement.innerHTML = '';
+
var data = {
background: slide.getAttribute( 'data-background' ),
backgroundSize: slide.getAttribute( 'data-background-size' ),
@@ -905,17 +984,13 @@
backgroundColor: slide.getAttribute( 'data-background-color' ),
backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
backgroundPosition: slide.getAttribute( 'data-background-position' ),
- backgroundTransition: slide.getAttribute( 'data-background-transition' )
+ backgroundTransition: slide.getAttribute( 'data-background-transition' ),
+ backgroundOpacity: slide.getAttribute( 'data-background-opacity' )
};
- 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 ) ) {
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#\s]|$)/gi.test( data.background ) ) {
slide.setAttribute( 'data-background-image', data.background );
}
else {
@@ -935,24 +1010,20 @@
data.backgroundColor +
data.backgroundRepeat +
data.backgroundPosition +
- data.backgroundTransition );
+ data.backgroundTransition +
+ data.backgroundOpacity );
}
// Additional and optional background properties
- if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
if( data.backgroundSize ) element.setAttribute( 'data-background-size', 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 );
-
- // If backgrounds are being recreated, clear old classes
- slide.classList.remove( 'has-dark-background' );
- slide.classList.remove( 'has-light-background' );
-
- slide.slideBackgroundElement = element;
+ // Background image options are set on the content wrapper
+ if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize;
+ if( data.backgroundRepeat ) contentElement.style.backgroundRepeat = data.backgroundRepeat;
+ if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition;
+ if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity;
// If this slide has a background color, add a class that
// signals if it is light or dark. If the slide has no background
@@ -974,8 +1045,6 @@
}
}
- return element;
-
}
/**
@@ -1152,13 +1221,8 @@
window.addEventListener( 'resize', onWindowResize, false );
if( config.touch ) {
- dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
- dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
- dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
-
- // Support pointer-style touch interaction as well
- if( window.navigator.pointerEnabled ) {
- // IE 11 uses un-prefixed version of pointer events
+ if( 'onpointerdown' in window ) {
+ // Use W3C pointer events
dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
@@ -1169,6 +1233,12 @@
dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
}
+ else {
+ // Fall back to touch events
+ dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
+ dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
+ dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
+ }
}
if( config.keyboard ) {
@@ -1231,23 +1301,18 @@
window.removeEventListener( 'hashchange', onWindowHashChange, false );
window.removeEventListener( 'resize', onWindowResize, false );
+ dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
+ dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
+ dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
+
+ dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
+ dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
+ dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
+
dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
- // 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 );
- }
-
if ( config.progress && dom.progress ) {
dom.progress.removeEventListener( 'click', onProgressClicked, false );
}
@@ -1264,6 +1329,38 @@
}
/**
+ * Add a custom key binding with optional description to
+ * be added to the help screen.
+ */
+ function addKeyBinding( binding, callback ) {
+
+ if( typeof binding === 'object' && binding.keyCode ) {
+ registeredKeyBindings[binding.keyCode] = {
+ callback: callback,
+ key: binding.key,
+ description: binding.description
+ };
+ }
+ else {
+ registeredKeyBindings[binding] = {
+ callback: callback,
+ key: null,
+ description: null
+ };
+ }
+
+ }
+
+ /**
+ * Removes the specified custom key binding.
+ */
+ function removeKeyBinding( keyCode ) {
+
+ delete registeredKeyBindings[keyCode];
+
+ }
+
+ /**
* Extend object a with the properties of object b.
* If there's a conflict, object b takes precedence.
*
@@ -1750,6 +1847,13 @@
html += '<tr><td>' + key + '</td><td>' + keyboardShortcuts[ key ] + '</td></tr>';
}
+ // Add custom key bindings that have associated descriptions
+ for( var binding in registeredKeyBindings ) {
+ if( registeredKeyBindings[binding].key && registeredKeyBindings[binding].description ) {
+ html += '<tr><td>' + registeredKeyBindings[binding].key + '</td><td>' + registeredKeyBindings[binding].description + '</td></tr>';
+ }
+ }
+
html += '</table>';
dom.overlay.innerHTML = [
@@ -1794,76 +1898,80 @@
if( dom.wrapper && !isPrintingPDF() ) {
- var size = getComputedSlideSize();
+ if( !config.disableLayout ) {
- // Layout the contents of the slides
- layoutSlideContents( config.width, config.height );
+ var size = getComputedSlideSize();
- dom.slides.style.width = size.width + 'px';
- dom.slides.style.height = size.height + 'px';
+ // Layout the contents of the slides
+ layoutSlideContents( config.width, config.height );
- // Determine scale of content to fit within available space
- scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
+ dom.slides.style.width = size.width + 'px';
+ dom.slides.style.height = size.height + 'px';
- // Respect max/min scale settings
- scale = Math.max( scale, config.minScale );
- scale = Math.min( scale, config.maxScale );
+ // Determine scale of content to fit within available space
+ scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
- // Don't apply any scaling styles if scale is 1
- if( scale === 1 ) {
- dom.slides.style.zoom = '';
- dom.slides.style.left = '';
- dom.slides.style.top = '';
- dom.slides.style.bottom = '';
- dom.slides.style.right = '';
- transformSlides( { layout: '' } );
- }
- else {
- // Prefer zoom for scaling up so that content remains crisp.
- // Don't use zoom to scale down since that can lead to shifts
- // in text layout/line breaks.
- if( scale > 1 && features.zoom ) {
- dom.slides.style.zoom = scale;
+ // Respect max/min scale settings
+ scale = Math.max( scale, config.minScale );
+ scale = Math.min( scale, config.maxScale );
+
+ // Don't apply any scaling styles if scale is 1
+ if( scale === 1 ) {
+ dom.slides.style.zoom = '';
dom.slides.style.left = '';
dom.slides.style.top = '';
dom.slides.style.bottom = '';
dom.slides.style.right = '';
transformSlides( { layout: '' } );
}
- // Apply scale transform as a fallback
else {
- dom.slides.style.zoom = '';
- dom.slides.style.left = '50%';
- dom.slides.style.top = '50%';
- dom.slides.style.bottom = 'auto';
- dom.slides.style.right = 'auto';
- transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } );
+ // Prefer zoom for scaling up so that content remains crisp.
+ // Don't use zoom to scale down since that can lead to shifts
+ // in text layout/line breaks.
+ if( scale > 1 && features.zoom ) {
+ dom.slides.style.zoom = scale;
+ dom.slides.style.left = '';
+ dom.slides.style.top = '';
+ dom.slides.style.bottom = '';
+ dom.slides.style.right = '';
+ transformSlides( { layout: '' } );
+ }
+ // Apply scale transform as a fallback
+ else {
+ dom.slides.style.zoom = '';
+ dom.slides.style.left = '50%';
+ dom.slides.style.top = '50%';
+ dom.slides.style.bottom = 'auto';
+ dom.slides.style.right = 'auto';
+ transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } );
+ }
}
- }
- // Select all slides, vertical and horizontal
- var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
+ // Select all slides, vertical and horizontal
+ var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
- for( var i = 0, len = slides.length; i < len; i++ ) {
- var slide = slides[ i ];
+ for( var i = 0, len = slides.length; i < len; i++ ) {
+ var slide = slides[ i ];
- // Don't bother updating invisible slides
- if( slide.style.display === 'none' ) {
- continue;
- }
+ // Don't bother updating invisible slides
+ if( slide.style.display === 'none' ) {
+ continue;
+ }
- if( config.center || slide.classList.contains( 'center' ) ) {
- // Vertical stacks are not centred since their section
- // children will be
- if( slide.classList.contains( 'stack' ) ) {
- slide.style.top = 0;
+ if( config.center || slide.classList.contains( 'center' ) ) {
+ // Vertical stacks are not centred since their section
+ // children will be
+ if( slide.classList.contains( 'stack' ) ) {
+ slide.style.top = 0;
+ }
+ else {
+ slide.style.top = Math.max( ( size.height - slide.scrollHeight ) / 2, 0 ) + 'px';
+ }
}
else {
- slide.style.top = Math.max( ( size.height - slide.scrollHeight ) / 2, 0 ) + 'px';
+ slide.style.top = '';
}
- }
- else {
- slide.style.top = '';
+
}
}
@@ -2190,6 +2298,41 @@
}
/**
+ * Return a hash URL that will resolve to the current slide location.
+ */
+ function locationHash() {
+
+ var url = '/';
+
+ // Attempt to create a named link based on the slide's ID
+ var id = currentSlide ? currentSlide.getAttribute( 'id' ) : null;
+ if( id ) {
+ id = encodeURIComponent( id );
+ }
+
+ var indexf;
+ if( config.fragmentInURL ) {
+ indexf = getIndices().f;
+ }
+
+ // If the current slide has an ID, use that as a named link,
+ // but we don't support named links with a fragment index
+ if( typeof id === 'string' && id.length && indexf === undefined ) {
+ url = '/' + id;
+ }
+ // Otherwise use the /h/v index
+ else {
+ var hashIndexBase = config.hashOneBasedIndex ? 1 : 0;
+ if( indexh > 0 || indexv > 0 || indexf !== undefined ) url += indexh + hashIndexBase;
+ if( indexv > 0 || indexf !== undefined ) url += '/' + (indexv + hashIndexBase );
+ if( indexf !== undefined ) url += '/' + indexf;
+ }
+
+ return url;
+
+ }
+
+ /**
* Checks if the current or specified slide is vertical
* (nested within another slide).
*
@@ -2413,16 +2556,7 @@
// Dispatch an event if the slide changed
var slideChanged = ( indexh !== indexhBefore || indexv !== indexvBefore );
- if( slideChanged ) {
- dispatchEvent( 'slidechanged', {
- 'indexh': indexh,
- 'indexv': indexv,
- 'previousSlide': previousSlide,
- 'currentSlide': currentSlide,
- 'origin': o
- } );
- }
- else {
+ if (!slideChanged) {
// Ensure that the previous slide is never the same as the current
previousSlide = null;
}
@@ -2430,7 +2564,7 @@
// Solves an edge case where the previous slide maintains the
// 'present' class when navigating between adjacent vertical
// stacks
- if( previousSlide ) {
+ if( previousSlide && previousSlide !== currentSlide ) {
previousSlide.classList.remove( 'present' );
previousSlide.setAttribute( 'aria-hidden', 'true' );
@@ -2450,6 +2584,16 @@
}
}
+ if( slideChanged ) {
+ dispatchEvent( 'slidechanged', {
+ 'indexh': indexh,
+ 'indexv': indexv,
+ 'previousSlide': previousSlide,
+ 'currentSlide': currentSlide,
+ 'origin': o
+ } );
+ }
+
// Handle embedded content
if( slideChanged || !previousSlide ) {
stopEmbeddedContent( previousSlide );
@@ -2526,6 +2670,41 @@
}
/**
+ * Updates reveal.js to keep in sync with new slide attributes. For
+ * example, if you add a new `data-background-image` you can call
+ * this to have reveal.js render the new background image.
+ *
+ * Similar to #sync() but more efficient when you only need to
+ * refresh a specific slide.
+ *
+ * @param {HTMLElement} slide
+ */
+ function syncSlide( slide ) {
+
+ syncBackground( slide );
+ syncFragments( slide );
+
+ updateBackground();
+ updateNotes();
+
+ loadSlide( slide );
+
+ }
+
+ /**
+ * Formats the fragments on the given slide so that they have
+ * valid indices. Call this if fragments are changed in the DOM
+ * after reveal.js has already initialized.
+ *
+ * @param {HTMLElement} slide
+ */
+ function syncFragments( slide ) {
+
+ sortFragments( slide.querySelectorAll( '.fragment' ) );
+
+ }
+
+ /**
* Resets all vertical slides so that only the first
* is visible.
*/
@@ -2853,6 +3032,7 @@
}
+
/**
* Updates the slide number div to reflect the current slide.
*
@@ -2875,6 +3055,12 @@
format = config.slideNumber;
}
+ // If there are ONLY vertical slides in this deck, always use
+ // a flattened slide number
+ if( !/c/.test( format ) && dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ).length === 1 ) {
+ format = 'c';
+ }
+
switch( format ) {
case 'c':
value.push( getSlidePastCount() + 1 );
@@ -2907,13 +3093,18 @@
*/
function formatSlideNumber( a, delimiter, b ) {
+ var url = '#' + locationHash();
if( typeof b === 'number' && !isNaN( b ) ) {
- return '<span class="slide-number-a">'+ a +'</span>' +
+ return '<a href="' + url + '">' +
+ '<span class="slide-number-a">'+ a +'</span>' +
'<span class="slide-number-delimiter">'+ delimiter +'</span>' +
- '<span class="slide-number-b">'+ b +'</span>';
+ '<span class="slide-number-b">'+ b +'</span>' +
+ '</a>';
}
else {
- return '<span class="slide-number-a">'+ a +'</span>';
+ return '<a href="' + url + '">' +
+ '<span class="slide-number-a">'+ a +'</span>' +
+ '</a>';
}
}
@@ -3064,13 +3255,18 @@
startEmbeddedContent( currentBackground );
- var backgroundImageURL = currentBackground.style.backgroundImage || '';
+ var currentBackgroundContent = currentBackground.querySelector( '.slide-background-content' );
+ if( currentBackgroundContent ) {
+
+ var backgroundImageURL = currentBackgroundContent.style.backgroundImage || '';
+
+ // Restart GIFs (doesn't work in Firefox)
+ if( /\.gif/i.test( backgroundImageURL ) ) {
+ currentBackgroundContent.style.backgroundImage = '';
+ window.getComputedStyle( currentBackgroundContent ).opacity;
+ currentBackgroundContent.style.backgroundImage = backgroundImageURL;
+ }
- // Restart GIFs (doesn't work in Firefox)
- if( /\.gif/i.test( backgroundImageURL ) ) {
- currentBackground.style.backgroundImage = '';
- window.getComputedStyle( currentBackground ).opacity;
- currentBackground.style.backgroundImage = backgroundImageURL;
}
// Don't transition between identical backgrounds. This
@@ -3202,11 +3398,12 @@
// Show the corresponding background element
- var indices = getIndices( slide );
- var background = getSlideBackground( indices.h, indices.v );
+ var background = slide.slideBackgroundElement;
if( background ) {
background.style.display = 'block';
+ var backgroundContent = slide.slideBackgroundContentElement;
+
// If the background contains media, load it
if( background.hasAttribute( 'data-loaded' ) === false ) {
background.setAttribute( 'data-loaded', 'true' );
@@ -3219,7 +3416,7 @@
// Images
if( backgroundImage ) {
- background.style.backgroundImage = 'url('+ backgroundImage +')';
+ backgroundContent.style.backgroundImage = 'url('+ encodeURI( backgroundImage ) +')';
}
// Videos
else if ( backgroundVideo && !isSpeakerNotes() ) {
@@ -3247,7 +3444,7 @@
video.innerHTML += '<source src="'+ source +'">';
} );
- background.appendChild( video );
+ backgroundContent.appendChild( video );
}
// Iframes
else if( backgroundIframe && options.excludeIframes !== true ) {
@@ -3270,7 +3467,7 @@
iframe.style.maxHeight = '100%';
iframe.style.maxWidth = '100%';
- background.appendChild( iframe );
+ backgroundContent.appendChild( iframe );
}
}
@@ -3290,8 +3487,7 @@
slide.style.display = 'none';
// Hide the corresponding background element
- var indices = getIndices( slide );
- var background = getSlideBackground( indices.h, indices.v );
+ var background = getSlideBackground( slide );
if( background ) {
background.style.display = 'none';
}
@@ -3321,13 +3517,27 @@
verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
var routes = {
- left: indexh > 0 || config.loop,
- right: indexh < horizontalSlides.length - 1 || config.loop,
+ left: indexh > 0,
+ right: indexh < horizontalSlides.length - 1,
up: indexv > 0,
down: indexv < verticalSlides.length - 1
};
- // reverse horizontal controls for rtl
+ // Looped presentations can always be navigated as long as
+ // there are slides available
+ if( config.loop ) {
+ if( horizontalSlides.length > 1 ) {
+ routes.left = true;
+ routes.right = true;
+ }
+
+ if( verticalSlides.length > 1 ) {
+ routes.up = true;
+ routes.down = true;
+ }
+ }
+
+ // Reverse horizontal controls for rtl
if( config.rtl ) {
var left = routes.left;
routes.left = routes.right;
@@ -3426,9 +3636,16 @@
if( autoplay && typeof el.play === 'function' ) {
+ // If the media is ready, start playback
if( el.readyState > 1 ) {
startEmbeddedMedia( { target: el } );
}
+ // Mobile devices never fire a loaded event so instead
+ // of waiting, we initiate playback
+ else if( isMobileDevice ) {
+ el.play();
+ }
+ // If the media isn't loaded, wait before playing
else {
el.removeEventListener( 'loadeddata', startEmbeddedMedia ); // remove first to avoid dupes
el.addEventListener( 'loadeddata', startEmbeddedMedia );
@@ -3693,12 +3910,15 @@
var element;
// Ensure the named link is a valid HTML ID attribute
- if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {
- // Find the slide with the specified ID
- element = document.getElementById( name );
+ try {
+ element = document.getElementById( decodeURIComponent( name ) );
}
+ catch ( error ) { }
- if( element ) {
+ // Ensure that we're not already on a slide with the same name
+ var isSameNameAsCurrentSlide = currentSlide ? currentSlide.getAttribute( 'id' ) === name : false;
+
+ if( element && !isSameNameAsCurrentSlide ) {
// Find the position of the named slide and navigate to it
var indices = Reveal.getIndices( element );
slide( indices.h, indices.v );
@@ -3709,12 +3929,22 @@
}
}
else {
+ var hashIndexBase = config.hashOneBasedIndex ? 1 : 0;
+
// Read the index components of the hash
- var h = parseInt( bits[0], 10 ) || 0,
- v = parseInt( bits[1], 10 ) || 0;
+ var h = ( parseInt( bits[0], 10 ) - hashIndexBase ) || 0,
+ v = ( parseInt( bits[1], 10 ) - hashIndexBase ) || 0,
+ f;
+
+ if( config.fragmentInURL ) {
+ f = parseInt( bits[2], 10 );
+ if( isNaN( f ) ) {
+ f = undefined;
+ }
+ }
- if( h !== indexh || v !== indexv ) {
- slide( h, v );
+ if( h !== indexh || v !== indexv || f !== undefined ) {
+ slide( h, v, f );
}
}
@@ -3739,25 +3969,7 @@
writeURLTimeout = setTimeout( writeURL, delay );
}
else if( currentSlide ) {
- var url = '/';
-
- // Attempt to create a named link based on the slide's ID
- var id = currentSlide.getAttribute( 'id' );
- if( id ) {
- id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
- }
-
- // If the current slide has an ID, use that as a named link
- if( typeof id === 'string' && id.length ) {
- url = '/' + id;
- }
- // Otherwise use the /h/v index
- else {
- if( indexh > 0 || indexv > 0 ) url += indexh;
- if( indexv > 0 ) url += '/' + indexv;
- }
-
- window.location.hash = url;
+ window.location.hash = locationHash();
}
}
@@ -3860,13 +4072,14 @@
* defined, have a background element so as long as the
* index is valid an element will be returned.
*
- * @param {number} x Horizontal background index
+ * @param {mixed} x Horizontal background index OR a slide
+ * HTML element
* @param {number} y Vertical background index
* @return {(HTMLElement[]|*)}
*/
function getSlideBackground( x, y ) {
- var slide = getSlide( x, y );
+ var slide = typeof x === 'number' ? getSlide( x, y ) : x;
if( slide ) {
return slide.slideBackgroundElement;
}
@@ -4091,6 +4304,9 @@
updateControls();
updateProgress();
+ if( config.fragmentInURL ) {
+ writeURL();
+ }
return !!( fragmentsShown.length || fragmentsHidden.length );
@@ -4330,7 +4546,17 @@
// Prioritize revealing fragments
if( nextFragment() === false ) {
- if( availableRoutes().down ) {
+
+ var routes = availableRoutes();
+
+ // When looping is enabled `routes.down` is always available
+ // so we need a separate check for when we've reached the
+ // end of a stack and should move horizontally
+ if( routes.down && routes.right && config.loop && Reveal.isLastVerticalSlide( currentSlide ) ) {
+ routes.down = false;
+ }
+
+ if( routes.down ) {
navigateDown();
}
else if( config.rtl ) {
@@ -4400,7 +4626,7 @@
// If there's a condition specified and it returns false,
// ignore this event
- if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {
+ if( typeof config.keyboardCondition === 'function' && config.keyboardCondition(event) === false ) {
return true;
}
@@ -4465,7 +4691,31 @@
}
- // 2. System defined key bindings
+ // 2. Registered custom key bindings
+ if( triggered === false ) {
+
+ for( key in registeredKeyBindings ) {
+
+ // Check if this binding matches the pressed key
+ if( parseInt( key, 10 ) === event.keyCode ) {
+
+ var action = registeredKeyBindings[ key ].callback;
+
+ // Callback function
+ if( typeof action === 'function' ) {
+ action.apply( null, [ event ] );
+ }
+ // String shortcuts to reveal.js API
+ else if( typeof action === 'string' && typeof Reveal[ action ] === 'function' ) {
+ Reveal[ action ].call();
+ }
+
+ triggered = true;
+ }
+ }
+ }
+
+ // 3. System defined key bindings
if( triggered === false ) {
// Assume true and try to prove false
@@ -5059,7 +5309,10 @@
initialize: initialize,
configure: configure,
+
sync: sync,
+ syncSlide: syncSlide,
+ syncFragments: syncFragments,
// Navigation methods
slide: slide,
@@ -5196,7 +5449,7 @@
// Returns true if we're currently on the last slide
isLastSlide: function() {
if( currentSlide ) {
- // Does this slide has next a sibling?
+ // Does this slide have a next sibling?
if( currentSlide.nextElementSibling ) return false;
// If it's vertical, does its parent have a next sibling?
@@ -5208,6 +5461,19 @@
return false;
},
+ // Returns true if we're on the last slide in the current
+ // vertical stack
+ isLastVerticalSlide: function() {
+ if( currentSlide && isVerticalSlide( currentSlide ) ) {
+ // Does this slide have a next sibling?
+ if( currentSlide.nextElementSibling ) return false;
+
+ return true;
+ }
+
+ return false;
+ },
+
// Checks if reveal.js has been loaded and is ready for use
isReady: function() {
return loaded;
@@ -5225,6 +5491,12 @@
}
},
+ // Adds a custom key binding
+ addKeyBinding: addKeyBinding,
+
+ // Removes a custom key binding
+ removeKeyBinding: removeKeyBinding,
+
// Programatically triggers a keyboard event
triggerKey: function( keyCode ) {
onDocumentKeyDown( { keyCode: keyCode } );