location.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. /**
  2. * Reads and writes the URL based on reveal.js' current state.
  3. */
  4. export default class Location {
  5. constructor( Reveal ) {
  6. this.Reveal = Reveal;
  7. // Delays updates to the URL due to a Chrome thumbnailer bug
  8. this.writeURLTimeout = 0;
  9. this.onWindowHashChange = this.onWindowHashChange.bind( this );
  10. }
  11. bind() {
  12. window.addEventListener( 'hashchange', this.onWindowHashChange, false );
  13. }
  14. unbind() {
  15. window.removeEventListener( 'hashchange', this.onWindowHashChange, false );
  16. }
  17. /**
  18. * Reads the current URL (hash) and navigates accordingly.
  19. */
  20. readURL() {
  21. let config = this.Reveal.getConfig();
  22. let indices = this.Reveal.getIndices();
  23. let currentSlide = this.Reveal.getCurrentSlide();
  24. let hash = window.location.hash;
  25. // Attempt to parse the hash as either an index or name
  26. let bits = hash.slice( 2 ).split( '/' ),
  27. name = hash.replace( /#\/?/gi, '' );
  28. // If the first bit is not fully numeric and there is a name we
  29. // can assume that this is a named link
  30. if( !/^[0-9]*$/.test( bits[0] ) && name.length ) {
  31. let element;
  32. let f;
  33. // Parse named links with fragments (#/named-link/2)
  34. if( /\/[-\d]+$/g.test( name ) ) {
  35. f = parseInt( name.split( '/' ).pop(), 10 );
  36. f = isNaN(f) ? undefined : f;
  37. name = name.split( '/' ).shift();
  38. }
  39. // Ensure the named link is a valid HTML ID attribute
  40. try {
  41. element = document.getElementById( decodeURIComponent( name ) );
  42. }
  43. catch ( error ) { }
  44. // Ensure that we're not already on a slide with the same name
  45. let isSameNameAsCurrentSlide = currentSlide ? currentSlide.getAttribute( 'id' ) === name : false;
  46. if( element ) {
  47. // If the slide exists and is not the current slide...
  48. if ( !isSameNameAsCurrentSlide || typeof f !== 'undefined' ) {
  49. // ...find the position of the named slide and navigate to it
  50. let slideIndices = this.Reveal.getIndices( element );
  51. this.Reveal.slide( slideIndices.h, slideIndices.v, f );
  52. }
  53. }
  54. // If the slide doesn't exist, navigate to the current slide
  55. else {
  56. this.Reveal.slide( indices.h || 0, indices.v || 0 );
  57. }
  58. }
  59. else {
  60. let hashIndexBase = config.hashOneBasedIndex ? 1 : 0;
  61. // Read the index components of the hash
  62. let h = ( parseInt( bits[0], 10 ) - hashIndexBase ) || 0,
  63. v = ( parseInt( bits[1], 10 ) - hashIndexBase ) || 0,
  64. f;
  65. if( config.fragmentInURL ) {
  66. f = parseInt( bits[2], 10 );
  67. if( isNaN( f ) ) {
  68. f = undefined;
  69. }
  70. }
  71. if( h !== indices.h || v !== indices.v || f !== undefined ) {
  72. this.Reveal.slide( h, v, f );
  73. }
  74. }
  75. }
  76. /**
  77. * Updates the page URL (hash) to reflect the current
  78. * state.
  79. *
  80. * @param {number} delay The time in ms to wait before
  81. * writing the hash
  82. */
  83. writeURL( delay ) {
  84. let config = this.Reveal.getConfig();
  85. let currentSlide = this.Reveal.getCurrentSlide();
  86. // Make sure there's never more than one timeout running
  87. clearTimeout( this.writeURLTimeout );
  88. // If a delay is specified, timeout this call
  89. if( typeof delay === 'number' ) {
  90. this.writeURLTimeout = setTimeout( this.writeURL, delay );
  91. }
  92. else if( currentSlide ) {
  93. let hash = this.getHash();
  94. // If we're configured to push to history OR the history
  95. // API is not avaialble.
  96. if( config.history ) {
  97. window.location.hash = hash;
  98. }
  99. // If we're configured to reflect the current slide in the
  100. // URL without pushing to history.
  101. else if( config.hash ) {
  102. // If the hash is empty, don't add it to the URL
  103. if( hash === '/' ) {
  104. window.history.replaceState( null, null, window.location.pathname + window.location.search );
  105. }
  106. else {
  107. window.history.replaceState( null, null, '#' + hash );
  108. }
  109. }
  110. // UPDATE: The below nuking of all hash changes breaks
  111. // anchors on pages where reveal.js is running. Removed
  112. // in 4.0. Why was it here in the first place? ¯\_(ツ)_/¯
  113. //
  114. // If history and hash are both disabled, a hash may still
  115. // be added to the URL by clicking on a href with a hash
  116. // target. Counter this by always removing the hash.
  117. // else {
  118. // window.history.replaceState( null, null, window.location.pathname + window.location.search );
  119. // }
  120. }
  121. }
  122. /**
  123. * Return a hash URL that will resolve to the given slide location.
  124. *
  125. * @param {HTMLElement} [slide=currentSlide] The slide to link to
  126. */
  127. getHash( slide ) {
  128. let url = '/';
  129. // Attempt to create a named link based on the slide's ID
  130. let s = slide || this.Reveal.getCurrentSlide();
  131. let id = s ? s.getAttribute( 'id' ) : null;
  132. if( id ) {
  133. id = encodeURIComponent( id );
  134. }
  135. let index = this.Reveal.getIndices( slide );
  136. if( !this.Reveal.getConfig().fragmentInURL ) {
  137. index.f = undefined;
  138. }
  139. // If the current slide has an ID, use that as a named link,
  140. // but we don't support named links with a fragment index
  141. if( typeof id === 'string' && id.length ) {
  142. url = '/' + id;
  143. // If there is also a fragment, append that at the end
  144. // of the named link, like: #/named-link/2
  145. if( index.f >= 0 ) url += '/' + index.f;
  146. }
  147. // Otherwise use the /h/v index
  148. else {
  149. let hashIndexBase = this.Reveal.getConfig().hashOneBasedIndex ? 1 : 0;
  150. if( index.h > 0 || index.v > 0 || index.f >= 0 ) url += index.h + hashIndexBase;
  151. if( index.v > 0 || index.f >= 0 ) url += '/' + (index.v + hashIndexBase );
  152. if( index.f >= 0 ) url += '/' + index.f;
  153. }
  154. return url;
  155. }
  156. /**
  157. * Handler for the window level 'hashchange' event.
  158. *
  159. * @param {object} [event]
  160. */
  161. onWindowHashChange( event ) {
  162. this.readURL();
  163. }
  164. }