reveal-ck 3.6.0 → 3.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|