您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

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