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