reveal-ck 3.6.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/files/reveal.js/CONTRIBUTING.md +4 -0
- data/files/reveal.js/Gruntfile.js +44 -26
- data/files/reveal.js/LICENSE +1 -1
- data/files/reveal.js/README.md +375 -161
- data/files/reveal.js/bower.json +27 -0
- data/files/reveal.js/css/print/paper.css +4 -3
- data/files/reveal.js/css/print/pdf.css +53 -38
- data/files/reveal.js/css/reveal.css +452 -206
- data/files/reveal.js/css/reveal.scss +328 -175
- data/files/reveal.js/css/theme/README.md +2 -6
- data/files/reveal.js/css/theme/beige.css +81 -50
- data/files/reveal.js/css/theme/black.css +70 -39
- data/files/reveal.js/css/theme/blood.css +81 -57
- data/files/reveal.js/css/theme/league.css +75 -44
- data/files/reveal.js/css/theme/moon.css +75 -44
- data/files/reveal.js/css/theme/night.css +70 -39
- data/files/reveal.js/css/theme/serif.css +72 -41
- data/files/reveal.js/css/theme/simple.css +72 -38
- data/files/reveal.js/css/theme/sky.css +75 -44
- data/files/reveal.js/css/theme/solarized.css +75 -44
- data/files/reveal.js/css/theme/source/black.scss +2 -2
- data/files/reveal.js/css/theme/source/blood.scss +3 -16
- data/files/reveal.js/css/theme/source/night.scss +0 -1
- data/files/reveal.js/css/theme/source/simple.scss +5 -0
- data/files/reveal.js/css/theme/source/white.scss +2 -2
- data/files/reveal.js/css/theme/template/settings.scss +1 -1
- data/files/reveal.js/css/theme/template/theme.scss +36 -23
- data/files/reveal.js/css/theme/white.css +75 -44
- data/files/reveal.js/demo.html +410 -0
- data/files/reveal.js/index.html +14 -373
- data/files/reveal.js/js/reveal.js +1186 -350
- data/files/reveal.js/lib/css/zenburn.css +41 -78
- data/files/reveal.js/lib/js/head.min.js +9 -8
- data/files/reveal.js/package.json +22 -26
- data/files/reveal.js/plugin/highlight/highlight.js +52 -4
- data/files/reveal.js/plugin/markdown/example.html +1 -1
- data/files/reveal.js/plugin/markdown/markdown.js +40 -21
- data/files/reveal.js/plugin/markdown/marked.js +2 -33
- data/files/reveal.js/plugin/math/math.js +5 -2
- data/files/reveal.js/plugin/multiplex/client.js +1 -1
- data/files/reveal.js/plugin/multiplex/index.js +24 -16
- data/files/reveal.js/plugin/multiplex/master.js +22 -42
- data/files/reveal.js/plugin/multiplex/package.json +19 -0
- data/files/reveal.js/plugin/notes-server/client.js +6 -1
- data/files/reveal.js/plugin/notes-server/index.js +17 -14
- data/files/reveal.js/plugin/notes-server/notes.html +215 -26
- data/files/reveal.js/plugin/notes/notes.html +372 -32
- data/files/reveal.js/plugin/notes/notes.js +40 -7
- data/files/reveal.js/plugin/print-pdf/print-pdf.js +47 -26
- data/files/reveal.js/plugin/zoom-js/zoom.js +12 -2
- data/files/reveal.js/test/examples/math.html +1 -1
- data/files/reveal.js/test/examples/slide-backgrounds.html +1 -1
- data/files/reveal.js/test/examples/slide-transitions.html +101 -0
- data/files/reveal.js/test/simple.md +12 -0
- data/files/reveal.js/test/test-markdown-element-attributes.html +3 -3
- data/files/reveal.js/test/test-markdown-element-attributes.js +1 -1
- data/files/reveal.js/test/test-markdown-external.html +36 -0
- data/files/reveal.js/test/test-markdown-external.js +24 -0
- data/files/reveal.js/test/test-markdown-options.html +41 -0
- data/files/reveal.js/test/test-markdown-options.js +26 -0
- data/files/reveal.js/test/test-markdown.html +1 -1
- data/files/reveal.js/test/test.html +5 -1
- data/files/reveal.js/test/test.js +26 -1
- data/lib/reveal-ck/version.rb +1 -1
- metadata +11 -4
- data/files/reveal.js/plugin/leap/leap.js +0 -159
- data/files/reveal.js/plugin/remotes/remotes.js +0 -39
@@ -3,7 +3,7 @@
|
|
3
3
|
* http://lab.hakim.se/reveal-js
|
4
4
|
* MIT licensed
|
5
5
|
*
|
6
|
-
* Copyright (C)
|
6
|
+
* Copyright (C) 2017 Hakim El Hattab, http://hakim.se
|
7
7
|
*/
|
8
8
|
(function( root, factory ) {
|
9
9
|
if( typeof define === 'function' && define.amd ) {
|
@@ -25,12 +25,16 @@
|
|
25
25
|
|
26
26
|
var Reveal;
|
27
27
|
|
28
|
+
// The reveal.js version
|
29
|
+
var VERSION = '3.5.0';
|
30
|
+
|
28
31
|
var SLIDES_SELECTOR = '.slides section',
|
29
32
|
HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
|
30
33
|
VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
|
31
34
|
HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',
|
35
|
+
UA = navigator.userAgent,
|
32
36
|
|
33
|
-
//
|
37
|
+
// Configuration defaults, can be overridden at initialization time
|
34
38
|
config = {
|
35
39
|
|
36
40
|
// The "normal" size of the presentation, aspect ratio will be preserved
|
@@ -39,11 +43,11 @@
|
|
39
43
|
height: 700,
|
40
44
|
|
41
45
|
// Factor of the display size that should remain empty around the content
|
42
|
-
margin: 0.
|
46
|
+
margin: 0.04,
|
43
47
|
|
44
48
|
// Bounds for smallest/largest possible scale to apply to content
|
45
49
|
minScale: 0.2,
|
46
|
-
maxScale:
|
50
|
+
maxScale: 2.0,
|
47
51
|
|
48
52
|
// Display controls in the bottom right corner
|
49
53
|
controls: true,
|
@@ -54,6 +58,9 @@
|
|
54
58
|
// Display the page number of the current slide
|
55
59
|
slideNumber: false,
|
56
60
|
|
61
|
+
// Determine which displays to show the slide number on
|
62
|
+
showSlideNumber: 'all',
|
63
|
+
|
57
64
|
// Push each slide change to the browser history
|
58
65
|
history: false,
|
59
66
|
|
@@ -78,6 +85,9 @@
|
|
78
85
|
// Change the presentation direction to be RTL
|
79
86
|
rtl: false,
|
80
87
|
|
88
|
+
// Randomizes the order of slides each time the presentation loads
|
89
|
+
shuffle: false,
|
90
|
+
|
81
91
|
// Turns fragments on and off globally
|
82
92
|
fragments: true,
|
83
93
|
|
@@ -85,13 +95,22 @@
|
|
85
95
|
// i.e. contained within a limited portion of the screen
|
86
96
|
embedded: false,
|
87
97
|
|
88
|
-
// Flags if we should show a help overlay when the
|
98
|
+
// Flags if we should show a help overlay when the question-mark
|
89
99
|
// key is pressed
|
90
100
|
help: true,
|
91
101
|
|
92
102
|
// Flags if it should be possible to pause the presentation (blackout)
|
93
103
|
pause: true,
|
94
104
|
|
105
|
+
// Flags if speaker notes should be visible to all viewers
|
106
|
+
showNotes: false,
|
107
|
+
|
108
|
+
// Global override for autolaying embedded media (video/audio/iframe)
|
109
|
+
// - null: Media will only autoplay if data-autoplay is present
|
110
|
+
// - true: All media will autoplay, regardless of individual setting
|
111
|
+
// - false: No media will autoplay, regardless of individual setting
|
112
|
+
autoPlayMedia: null,
|
113
|
+
|
95
114
|
// Number of milliseconds between automatically proceeding to the
|
96
115
|
// next slide, disabled when set to 0, this value can be overwritten
|
97
116
|
// by using a data-autoslide attribute on your slides
|
@@ -100,6 +119,9 @@
|
|
100
119
|
// Stop auto-sliding after user input
|
101
120
|
autoSlideStoppable: true,
|
102
121
|
|
122
|
+
// Use this method for navigation when auto-sliding (defaults to navigateNext)
|
123
|
+
autoSlideMethod: null,
|
124
|
+
|
103
125
|
// Enable slide navigation via mouse wheel
|
104
126
|
mouseWheel: false,
|
105
127
|
|
@@ -118,7 +140,7 @@
|
|
118
140
|
// Dispatches all reveal.js events to the parent window through postMessage
|
119
141
|
postMessageEvents: false,
|
120
142
|
|
121
|
-
// Focuses body when page changes
|
143
|
+
// Focuses body when page changes visibility to ensure keyboard shortcuts work
|
122
144
|
focusBodyOnPageVisibilityChange: true,
|
123
145
|
|
124
146
|
// Transition style
|
@@ -136,17 +158,45 @@
|
|
136
158
|
// Parallax background size
|
137
159
|
parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
|
138
160
|
|
161
|
+
// Amount of pixels to move the parallax background per slide step
|
162
|
+
parallaxBackgroundHorizontal: null,
|
163
|
+
parallaxBackgroundVertical: null,
|
164
|
+
|
165
|
+
// The maximum number of pages a single slide can expand onto when printing
|
166
|
+
// to PDF, unlimited by default
|
167
|
+
pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY,
|
168
|
+
|
169
|
+
// Offset used to reduce the height of content within exported PDF pages.
|
170
|
+
// This exists to account for environment differences based on how you
|
171
|
+
// print to PDF. CLI printing options, like phantomjs and wkpdf, can end
|
172
|
+
// on precisely the total height of the document whereas in-browser
|
173
|
+
// printing has to end one pixel before.
|
174
|
+
pdfPageHeightOffset: -1,
|
175
|
+
|
139
176
|
// Number of slides away from the current that are visible
|
140
177
|
viewDistance: 3,
|
141
178
|
|
179
|
+
// The display mode that will be used to show slides
|
180
|
+
display: 'block',
|
181
|
+
|
142
182
|
// Script dependencies to load
|
143
183
|
dependencies: []
|
144
184
|
|
145
185
|
},
|
146
186
|
|
187
|
+
// Flags if Reveal.initialize() has been called
|
188
|
+
initialized = false,
|
189
|
+
|
147
190
|
// Flags if reveal.js is loaded (has dispatched the 'ready' event)
|
148
191
|
loaded = false,
|
149
192
|
|
193
|
+
// Flags if the overview mode is currently active
|
194
|
+
overview = false,
|
195
|
+
|
196
|
+
// Holds the dimensions of our overview slides, including margins
|
197
|
+
overviewSlideWidth = null,
|
198
|
+
overviewSlideHeight = null,
|
199
|
+
|
150
200
|
// The horizontal and vertical index of the currently active slide
|
151
201
|
indexh,
|
152
202
|
indexv,
|
@@ -165,6 +215,10 @@
|
|
165
215
|
// The current scale of the presentation (see width/height config)
|
166
216
|
scale = 1,
|
167
217
|
|
218
|
+
// CSS transform that is currently applied to the slides container,
|
219
|
+
// split into two groups
|
220
|
+
slidesTransform = { layout: '', overview: '' },
|
221
|
+
|
168
222
|
// Cached references to DOM elements
|
169
223
|
dom = {},
|
170
224
|
|
@@ -174,6 +228,9 @@
|
|
174
228
|
// Client is a mobile device, see #checkCapabilities()
|
175
229
|
isMobileDevice,
|
176
230
|
|
231
|
+
// Client is a desktop Chrome, see #checkCapabilities()
|
232
|
+
isChrome,
|
233
|
+
|
177
234
|
// Throttles mouse wheel navigation
|
178
235
|
lastMouseWheelStep = 0,
|
179
236
|
|
@@ -222,19 +279,28 @@
|
|
222
279
|
*/
|
223
280
|
function initialize( options ) {
|
224
281
|
|
282
|
+
// Make sure we only initialize once
|
283
|
+
if( initialized === true ) return;
|
284
|
+
|
285
|
+
initialized = true;
|
286
|
+
|
225
287
|
checkCapabilities();
|
226
288
|
|
227
289
|
if( !features.transforms2d && !features.transforms3d ) {
|
228
290
|
document.body.setAttribute( 'class', 'no-transforms' );
|
229
291
|
|
230
|
-
// Since JS won't be running any further, we
|
231
|
-
//
|
232
|
-
var images = document.getElementsByTagName( 'img' )
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
292
|
+
// Since JS won't be running any further, we load all lazy
|
293
|
+
// loading elements upfront
|
294
|
+
var images = toArray( document.getElementsByTagName( 'img' ) ),
|
295
|
+
iframes = toArray( document.getElementsByTagName( 'iframe' ) );
|
296
|
+
|
297
|
+
var lazyLoadable = images.concat( iframes );
|
298
|
+
|
299
|
+
for( var i = 0, len = lazyLoadable.length; i < len; i++ ) {
|
300
|
+
var element = lazyLoadable[i];
|
301
|
+
if( element.getAttribute( 'data-src' ) ) {
|
302
|
+
element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
|
303
|
+
element.removeAttribute( 'data-src' );
|
238
304
|
}
|
239
305
|
}
|
240
306
|
|
@@ -274,26 +340,37 @@
|
|
274
340
|
*/
|
275
341
|
function checkCapabilities() {
|
276
342
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
'perspective' in document.body.style;
|
343
|
+
isMobileDevice = /(iphone|ipod|ipad|android)/gi.test( UA );
|
344
|
+
isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA );
|
345
|
+
|
346
|
+
var testElement = document.createElement( 'div' );
|
282
347
|
|
283
|
-
features.
|
284
|
-
'
|
285
|
-
'
|
286
|
-
'
|
287
|
-
'
|
348
|
+
features.transforms3d = 'WebkitPerspective' in testElement.style ||
|
349
|
+
'MozPerspective' in testElement.style ||
|
350
|
+
'msPerspective' in testElement.style ||
|
351
|
+
'OPerspective' in testElement.style ||
|
352
|
+
'perspective' in testElement.style;
|
353
|
+
|
354
|
+
features.transforms2d = 'WebkitTransform' in testElement.style ||
|
355
|
+
'MozTransform' in testElement.style ||
|
356
|
+
'msTransform' in testElement.style ||
|
357
|
+
'OTransform' in testElement.style ||
|
358
|
+
'transform' in testElement.style;
|
288
359
|
|
289
360
|
features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
|
290
361
|
features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';
|
291
362
|
|
292
363
|
features.canvas = !!document.createElement( 'canvas' ).getContext;
|
293
364
|
|
294
|
-
|
365
|
+
// Transitions in the overview are disabled in desktop and
|
366
|
+
// Safari due to lag
|
367
|
+
features.overviewTransitions = !/Version\/[\d\.]+.*Safari/.test( UA );
|
295
368
|
|
296
|
-
|
369
|
+
// Flags if we should use zoom instead of transform to scale
|
370
|
+
// up slides. Zoom produces crisper results but has a lot of
|
371
|
+
// xbrowser quirks so we only use it in whitelsited browsers.
|
372
|
+
features.zoom = 'zoom' in testElement.style && !isMobileDevice &&
|
373
|
+
( isChrome || /Version\/[\d\.]+.*Safari/.test( UA ) );
|
297
374
|
|
298
375
|
}
|
299
376
|
|
@@ -373,6 +450,9 @@
|
|
373
450
|
// Listen to messages posted to this window
|
374
451
|
setupPostMessage();
|
375
452
|
|
453
|
+
// Prevent the slides from being scrolled out of view
|
454
|
+
setupScrollPrevention();
|
455
|
+
|
376
456
|
// Resets all vertical slides so that only the first is visible
|
377
457
|
resetVerticalSlides();
|
378
458
|
|
@@ -393,6 +473,8 @@
|
|
393
473
|
|
394
474
|
loaded = true;
|
395
475
|
|
476
|
+
dom.wrapper.classList.add( 'ready' );
|
477
|
+
|
396
478
|
dispatchEvent( 'ready', {
|
397
479
|
'indexh': indexh,
|
398
480
|
'indexv': indexv,
|
@@ -435,20 +517,24 @@
|
|
435
517
|
|
436
518
|
// Arrow controls
|
437
519
|
createSingletonNode( dom.wrapper, 'aside', 'controls',
|
438
|
-
'<
|
439
|
-
'<
|
440
|
-
'<
|
441
|
-
'<
|
520
|
+
'<button class="navigate-left" aria-label="previous slide"></button>' +
|
521
|
+
'<button class="navigate-right" aria-label="next slide"></button>' +
|
522
|
+
'<button class="navigate-up" aria-label="above slide"></button>' +
|
523
|
+
'<button class="navigate-down" aria-label="below slide"></button>' );
|
442
524
|
|
443
525
|
// Slide number
|
444
526
|
dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
|
445
527
|
|
528
|
+
// Element containing notes that are visible to the audience
|
529
|
+
dom.speakerNotes = createSingletonNode( dom.wrapper, 'div', 'speaker-notes', null );
|
530
|
+
dom.speakerNotes.setAttribute( 'data-prevent-swipe', '' );
|
531
|
+
dom.speakerNotes.setAttribute( 'tabindex', '0' );
|
532
|
+
|
446
533
|
// Overlay graphic which is displayed during the paused mode
|
447
534
|
createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
|
448
535
|
|
449
536
|
// Cache references to elements
|
450
537
|
dom.controls = document.querySelector( '.reveal .controls' );
|
451
|
-
dom.theme = document.querySelector( '#theme' );
|
452
538
|
|
453
539
|
dom.wrapper.setAttribute( 'role', 'application' );
|
454
540
|
|
@@ -467,6 +553,8 @@
|
|
467
553
|
* Creates a hidden div with role aria-live to announce the
|
468
554
|
* current slide content. Hide the div off-screen to make it
|
469
555
|
* available only to Assistive Technologies.
|
556
|
+
*
|
557
|
+
* @return {HTMLElement}
|
470
558
|
*/
|
471
559
|
function createStatusDiv() {
|
472
560
|
|
@@ -476,7 +564,7 @@
|
|
476
564
|
statusDiv.style.position = 'absolute';
|
477
565
|
statusDiv.style.height = '1px';
|
478
566
|
statusDiv.style.width = '1px';
|
479
|
-
statusDiv.style.overflow ='hidden';
|
567
|
+
statusDiv.style.overflow = 'hidden';
|
480
568
|
statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';
|
481
569
|
statusDiv.setAttribute( 'id', 'aria-status-div' );
|
482
570
|
statusDiv.setAttribute( 'aria-live', 'polite' );
|
@@ -487,6 +575,38 @@
|
|
487
575
|
|
488
576
|
}
|
489
577
|
|
578
|
+
/**
|
579
|
+
* Converts the given HTML element into a string of text
|
580
|
+
* that can be announced to a screen reader. Hidden
|
581
|
+
* elements are excluded.
|
582
|
+
*/
|
583
|
+
function getStatusText( node ) {
|
584
|
+
|
585
|
+
var text = '';
|
586
|
+
|
587
|
+
// Text node
|
588
|
+
if( node.nodeType === 3 ) {
|
589
|
+
text += node.textContent;
|
590
|
+
}
|
591
|
+
// Element node
|
592
|
+
else if( node.nodeType === 1 ) {
|
593
|
+
|
594
|
+
var isAriaHidden = node.getAttribute( 'aria-hidden' );
|
595
|
+
var isDisplayHidden = window.getComputedStyle( node )['display'] === 'none';
|
596
|
+
if( isAriaHidden !== 'true' && !isDisplayHidden ) {
|
597
|
+
|
598
|
+
toArray( node.childNodes ).forEach( function( child ) {
|
599
|
+
text += getStatusText( child );
|
600
|
+
} );
|
601
|
+
|
602
|
+
}
|
603
|
+
|
604
|
+
}
|
605
|
+
|
606
|
+
return text;
|
607
|
+
|
608
|
+
}
|
609
|
+
|
490
610
|
/**
|
491
611
|
* Configures the presentation for printing to a static
|
492
612
|
* PDF.
|
@@ -497,14 +617,14 @@
|
|
497
617
|
|
498
618
|
// Dimensions of the PDF pages
|
499
619
|
var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
|
500
|
-
pageHeight = Math.floor( slideSize.height * ( 1 + config.margin
|
620
|
+
pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
|
501
621
|
|
502
622
|
// Dimensions of slides within the pages
|
503
623
|
var slideWidth = slideSize.width,
|
504
624
|
slideHeight = slideSize.height;
|
505
625
|
|
506
626
|
// Let the browser know what page size we want to print
|
507
|
-
injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin:
|
627
|
+
injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' );
|
508
628
|
|
509
629
|
// Limit the size of certain elements to the dimensions of the slide
|
510
630
|
injectStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
|
@@ -513,6 +633,22 @@
|
|
513
633
|
document.body.style.width = pageWidth + 'px';
|
514
634
|
document.body.style.height = pageHeight + 'px';
|
515
635
|
|
636
|
+
// Make sure stretch elements fit on slide
|
637
|
+
layoutSlideContents( slideWidth, slideHeight );
|
638
|
+
|
639
|
+
// Add each slide's index as attributes on itself, we need these
|
640
|
+
// indices to generate slide numbers below
|
641
|
+
toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {
|
642
|
+
hslide.setAttribute( 'data-index-h', h );
|
643
|
+
|
644
|
+
if( hslide.classList.contains( 'stack' ) ) {
|
645
|
+
toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {
|
646
|
+
vslide.setAttribute( 'data-index-h', h );
|
647
|
+
vslide.setAttribute( 'data-index-v', v );
|
648
|
+
} );
|
649
|
+
}
|
650
|
+
} );
|
651
|
+
|
516
652
|
// Slide and slide background layout
|
517
653
|
toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
|
518
654
|
|
@@ -523,27 +659,73 @@
|
|
523
659
|
var left = ( pageWidth - slideWidth ) / 2,
|
524
660
|
top = ( pageHeight - slideHeight ) / 2;
|
525
661
|
|
526
|
-
var contentHeight =
|
662
|
+
var contentHeight = slide.scrollHeight;
|
527
663
|
var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
|
528
664
|
|
665
|
+
// Adhere to configured pages per slide limit
|
666
|
+
numberOfPages = Math.min( numberOfPages, config.pdfMaxPagesPerSlide );
|
667
|
+
|
529
668
|
// Center slides vertically
|
530
669
|
if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
|
531
670
|
top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
|
532
671
|
}
|
533
672
|
|
673
|
+
// Wrap the slide in a page element and hide its overflow
|
674
|
+
// so that no page ever flows onto another
|
675
|
+
var page = document.createElement( 'div' );
|
676
|
+
page.className = 'pdf-page';
|
677
|
+
page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px';
|
678
|
+
slide.parentNode.insertBefore( page, slide );
|
679
|
+
page.appendChild( slide );
|
680
|
+
|
534
681
|
// Position the slide inside of the page
|
535
682
|
slide.style.left = left + 'px';
|
536
683
|
slide.style.top = top + 'px';
|
537
684
|
slide.style.width = slideWidth + 'px';
|
538
685
|
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
686
|
+
if( slide.slideBackgroundElement ) {
|
687
|
+
page.insertBefore( slide.slideBackgroundElement, slide );
|
688
|
+
}
|
689
|
+
|
690
|
+
// Inject notes if `showNotes` is enabled
|
691
|
+
if( config.showNotes ) {
|
692
|
+
|
693
|
+
// Are there notes for this slide?
|
694
|
+
var notes = getSlideNotes( slide );
|
695
|
+
if( notes ) {
|
696
|
+
|
697
|
+
var notesSpacing = 8;
|
698
|
+
var notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline';
|
699
|
+
var notesElement = document.createElement( 'div' );
|
700
|
+
notesElement.classList.add( 'speaker-notes' );
|
701
|
+
notesElement.classList.add( 'speaker-notes-pdf' );
|
702
|
+
notesElement.setAttribute( 'data-layout', notesLayout );
|
703
|
+
notesElement.innerHTML = notes;
|
704
|
+
|
705
|
+
if( notesLayout === 'separate-page' ) {
|
706
|
+
page.parentNode.insertBefore( notesElement, page.nextSibling );
|
707
|
+
}
|
708
|
+
else {
|
709
|
+
notesElement.style.left = notesSpacing + 'px';
|
710
|
+
notesElement.style.bottom = notesSpacing + 'px';
|
711
|
+
notesElement.style.width = ( pageWidth - notesSpacing*2 ) + 'px';
|
712
|
+
page.appendChild( notesElement );
|
713
|
+
}
|
714
|
+
|
715
|
+
}
|
716
|
+
|
717
|
+
}
|
718
|
+
|
719
|
+
// Inject slide numbers if `slideNumbers` are enabled
|
720
|
+
if( config.slideNumber && /all|print/i.test( config.showSlideNumber ) ) {
|
721
|
+
var slideNumberH = parseInt( slide.getAttribute( 'data-index-h' ), 10 ) + 1,
|
722
|
+
slideNumberV = parseInt( slide.getAttribute( 'data-index-v' ), 10 ) + 1;
|
723
|
+
|
724
|
+
var numberElement = document.createElement( 'div' );
|
725
|
+
numberElement.classList.add( 'slide-number' );
|
726
|
+
numberElement.classList.add( 'slide-number-pdf' );
|
727
|
+
numberElement.innerHTML = formatSlideNumber( slideNumberH, '.', slideNumberV );
|
728
|
+
page.appendChild( numberElement );
|
547
729
|
}
|
548
730
|
}
|
549
731
|
|
@@ -554,12 +736,42 @@
|
|
554
736
|
fragment.classList.add( 'visible' );
|
555
737
|
} );
|
556
738
|
|
739
|
+
// Notify subscribers that the PDF layout is good to go
|
740
|
+
dispatchEvent( 'pdf-ready' );
|
741
|
+
|
742
|
+
}
|
743
|
+
|
744
|
+
/**
|
745
|
+
* This is an unfortunate necessity. Some actions – such as
|
746
|
+
* an input field being focused in an iframe or using the
|
747
|
+
* keyboard to expand text selection beyond the bounds of
|
748
|
+
* a slide – can trigger our content to be pushed out of view.
|
749
|
+
* This scrolling can not be prevented by hiding overflow in
|
750
|
+
* CSS (we already do) so we have to resort to repeatedly
|
751
|
+
* checking if the slides have been offset :(
|
752
|
+
*/
|
753
|
+
function setupScrollPrevention() {
|
754
|
+
|
755
|
+
setInterval( function() {
|
756
|
+
if( dom.wrapper.scrollTop !== 0 || dom.wrapper.scrollLeft !== 0 ) {
|
757
|
+
dom.wrapper.scrollTop = 0;
|
758
|
+
dom.wrapper.scrollLeft = 0;
|
759
|
+
}
|
760
|
+
}, 1000 );
|
761
|
+
|
557
762
|
}
|
558
763
|
|
559
764
|
/**
|
560
765
|
* Creates an HTML element and returns a reference to it.
|
561
766
|
* If the element already exists the existing instance will
|
562
767
|
* be returned.
|
768
|
+
*
|
769
|
+
* @param {HTMLElement} container
|
770
|
+
* @param {string} tagname
|
771
|
+
* @param {string} classname
|
772
|
+
* @param {string} innerHTML
|
773
|
+
*
|
774
|
+
* @return {HTMLElement}
|
563
775
|
*/
|
564
776
|
function createSingletonNode( container, tagname, classname, innerHTML ) {
|
565
777
|
|
@@ -603,24 +815,12 @@
|
|
603
815
|
// Iterate over all horizontal slides
|
604
816
|
toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
|
605
817
|
|
606
|
-
var backgroundStack;
|
607
|
-
|
608
|
-
if( printMode ) {
|
609
|
-
backgroundStack = createBackground( slideh, slideh );
|
610
|
-
}
|
611
|
-
else {
|
612
|
-
backgroundStack = createBackground( slideh, dom.background );
|
613
|
-
}
|
818
|
+
var backgroundStack = createBackground( slideh, dom.background );
|
614
819
|
|
615
820
|
// Iterate over all vertical slides
|
616
821
|
toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
|
617
822
|
|
618
|
-
|
619
|
-
createBackground( slidev, slidev );
|
620
|
-
}
|
621
|
-
else {
|
622
|
-
createBackground( slidev, backgroundStack );
|
623
|
-
}
|
823
|
+
createBackground( slidev, backgroundStack );
|
624
824
|
|
625
825
|
backgroundStack.classList.add( 'stack' );
|
626
826
|
|
@@ -658,6 +858,7 @@
|
|
658
858
|
* @param {HTMLElement} slide
|
659
859
|
* @param {HTMLElement} container The element that the background
|
660
860
|
* should be appended to
|
861
|
+
* @return {HTMLElement} New background div
|
661
862
|
*/
|
662
863
|
function createBackground( slide, container ) {
|
663
864
|
|
@@ -680,7 +881,7 @@
|
|
680
881
|
|
681
882
|
if( data.background ) {
|
682
883
|
// Auto-wrap image urls in url(...)
|
683
|
-
if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)
|
884
|
+
if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#]|$)/gi.test( data.background ) ) {
|
684
885
|
slide.setAttribute( 'data-background-image', data.background );
|
685
886
|
}
|
686
887
|
else {
|
@@ -705,6 +906,7 @@
|
|
705
906
|
|
706
907
|
// Additional and optional background properties
|
707
908
|
if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
|
909
|
+
if( data.backgroundSize ) element.setAttribute( 'data-background-size', data.backgroundSize );
|
708
910
|
if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
|
709
911
|
if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
|
710
912
|
if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
|
@@ -716,18 +918,20 @@
|
|
716
918
|
slide.classList.remove( 'has-dark-background' );
|
717
919
|
slide.classList.remove( 'has-light-background' );
|
718
920
|
|
921
|
+
slide.slideBackgroundElement = element;
|
922
|
+
|
719
923
|
// If this slide has a background color, add a class that
|
720
924
|
// signals if it is light or dark. If the slide has no background
|
721
925
|
// color, no class will be set
|
722
|
-
var
|
723
|
-
if(
|
724
|
-
var rgb = colorToRgb(
|
926
|
+
var computedBackgroundStyle = window.getComputedStyle( element );
|
927
|
+
if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) {
|
928
|
+
var rgb = colorToRgb( computedBackgroundStyle.backgroundColor );
|
725
929
|
|
726
930
|
// Ignore fully transparent backgrounds. Some browsers return
|
727
931
|
// rgba(0,0,0,0) when reading the computed background color of
|
728
932
|
// an element with no background
|
729
933
|
if( rgb && rgb.a !== 0 ) {
|
730
|
-
if( colorBrightness(
|
934
|
+
if( colorBrightness( computedBackgroundStyle.backgroundColor ) < 128 ) {
|
731
935
|
slide.classList.add( 'has-dark-background' );
|
732
936
|
}
|
733
937
|
else {
|
@@ -757,7 +961,7 @@
|
|
757
961
|
var data = event.data;
|
758
962
|
|
759
963
|
// Make sure we're dealing with JSON
|
760
|
-
if( data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
|
964
|
+
if( typeof data === 'string' && data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
|
761
965
|
data = JSON.parse( data );
|
762
966
|
|
763
967
|
// Check if the requested method can be found
|
@@ -773,6 +977,8 @@
|
|
773
977
|
/**
|
774
978
|
* Applies the configuration settings from the config
|
775
979
|
* object. May be called multiple times.
|
980
|
+
*
|
981
|
+
* @param {object} options
|
776
982
|
*/
|
777
983
|
function configure( options ) {
|
778
984
|
|
@@ -795,6 +1001,10 @@
|
|
795
1001
|
dom.controls.style.display = config.controls ? 'block' : 'none';
|
796
1002
|
dom.progress.style.display = config.progress ? 'block' : 'none';
|
797
1003
|
|
1004
|
+
if( config.shuffle ) {
|
1005
|
+
shuffle();
|
1006
|
+
}
|
1007
|
+
|
798
1008
|
if( config.rtl ) {
|
799
1009
|
dom.wrapper.classList.add( 'rtl' );
|
800
1010
|
}
|
@@ -814,6 +1024,14 @@
|
|
814
1024
|
resume();
|
815
1025
|
}
|
816
1026
|
|
1027
|
+
if( config.showNotes ) {
|
1028
|
+
dom.speakerNotes.classList.add( 'visible' );
|
1029
|
+
dom.speakerNotes.setAttribute( 'data-layout', typeof config.showNotes === 'string' ? config.showNotes : 'inline' );
|
1030
|
+
}
|
1031
|
+
else {
|
1032
|
+
dom.speakerNotes.classList.remove( 'visible' );
|
1033
|
+
}
|
1034
|
+
|
817
1035
|
if( config.mouseWheel ) {
|
818
1036
|
document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
|
819
1037
|
document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
|
@@ -834,10 +1052,11 @@
|
|
834
1052
|
// Iframe link previews
|
835
1053
|
if( config.previewLinks ) {
|
836
1054
|
enablePreviewLinks();
|
1055
|
+
disablePreviewLinks( '[data-preview-link=false]' );
|
837
1056
|
}
|
838
1057
|
else {
|
839
1058
|
disablePreviewLinks();
|
840
|
-
enablePreviewLinks( '[data-preview-link]' );
|
1059
|
+
enablePreviewLinks( '[data-preview-link]:not([data-preview-link=false])' );
|
841
1060
|
}
|
842
1061
|
|
843
1062
|
// Remove existing auto-slide controls
|
@@ -864,6 +1083,19 @@
|
|
864
1083
|
} );
|
865
1084
|
}
|
866
1085
|
|
1086
|
+
// Slide numbers
|
1087
|
+
var slideNumberDisplay = 'none';
|
1088
|
+
if( config.slideNumber && !isPrintingPDF() ) {
|
1089
|
+
if( config.showSlideNumber === 'all' ) {
|
1090
|
+
slideNumberDisplay = 'block';
|
1091
|
+
}
|
1092
|
+
else if( config.showSlideNumber === 'speaker' && isSpeakerNotes() ) {
|
1093
|
+
slideNumberDisplay = 'block';
|
1094
|
+
}
|
1095
|
+
}
|
1096
|
+
|
1097
|
+
dom.slideNumber.style.display = slideNumberDisplay;
|
1098
|
+
|
867
1099
|
sync();
|
868
1100
|
|
869
1101
|
}
|
@@ -931,7 +1163,7 @@
|
|
931
1163
|
|
932
1164
|
// Only support touch for Android, fixes double navigations in
|
933
1165
|
// stock browser
|
934
|
-
if(
|
1166
|
+
if( UA.match( /android/gi ) ) {
|
935
1167
|
pointerEvents = [ 'touchstart' ];
|
936
1168
|
}
|
937
1169
|
|
@@ -993,6 +1225,9 @@
|
|
993
1225
|
/**
|
994
1226
|
* Extend object a with the properties of object b.
|
995
1227
|
* If there's a conflict, object b takes precedence.
|
1228
|
+
*
|
1229
|
+
* @param {object} a
|
1230
|
+
* @param {object} b
|
996
1231
|
*/
|
997
1232
|
function extend( a, b ) {
|
998
1233
|
|
@@ -1004,6 +1239,9 @@
|
|
1004
1239
|
|
1005
1240
|
/**
|
1006
1241
|
* Converts the target object to an array.
|
1242
|
+
*
|
1243
|
+
* @param {object} o
|
1244
|
+
* @return {object[]}
|
1007
1245
|
*/
|
1008
1246
|
function toArray( o ) {
|
1009
1247
|
|
@@ -1013,6 +1251,9 @@
|
|
1013
1251
|
|
1014
1252
|
/**
|
1015
1253
|
* Utility for deserializing a value.
|
1254
|
+
*
|
1255
|
+
* @param {*} value
|
1256
|
+
* @return {*}
|
1016
1257
|
*/
|
1017
1258
|
function deserialize( value ) {
|
1018
1259
|
|
@@ -1020,7 +1261,7 @@
|
|
1020
1261
|
if( value === 'null' ) return null;
|
1021
1262
|
else if( value === 'true' ) return true;
|
1022
1263
|
else if( value === 'false' ) return false;
|
1023
|
-
else if( value.match(
|
1264
|
+
else if( value.match( /^[\d\.]+$/ ) ) return parseFloat( value );
|
1024
1265
|
}
|
1025
1266
|
|
1026
1267
|
return value;
|
@@ -1031,8 +1272,10 @@
|
|
1031
1272
|
* Measures the distance in pixels between point a
|
1032
1273
|
* and point b.
|
1033
1274
|
*
|
1034
|
-
* @param {
|
1035
|
-
* @param {
|
1275
|
+
* @param {object} a point with x/y properties
|
1276
|
+
* @param {object} b point with x/y properties
|
1277
|
+
*
|
1278
|
+
* @return {number}
|
1036
1279
|
*/
|
1037
1280
|
function distanceBetween( a, b ) {
|
1038
1281
|
|
@@ -1045,19 +1288,46 @@
|
|
1045
1288
|
|
1046
1289
|
/**
|
1047
1290
|
* Applies a CSS transform to the target element.
|
1291
|
+
*
|
1292
|
+
* @param {HTMLElement} element
|
1293
|
+
* @param {string} transform
|
1048
1294
|
*/
|
1049
1295
|
function transformElement( element, transform ) {
|
1050
1296
|
|
1051
1297
|
element.style.WebkitTransform = transform;
|
1052
1298
|
element.style.MozTransform = transform;
|
1053
1299
|
element.style.msTransform = transform;
|
1054
|
-
element.style.OTransform = transform;
|
1055
1300
|
element.style.transform = transform;
|
1056
1301
|
|
1057
1302
|
}
|
1058
1303
|
|
1304
|
+
/**
|
1305
|
+
* Applies CSS transforms to the slides container. The container
|
1306
|
+
* is transformed from two separate sources: layout and the overview
|
1307
|
+
* mode.
|
1308
|
+
*
|
1309
|
+
* @param {object} transforms
|
1310
|
+
*/
|
1311
|
+
function transformSlides( transforms ) {
|
1312
|
+
|
1313
|
+
// Pick up new transforms from arguments
|
1314
|
+
if( typeof transforms.layout === 'string' ) slidesTransform.layout = transforms.layout;
|
1315
|
+
if( typeof transforms.overview === 'string' ) slidesTransform.overview = transforms.overview;
|
1316
|
+
|
1317
|
+
// Apply the transforms to the slides container
|
1318
|
+
if( slidesTransform.layout ) {
|
1319
|
+
transformElement( dom.slides, slidesTransform.layout + ' ' + slidesTransform.overview );
|
1320
|
+
}
|
1321
|
+
else {
|
1322
|
+
transformElement( dom.slides, slidesTransform.overview );
|
1323
|
+
}
|
1324
|
+
|
1325
|
+
}
|
1326
|
+
|
1059
1327
|
/**
|
1060
1328
|
* Injects the given CSS styles into the DOM.
|
1329
|
+
*
|
1330
|
+
* @param {string} value
|
1061
1331
|
*/
|
1062
1332
|
function injectStyleSheet( value ) {
|
1063
1333
|
|
@@ -1074,13 +1344,55 @@
|
|
1074
1344
|
}
|
1075
1345
|
|
1076
1346
|
/**
|
1077
|
-
*
|
1347
|
+
* Find the closest parent that matches the given
|
1348
|
+
* selector.
|
1349
|
+
*
|
1350
|
+
* @param {HTMLElement} target The child element
|
1351
|
+
* @param {String} selector The CSS selector to match
|
1352
|
+
* the parents against
|
1353
|
+
*
|
1354
|
+
* @return {HTMLElement} The matched parent or null
|
1355
|
+
* if no matching parent was found
|
1356
|
+
*/
|
1357
|
+
function closestParent( target, selector ) {
|
1358
|
+
|
1359
|
+
var parent = target.parentNode;
|
1360
|
+
|
1361
|
+
while( parent ) {
|
1362
|
+
|
1363
|
+
// There's some overhead doing this each time, we don't
|
1364
|
+
// want to rewrite the element prototype but should still
|
1365
|
+
// be enough to feature detect once at startup...
|
1366
|
+
var matchesMethod = parent.matches || parent.matchesSelector || parent.msMatchesSelector;
|
1367
|
+
|
1368
|
+
// If we find a match, we're all set
|
1369
|
+
if( matchesMethod && matchesMethod.call( parent, selector ) ) {
|
1370
|
+
return parent;
|
1371
|
+
}
|
1372
|
+
|
1373
|
+
// Keep searching
|
1374
|
+
parent = parent.parentNode;
|
1375
|
+
|
1376
|
+
}
|
1377
|
+
|
1378
|
+
return null;
|
1379
|
+
|
1380
|
+
}
|
1381
|
+
|
1382
|
+
/**
|
1383
|
+
* Converts various color input formats to an {r:0,g:0,b:0} object.
|
1384
|
+
*
|
1385
|
+
* @param {string} color The string representation of a color
|
1386
|
+
* @example
|
1387
|
+
* colorToRgb('#000');
|
1388
|
+
* @example
|
1389
|
+
* colorToRgb('#000000');
|
1390
|
+
* @example
|
1391
|
+
* colorToRgb('rgb(0,0,0)');
|
1392
|
+
* @example
|
1393
|
+
* colorToRgb('rgba(0,0,0)');
|
1078
1394
|
*
|
1079
|
-
* @
|
1080
|
-
* the following formats are supported:
|
1081
|
-
* - #000
|
1082
|
-
* - #000000
|
1083
|
-
* - rgb(0,0,0)
|
1395
|
+
* @return {{r: number, g: number, b: number, [a]: number}|null}
|
1084
1396
|
*/
|
1085
1397
|
function colorToRgb( color ) {
|
1086
1398
|
|
@@ -1130,7 +1442,8 @@
|
|
1130
1442
|
/**
|
1131
1443
|
* Calculates brightness on a scale of 0-255.
|
1132
1444
|
*
|
1133
|
-
* @param color See
|
1445
|
+
* @param {string} color See colorToRgb for supported formats.
|
1446
|
+
* @see {@link colorToRgb}
|
1134
1447
|
*/
|
1135
1448
|
function colorBrightness( color ) {
|
1136
1449
|
|
@@ -1144,46 +1457,14 @@
|
|
1144
1457
|
|
1145
1458
|
}
|
1146
1459
|
|
1147
|
-
/**
|
1148
|
-
* Retrieves the height of the given element by looking
|
1149
|
-
* at the position and height of its immediate children.
|
1150
|
-
*/
|
1151
|
-
function getAbsoluteHeight( element ) {
|
1152
|
-
|
1153
|
-
var height = 0;
|
1154
|
-
|
1155
|
-
if( element ) {
|
1156
|
-
var absoluteChildren = 0;
|
1157
|
-
|
1158
|
-
toArray( element.childNodes ).forEach( function( child ) {
|
1159
|
-
|
1160
|
-
if( typeof child.offsetTop === 'number' && child.style ) {
|
1161
|
-
// Count # of abs children
|
1162
|
-
if( window.getComputedStyle( child ).position === 'absolute' ) {
|
1163
|
-
absoluteChildren += 1;
|
1164
|
-
}
|
1165
|
-
|
1166
|
-
height = Math.max( height, child.offsetTop + child.offsetHeight );
|
1167
|
-
}
|
1168
|
-
|
1169
|
-
} );
|
1170
|
-
|
1171
|
-
// If there are no absolute children, use offsetHeight
|
1172
|
-
if( absoluteChildren === 0 ) {
|
1173
|
-
height = element.offsetHeight;
|
1174
|
-
}
|
1175
|
-
|
1176
|
-
}
|
1177
|
-
|
1178
|
-
return height;
|
1179
|
-
|
1180
|
-
}
|
1181
|
-
|
1182
1460
|
/**
|
1183
1461
|
* Returns the remaining height within the parent of the
|
1184
1462
|
* target element.
|
1185
1463
|
*
|
1186
1464
|
* remaining height = [ configured parent height ] - [ current parent height ]
|
1465
|
+
*
|
1466
|
+
* @param {HTMLElement} element
|
1467
|
+
* @param {number} [height]
|
1187
1468
|
*/
|
1188
1469
|
function getRemainingHeight( element, height ) {
|
1189
1470
|
|
@@ -1306,6 +1587,8 @@
|
|
1306
1587
|
|
1307
1588
|
/**
|
1308
1589
|
* Bind preview frame links.
|
1590
|
+
*
|
1591
|
+
* @param {string} [selector=a] - selector for anchors
|
1309
1592
|
*/
|
1310
1593
|
function enablePreviewLinks( selector ) {
|
1311
1594
|
|
@@ -1322,9 +1605,9 @@
|
|
1322
1605
|
/**
|
1323
1606
|
* Unbind preview frame links.
|
1324
1607
|
*/
|
1325
|
-
function disablePreviewLinks() {
|
1608
|
+
function disablePreviewLinks( selector ) {
|
1326
1609
|
|
1327
|
-
var anchors = toArray( document.querySelectorAll( 'a' ) );
|
1610
|
+
var anchors = toArray( document.querySelectorAll( selector ? selector : 'a' ) );
|
1328
1611
|
|
1329
1612
|
anchors.forEach( function( element ) {
|
1330
1613
|
if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
|
@@ -1336,6 +1619,8 @@
|
|
1336
1619
|
|
1337
1620
|
/**
|
1338
1621
|
* Opens a preview window for the target URL.
|
1622
|
+
*
|
1623
|
+
* @param {string} url - url for preview iframe src
|
1339
1624
|
*/
|
1340
1625
|
function showPreview( url ) {
|
1341
1626
|
|
@@ -1354,6 +1639,9 @@
|
|
1354
1639
|
'<div class="spinner"></div>',
|
1355
1640
|
'<div class="viewport">',
|
1356
1641
|
'<iframe src="'+ url +'"></iframe>',
|
1642
|
+
'<small class="viewport-inner">',
|
1643
|
+
'<span class="x-frame-error">Unable to load iframe. This is likely due to the site\'s policy (x-frame-options).</span>',
|
1644
|
+
'</small>',
|
1357
1645
|
'</div>'
|
1358
1646
|
].join('');
|
1359
1647
|
|
@@ -1377,7 +1665,29 @@
|
|
1377
1665
|
}
|
1378
1666
|
|
1379
1667
|
/**
|
1380
|
-
*
|
1668
|
+
* Open or close help overlay window.
|
1669
|
+
*
|
1670
|
+
* @param {Boolean} [override] Flag which overrides the
|
1671
|
+
* toggle logic and forcibly sets the desired state. True means
|
1672
|
+
* help is open, false means it's closed.
|
1673
|
+
*/
|
1674
|
+
function toggleHelp( override ){
|
1675
|
+
|
1676
|
+
if( typeof override === 'boolean' ) {
|
1677
|
+
override ? showHelp() : closeOverlay();
|
1678
|
+
}
|
1679
|
+
else {
|
1680
|
+
if( dom.overlay ) {
|
1681
|
+
closeOverlay();
|
1682
|
+
}
|
1683
|
+
else {
|
1684
|
+
showHelp();
|
1685
|
+
}
|
1686
|
+
}
|
1687
|
+
}
|
1688
|
+
|
1689
|
+
/**
|
1690
|
+
* Opens an overlay window with help material.
|
1381
1691
|
*/
|
1382
1692
|
function showHelp() {
|
1383
1693
|
|
@@ -1443,10 +1753,8 @@
|
|
1443
1753
|
|
1444
1754
|
var size = getComputedSlideSize();
|
1445
1755
|
|
1446
|
-
var slidePadding = 20; // TODO Dig this out of DOM
|
1447
|
-
|
1448
1756
|
// Layout the contents of the slides
|
1449
|
-
layoutSlideContents( config.width, config.height
|
1757
|
+
layoutSlideContents( config.width, config.height );
|
1450
1758
|
|
1451
1759
|
dom.slides.style.width = size.width + 'px';
|
1452
1760
|
dom.slides.style.height = size.height + 'px';
|
@@ -1465,20 +1773,28 @@
|
|
1465
1773
|
dom.slides.style.top = '';
|
1466
1774
|
dom.slides.style.bottom = '';
|
1467
1775
|
dom.slides.style.right = '';
|
1468
|
-
|
1776
|
+
transformSlides( { layout: '' } );
|
1469
1777
|
}
|
1470
1778
|
else {
|
1471
|
-
// Prefer
|
1472
|
-
|
1779
|
+
// Prefer zoom for scaling up so that content remains crisp.
|
1780
|
+
// Don't use zoom to scale down since that can lead to shifts
|
1781
|
+
// in text layout/line breaks.
|
1782
|
+
if( scale > 1 && features.zoom ) {
|
1473
1783
|
dom.slides.style.zoom = scale;
|
1784
|
+
dom.slides.style.left = '';
|
1785
|
+
dom.slides.style.top = '';
|
1786
|
+
dom.slides.style.bottom = '';
|
1787
|
+
dom.slides.style.right = '';
|
1788
|
+
transformSlides( { layout: '' } );
|
1474
1789
|
}
|
1475
1790
|
// Apply scale transform as a fallback
|
1476
1791
|
else {
|
1792
|
+
dom.slides.style.zoom = '';
|
1477
1793
|
dom.slides.style.left = '50%';
|
1478
1794
|
dom.slides.style.top = '50%';
|
1479
1795
|
dom.slides.style.bottom = 'auto';
|
1480
1796
|
dom.slides.style.right = 'auto';
|
1481
|
-
|
1797
|
+
transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } );
|
1482
1798
|
}
|
1483
1799
|
}
|
1484
1800
|
|
@@ -1500,7 +1816,7 @@
|
|
1500
1816
|
slide.style.top = 0;
|
1501
1817
|
}
|
1502
1818
|
else {
|
1503
|
-
slide.style.top = Math.max( (
|
1819
|
+
slide.style.top = Math.max( ( size.height - slide.scrollHeight ) / 2, 0 ) + 'px';
|
1504
1820
|
}
|
1505
1821
|
}
|
1506
1822
|
else {
|
@@ -1512,6 +1828,10 @@
|
|
1512
1828
|
updateProgress();
|
1513
1829
|
updateParallax();
|
1514
1830
|
|
1831
|
+
if( isOverview() ) {
|
1832
|
+
updateOverview();
|
1833
|
+
}
|
1834
|
+
|
1515
1835
|
}
|
1516
1836
|
|
1517
1837
|
}
|
@@ -1519,8 +1839,11 @@
|
|
1519
1839
|
/**
|
1520
1840
|
* Applies layout logic to the contents of all slides in
|
1521
1841
|
* the presentation.
|
1842
|
+
*
|
1843
|
+
* @param {string|number} width
|
1844
|
+
* @param {string|number} height
|
1522
1845
|
*/
|
1523
|
-
function layoutSlideContents( width, height
|
1846
|
+
function layoutSlideContents( width, height ) {
|
1524
1847
|
|
1525
1848
|
// Handle sizing of elements with the 'stretch' class
|
1526
1849
|
toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
|
@@ -1552,6 +1875,9 @@
|
|
1552
1875
|
* Calculates the computed pixel size of our slides. These
|
1553
1876
|
* values are based on the width and height configuration
|
1554
1877
|
* options.
|
1878
|
+
*
|
1879
|
+
* @param {number} [presentationWidth=dom.wrapper.offsetWidth]
|
1880
|
+
* @param {number} [presentationHeight=dom.wrapper.offsetHeight]
|
1555
1881
|
*/
|
1556
1882
|
function getComputedSlideSize( presentationWidth, presentationHeight ) {
|
1557
1883
|
|
@@ -1566,7 +1892,7 @@
|
|
1566
1892
|
};
|
1567
1893
|
|
1568
1894
|
// Reduce available space by margin
|
1569
|
-
size.presentationWidth -= ( size.
|
1895
|
+
size.presentationWidth -= ( size.presentationWidth * config.margin );
|
1570
1896
|
size.presentationHeight -= ( size.presentationHeight * config.margin );
|
1571
1897
|
|
1572
1898
|
// Slide width may be a percentage of available width
|
@@ -1589,7 +1915,7 @@
|
|
1589
1915
|
* from the stack.
|
1590
1916
|
*
|
1591
1917
|
* @param {HTMLElement} stack The vertical stack element
|
1592
|
-
* @param {
|
1918
|
+
* @param {string|number} [v=0] Index to memorize
|
1593
1919
|
*/
|
1594
1920
|
function setPreviousVerticalIndex( stack, v ) {
|
1595
1921
|
|
@@ -1620,81 +1946,117 @@
|
|
1620
1946
|
}
|
1621
1947
|
|
1622
1948
|
/**
|
1623
|
-
* Displays the overview of slides (quick nav) by
|
1624
|
-
*
|
1625
|
-
*
|
1626
|
-
* Experimental feature, might be dropped if perf
|
1627
|
-
* can't be improved.
|
1949
|
+
* Displays the overview of slides (quick nav) by scaling
|
1950
|
+
* down and arranging all slide elements.
|
1628
1951
|
*/
|
1629
1952
|
function activateOverview() {
|
1630
1953
|
|
1631
1954
|
// Only proceed if enabled in config
|
1632
|
-
if( config.overview ) {
|
1955
|
+
if( config.overview && !isOverview() ) {
|
1956
|
+
|
1957
|
+
overview = true;
|
1958
|
+
|
1959
|
+
dom.wrapper.classList.add( 'overview' );
|
1960
|
+
dom.wrapper.classList.remove( 'overview-deactivating' );
|
1961
|
+
|
1962
|
+
if( features.overviewTransitions ) {
|
1963
|
+
setTimeout( function() {
|
1964
|
+
dom.wrapper.classList.add( 'overview-animated' );
|
1965
|
+
}, 1 );
|
1966
|
+
}
|
1633
1967
|
|
1634
1968
|
// Don't auto-slide while in overview mode
|
1635
1969
|
cancelAutoSlide();
|
1636
1970
|
|
1637
|
-
|
1971
|
+
// Move the backgrounds element into the slide container to
|
1972
|
+
// that the same scaling is applied
|
1973
|
+
dom.slides.appendChild( dom.background );
|
1638
1974
|
|
1639
|
-
//
|
1640
|
-
|
1975
|
+
// Clicking on an overview slide navigates to it
|
1976
|
+
toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
|
1977
|
+
if( !slide.classList.contains( 'stack' ) ) {
|
1978
|
+
slide.addEventListener( 'click', onOverviewSlideClicked, true );
|
1979
|
+
}
|
1980
|
+
} );
|
1641
1981
|
|
1642
|
-
|
1643
|
-
|
1982
|
+
// Calculate slide sizes
|
1983
|
+
var margin = 70;
|
1984
|
+
var slideSize = getComputedSlideSize();
|
1985
|
+
overviewSlideWidth = slideSize.width + margin;
|
1986
|
+
overviewSlideHeight = slideSize.height + margin;
|
1644
1987
|
|
1645
|
-
|
1988
|
+
// Reverse in RTL mode
|
1989
|
+
if( config.rtl ) {
|
1990
|
+
overviewSlideWidth = -overviewSlideWidth;
|
1991
|
+
}
|
1646
1992
|
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1993
|
+
updateSlidesVisibility();
|
1994
|
+
layoutOverview();
|
1995
|
+
updateOverview();
|
1650
1996
|
|
1651
|
-
|
1997
|
+
layout();
|
1652
1998
|
|
1653
|
-
|
1654
|
-
|
1999
|
+
// Notify observers of the overview showing
|
2000
|
+
dispatchEvent( 'overviewshown', {
|
2001
|
+
'indexh': indexh,
|
2002
|
+
'indexv': indexv,
|
2003
|
+
'currentSlide': currentSlide
|
2004
|
+
} );
|
1655
2005
|
|
1656
|
-
|
2006
|
+
}
|
1657
2007
|
|
1658
|
-
|
2008
|
+
}
|
1659
2009
|
|
1660
|
-
|
1661
|
-
|
2010
|
+
/**
|
2011
|
+
* Uses CSS transforms to position all slides in a grid for
|
2012
|
+
* display inside of the overview mode.
|
2013
|
+
*/
|
2014
|
+
function layoutOverview() {
|
1662
2015
|
|
1663
|
-
|
2016
|
+
// Layout slides
|
2017
|
+
toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {
|
2018
|
+
hslide.setAttribute( 'data-index-h', h );
|
2019
|
+
transformElement( hslide, 'translate3d(' + ( h * overviewSlideWidth ) + 'px, 0, 0)' );
|
1664
2020
|
|
1665
|
-
|
1666
|
-
vslide.setAttribute( 'data-index-v', j );
|
2021
|
+
if( hslide.classList.contains( 'stack' ) ) {
|
1667
2022
|
|
1668
|
-
|
1669
|
-
|
2023
|
+
toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {
|
2024
|
+
vslide.setAttribute( 'data-index-h', h );
|
2025
|
+
vslide.setAttribute( 'data-index-v', v );
|
1670
2026
|
|
1671
|
-
|
1672
|
-
|
1673
|
-
}
|
2027
|
+
transformElement( vslide, 'translate3d(0, ' + ( v * overviewSlideHeight ) + 'px, 0)' );
|
2028
|
+
} );
|
1674
2029
|
|
1675
|
-
|
1676
|
-
|
2030
|
+
}
|
2031
|
+
} );
|
1677
2032
|
|
1678
|
-
|
1679
|
-
|
2033
|
+
// Layout slide backgrounds
|
2034
|
+
toArray( dom.background.childNodes ).forEach( function( hbackground, h ) {
|
2035
|
+
transformElement( hbackground, 'translate3d(' + ( h * overviewSlideWidth ) + 'px, 0, 0)' );
|
1680
2036
|
|
1681
|
-
|
1682
|
-
|
2037
|
+
toArray( hbackground.querySelectorAll( '.slide-background' ) ).forEach( function( vbackground, v ) {
|
2038
|
+
transformElement( vbackground, 'translate3d(0, ' + ( v * overviewSlideHeight ) + 'px, 0)' );
|
2039
|
+
} );
|
2040
|
+
} );
|
1683
2041
|
|
1684
|
-
|
2042
|
+
}
|
1685
2043
|
|
1686
|
-
|
2044
|
+
/**
|
2045
|
+
* Moves the overview viewport to the current slides.
|
2046
|
+
* Called each time the current slide changes.
|
2047
|
+
*/
|
2048
|
+
function updateOverview() {
|
1687
2049
|
|
1688
|
-
|
1689
|
-
|
1690
|
-
dispatchEvent( 'overviewshown', {
|
1691
|
-
'indexh': indexh,
|
1692
|
-
'indexv': indexv,
|
1693
|
-
'currentSlide': currentSlide
|
1694
|
-
} );
|
1695
|
-
}
|
2050
|
+
var vmin = Math.min( window.innerWidth, window.innerHeight );
|
2051
|
+
var scale = Math.max( vmin / 5, 150 ) / vmin;
|
1696
2052
|
|
1697
|
-
|
2053
|
+
transformSlides( {
|
2054
|
+
overview: [
|
2055
|
+
'scale('+ scale +')',
|
2056
|
+
'translateX('+ ( -indexh * overviewSlideWidth ) +'px)',
|
2057
|
+
'translateY('+ ( -indexv * overviewSlideHeight ) +'px)'
|
2058
|
+
].join( ' ' )
|
2059
|
+
} );
|
1698
2060
|
|
1699
2061
|
}
|
1700
2062
|
|
@@ -1707,7 +2069,10 @@
|
|
1707
2069
|
// Only proceed if enabled in config
|
1708
2070
|
if( config.overview ) {
|
1709
2071
|
|
2072
|
+
overview = false;
|
2073
|
+
|
1710
2074
|
dom.wrapper.classList.remove( 'overview' );
|
2075
|
+
dom.wrapper.classList.remove( 'overview-animated' );
|
1711
2076
|
|
1712
2077
|
// Temporarily add a class so that transitions can do different things
|
1713
2078
|
// depending on whether they are exiting/entering overview, or just
|
@@ -1718,16 +2083,27 @@
|
|
1718
2083
|
dom.wrapper.classList.remove( 'overview-deactivating' );
|
1719
2084
|
}, 1 );
|
1720
2085
|
|
1721
|
-
//
|
2086
|
+
// Move the background element back out
|
2087
|
+
dom.wrapper.appendChild( dom.background );
|
2088
|
+
|
2089
|
+
// Clean up changes made to slides
|
1722
2090
|
toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
|
1723
|
-
// Resets all transforms to use the external styles
|
1724
2091
|
transformElement( slide, '' );
|
1725
2092
|
|
1726
2093
|
slide.removeEventListener( 'click', onOverviewSlideClicked, true );
|
1727
2094
|
} );
|
1728
2095
|
|
2096
|
+
// Clean up changes made to backgrounds
|
2097
|
+
toArray( dom.background.querySelectorAll( '.slide-background' ) ).forEach( function( background ) {
|
2098
|
+
transformElement( background, '' );
|
2099
|
+
} );
|
2100
|
+
|
2101
|
+
transformSlides( { overview: '' } );
|
2102
|
+
|
1729
2103
|
slide( indexh, indexv );
|
1730
2104
|
|
2105
|
+
layout();
|
2106
|
+
|
1731
2107
|
cueAutoSlide();
|
1732
2108
|
|
1733
2109
|
// Notify observers of the overview hiding
|
@@ -1743,7 +2119,7 @@
|
|
1743
2119
|
/**
|
1744
2120
|
* Toggles the slide overview mode on and off.
|
1745
2121
|
*
|
1746
|
-
* @param {Boolean} override
|
2122
|
+
* @param {Boolean} [override] Flag which overrides the
|
1747
2123
|
* toggle logic and forcibly sets the desired state. True means
|
1748
2124
|
* overview is open, false means it's closed.
|
1749
2125
|
*/
|
@@ -1766,7 +2142,7 @@
|
|
1766
2142
|
*/
|
1767
2143
|
function isOverview() {
|
1768
2144
|
|
1769
|
-
return
|
2145
|
+
return overview;
|
1770
2146
|
|
1771
2147
|
}
|
1772
2148
|
|
@@ -1774,8 +2150,9 @@
|
|
1774
2150
|
* Checks if the current or specified slide is vertical
|
1775
2151
|
* (nested within another slide).
|
1776
2152
|
*
|
1777
|
-
* @param {HTMLElement} slide
|
2153
|
+
* @param {HTMLElement} [slide=currentSlide] The slide to check
|
1778
2154
|
* orientation of
|
2155
|
+
* @return {Boolean}
|
1779
2156
|
*/
|
1780
2157
|
function isVerticalSlide( slide ) {
|
1781
2158
|
|
@@ -1794,10 +2171,10 @@
|
|
1794
2171
|
*/
|
1795
2172
|
function enterFullscreen() {
|
1796
2173
|
|
1797
|
-
var element = document.
|
2174
|
+
var element = document.documentElement;
|
1798
2175
|
|
1799
2176
|
// Check which implementation is available
|
1800
|
-
var requestMethod = element.
|
2177
|
+
var requestMethod = element.requestFullscreen ||
|
1801
2178
|
element.webkitRequestFullscreen ||
|
1802
2179
|
element.webkitRequestFullScreen ||
|
1803
2180
|
element.mozRequestFullScreen ||
|
@@ -1860,6 +2237,8 @@
|
|
1860
2237
|
|
1861
2238
|
/**
|
1862
2239
|
* Checks if we are currently in the paused mode.
|
2240
|
+
*
|
2241
|
+
* @return {Boolean}
|
1863
2242
|
*/
|
1864
2243
|
function isPaused() {
|
1865
2244
|
|
@@ -1870,7 +2249,7 @@
|
|
1870
2249
|
/**
|
1871
2250
|
* Toggles the auto slide mode on and off.
|
1872
2251
|
*
|
1873
|
-
* @param {Boolean} override
|
2252
|
+
* @param {Boolean} [override] Flag which sets the desired state.
|
1874
2253
|
* True means autoplay starts, false means it stops.
|
1875
2254
|
*/
|
1876
2255
|
|
@@ -1888,6 +2267,8 @@
|
|
1888
2267
|
|
1889
2268
|
/**
|
1890
2269
|
* Checks if the auto slide mode is currently on.
|
2270
|
+
*
|
2271
|
+
* @return {Boolean}
|
1891
2272
|
*/
|
1892
2273
|
function isAutoSliding() {
|
1893
2274
|
|
@@ -1900,11 +2281,11 @@
|
|
1900
2281
|
* slide which matches the specified horizontal and vertical
|
1901
2282
|
* indices.
|
1902
2283
|
*
|
1903
|
-
* @param {
|
1904
|
-
* @param {
|
1905
|
-
* @param {
|
2284
|
+
* @param {number} [h=indexh] Horizontal index of the target slide
|
2285
|
+
* @param {number} [v=indexv] Vertical index of the target slide
|
2286
|
+
* @param {number} [f] Index of a fragment within the
|
1906
2287
|
* target slide to activate
|
1907
|
-
* @param {
|
2288
|
+
* @param {number} [o] Origin for use in multimaster environments
|
1908
2289
|
*/
|
1909
2290
|
function slide( h, v, f, o ) {
|
1910
2291
|
|
@@ -1914,9 +2295,12 @@
|
|
1914
2295
|
// Query all horizontal slides in the deck
|
1915
2296
|
var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
|
1916
2297
|
|
2298
|
+
// Abort if there are no slides
|
2299
|
+
if( horizontalSlides.length === 0 ) return;
|
2300
|
+
|
1917
2301
|
// If no vertical index is specified and the upcoming slide is a
|
1918
2302
|
// stack, resume at its previous vertical index
|
1919
|
-
if( v === undefined ) {
|
2303
|
+
if( v === undefined && !isOverview() ) {
|
1920
2304
|
v = getPreviousVerticalIndex( horizontalSlides[ h ] );
|
1921
2305
|
}
|
1922
2306
|
|
@@ -1966,9 +2350,9 @@
|
|
1966
2350
|
document.documentElement.classList.remove( stateBefore.pop() );
|
1967
2351
|
}
|
1968
2352
|
|
1969
|
-
//
|
2353
|
+
// Update the overview if it's currently active
|
1970
2354
|
if( isOverview() ) {
|
1971
|
-
|
2355
|
+
updateOverview();
|
1972
2356
|
}
|
1973
2357
|
|
1974
2358
|
// Find the current horizontal slide and any possible vertical slides
|
@@ -2030,13 +2414,14 @@
|
|
2030
2414
|
}
|
2031
2415
|
|
2032
2416
|
// Announce the current slide contents, for screen readers
|
2033
|
-
dom.statusDiv.textContent = currentSlide
|
2417
|
+
dom.statusDiv.textContent = getStatusText( currentSlide );
|
2034
2418
|
|
2035
2419
|
updateControls();
|
2036
2420
|
updateProgress();
|
2037
2421
|
updateBackground();
|
2038
2422
|
updateParallax();
|
2039
2423
|
updateSlideNumber();
|
2424
|
+
updateNotes();
|
2040
2425
|
|
2041
2426
|
// Update the URL hash
|
2042
2427
|
writeURL();
|
@@ -2075,12 +2460,25 @@
|
|
2075
2460
|
|
2076
2461
|
updateControls();
|
2077
2462
|
updateProgress();
|
2078
|
-
updateBackground( true );
|
2079
2463
|
updateSlideNumber();
|
2080
2464
|
updateSlidesVisibility();
|
2465
|
+
updateBackground( true );
|
2466
|
+
updateNotes();
|
2081
2467
|
|
2082
2468
|
formatEmbeddedContent();
|
2083
2469
|
|
2470
|
+
// Start or stop embedded content depending on global config
|
2471
|
+
if( config.autoPlayMedia === false ) {
|
2472
|
+
stopEmbeddedContent( currentSlide );
|
2473
|
+
}
|
2474
|
+
else {
|
2475
|
+
startEmbeddedContent( currentSlide );
|
2476
|
+
}
|
2477
|
+
|
2478
|
+
if( isOverview() ) {
|
2479
|
+
layoutOverview();
|
2480
|
+
}
|
2481
|
+
|
2084
2482
|
}
|
2085
2483
|
|
2086
2484
|
/**
|
@@ -2130,16 +2528,33 @@
|
|
2130
2528
|
|
2131
2529
|
}
|
2132
2530
|
|
2531
|
+
/**
|
2532
|
+
* Randomly shuffles all slides in the deck.
|
2533
|
+
*/
|
2534
|
+
function shuffle() {
|
2535
|
+
|
2536
|
+
var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
|
2537
|
+
|
2538
|
+
slides.forEach( function( slide ) {
|
2539
|
+
|
2540
|
+
// Insert this slide next to another random slide. This may
|
2541
|
+
// cause the slide to insert before itself but that's fine.
|
2542
|
+
dom.slides.insertBefore( slide, slides[ Math.floor( Math.random() * slides.length ) ] );
|
2543
|
+
|
2544
|
+
} );
|
2545
|
+
|
2546
|
+
}
|
2547
|
+
|
2133
2548
|
/**
|
2134
2549
|
* Updates one dimension of slides by showing the slide
|
2135
2550
|
* with the specified index.
|
2136
2551
|
*
|
2137
|
-
* @param {
|
2552
|
+
* @param {string} selector A CSS selector that will fetch
|
2138
2553
|
* the group of slides we are working with
|
2139
|
-
* @param {
|
2554
|
+
* @param {number} index The index of the slide that should be
|
2140
2555
|
* shown
|
2141
2556
|
*
|
2142
|
-
* @return {
|
2557
|
+
* @return {number} The index of the slide that is now shown,
|
2143
2558
|
* might differ from the passed in index if it was out of
|
2144
2559
|
* bounds.
|
2145
2560
|
*/
|
@@ -2269,7 +2684,7 @@
|
|
2269
2684
|
viewDistance = isOverview() ? 6 : 2;
|
2270
2685
|
}
|
2271
2686
|
|
2272
|
-
//
|
2687
|
+
// All slides need to be visible when exporting to PDF
|
2273
2688
|
if( isPrintingPDF() ) {
|
2274
2689
|
viewDistance = Number.MAX_VALUE;
|
2275
2690
|
}
|
@@ -2280,8 +2695,14 @@
|
|
2280
2695
|
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ),
|
2281
2696
|
verticalSlidesLength = verticalSlides.length;
|
2282
2697
|
|
2283
|
-
//
|
2284
|
-
distanceX = Math.abs( (
|
2698
|
+
// Determine how far away this slide is from the present
|
2699
|
+
distanceX = Math.abs( ( indexh || 0 ) - x ) || 0;
|
2700
|
+
|
2701
|
+
// If the presentation is looped, distance should measure
|
2702
|
+
// 1 between the first and last slides
|
2703
|
+
if( config.loop ) {
|
2704
|
+
distanceX = Math.abs( ( ( indexh || 0 ) - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
|
2705
|
+
}
|
2285
2706
|
|
2286
2707
|
// Show the horizontal slide if it's within the view distance
|
2287
2708
|
if( distanceX < viewDistance ) {
|
@@ -2315,6 +2736,22 @@
|
|
2315
2736
|
|
2316
2737
|
}
|
2317
2738
|
|
2739
|
+
/**
|
2740
|
+
* Pick up notes from the current slide and display them
|
2741
|
+
* to the viewer.
|
2742
|
+
*
|
2743
|
+
* @see {@link config.showNotes}
|
2744
|
+
*/
|
2745
|
+
function updateNotes() {
|
2746
|
+
|
2747
|
+
if( config.showNotes && dom.speakerNotes && currentSlide && !isPrintingPDF() ) {
|
2748
|
+
|
2749
|
+
dom.speakerNotes.innerHTML = getSlideNotes() || '';
|
2750
|
+
|
2751
|
+
}
|
2752
|
+
|
2753
|
+
}
|
2754
|
+
|
2318
2755
|
/**
|
2319
2756
|
* Updates the progress bar to reflect the current slide.
|
2320
2757
|
*/
|
@@ -2331,19 +2768,65 @@
|
|
2331
2768
|
|
2332
2769
|
/**
|
2333
2770
|
* Updates the slide number div to reflect the current slide.
|
2771
|
+
*
|
2772
|
+
* The following slide number formats are available:
|
2773
|
+
* "h.v": horizontal . vertical slide number (default)
|
2774
|
+
* "h/v": horizontal / vertical slide number
|
2775
|
+
* "c": flattened slide number
|
2776
|
+
* "c/t": flattened slide number / total slides
|
2334
2777
|
*/
|
2335
2778
|
function updateSlideNumber() {
|
2336
2779
|
|
2337
2780
|
// Update slide number if enabled
|
2338
|
-
if( config.slideNumber && dom.slideNumber) {
|
2781
|
+
if( config.slideNumber && dom.slideNumber ) {
|
2339
2782
|
|
2340
|
-
|
2341
|
-
var
|
2342
|
-
|
2343
|
-
|
2783
|
+
var value = [];
|
2784
|
+
var format = 'h.v';
|
2785
|
+
|
2786
|
+
// Check if a custom number format is available
|
2787
|
+
if( typeof config.slideNumber === 'string' ) {
|
2788
|
+
format = config.slideNumber;
|
2789
|
+
}
|
2790
|
+
|
2791
|
+
switch( format ) {
|
2792
|
+
case 'c':
|
2793
|
+
value.push( getSlidePastCount() + 1 );
|
2794
|
+
break;
|
2795
|
+
case 'c/t':
|
2796
|
+
value.push( getSlidePastCount() + 1, '/', getTotalSlides() );
|
2797
|
+
break;
|
2798
|
+
case 'h/v':
|
2799
|
+
value.push( indexh + 1 );
|
2800
|
+
if( isVerticalSlide() ) value.push( '/', indexv + 1 );
|
2801
|
+
break;
|
2802
|
+
default:
|
2803
|
+
value.push( indexh + 1 );
|
2804
|
+
if( isVerticalSlide() ) value.push( '.', indexv + 1 );
|
2344
2805
|
}
|
2345
2806
|
|
2346
|
-
dom.slideNumber.innerHTML =
|
2807
|
+
dom.slideNumber.innerHTML = formatSlideNumber( value[0], value[1], value[2] );
|
2808
|
+
}
|
2809
|
+
|
2810
|
+
}
|
2811
|
+
|
2812
|
+
/**
|
2813
|
+
* Applies HTML formatting to a slide number before it's
|
2814
|
+
* written to the DOM.
|
2815
|
+
*
|
2816
|
+
* @param {number} a Current slide
|
2817
|
+
* @param {string} delimiter Character to separate slide numbers
|
2818
|
+
* @param {(number|*)} b Total slides
|
2819
|
+
* @return {string} HTML string fragment
|
2820
|
+
*/
|
2821
|
+
function formatSlideNumber( a, delimiter, b ) {
|
2822
|
+
|
2823
|
+
if( typeof b === 'number' && !isNaN( b ) ) {
|
2824
|
+
return '<span class="slide-number-a">'+ a +'</span>' +
|
2825
|
+
'<span class="slide-number-delimiter">'+ delimiter +'</span>' +
|
2826
|
+
'<span class="slide-number-b">'+ b +'</span>';
|
2827
|
+
}
|
2828
|
+
else {
|
2829
|
+
return '<span class="slide-number-a">'+ a +'</span>';
|
2347
2830
|
}
|
2348
2831
|
|
2349
2832
|
}
|
@@ -2364,34 +2847,37 @@
|
|
2364
2847
|
.concat( dom.controlsNext ).forEach( function( node ) {
|
2365
2848
|
node.classList.remove( 'enabled' );
|
2366
2849
|
node.classList.remove( 'fragmented' );
|
2850
|
+
|
2851
|
+
// Set 'disabled' attribute on all directions
|
2852
|
+
node.setAttribute( 'disabled', 'disabled' );
|
2367
2853
|
} );
|
2368
2854
|
|
2369
|
-
// Add the 'enabled' class to the available routes
|
2370
|
-
if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' );
|
2371
|
-
if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
2372
|
-
if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' );
|
2373
|
-
if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
2855
|
+
// Add the 'enabled' class to the available routes; remove 'disabled' attribute to enable buttons
|
2856
|
+
if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2857
|
+
if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2858
|
+
if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2859
|
+
if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2374
2860
|
|
2375
2861
|
// Prev/next buttons
|
2376
|
-
if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
2377
|
-
if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
2862
|
+
if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2863
|
+
if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2378
2864
|
|
2379
2865
|
// Highlight fragment directions
|
2380
2866
|
if( currentSlide ) {
|
2381
2867
|
|
2382
2868
|
// Always apply fragment decorator to prev/next buttons
|
2383
|
-
if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2384
|
-
if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2869
|
+
if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2870
|
+
if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2385
2871
|
|
2386
2872
|
// Apply fragment decorators to directional buttons based on
|
2387
2873
|
// what slide axis they are in
|
2388
2874
|
if( isVerticalSlide( currentSlide ) ) {
|
2389
|
-
if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2390
|
-
if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2875
|
+
if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2876
|
+
if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2391
2877
|
}
|
2392
2878
|
else {
|
2393
|
-
if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2394
|
-
if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2879
|
+
if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2880
|
+
if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2395
2881
|
}
|
2396
2882
|
|
2397
2883
|
}
|
@@ -2402,7 +2888,7 @@
|
|
2402
2888
|
* Updates the background elements to reflect the current
|
2403
2889
|
* slide.
|
2404
2890
|
*
|
2405
|
-
* @param {
|
2891
|
+
* @param {boolean} includeAll If true, the backgrounds of
|
2406
2892
|
* all vertical slides (not just the present) will be updated.
|
2407
2893
|
*/
|
2408
2894
|
function updateBackground( includeAll ) {
|
@@ -2459,21 +2945,25 @@
|
|
2459
2945
|
|
2460
2946
|
} );
|
2461
2947
|
|
2462
|
-
// Stop
|
2948
|
+
// Stop content inside of previous backgrounds
|
2463
2949
|
if( previousBackground ) {
|
2464
2950
|
|
2465
|
-
|
2466
|
-
if( previousVideo ) previousVideo.pause();
|
2951
|
+
stopEmbeddedContent( previousBackground );
|
2467
2952
|
|
2468
2953
|
}
|
2469
2954
|
|
2955
|
+
// Start content in the current background
|
2470
2956
|
if( currentBackground ) {
|
2471
2957
|
|
2472
|
-
|
2473
|
-
|
2474
|
-
|
2475
|
-
|
2476
|
-
|
2958
|
+
startEmbeddedContent( currentBackground );
|
2959
|
+
|
2960
|
+
var backgroundImageURL = currentBackground.style.backgroundImage || '';
|
2961
|
+
|
2962
|
+
// Restart GIFs (doesn't work in Firefox)
|
2963
|
+
if( /\.gif/i.test( backgroundImageURL ) ) {
|
2964
|
+
currentBackground.style.backgroundImage = '';
|
2965
|
+
window.getComputedStyle( currentBackground ).opacity;
|
2966
|
+
currentBackground.style.backgroundImage = backgroundImageURL;
|
2477
2967
|
}
|
2478
2968
|
|
2479
2969
|
// Don't transition between identical backgrounds. This
|
@@ -2530,15 +3020,35 @@
|
|
2530
3020
|
backgroundHeight = parseInt( backgroundSize[1], 10 );
|
2531
3021
|
}
|
2532
3022
|
|
2533
|
-
var slideWidth = dom.background.offsetWidth
|
2534
|
-
|
2535
|
-
|
3023
|
+
var slideWidth = dom.background.offsetWidth,
|
3024
|
+
horizontalSlideCount = horizontalSlides.length,
|
3025
|
+
horizontalOffsetMultiplier,
|
3026
|
+
horizontalOffset;
|
3027
|
+
|
3028
|
+
if( typeof config.parallaxBackgroundHorizontal === 'number' ) {
|
3029
|
+
horizontalOffsetMultiplier = config.parallaxBackgroundHorizontal;
|
3030
|
+
}
|
3031
|
+
else {
|
3032
|
+
horizontalOffsetMultiplier = horizontalSlideCount > 1 ? ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) : 0;
|
3033
|
+
}
|
3034
|
+
|
3035
|
+
horizontalOffset = horizontalOffsetMultiplier * indexh * -1;
|
3036
|
+
|
3037
|
+
var slideHeight = dom.background.offsetHeight,
|
3038
|
+
verticalSlideCount = verticalSlides.length,
|
3039
|
+
verticalOffsetMultiplier,
|
3040
|
+
verticalOffset;
|
3041
|
+
|
3042
|
+
if( typeof config.parallaxBackgroundVertical === 'number' ) {
|
3043
|
+
verticalOffsetMultiplier = config.parallaxBackgroundVertical;
|
3044
|
+
}
|
3045
|
+
else {
|
3046
|
+
verticalOffsetMultiplier = ( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 );
|
3047
|
+
}
|
2536
3048
|
|
2537
|
-
|
2538
|
-
var verticalSlideCount = verticalSlides.length;
|
2539
|
-
var verticalOffset = verticalSlideCount > 1 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
|
3049
|
+
verticalOffset = verticalSlideCount > 0 ? verticalOffsetMultiplier * indexv : 0;
|
2540
3050
|
|
2541
|
-
dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
|
3051
|
+
dom.background.style.backgroundPosition = horizontalOffset + 'px ' + -verticalOffset + 'px';
|
2542
3052
|
|
2543
3053
|
}
|
2544
3054
|
|
@@ -2548,14 +3058,23 @@
|
|
2548
3058
|
* Called when the given slide is within the configured view
|
2549
3059
|
* distance. Shows the slide element and loads any content
|
2550
3060
|
* that is set to load lazily (data-src).
|
3061
|
+
*
|
3062
|
+
* @param {HTMLElement} slide Slide to show
|
3063
|
+
*/
|
3064
|
+
/**
|
3065
|
+
* Called when the given slide is within the configured view
|
3066
|
+
* distance. Shows the slide element and loads any content
|
3067
|
+
* that is set to load lazily (data-src).
|
3068
|
+
*
|
3069
|
+
* @param {HTMLElement} slide Slide to show
|
2551
3070
|
*/
|
2552
3071
|
function showSlide( slide ) {
|
2553
3072
|
|
2554
3073
|
// Show the slide element
|
2555
|
-
slide.style.display =
|
3074
|
+
slide.style.display = config.display;
|
2556
3075
|
|
2557
3076
|
// Media elements with data-src attributes
|
2558
|
-
toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]
|
3077
|
+
toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) {
|
2559
3078
|
element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
|
2560
3079
|
element.removeAttribute( 'data-src' );
|
2561
3080
|
} );
|
@@ -2590,6 +3109,8 @@
|
|
2590
3109
|
|
2591
3110
|
var backgroundImage = slide.getAttribute( 'data-background-image' ),
|
2592
3111
|
backgroundVideo = slide.getAttribute( 'data-background-video' ),
|
3112
|
+
backgroundVideoLoop = slide.hasAttribute( 'data-background-video-loop' ),
|
3113
|
+
backgroundVideoMuted = slide.hasAttribute( 'data-background-video-muted' ),
|
2593
3114
|
backgroundIframe = slide.getAttribute( 'data-background-iframe' );
|
2594
3115
|
|
2595
3116
|
// Images
|
@@ -2600,6 +3121,23 @@
|
|
2600
3121
|
else if ( backgroundVideo && !isSpeakerNotes() ) {
|
2601
3122
|
var video = document.createElement( 'video' );
|
2602
3123
|
|
3124
|
+
if( backgroundVideoLoop ) {
|
3125
|
+
video.setAttribute( 'loop', '' );
|
3126
|
+
}
|
3127
|
+
|
3128
|
+
if( backgroundVideoMuted ) {
|
3129
|
+
video.muted = true;
|
3130
|
+
}
|
3131
|
+
|
3132
|
+
// Inline video playback works (at least in Mobile Safari) as
|
3133
|
+
// long as the video is muted and the `playsinline` attribute is
|
3134
|
+
// present
|
3135
|
+
if( isMobileDevice ) {
|
3136
|
+
video.muted = true;
|
3137
|
+
video.autoplay = true;
|
3138
|
+
video.setAttribute( 'playsinline', '' );
|
3139
|
+
}
|
3140
|
+
|
2603
3141
|
// Support comma separated lists of video sources
|
2604
3142
|
backgroundVideo.split( ',' ).forEach( function( source ) {
|
2605
3143
|
video.innerHTML += '<source src="'+ source +'">';
|
@@ -2608,17 +3146,30 @@
|
|
2608
3146
|
background.appendChild( video );
|
2609
3147
|
}
|
2610
3148
|
// Iframes
|
2611
|
-
else if
|
3149
|
+
else if( backgroundIframe ) {
|
2612
3150
|
var iframe = document.createElement( 'iframe' );
|
3151
|
+
iframe.setAttribute( 'allowfullscreen', '' );
|
3152
|
+
iframe.setAttribute( 'mozallowfullscreen', '' );
|
3153
|
+
iframe.setAttribute( 'webkitallowfullscreen', '' );
|
3154
|
+
|
3155
|
+
// Only load autoplaying content when the slide is shown to
|
3156
|
+
// avoid having it play in the background
|
3157
|
+
if( /autoplay=(1|true|yes)/gi.test( backgroundIframe ) ) {
|
3158
|
+
iframe.setAttribute( 'data-src', backgroundIframe );
|
3159
|
+
}
|
3160
|
+
else {
|
2613
3161
|
iframe.setAttribute( 'src', backgroundIframe );
|
2614
|
-
|
2615
|
-
|
2616
|
-
|
2617
|
-
|
3162
|
+
}
|
3163
|
+
|
3164
|
+
iframe.style.width = '100%';
|
3165
|
+
iframe.style.height = '100%';
|
3166
|
+
iframe.style.maxHeight = '100%';
|
3167
|
+
iframe.style.maxWidth = '100%';
|
2618
3168
|
|
2619
3169
|
background.appendChild( iframe );
|
2620
3170
|
}
|
2621
3171
|
}
|
3172
|
+
|
2622
3173
|
}
|
2623
3174
|
|
2624
3175
|
}
|
@@ -2626,6 +3177,8 @@
|
|
2626
3177
|
/**
|
2627
3178
|
* Called when the given slide is moved outside of the
|
2628
3179
|
* configured view distance.
|
3180
|
+
*
|
3181
|
+
* @param {HTMLElement} slide
|
2629
3182
|
*/
|
2630
3183
|
function hideSlide( slide ) {
|
2631
3184
|
|
@@ -2644,7 +3197,7 @@
|
|
2644
3197
|
/**
|
2645
3198
|
* Determine what available routes there are for navigation.
|
2646
3199
|
*
|
2647
|
-
* @return {
|
3200
|
+
* @return {{left: boolean, right: boolean, up: boolean, down: boolean}}
|
2648
3201
|
*/
|
2649
3202
|
function availableRoutes() {
|
2650
3203
|
|
@@ -2673,7 +3226,7 @@
|
|
2673
3226
|
* Returns an object describing the available fragment
|
2674
3227
|
* directions.
|
2675
3228
|
*
|
2676
|
-
* @return {
|
3229
|
+
* @return {{prev: boolean, next: boolean}}
|
2677
3230
|
*/
|
2678
3231
|
function availableFragments() {
|
2679
3232
|
|
@@ -2697,56 +3250,157 @@
|
|
2697
3250
|
*/
|
2698
3251
|
function formatEmbeddedContent() {
|
2699
3252
|
|
3253
|
+
var _appendParamToIframeSource = function( sourceAttribute, sourceURL, param ) {
|
3254
|
+
toArray( dom.slides.querySelectorAll( 'iframe['+ sourceAttribute +'*="'+ sourceURL +'"]' ) ).forEach( function( el ) {
|
3255
|
+
var src = el.getAttribute( sourceAttribute );
|
3256
|
+
if( src && src.indexOf( param ) === -1 ) {
|
3257
|
+
el.setAttribute( sourceAttribute, src + ( !/\?/.test( src ) ? '?' : '&' ) + param );
|
3258
|
+
}
|
3259
|
+
});
|
3260
|
+
};
|
3261
|
+
|
2700
3262
|
// YouTube frames must include "?enablejsapi=1"
|
2701
|
-
|
2702
|
-
|
2703
|
-
if( !/enablejsapi\=1/gi.test( src ) ) {
|
2704
|
-
el.setAttribute( 'src', src + ( !/\?/.test( src ) ? '?' : '&' ) + 'enablejsapi=1' );
|
2705
|
-
}
|
2706
|
-
});
|
3263
|
+
_appendParamToIframeSource( 'src', 'youtube.com/embed/', 'enablejsapi=1' );
|
3264
|
+
_appendParamToIframeSource( 'data-src', 'youtube.com/embed/', 'enablejsapi=1' );
|
2707
3265
|
|
2708
3266
|
// Vimeo frames must include "?api=1"
|
2709
|
-
|
2710
|
-
|
2711
|
-
if( !/api\=1/gi.test( src ) ) {
|
2712
|
-
el.setAttribute( 'src', src + ( !/\?/.test( src ) ? '?' : '&' ) + 'api=1' );
|
2713
|
-
}
|
2714
|
-
});
|
3267
|
+
_appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' );
|
3268
|
+
_appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' );
|
2715
3269
|
|
2716
3270
|
}
|
2717
3271
|
|
2718
3272
|
/**
|
2719
3273
|
* Start playback of any embedded content inside of
|
2720
|
-
* the
|
3274
|
+
* the given element.
|
3275
|
+
*
|
3276
|
+
* @param {HTMLElement} element
|
2721
3277
|
*/
|
2722
|
-
function startEmbeddedContent(
|
3278
|
+
function startEmbeddedContent( element ) {
|
3279
|
+
|
3280
|
+
if( element && !isSpeakerNotes() ) {
|
3281
|
+
|
3282
|
+
// Restart GIFs
|
3283
|
+
toArray( element.querySelectorAll( 'img[src$=".gif"]' ) ).forEach( function( el ) {
|
3284
|
+
// Setting the same unchanged source like this was confirmed
|
3285
|
+
// to work in Chrome, FF & Safari
|
3286
|
+
el.setAttribute( 'src', el.getAttribute( 'src' ) );
|
3287
|
+
} );
|
2723
3288
|
|
2724
|
-
if( slide && !isSpeakerNotes() ) {
|
2725
3289
|
// HTML5 media elements
|
2726
|
-
toArray(
|
2727
|
-
if( el.
|
2728
|
-
|
3290
|
+
toArray( element.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
|
3291
|
+
if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {
|
3292
|
+
return;
|
3293
|
+
}
|
3294
|
+
|
3295
|
+
// Prefer an explicit global autoplay setting
|
3296
|
+
var autoplay = config.autoPlayMedia;
|
3297
|
+
|
3298
|
+
// If no global setting is available, fall back on the element's
|
3299
|
+
// own autoplay setting
|
3300
|
+
if( typeof autoplay !== 'boolean' ) {
|
3301
|
+
autoplay = el.hasAttribute( 'data-autoplay' ) || !!closestParent( el, '.slide-background' );
|
3302
|
+
}
|
3303
|
+
|
3304
|
+
if( autoplay && typeof el.play === 'function' ) {
|
3305
|
+
|
3306
|
+
if( el.readyState > 1 ) {
|
3307
|
+
startEmbeddedMedia( { target: el } );
|
3308
|
+
}
|
3309
|
+
else {
|
3310
|
+
el.removeEventListener( 'loadeddata', startEmbeddedMedia ); // remove first to avoid dupes
|
3311
|
+
el.addEventListener( 'loadeddata', startEmbeddedMedia );
|
3312
|
+
}
|
3313
|
+
|
2729
3314
|
}
|
2730
3315
|
} );
|
2731
3316
|
|
2732
|
-
//
|
2733
|
-
toArray(
|
2734
|
-
el.
|
2735
|
-
|
3317
|
+
// Normal iframes
|
3318
|
+
toArray( element.querySelectorAll( 'iframe[src]' ) ).forEach( function( el ) {
|
3319
|
+
if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {
|
3320
|
+
return;
|
3321
|
+
}
|
3322
|
+
|
3323
|
+
startEmbeddedIframe( { target: el } );
|
3324
|
+
} );
|
2736
3325
|
|
2737
|
-
//
|
2738
|
-
toArray(
|
2739
|
-
if( el.
|
2740
|
-
|
3326
|
+
// Lazy loading iframes
|
3327
|
+
toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
|
3328
|
+
if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {
|
3329
|
+
return;
|
2741
3330
|
}
|
2742
|
-
});
|
2743
3331
|
|
2744
|
-
|
2745
|
-
|
2746
|
-
|
2747
|
-
el.
|
3332
|
+
if( el.getAttribute( 'src' ) !== el.getAttribute( 'data-src' ) ) {
|
3333
|
+
el.removeEventListener( 'load', startEmbeddedIframe ); // remove first to avoid dupes
|
3334
|
+
el.addEventListener( 'load', startEmbeddedIframe );
|
3335
|
+
el.setAttribute( 'src', el.getAttribute( 'data-src' ) );
|
2748
3336
|
}
|
2749
|
-
});
|
3337
|
+
} );
|
3338
|
+
|
3339
|
+
}
|
3340
|
+
|
3341
|
+
}
|
3342
|
+
|
3343
|
+
/**
|
3344
|
+
* Starts playing an embedded video/audio element after
|
3345
|
+
* it has finished loading.
|
3346
|
+
*
|
3347
|
+
* @param {object} event
|
3348
|
+
*/
|
3349
|
+
function startEmbeddedMedia( event ) {
|
3350
|
+
|
3351
|
+
var isAttachedToDOM = !!closestParent( event.target, 'html' ),
|
3352
|
+
isVisible = !!closestParent( event.target, '.present' );
|
3353
|
+
|
3354
|
+
if( isAttachedToDOM && isVisible ) {
|
3355
|
+
event.target.currentTime = 0;
|
3356
|
+
event.target.play();
|
3357
|
+
}
|
3358
|
+
|
3359
|
+
event.target.removeEventListener( 'loadeddata', startEmbeddedMedia );
|
3360
|
+
|
3361
|
+
}
|
3362
|
+
|
3363
|
+
/**
|
3364
|
+
* "Starts" the content of an embedded iframe using the
|
3365
|
+
* postMessage API.
|
3366
|
+
*
|
3367
|
+
* @param {object} event
|
3368
|
+
*/
|
3369
|
+
function startEmbeddedIframe( event ) {
|
3370
|
+
|
3371
|
+
var iframe = event.target;
|
3372
|
+
|
3373
|
+
if( iframe && iframe.contentWindow ) {
|
3374
|
+
|
3375
|
+
var isAttachedToDOM = !!closestParent( event.target, 'html' ),
|
3376
|
+
isVisible = !!closestParent( event.target, '.present' );
|
3377
|
+
|
3378
|
+
if( isAttachedToDOM && isVisible ) {
|
3379
|
+
|
3380
|
+
// Prefer an explicit global autoplay setting
|
3381
|
+
var autoplay = config.autoPlayMedia;
|
3382
|
+
|
3383
|
+
// If no global setting is available, fall back on the element's
|
3384
|
+
// own autoplay setting
|
3385
|
+
if( typeof autoplay !== 'boolean' ) {
|
3386
|
+
autoplay = iframe.hasAttribute( 'data-autoplay' ) || !!closestParent( iframe, '.slide-background' );
|
3387
|
+
}
|
3388
|
+
|
3389
|
+
// YouTube postMessage API
|
3390
|
+
if( /youtube\.com\/embed\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) {
|
3391
|
+
iframe.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
|
3392
|
+
}
|
3393
|
+
// Vimeo postMessage API
|
3394
|
+
else if( /player\.vimeo\.com\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) {
|
3395
|
+
iframe.contentWindow.postMessage( '{"method":"play"}', '*' );
|
3396
|
+
}
|
3397
|
+
// Generic postMessage API
|
3398
|
+
else {
|
3399
|
+
iframe.contentWindow.postMessage( 'slide:start', '*' );
|
3400
|
+
}
|
3401
|
+
|
3402
|
+
}
|
3403
|
+
|
2750
3404
|
}
|
2751
3405
|
|
2752
3406
|
}
|
@@ -2754,49 +3408,62 @@
|
|
2754
3408
|
/**
|
2755
3409
|
* Stop playback of any embedded content inside of
|
2756
3410
|
* the targeted slide.
|
3411
|
+
*
|
3412
|
+
* @param {HTMLElement} element
|
2757
3413
|
*/
|
2758
|
-
function stopEmbeddedContent(
|
3414
|
+
function stopEmbeddedContent( element ) {
|
2759
3415
|
|
2760
|
-
if(
|
3416
|
+
if( element && element.parentNode ) {
|
2761
3417
|
// HTML5 media elements
|
2762
|
-
toArray(
|
2763
|
-
if( !el.hasAttribute( 'data-ignore' ) ) {
|
3418
|
+
toArray( element.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
|
3419
|
+
if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {
|
3420
|
+
el.setAttribute('data-paused-by-reveal', '');
|
2764
3421
|
el.pause();
|
2765
3422
|
}
|
2766
3423
|
} );
|
2767
3424
|
|
2768
|
-
//
|
2769
|
-
toArray(
|
2770
|
-
el.contentWindow.postMessage( 'slide:stop', '*' );
|
3425
|
+
// Generic postMessage API for non-lazy loaded iframes
|
3426
|
+
toArray( element.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
|
3427
|
+
if( el.contentWindow ) el.contentWindow.postMessage( 'slide:stop', '*' );
|
3428
|
+
el.removeEventListener( 'load', startEmbeddedIframe );
|
2771
3429
|
});
|
2772
3430
|
|
2773
|
-
// YouTube
|
2774
|
-
toArray(
|
2775
|
-
if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
|
3431
|
+
// YouTube postMessage API
|
3432
|
+
toArray( element.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
|
3433
|
+
if( !el.hasAttribute( 'data-ignore' ) && el.contentWindow && typeof el.contentWindow.postMessage === 'function' ) {
|
2776
3434
|
el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
|
2777
3435
|
}
|
2778
3436
|
});
|
2779
3437
|
|
2780
|
-
// Vimeo
|
2781
|
-
toArray(
|
2782
|
-
if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
|
3438
|
+
// Vimeo postMessage API
|
3439
|
+
toArray( element.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
|
3440
|
+
if( !el.hasAttribute( 'data-ignore' ) && el.contentWindow && typeof el.contentWindow.postMessage === 'function' ) {
|
2783
3441
|
el.contentWindow.postMessage( '{"method":"pause"}', '*' );
|
2784
3442
|
}
|
2785
3443
|
});
|
3444
|
+
|
3445
|
+
// Lazy loading iframes
|
3446
|
+
toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
|
3447
|
+
// Only removing the src doesn't actually unload the frame
|
3448
|
+
// in all browsers (Firefox) so we set it to blank first
|
3449
|
+
el.setAttribute( 'src', 'about:blank' );
|
3450
|
+
el.removeAttribute( 'src' );
|
3451
|
+
} );
|
2786
3452
|
}
|
2787
3453
|
|
2788
3454
|
}
|
2789
3455
|
|
2790
3456
|
/**
|
2791
|
-
* Returns
|
2792
|
-
*
|
3457
|
+
* Returns the number of past slides. This can be used as a global
|
3458
|
+
* flattened index for slides.
|
3459
|
+
*
|
3460
|
+
* @return {number} Past slide count
|
2793
3461
|
*/
|
2794
|
-
function
|
3462
|
+
function getSlidePastCount() {
|
2795
3463
|
|
2796
3464
|
var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
|
2797
3465
|
|
2798
|
-
// The number of past
|
2799
|
-
var totalCount = getTotalSlides();
|
3466
|
+
// The number of past slides
|
2800
3467
|
var pastCount = 0;
|
2801
3468
|
|
2802
3469
|
// Step through all slides and count the past ones
|
@@ -2828,6 +3495,22 @@
|
|
2828
3495
|
|
2829
3496
|
}
|
2830
3497
|
|
3498
|
+
return pastCount;
|
3499
|
+
|
3500
|
+
}
|
3501
|
+
|
3502
|
+
/**
|
3503
|
+
* Returns a value ranging from 0-1 that represents
|
3504
|
+
* how far into the presentation we have navigated.
|
3505
|
+
*
|
3506
|
+
* @return {number}
|
3507
|
+
*/
|
3508
|
+
function getProgress() {
|
3509
|
+
|
3510
|
+
// The number of past and total slides
|
3511
|
+
var totalCount = getTotalSlides();
|
3512
|
+
var pastCount = getSlidePastCount();
|
3513
|
+
|
2831
3514
|
if( currentSlide ) {
|
2832
3515
|
|
2833
3516
|
var allFragments = currentSlide.querySelectorAll( '.fragment' );
|
@@ -2854,6 +3537,8 @@
|
|
2854
3537
|
/**
|
2855
3538
|
* Checks if this presentation is running inside of the
|
2856
3539
|
* speaker notes window.
|
3540
|
+
*
|
3541
|
+
* @return {boolean}
|
2857
3542
|
*/
|
2858
3543
|
function isSpeakerNotes() {
|
2859
3544
|
|
@@ -2880,7 +3565,7 @@
|
|
2880
3565
|
// Ensure the named link is a valid HTML ID attribute
|
2881
3566
|
if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {
|
2882
3567
|
// Find the slide with the specified ID
|
2883
|
-
element = document.
|
3568
|
+
element = document.getElementById( name );
|
2884
3569
|
}
|
2885
3570
|
|
2886
3571
|
if( element ) {
|
@@ -2909,7 +3594,7 @@
|
|
2909
3594
|
* Updates the page URL (hash) to reflect the current
|
2910
3595
|
* state.
|
2911
3596
|
*
|
2912
|
-
* @param {
|
3597
|
+
* @param {number} delay The time in ms to wait before
|
2913
3598
|
* writing the hash
|
2914
3599
|
*/
|
2915
3600
|
function writeURL( delay ) {
|
@@ -2929,7 +3614,6 @@
|
|
2929
3614
|
// Attempt to create a named link based on the slide's ID
|
2930
3615
|
var id = currentSlide.getAttribute( 'id' );
|
2931
3616
|
if( id ) {
|
2932
|
-
id = id.toLowerCase();
|
2933
3617
|
id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
|
2934
3618
|
}
|
2935
3619
|
|
@@ -2948,16 +3632,15 @@
|
|
2948
3632
|
}
|
2949
3633
|
|
2950
3634
|
}
|
2951
|
-
|
2952
3635
|
/**
|
2953
|
-
* Retrieves the h/v location of the current,
|
2954
|
-
* slide.
|
3636
|
+
* Retrieves the h/v location and fragment of the current,
|
3637
|
+
* or specified, slide.
|
2955
3638
|
*
|
2956
|
-
* @param {HTMLElement} slide If specified, the returned
|
3639
|
+
* @param {HTMLElement} [slide] If specified, the returned
|
2957
3640
|
* index will be for this slide rather than the currently
|
2958
3641
|
* active one
|
2959
3642
|
*
|
2960
|
-
* @return {
|
3643
|
+
* @return {{h: number, v: number, f: number}}
|
2961
3644
|
*/
|
2962
3645
|
function getIndices( slide ) {
|
2963
3646
|
|
@@ -3003,17 +3686,30 @@
|
|
3003
3686
|
|
3004
3687
|
}
|
3005
3688
|
|
3689
|
+
/**
|
3690
|
+
* Retrieves all slides in this presentation.
|
3691
|
+
*/
|
3692
|
+
function getSlides() {
|
3693
|
+
|
3694
|
+
return toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ));
|
3695
|
+
|
3696
|
+
}
|
3697
|
+
|
3006
3698
|
/**
|
3007
3699
|
* Retrieves the total number of slides in this presentation.
|
3700
|
+
*
|
3701
|
+
* @return {number}
|
3008
3702
|
*/
|
3009
3703
|
function getTotalSlides() {
|
3010
3704
|
|
3011
|
-
return
|
3705
|
+
return getSlides().length;
|
3012
3706
|
|
3013
3707
|
}
|
3014
3708
|
|
3015
3709
|
/**
|
3016
3710
|
* Returns the slide element matching the specified index.
|
3711
|
+
*
|
3712
|
+
* @return {HTMLElement}
|
3017
3713
|
*/
|
3018
3714
|
function getSlide( x, y ) {
|
3019
3715
|
|
@@ -3033,6 +3729,10 @@
|
|
3033
3729
|
* All slides, even the ones with no background properties
|
3034
3730
|
* defined, have a background element so as long as the
|
3035
3731
|
* index is valid an element will be returned.
|
3732
|
+
*
|
3733
|
+
* @param {number} x Horizontal background index
|
3734
|
+
* @param {number} y Vertical background index
|
3735
|
+
* @return {(HTMLElement[]|*)}
|
3036
3736
|
*/
|
3037
3737
|
function getSlideBackground( x, y ) {
|
3038
3738
|
|
@@ -3041,10 +3741,7 @@
|
|
3041
3741
|
if( isPrintingPDF() ) {
|
3042
3742
|
var slide = getSlide( x, y );
|
3043
3743
|
if( slide ) {
|
3044
|
-
|
3045
|
-
if( background && background.parentNode === slide ) {
|
3046
|
-
return background;
|
3047
|
-
}
|
3744
|
+
return slide.slideBackgroundElement;
|
3048
3745
|
}
|
3049
3746
|
|
3050
3747
|
return undefined;
|
@@ -3061,10 +3758,41 @@
|
|
3061
3758
|
|
3062
3759
|
}
|
3063
3760
|
|
3761
|
+
/**
|
3762
|
+
* Retrieves the speaker notes from a slide. Notes can be
|
3763
|
+
* defined in two ways:
|
3764
|
+
* 1. As a data-notes attribute on the slide <section>
|
3765
|
+
* 2. As an <aside class="notes"> inside of the slide
|
3766
|
+
*
|
3767
|
+
* @param {HTMLElement} [slide=currentSlide]
|
3768
|
+
* @return {(string|null)}
|
3769
|
+
*/
|
3770
|
+
function getSlideNotes( slide ) {
|
3771
|
+
|
3772
|
+
// Default to the current slide
|
3773
|
+
slide = slide || currentSlide;
|
3774
|
+
|
3775
|
+
// Notes can be specified via the data-notes attribute...
|
3776
|
+
if( slide.hasAttribute( 'data-notes' ) ) {
|
3777
|
+
return slide.getAttribute( 'data-notes' );
|
3778
|
+
}
|
3779
|
+
|
3780
|
+
// ... or using an <aside class="notes"> element
|
3781
|
+
var notesElement = slide.querySelector( 'aside.notes' );
|
3782
|
+
if( notesElement ) {
|
3783
|
+
return notesElement.innerHTML;
|
3784
|
+
}
|
3785
|
+
|
3786
|
+
return null;
|
3787
|
+
|
3788
|
+
}
|
3789
|
+
|
3064
3790
|
/**
|
3065
3791
|
* Retrieves the current state of the presentation as
|
3066
3792
|
* an object. This state can then be restored at any
|
3067
3793
|
* time.
|
3794
|
+
*
|
3795
|
+
* @return {{indexh: number, indexv: number, indexf: number, paused: boolean, overview: boolean}}
|
3068
3796
|
*/
|
3069
3797
|
function getState() {
|
3070
3798
|
|
@@ -3083,7 +3811,8 @@
|
|
3083
3811
|
/**
|
3084
3812
|
* Restores the presentation to the given state.
|
3085
3813
|
*
|
3086
|
-
* @param {
|
3814
|
+
* @param {object} state As generated by getState()
|
3815
|
+
* @see {@link getState} generates the parameter `state`
|
3087
3816
|
*/
|
3088
3817
|
function setState( state ) {
|
3089
3818
|
|
@@ -3117,6 +3846,9 @@
|
|
3117
3846
|
* attribute to each node if such an attribute is not already present,
|
3118
3847
|
* and sets that attribute to an integer value which is the position of
|
3119
3848
|
* the fragment within the fragments list.
|
3849
|
+
*
|
3850
|
+
* @param {object[]|*} fragments
|
3851
|
+
* @return {object[]} sorted Sorted array of fragments
|
3120
3852
|
*/
|
3121
3853
|
function sortFragments( fragments ) {
|
3122
3854
|
|
@@ -3168,12 +3900,12 @@
|
|
3168
3900
|
/**
|
3169
3901
|
* Navigate to the specified slide fragment.
|
3170
3902
|
*
|
3171
|
-
* @param {
|
3903
|
+
* @param {?number} index The index of the fragment that
|
3172
3904
|
* should be shown, -1 means all are invisible
|
3173
|
-
* @param {
|
3905
|
+
* @param {number} offset Integer offset to apply to the
|
3174
3906
|
* fragment index
|
3175
3907
|
*
|
3176
|
-
* @return {
|
3908
|
+
* @return {boolean} true if a change was made in any
|
3177
3909
|
* fragments visibility as part of this call
|
3178
3910
|
*/
|
3179
3911
|
function navigateFragment( index, offset ) {
|
@@ -3216,10 +3948,11 @@
|
|
3216
3948
|
element.classList.remove( 'current-fragment' );
|
3217
3949
|
|
3218
3950
|
// Announce the fragments one by one to the Screen Reader
|
3219
|
-
dom.statusDiv.textContent = element
|
3951
|
+
dom.statusDiv.textContent = getStatusText( element );
|
3220
3952
|
|
3221
3953
|
if( i === index ) {
|
3222
3954
|
element.classList.add( 'current-fragment' );
|
3955
|
+
startEmbeddedContent( element );
|
3223
3956
|
}
|
3224
3957
|
}
|
3225
3958
|
// Hidden fragments
|
@@ -3229,7 +3962,6 @@
|
|
3229
3962
|
element.classList.remove( 'current-fragment' );
|
3230
3963
|
}
|
3231
3964
|
|
3232
|
-
|
3233
3965
|
} );
|
3234
3966
|
|
3235
3967
|
if( fragmentsHidden.length ) {
|
@@ -3256,7 +3988,7 @@
|
|
3256
3988
|
/**
|
3257
3989
|
* Navigate to the next slide fragment.
|
3258
3990
|
*
|
3259
|
-
* @return {
|
3991
|
+
* @return {boolean} true if there was a next fragment,
|
3260
3992
|
* false otherwise
|
3261
3993
|
*/
|
3262
3994
|
function nextFragment() {
|
@@ -3268,7 +4000,7 @@
|
|
3268
4000
|
/**
|
3269
4001
|
* Navigate to the previous slide fragment.
|
3270
4002
|
*
|
3271
|
-
* @return {
|
4003
|
+
* @return {boolean} true if there was a previous fragment,
|
3272
4004
|
* false otherwise
|
3273
4005
|
*/
|
3274
4006
|
function previousFragment() {
|
@@ -3286,9 +4018,13 @@
|
|
3286
4018
|
|
3287
4019
|
if( currentSlide ) {
|
3288
4020
|
|
3289
|
-
var
|
4021
|
+
var fragment = currentSlide.querySelector( '.current-fragment' );
|
4022
|
+
|
4023
|
+
// When the slide first appears there is no "current" fragment so
|
4024
|
+
// we look for a data-autoslide timing on the first fragment
|
4025
|
+
if( !fragment ) fragment = currentSlide.querySelector( '.fragment' );
|
3290
4026
|
|
3291
|
-
var fragmentAutoSlide =
|
4027
|
+
var fragmentAutoSlide = fragment ? fragment.getAttribute( 'data-autoslide' ) : null;
|
3292
4028
|
var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
|
3293
4029
|
var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
|
3294
4030
|
|
@@ -3312,14 +4048,18 @@
|
|
3312
4048
|
|
3313
4049
|
// If there are media elements with data-autoplay,
|
3314
4050
|
// automatically set the autoSlide duration to the
|
3315
|
-
// length of that media
|
3316
|
-
|
3317
|
-
|
3318
|
-
|
3319
|
-
|
4051
|
+
// length of that media. Not applicable if the slide
|
4052
|
+
// is divided up into fragments.
|
4053
|
+
// playbackRate is accounted for in the duration.
|
4054
|
+
if( currentSlide.querySelectorAll( '.fragment' ).length === 0 ) {
|
4055
|
+
toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
|
4056
|
+
if( el.hasAttribute( 'data-autoplay' ) ) {
|
4057
|
+
if( autoSlide && (el.duration * 1000 / el.playbackRate ) > autoSlide ) {
|
4058
|
+
autoSlide = ( el.duration * 1000 / el.playbackRate ) + 1000;
|
4059
|
+
}
|
3320
4060
|
}
|
3321
|
-
}
|
3322
|
-
}
|
4061
|
+
} );
|
4062
|
+
}
|
3323
4063
|
|
3324
4064
|
// Cue the next auto-slide if:
|
3325
4065
|
// - There is an autoSlide value
|
@@ -3328,7 +4068,10 @@
|
|
3328
4068
|
// - The overview isn't active
|
3329
4069
|
// - The presentation isn't over
|
3330
4070
|
if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || availableFragments().next || config.loop === true ) ) {
|
3331
|
-
autoSlideTimeout = setTimeout(
|
4071
|
+
autoSlideTimeout = setTimeout( function() {
|
4072
|
+
typeof config.autoSlideMethod === 'function' ? config.autoSlideMethod() : navigateNext();
|
4073
|
+
cueAutoSlide();
|
4074
|
+
}, autoSlide );
|
3332
4075
|
autoSlideStartTime = Date.now();
|
3333
4076
|
}
|
3334
4077
|
|
@@ -3474,9 +4217,20 @@
|
|
3474
4217
|
}
|
3475
4218
|
}
|
3476
4219
|
|
3477
|
-
|
3478
|
-
|
3479
|
-
|
4220
|
+
}
|
4221
|
+
|
4222
|
+
/**
|
4223
|
+
* Checks if the target element prevents the triggering of
|
4224
|
+
* swipe navigation.
|
4225
|
+
*/
|
4226
|
+
function isSwipePrevented( target ) {
|
4227
|
+
|
4228
|
+
while( target && typeof target.hasAttribute === 'function' ) {
|
4229
|
+
if( target.hasAttribute( 'data-prevent-swipe' ) ) return true;
|
4230
|
+
target = target.parentNode;
|
4231
|
+
}
|
4232
|
+
|
4233
|
+
return false;
|
3480
4234
|
|
3481
4235
|
}
|
3482
4236
|
|
@@ -3488,6 +4242,8 @@
|
|
3488
4242
|
/**
|
3489
4243
|
* Called by all event handlers that are based on user
|
3490
4244
|
* input.
|
4245
|
+
*
|
4246
|
+
* @param {object} [event]
|
3491
4247
|
*/
|
3492
4248
|
function onUserInput( event ) {
|
3493
4249
|
|
@@ -3499,23 +4255,22 @@
|
|
3499
4255
|
|
3500
4256
|
/**
|
3501
4257
|
* Handler for the document level 'keypress' event.
|
4258
|
+
*
|
4259
|
+
* @param {object} event
|
3502
4260
|
*/
|
3503
4261
|
function onDocumentKeyPress( event ) {
|
3504
4262
|
|
3505
4263
|
// Check if the pressed key is question mark
|
3506
4264
|
if( event.shiftKey && event.charCode === 63 ) {
|
3507
|
-
|
3508
|
-
closeOverlay();
|
3509
|
-
}
|
3510
|
-
else {
|
3511
|
-
showHelp( true );
|
3512
|
-
}
|
4265
|
+
toggleHelp();
|
3513
4266
|
}
|
3514
4267
|
|
3515
4268
|
}
|
3516
4269
|
|
3517
4270
|
/**
|
3518
4271
|
* Handler for the document level 'keydown' event.
|
4272
|
+
*
|
4273
|
+
* @param {object} event
|
3519
4274
|
*/
|
3520
4275
|
function onDocumentKeyDown( event ) {
|
3521
4276
|
|
@@ -3534,13 +4289,26 @@
|
|
3534
4289
|
// the keyboard
|
3535
4290
|
var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';
|
3536
4291
|
var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
|
4292
|
+
var activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className);
|
3537
4293
|
|
3538
4294
|
// Disregard the event if there's a focused element or a
|
3539
4295
|
// keyboard modifier key is present
|
3540
|
-
if( activeElementIsCE || activeElementIsInput || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
|
4296
|
+
if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
|
3541
4297
|
|
3542
|
-
// While paused only allow
|
3543
|
-
|
4298
|
+
// While paused only allow resume keyboard events; 'b', 'v', '.'
|
4299
|
+
var resumeKeyCodes = [66,86,190,191];
|
4300
|
+
var key;
|
4301
|
+
|
4302
|
+
// Custom key bindings for togglePause should be able to resume
|
4303
|
+
if( typeof config.keyboard === 'object' ) {
|
4304
|
+
for( key in config.keyboard ) {
|
4305
|
+
if( config.keyboard[key] === 'togglePause' ) {
|
4306
|
+
resumeKeyCodes.push( parseInt( key, 10 ) );
|
4307
|
+
}
|
4308
|
+
}
|
4309
|
+
}
|
4310
|
+
|
4311
|
+
if( isPaused() && resumeKeyCodes.indexOf( event.keyCode ) === -1 ) {
|
3544
4312
|
return false;
|
3545
4313
|
}
|
3546
4314
|
|
@@ -3549,7 +4317,7 @@
|
|
3549
4317
|
// 1. User defined key bindings
|
3550
4318
|
if( typeof config.keyboard === 'object' ) {
|
3551
4319
|
|
3552
|
-
for(
|
4320
|
+
for( key in config.keyboard ) {
|
3553
4321
|
|
3554
4322
|
// Check if this binding matches the pressed key
|
3555
4323
|
if( parseInt( key, 10 ) === event.keyCode ) {
|
@@ -3600,8 +4368,8 @@
|
|
3600
4368
|
case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
|
3601
4369
|
// return
|
3602
4370
|
case 13: isOverview() ? deactivateOverview() : triggered = false; break;
|
3603
|
-
// two-spot, semicolon, b, period, Logitech presenter tools "black screen" button
|
3604
|
-
case 58: case 59: case 66: case 190: case 191: togglePause(); break;
|
4371
|
+
// two-spot, semicolon, b, v, period, Logitech presenter tools "black screen" button
|
4372
|
+
case 58: case 59: case 66: case 86: case 190: case 191: togglePause(); break;
|
3605
4373
|
// f
|
3606
4374
|
case 70: enterFullscreen(); break;
|
3607
4375
|
// a
|
@@ -3638,9 +4406,13 @@
|
|
3638
4406
|
/**
|
3639
4407
|
* Handler for the 'touchstart' event, enables support for
|
3640
4408
|
* swipe and pinch gestures.
|
4409
|
+
*
|
4410
|
+
* @param {object} event
|
3641
4411
|
*/
|
3642
4412
|
function onTouchStart( event ) {
|
3643
4413
|
|
4414
|
+
if( isSwipePrevented( event.target ) ) return true;
|
4415
|
+
|
3644
4416
|
touch.startX = event.touches[0].clientX;
|
3645
4417
|
touch.startY = event.touches[0].clientY;
|
3646
4418
|
touch.startCount = event.touches.length;
|
@@ -3661,9 +4433,13 @@
|
|
3661
4433
|
|
3662
4434
|
/**
|
3663
4435
|
* Handler for the 'touchmove' event.
|
4436
|
+
*
|
4437
|
+
* @param {object} event
|
3664
4438
|
*/
|
3665
4439
|
function onTouchMove( event ) {
|
3666
4440
|
|
4441
|
+
if( isSwipePrevented( event.target ) ) return true;
|
4442
|
+
|
3667
4443
|
// Each touch should only trigger one action
|
3668
4444
|
if( !touch.captured ) {
|
3669
4445
|
onUserInput( event );
|
@@ -3740,7 +4516,7 @@
|
|
3740
4516
|
}
|
3741
4517
|
// There's a bug with swiping on some Android devices unless
|
3742
4518
|
// the default action is always prevented
|
3743
|
-
else if(
|
4519
|
+
else if( UA.match( /android/gi ) ) {
|
3744
4520
|
event.preventDefault();
|
3745
4521
|
}
|
3746
4522
|
|
@@ -3748,6 +4524,8 @@
|
|
3748
4524
|
|
3749
4525
|
/**
|
3750
4526
|
* Handler for the 'touchend' event.
|
4527
|
+
*
|
4528
|
+
* @param {object} event
|
3751
4529
|
*/
|
3752
4530
|
function onTouchEnd( event ) {
|
3753
4531
|
|
@@ -3757,6 +4535,8 @@
|
|
3757
4535
|
|
3758
4536
|
/**
|
3759
4537
|
* Convert pointer down to touch start.
|
4538
|
+
*
|
4539
|
+
* @param {object} event
|
3760
4540
|
*/
|
3761
4541
|
function onPointerDown( event ) {
|
3762
4542
|
|
@@ -3769,6 +4549,8 @@
|
|
3769
4549
|
|
3770
4550
|
/**
|
3771
4551
|
* Convert pointer move to touch move.
|
4552
|
+
*
|
4553
|
+
* @param {object} event
|
3772
4554
|
*/
|
3773
4555
|
function onPointerMove( event ) {
|
3774
4556
|
|
@@ -3781,6 +4563,8 @@
|
|
3781
4563
|
|
3782
4564
|
/**
|
3783
4565
|
* Convert pointer up to touch end.
|
4566
|
+
*
|
4567
|
+
* @param {object} event
|
3784
4568
|
*/
|
3785
4569
|
function onPointerUp( event ) {
|
3786
4570
|
|
@@ -3794,6 +4578,8 @@
|
|
3794
4578
|
/**
|
3795
4579
|
* Handles mouse wheel scrolling, throttled to avoid skipping
|
3796
4580
|
* multiple slides.
|
4581
|
+
*
|
4582
|
+
* @param {object} event
|
3797
4583
|
*/
|
3798
4584
|
function onDocumentMouseScroll( event ) {
|
3799
4585
|
|
@@ -3805,7 +4591,7 @@
|
|
3805
4591
|
if( delta > 0 ) {
|
3806
4592
|
navigateNext();
|
3807
4593
|
}
|
3808
|
-
else {
|
4594
|
+
else if( delta < 0 ) {
|
3809
4595
|
navigatePrev();
|
3810
4596
|
}
|
3811
4597
|
|
@@ -3818,6 +4604,8 @@
|
|
3818
4604
|
* closest approximate horizontal slide using this equation:
|
3819
4605
|
*
|
3820
4606
|
* ( clickX / presentationWidth ) * numberOfSlides
|
4607
|
+
*
|
4608
|
+
* @param {object} event
|
3821
4609
|
*/
|
3822
4610
|
function onProgressClicked( event ) {
|
3823
4611
|
|
@@ -3828,6 +4616,10 @@
|
|
3828
4616
|
var slidesTotal = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
|
3829
4617
|
var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
|
3830
4618
|
|
4619
|
+
if( config.rtl ) {
|
4620
|
+
slideIndex = slidesTotal - slideIndex;
|
4621
|
+
}
|
4622
|
+
|
3831
4623
|
slide( slideIndex );
|
3832
4624
|
|
3833
4625
|
}
|
@@ -3844,6 +4636,8 @@
|
|
3844
4636
|
|
3845
4637
|
/**
|
3846
4638
|
* Handler for the window level 'hashchange' event.
|
4639
|
+
*
|
4640
|
+
* @param {object} [event]
|
3847
4641
|
*/
|
3848
4642
|
function onWindowHashChange( event ) {
|
3849
4643
|
|
@@ -3853,6 +4647,8 @@
|
|
3853
4647
|
|
3854
4648
|
/**
|
3855
4649
|
* Handler for the window level 'resize' event.
|
4650
|
+
*
|
4651
|
+
* @param {object} [event]
|
3856
4652
|
*/
|
3857
4653
|
function onWindowResize( event ) {
|
3858
4654
|
|
@@ -3862,6 +4658,8 @@
|
|
3862
4658
|
|
3863
4659
|
/**
|
3864
4660
|
* Handle for the window level 'visibilitychange' event.
|
4661
|
+
*
|
4662
|
+
* @param {object} [event]
|
3865
4663
|
*/
|
3866
4664
|
function onPageVisibilityChange( event ) {
|
3867
4665
|
|
@@ -3872,7 +4670,10 @@
|
|
3872
4670
|
// If, after clicking a link or similar and we're coming back,
|
3873
4671
|
// focus the document.body to ensure we can use keyboard shortcuts
|
3874
4672
|
if( isHidden === false && document.activeElement !== document.body ) {
|
3875
|
-
|
4673
|
+
// Not all elements support .blur() - SVGs among them.
|
4674
|
+
if( typeof document.activeElement.blur === 'function' ) {
|
4675
|
+
document.activeElement.blur();
|
4676
|
+
}
|
3876
4677
|
document.body.focus();
|
3877
4678
|
}
|
3878
4679
|
|
@@ -3880,6 +4681,8 @@
|
|
3880
4681
|
|
3881
4682
|
/**
|
3882
4683
|
* Invoked when a slide is and we're in the overview.
|
4684
|
+
*
|
4685
|
+
* @param {object} event
|
3883
4686
|
*/
|
3884
4687
|
function onOverviewSlideClicked( event ) {
|
3885
4688
|
|
@@ -3913,6 +4716,8 @@
|
|
3913
4716
|
/**
|
3914
4717
|
* Handles clicks on links that are set to preview in the
|
3915
4718
|
* iframe overlay.
|
4719
|
+
*
|
4720
|
+
* @param {object} event
|
3916
4721
|
*/
|
3917
4722
|
function onPreviewLinkClicked( event ) {
|
3918
4723
|
|
@@ -3928,6 +4733,8 @@
|
|
3928
4733
|
|
3929
4734
|
/**
|
3930
4735
|
* Handles click on the auto-sliding controls element.
|
4736
|
+
*
|
4737
|
+
* @param {object} [event]
|
3931
4738
|
*/
|
3932
4739
|
function onAutoSlidePlayerClick( event ) {
|
3933
4740
|
|
@@ -3959,15 +4766,16 @@
|
|
3959
4766
|
*
|
3960
4767
|
* @param {HTMLElement} container The component will append
|
3961
4768
|
* itself to this
|
3962
|
-
* @param {
|
4769
|
+
* @param {function} progressCheck A method which will be
|
3963
4770
|
* called frequently to get the current progress on a range
|
3964
4771
|
* of 0-1
|
3965
4772
|
*/
|
3966
4773
|
function Playback( container, progressCheck ) {
|
3967
4774
|
|
3968
4775
|
// Cosmetics
|
3969
|
-
this.diameter =
|
3970
|
-
this.
|
4776
|
+
this.diameter = 100;
|
4777
|
+
this.diameter2 = this.diameter/2;
|
4778
|
+
this.thickness = 6;
|
3971
4779
|
|
3972
4780
|
// Flags if we are currently playing
|
3973
4781
|
this.playing = false;
|
@@ -3985,6 +4793,8 @@
|
|
3985
4793
|
this.canvas.className = 'playback';
|
3986
4794
|
this.canvas.width = this.diameter;
|
3987
4795
|
this.canvas.height = this.diameter;
|
4796
|
+
this.canvas.style.width = this.diameter2 + 'px';
|
4797
|
+
this.canvas.style.height = this.diameter2 + 'px';
|
3988
4798
|
this.context = this.canvas.getContext( '2d' );
|
3989
4799
|
|
3990
4800
|
this.container.appendChild( this.canvas );
|
@@ -3993,6 +4803,9 @@
|
|
3993
4803
|
|
3994
4804
|
}
|
3995
4805
|
|
4806
|
+
/**
|
4807
|
+
* @param value
|
4808
|
+
*/
|
3996
4809
|
Playback.prototype.setPlaying = function( value ) {
|
3997
4810
|
|
3998
4811
|
var wasPlaying = this.playing;
|
@@ -4035,10 +4848,10 @@
|
|
4035
4848
|
Playback.prototype.render = function() {
|
4036
4849
|
|
4037
4850
|
var progress = this.playing ? this.progress : 0,
|
4038
|
-
radius = ( this.
|
4039
|
-
x = this.
|
4040
|
-
y = this.
|
4041
|
-
iconSize =
|
4851
|
+
radius = ( this.diameter2 ) - this.thickness,
|
4852
|
+
x = this.diameter2,
|
4853
|
+
y = this.diameter2,
|
4854
|
+
iconSize = 28;
|
4042
4855
|
|
4043
4856
|
// Ease towards 1
|
4044
4857
|
this.progressOffset += ( 1 - this.progressOffset ) * 0.1;
|
@@ -4051,7 +4864,7 @@
|
|
4051
4864
|
|
4052
4865
|
// Solid background color
|
4053
4866
|
this.context.beginPath();
|
4054
|
-
this.context.arc( x, y, radius +
|
4867
|
+
this.context.arc( x, y, radius + 4, 0, Math.PI * 2, false );
|
4055
4868
|
this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )';
|
4056
4869
|
this.context.fill();
|
4057
4870
|
|
@@ -4076,14 +4889,14 @@
|
|
4076
4889
|
// Draw play/pause icons
|
4077
4890
|
if( this.playing ) {
|
4078
4891
|
this.context.fillStyle = '#fff';
|
4079
|
-
this.context.fillRect( 0, 0, iconSize / 2 -
|
4080
|
-
this.context.fillRect( iconSize / 2 +
|
4892
|
+
this.context.fillRect( 0, 0, iconSize / 2 - 4, iconSize );
|
4893
|
+
this.context.fillRect( iconSize / 2 + 4, 0, iconSize / 2 - 4, iconSize );
|
4081
4894
|
}
|
4082
4895
|
else {
|
4083
4896
|
this.context.beginPath();
|
4084
|
-
this.context.translate(
|
4897
|
+
this.context.translate( 4, 0 );
|
4085
4898
|
this.context.moveTo( 0, 0 );
|
4086
|
-
this.context.lineTo( iconSize -
|
4899
|
+
this.context.lineTo( iconSize - 4, iconSize / 2 );
|
4087
4900
|
this.context.lineTo( 0, iconSize );
|
4088
4901
|
this.context.fillStyle = '#fff';
|
4089
4902
|
this.context.fill();
|
@@ -4118,6 +4931,8 @@
|
|
4118
4931
|
|
4119
4932
|
|
4120
4933
|
Reveal = {
|
4934
|
+
VERSION: VERSION,
|
4935
|
+
|
4121
4936
|
initialize: initialize,
|
4122
4937
|
configure: configure,
|
4123
4938
|
sync: sync,
|
@@ -4148,12 +4963,18 @@
|
|
4148
4963
|
// Forces an update in slide layout
|
4149
4964
|
layout: layout,
|
4150
4965
|
|
4966
|
+
// Randomizes the order of slides
|
4967
|
+
shuffle: shuffle,
|
4968
|
+
|
4151
4969
|
// Returns an object with the available routes as booleans (left/right/top/bottom)
|
4152
4970
|
availableRoutes: availableRoutes,
|
4153
4971
|
|
4154
4972
|
// Returns an object with the available fragments as booleans (prev/next)
|
4155
4973
|
availableFragments: availableFragments,
|
4156
4974
|
|
4975
|
+
// Toggles a help overlay with keyboard shortcuts
|
4976
|
+
toggleHelp: toggleHelp,
|
4977
|
+
|
4157
4978
|
// Toggles the overview mode on/off
|
4158
4979
|
toggleOverview: toggleOverview,
|
4159
4980
|
|
@@ -4176,12 +4997,19 @@
|
|
4176
4997
|
getState: getState,
|
4177
4998
|
setState: setState,
|
4178
4999
|
|
5000
|
+
// Presentation progress
|
5001
|
+
getSlidePastCount: getSlidePastCount,
|
5002
|
+
|
4179
5003
|
// Presentation progress on range of 0-1
|
4180
5004
|
getProgress: getProgress,
|
4181
5005
|
|
4182
5006
|
// Returns the indices of the current, or specified, slide
|
4183
5007
|
getIndices: getIndices,
|
4184
5008
|
|
5009
|
+
// Returns an Array of all slides
|
5010
|
+
getSlides: getSlides,
|
5011
|
+
|
5012
|
+
// Returns the total number of slides
|
4185
5013
|
getTotalSlides: getTotalSlides,
|
4186
5014
|
|
4187
5015
|
// Returns the slide element at the specified index
|
@@ -4190,6 +5018,9 @@
|
|
4190
5018
|
// Returns the slide background element at the specified index
|
4191
5019
|
getSlideBackground: getSlideBackground,
|
4192
5020
|
|
5021
|
+
// Returns the speaker notes string for a slide, or null
|
5022
|
+
getSlideNotes: getSlideNotes,
|
5023
|
+
|
4193
5024
|
// Returns the previous slide element, may be null
|
4194
5025
|
getPreviousSlide: function() {
|
4195
5026
|
return previousSlide;
|
@@ -4268,6 +5099,11 @@
|
|
4268
5099
|
// Programatically triggers a keyboard event
|
4269
5100
|
triggerKey: function( keyCode ) {
|
4270
5101
|
onDocumentKeyDown( { keyCode: keyCode } );
|
5102
|
+
},
|
5103
|
+
|
5104
|
+
// Registers a new shortcut to include in the help overlay
|
5105
|
+
registerKeyboardShortcut: function( key, value ) {
|
5106
|
+
keyboardShortcuts[key] = value;
|
4271
5107
|
}
|
4272
5108
|
};
|
4273
5109
|
|