Merge lp:~tomasgroth/openlp/revealjs-380 into lp:openlp
- revealjs-380
- Merge into trunk
Proposed by
Tomas Groth
Status: | Merged |
---|---|
Approved by: | Raoul Snyman |
Approved revision: | 2854 |
Merged at revision: | 2855 |
Proposed branch: | lp:~tomasgroth/openlp/revealjs-380 |
Merge into: | lp:openlp |
Diff against target: |
1569 lines (+761/-310) 1 file modified
openlp/core/display/html/reveal.js (+761/-310) |
To merge this branch: | bzr merge lp:~tomasgroth/openlp/revealjs-380 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenLP Core | Pending | ||
Review via email: mp+365698@code.launchpad.net |
Commit message
Update Reveal.js to 3.8.0.
Description of the change
To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : | # |
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : | # |
Linting passed!
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : | # |
macOS tests passed!
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'openlp/core/display/html/reveal.js' | |||
2 | --- openlp/core/display/html/reveal.js 2019-02-11 20:34:20 +0000 | |||
3 | +++ openlp/core/display/html/reveal.js 2019-04-08 19:57:45 +0000 | |||
4 | @@ -3,7 +3,7 @@ | |||
5 | 3 | * http://revealjs.com | 3 | * http://revealjs.com |
6 | 4 | * MIT licensed | 4 | * MIT licensed |
7 | 5 | * | 5 | * |
9 | 6 | * Copyright (C) 2018 Hakim El Hattab, http://hakim.se | 6 | * Copyright (C) 2019 Hakim El Hattab, http://hakim.se |
10 | 7 | */ | 7 | */ |
11 | 8 | (function( root, factory ) { | 8 | (function( root, factory ) { |
12 | 9 | if( typeof define === 'function' && define.amd ) { | 9 | if( typeof define === 'function' && define.amd ) { |
13 | @@ -26,7 +26,7 @@ | |||
14 | 26 | var Reveal; | 26 | var Reveal; |
15 | 27 | 27 | ||
16 | 28 | // The reveal.js version | 28 | // The reveal.js version |
18 | 29 | var VERSION = '3.7.0'; | 29 | var VERSION = '3.8.0'; |
19 | 30 | 30 | ||
20 | 31 | var SLIDES_SELECTOR = '.slides section', | 31 | var SLIDES_SELECTOR = '.slides section', |
21 | 32 | HORIZONTAL_SLIDES_SELECTOR = '.slides>section', | 32 | HORIZONTAL_SLIDES_SELECTOR = '.slides>section', |
22 | @@ -67,16 +67,36 @@ | |||
23 | 67 | progress: true, | 67 | progress: true, |
24 | 68 | 68 | ||
25 | 69 | // Display the page number of the current slide | 69 | // Display the page number of the current slide |
26 | 70 | // - true: Show slide number | ||
27 | 71 | // - false: Hide slide number | ||
28 | 72 | // | ||
29 | 73 | // Can optionally be set as a string that specifies the number formatting: | ||
30 | 74 | // - "h.v": Horizontal . vertical slide number (default) | ||
31 | 75 | // - "h/v": Horizontal / vertical slide number | ||
32 | 76 | // - "c": Flattened slide number | ||
33 | 77 | // - "c/t": Flattened slide number / total slides | ||
34 | 78 | // | ||
35 | 79 | // Alternatively, you can provide a function that returns the slide | ||
36 | 80 | // number for the current slide. The function needs to return an array | ||
37 | 81 | // with one string [slideNumber] or three strings [n1,delimiter,n2]. | ||
38 | 82 | // See #formatSlideNumber(). | ||
39 | 70 | slideNumber: false, | 83 | slideNumber: false, |
40 | 71 | 84 | ||
41 | 85 | // Can be used to limit the contexts in which the slide number appears | ||
42 | 86 | // - "all": Always show the slide number | ||
43 | 87 | // - "print": Only when printing to PDF | ||
44 | 88 | // - "speaker": Only in the speaker view | ||
45 | 89 | showSlideNumber: 'all', | ||
46 | 90 | |||
47 | 72 | // Use 1 based indexing for # links to match slide number (default is zero | 91 | // Use 1 based indexing for # links to match slide number (default is zero |
48 | 73 | // based) | 92 | // based) |
49 | 74 | hashOneBasedIndex: false, | 93 | hashOneBasedIndex: false, |
50 | 75 | 94 | ||
53 | 76 | // Determine which displays to show the slide number on | 95 | // Add the current slide number to the URL hash so that reloading the |
54 | 77 | showSlideNumber: 'all', | 96 | // page/copying the URL will return you to the same slide |
55 | 97 | hash: false, | ||
56 | 78 | 98 | ||
58 | 79 | // Push each slide change to the browser history | 99 | // Push each slide change to the browser history. Implies `hash: true` |
59 | 80 | history: false, | 100 | history: false, |
60 | 81 | 101 | ||
61 | 82 | // Enable keyboard shortcuts for navigation | 102 | // Enable keyboard shortcuts for navigation |
62 | @@ -104,6 +124,32 @@ | |||
63 | 104 | // Change the presentation direction to be RTL | 124 | // Change the presentation direction to be RTL |
64 | 105 | rtl: false, | 125 | rtl: false, |
65 | 106 | 126 | ||
66 | 127 | // Changes the behavior of our navigation directions. | ||
67 | 128 | // | ||
68 | 129 | // "default" | ||
69 | 130 | // Left/right arrow keys step between horizontal slides, up/down | ||
70 | 131 | // arrow keys step between vertical slides. Space key steps through | ||
71 | 132 | // all slides (both horizontal and vertical). | ||
72 | 133 | // | ||
73 | 134 | // "linear" | ||
74 | 135 | // Removes the up/down arrows. Left/right arrows step through all | ||
75 | 136 | // slides (both horizontal and vertical). | ||
76 | 137 | // | ||
77 | 138 | // "grid" | ||
78 | 139 | // When this is enabled, stepping left/right from a vertical stack | ||
79 | 140 | // to an adjacent vertical stack will land you at the same vertical | ||
80 | 141 | // index. | ||
81 | 142 | // | ||
82 | 143 | // Consider a deck with six slides ordered in two vertical stacks: | ||
83 | 144 | // 1.1 2.1 | ||
84 | 145 | // 1.2 2.2 | ||
85 | 146 | // 1.3 2.3 | ||
86 | 147 | // | ||
87 | 148 | // If you're on slide 1.3 and navigate right, you will normally move | ||
88 | 149 | // from 1.3 -> 2.1. If "grid" is used, the same navigation takes you | ||
89 | 150 | // from 1.3 -> 2.3. | ||
90 | 151 | navigationMode: 'default', | ||
91 | 152 | |||
92 | 107 | // Randomizes the order of slides each time the presentation loads | 153 | // Randomizes the order of slides each time the presentation loads |
93 | 108 | shuffle: false, | 154 | shuffle: false, |
94 | 109 | 155 | ||
95 | @@ -134,6 +180,13 @@ | |||
96 | 134 | // - false: No media will autoplay, regardless of individual setting | 180 | // - false: No media will autoplay, regardless of individual setting |
97 | 135 | autoPlayMedia: null, | 181 | autoPlayMedia: null, |
98 | 136 | 182 | ||
99 | 183 | // Global override for preloading lazy-loaded iframes | ||
100 | 184 | // - null: Iframes with data-src AND data-preload will be loaded when within | ||
101 | 185 | // the viewDistance, iframes with only data-src will be loaded when visible | ||
102 | 186 | // - true: All iframes with data-src will be loaded when within the viewDistance | ||
103 | 187 | // - false: All iframes with data-src will be loaded only when visible | ||
104 | 188 | preloadIframes: null, | ||
105 | 189 | |||
106 | 137 | // Controls automatic progression to the next slide | 190 | // Controls automatic progression to the next slide |
107 | 138 | // - 0: Auto-sliding only happens if the data-autoslide HTML attribute | 191 | // - 0: Auto-sliding only happens if the data-autoslide HTML attribute |
108 | 139 | // is present on the current slide or fragment | 192 | // is present on the current slide or fragment |
109 | @@ -220,6 +273,12 @@ | |||
110 | 220 | // The display mode that will be used to show slides | 273 | // The display mode that will be used to show slides |
111 | 221 | display: 'block', | 274 | display: 'block', |
112 | 222 | 275 | ||
113 | 276 | // Hide cursor if inactive | ||
114 | 277 | hideInactiveCursor: true, | ||
115 | 278 | |||
116 | 279 | // Time before the cursor is hidden (in ms) | ||
117 | 280 | hideCursorTime: 5000, | ||
118 | 281 | |||
119 | 223 | // Script dependencies to load | 282 | // Script dependencies to load |
120 | 224 | dependencies: [] | 283 | dependencies: [] |
121 | 225 | 284 | ||
122 | @@ -267,6 +326,12 @@ | |||
123 | 267 | // Cached references to DOM elements | 326 | // Cached references to DOM elements |
124 | 268 | dom = {}, | 327 | dom = {}, |
125 | 269 | 328 | ||
126 | 329 | // A list of registered reveal.js plugins | ||
127 | 330 | plugins = {}, | ||
128 | 331 | |||
129 | 332 | // List of asynchronously loaded reveal.js dependencies | ||
130 | 333 | asyncDependencies = [], | ||
131 | 334 | |||
132 | 270 | // Features supported by the browser, see #checkCapabilities() | 335 | // Features supported by the browser, see #checkCapabilities() |
133 | 271 | features = {}, | 336 | features = {}, |
134 | 272 | 337 | ||
135 | @@ -282,6 +347,12 @@ | |||
136 | 282 | // Delays updates to the URL due to a Chrome thumbnailer bug | 347 | // Delays updates to the URL due to a Chrome thumbnailer bug |
137 | 283 | writeURLTimeout = 0, | 348 | writeURLTimeout = 0, |
138 | 284 | 349 | ||
139 | 350 | // Is the mouse pointer currently hidden from view | ||
140 | 351 | cursorHidden = false, | ||
141 | 352 | |||
142 | 353 | // Timeout used to determine when the cursor is inactive | ||
143 | 354 | cursorInactiveTimeout = 0, | ||
144 | 355 | |||
145 | 285 | // Flags if the interaction event listeners are bound | 356 | // Flags if the interaction event listeners are bound |
146 | 286 | eventsAreBound = false, | 357 | eventsAreBound = false, |
147 | 287 | 358 | ||
148 | @@ -298,26 +369,14 @@ | |||
149 | 298 | touch = { | 369 | touch = { |
150 | 299 | startX: 0, | 370 | startX: 0, |
151 | 300 | startY: 0, | 371 | startY: 0, |
152 | 301 | startSpan: 0, | ||
153 | 302 | startCount: 0, | 372 | startCount: 0, |
154 | 303 | captured: false, | 373 | captured: false, |
155 | 304 | threshold: 40 | 374 | threshold: 40 |
156 | 305 | }, | 375 | }, |
157 | 306 | 376 | ||
172 | 307 | // Holds information about the keyboard shortcuts | 377 | // A key:value map of shortcut keyboard keys and descriptions of |
173 | 308 | keyboardShortcuts = { | 378 | // the actions they trigger, generated in #configure() |
174 | 309 | 'N , SPACE': 'Next slide', | 379 | keyboardShortcuts = {}, |
161 | 310 | 'P': 'Previous slide', | ||
162 | 311 | '← , H': 'Navigate left', | ||
163 | 312 | '→ , L': 'Navigate right', | ||
164 | 313 | '↑ , K': 'Navigate up', | ||
165 | 314 | '↓ , J': 'Navigate down', | ||
166 | 315 | 'Home': 'First slide', | ||
167 | 316 | 'End': 'Last slide', | ||
168 | 317 | 'B , .': 'Pause', | ||
169 | 318 | 'F': 'Fullscreen', | ||
170 | 319 | 'ESC, O': 'Slide overview' | ||
171 | 320 | }, | ||
175 | 321 | 380 | ||
176 | 322 | // Holds custom key code mappings | 381 | // Holds custom key code mappings |
177 | 323 | registeredKeyBindings = {}; | 382 | registeredKeyBindings = {}; |
178 | @@ -377,7 +436,7 @@ | |||
179 | 377 | // Hide the address bar in mobile browsers | 436 | // Hide the address bar in mobile browsers |
180 | 378 | hideAddressBar(); | 437 | hideAddressBar(); |
181 | 379 | 438 | ||
183 | 380 | // Loads the dependencies and continues to #start() once done | 439 | // Loads dependencies and continues to #start() once done |
184 | 381 | load(); | 440 | load(); |
185 | 382 | 441 | ||
186 | 383 | } | 442 | } |
187 | @@ -440,57 +499,148 @@ | |||
188 | 440 | function load() { | 499 | function load() { |
189 | 441 | 500 | ||
190 | 442 | var scripts = [], | 501 | var scripts = [], |
220 | 443 | scriptsAsync = [], | 502 | scriptsToLoad = 0; |
221 | 444 | scriptsToPreload = 0; | 503 | |
222 | 445 | 504 | config.dependencies.forEach( function( s ) { | |
194 | 446 | // Called once synchronous scripts finish loading | ||
195 | 447 | function proceed() { | ||
196 | 448 | if( scriptsAsync.length ) { | ||
197 | 449 | // Load asynchronous scripts | ||
198 | 450 | head.js.apply( null, scriptsAsync ); | ||
199 | 451 | } | ||
200 | 452 | |||
201 | 453 | start(); | ||
202 | 454 | } | ||
203 | 455 | |||
204 | 456 | function loadScript( s ) { | ||
205 | 457 | head.ready( s.src.match( /([\w\d_\-]*)\.?js(\?[\w\d.=&]*)?$|[^\\\/]*$/i )[0], function() { | ||
206 | 458 | // Extension may contain callback functions | ||
207 | 459 | if( typeof s.callback === 'function' ) { | ||
208 | 460 | s.callback.apply( this ); | ||
209 | 461 | } | ||
210 | 462 | |||
211 | 463 | if( --scriptsToPreload === 0 ) { | ||
212 | 464 | proceed(); | ||
213 | 465 | } | ||
214 | 466 | }); | ||
215 | 467 | } | ||
216 | 468 | |||
217 | 469 | for( var i = 0, len = config.dependencies.length; i < len; i++ ) { | ||
218 | 470 | var s = config.dependencies[i]; | ||
219 | 471 | |||
223 | 472 | // Load if there's no condition or the condition is truthy | 505 | // Load if there's no condition or the condition is truthy |
224 | 473 | if( !s.condition || s.condition() ) { | 506 | if( !s.condition || s.condition() ) { |
225 | 474 | if( s.async ) { | 507 | if( s.async ) { |
227 | 475 | scriptsAsync.push( s.src ); | 508 | asyncDependencies.push( s ); |
228 | 476 | } | 509 | } |
229 | 477 | else { | 510 | else { |
231 | 478 | scripts.push( s.src ); | 511 | scripts.push( s ); |
232 | 479 | } | 512 | } |
233 | 480 | |||
234 | 481 | loadScript( s ); | ||
235 | 482 | } | 513 | } |
237 | 483 | } | 514 | } ); |
238 | 484 | 515 | ||
239 | 485 | if( scripts.length ) { | 516 | if( scripts.length ) { |
241 | 486 | scriptsToPreload = scripts.length; | 517 | scriptsToLoad = scripts.length; |
242 | 487 | 518 | ||
243 | 488 | // Load synchronous scripts | 519 | // Load synchronous scripts |
249 | 489 | head.js.apply( null, scripts ); | 520 | scripts.forEach( function( s ) { |
250 | 490 | } | 521 | loadScript( s.src, function() { |
251 | 491 | else { | 522 | |
252 | 492 | proceed(); | 523 | if( typeof s.callback === 'function' ) s.callback(); |
253 | 493 | } | 524 | |
254 | 525 | if( --scriptsToLoad === 0 ) { | ||
255 | 526 | initPlugins(); | ||
256 | 527 | } | ||
257 | 528 | |||
258 | 529 | } ); | ||
259 | 530 | } ); | ||
260 | 531 | } | ||
261 | 532 | else { | ||
262 | 533 | initPlugins(); | ||
263 | 534 | } | ||
264 | 535 | |||
265 | 536 | } | ||
266 | 537 | |||
267 | 538 | /** | ||
268 | 539 | * Initializes our plugins and waits for them to be ready | ||
269 | 540 | * before proceeding. | ||
270 | 541 | */ | ||
271 | 542 | function initPlugins() { | ||
272 | 543 | |||
273 | 544 | var pluginsToInitialize = Object.keys( plugins ).length; | ||
274 | 545 | |||
275 | 546 | // If there are no plugins, skip this step | ||
276 | 547 | if( pluginsToInitialize === 0 ) { | ||
277 | 548 | loadAsyncDependencies(); | ||
278 | 549 | } | ||
279 | 550 | // ... otherwise initialize plugins | ||
280 | 551 | else { | ||
281 | 552 | |||
282 | 553 | var afterPlugInitialized = function() { | ||
283 | 554 | if( --pluginsToInitialize === 0 ) { | ||
284 | 555 | loadAsyncDependencies(); | ||
285 | 556 | } | ||
286 | 557 | }; | ||
287 | 558 | |||
288 | 559 | for( var i in plugins ) { | ||
289 | 560 | |||
290 | 561 | var plugin = plugins[i]; | ||
291 | 562 | |||
292 | 563 | // If the plugin has an 'init' method, invoke it | ||
293 | 564 | if( typeof plugin.init === 'function' ) { | ||
294 | 565 | var callback = plugin.init(); | ||
295 | 566 | |||
296 | 567 | // If the plugin returned a Promise, wait for it | ||
297 | 568 | if( callback && typeof callback.then === 'function' ) { | ||
298 | 569 | callback.then( afterPlugInitialized ); | ||
299 | 570 | } | ||
300 | 571 | else { | ||
301 | 572 | afterPlugInitialized(); | ||
302 | 573 | } | ||
303 | 574 | } | ||
304 | 575 | else { | ||
305 | 576 | afterPlugInitialized(); | ||
306 | 577 | } | ||
307 | 578 | |||
308 | 579 | } | ||
309 | 580 | |||
310 | 581 | } | ||
311 | 582 | |||
312 | 583 | } | ||
313 | 584 | |||
314 | 585 | /** | ||
315 | 586 | * Loads all async reveal.js dependencies. | ||
316 | 587 | */ | ||
317 | 588 | function loadAsyncDependencies() { | ||
318 | 589 | |||
319 | 590 | if( asyncDependencies.length ) { | ||
320 | 591 | asyncDependencies.forEach( function( s ) { | ||
321 | 592 | loadScript( s.src, s.callback ); | ||
322 | 593 | } ); | ||
323 | 594 | } | ||
324 | 595 | |||
325 | 596 | start(); | ||
326 | 597 | |||
327 | 598 | } | ||
328 | 599 | |||
329 | 600 | /** | ||
330 | 601 | * Loads a JavaScript file from the given URL and executes it. | ||
331 | 602 | * | ||
332 | 603 | * @param {string} url Address of the .js file to load | ||
333 | 604 | * @param {function} callback Method to invoke when the script | ||
334 | 605 | * has loaded and executed | ||
335 | 606 | */ | ||
336 | 607 | function loadScript( url, callback ) { | ||
337 | 608 | |||
338 | 609 | var script = document.createElement( 'script' ); | ||
339 | 610 | script.type = 'text/javascript'; | ||
340 | 611 | script.async = false; | ||
341 | 612 | script.defer = false; | ||
342 | 613 | script.src = url; | ||
343 | 614 | |||
344 | 615 | if( callback ) { | ||
345 | 616 | |||
346 | 617 | // Success callback | ||
347 | 618 | script.onload = script.onreadystatechange = function( event ) { | ||
348 | 619 | if( event.type === "load" || (/loaded|complete/.test( script.readyState ) ) ) { | ||
349 | 620 | |||
350 | 621 | // Kill event listeners | ||
351 | 622 | script.onload = script.onreadystatechange = script.onerror = null; | ||
352 | 623 | |||
353 | 624 | callback(); | ||
354 | 625 | |||
355 | 626 | } | ||
356 | 627 | }; | ||
357 | 628 | |||
358 | 629 | // Error callback | ||
359 | 630 | script.onerror = function( err ) { | ||
360 | 631 | |||
361 | 632 | // Kill event listeners | ||
362 | 633 | script.onload = script.onreadystatechange = script.onerror = null; | ||
363 | 634 | |||
364 | 635 | callback( new Error( 'Failed loading script: ' + script.src + '\n' + err) ); | ||
365 | 636 | |||
366 | 637 | }; | ||
367 | 638 | |||
368 | 639 | } | ||
369 | 640 | |||
370 | 641 | // Append the script at the end of <head> | ||
371 | 642 | var head = document.querySelector( 'head' ); | ||
372 | 643 | head.insertBefore( script, head.lastChild ); | ||
373 | 494 | 644 | ||
374 | 495 | } | 645 | } |
375 | 496 | 646 | ||
376 | @@ -601,8 +751,7 @@ | |||
377 | 601 | dom.speakerNotes.setAttribute( 'tabindex', '0' ); | 751 | dom.speakerNotes.setAttribute( 'tabindex', '0' ); |
378 | 602 | 752 | ||
379 | 603 | // Overlay graphic which is displayed during the paused mode | 753 | // Overlay graphic which is displayed during the paused mode |
382 | 604 | dom.pauseOverlay = createSingletonNode( dom.wrapper, 'div', 'pause-overlay', '<button class="resume-button">Resume presentation</button>' ); | 754 | dom.pauseOverlay = createSingletonNode( dom.wrapper, 'div', 'pause-overlay', config.controls ? '<button class="resume-button">Resume presentation</button>' : null ); |
381 | 605 | dom.resumeButton = dom.pauseOverlay.querySelector( '.resume-button' ); | ||
383 | 606 | 755 | ||
384 | 607 | dom.wrapper.setAttribute( 'role', 'application' ); | 756 | dom.wrapper.setAttribute( 'role', 'application' ); |
385 | 608 | 757 | ||
386 | @@ -1082,18 +1231,27 @@ | |||
387 | 1082 | if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition; | 1231 | if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition; |
388 | 1083 | if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity; | 1232 | if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity; |
389 | 1084 | 1233 | ||
391 | 1085 | // If this slide has a background color, add a class that | 1234 | // If this slide has a background color, we add a class that |
392 | 1086 | // signals if it is light or dark. If the slide has no background | 1235 | // signals if it is light or dark. If the slide has no background |
397 | 1087 | // color, no class will be set | 1236 | // color, no class will be added |
398 | 1088 | var computedBackgroundStyle = window.getComputedStyle( element ); | 1237 | var contrastColor = data.backgroundColor; |
399 | 1089 | if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) { | 1238 | |
400 | 1090 | var rgb = colorToRgb( computedBackgroundStyle.backgroundColor ); | 1239 | // If no bg color was found, check the computed background |
401 | 1240 | if( !contrastColor ) { | ||
402 | 1241 | var computedBackgroundStyle = window.getComputedStyle( element ); | ||
403 | 1242 | if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) { | ||
404 | 1243 | contrastColor = computedBackgroundStyle.backgroundColor; | ||
405 | 1244 | } | ||
406 | 1245 | } | ||
407 | 1246 | |||
408 | 1247 | if( contrastColor ) { | ||
409 | 1248 | var rgb = colorToRgb( contrastColor ); | ||
410 | 1091 | 1249 | ||
411 | 1092 | // Ignore fully transparent backgrounds. Some browsers return | 1250 | // Ignore fully transparent backgrounds. Some browsers return |
412 | 1093 | // rgba(0,0,0,0) when reading the computed background color of | 1251 | // rgba(0,0,0,0) when reading the computed background color of |
413 | 1094 | // an element with no background | 1252 | // an element with no background |
414 | 1095 | if( rgb && rgb.a !== 0 ) { | 1253 | if( rgb && rgb.a !== 0 ) { |
416 | 1096 | if( colorBrightness( computedBackgroundStyle.backgroundColor ) < 128 ) { | 1254 | if( colorBrightness( contrastColor ) < 128 ) { |
417 | 1097 | slide.classList.add( 'has-dark-background' ); | 1255 | slide.classList.add( 'has-dark-background' ); |
418 | 1098 | } | 1256 | } |
419 | 1099 | else { | 1257 | else { |
420 | @@ -1216,6 +1374,18 @@ | |||
421 | 1216 | disableRollingLinks(); | 1374 | disableRollingLinks(); |
422 | 1217 | } | 1375 | } |
423 | 1218 | 1376 | ||
424 | 1377 | // Auto-hide the mouse pointer when its inactive | ||
425 | 1378 | if( config.hideInactiveCursor ) { | ||
426 | 1379 | document.addEventListener( 'mousemove', onDocumentCursorActive, false ); | ||
427 | 1380 | document.addEventListener( 'mousedown', onDocumentCursorActive, false ); | ||
428 | 1381 | } | ||
429 | 1382 | else { | ||
430 | 1383 | showCursor(); | ||
431 | 1384 | |||
432 | 1385 | document.removeEventListener( 'mousemove', onDocumentCursorActive, false ); | ||
433 | 1386 | document.removeEventListener( 'mousedown', onDocumentCursorActive, false ); | ||
434 | 1387 | } | ||
435 | 1388 | |||
436 | 1219 | // Iframe link previews | 1389 | // Iframe link previews |
437 | 1220 | if( config.previewLinks ) { | 1390 | if( config.previewLinks ) { |
438 | 1221 | enablePreviewLinks(); | 1391 | enablePreviewLinks(); |
439 | @@ -1263,6 +1433,34 @@ | |||
440 | 1263 | 1433 | ||
441 | 1264 | dom.slideNumber.style.display = slideNumberDisplay; | 1434 | dom.slideNumber.style.display = slideNumberDisplay; |
442 | 1265 | 1435 | ||
443 | 1436 | // Add the navigation mode to the DOM so we can adjust styling | ||
444 | 1437 | if( config.navigationMode !== 'default' ) { | ||
445 | 1438 | dom.wrapper.setAttribute( 'data-navigation-mode', config.navigationMode ); | ||
446 | 1439 | } | ||
447 | 1440 | else { | ||
448 | 1441 | dom.wrapper.removeAttribute( 'data-navigation-mode' ); | ||
449 | 1442 | } | ||
450 | 1443 | |||
451 | 1444 | // Define our contextual list of keyboard shortcuts | ||
452 | 1445 | if( config.navigationMode === 'linear' ) { | ||
453 | 1446 | keyboardShortcuts['→ , ↓ , SPACE , N , L , J'] = 'Next slide'; | ||
454 | 1447 | keyboardShortcuts['← , ↑ , P , H , K'] = 'Previous slide'; | ||
455 | 1448 | } | ||
456 | 1449 | else { | ||
457 | 1450 | keyboardShortcuts['N , SPACE'] = 'Next slide'; | ||
458 | 1451 | keyboardShortcuts['P'] = 'Previous slide'; | ||
459 | 1452 | keyboardShortcuts['← , H'] = 'Navigate left'; | ||
460 | 1453 | keyboardShortcuts['→ , L'] = 'Navigate right'; | ||
461 | 1454 | keyboardShortcuts['↑ , K'] = 'Navigate up'; | ||
462 | 1455 | keyboardShortcuts['↓ , J'] = 'Navigate down'; | ||
463 | 1456 | } | ||
464 | 1457 | |||
465 | 1458 | keyboardShortcuts['Home , ⌘/CTRL ←'] = 'First slide'; | ||
466 | 1459 | keyboardShortcuts['End , ⌘/CTRL →'] = 'Last slide'; | ||
467 | 1460 | keyboardShortcuts['B , .'] = 'Pause'; | ||
468 | 1461 | keyboardShortcuts['F'] = 'Fullscreen'; | ||
469 | 1462 | keyboardShortcuts['ESC, O'] = 'Slide overview'; | ||
470 | 1463 | |||
471 | 1266 | sync(); | 1464 | sync(); |
472 | 1267 | 1465 | ||
473 | 1268 | } | 1466 | } |
474 | @@ -1307,7 +1505,7 @@ | |||
475 | 1307 | dom.progress.addEventListener( 'click', onProgressClicked, false ); | 1505 | dom.progress.addEventListener( 'click', onProgressClicked, false ); |
476 | 1308 | } | 1506 | } |
477 | 1309 | 1507 | ||
479 | 1310 | dom.resumeButton.addEventListener( 'click', resume, false ); | 1508 | dom.pauseOverlay.addEventListener( 'click', resume, false ); |
480 | 1311 | 1509 | ||
481 | 1312 | if( config.focusBodyOnPageVisibilityChange ) { | 1510 | if( config.focusBodyOnPageVisibilityChange ) { |
482 | 1313 | var visibilityChange; | 1511 | var visibilityChange; |
483 | @@ -1372,7 +1570,7 @@ | |||
484 | 1372 | dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false ); | 1570 | dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false ); |
485 | 1373 | dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false ); | 1571 | dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false ); |
486 | 1374 | 1572 | ||
488 | 1375 | dom.resumeButton.removeEventListener( 'click', resume, false ); | 1573 | dom.pauseOverlay.removeEventListener( 'click', resume, false ); |
489 | 1376 | 1574 | ||
490 | 1377 | if ( config.progress && dom.progress ) { | 1575 | if ( config.progress && dom.progress ) { |
491 | 1378 | dom.progress.removeEventListener( 'click', onProgressClicked, false ); | 1576 | dom.progress.removeEventListener( 'click', onProgressClicked, false ); |
492 | @@ -1390,6 +1588,53 @@ | |||
493 | 1390 | } | 1588 | } |
494 | 1391 | 1589 | ||
495 | 1392 | /** | 1590 | /** |
496 | 1591 | * Registers a new plugin with this reveal.js instance. | ||
497 | 1592 | * | ||
498 | 1593 | * reveal.js waits for all regisered plugins to initialize | ||
499 | 1594 | * before considering itself ready, as long as the plugin | ||
500 | 1595 | * is registered before calling `Reveal.initialize()`. | ||
501 | 1596 | */ | ||
502 | 1597 | function registerPlugin( id, plugin ) { | ||
503 | 1598 | |||
504 | 1599 | if( plugins[id] === undefined ) { | ||
505 | 1600 | plugins[id] = plugin; | ||
506 | 1601 | |||
507 | 1602 | // If a plugin is registered after reveal.js is loaded, | ||
508 | 1603 | // initialize it right away | ||
509 | 1604 | if( loaded && typeof plugin.init === 'function' ) { | ||
510 | 1605 | plugin.init(); | ||
511 | 1606 | } | ||
512 | 1607 | } | ||
513 | 1608 | else { | ||
514 | 1609 | console.warn( 'reveal.js: "'+ id +'" plugin has already been registered' ); | ||
515 | 1610 | } | ||
516 | 1611 | |||
517 | 1612 | } | ||
518 | 1613 | |||
519 | 1614 | /** | ||
520 | 1615 | * Checks if a specific plugin has been registered. | ||
521 | 1616 | * | ||
522 | 1617 | * @param {String} id Unique plugin identifier | ||
523 | 1618 | */ | ||
524 | 1619 | function hasPlugin( id ) { | ||
525 | 1620 | |||
526 | 1621 | return !!plugins[id]; | ||
527 | 1622 | |||
528 | 1623 | } | ||
529 | 1624 | |||
530 | 1625 | /** | ||
531 | 1626 | * Returns the specific plugin instance, if a plugin | ||
532 | 1627 | * with the given ID has been registered. | ||
533 | 1628 | * | ||
534 | 1629 | * @param {String} id Unique plugin identifier | ||
535 | 1630 | */ | ||
536 | 1631 | function getPlugin( id ) { | ||
537 | 1632 | |||
538 | 1633 | return plugins[id]; | ||
539 | 1634 | |||
540 | 1635 | } | ||
541 | 1636 | |||
542 | 1637 | /** | ||
543 | 1393 | * Add a custom key binding with optional description to | 1638 | * Add a custom key binding with optional description to |
544 | 1394 | * be added to the help screen. | 1639 | * be added to the help screen. |
545 | 1395 | */ | 1640 | */ |
546 | @@ -1677,11 +1922,19 @@ | |||
547 | 1677 | // Change the .stretch element height to 0 in order find the height of all | 1922 | // Change the .stretch element height to 0 in order find the height of all |
548 | 1678 | // the other elements | 1923 | // the other elements |
549 | 1679 | element.style.height = '0px'; | 1924 | element.style.height = '0px'; |
550 | 1925 | |||
551 | 1926 | // In Overview mode, the parent (.slide) height is set of 700px. | ||
552 | 1927 | // Restore it temporarily to its natural height. | ||
553 | 1928 | element.parentNode.style.height = 'auto'; | ||
554 | 1929 | |||
555 | 1680 | newHeight = height - element.parentNode.offsetHeight; | 1930 | newHeight = height - element.parentNode.offsetHeight; |
556 | 1681 | 1931 | ||
557 | 1682 | // Restore the old height, just in case | 1932 | // Restore the old height, just in case |
558 | 1683 | element.style.height = oldHeight + 'px'; | 1933 | element.style.height = oldHeight + 'px'; |
559 | 1684 | 1934 | ||
560 | 1935 | // Clear the parent (.slide) height. .removeProperty works in IE9+ | ||
561 | 1936 | element.parentNode.style.removeProperty('height'); | ||
562 | 1937 | |||
563 | 1685 | return newHeight; | 1938 | return newHeight; |
564 | 1686 | } | 1939 | } |
565 | 1687 | 1940 | ||
566 | @@ -1699,15 +1952,6 @@ | |||
567 | 1699 | } | 1952 | } |
568 | 1700 | 1953 | ||
569 | 1701 | /** | 1954 | /** |
570 | 1702 | * Check if this instance is being used to print a PDF with fragments. | ||
571 | 1703 | */ | ||
572 | 1704 | function isPrintingPDFFragments() { | ||
573 | 1705 | |||
574 | 1706 | return ( /print-pdf-fragments/gi ).test( window.location.search ); | ||
575 | 1707 | |||
576 | 1708 | } | ||
577 | 1709 | |||
578 | 1710 | /** | ||
579 | 1711 | * Hides the address bar if we're on a mobile device. | 1955 | * Hides the address bar if we're on a mobile device. |
580 | 1712 | */ | 1956 | */ |
581 | 1713 | function hideAddressBar() { | 1957 | function hideAddressBar() { |
582 | @@ -1970,8 +2214,20 @@ | |||
583 | 1970 | 2214 | ||
584 | 1971 | if( !config.disableLayout ) { | 2215 | if( !config.disableLayout ) { |
585 | 1972 | 2216 | ||
586 | 2217 | // On some mobile devices '100vh' is taller than the visible | ||
587 | 2218 | // viewport which leads to part of the presentation being | ||
588 | 2219 | // cut off. To work around this we define our own '--vh' custom | ||
589 | 2220 | // property where 100x adds up to the correct height. | ||
590 | 2221 | // | ||
591 | 2222 | // https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ | ||
592 | 2223 | if( isMobileDevice ) { | ||
593 | 2224 | document.documentElement.style.setProperty( '--vh', ( window.innerHeight * 0.01 ) + 'px' ); | ||
594 | 2225 | } | ||
595 | 2226 | |||
596 | 1973 | var size = getComputedSlideSize(); | 2227 | var size = getComputedSlideSize(); |
597 | 1974 | 2228 | ||
598 | 2229 | var oldScale = scale; | ||
599 | 2230 | |||
600 | 1975 | // Layout the contents of the slides | 2231 | // Layout the contents of the slides |
601 | 1976 | layoutSlideContents( config.width, config.height ); | 2232 | layoutSlideContents( config.width, config.height ); |
602 | 1977 | 2233 | ||
603 | @@ -2044,6 +2300,13 @@ | |||
604 | 2044 | 2300 | ||
605 | 2045 | } | 2301 | } |
606 | 2046 | 2302 | ||
607 | 2303 | if( oldScale !== scale ) { | ||
608 | 2304 | dispatchEvent( 'resize', { | ||
609 | 2305 | 'oldScale': oldScale, | ||
610 | 2306 | 'scale': scale, | ||
611 | 2307 | 'size': size | ||
612 | 2308 | } ); | ||
613 | 2309 | } | ||
614 | 2047 | } | 2310 | } |
615 | 2048 | 2311 | ||
616 | 2049 | updateProgress(); | 2312 | updateProgress(); |
617 | @@ -2443,6 +2706,32 @@ | |||
618 | 2443 | } | 2706 | } |
619 | 2444 | 2707 | ||
620 | 2445 | /** | 2708 | /** |
621 | 2709 | * Shows the mouse pointer after it has been hidden with | ||
622 | 2710 | * #hideCursor. | ||
623 | 2711 | */ | ||
624 | 2712 | function showCursor() { | ||
625 | 2713 | |||
626 | 2714 | if( cursorHidden ) { | ||
627 | 2715 | cursorHidden = false; | ||
628 | 2716 | dom.wrapper.style.cursor = ''; | ||
629 | 2717 | } | ||
630 | 2718 | |||
631 | 2719 | } | ||
632 | 2720 | |||
633 | 2721 | /** | ||
634 | 2722 | * Hides the mouse pointer when it's on top of the .reveal | ||
635 | 2723 | * container. | ||
636 | 2724 | */ | ||
637 | 2725 | function hideCursor() { | ||
638 | 2726 | |||
639 | 2727 | if( cursorHidden === false ) { | ||
640 | 2728 | cursorHidden = true; | ||
641 | 2729 | dom.wrapper.style.cursor = 'none'; | ||
642 | 2730 | } | ||
643 | 2731 | |||
644 | 2732 | } | ||
645 | 2733 | |||
646 | 2734 | /** | ||
647 | 2446 | * Enters the paused mode which fades everything on screen to | 2735 | * Enters the paused mode which fades everything on screen to |
648 | 2447 | * black. | 2736 | * black. |
649 | 2448 | */ | 2737 | */ |
650 | @@ -2584,28 +2873,6 @@ | |||
651 | 2584 | 2873 | ||
652 | 2585 | layout(); | 2874 | layout(); |
653 | 2586 | 2875 | ||
654 | 2587 | // Apply the new state | ||
655 | 2588 | stateLoop: for( var i = 0, len = state.length; i < len; i++ ) { | ||
656 | 2589 | // Check if this state existed on the previous slide. If it | ||
657 | 2590 | // did, we will avoid adding it repeatedly | ||
658 | 2591 | for( var j = 0; j < stateBefore.length; j++ ) { | ||
659 | 2592 | if( stateBefore[j] === state[i] ) { | ||
660 | 2593 | stateBefore.splice( j, 1 ); | ||
661 | 2594 | continue stateLoop; | ||
662 | 2595 | } | ||
663 | 2596 | } | ||
664 | 2597 | |||
665 | 2598 | document.documentElement.classList.add( state[i] ); | ||
666 | 2599 | |||
667 | 2600 | // Dispatch custom event matching the state's name | ||
668 | 2601 | dispatchEvent( state[i] ); | ||
669 | 2602 | } | ||
670 | 2603 | |||
671 | 2604 | // Clean up the remains of the previous state | ||
672 | 2605 | while( stateBefore.length ) { | ||
673 | 2606 | document.documentElement.classList.remove( stateBefore.pop() ); | ||
674 | 2607 | } | ||
675 | 2608 | |||
676 | 2609 | // Update the overview if it's currently active | 2876 | // Update the overview if it's currently active |
677 | 2610 | if( isOverview() ) { | 2877 | if( isOverview() ) { |
678 | 2611 | updateOverview(); | 2878 | updateOverview(); |
679 | @@ -2654,6 +2921,28 @@ | |||
680 | 2654 | } | 2921 | } |
681 | 2655 | } | 2922 | } |
682 | 2656 | 2923 | ||
683 | 2924 | // Apply the new state | ||
684 | 2925 | stateLoop: for( var i = 0, len = state.length; i < len; i++ ) { | ||
685 | 2926 | // Check if this state existed on the previous slide. If it | ||
686 | 2927 | // did, we will avoid adding it repeatedly | ||
687 | 2928 | for( var j = 0; j < stateBefore.length; j++ ) { | ||
688 | 2929 | if( stateBefore[j] === state[i] ) { | ||
689 | 2930 | stateBefore.splice( j, 1 ); | ||
690 | 2931 | continue stateLoop; | ||
691 | 2932 | } | ||
692 | 2933 | } | ||
693 | 2934 | |||
694 | 2935 | document.documentElement.classList.add( state[i] ); | ||
695 | 2936 | |||
696 | 2937 | // Dispatch custom event matching the state's name | ||
697 | 2938 | dispatchEvent( state[i] ); | ||
698 | 2939 | } | ||
699 | 2940 | |||
700 | 2941 | // Clean up the remains of the previous state | ||
701 | 2942 | while( stateBefore.length ) { | ||
702 | 2943 | document.documentElement.classList.remove( stateBefore.pop() ); | ||
703 | 2944 | } | ||
704 | 2945 | |||
705 | 2657 | if( slideChanged ) { | 2946 | if( slideChanged ) { |
706 | 2658 | dispatchEvent( 'slidechanged', { | 2947 | dispatchEvent( 'slidechanged', { |
707 | 2659 | 'indexh': indexh, | 2948 | 'indexh': indexh, |
708 | @@ -2679,6 +2968,7 @@ | |||
709 | 2679 | updateParallax(); | 2968 | updateParallax(); |
710 | 2680 | updateSlideNumber(); | 2969 | updateSlideNumber(); |
711 | 2681 | updateNotes(); | 2970 | updateNotes(); |
712 | 2971 | updateFragments(); | ||
713 | 2682 | 2972 | ||
714 | 2683 | // Update the URL hash | 2973 | // Update the URL hash |
715 | 2684 | writeURL(); | 2974 | writeURL(); |
716 | @@ -2751,6 +3041,9 @@ | |||
717 | 2751 | */ | 3041 | */ |
718 | 2752 | function syncSlide( slide ) { | 3042 | function syncSlide( slide ) { |
719 | 2753 | 3043 | ||
720 | 3044 | // Default to the current slide | ||
721 | 3045 | slide = slide || currentSlide; | ||
722 | 3046 | |||
723 | 2754 | syncBackground( slide ); | 3047 | syncBackground( slide ); |
724 | 2755 | syncFragments( slide ); | 3048 | syncFragments( slide ); |
725 | 2756 | 3049 | ||
726 | @@ -2767,10 +3060,14 @@ | |||
727 | 2767 | * after reveal.js has already initialized. | 3060 | * after reveal.js has already initialized. |
728 | 2768 | * | 3061 | * |
729 | 2769 | * @param {HTMLElement} slide | 3062 | * @param {HTMLElement} slide |
730 | 3063 | * @return {Array} a list of the HTML fragments that were synced | ||
731 | 2770 | */ | 3064 | */ |
732 | 2771 | function syncFragments( slide ) { | 3065 | function syncFragments( slide ) { |
733 | 2772 | 3066 | ||
735 | 2773 | sortFragments( slide.querySelectorAll( '.fragment' ) ); | 3067 | // Default to the current slide |
736 | 3068 | slide = slide || currentSlide; | ||
737 | 3069 | |||
738 | 3070 | return sortFragments( slide.querySelectorAll( '.fragment' ) ); | ||
739 | 2774 | 3071 | ||
740 | 2775 | } | 3072 | } |
741 | 2776 | 3073 | ||
742 | @@ -2903,14 +3200,11 @@ | |||
743 | 2903 | element.classList.add( reverse ? 'future' : 'past' ); | 3200 | element.classList.add( reverse ? 'future' : 'past' ); |
744 | 2904 | 3201 | ||
745 | 2905 | if( config.fragments ) { | 3202 | if( config.fragments ) { |
754 | 2906 | var pastFragments = toArray( element.querySelectorAll( '.fragment' ) ); | 3203 | // Show all fragments in prior slides |
755 | 2907 | 3204 | toArray( element.querySelectorAll( '.fragment' ) ).forEach( function( fragment ) { | |
756 | 2908 | // Show all fragments on prior slides | 3205 | fragment.classList.add( 'visible' ); |
757 | 2909 | while( pastFragments.length ) { | 3206 | fragment.classList.remove( 'current-fragment' ); |
758 | 2910 | var pastFragment = pastFragments.pop(); | 3207 | } ); |
751 | 2911 | pastFragment.classList.add( 'visible' ); | ||
752 | 2912 | pastFragment.classList.remove( 'current-fragment' ); | ||
753 | 2913 | } | ||
759 | 2914 | } | 3208 | } |
760 | 2915 | } | 3209 | } |
761 | 2916 | else if( i > index ) { | 3210 | else if( i > index ) { |
762 | @@ -2918,14 +3212,11 @@ | |||
763 | 2918 | element.classList.add( reverse ? 'past' : 'future' ); | 3212 | element.classList.add( reverse ? 'past' : 'future' ); |
764 | 2919 | 3213 | ||
765 | 2920 | if( config.fragments ) { | 3214 | if( config.fragments ) { |
774 | 2921 | var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) ); | 3215 | // Hide all fragments in future slides |
775 | 2922 | 3216 | toArray( element.querySelectorAll( '.fragment.visible' ) ).forEach( function( fragment ) { | |
776 | 2923 | // No fragments in future slides should be visible ahead of time | 3217 | fragment.classList.remove( 'visible' ); |
777 | 2924 | while( futureFragments.length ) { | 3218 | fragment.classList.remove( 'current-fragment' ); |
778 | 2925 | var futureFragment = futureFragments.pop(); | 3219 | } ); |
771 | 2926 | futureFragment.classList.remove( 'visible' ); | ||
772 | 2927 | futureFragment.classList.remove( 'current-fragment' ); | ||
773 | 2928 | } | ||
779 | 2929 | } | 3220 | } |
780 | 2930 | } | 3221 | } |
781 | 2931 | } | 3222 | } |
782 | @@ -3104,47 +3395,47 @@ | |||
783 | 3104 | 3395 | ||
784 | 3105 | 3396 | ||
785 | 3106 | /** | 3397 | /** |
793 | 3107 | * Updates the slide number div to reflect the current slide. | 3398 | * Updates the slide number to match the current slide. |
787 | 3108 | * | ||
788 | 3109 | * The following slide number formats are available: | ||
789 | 3110 | * "h.v": horizontal . vertical slide number (default) | ||
790 | 3111 | * "h/v": horizontal / vertical slide number | ||
791 | 3112 | * "c": flattened slide number | ||
792 | 3113 | * "c/t": flattened slide number / total slides | ||
794 | 3114 | */ | 3399 | */ |
795 | 3115 | function updateSlideNumber() { | 3400 | function updateSlideNumber() { |
796 | 3116 | 3401 | ||
797 | 3117 | // Update slide number if enabled | 3402 | // Update slide number if enabled |
798 | 3118 | if( config.slideNumber && dom.slideNumber ) { | 3403 | if( config.slideNumber && dom.slideNumber ) { |
799 | 3119 | 3404 | ||
801 | 3120 | var value = []; | 3405 | var value; |
802 | 3121 | var format = 'h.v'; | 3406 | var format = 'h.v'; |
803 | 3122 | 3407 | ||
829 | 3123 | // Check if a custom number format is available | 3408 | if( typeof config.slideNumber === 'function' ) { |
830 | 3124 | if( typeof config.slideNumber === 'string' ) { | 3409 | value = config.slideNumber(); |
831 | 3125 | format = config.slideNumber; | 3410 | } |
832 | 3126 | } | 3411 | else { |
833 | 3127 | 3412 | // Check if a custom number format is available | |
834 | 3128 | // If there are ONLY vertical slides in this deck, always use | 3413 | if( typeof config.slideNumber === 'string' ) { |
835 | 3129 | // a flattened slide number | 3414 | format = config.slideNumber; |
836 | 3130 | if( !/c/.test( format ) && dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ).length === 1 ) { | 3415 | } |
837 | 3131 | format = 'c'; | 3416 | |
838 | 3132 | } | 3417 | // If there are ONLY vertical slides in this deck, always use |
839 | 3133 | 3418 | // a flattened slide number | |
840 | 3134 | switch( format ) { | 3419 | if( !/c/.test( format ) && dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ).length === 1 ) { |
841 | 3135 | case 'c': | 3420 | format = 'c'; |
842 | 3136 | value.push( getSlidePastCount() + 1 ); | 3421 | } |
843 | 3137 | break; | 3422 | |
844 | 3138 | case 'c/t': | 3423 | value = []; |
845 | 3139 | value.push( getSlidePastCount() + 1, '/', getTotalSlides() ); | 3424 | switch( format ) { |
846 | 3140 | break; | 3425 | case 'c': |
847 | 3141 | case 'h/v': | 3426 | value.push( getSlidePastCount() + 1 ); |
848 | 3142 | value.push( indexh + 1 ); | 3427 | break; |
849 | 3143 | if( isVerticalSlide() ) value.push( '/', indexv + 1 ); | 3428 | case 'c/t': |
850 | 3144 | break; | 3429 | value.push( getSlidePastCount() + 1, '/', getTotalSlides() ); |
851 | 3145 | default: | 3430 | break; |
852 | 3146 | value.push( indexh + 1 ); | 3431 | case 'h/v': |
853 | 3147 | if( isVerticalSlide() ) value.push( '.', indexv + 1 ); | 3432 | value.push( indexh + 1 ); |
854 | 3433 | if( isVerticalSlide() ) value.push( '/', indexv + 1 ); | ||
855 | 3434 | break; | ||
856 | 3435 | default: | ||
857 | 3436 | value.push( indexh + 1 ); | ||
858 | 3437 | if( isVerticalSlide() ) value.push( '.', indexv + 1 ); | ||
859 | 3438 | } | ||
860 | 3148 | } | 3439 | } |
861 | 3149 | 3440 | ||
862 | 3150 | dom.slideNumber.innerHTML = formatSlideNumber( value[0], value[1], value[2] ); | 3441 | dom.slideNumber.innerHTML = formatSlideNumber( value[0], value[1], value[2] ); |
863 | @@ -3428,6 +3719,26 @@ | |||
864 | 3428 | } | 3719 | } |
865 | 3429 | 3720 | ||
866 | 3430 | /** | 3721 | /** |
867 | 3722 | * Should the given element be preloaded? | ||
868 | 3723 | * Decides based on local element attributes and global config. | ||
869 | 3724 | * | ||
870 | 3725 | * @param {HTMLElement} element | ||
871 | 3726 | */ | ||
872 | 3727 | function shouldPreload( element ) { | ||
873 | 3728 | |||
874 | 3729 | // Prefer an explicit global preload setting | ||
875 | 3730 | var preload = config.preloadIframes; | ||
876 | 3731 | |||
877 | 3732 | // If no global setting is available, fall back on the element's | ||
878 | 3733 | // own preload setting | ||
879 | 3734 | if( typeof preload !== 'boolean' ) { | ||
880 | 3735 | preload = element.hasAttribute( 'data-preload' ); | ||
881 | 3736 | } | ||
882 | 3737 | |||
883 | 3738 | return preload; | ||
884 | 3739 | } | ||
885 | 3740 | |||
886 | 3741 | /** | ||
887 | 3431 | * Called when the given slide is within the configured view | 3742 | * Called when the given slide is within the configured view |
888 | 3432 | * distance. Shows the slide element and loads any content | 3743 | * distance. Shows the slide element and loads any content |
889 | 3433 | * that is set to load lazily (data-src). | 3744 | * that is set to load lazily (data-src). |
890 | @@ -3442,10 +3753,12 @@ | |||
891 | 3442 | slide.style.display = config.display; | 3753 | slide.style.display = config.display; |
892 | 3443 | 3754 | ||
893 | 3444 | // Media elements with data-src attributes | 3755 | // Media elements with data-src attributes |
898 | 3445 | toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) { | 3756 | toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ) ).forEach( function( element ) { |
899 | 3446 | element.setAttribute( 'src', element.getAttribute( 'data-src' ) ); | 3757 | if( element.tagName !== 'IFRAME' || shouldPreload( element ) ) { |
900 | 3447 | element.setAttribute( 'data-lazy-loaded', '' ); | 3758 | element.setAttribute( 'src', element.getAttribute( 'data-src' ) ); |
901 | 3448 | element.removeAttribute( 'data-src' ); | 3759 | element.setAttribute( 'data-lazy-loaded', '' ); |
902 | 3760 | element.removeAttribute( 'data-src' ); | ||
903 | 3761 | } | ||
904 | 3449 | } ); | 3762 | } ); |
905 | 3450 | 3763 | ||
906 | 3451 | // Media elements with <source> children | 3764 | // Media elements with <source> children |
907 | @@ -3563,7 +3876,7 @@ | |||
908 | 3563 | } | 3876 | } |
909 | 3564 | 3877 | ||
910 | 3565 | // Reset lazy-loaded media elements with src attributes | 3878 | // Reset lazy-loaded media elements with src attributes |
912 | 3566 | toArray( slide.querySelectorAll( 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src]' ) ).forEach( function( element ) { | 3879 | toArray( slide.querySelectorAll( 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src], iframe[data-lazy-loaded][src]' ) ).forEach( function( element ) { |
913 | 3567 | element.setAttribute( 'data-src', element.getAttribute( 'src' ) ); | 3880 | element.setAttribute( 'data-src', element.getAttribute( 'src' ) ); |
914 | 3568 | element.removeAttribute( 'src' ); | 3881 | element.removeAttribute( 'src' ); |
915 | 3569 | } ); | 3882 | } ); |
916 | @@ -3663,13 +3976,6 @@ | |||
917 | 3663 | _appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' ); | 3976 | _appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' ); |
918 | 3664 | _appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' ); | 3977 | _appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' ); |
919 | 3665 | 3978 | ||
920 | 3666 | // Always show media controls on mobile devices | ||
921 | 3667 | if( isMobileDevice ) { | ||
922 | 3668 | toArray( dom.slides.querySelectorAll( 'video, audio' ) ).forEach( function( el ) { | ||
923 | 3669 | el.controls = true; | ||
924 | 3670 | } ); | ||
925 | 3671 | } | ||
926 | 3672 | |||
927 | 3673 | } | 3979 | } |
928 | 3674 | 3980 | ||
929 | 3675 | /** | 3981 | /** |
930 | @@ -3713,7 +4019,20 @@ | |||
931 | 3713 | // Mobile devices never fire a loaded event so instead | 4019 | // Mobile devices never fire a loaded event so instead |
932 | 3714 | // of waiting, we initiate playback | 4020 | // of waiting, we initiate playback |
933 | 3715 | else if( isMobileDevice ) { | 4021 | else if( isMobileDevice ) { |
935 | 3716 | el.play(); | 4022 | var promise = el.play(); |
936 | 4023 | |||
937 | 4024 | // If autoplay does not work, ensure that the controls are visible so | ||
938 | 4025 | // that the viewer can start the media on their own | ||
939 | 4026 | if( promise && typeof promise.catch === 'function' && el.controls === false ) { | ||
940 | 4027 | promise.catch( function() { | ||
941 | 4028 | el.controls = true; | ||
942 | 4029 | |||
943 | 4030 | // Once the video does start playing, hide the controls again | ||
944 | 4031 | el.addEventListener( 'play', function() { | ||
945 | 4032 | el.controls = false; | ||
946 | 4033 | } ); | ||
947 | 4034 | } ); | ||
948 | 4035 | } | ||
949 | 3717 | } | 4036 | } |
950 | 3718 | // If the media isn't loaded, wait before playing | 4037 | // If the media isn't loaded, wait before playing |
951 | 3719 | else { | 4038 | else { |
952 | @@ -3947,7 +4266,7 @@ | |||
953 | 3947 | 4266 | ||
954 | 3948 | } | 4267 | } |
955 | 3949 | 4268 | ||
957 | 3950 | return pastCount / ( totalCount - 1 ); | 4269 | return Math.min( pastCount / ( totalCount - 1 ), 1 ); |
958 | 3951 | 4270 | ||
959 | 3952 | } | 4271 | } |
960 | 3953 | 4272 | ||
961 | @@ -3974,9 +4293,9 @@ | |||
962 | 3974 | var bits = hash.slice( 2 ).split( '/' ), | 4293 | var bits = hash.slice( 2 ).split( '/' ), |
963 | 3975 | name = hash.replace( /#|\//gi, '' ); | 4294 | name = hash.replace( /#|\//gi, '' ); |
964 | 3976 | 4295 | ||
968 | 3977 | // If the first bit is invalid and there is a name we can | 4296 | // If the first bit is not fully numeric and there is a name we |
969 | 3978 | // assume that this is a named link | 4297 | // can assume that this is a named link |
970 | 3979 | if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) { | 4298 | if( !/^[0-9]*$/.test( bits[0] ) && name.length ) { |
971 | 3980 | var element; | 4299 | var element; |
972 | 3981 | 4300 | ||
973 | 3982 | // Ensure the named link is a valid HTML ID attribute | 4301 | // Ensure the named link is a valid HTML ID attribute |
974 | @@ -3988,10 +4307,13 @@ | |||
975 | 3988 | // Ensure that we're not already on a slide with the same name | 4307 | // Ensure that we're not already on a slide with the same name |
976 | 3989 | var isSameNameAsCurrentSlide = currentSlide ? currentSlide.getAttribute( 'id' ) === name : false; | 4308 | var isSameNameAsCurrentSlide = currentSlide ? currentSlide.getAttribute( 'id' ) === name : false; |
977 | 3990 | 4309 | ||
982 | 3991 | if( element && !isSameNameAsCurrentSlide ) { | 4310 | if( element ) { |
983 | 3992 | // Find the position of the named slide and navigate to it | 4311 | // If the slide exists and is not the current slide... |
984 | 3993 | var indices = Reveal.getIndices( element ); | 4312 | if ( !isSameNameAsCurrentSlide ) { |
985 | 3994 | slide( indices.h, indices.v ); | 4313 | // ...find the position of the named slide and navigate to it |
986 | 4314 | var indices = Reveal.getIndices(element); | ||
987 | 4315 | slide(indices.h, indices.v); | ||
988 | 4316 | } | ||
989 | 3995 | } | 4317 | } |
990 | 3996 | // If the slide doesn't exist, navigate to the current slide | 4318 | // If the slide doesn't exist, navigate to the current slide |
991 | 3997 | else { | 4319 | else { |
992 | @@ -4029,18 +4351,30 @@ | |||
993 | 4029 | */ | 4351 | */ |
994 | 4030 | function writeURL( delay ) { | 4352 | function writeURL( delay ) { |
995 | 4031 | 4353 | ||
1006 | 4032 | if( config.history ) { | 4354 | // Make sure there's never more than one timeout running |
1007 | 4033 | 4355 | clearTimeout( writeURLTimeout ); | |
1008 | 4034 | // Make sure there's never more than one timeout running | 4356 | |
1009 | 4035 | clearTimeout( writeURLTimeout ); | 4357 | // If a delay is specified, timeout this call |
1010 | 4036 | 4358 | if( typeof delay === 'number' ) { | |
1011 | 4037 | // If a delay is specified, timeout this call | 4359 | writeURLTimeout = setTimeout( writeURL, delay ); |
1012 | 4038 | if( typeof delay === 'number' ) { | 4360 | } |
1013 | 4039 | writeURLTimeout = setTimeout( writeURL, delay ); | 4361 | else if( currentSlide ) { |
1014 | 4040 | } | 4362 | // If we're configured to push to history OR the history |
1015 | 4041 | else if( currentSlide ) { | 4363 | // API is not avaialble. |
1016 | 4364 | if( config.history || !window.history ) { | ||
1017 | 4042 | window.location.hash = locationHash(); | 4365 | window.location.hash = locationHash(); |
1018 | 4043 | } | 4366 | } |
1019 | 4367 | // If we're configured to reflect the current slide in the | ||
1020 | 4368 | // URL without pushing to history. | ||
1021 | 4369 | else if( config.hash ) { | ||
1022 | 4370 | window.history.replaceState( null, null, '#' + locationHash() ); | ||
1023 | 4371 | } | ||
1024 | 4372 | // If history and hash are both disabled, a hash may still | ||
1025 | 4373 | // be added to the URL by clicking on a href with a hash | ||
1026 | 4374 | // target. Counter this by always removing the hash. | ||
1027 | 4375 | else { | ||
1028 | 4376 | window.history.replaceState( null, null, window.location.pathname + window.location.search ); | ||
1029 | 4377 | } | ||
1030 | 4044 | } | 4378 | } |
1031 | 4045 | 4379 | ||
1032 | 4046 | } | 4380 | } |
1033 | @@ -4108,6 +4442,25 @@ | |||
1034 | 4108 | } | 4442 | } |
1035 | 4109 | 4443 | ||
1036 | 4110 | /** | 4444 | /** |
1037 | 4445 | * Returns an array of objects where each object represents the | ||
1038 | 4446 | * attributes on its respective slide. | ||
1039 | 4447 | */ | ||
1040 | 4448 | function getSlidesAttributes() { | ||
1041 | 4449 | |||
1042 | 4450 | return getSlides().map( function( slide ) { | ||
1043 | 4451 | |||
1044 | 4452 | var attributes = {}; | ||
1045 | 4453 | for( var i = 0; i < slide.attributes.length; i++ ) { | ||
1046 | 4454 | var attribute = slide.attributes[ i ]; | ||
1047 | 4455 | attributes[ attribute.name ] = attribute.value; | ||
1048 | 4456 | } | ||
1049 | 4457 | return attributes; | ||
1050 | 4458 | |||
1051 | 4459 | } ); | ||
1052 | 4460 | |||
1053 | 4461 | } | ||
1054 | 4462 | |||
1055 | 4463 | /** | ||
1056 | 4111 | * Retrieves the total number of slides in this presentation. | 4464 | * Retrieves the total number of slides in this presentation. |
1057 | 4112 | * | 4465 | * |
1058 | 4113 | * @return {number} | 4466 | * @return {number} |
1059 | @@ -4300,6 +4653,73 @@ | |||
1060 | 4300 | } | 4653 | } |
1061 | 4301 | 4654 | ||
1062 | 4302 | /** | 4655 | /** |
1063 | 4656 | * Refreshes the fragments on the current slide so that they | ||
1064 | 4657 | * have the appropriate classes (.visible + .current-fragment). | ||
1065 | 4658 | * | ||
1066 | 4659 | * @param {number} [index] The index of the current fragment | ||
1067 | 4660 | * @param {array} [fragments] Array containing all fragments | ||
1068 | 4661 | * in the current slide | ||
1069 | 4662 | * | ||
1070 | 4663 | * @return {{shown: array, hidden: array}} | ||
1071 | 4664 | */ | ||
1072 | 4665 | function updateFragments( index, fragments ) { | ||
1073 | 4666 | |||
1074 | 4667 | var changedFragments = { | ||
1075 | 4668 | shown: [], | ||
1076 | 4669 | hidden: [] | ||
1077 | 4670 | }; | ||
1078 | 4671 | |||
1079 | 4672 | if( currentSlide && config.fragments ) { | ||
1080 | 4673 | |||
1081 | 4674 | fragments = fragments || sortFragments( currentSlide.querySelectorAll( '.fragment' ) ); | ||
1082 | 4675 | |||
1083 | 4676 | if( fragments.length ) { | ||
1084 | 4677 | |||
1085 | 4678 | if( typeof index !== 'number' ) { | ||
1086 | 4679 | var currentFragment = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop(); | ||
1087 | 4680 | if( currentFragment ) { | ||
1088 | 4681 | index = parseInt( currentFragment.getAttribute( 'data-fragment-index' ) || 0, 10 ); | ||
1089 | 4682 | } | ||
1090 | 4683 | } | ||
1091 | 4684 | |||
1092 | 4685 | toArray( fragments ).forEach( function( el, i ) { | ||
1093 | 4686 | |||
1094 | 4687 | if( el.hasAttribute( 'data-fragment-index' ) ) { | ||
1095 | 4688 | i = parseInt( el.getAttribute( 'data-fragment-index' ), 10 ); | ||
1096 | 4689 | } | ||
1097 | 4690 | |||
1098 | 4691 | // Visible fragments | ||
1099 | 4692 | if( i <= index ) { | ||
1100 | 4693 | if( !el.classList.contains( 'visible' ) ) changedFragments.shown.push( el ); | ||
1101 | 4694 | el.classList.add( 'visible' ); | ||
1102 | 4695 | el.classList.remove( 'current-fragment' ); | ||
1103 | 4696 | |||
1104 | 4697 | // Announce the fragments one by one to the Screen Reader | ||
1105 | 4698 | dom.statusDiv.textContent = getStatusText( el ); | ||
1106 | 4699 | |||
1107 | 4700 | if( i === index ) { | ||
1108 | 4701 | el.classList.add( 'current-fragment' ); | ||
1109 | 4702 | startEmbeddedContent( el ); | ||
1110 | 4703 | } | ||
1111 | 4704 | } | ||
1112 | 4705 | // Hidden fragments | ||
1113 | 4706 | else { | ||
1114 | 4707 | if( el.classList.contains( 'visible' ) ) changedFragments.hidden.push( el ); | ||
1115 | 4708 | el.classList.remove( 'visible' ); | ||
1116 | 4709 | el.classList.remove( 'current-fragment' ); | ||
1117 | 4710 | } | ||
1118 | 4711 | |||
1119 | 4712 | } ); | ||
1120 | 4713 | |||
1121 | 4714 | } | ||
1122 | 4715 | |||
1123 | 4716 | } | ||
1124 | 4717 | |||
1125 | 4718 | return changedFragments; | ||
1126 | 4719 | |||
1127 | 4720 | } | ||
1128 | 4721 | |||
1129 | 4722 | /** | ||
1130 | 4303 | * Navigate to the specified slide fragment. | 4723 | * Navigate to the specified slide fragment. |
1131 | 4304 | * | 4724 | * |
1132 | 4305 | * @param {?number} index The index of the fragment that | 4725 | * @param {?number} index The index of the fragment that |
1133 | @@ -4334,53 +4754,24 @@ | |||
1134 | 4334 | index += offset; | 4754 | index += offset; |
1135 | 4335 | } | 4755 | } |
1136 | 4336 | 4756 | ||
1171 | 4337 | var fragmentsShown = [], | 4757 | var changedFragments = updateFragments( index, fragments ); |
1172 | 4338 | fragmentsHidden = []; | 4758 | |
1173 | 4339 | 4759 | if( changedFragments.hidden.length ) { | |
1174 | 4340 | toArray( fragments ).forEach( function( element, i ) { | 4760 | dispatchEvent( 'fragmenthidden', { fragment: changedFragments.hidden[0], fragments: changedFragments.hidden } ); |
1141 | 4341 | |||
1142 | 4342 | if( element.hasAttribute( 'data-fragment-index' ) ) { | ||
1143 | 4343 | i = parseInt( element.getAttribute( 'data-fragment-index' ), 10 ); | ||
1144 | 4344 | } | ||
1145 | 4345 | |||
1146 | 4346 | // Visible fragments | ||
1147 | 4347 | if( i <= index ) { | ||
1148 | 4348 | if( !element.classList.contains( 'visible' ) ) fragmentsShown.push( element ); | ||
1149 | 4349 | element.classList.add( 'visible' ); | ||
1150 | 4350 | element.classList.remove( 'current-fragment' ); | ||
1151 | 4351 | |||
1152 | 4352 | // Announce the fragments one by one to the Screen Reader | ||
1153 | 4353 | dom.statusDiv.textContent = getStatusText( element ); | ||
1154 | 4354 | |||
1155 | 4355 | if( i === index ) { | ||
1156 | 4356 | element.classList.add( 'current-fragment' ); | ||
1157 | 4357 | startEmbeddedContent( element ); | ||
1158 | 4358 | } | ||
1159 | 4359 | } | ||
1160 | 4360 | // Hidden fragments | ||
1161 | 4361 | else { | ||
1162 | 4362 | if( element.classList.contains( 'visible' ) ) fragmentsHidden.push( element ); | ||
1163 | 4363 | element.classList.remove( 'visible' ); | ||
1164 | 4364 | element.classList.remove( 'current-fragment' ); | ||
1165 | 4365 | } | ||
1166 | 4366 | |||
1167 | 4367 | } ); | ||
1168 | 4368 | |||
1169 | 4369 | if( fragmentsHidden.length ) { | ||
1170 | 4370 | dispatchEvent( 'fragmenthidden', { fragment: fragmentsHidden[0], fragments: fragmentsHidden } ); | ||
1175 | 4371 | } | 4761 | } |
1176 | 4372 | 4762 | ||
1179 | 4373 | if( fragmentsShown.length ) { | 4763 | if( changedFragments.shown.length ) { |
1180 | 4374 | dispatchEvent( 'fragmentshown', { fragment: fragmentsShown[0], fragments: fragmentsShown } ); | 4764 | dispatchEvent( 'fragmentshown', { fragment: changedFragments.shown[0], fragments: changedFragments.shown } ); |
1181 | 4375 | } | 4765 | } |
1182 | 4376 | 4766 | ||
1183 | 4377 | updateControls(); | 4767 | updateControls(); |
1184 | 4378 | updateProgress(); | 4768 | updateProgress(); |
1185 | 4769 | |||
1186 | 4379 | if( config.fragmentInURL ) { | 4770 | if( config.fragmentInURL ) { |
1187 | 4380 | writeURL(); | 4771 | writeURL(); |
1188 | 4381 | } | 4772 | } |
1189 | 4382 | 4773 | ||
1191 | 4383 | return !!( fragmentsShown.length || fragmentsHidden.length ); | 4774 | return !!( changedFragments.shown.length || changedFragments.hidden.length ); |
1192 | 4384 | 4775 | ||
1193 | 4385 | } | 4776 | } |
1194 | 4386 | 4777 | ||
1195 | @@ -4527,12 +4918,12 @@ | |||
1196 | 4527 | // Reverse for RTL | 4918 | // Reverse for RTL |
1197 | 4528 | if( config.rtl ) { | 4919 | if( config.rtl ) { |
1198 | 4529 | if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) { | 4920 | if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) { |
1200 | 4530 | slide( indexh + 1 ); | 4921 | slide( indexh + 1, config.navigationMode === 'grid' ? indexv : undefined ); |
1201 | 4531 | } | 4922 | } |
1202 | 4532 | } | 4923 | } |
1203 | 4533 | // Normal navigation | 4924 | // Normal navigation |
1204 | 4534 | else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) { | 4925 | else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) { |
1206 | 4535 | slide( indexh - 1 ); | 4926 | slide( indexh - 1, config.navigationMode === 'grid' ? indexv : undefined ); |
1207 | 4536 | } | 4927 | } |
1208 | 4537 | 4928 | ||
1209 | 4538 | } | 4929 | } |
1210 | @@ -4544,12 +4935,12 @@ | |||
1211 | 4544 | // Reverse for RTL | 4935 | // Reverse for RTL |
1212 | 4545 | if( config.rtl ) { | 4936 | if( config.rtl ) { |
1213 | 4546 | if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) { | 4937 | if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) { |
1215 | 4547 | slide( indexh - 1 ); | 4938 | slide( indexh - 1, config.navigationMode === 'grid' ? indexv : undefined ); |
1216 | 4548 | } | 4939 | } |
1217 | 4549 | } | 4940 | } |
1218 | 4550 | // Normal navigation | 4941 | // Normal navigation |
1219 | 4551 | else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) { | 4942 | else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) { |
1221 | 4552 | slide( indexh + 1 ); | 4943 | slide( indexh + 1, config.navigationMode === 'grid' ? indexv : undefined ); |
1222 | 4553 | } | 4944 | } |
1223 | 4554 | 4945 | ||
1224 | 4555 | } | 4946 | } |
1225 | @@ -4676,6 +5067,22 @@ | |||
1226 | 4676 | } | 5067 | } |
1227 | 4677 | 5068 | ||
1228 | 4678 | /** | 5069 | /** |
1229 | 5070 | * Called whenever there is mouse input at the document level | ||
1230 | 5071 | * to determine if the cursor is active or not. | ||
1231 | 5072 | * | ||
1232 | 5073 | * @param {object} event | ||
1233 | 5074 | */ | ||
1234 | 5075 | function onDocumentCursorActive( event ) { | ||
1235 | 5076 | |||
1236 | 5077 | showCursor(); | ||
1237 | 5078 | |||
1238 | 5079 | clearTimeout( cursorInactiveTimeout ); | ||
1239 | 5080 | |||
1240 | 5081 | cursorInactiveTimeout = setTimeout( hideCursor, config.hideCursorTime ); | ||
1241 | 5082 | |||
1242 | 5083 | } | ||
1243 | 5084 | |||
1244 | 5085 | /** | ||
1245 | 4679 | * Handler for the document level 'keypress' event. | 5086 | * Handler for the document level 'keypress' event. |
1246 | 4680 | * | 5087 | * |
1247 | 4681 | * @param {object} event | 5088 | * @param {object} event |
1248 | @@ -4702,20 +5109,31 @@ | |||
1249 | 4702 | return true; | 5109 | return true; |
1250 | 4703 | } | 5110 | } |
1251 | 4704 | 5111 | ||
1252 | 5112 | // Shorthand | ||
1253 | 5113 | var keyCode = event.keyCode; | ||
1254 | 5114 | |||
1255 | 4705 | // Remember if auto-sliding was paused so we can toggle it | 5115 | // Remember if auto-sliding was paused so we can toggle it |
1256 | 4706 | var autoSlideWasPaused = autoSlidePaused; | 5116 | var autoSlideWasPaused = autoSlidePaused; |
1257 | 4707 | 5117 | ||
1258 | 4708 | onUserInput( event ); | 5118 | onUserInput( event ); |
1259 | 4709 | 5119 | ||
1262 | 4710 | // Check if there's a focused element that could be using | 5120 | // Is there a focused element that could be using the keyboard? |
1261 | 4711 | // the keyboard | ||
1263 | 4712 | var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit'; | 5121 | var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit'; |
1264 | 4713 | var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName ); | 5122 | var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName ); |
1265 | 4714 | var activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className); | 5123 | var activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className); |
1266 | 4715 | 5124 | ||
1267 | 5125 | // Whitelist specific modified + keycode combinations | ||
1268 | 5126 | var prevSlideShortcut = event.shiftKey && event.keyCode === 32; | ||
1269 | 5127 | var firstSlideShortcut = ( event.metaKey || event.ctrlKey ) && keyCode === 37; | ||
1270 | 5128 | var lastSlideShortcut = ( event.metaKey || event.ctrlKey ) && keyCode === 39; | ||
1271 | 5129 | |||
1272 | 5130 | // Prevent all other events when a modifier is pressed | ||
1273 | 5131 | var unusedModifier = !prevSlideShortcut && !firstSlideShortcut && !lastSlideShortcut && | ||
1274 | 5132 | ( event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ); | ||
1275 | 5133 | |||
1276 | 4716 | // Disregard the event if there's a focused element or a | 5134 | // Disregard the event if there's a focused element or a |
1277 | 4717 | // keyboard modifier key is present | 5135 | // keyboard modifier key is present |
1279 | 4718 | if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return; | 5136 | if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || unusedModifier ) return; |
1280 | 4719 | 5137 | ||
1281 | 4720 | // While paused only allow resume keyboard events; 'b', 'v', '.' | 5138 | // While paused only allow resume keyboard events; 'b', 'v', '.' |
1282 | 4721 | var resumeKeyCodes = [66,86,190,191]; | 5139 | var resumeKeyCodes = [66,86,190,191]; |
1283 | @@ -4730,7 +5148,7 @@ | |||
1284 | 4730 | } | 5148 | } |
1285 | 4731 | } | 5149 | } |
1286 | 4732 | 5150 | ||
1288 | 4733 | if( isPaused() && resumeKeyCodes.indexOf( event.keyCode ) === -1 ) { | 5151 | if( isPaused() && resumeKeyCodes.indexOf( keyCode ) === -1 ) { |
1289 | 4734 | return false; | 5152 | return false; |
1290 | 4735 | } | 5153 | } |
1291 | 4736 | 5154 | ||
1292 | @@ -4742,7 +5160,7 @@ | |||
1293 | 4742 | for( key in config.keyboard ) { | 5160 | for( key in config.keyboard ) { |
1294 | 4743 | 5161 | ||
1295 | 4744 | // Check if this binding matches the pressed key | 5162 | // Check if this binding matches the pressed key |
1297 | 4745 | if( parseInt( key, 10 ) === event.keyCode ) { | 5163 | if( parseInt( key, 10 ) === keyCode ) { |
1298 | 4746 | 5164 | ||
1299 | 4747 | var value = config.keyboard[ key ]; | 5165 | var value = config.keyboard[ key ]; |
1300 | 4748 | 5166 | ||
1301 | @@ -4769,7 +5187,7 @@ | |||
1302 | 4769 | for( key in registeredKeyBindings ) { | 5187 | for( key in registeredKeyBindings ) { |
1303 | 4770 | 5188 | ||
1304 | 4771 | // Check if this binding matches the pressed key | 5189 | // Check if this binding matches the pressed key |
1306 | 4772 | if( parseInt( key, 10 ) === event.keyCode ) { | 5190 | if( parseInt( key, 10 ) === keyCode ) { |
1307 | 4773 | 5191 | ||
1308 | 4774 | var action = registeredKeyBindings[ key ].callback; | 5192 | var action = registeredKeyBindings[ key ].callback; |
1309 | 4775 | 5193 | ||
1310 | @@ -4793,35 +5211,92 @@ | |||
1311 | 4793 | // Assume true and try to prove false | 5211 | // Assume true and try to prove false |
1312 | 4794 | triggered = true; | 5212 | triggered = true; |
1313 | 4795 | 5213 | ||
1343 | 4796 | switch( event.keyCode ) { | 5214 | // P, PAGE UP |
1344 | 4797 | // p, page up | 5215 | if( keyCode === 80 || keyCode === 33 ) { |
1345 | 4798 | case 80: case 33: navigatePrev(); break; | 5216 | navigatePrev(); |
1346 | 4799 | // n, page down | 5217 | } |
1347 | 4800 | case 78: case 34: navigateNext(); break; | 5218 | // N, PAGE DOWN |
1348 | 4801 | // h, left | 5219 | else if( keyCode === 78 || keyCode === 34 ) { |
1349 | 4802 | case 72: case 37: navigateLeft(); break; | 5220 | navigateNext(); |
1350 | 4803 | // l, right | 5221 | } |
1351 | 4804 | case 76: case 39: navigateRight(); break; | 5222 | // H, LEFT |
1352 | 4805 | // k, up | 5223 | else if( keyCode === 72 || keyCode === 37 ) { |
1353 | 4806 | case 75: case 38: navigateUp(); break; | 5224 | if( firstSlideShortcut ) { |
1354 | 4807 | // j, down | 5225 | slide( 0 ); |
1355 | 4808 | case 74: case 40: navigateDown(); break; | 5226 | } |
1356 | 4809 | // home | 5227 | else if( !isOverview() && config.navigationMode === 'linear' ) { |
1357 | 4810 | case 36: slide( 0 ); break; | 5228 | navigatePrev(); |
1358 | 4811 | // end | 5229 | } |
1359 | 4812 | case 35: slide( Number.MAX_VALUE ); break; | 5230 | else { |
1360 | 4813 | // space | 5231 | navigateLeft(); |
1361 | 4814 | case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break; | 5232 | } |
1362 | 4815 | // return | 5233 | } |
1363 | 4816 | case 13: isOverview() ? deactivateOverview() : triggered = false; break; | 5234 | // L, RIGHT |
1364 | 4817 | // two-spot, semicolon, b, v, period, Logitech presenter tools "black screen" button | 5235 | else if( keyCode === 76 || keyCode === 39 ) { |
1365 | 4818 | case 58: case 59: case 66: case 86: case 190: case 191: togglePause(); break; | 5236 | if( lastSlideShortcut ) { |
1366 | 4819 | // f | 5237 | slide( Number.MAX_VALUE ); |
1367 | 4820 | case 70: enterFullscreen(); break; | 5238 | } |
1368 | 4821 | // a | 5239 | else if( !isOverview() && config.navigationMode === 'linear' ) { |
1369 | 4822 | case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break; | 5240 | navigateNext(); |
1370 | 4823 | default: | 5241 | } |
1371 | 4824 | triggered = false; | 5242 | else { |
1372 | 5243 | navigateRight(); | ||
1373 | 5244 | } | ||
1374 | 5245 | } | ||
1375 | 5246 | // K, UP | ||
1376 | 5247 | else if( keyCode === 75 || keyCode === 38 ) { | ||
1377 | 5248 | if( !isOverview() && config.navigationMode === 'linear' ) { | ||
1378 | 5249 | navigatePrev(); | ||
1379 | 5250 | } | ||
1380 | 5251 | else { | ||
1381 | 5252 | navigateUp(); | ||
1382 | 5253 | } | ||
1383 | 5254 | } | ||
1384 | 5255 | // J, DOWN | ||
1385 | 5256 | else if( keyCode === 74 || keyCode === 40 ) { | ||
1386 | 5257 | if( !isOverview() && config.navigationMode === 'linear' ) { | ||
1387 | 5258 | navigateNext(); | ||
1388 | 5259 | } | ||
1389 | 5260 | else { | ||
1390 | 5261 | navigateDown(); | ||
1391 | 5262 | } | ||
1392 | 5263 | } | ||
1393 | 5264 | // HOME | ||
1394 | 5265 | else if( keyCode === 36 ) { | ||
1395 | 5266 | slide( 0 ); | ||
1396 | 5267 | } | ||
1397 | 5268 | // END | ||
1398 | 5269 | else if( keyCode === 35 ) { | ||
1399 | 5270 | slide( Number.MAX_VALUE ); | ||
1400 | 5271 | } | ||
1401 | 5272 | // SPACE | ||
1402 | 5273 | else if( keyCode === 32 ) { | ||
1403 | 5274 | if( isOverview() ) { | ||
1404 | 5275 | deactivateOverview(); | ||
1405 | 5276 | } | ||
1406 | 5277 | if( event.shiftKey ) { | ||
1407 | 5278 | navigatePrev(); | ||
1408 | 5279 | } | ||
1409 | 5280 | else { | ||
1410 | 5281 | navigateNext(); | ||
1411 | 5282 | } | ||
1412 | 5283 | } | ||
1413 | 5284 | // TWO-SPOT, SEMICOLON, B, V, PERIOD, LOGITECH PRESENTER TOOLS "BLACK SCREEN" BUTTON | ||
1414 | 5285 | else if( keyCode === 58 || keyCode === 59 || keyCode === 66 || keyCode === 86 || keyCode === 190 || keyCode === 191 ) { | ||
1415 | 5286 | togglePause(); | ||
1416 | 5287 | } | ||
1417 | 5288 | // F | ||
1418 | 5289 | else if( keyCode === 70 ) { | ||
1419 | 5290 | enterFullscreen(); | ||
1420 | 5291 | } | ||
1421 | 5292 | // A | ||
1422 | 5293 | else if( keyCode === 65 ) { | ||
1423 | 5294 | if ( config.autoSlideStoppable ) { | ||
1424 | 5295 | toggleAutoSlide( autoSlideWasPaused ); | ||
1425 | 5296 | } | ||
1426 | 5297 | } | ||
1427 | 5298 | else { | ||
1428 | 5299 | triggered = false; | ||
1429 | 4825 | } | 5300 | } |
1430 | 4826 | 5301 | ||
1431 | 4827 | } | 5302 | } |
1432 | @@ -4832,7 +5307,7 @@ | |||
1433 | 4832 | event.preventDefault && event.preventDefault(); | 5307 | event.preventDefault && event.preventDefault(); |
1434 | 4833 | } | 5308 | } |
1435 | 4834 | // ESC or O key | 5309 | // ESC or O key |
1437 | 4835 | else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) { | 5310 | else if ( ( keyCode === 27 || keyCode === 79 ) && features.transforms3d ) { |
1438 | 4836 | if( dom.overlay ) { | 5311 | if( dom.overlay ) { |
1439 | 4837 | closeOverlay(); | 5312 | closeOverlay(); |
1440 | 4838 | } | 5313 | } |
1441 | @@ -4863,18 +5338,6 @@ | |||
1442 | 4863 | touch.startY = event.touches[0].clientY; | 5338 | touch.startY = event.touches[0].clientY; |
1443 | 4864 | touch.startCount = event.touches.length; | 5339 | touch.startCount = event.touches.length; |
1444 | 4865 | 5340 | ||
1445 | 4866 | // If there's two touches we need to memorize the distance | ||
1446 | 4867 | // between those two points to detect pinching | ||
1447 | 4868 | if( event.touches.length === 2 && config.overview ) { | ||
1448 | 4869 | touch.startSpan = distanceBetween( { | ||
1449 | 4870 | x: event.touches[1].clientX, | ||
1450 | 4871 | y: event.touches[1].clientY | ||
1451 | 4872 | }, { | ||
1452 | 4873 | x: touch.startX, | ||
1453 | 4874 | y: touch.startY | ||
1454 | 4875 | } ); | ||
1455 | 4876 | } | ||
1456 | 4877 | |||
1457 | 4878 | } | 5341 | } |
1458 | 4879 | 5342 | ||
1459 | 4880 | /** | 5343 | /** |
1460 | @@ -4893,37 +5356,8 @@ | |||
1461 | 4893 | var currentX = event.touches[0].clientX; | 5356 | var currentX = event.touches[0].clientX; |
1462 | 4894 | var currentY = event.touches[0].clientY; | 5357 | var currentY = event.touches[0].clientY; |
1463 | 4895 | 5358 | ||
1464 | 4896 | // If the touch started with two points and still has | ||
1465 | 4897 | // two active touches; test for the pinch gesture | ||
1466 | 4898 | if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) { | ||
1467 | 4899 | |||
1468 | 4900 | // The current distance in pixels between the two touch points | ||
1469 | 4901 | var currentSpan = distanceBetween( { | ||
1470 | 4902 | x: event.touches[1].clientX, | ||
1471 | 4903 | y: event.touches[1].clientY | ||
1472 | 4904 | }, { | ||
1473 | 4905 | x: touch.startX, | ||
1474 | 4906 | y: touch.startY | ||
1475 | 4907 | } ); | ||
1476 | 4908 | |||
1477 | 4909 | // If the span is larger than the desire amount we've got | ||
1478 | 4910 | // ourselves a pinch | ||
1479 | 4911 | if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) { | ||
1480 | 4912 | touch.captured = true; | ||
1481 | 4913 | |||
1482 | 4914 | if( currentSpan < touch.startSpan ) { | ||
1483 | 4915 | activateOverview(); | ||
1484 | 4916 | } | ||
1485 | 4917 | else { | ||
1486 | 4918 | deactivateOverview(); | ||
1487 | 4919 | } | ||
1488 | 4920 | } | ||
1489 | 4921 | |||
1490 | 4922 | event.preventDefault(); | ||
1491 | 4923 | |||
1492 | 4924 | } | ||
1493 | 4925 | // There was only one touch point, look for a swipe | 5359 | // There was only one touch point, look for a swipe |
1495 | 4926 | else if( event.touches.length === 1 && touch.startCount !== 2 ) { | 5360 | if( event.touches.length === 1 && touch.startCount !== 2 ) { |
1496 | 4927 | 5361 | ||
1497 | 4928 | var deltaX = currentX - touch.startX, | 5362 | var deltaX = currentX - touch.startX, |
1498 | 4929 | deltaY = currentY - touch.startY; | 5363 | deltaY = currentY - touch.startY; |
1499 | @@ -5073,8 +5507,8 @@ | |||
1500 | 5073 | /** | 5507 | /** |
1501 | 5074 | * Event handler for navigation control buttons. | 5508 | * Event handler for navigation control buttons. |
1502 | 5075 | */ | 5509 | */ |
1505 | 5076 | function onNavigateLeftClicked( event ) { event.preventDefault(); onUserInput(); navigateLeft(); } | 5510 | function onNavigateLeftClicked( event ) { event.preventDefault(); onUserInput(); config.navigationMode === 'linear' ? navigatePrev() : navigateLeft(); } |
1506 | 5077 | function onNavigateRightClicked( event ) { event.preventDefault(); onUserInput(); navigateRight(); } | 5511 | function onNavigateRightClicked( event ) { event.preventDefault(); onUserInput(); config.navigationMode === 'linear' ? navigateNext() : navigateRight(); } |
1507 | 5078 | function onNavigateUpClicked( event ) { event.preventDefault(); onUserInput(); navigateUp(); } | 5512 | function onNavigateUpClicked( event ) { event.preventDefault(); onUserInput(); navigateUp(); } |
1508 | 5079 | function onNavigateDownClicked( event ) { event.preventDefault(); onUserInput(); navigateDown(); } | 5513 | function onNavigateDownClicked( event ) { event.preventDefault(); onUserInput(); navigateDown(); } |
1509 | 5080 | function onNavigatePrevClicked( event ) { event.preventDefault(); onUserInput(); navigatePrev(); } | 5514 | function onNavigatePrevClicked( event ) { event.preventDefault(); onUserInput(); navigatePrev(); } |
1510 | @@ -5464,6 +5898,10 @@ | |||
1511 | 5464 | // Returns an Array of all slides | 5898 | // Returns an Array of all slides |
1512 | 5465 | getSlides: getSlides, | 5899 | getSlides: getSlides, |
1513 | 5466 | 5900 | ||
1514 | 5901 | // Returns an Array of objects representing the attributes on | ||
1515 | 5902 | // the slides | ||
1516 | 5903 | getSlidesAttributes: getSlidesAttributes, | ||
1517 | 5904 | |||
1518 | 5467 | // Returns the total number of slides | 5905 | // Returns the total number of slides |
1519 | 5468 | getTotalSlides: getTotalSlides, | 5906 | getTotalSlides: getTotalSlides, |
1520 | 5469 | 5907 | ||
1521 | @@ -5514,6 +5952,16 @@ | |||
1522 | 5514 | return query; | 5952 | return query; |
1523 | 5515 | }, | 5953 | }, |
1524 | 5516 | 5954 | ||
1525 | 5955 | // Returns the top-level DOM element | ||
1526 | 5956 | getRevealElement: function() { | ||
1527 | 5957 | return dom.wrapper || document.querySelector( '.reveal' ); | ||
1528 | 5958 | }, | ||
1529 | 5959 | |||
1530 | 5960 | // Returns a hash with all registered plugins | ||
1531 | 5961 | getPlugins: function() { | ||
1532 | 5962 | return plugins; | ||
1533 | 5963 | }, | ||
1534 | 5964 | |||
1535 | 5517 | // Returns true if we're currently on the first slide | 5965 | // Returns true if we're currently on the first slide |
1536 | 5518 | isFirstSlide: function() { | 5966 | isFirstSlide: function() { |
1537 | 5519 | return ( indexh === 0 && indexv === 0 ); | 5967 | return ( indexh === 0 && indexv === 0 ); |
1538 | @@ -5555,22 +6003,25 @@ | |||
1539 | 5555 | // Forward event binding to the reveal DOM element | 6003 | // Forward event binding to the reveal DOM element |
1540 | 5556 | addEventListener: function( type, listener, useCapture ) { | 6004 | addEventListener: function( type, listener, useCapture ) { |
1541 | 5557 | if( 'addEventListener' in window ) { | 6005 | if( 'addEventListener' in window ) { |
1543 | 5558 | ( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture ); | 6006 | Reveal.getRevealElement().addEventListener( type, listener, useCapture ); |
1544 | 5559 | } | 6007 | } |
1545 | 5560 | }, | 6008 | }, |
1546 | 5561 | removeEventListener: function( type, listener, useCapture ) { | 6009 | removeEventListener: function( type, listener, useCapture ) { |
1547 | 5562 | if( 'addEventListener' in window ) { | 6010 | if( 'addEventListener' in window ) { |
1549 | 5563 | ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture ); | 6011 | Reveal.getRevealElement().removeEventListener( type, listener, useCapture ); |
1550 | 5564 | } | 6012 | } |
1551 | 5565 | }, | 6013 | }, |
1552 | 5566 | 6014 | ||
1554 | 5567 | // Adds a custom key binding | 6015 | // Adds/removes a custom key binding |
1555 | 5568 | addKeyBinding: addKeyBinding, | 6016 | addKeyBinding: addKeyBinding, |
1556 | 5569 | |||
1557 | 5570 | // Removes a custom key binding | ||
1558 | 5571 | removeKeyBinding: removeKeyBinding, | 6017 | removeKeyBinding: removeKeyBinding, |
1559 | 5572 | 6018 | ||
1561 | 5573 | // Programatically triggers a keyboard event | 6019 | // API for registering and retrieving plugins |
1562 | 6020 | registerPlugin: registerPlugin, | ||
1563 | 6021 | hasPlugin: hasPlugin, | ||
1564 | 6022 | getPlugin: getPlugin, | ||
1565 | 6023 | |||
1566 | 6024 | // Programmatically triggers a keyboard event | ||
1567 | 5574 | triggerKey: function( keyCode ) { | 6025 | triggerKey: function( keyCode ) { |
1568 | 5575 | onDocumentKeyDown( { keyCode: keyCode } ); | 6026 | onDocumentKeyDown( { keyCode: keyCode } ); |
1569 | 5576 | }, | 6027 | }, |
Linux tests passed!