add slidenumber & location controllers

This commit is contained in:
Hakim El Hattab 2020-03-10 21:08:11 +01:00
parent ac15678dea
commit 3683ad255d
6 changed files with 240 additions and 184 deletions

2
dist/reveal.min.js vendored

File diff suppressed because one or more lines are too long

View file

@ -263,7 +263,7 @@ export default class Keyboard {
} }
// N, PAGE DOWN // N, PAGE DOWN
else if( keyCode === 78 || keyCode === 34 ) { else if( keyCode === 78 || keyCode === 34 ) {
this.Review.next(); this.Reveal.next();
} }
// H, LEFT // H, LEFT
else if( keyCode === 72 || keyCode === 37 ) { else if( keyCode === 72 || keyCode === 37 ) {
@ -283,7 +283,7 @@ export default class Keyboard {
this.Reveal.slide( Number.MAX_VALUE ); this.Reveal.slide( Number.MAX_VALUE );
} }
else if( !this.Reveal.overview.isActive() && useLinearMode ) { else if( !this.Reveal.overview.isActive() && useLinearMode ) {
this.Review.next(); this.Reveal.next();
} }
else { else {
this.Reveal.right(); this.Reveal.right();
@ -301,7 +301,7 @@ export default class Keyboard {
// J, DOWN // J, DOWN
else if( keyCode === 74 || keyCode === 40 ) { else if( keyCode === 74 || keyCode === 40 ) {
if( !this.Reveal.overview.isActive() && useLinearMode ) { if( !this.Reveal.overview.isActive() && useLinearMode ) {
this.Review.next(); this.Reveal.next();
} }
else { else {
this.Reveal.down(); this.Reveal.down();
@ -324,7 +324,7 @@ export default class Keyboard {
this.Reveal.prev(); this.Reveal.prev();
} }
else { else {
this.Review.next(); this.Reveal.next();
} }
} }
// TWO-SPOT, SEMICOLON, B, V, PERIOD, LOGITECH PRESENTER TOOLS "BLACK SCREEN" BUTTON // TWO-SPOT, SEMICOLON, B, V, PERIOD, LOGITECH PRESENTER TOOLS "BLACK SCREEN" BUTTON

View file

@ -0,0 +1,51 @@
import { enterFullscreen } from '../utils/util.js'
/**
* Handles all reveal.js keyboard interactions.
*/
export default class Location {
constructor( Reveal ) {
this.Reveal = Reveal;
}
/**
* Return a hash URL that will resolve to the given slide location.
*
* @param {HTMLElement} [slide=currentSlide] The slide to link to
*/
getHash( slide = this.Reveal.getCurrentSlide() ) {
let url = '/';
// Attempt to create a named link based on the slide's ID
let id = slide ? slide.getAttribute( 'id' ) : null;
if( id ) {
id = encodeURIComponent( id );
}
let index = this.Reveal.getIndices( slide );
if( !this.Reveal.getConfig().fragmentInURL ) {
index.f = undefined;
}
// 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 && index.f === undefined ) {
url = '/' + id;
}
// Otherwise use the /h/v index
else {
let hashIndexBase = this.Reveal.getConfig().hashOneBasedIndex ? 1 : 0;
if( index.h > 0 || index.v > 0 || index.f !== undefined ) url += index.h + hashIndexBase;
if( index.v > 0 || index.f !== undefined ) url += '/' + (index.v + hashIndexBase );
if( index.f !== undefined ) url += '/' + index.f;
}
return url;
}
}

View file

@ -0,0 +1,128 @@
import { enterFullscreen } from '../utils/util.js'
/**
* Handles all reveal.js keyboard interactions.
*/
export default class SlideNumber {
constructor( Reveal ) {
this.Reveal = Reveal;
}
createElement() {
this.element = document.createElement( 'div' );
this.element.className = 'slide-number';
this.Reveal.getRevealElement().appendChild( this.element );
}
/**
* Shows or hides the slide number depending on the
* current config and state.
*/
refreshVisibility() {
let config = this.Reveal.getConfig();
let slideNumberDisplay = 'none';
if( config.slideNumber && !this.Reveal.isPrintingPDF() ) {
if( config.showSlideNumber === 'all' ) {
slideNumberDisplay = 'block';
}
else if( config.showSlideNumber === 'speaker' && this.Reveal.isSpeakerNotes() ) {
slideNumberDisplay = 'block';
}
}
this.element.style.display = slideNumberDisplay;
}
/**
* Updates the slide number to match the current slide.
*/
update() {
// Update slide number if enabled
if( this.Reveal.getConfig().slideNumber && this.element ) {
this.element.innerHTML = this.getSlideNumber();
}
}
/**
* Returns the HTML string corresponding to the current slide
* number, including formatting.
*/
getSlideNumber( slide = this.Reveal.getCurrentSlide() ) {
let config = this.Reveal.getConfig();
let value;
let format = 'h.v';
if ( typeof config.slideNumber === 'function' ) {
value = config.slideNumber( slide );
} else {
// Check if a custom number format is available
if( typeof config.slideNumber === 'string' ) {
format = config.slideNumber;
}
// If there are ONLY vertical slides in this deck, always use
// a flattened slide number
if( !/c/.test( format ) && this.Reveal.getHorizontalSlides().length === 1 ) {
format = 'c';
}
value = [];
switch( format ) {
case 'c':
value.push( this.Reveal.getSlidePastCount( slide ) + 1 );
break;
case 'c/t':
value.push( this.Reveal.getSlidePastCount( slide ) + 1, '/', this.Reveal.getTotalSlides() );
break;
default:
let indices = this.Reveal.getIndices( slide );
value.push( indices.h + 1 );
let sep = format === 'h/v' ? '/' : '.';
if( this.Reveal.isVerticalSlide( slide ) ) value.push( sep, indices.v + 1 );
}
}
let url = '#' + this.Reveal.location.getHash( slide );
return this.formatNumber( value[0], value[1], value[2], url );
}
/**
* Applies HTML formatting to a slide number before it's
* written to the DOM.
*
* @param {number} a Current slide
* @param {string} delimiter Character to separate slide numbers
* @param {(number|*)} b Total slides
* @param {HTMLElement} [url='#'+locationHash()] The url to link to
* @return {string} HTML string fragment
*/
formatNumber( a, delimiter, b, url = '#' + this.Reveal.location.getHash() ) {
if( typeof b === 'number' && !isNaN( b ) ) {
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>
</a>`;
}
else {
return `<a href="${url}">
<span class="slide-number-a">${a}</span>
</a>`;
}
}
}

View file

@ -1,8 +1,10 @@
import SlideContent from './controllers/slidecontent.js' import SlideContent from './controllers/slidecontent.js'
import SlideNumber from './controllers/slidenumber.js'
import AutoAnimate from './controllers/autoanimate.js' import AutoAnimate from './controllers/autoanimate.js'
import Fragments from './controllers/fragments.js' import Fragments from './controllers/fragments.js'
import Overview from './controllers/overview.js' import Overview from './controllers/overview.js'
import Keyboard from './controllers/keyboard.js' import Keyboard from './controllers/keyboard.js'
import Location from './controllers/location.js'
import Plugins from './controllers/plugins.js' import Plugins from './controllers/plugins.js'
import Playback from './components/playback.js' import Playback from './components/playback.js'
import defaultConfig from './config.js' import defaultConfig from './config.js'
@ -18,6 +20,7 @@ import {
distanceBetween, distanceBetween,
deserialize, deserialize,
transformElement, transformElement,
createSingletonNode,
createStyleSheet, createStyleSheet,
closestParent, closestParent,
enterFullscreen, enterFullscreen,
@ -81,6 +84,9 @@ export default function( revealElement, options ) {
// Controls loading and playback of slide content // Controls loading and playback of slide content
slideContent = new SlideContent( Reveal ), slideContent = new SlideContent( Reveal ),
// Controls the optional slide number display
slideNumber = new SlideNumber( Reveal ),
// Controls auto-animations between slides // Controls auto-animations between slides
autoAnimate = new AutoAnimate( Reveal ), autoAnimate = new AutoAnimate( Reveal ),
@ -93,6 +99,9 @@ export default function( revealElement, options ) {
// Controls all keyboard interactions // Controls all keyboard interactions
keyboard = new Keyboard( Reveal ), keyboard = new Keyboard( Reveal ),
// Controls the current location/URL
location = new Location( Reveal ),
// List of asynchronously loaded reveal.js dependencies // List of asynchronously loaded reveal.js dependencies
asyncDependencies = [], asyncDependencies = [],
@ -248,7 +257,7 @@ export default function( revealElement, options ) {
<button class="navigate-down" aria-label="below slide"><div class="controls-arrow"></div></button>` ); <button class="navigate-down" aria-label="below slide"><div class="controls-arrow"></div></button>` );
// Slide number // Slide number
dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' ); slideNumber.createElement();
// Element containing notes that are visible to the audience // Element containing notes that are visible to the audience
dom.speakerNotes = createSingletonNode( dom.wrapper, 'div', 'speaker-notes', null ); dom.speakerNotes = createSingletonNode( dom.wrapper, 'div', 'speaker-notes', null );
@ -377,7 +386,7 @@ export default function( revealElement, options ) {
// Compute slide numbers now, before we start duplicating slides // Compute slide numbers now, before we start duplicating slides
let doingSlideNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber ); let doingSlideNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber );
toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) { toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
slide.setAttribute( 'data-slide-number', getSlideNumber( slide ) ); slide.setAttribute( 'data-slide-number', slideNumber.getSlideNumber( slide ) );
} ); } );
// Slide and slide background layout // Slide and slide background layout
@ -534,42 +543,6 @@ export default function( revealElement, options ) {
} }
/**
* Creates an HTML element and returns a reference to it.
* If the element already exists the existing instance will
* be returned.
*
* @param {HTMLElement} container
* @param {string} tagname
* @param {string} classname
* @param {string} innerHTML
*
* @return {HTMLElement}
*/
function createSingletonNode( container, tagname, classname, innerHTML='' ) {
// Find all nodes matching the description
let nodes = container.querySelectorAll( '.' + classname );
// Check all matches to find one which is a direct child of
// the specified container
for( let i = 0; i < nodes.length; i++ ) {
let testNode = nodes[i];
if( testNode.parentNode === container ) {
return testNode;
}
}
// If no node was found, create it now
let node = document.createElement( tagname );
node.className = classname;
node.innerHTML = innerHTML;
container.appendChild( node );
return node;
}
/** /**
* Creates the slide background elements and appends them * Creates the slide background elements and appends them
* to the background container. One element is created per * to the background container. One element is created per
@ -831,9 +804,8 @@ export default function( revealElement, options ) {
const numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length; const numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
// Remove the previously configured transition class // The transition is added as a class on the .reveal element
dom.wrapper.classList.remove( oldTransition ); dom.wrapper.classList.remove( oldTransition );
dom.wrapper.classList.add( config.transition ); dom.wrapper.classList.add( config.transition );
dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed ); dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
@ -927,19 +899,6 @@ export default function( revealElement, options ) {
fragments.showAll(); fragments.showAll();
} }
// Slide numbers
let slideNumberDisplay = 'none';
if( config.slideNumber && !isPrintingPDF() ) {
if( config.showSlideNumber === 'all' ) {
slideNumberDisplay = 'block';
}
else if( config.showSlideNumber === 'speaker' && isSpeakerNotes() ) {
slideNumberDisplay = 'block';
}
}
dom.slideNumber.style.display = slideNumberDisplay;
// Add the navigation mode to the DOM so we can adjust styling // Add the navigation mode to the DOM so we can adjust styling
if( config.navigationMode !== 'default' ) { if( config.navigationMode !== 'default' ) {
dom.wrapper.setAttribute( 'data-navigation-mode', config.navigationMode ); dom.wrapper.setAttribute( 'data-navigation-mode', config.navigationMode );
@ -948,6 +907,7 @@ export default function( revealElement, options ) {
dom.wrapper.removeAttribute( 'data-navigation-mode' ); dom.wrapper.removeAttribute( 'data-navigation-mode' );
} }
slideNumber.refreshVisibility();
keyboard.refreshSortcuts(); keyboard.refreshSortcuts();
sync(); sync();
@ -1551,44 +1511,6 @@ export default function( revealElement, options ) {
} }
/**
* Return a hash URL that will resolve to the given slide location.
*
* @param {HTMLElement} [slide=currentSlide] The slide to link to
*/
function locationHash( slide ) {
let url = '/';
// Attempt to create a named link based on the slide's ID
let s = slide || currentSlide;
let id = s ? s.getAttribute( 'id' ) : null;
if( id ) {
id = encodeURIComponent( id );
}
let index = getIndices( slide );
if( !config.fragmentInURL ) {
index.f = undefined;
}
// 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 && index.f === undefined ) {
url = '/' + id;
}
// Otherwise use the /h/v index
else {
let hashIndexBase = config.hashOneBasedIndex ? 1 : 0;
if( index.h > 0 || index.v > 0 || index.f !== undefined ) url += index.h + hashIndexBase;
if( index.v > 0 || index.f !== undefined ) url += '/' + (index.v + hashIndexBase );
if( index.f !== undefined ) url += '/' + index.f;
}
return url;
}
/** /**
* Checks if the current or specified slide is vertical * Checks if the current or specified slide is vertical
* (nested within another slide). * (nested within another slide).
@ -1908,9 +1830,9 @@ export default function( revealElement, options ) {
updateProgress(); updateProgress();
updateBackground(); updateBackground();
updateParallax(); updateParallax();
updateSlideNumber();
updateNotes(); updateNotes();
slideNumber.update();
fragments.update(); fragments.update();
// Update the URL hash // Update the URL hash
@ -1970,12 +1892,12 @@ export default function( revealElement, options ) {
updateControls(); updateControls();
updateProgress(); updateProgress();
updateSlideNumber();
updateSlidesVisibility(); updateSlidesVisibility();
updateBackground( true ); updateBackground( true );
updateNotesVisibility(); updateNotesVisibility();
updateNotes(); updateNotes();
slideNumber.update();
slideContent.formatEmbeddedContent(); slideContent.formatEmbeddedContent();
// Start or stop embedded content depending on global config // Start or stop embedded content depending on global config
@ -2322,90 +2244,6 @@ export default function( revealElement, options ) {
} }
/**
* Updates the slide number to match the current slide.
*/
function updateSlideNumber() {
// Update slide number if enabled
if( config.slideNumber && dom.slideNumber ) {
dom.slideNumber.innerHTML = getSlideNumber();
}
}
/**
* Returns the HTML string corresponding to the current slide number,
* including formatting.
*/
function getSlideNumber( slide = currentSlide ) {
let value;
let format = 'h.v';
if ( typeof config.slideNumber === 'function' ) {
value = config.slideNumber( slide );
} else {
// Check if a custom number format is available
if( typeof config.slideNumber === 'string' ) {
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';
}
value = [];
switch( format ) {
case 'c':
value.push( getSlidePastCount( slide ) + 1 );
break;
case 'c/t':
value.push( getSlidePastCount( slide ) + 1, '/', getTotalSlides() );
break;
default:
let indices = getIndices( slide );
value.push( indices.h + 1 );
let sep = format === 'h/v' ? '/' : '.';
if( isVerticalSlide( slide ) ) value.push( sep, indices.v + 1 );
}
}
let url = '#' + locationHash( slide );
return formatSlideNumber( value[0], value[1], value[2], url );
}
/**
* Applies HTML formatting to a slide number before it's
* written to the DOM.
*
* @param {number} a Current slide
* @param {string} delimiter Character to separate slide numbers
* @param {(number|*)} b Total slides
* @param {HTMLElement} [url='#'+locationHash()] The url to link to
* @return {string} HTML string fragment
*/
function formatSlideNumber( a, delimiter, b, url = '#' + locationHash() ) {
if( typeof b === 'number' && !isNaN( b ) ) {
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>
</a>`;
}
else {
return `<a href="${url}">
<span class="slide-number-a">${a}</span>
</a>`;
}
}
/** /**
* Updates the state of all control/navigation arrows. * Updates the state of all control/navigation arrows.
*/ */
@ -2881,12 +2719,12 @@ export default function( revealElement, options ) {
// If we're configured to push to history OR the history // If we're configured to push to history OR the history
// API is not avaialble. // API is not avaialble.
if( config.history || !window.history ) { if( config.history || !window.history ) {
window.location.hash = locationHash(); window.location.hash = location.getHash();
} }
// If we're configured to reflect the current slide in the // If we're configured to reflect the current slide in the
// URL without pushing to history. // URL without pushing to history.
else if( config.hash ) { else if( config.hash ) {
window.history.replaceState( null, null, '#' + locationHash() ); window.history.replaceState( null, null, '#' + location.getHash() );
} }
// If history and hash are both disabled, a hash may still // If history and hash are both disabled, a hash may still
// be added to the URL by clicking on a href with a hash // be added to the URL by clicking on a href with a hash
@ -3810,12 +3648,14 @@ export default function( revealElement, options ) {
isFirstSlide, isFirstSlide,
isLastSlide, isLastSlide,
isLastVerticalSlide, isLastVerticalSlide,
isVerticalSlide,
// State checks // State checks
isOverview: overview.isActive.bind( overview ), isOverview: overview.isActive.bind( overview ),
isPaused, isPaused,
isAutoSliding, isAutoSliding,
isSpeakerNotes, isSpeakerNotes,
isPrintingPDF,
// Slide preloading // Slide preloading
loadSlide: slideContent.load.bind( slideContent ), loadSlide: slideContent.load.bind( slideContent ),
@ -3916,6 +3756,7 @@ export default function( revealElement, options ) {
announceStatus, announceStatus,
getStatusText, getStatusText,
location,
overview, overview,
slideContent, slideContent,
onUserInput, onUserInput,

View file

@ -135,6 +135,42 @@ export const enterFullscreen = () => {
} }
/**
* Creates an HTML element and returns a reference to it.
* If the element already exists the existing instance will
* be returned.
*
* @param {HTMLElement} container
* @param {string} tagname
* @param {string} classname
* @param {string} innerHTML
*
* @return {HTMLElement}
*/
export const createSingletonNode = ( container, tagname, classname, innerHTML='' ) => {
// Find all nodes matching the description
let nodes = container.querySelectorAll( '.' + classname );
// Check all matches to find one which is a direct child of
// the specified container
for( let i = 0; i < nodes.length; i++ ) {
let testNode = nodes[i];
if( testNode.parentNode === container ) {
return testNode;
}
}
// If no node was found, create it now
let node = document.createElement( tagname );
node.className = classname;
node.innerHTML = innerHTML;
container.appendChild( node );
return node;
}
/** /**
* Injects the given CSS styles into the DOM. * Injects the given CSS styles into the DOM.
* *