123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- /**
- * Reads and writes the URL based on reveal.js' current state.
- */
- export default class Location {
-
- // The minimum number of milliseconds that must pass between
- // calls to history.replaceState
- MAX_REPLACE_STATE_FREQUENCY = 1000
-
- constructor( Reveal ) {
-
- this.Reveal = Reveal;
-
- // Delays updates to the URL due to a Chrome thumbnailer bug
- this.writeURLTimeout = 0;
-
- this.replaceStateTimestamp = 0;
-
- this.onWindowHashChange = this.onWindowHashChange.bind( this );
-
- }
-
- bind() {
-
- window.addEventListener( 'hashchange', this.onWindowHashChange, false );
-
- }
-
- unbind() {
-
- window.removeEventListener( 'hashchange', this.onWindowHashChange, false );
-
- }
-
- /**
- * Returns the slide indices for the given hash link.
- *
- * @param {string} [hash] the hash string that we want to
- * find the indices for
- *
- * @returns slide indices or null
- */
- getIndicesFromHash( hash=window.location.hash, options={} ) {
-
- // Attempt to parse the hash as either an index or name
- let name = hash.replace( /^#\/?/, '' );
- let bits = name.split( '/' );
-
- // If the first bit is not fully numeric and there is a name we
- // can assume that this is a named link
- if( !/^[0-9]*$/.test( bits[0] ) && name.length ) {
- let slide;
-
- let f;
-
- // Parse named links with fragments (#/named-link/2)
- if( /\/[-\d]+$/g.test( name ) ) {
- f = parseInt( name.split( '/' ).pop(), 10 );
- f = isNaN(f) ? undefined : f;
- name = name.split( '/' ).shift();
- }
-
- // Ensure the named link is a valid HTML ID attribute
- try {
- slide = document
- .getElementById( decodeURIComponent( name ) )
- .closest('.slides>section, .slides>section>section');
- }
- catch ( error ) { }
-
- if( slide ) {
- return { ...this.Reveal.getIndices( slide ), f };
- }
- }
- else {
- const config = this.Reveal.getConfig();
- let hashIndexBase = config.hashOneBasedIndex || options.oneBasedIndex ? 1 : 0;
-
- // Read the index components of the hash
- let 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;
- }
- }
-
- return { h, v, f };
- }
-
- // The hash couldn't be parsed or no matching named link was found
- return null
-
- }
-
- /**
- * Reads the current URL (hash) and navigates accordingly.
- */
- readURL() {
-
- const currentIndices = this.Reveal.getIndices();
- const newIndices = this.getIndicesFromHash();
-
- if( newIndices ) {
- if( ( newIndices.h !== currentIndices.h || newIndices.v !== currentIndices.v || newIndices.f !== undefined ) ) {
- this.Reveal.slide( newIndices.h, newIndices.v, newIndices.f );
- }
- }
- // If no new indices are available, we're trying to navigate to
- // a slide hash that does not exist
- else {
- this.Reveal.slide( currentIndices.h || 0, currentIndices.v || 0 );
- }
-
- }
-
- /**
- * Updates the page URL (hash) to reflect the current
- * state.
- *
- * @param {number} delay The time in ms to wait before
- * writing the hash
- */
- writeURL( delay ) {
-
- let config = this.Reveal.getConfig();
- let currentSlide = this.Reveal.getCurrentSlide();
-
- // Make sure there's never more than one timeout running
- clearTimeout( this.writeURLTimeout );
-
- // If a delay is specified, timeout this call
- if( typeof delay === 'number' ) {
- this.writeURLTimeout = setTimeout( this.writeURL, delay );
- }
- else if( currentSlide ) {
-
- let hash = this.getHash();
-
- // If we're configured to push to history OR the history
- // API is not available.
- if( config.history ) {
- window.location.hash = hash;
- }
- // If we're configured to reflect the current slide in the
- // URL without pushing to history.
- else if( config.hash ) {
- // If the hash is empty, don't add it to the URL
- if( hash === '/' ) {
- this.debouncedReplaceState( window.location.pathname + window.location.search );
- }
- else {
- this.debouncedReplaceState( '#' + hash );
- }
- }
- // UPDATE: The below nuking of all hash changes breaks
- // anchors on pages where reveal.js is running. Removed
- // in 4.0. Why was it here in the first place? ¯\_(ツ)_/¯
- //
- // If history and hash are both disabled, a hash may still
- // be added to the URL by clicking on a href with a hash
- // target. Counter this by always removing the hash.
- // else {
- // window.history.replaceState( null, null, window.location.pathname + window.location.search );
- // }
-
- }
-
- }
-
- replaceState( url ) {
-
- window.history.replaceState( null, null, url );
- this.replaceStateTimestamp = Date.now();
-
- }
-
- debouncedReplaceState( url ) {
-
- clearTimeout( this.replaceStateTimeout );
-
- if( Date.now() - this.replaceStateTimestamp > this.MAX_REPLACE_STATE_FREQUENCY ) {
- this.replaceState( url );
- }
- else {
- this.replaceStateTimeout = setTimeout( () => this.replaceState( url ), this.MAX_REPLACE_STATE_FREQUENCY );
- }
-
- }
-
- /**
- * Return a hash URL that will resolve to the given slide location.
- *
- * @param {HTMLElement} [slide=currentSlide] The slide to link to
- */
- getHash( slide ) {
-
- let url = '/';
-
- // Attempt to create a named link based on the slide's ID
- let s = slide || this.Reveal.getCurrentSlide();
- let id = s ? s.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 ) {
- url = '/' + id;
-
- // If there is also a fragment, append that at the end
- // of the named link, like: #/named-link/2
- if( index.f >= 0 ) url += '/' + index.f;
- }
- // Otherwise use the /h/v index
- else {
- let hashIndexBase = this.Reveal.getConfig().hashOneBasedIndex ? 1 : 0;
- if( index.h > 0 || index.v > 0 || index.f >= 0 ) url += index.h + hashIndexBase;
- if( index.v > 0 || index.f >= 0 ) url += '/' + (index.v + hashIndexBase );
- if( index.f >= 0 ) url += '/' + index.f;
- }
-
- return url;
-
- }
-
- /**
- * Handler for the window level 'hashchange' event.
- *
- * @param {object} [event]
- */
- onWindowHashChange( event ) {
-
- this.readURL();
-
- }
-
- }
|