You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
5.3KB

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