location.js 5.5 KB

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