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