reveal.rb 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.ruby-version +1 -1
- data/.travis.yml +2 -1
- data/Dockerfile +4 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +7 -1
- data/README.md +1 -1
- data/lib/reveal/command.rb +9 -3
- data/lib/reveal/templates/revealjs/css/print/paper.css +4 -3
- data/lib/reveal/templates/revealjs/css/print/pdf.css +59 -38
- data/lib/reveal/templates/revealjs/css/reveal.css +654 -274
- data/lib/reveal/templates/revealjs/css/theme/beige.css +65 -68
- data/lib/reveal/templates/revealjs/css/theme/black.css +58 -61
- data/lib/reveal/templates/revealjs/css/theme/blood.css +64 -62
- data/lib/reveal/templates/revealjs/css/theme/league.css +59 -62
- data/lib/reveal/templates/revealjs/css/theme/moon.css +59 -62
- data/lib/reveal/templates/revealjs/css/theme/night.css +58 -61
- data/lib/reveal/templates/revealjs/css/theme/serif.css +56 -59
- data/lib/reveal/templates/revealjs/css/theme/simple.css +60 -60
- data/lib/reveal/templates/revealjs/css/theme/sky.css +59 -62
- data/lib/reveal/templates/revealjs/css/theme/solarized.css +59 -62
- data/lib/reveal/templates/revealjs/css/theme/white.css +59 -62
- data/lib/reveal/templates/revealjs/index.html +14 -376
- data/lib/reveal/templates/revealjs/js/reveal.js +1073 -342
- data/lib/reveal/templates/revealjs/lib/css/zenburn.css +41 -78
- data/lib/reveal/templates/revealjs/lib/js/head.min.js +9 -8
- data/lib/reveal/templates/revealjs/plugin/highlight/highlight.js +51 -4
- data/lib/reveal/templates/revealjs/plugin/markdown/markdown.js +38 -19
- data/lib/reveal/templates/revealjs/plugin/markdown/marked.js +1 -1
- data/lib/reveal/templates/revealjs/plugin/math/math.js +5 -2
- data/lib/reveal/templates/revealjs/plugin/multiplex/client.js +1 -1
- data/lib/reveal/templates/revealjs/plugin/multiplex/index.js +24 -16
- data/lib/reveal/templates/revealjs/plugin/multiplex/master.js +26 -43
- data/lib/reveal/templates/revealjs/plugin/multiplex/package.json +19 -0
- data/lib/reveal/templates/revealjs/plugin/notes/notes.html +385 -32
- data/lib/reveal/templates/revealjs/plugin/notes/notes.js +39 -6
- data/lib/reveal/templates/revealjs/plugin/notes-server/client.js +6 -1
- data/lib/reveal/templates/revealjs/plugin/notes-server/index.js +17 -14
- data/lib/reveal/templates/revealjs/plugin/notes-server/notes.html +215 -26
- data/lib/reveal/templates/revealjs/plugin/print-pdf/print-pdf.js +48 -27
- data/lib/reveal/templates/revealjs/plugin/search/search.js +41 -31
- data/lib/reveal/templates/revealjs/plugin/zoom-js/zoom.js +17 -23
- data/lib/reveal/templates/template.html +12 -41
- data/lib/reveal/version.rb +1 -1
- data/spec/lib/reveal/command_spec.rb +37 -0
- metadata +14 -16
- data/lib/reveal/templates/revealjs/lib/font/league-gothic/LICENSE +0 -2
- data/lib/reveal/templates/revealjs/lib/font/source-sans-pro/LICENSE +0 -45
- data/lib/reveal/templates/revealjs/plugin/leap/leap.js +0 -159
- data/lib/reveal/templates/revealjs/plugin/remotes/remotes.js +0 -39
@@ -1,9 +1,9 @@
|
|
1
1
|
/*!
|
2
2
|
* reveal.js
|
3
|
-
* http://
|
3
|
+
* http://revealjs.com
|
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,10 +25,14 @@
|
|
25
25
|
|
26
26
|
var Reveal;
|
27
27
|
|
28
|
+
// The reveal.js version
|
29
|
+
var VERSION = '3.6.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 = {
|
@@ -39,21 +43,35 @@
|
|
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
|
-
// Display
|
52
|
+
// Display presentation control arrows
|
49
53
|
controls: true,
|
50
54
|
|
55
|
+
// Help the user learn the controls by providing hints, for example by
|
56
|
+
// bouncing the down arrow when they first encounter a vertical slide
|
57
|
+
controlsTutorial: true,
|
58
|
+
|
59
|
+
// Determines where controls appear, "edges" or "bottom-right"
|
60
|
+
controlsLayout: 'bottom-right',
|
61
|
+
|
62
|
+
// Visibility rule for backwards navigation arrows; "faded", "hidden"
|
63
|
+
// or "visible"
|
64
|
+
controlsBackArrows: 'faded',
|
65
|
+
|
51
66
|
// Display a presentation progress bar
|
52
67
|
progress: true,
|
53
68
|
|
54
69
|
// Display the page number of the current slide
|
55
70
|
slideNumber: false,
|
56
71
|
|
72
|
+
// Determine which displays to show the slide number on
|
73
|
+
showSlideNumber: 'all',
|
74
|
+
|
57
75
|
// Push each slide change to the browser history
|
58
76
|
history: false,
|
59
77
|
|
@@ -78,6 +96,9 @@
|
|
78
96
|
// Change the presentation direction to be RTL
|
79
97
|
rtl: false,
|
80
98
|
|
99
|
+
// Randomizes the order of slides each time the presentation loads
|
100
|
+
shuffle: false,
|
101
|
+
|
81
102
|
// Turns fragments on and off globally
|
82
103
|
fragments: true,
|
83
104
|
|
@@ -85,21 +106,35 @@
|
|
85
106
|
// i.e. contained within a limited portion of the screen
|
86
107
|
embedded: false,
|
87
108
|
|
88
|
-
// Flags if we should show a help overlay when the
|
109
|
+
// Flags if we should show a help overlay when the question-mark
|
89
110
|
// key is pressed
|
90
111
|
help: true,
|
91
112
|
|
92
113
|
// Flags if it should be possible to pause the presentation (blackout)
|
93
114
|
pause: true,
|
94
115
|
|
95
|
-
//
|
96
|
-
|
97
|
-
|
116
|
+
// Flags if speaker notes should be visible to all viewers
|
117
|
+
showNotes: false,
|
118
|
+
|
119
|
+
// Global override for autolaying embedded media (video/audio/iframe)
|
120
|
+
// - null: Media will only autoplay if data-autoplay is present
|
121
|
+
// - true: All media will autoplay, regardless of individual setting
|
122
|
+
// - false: No media will autoplay, regardless of individual setting
|
123
|
+
autoPlayMedia: null,
|
124
|
+
|
125
|
+
// Controls automatic progression to the next slide
|
126
|
+
// - 0: Auto-sliding only happens if the data-autoslide HTML attribute
|
127
|
+
// is present on the current slide or fragment
|
128
|
+
// - 1+: All slides will progress automatically at the given interval
|
129
|
+
// - false: No auto-sliding, even if data-autoslide is present
|
98
130
|
autoSlide: 0,
|
99
131
|
|
100
132
|
// Stop auto-sliding after user input
|
101
133
|
autoSlideStoppable: true,
|
102
134
|
|
135
|
+
// Use this method for navigation when auto-sliding (defaults to navigateNext)
|
136
|
+
autoSlideMethod: null,
|
137
|
+
|
103
138
|
// Enable slide navigation via mouse wheel
|
104
139
|
mouseWheel: false,
|
105
140
|
|
@@ -118,7 +153,7 @@
|
|
118
153
|
// Dispatches all reveal.js events to the parent window through postMessage
|
119
154
|
postMessageEvents: false,
|
120
155
|
|
121
|
-
// Focuses body when page changes
|
156
|
+
// Focuses body when page changes visibility to ensure keyboard shortcuts work
|
122
157
|
focusBodyOnPageVisibilityChange: true,
|
123
158
|
|
124
159
|
// Transition style
|
@@ -140,20 +175,41 @@
|
|
140
175
|
parallaxBackgroundHorizontal: null,
|
141
176
|
parallaxBackgroundVertical: null,
|
142
177
|
|
178
|
+
// The maximum number of pages a single slide can expand onto when printing
|
179
|
+
// to PDF, unlimited by default
|
180
|
+
pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY,
|
181
|
+
|
182
|
+
// Offset used to reduce the height of content within exported PDF pages.
|
183
|
+
// This exists to account for environment differences based on how you
|
184
|
+
// print to PDF. CLI printing options, like phantomjs and wkpdf, can end
|
185
|
+
// on precisely the total height of the document whereas in-browser
|
186
|
+
// printing has to end one pixel before.
|
187
|
+
pdfPageHeightOffset: -1,
|
188
|
+
|
143
189
|
// Number of slides away from the current that are visible
|
144
190
|
viewDistance: 3,
|
145
191
|
|
192
|
+
// The display mode that will be used to show slides
|
193
|
+
display: 'block',
|
194
|
+
|
146
195
|
// Script dependencies to load
|
147
196
|
dependencies: []
|
148
197
|
|
149
198
|
},
|
150
199
|
|
200
|
+
// Flags if Reveal.initialize() has been called
|
201
|
+
initialized = false,
|
202
|
+
|
151
203
|
// Flags if reveal.js is loaded (has dispatched the 'ready' event)
|
152
204
|
loaded = false,
|
153
205
|
|
154
206
|
// Flags if the overview mode is currently active
|
155
207
|
overview = false,
|
156
208
|
|
209
|
+
// Holds the dimensions of our overview slides, including margins
|
210
|
+
overviewSlideWidth = null,
|
211
|
+
overviewSlideHeight = null,
|
212
|
+
|
157
213
|
// The horizontal and vertical index of the currently active slide
|
158
214
|
indexh,
|
159
215
|
indexv,
|
@@ -164,6 +220,10 @@
|
|
164
220
|
|
165
221
|
previousBackground,
|
166
222
|
|
223
|
+
// Remember which directions that the user has navigated towards
|
224
|
+
hasNavigatedRight = false,
|
225
|
+
hasNavigatedDown = false,
|
226
|
+
|
167
227
|
// Slides may hold a data-state attribute which we pick up and apply
|
168
228
|
// as a class to the body. This list contains the combined state of
|
169
229
|
// all current slides.
|
@@ -185,6 +245,9 @@
|
|
185
245
|
// Client is a mobile device, see #checkCapabilities()
|
186
246
|
isMobileDevice,
|
187
247
|
|
248
|
+
// Client is a desktop Chrome, see #checkCapabilities()
|
249
|
+
isChrome,
|
250
|
+
|
188
251
|
// Throttles mouse wheel navigation
|
189
252
|
lastMouseWheelStep = 0,
|
190
253
|
|
@@ -233,6 +296,11 @@
|
|
233
296
|
*/
|
234
297
|
function initialize( options ) {
|
235
298
|
|
299
|
+
// Make sure we only initialize once
|
300
|
+
if( initialized === true ) return;
|
301
|
+
|
302
|
+
initialized = true;
|
303
|
+
|
236
304
|
checkCapabilities();
|
237
305
|
|
238
306
|
if( !features.transforms2d && !features.transforms3d ) {
|
@@ -289,30 +357,37 @@
|
|
289
357
|
*/
|
290
358
|
function checkCapabilities() {
|
291
359
|
|
292
|
-
|
293
|
-
|
294
|
-
'msPerspective' in document.body.style ||
|
295
|
-
'OPerspective' in document.body.style ||
|
296
|
-
'perspective' in document.body.style;
|
360
|
+
isMobileDevice = /(iphone|ipod|ipad|android)/gi.test( UA );
|
361
|
+
isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA );
|
297
362
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
'
|
302
|
-
'
|
363
|
+
var testElement = document.createElement( 'div' );
|
364
|
+
|
365
|
+
features.transforms3d = 'WebkitPerspective' in testElement.style ||
|
366
|
+
'MozPerspective' in testElement.style ||
|
367
|
+
'msPerspective' in testElement.style ||
|
368
|
+
'OPerspective' in testElement.style ||
|
369
|
+
'perspective' in testElement.style;
|
370
|
+
|
371
|
+
features.transforms2d = 'WebkitTransform' in testElement.style ||
|
372
|
+
'MozTransform' in testElement.style ||
|
373
|
+
'msTransform' in testElement.style ||
|
374
|
+
'OTransform' in testElement.style ||
|
375
|
+
'transform' in testElement.style;
|
303
376
|
|
304
377
|
features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
|
305
378
|
features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';
|
306
379
|
|
307
380
|
features.canvas = !!document.createElement( 'canvas' ).getContext;
|
308
381
|
|
309
|
-
features.touch = !!( 'ontouchstart' in window );
|
310
|
-
|
311
382
|
// Transitions in the overview are disabled in desktop and
|
312
|
-
//
|
313
|
-
features.overviewTransitions = !/Version\/[\d\.]+.*Safari/.test(
|
383
|
+
// Safari due to lag
|
384
|
+
features.overviewTransitions = !/Version\/[\d\.]+.*Safari/.test( UA );
|
314
385
|
|
315
|
-
|
386
|
+
// Flags if we should use zoom instead of transform to scale
|
387
|
+
// up slides. Zoom produces crisper results but has a lot of
|
388
|
+
// xbrowser quirks so we only use it in whitelsited browsers.
|
389
|
+
features.zoom = 'zoom' in testElement.style && !isMobileDevice &&
|
390
|
+
( isChrome || /Version\/[\d\.]+.*Safari/.test( UA ) );
|
316
391
|
|
317
392
|
}
|
318
393
|
|
@@ -386,14 +461,16 @@
|
|
386
461
|
*/
|
387
462
|
function start() {
|
388
463
|
|
464
|
+
loaded = true;
|
465
|
+
|
389
466
|
// Make sure we've got all the DOM elements we need
|
390
467
|
setupDOM();
|
391
468
|
|
392
469
|
// Listen to messages posted to this window
|
393
470
|
setupPostMessage();
|
394
471
|
|
395
|
-
// Prevent
|
396
|
-
|
472
|
+
// Prevent the slides from being scrolled out of view
|
473
|
+
setupScrollPrevention();
|
397
474
|
|
398
475
|
// Resets all vertical slides so that only the first is visible
|
399
476
|
resetVerticalSlides();
|
@@ -413,7 +490,7 @@
|
|
413
490
|
// Enable transitions now that we're loaded
|
414
491
|
dom.slides.classList.remove( 'no-transition' );
|
415
492
|
|
416
|
-
|
493
|
+
dom.wrapper.classList.add( 'ready' );
|
417
494
|
|
418
495
|
dispatchEvent( 'ready', {
|
419
496
|
'indexh': indexh,
|
@@ -448,6 +525,20 @@
|
|
448
525
|
// Prevent transitions while we're loading
|
449
526
|
dom.slides.classList.add( 'no-transition' );
|
450
527
|
|
528
|
+
if( isMobileDevice ) {
|
529
|
+
dom.wrapper.classList.add( 'no-hover' );
|
530
|
+
}
|
531
|
+
else {
|
532
|
+
dom.wrapper.classList.remove( 'no-hover' );
|
533
|
+
}
|
534
|
+
|
535
|
+
if( /iphone/gi.test( UA ) ) {
|
536
|
+
dom.wrapper.classList.add( 'ua-iphone' );
|
537
|
+
}
|
538
|
+
else {
|
539
|
+
dom.wrapper.classList.remove( 'ua-iphone' );
|
540
|
+
}
|
541
|
+
|
451
542
|
// Background element
|
452
543
|
dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null );
|
453
544
|
|
@@ -456,22 +547,23 @@
|
|
456
547
|
dom.progressbar = dom.progress.querySelector( 'span' );
|
457
548
|
|
458
549
|
// Arrow controls
|
459
|
-
createSingletonNode( dom.wrapper, 'aside', 'controls',
|
460
|
-
'<
|
461
|
-
'<
|
462
|
-
'<
|
463
|
-
'<
|
550
|
+
dom.controls = createSingletonNode( dom.wrapper, 'aside', 'controls',
|
551
|
+
'<button class="navigate-left" aria-label="previous slide"><div class="controls-arrow"></div></button>' +
|
552
|
+
'<button class="navigate-right" aria-label="next slide"><div class="controls-arrow"></div></button>' +
|
553
|
+
'<button class="navigate-up" aria-label="above slide"><div class="controls-arrow"></div></button>' +
|
554
|
+
'<button class="navigate-down" aria-label="below slide"><div class="controls-arrow"></div></button>' );
|
464
555
|
|
465
556
|
// Slide number
|
466
557
|
dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
|
467
558
|
|
559
|
+
// Element containing notes that are visible to the audience
|
560
|
+
dom.speakerNotes = createSingletonNode( dom.wrapper, 'div', 'speaker-notes', null );
|
561
|
+
dom.speakerNotes.setAttribute( 'data-prevent-swipe', '' );
|
562
|
+
dom.speakerNotes.setAttribute( 'tabindex', '0' );
|
563
|
+
|
468
564
|
// Overlay graphic which is displayed during the paused mode
|
469
565
|
createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
|
470
566
|
|
471
|
-
// Cache references to elements
|
472
|
-
dom.controls = document.querySelector( '.reveal .controls' );
|
473
|
-
dom.theme = document.querySelector( '#theme' );
|
474
|
-
|
475
567
|
dom.wrapper.setAttribute( 'role', 'application' );
|
476
568
|
|
477
569
|
// There can be multiple instances of controls throughout the page
|
@@ -482,6 +574,10 @@
|
|
482
574
|
dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
|
483
575
|
dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
|
484
576
|
|
577
|
+
// The right and down arrows in the standard reveal.js controls
|
578
|
+
dom.controlsRightArrow = dom.controls.querySelector( '.navigate-right' );
|
579
|
+
dom.controlsDownArrow = dom.controls.querySelector( '.navigate-down' );
|
580
|
+
|
485
581
|
dom.statusDiv = createStatusDiv();
|
486
582
|
}
|
487
583
|
|
@@ -489,6 +585,8 @@
|
|
489
585
|
* Creates a hidden div with role aria-live to announce the
|
490
586
|
* current slide content. Hide the div off-screen to make it
|
491
587
|
* available only to Assistive Technologies.
|
588
|
+
*
|
589
|
+
* @return {HTMLElement}
|
492
590
|
*/
|
493
591
|
function createStatusDiv() {
|
494
592
|
|
@@ -498,7 +596,7 @@
|
|
498
596
|
statusDiv.style.position = 'absolute';
|
499
597
|
statusDiv.style.height = '1px';
|
500
598
|
statusDiv.style.width = '1px';
|
501
|
-
statusDiv.style.overflow ='hidden';
|
599
|
+
statusDiv.style.overflow = 'hidden';
|
502
600
|
statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';
|
503
601
|
statusDiv.setAttribute( 'id', 'aria-status-div' );
|
504
602
|
statusDiv.setAttribute( 'aria-live', 'polite' );
|
@@ -509,6 +607,38 @@
|
|
509
607
|
|
510
608
|
}
|
511
609
|
|
610
|
+
/**
|
611
|
+
* Converts the given HTML element into a string of text
|
612
|
+
* that can be announced to a screen reader. Hidden
|
613
|
+
* elements are excluded.
|
614
|
+
*/
|
615
|
+
function getStatusText( node ) {
|
616
|
+
|
617
|
+
var text = '';
|
618
|
+
|
619
|
+
// Text node
|
620
|
+
if( node.nodeType === 3 ) {
|
621
|
+
text += node.textContent;
|
622
|
+
}
|
623
|
+
// Element node
|
624
|
+
else if( node.nodeType === 1 ) {
|
625
|
+
|
626
|
+
var isAriaHidden = node.getAttribute( 'aria-hidden' );
|
627
|
+
var isDisplayHidden = window.getComputedStyle( node )['display'] === 'none';
|
628
|
+
if( isAriaHidden !== 'true' && !isDisplayHidden ) {
|
629
|
+
|
630
|
+
toArray( node.childNodes ).forEach( function( child ) {
|
631
|
+
text += getStatusText( child );
|
632
|
+
} );
|
633
|
+
|
634
|
+
}
|
635
|
+
|
636
|
+
}
|
637
|
+
|
638
|
+
return text;
|
639
|
+
|
640
|
+
}
|
641
|
+
|
512
642
|
/**
|
513
643
|
* Configures the presentation for printing to a static
|
514
644
|
* PDF.
|
@@ -519,14 +649,14 @@
|
|
519
649
|
|
520
650
|
// Dimensions of the PDF pages
|
521
651
|
var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
|
522
|
-
pageHeight = Math.floor( slideSize.height * ( 1 + config.margin
|
652
|
+
pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
|
523
653
|
|
524
654
|
// Dimensions of slides within the pages
|
525
655
|
var slideWidth = slideSize.width,
|
526
656
|
slideHeight = slideSize.height;
|
527
657
|
|
528
658
|
// Let the browser know what page size we want to print
|
529
|
-
injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin:
|
659
|
+
injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' );
|
530
660
|
|
531
661
|
// Limit the size of certain elements to the dimensions of the slide
|
532
662
|
injectStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
|
@@ -535,6 +665,22 @@
|
|
535
665
|
document.body.style.width = pageWidth + 'px';
|
536
666
|
document.body.style.height = pageHeight + 'px';
|
537
667
|
|
668
|
+
// Make sure stretch elements fit on slide
|
669
|
+
layoutSlideContents( slideWidth, slideHeight );
|
670
|
+
|
671
|
+
// Add each slide's index as attributes on itself, we need these
|
672
|
+
// indices to generate slide numbers below
|
673
|
+
toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {
|
674
|
+
hslide.setAttribute( 'data-index-h', h );
|
675
|
+
|
676
|
+
if( hslide.classList.contains( 'stack' ) ) {
|
677
|
+
toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {
|
678
|
+
vslide.setAttribute( 'data-index-h', h );
|
679
|
+
vslide.setAttribute( 'data-index-v', v );
|
680
|
+
} );
|
681
|
+
}
|
682
|
+
} );
|
683
|
+
|
538
684
|
// Slide and slide background layout
|
539
685
|
toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
|
540
686
|
|
@@ -545,27 +691,73 @@
|
|
545
691
|
var left = ( pageWidth - slideWidth ) / 2,
|
546
692
|
top = ( pageHeight - slideHeight ) / 2;
|
547
693
|
|
548
|
-
var contentHeight =
|
694
|
+
var contentHeight = slide.scrollHeight;
|
549
695
|
var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
|
550
696
|
|
697
|
+
// Adhere to configured pages per slide limit
|
698
|
+
numberOfPages = Math.min( numberOfPages, config.pdfMaxPagesPerSlide );
|
699
|
+
|
551
700
|
// Center slides vertically
|
552
701
|
if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
|
553
702
|
top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
|
554
703
|
}
|
555
704
|
|
705
|
+
// Wrap the slide in a page element and hide its overflow
|
706
|
+
// so that no page ever flows onto another
|
707
|
+
var page = document.createElement( 'div' );
|
708
|
+
page.className = 'pdf-page';
|
709
|
+
page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px';
|
710
|
+
slide.parentNode.insertBefore( page, slide );
|
711
|
+
page.appendChild( slide );
|
712
|
+
|
556
713
|
// Position the slide inside of the page
|
557
714
|
slide.style.left = left + 'px';
|
558
715
|
slide.style.top = top + 'px';
|
559
716
|
slide.style.width = slideWidth + 'px';
|
560
717
|
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
718
|
+
if( slide.slideBackgroundElement ) {
|
719
|
+
page.insertBefore( slide.slideBackgroundElement, slide );
|
720
|
+
}
|
721
|
+
|
722
|
+
// Inject notes if `showNotes` is enabled
|
723
|
+
if( config.showNotes ) {
|
724
|
+
|
725
|
+
// Are there notes for this slide?
|
726
|
+
var notes = getSlideNotes( slide );
|
727
|
+
if( notes ) {
|
728
|
+
|
729
|
+
var notesSpacing = 8;
|
730
|
+
var notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline';
|
731
|
+
var notesElement = document.createElement( 'div' );
|
732
|
+
notesElement.classList.add( 'speaker-notes' );
|
733
|
+
notesElement.classList.add( 'speaker-notes-pdf' );
|
734
|
+
notesElement.setAttribute( 'data-layout', notesLayout );
|
735
|
+
notesElement.innerHTML = notes;
|
736
|
+
|
737
|
+
if( notesLayout === 'separate-page' ) {
|
738
|
+
page.parentNode.insertBefore( notesElement, page.nextSibling );
|
739
|
+
}
|
740
|
+
else {
|
741
|
+
notesElement.style.left = notesSpacing + 'px';
|
742
|
+
notesElement.style.bottom = notesSpacing + 'px';
|
743
|
+
notesElement.style.width = ( pageWidth - notesSpacing*2 ) + 'px';
|
744
|
+
page.appendChild( notesElement );
|
745
|
+
}
|
746
|
+
|
747
|
+
}
|
748
|
+
|
749
|
+
}
|
750
|
+
|
751
|
+
// Inject slide numbers if `slideNumbers` are enabled
|
752
|
+
if( config.slideNumber && /all|print/i.test( config.showSlideNumber ) ) {
|
753
|
+
var slideNumberH = parseInt( slide.getAttribute( 'data-index-h' ), 10 ) + 1,
|
754
|
+
slideNumberV = parseInt( slide.getAttribute( 'data-index-v' ), 10 ) + 1;
|
755
|
+
|
756
|
+
var numberElement = document.createElement( 'div' );
|
757
|
+
numberElement.classList.add( 'slide-number' );
|
758
|
+
numberElement.classList.add( 'slide-number-pdf' );
|
759
|
+
numberElement.innerHTML = formatSlideNumber( slideNumberH, '.', slideNumberV );
|
760
|
+
page.appendChild( numberElement );
|
569
761
|
}
|
570
762
|
}
|
571
763
|
|
@@ -576,25 +768,28 @@
|
|
576
768
|
fragment.classList.add( 'visible' );
|
577
769
|
} );
|
578
770
|
|
771
|
+
// Notify subscribers that the PDF layout is good to go
|
772
|
+
dispatchEvent( 'pdf-ready' );
|
773
|
+
|
579
774
|
}
|
580
775
|
|
581
776
|
/**
|
582
|
-
* This is an unfortunate necessity.
|
583
|
-
*
|
777
|
+
* This is an unfortunate necessity. Some actions – such as
|
778
|
+
* an input field being focused in an iframe or using the
|
779
|
+
* keyboard to expand text selection beyond the bounds of
|
780
|
+
* a slide – can trigger our content to be pushed out of view.
|
584
781
|
* This scrolling can not be prevented by hiding overflow in
|
585
|
-
* CSS so we have to resort to repeatedly
|
586
|
-
*
|
782
|
+
* CSS (we already do) so we have to resort to repeatedly
|
783
|
+
* checking if the slides have been offset :(
|
587
784
|
*/
|
588
|
-
function
|
785
|
+
function setupScrollPrevention() {
|
589
786
|
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
}, 500 );
|
597
|
-
}
|
787
|
+
setInterval( function() {
|
788
|
+
if( dom.wrapper.scrollTop !== 0 || dom.wrapper.scrollLeft !== 0 ) {
|
789
|
+
dom.wrapper.scrollTop = 0;
|
790
|
+
dom.wrapper.scrollLeft = 0;
|
791
|
+
}
|
792
|
+
}, 1000 );
|
598
793
|
|
599
794
|
}
|
600
795
|
|
@@ -602,6 +797,13 @@
|
|
602
797
|
* Creates an HTML element and returns a reference to it.
|
603
798
|
* If the element already exists the existing instance will
|
604
799
|
* be returned.
|
800
|
+
*
|
801
|
+
* @param {HTMLElement} container
|
802
|
+
* @param {string} tagname
|
803
|
+
* @param {string} classname
|
804
|
+
* @param {string} innerHTML
|
805
|
+
*
|
806
|
+
* @return {HTMLElement}
|
605
807
|
*/
|
606
808
|
function createSingletonNode( container, tagname, classname, innerHTML ) {
|
607
809
|
|
@@ -619,7 +821,7 @@
|
|
619
821
|
|
620
822
|
// If no node was found, create it now
|
621
823
|
var node = document.createElement( tagname );
|
622
|
-
node.
|
824
|
+
node.className = classname;
|
623
825
|
if( typeof innerHTML === 'string' ) {
|
624
826
|
node.innerHTML = innerHTML;
|
625
827
|
}
|
@@ -645,24 +847,12 @@
|
|
645
847
|
// Iterate over all horizontal slides
|
646
848
|
toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
|
647
849
|
|
648
|
-
var backgroundStack;
|
649
|
-
|
650
|
-
if( printMode ) {
|
651
|
-
backgroundStack = createBackground( slideh, slideh );
|
652
|
-
}
|
653
|
-
else {
|
654
|
-
backgroundStack = createBackground( slideh, dom.background );
|
655
|
-
}
|
850
|
+
var backgroundStack = createBackground( slideh, dom.background );
|
656
851
|
|
657
852
|
// Iterate over all vertical slides
|
658
853
|
toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
|
659
854
|
|
660
|
-
|
661
|
-
createBackground( slidev, slidev );
|
662
|
-
}
|
663
|
-
else {
|
664
|
-
createBackground( slidev, backgroundStack );
|
665
|
-
}
|
855
|
+
createBackground( slidev, backgroundStack );
|
666
856
|
|
667
857
|
backgroundStack.classList.add( 'stack' );
|
668
858
|
|
@@ -700,6 +890,7 @@
|
|
700
890
|
* @param {HTMLElement} slide
|
701
891
|
* @param {HTMLElement} container The element that the background
|
702
892
|
* should be appended to
|
893
|
+
* @return {HTMLElement} New background div
|
703
894
|
*/
|
704
895
|
function createBackground( slide, container ) {
|
705
896
|
|
@@ -722,7 +913,7 @@
|
|
722
913
|
|
723
914
|
if( data.background ) {
|
724
915
|
// Auto-wrap image urls in url(...)
|
725
|
-
if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)
|
916
|
+
if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#]|$)/gi.test( data.background ) ) {
|
726
917
|
slide.setAttribute( 'data-background-image', data.background );
|
727
918
|
}
|
728
919
|
else {
|
@@ -747,6 +938,7 @@
|
|
747
938
|
|
748
939
|
// Additional and optional background properties
|
749
940
|
if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
|
941
|
+
if( data.backgroundSize ) element.setAttribute( 'data-background-size', data.backgroundSize );
|
750
942
|
if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
|
751
943
|
if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
|
752
944
|
if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
|
@@ -758,18 +950,20 @@
|
|
758
950
|
slide.classList.remove( 'has-dark-background' );
|
759
951
|
slide.classList.remove( 'has-light-background' );
|
760
952
|
|
953
|
+
slide.slideBackgroundElement = element;
|
954
|
+
|
761
955
|
// If this slide has a background color, add a class that
|
762
956
|
// signals if it is light or dark. If the slide has no background
|
763
957
|
// color, no class will be set
|
764
|
-
var
|
765
|
-
if(
|
766
|
-
var rgb = colorToRgb(
|
958
|
+
var computedBackgroundStyle = window.getComputedStyle( element );
|
959
|
+
if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) {
|
960
|
+
var rgb = colorToRgb( computedBackgroundStyle.backgroundColor );
|
767
961
|
|
768
962
|
// Ignore fully transparent backgrounds. Some browsers return
|
769
963
|
// rgba(0,0,0,0) when reading the computed background color of
|
770
964
|
// an element with no background
|
771
965
|
if( rgb && rgb.a !== 0 ) {
|
772
|
-
if( colorBrightness(
|
966
|
+
if( colorBrightness( computedBackgroundStyle.backgroundColor ) < 128 ) {
|
773
967
|
slide.classList.add( 'has-dark-background' );
|
774
968
|
}
|
775
969
|
else {
|
@@ -815,17 +1009,27 @@
|
|
815
1009
|
/**
|
816
1010
|
* Applies the configuration settings from the config
|
817
1011
|
* object. May be called multiple times.
|
1012
|
+
*
|
1013
|
+
* @param {object} options
|
818
1014
|
*/
|
819
1015
|
function configure( options ) {
|
820
1016
|
|
821
|
-
var
|
822
|
-
|
823
|
-
dom.wrapper.classList.remove( config.transition );
|
1017
|
+
var oldTransition = config.transition;
|
824
1018
|
|
825
1019
|
// New config options may be passed when this method
|
826
1020
|
// is invoked through the API after initialization
|
827
1021
|
if( typeof options === 'object' ) extend( config, options );
|
828
1022
|
|
1023
|
+
// Abort if reveal.js hasn't finished loading, config
|
1024
|
+
// changes will be applied automatically once loading
|
1025
|
+
// finishes
|
1026
|
+
if( loaded === false ) return;
|
1027
|
+
|
1028
|
+
var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
|
1029
|
+
|
1030
|
+
// Remove the previously configured transition class
|
1031
|
+
dom.wrapper.classList.remove( oldTransition );
|
1032
|
+
|
829
1033
|
// Force linear transition based on browser capabilities
|
830
1034
|
if( features.transforms3d === false ) config.transition = 'linear';
|
831
1035
|
|
@@ -837,6 +1041,13 @@
|
|
837
1041
|
dom.controls.style.display = config.controls ? 'block' : 'none';
|
838
1042
|
dom.progress.style.display = config.progress ? 'block' : 'none';
|
839
1043
|
|
1044
|
+
dom.controls.setAttribute( 'data-controls-layout', config.controlsLayout );
|
1045
|
+
dom.controls.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows );
|
1046
|
+
|
1047
|
+
if( config.shuffle ) {
|
1048
|
+
shuffle();
|
1049
|
+
}
|
1050
|
+
|
840
1051
|
if( config.rtl ) {
|
841
1052
|
dom.wrapper.classList.add( 'rtl' );
|
842
1053
|
}
|
@@ -856,6 +1067,10 @@
|
|
856
1067
|
resume();
|
857
1068
|
}
|
858
1069
|
|
1070
|
+
if( config.showNotes ) {
|
1071
|
+
dom.speakerNotes.setAttribute( 'data-layout', typeof config.showNotes === 'string' ? config.showNotes : 'inline' );
|
1072
|
+
}
|
1073
|
+
|
859
1074
|
if( config.mouseWheel ) {
|
860
1075
|
document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
|
861
1076
|
document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
|
@@ -876,10 +1091,11 @@
|
|
876
1091
|
// Iframe link previews
|
877
1092
|
if( config.previewLinks ) {
|
878
1093
|
enablePreviewLinks();
|
1094
|
+
disablePreviewLinks( '[data-preview-link=false]' );
|
879
1095
|
}
|
880
1096
|
else {
|
881
1097
|
disablePreviewLinks();
|
882
|
-
enablePreviewLinks( '[data-preview-link]' );
|
1098
|
+
enablePreviewLinks( '[data-preview-link]:not([data-preview-link=false])' );
|
883
1099
|
}
|
884
1100
|
|
885
1101
|
// Remove existing auto-slide controls
|
@@ -906,6 +1122,19 @@
|
|
906
1122
|
} );
|
907
1123
|
}
|
908
1124
|
|
1125
|
+
// Slide numbers
|
1126
|
+
var slideNumberDisplay = 'none';
|
1127
|
+
if( config.slideNumber && !isPrintingPDF() ) {
|
1128
|
+
if( config.showSlideNumber === 'all' ) {
|
1129
|
+
slideNumberDisplay = 'block';
|
1130
|
+
}
|
1131
|
+
else if( config.showSlideNumber === 'speaker' && isSpeakerNotes() ) {
|
1132
|
+
slideNumberDisplay = 'block';
|
1133
|
+
}
|
1134
|
+
}
|
1135
|
+
|
1136
|
+
dom.slideNumber.style.display = slideNumberDisplay;
|
1137
|
+
|
909
1138
|
sync();
|
910
1139
|
|
911
1140
|
}
|
@@ -973,7 +1202,7 @@
|
|
973
1202
|
|
974
1203
|
// Only support touch for Android, fixes double navigations in
|
975
1204
|
// stock browser
|
976
|
-
if(
|
1205
|
+
if( UA.match( /android/gi ) ) {
|
977
1206
|
pointerEvents = [ 'touchstart' ];
|
978
1207
|
}
|
979
1208
|
|
@@ -1035,6 +1264,9 @@
|
|
1035
1264
|
/**
|
1036
1265
|
* Extend object a with the properties of object b.
|
1037
1266
|
* If there's a conflict, object b takes precedence.
|
1267
|
+
*
|
1268
|
+
* @param {object} a
|
1269
|
+
* @param {object} b
|
1038
1270
|
*/
|
1039
1271
|
function extend( a, b ) {
|
1040
1272
|
|
@@ -1042,10 +1274,15 @@
|
|
1042
1274
|
a[ i ] = b[ i ];
|
1043
1275
|
}
|
1044
1276
|
|
1277
|
+
return a;
|
1278
|
+
|
1045
1279
|
}
|
1046
1280
|
|
1047
1281
|
/**
|
1048
1282
|
* Converts the target object to an array.
|
1283
|
+
*
|
1284
|
+
* @param {object} o
|
1285
|
+
* @return {object[]}
|
1049
1286
|
*/
|
1050
1287
|
function toArray( o ) {
|
1051
1288
|
|
@@ -1055,6 +1292,9 @@
|
|
1055
1292
|
|
1056
1293
|
/**
|
1057
1294
|
* Utility for deserializing a value.
|
1295
|
+
*
|
1296
|
+
* @param {*} value
|
1297
|
+
* @return {*}
|
1058
1298
|
*/
|
1059
1299
|
function deserialize( value ) {
|
1060
1300
|
|
@@ -1062,7 +1302,7 @@
|
|
1062
1302
|
if( value === 'null' ) return null;
|
1063
1303
|
else if( value === 'true' ) return true;
|
1064
1304
|
else if( value === 'false' ) return false;
|
1065
|
-
else if( value.match(
|
1305
|
+
else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value );
|
1066
1306
|
}
|
1067
1307
|
|
1068
1308
|
return value;
|
@@ -1073,8 +1313,10 @@
|
|
1073
1313
|
* Measures the distance in pixels between point a
|
1074
1314
|
* and point b.
|
1075
1315
|
*
|
1076
|
-
* @param {
|
1077
|
-
* @param {
|
1316
|
+
* @param {object} a point with x/y properties
|
1317
|
+
* @param {object} b point with x/y properties
|
1318
|
+
*
|
1319
|
+
* @return {number}
|
1078
1320
|
*/
|
1079
1321
|
function distanceBetween( a, b ) {
|
1080
1322
|
|
@@ -1087,6 +1329,9 @@
|
|
1087
1329
|
|
1088
1330
|
/**
|
1089
1331
|
* Applies a CSS transform to the target element.
|
1332
|
+
*
|
1333
|
+
* @param {HTMLElement} element
|
1334
|
+
* @param {string} transform
|
1090
1335
|
*/
|
1091
1336
|
function transformElement( element, transform ) {
|
1092
1337
|
|
@@ -1101,6 +1346,8 @@
|
|
1101
1346
|
* Applies CSS transforms to the slides container. The container
|
1102
1347
|
* is transformed from two separate sources: layout and the overview
|
1103
1348
|
* mode.
|
1349
|
+
*
|
1350
|
+
* @param {object} transforms
|
1104
1351
|
*/
|
1105
1352
|
function transformSlides( transforms ) {
|
1106
1353
|
|
@@ -1120,6 +1367,8 @@
|
|
1120
1367
|
|
1121
1368
|
/**
|
1122
1369
|
* Injects the given CSS styles into the DOM.
|
1370
|
+
*
|
1371
|
+
* @param {string} value
|
1123
1372
|
*/
|
1124
1373
|
function injectStyleSheet( value ) {
|
1125
1374
|
|
@@ -1135,14 +1384,56 @@
|
|
1135
1384
|
|
1136
1385
|
}
|
1137
1386
|
|
1387
|
+
/**
|
1388
|
+
* Find the closest parent that matches the given
|
1389
|
+
* selector.
|
1390
|
+
*
|
1391
|
+
* @param {HTMLElement} target The child element
|
1392
|
+
* @param {String} selector The CSS selector to match
|
1393
|
+
* the parents against
|
1394
|
+
*
|
1395
|
+
* @return {HTMLElement} The matched parent or null
|
1396
|
+
* if no matching parent was found
|
1397
|
+
*/
|
1398
|
+
function closestParent( target, selector ) {
|
1399
|
+
|
1400
|
+
var parent = target.parentNode;
|
1401
|
+
|
1402
|
+
while( parent ) {
|
1403
|
+
|
1404
|
+
// There's some overhead doing this each time, we don't
|
1405
|
+
// want to rewrite the element prototype but should still
|
1406
|
+
// be enough to feature detect once at startup...
|
1407
|
+
var matchesMethod = parent.matches || parent.matchesSelector || parent.msMatchesSelector;
|
1408
|
+
|
1409
|
+
// If we find a match, we're all set
|
1410
|
+
if( matchesMethod && matchesMethod.call( parent, selector ) ) {
|
1411
|
+
return parent;
|
1412
|
+
}
|
1413
|
+
|
1414
|
+
// Keep searching
|
1415
|
+
parent = parent.parentNode;
|
1416
|
+
|
1417
|
+
}
|
1418
|
+
|
1419
|
+
return null;
|
1420
|
+
|
1421
|
+
}
|
1422
|
+
|
1138
1423
|
/**
|
1139
1424
|
* Converts various color input formats to an {r:0,g:0,b:0} object.
|
1140
1425
|
*
|
1141
|
-
* @param {
|
1142
|
-
*
|
1143
|
-
*
|
1144
|
-
*
|
1145
|
-
*
|
1426
|
+
* @param {string} color The string representation of a color
|
1427
|
+
* @example
|
1428
|
+
* colorToRgb('#000');
|
1429
|
+
* @example
|
1430
|
+
* colorToRgb('#000000');
|
1431
|
+
* @example
|
1432
|
+
* colorToRgb('rgb(0,0,0)');
|
1433
|
+
* @example
|
1434
|
+
* colorToRgb('rgba(0,0,0)');
|
1435
|
+
*
|
1436
|
+
* @return {{r: number, g: number, b: number, [a]: number}|null}
|
1146
1437
|
*/
|
1147
1438
|
function colorToRgb( color ) {
|
1148
1439
|
|
@@ -1192,7 +1483,8 @@
|
|
1192
1483
|
/**
|
1193
1484
|
* Calculates brightness on a scale of 0-255.
|
1194
1485
|
*
|
1195
|
-
* @param color See
|
1486
|
+
* @param {string} color See colorToRgb for supported formats.
|
1487
|
+
* @see {@link colorToRgb}
|
1196
1488
|
*/
|
1197
1489
|
function colorBrightness( color ) {
|
1198
1490
|
|
@@ -1206,46 +1498,14 @@
|
|
1206
1498
|
|
1207
1499
|
}
|
1208
1500
|
|
1209
|
-
/**
|
1210
|
-
* Retrieves the height of the given element by looking
|
1211
|
-
* at the position and height of its immediate children.
|
1212
|
-
*/
|
1213
|
-
function getAbsoluteHeight( element ) {
|
1214
|
-
|
1215
|
-
var height = 0;
|
1216
|
-
|
1217
|
-
if( element ) {
|
1218
|
-
var absoluteChildren = 0;
|
1219
|
-
|
1220
|
-
toArray( element.childNodes ).forEach( function( child ) {
|
1221
|
-
|
1222
|
-
if( typeof child.offsetTop === 'number' && child.style ) {
|
1223
|
-
// Count # of abs children
|
1224
|
-
if( window.getComputedStyle( child ).position === 'absolute' ) {
|
1225
|
-
absoluteChildren += 1;
|
1226
|
-
}
|
1227
|
-
|
1228
|
-
height = Math.max( height, child.offsetTop + child.offsetHeight );
|
1229
|
-
}
|
1230
|
-
|
1231
|
-
} );
|
1232
|
-
|
1233
|
-
// If there are no absolute children, use offsetHeight
|
1234
|
-
if( absoluteChildren === 0 ) {
|
1235
|
-
height = element.offsetHeight;
|
1236
|
-
}
|
1237
|
-
|
1238
|
-
}
|
1239
|
-
|
1240
|
-
return height;
|
1241
|
-
|
1242
|
-
}
|
1243
|
-
|
1244
1501
|
/**
|
1245
1502
|
* Returns the remaining height within the parent of the
|
1246
1503
|
* target element.
|
1247
1504
|
*
|
1248
1505
|
* remaining height = [ configured parent height ] - [ current parent height ]
|
1506
|
+
*
|
1507
|
+
* @param {HTMLElement} element
|
1508
|
+
* @param {number} [height]
|
1249
1509
|
*/
|
1250
1510
|
function getRemainingHeight( element, height ) {
|
1251
1511
|
|
@@ -1368,6 +1628,8 @@
|
|
1368
1628
|
|
1369
1629
|
/**
|
1370
1630
|
* Bind preview frame links.
|
1631
|
+
*
|
1632
|
+
* @param {string} [selector=a] - selector for anchors
|
1371
1633
|
*/
|
1372
1634
|
function enablePreviewLinks( selector ) {
|
1373
1635
|
|
@@ -1384,9 +1646,9 @@
|
|
1384
1646
|
/**
|
1385
1647
|
* Unbind preview frame links.
|
1386
1648
|
*/
|
1387
|
-
function disablePreviewLinks() {
|
1649
|
+
function disablePreviewLinks( selector ) {
|
1388
1650
|
|
1389
|
-
var anchors = toArray( document.querySelectorAll( 'a' ) );
|
1651
|
+
var anchors = toArray( document.querySelectorAll( selector ? selector : 'a' ) );
|
1390
1652
|
|
1391
1653
|
anchors.forEach( function( element ) {
|
1392
1654
|
if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
|
@@ -1398,6 +1660,8 @@
|
|
1398
1660
|
|
1399
1661
|
/**
|
1400
1662
|
* Opens a preview window for the target URL.
|
1663
|
+
*
|
1664
|
+
* @param {string} url - url for preview iframe src
|
1401
1665
|
*/
|
1402
1666
|
function showPreview( url ) {
|
1403
1667
|
|
@@ -1416,6 +1680,9 @@
|
|
1416
1680
|
'<div class="spinner"></div>',
|
1417
1681
|
'<div class="viewport">',
|
1418
1682
|
'<iframe src="'+ url +'"></iframe>',
|
1683
|
+
'<small class="viewport-inner">',
|
1684
|
+
'<span class="x-frame-error">Unable to load iframe. This is likely due to the site\'s policy (x-frame-options).</span>',
|
1685
|
+
'</small>',
|
1419
1686
|
'</div>'
|
1420
1687
|
].join('');
|
1421
1688
|
|
@@ -1439,7 +1706,29 @@
|
|
1439
1706
|
}
|
1440
1707
|
|
1441
1708
|
/**
|
1442
|
-
*
|
1709
|
+
* Open or close help overlay window.
|
1710
|
+
*
|
1711
|
+
* @param {Boolean} [override] Flag which overrides the
|
1712
|
+
* toggle logic and forcibly sets the desired state. True means
|
1713
|
+
* help is open, false means it's closed.
|
1714
|
+
*/
|
1715
|
+
function toggleHelp( override ){
|
1716
|
+
|
1717
|
+
if( typeof override === 'boolean' ) {
|
1718
|
+
override ? showHelp() : closeOverlay();
|
1719
|
+
}
|
1720
|
+
else {
|
1721
|
+
if( dom.overlay ) {
|
1722
|
+
closeOverlay();
|
1723
|
+
}
|
1724
|
+
else {
|
1725
|
+
showHelp();
|
1726
|
+
}
|
1727
|
+
}
|
1728
|
+
}
|
1729
|
+
|
1730
|
+
/**
|
1731
|
+
* Opens an overlay window with help material.
|
1443
1732
|
*/
|
1444
1733
|
function showHelp() {
|
1445
1734
|
|
@@ -1505,10 +1794,8 @@
|
|
1505
1794
|
|
1506
1795
|
var size = getComputedSlideSize();
|
1507
1796
|
|
1508
|
-
var slidePadding = 20; // TODO Dig this out of DOM
|
1509
|
-
|
1510
1797
|
// Layout the contents of the slides
|
1511
|
-
layoutSlideContents( config.width, config.height
|
1798
|
+
layoutSlideContents( config.width, config.height );
|
1512
1799
|
|
1513
1800
|
dom.slides.style.width = size.width + 'px';
|
1514
1801
|
dom.slides.style.height = size.height + 'px';
|
@@ -1530,13 +1817,20 @@
|
|
1530
1817
|
transformSlides( { layout: '' } );
|
1531
1818
|
}
|
1532
1819
|
else {
|
1533
|
-
// Prefer
|
1534
|
-
|
1820
|
+
// Prefer zoom for scaling up so that content remains crisp.
|
1821
|
+
// Don't use zoom to scale down since that can lead to shifts
|
1822
|
+
// in text layout/line breaks.
|
1823
|
+
if( scale > 1 && features.zoom ) {
|
1535
1824
|
dom.slides.style.zoom = scale;
|
1825
|
+
dom.slides.style.left = '';
|
1826
|
+
dom.slides.style.top = '';
|
1827
|
+
dom.slides.style.bottom = '';
|
1828
|
+
dom.slides.style.right = '';
|
1536
1829
|
transformSlides( { layout: '' } );
|
1537
1830
|
}
|
1538
1831
|
// Apply scale transform as a fallback
|
1539
1832
|
else {
|
1833
|
+
dom.slides.style.zoom = '';
|
1540
1834
|
dom.slides.style.left = '50%';
|
1541
1835
|
dom.slides.style.top = '50%';
|
1542
1836
|
dom.slides.style.bottom = 'auto';
|
@@ -1563,7 +1857,7 @@
|
|
1563
1857
|
slide.style.top = 0;
|
1564
1858
|
}
|
1565
1859
|
else {
|
1566
|
-
slide.style.top = Math.max( (
|
1860
|
+
slide.style.top = Math.max( ( size.height - slide.scrollHeight ) / 2, 0 ) + 'px';
|
1567
1861
|
}
|
1568
1862
|
}
|
1569
1863
|
else {
|
@@ -1575,6 +1869,10 @@
|
|
1575
1869
|
updateProgress();
|
1576
1870
|
updateParallax();
|
1577
1871
|
|
1872
|
+
if( isOverview() ) {
|
1873
|
+
updateOverview();
|
1874
|
+
}
|
1875
|
+
|
1578
1876
|
}
|
1579
1877
|
|
1580
1878
|
}
|
@@ -1582,8 +1880,11 @@
|
|
1582
1880
|
/**
|
1583
1881
|
* Applies layout logic to the contents of all slides in
|
1584
1882
|
* the presentation.
|
1883
|
+
*
|
1884
|
+
* @param {string|number} width
|
1885
|
+
* @param {string|number} height
|
1585
1886
|
*/
|
1586
|
-
function layoutSlideContents( width, height
|
1887
|
+
function layoutSlideContents( width, height ) {
|
1587
1888
|
|
1588
1889
|
// Handle sizing of elements with the 'stretch' class
|
1589
1890
|
toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
|
@@ -1615,6 +1916,9 @@
|
|
1615
1916
|
* Calculates the computed pixel size of our slides. These
|
1616
1917
|
* values are based on the width and height configuration
|
1617
1918
|
* options.
|
1919
|
+
*
|
1920
|
+
* @param {number} [presentationWidth=dom.wrapper.offsetWidth]
|
1921
|
+
* @param {number} [presentationHeight=dom.wrapper.offsetHeight]
|
1618
1922
|
*/
|
1619
1923
|
function getComputedSlideSize( presentationWidth, presentationHeight ) {
|
1620
1924
|
|
@@ -1652,7 +1956,7 @@
|
|
1652
1956
|
* from the stack.
|
1653
1957
|
*
|
1654
1958
|
* @param {HTMLElement} stack The vertical stack element
|
1655
|
-
* @param {
|
1959
|
+
* @param {string|number} [v=0] Index to memorize
|
1656
1960
|
*/
|
1657
1961
|
function setPreviousVerticalIndex( stack, v ) {
|
1658
1962
|
|
@@ -1716,6 +2020,17 @@
|
|
1716
2020
|
}
|
1717
2021
|
} );
|
1718
2022
|
|
2023
|
+
// Calculate slide sizes
|
2024
|
+
var margin = 70;
|
2025
|
+
var slideSize = getComputedSlideSize();
|
2026
|
+
overviewSlideWidth = slideSize.width + margin;
|
2027
|
+
overviewSlideHeight = slideSize.height + margin;
|
2028
|
+
|
2029
|
+
// Reverse in RTL mode
|
2030
|
+
if( config.rtl ) {
|
2031
|
+
overviewSlideWidth = -overviewSlideWidth;
|
2032
|
+
}
|
2033
|
+
|
1719
2034
|
updateSlidesVisibility();
|
1720
2035
|
layoutOverview();
|
1721
2036
|
updateOverview();
|
@@ -1739,19 +2054,10 @@
|
|
1739
2054
|
*/
|
1740
2055
|
function layoutOverview() {
|
1741
2056
|
|
1742
|
-
var margin = 70;
|
1743
|
-
var slideWidth = config.width + margin,
|
1744
|
-
slideHeight = config.height + margin;
|
1745
|
-
|
1746
|
-
// Reverse in RTL mode
|
1747
|
-
if( config.rtl ) {
|
1748
|
-
slideWidth = -slideWidth;
|
1749
|
-
}
|
1750
|
-
|
1751
2057
|
// Layout slides
|
1752
2058
|
toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {
|
1753
2059
|
hslide.setAttribute( 'data-index-h', h );
|
1754
|
-
transformElement( hslide, 'translate3d(' + ( h *
|
2060
|
+
transformElement( hslide, 'translate3d(' + ( h * overviewSlideWidth ) + 'px, 0, 0)' );
|
1755
2061
|
|
1756
2062
|
if( hslide.classList.contains( 'stack' ) ) {
|
1757
2063
|
|
@@ -1759,7 +2065,7 @@
|
|
1759
2065
|
vslide.setAttribute( 'data-index-h', h );
|
1760
2066
|
vslide.setAttribute( 'data-index-v', v );
|
1761
2067
|
|
1762
|
-
transformElement( vslide, 'translate3d(0, ' + ( v *
|
2068
|
+
transformElement( vslide, 'translate3d(0, ' + ( v * overviewSlideHeight ) + 'px, 0)' );
|
1763
2069
|
} );
|
1764
2070
|
|
1765
2071
|
}
|
@@ -1767,10 +2073,10 @@
|
|
1767
2073
|
|
1768
2074
|
// Layout slide backgrounds
|
1769
2075
|
toArray( dom.background.childNodes ).forEach( function( hbackground, h ) {
|
1770
|
-
transformElement( hbackground, 'translate3d(' + ( h *
|
2076
|
+
transformElement( hbackground, 'translate3d(' + ( h * overviewSlideWidth ) + 'px, 0, 0)' );
|
1771
2077
|
|
1772
2078
|
toArray( hbackground.querySelectorAll( '.slide-background' ) ).forEach( function( vbackground, v ) {
|
1773
|
-
transformElement( vbackground, 'translate3d(0, ' + ( v *
|
2079
|
+
transformElement( vbackground, 'translate3d(0, ' + ( v * overviewSlideHeight ) + 'px, 0)' );
|
1774
2080
|
} );
|
1775
2081
|
} );
|
1776
2082
|
|
@@ -1782,20 +2088,14 @@
|
|
1782
2088
|
*/
|
1783
2089
|
function updateOverview() {
|
1784
2090
|
|
1785
|
-
var
|
1786
|
-
var
|
1787
|
-
slideHeight = config.height + margin;
|
1788
|
-
|
1789
|
-
// Reverse in RTL mode
|
1790
|
-
if( config.rtl ) {
|
1791
|
-
slideWidth = -slideWidth;
|
1792
|
-
}
|
2091
|
+
var vmin = Math.min( window.innerWidth, window.innerHeight );
|
2092
|
+
var scale = Math.max( vmin / 5, 150 ) / vmin;
|
1793
2093
|
|
1794
2094
|
transformSlides( {
|
1795
2095
|
overview: [
|
1796
|
-
'
|
1797
|
-
'
|
1798
|
-
'
|
2096
|
+
'scale('+ scale +')',
|
2097
|
+
'translateX('+ ( -indexh * overviewSlideWidth ) +'px)',
|
2098
|
+
'translateY('+ ( -indexv * overviewSlideHeight ) +'px)'
|
1799
2099
|
].join( ' ' )
|
1800
2100
|
} );
|
1801
2101
|
|
@@ -1860,7 +2160,7 @@
|
|
1860
2160
|
/**
|
1861
2161
|
* Toggles the slide overview mode on and off.
|
1862
2162
|
*
|
1863
|
-
* @param {Boolean} override
|
2163
|
+
* @param {Boolean} [override] Flag which overrides the
|
1864
2164
|
* toggle logic and forcibly sets the desired state. True means
|
1865
2165
|
* overview is open, false means it's closed.
|
1866
2166
|
*/
|
@@ -1891,8 +2191,9 @@
|
|
1891
2191
|
* Checks if the current or specified slide is vertical
|
1892
2192
|
* (nested within another slide).
|
1893
2193
|
*
|
1894
|
-
* @param {HTMLElement} slide
|
2194
|
+
* @param {HTMLElement} [slide=currentSlide] The slide to check
|
1895
2195
|
* orientation of
|
2196
|
+
* @return {Boolean}
|
1896
2197
|
*/
|
1897
2198
|
function isVerticalSlide( slide ) {
|
1898
2199
|
|
@@ -1911,10 +2212,10 @@
|
|
1911
2212
|
*/
|
1912
2213
|
function enterFullscreen() {
|
1913
2214
|
|
1914
|
-
var element = document.
|
2215
|
+
var element = document.documentElement;
|
1915
2216
|
|
1916
2217
|
// Check which implementation is available
|
1917
|
-
var requestMethod = element.
|
2218
|
+
var requestMethod = element.requestFullscreen ||
|
1918
2219
|
element.webkitRequestFullscreen ||
|
1919
2220
|
element.webkitRequestFullScreen ||
|
1920
2221
|
element.mozRequestFullScreen ||
|
@@ -1977,6 +2278,8 @@
|
|
1977
2278
|
|
1978
2279
|
/**
|
1979
2280
|
* Checks if we are currently in the paused mode.
|
2281
|
+
*
|
2282
|
+
* @return {Boolean}
|
1980
2283
|
*/
|
1981
2284
|
function isPaused() {
|
1982
2285
|
|
@@ -1987,7 +2290,7 @@
|
|
1987
2290
|
/**
|
1988
2291
|
* Toggles the auto slide mode on and off.
|
1989
2292
|
*
|
1990
|
-
* @param {Boolean} override
|
2293
|
+
* @param {Boolean} [override] Flag which sets the desired state.
|
1991
2294
|
* True means autoplay starts, false means it stops.
|
1992
2295
|
*/
|
1993
2296
|
|
@@ -2005,6 +2308,8 @@
|
|
2005
2308
|
|
2006
2309
|
/**
|
2007
2310
|
* Checks if the auto slide mode is currently on.
|
2311
|
+
*
|
2312
|
+
* @return {Boolean}
|
2008
2313
|
*/
|
2009
2314
|
function isAutoSliding() {
|
2010
2315
|
|
@@ -2017,11 +2322,11 @@
|
|
2017
2322
|
* slide which matches the specified horizontal and vertical
|
2018
2323
|
* indices.
|
2019
2324
|
*
|
2020
|
-
* @param {
|
2021
|
-
* @param {
|
2022
|
-
* @param {
|
2325
|
+
* @param {number} [h=indexh] Horizontal index of the target slide
|
2326
|
+
* @param {number} [v=indexv] Vertical index of the target slide
|
2327
|
+
* @param {number} [f] Index of a fragment within the
|
2023
2328
|
* target slide to activate
|
2024
|
-
* @param {
|
2329
|
+
* @param {number} [o] Origin for use in multimaster environments
|
2025
2330
|
*/
|
2026
2331
|
function slide( h, v, f, o ) {
|
2027
2332
|
|
@@ -2031,6 +2336,9 @@
|
|
2031
2336
|
// Query all horizontal slides in the deck
|
2032
2337
|
var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
|
2033
2338
|
|
2339
|
+
// Abort if there are no slides
|
2340
|
+
if( horizontalSlides.length === 0 ) return;
|
2341
|
+
|
2034
2342
|
// If no vertical index is specified and the upcoming slide is a
|
2035
2343
|
// stack, resume at its previous vertical index
|
2036
2344
|
if( v === undefined && !isOverview() ) {
|
@@ -2147,13 +2455,14 @@
|
|
2147
2455
|
}
|
2148
2456
|
|
2149
2457
|
// Announce the current slide contents, for screen readers
|
2150
|
-
dom.statusDiv.textContent = currentSlide
|
2458
|
+
dom.statusDiv.textContent = getStatusText( currentSlide );
|
2151
2459
|
|
2152
2460
|
updateControls();
|
2153
2461
|
updateProgress();
|
2154
2462
|
updateBackground();
|
2155
2463
|
updateParallax();
|
2156
2464
|
updateSlideNumber();
|
2465
|
+
updateNotes();
|
2157
2466
|
|
2158
2467
|
// Update the URL hash
|
2159
2468
|
writeURL();
|
@@ -2192,12 +2501,21 @@
|
|
2192
2501
|
|
2193
2502
|
updateControls();
|
2194
2503
|
updateProgress();
|
2195
|
-
updateBackground( true );
|
2196
2504
|
updateSlideNumber();
|
2197
2505
|
updateSlidesVisibility();
|
2506
|
+
updateBackground( true );
|
2507
|
+
updateNotesVisibility();
|
2508
|
+
updateNotes();
|
2198
2509
|
|
2199
2510
|
formatEmbeddedContent();
|
2200
|
-
|
2511
|
+
|
2512
|
+
// Start or stop embedded content depending on global config
|
2513
|
+
if( config.autoPlayMedia === false ) {
|
2514
|
+
stopEmbeddedContent( currentSlide, { unloadIframes: false } );
|
2515
|
+
}
|
2516
|
+
else {
|
2517
|
+
startEmbeddedContent( currentSlide );
|
2518
|
+
}
|
2201
2519
|
|
2202
2520
|
if( isOverview() ) {
|
2203
2521
|
layoutOverview();
|
@@ -2252,16 +2570,33 @@
|
|
2252
2570
|
|
2253
2571
|
}
|
2254
2572
|
|
2573
|
+
/**
|
2574
|
+
* Randomly shuffles all slides in the deck.
|
2575
|
+
*/
|
2576
|
+
function shuffle() {
|
2577
|
+
|
2578
|
+
var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
|
2579
|
+
|
2580
|
+
slides.forEach( function( slide ) {
|
2581
|
+
|
2582
|
+
// Insert this slide next to another random slide. This may
|
2583
|
+
// cause the slide to insert before itself but that's fine.
|
2584
|
+
dom.slides.insertBefore( slide, slides[ Math.floor( Math.random() * slides.length ) ] );
|
2585
|
+
|
2586
|
+
} );
|
2587
|
+
|
2588
|
+
}
|
2589
|
+
|
2255
2590
|
/**
|
2256
2591
|
* Updates one dimension of slides by showing the slide
|
2257
2592
|
* with the specified index.
|
2258
2593
|
*
|
2259
|
-
* @param {
|
2594
|
+
* @param {string} selector A CSS selector that will fetch
|
2260
2595
|
* the group of slides we are working with
|
2261
|
-
* @param {
|
2596
|
+
* @param {number} index The index of the slide that should be
|
2262
2597
|
* shown
|
2263
2598
|
*
|
2264
|
-
* @return {
|
2599
|
+
* @return {number} The index of the slide that is now shown,
|
2265
2600
|
* might differ from the passed in index if it was out of
|
2266
2601
|
* bounds.
|
2267
2602
|
*/
|
@@ -2413,10 +2748,10 @@
|
|
2413
2748
|
|
2414
2749
|
// Show the horizontal slide if it's within the view distance
|
2415
2750
|
if( distanceX < viewDistance ) {
|
2416
|
-
|
2751
|
+
loadSlide( horizontalSlide );
|
2417
2752
|
}
|
2418
2753
|
else {
|
2419
|
-
|
2754
|
+
unloadSlide( horizontalSlide );
|
2420
2755
|
}
|
2421
2756
|
|
2422
2757
|
if( verticalSlidesLength ) {
|
@@ -2429,20 +2764,79 @@
|
|
2429
2764
|
distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy );
|
2430
2765
|
|
2431
2766
|
if( distanceX + distanceY < viewDistance ) {
|
2432
|
-
|
2767
|
+
loadSlide( verticalSlide );
|
2433
2768
|
}
|
2434
2769
|
else {
|
2435
|
-
|
2770
|
+
unloadSlide( verticalSlide );
|
2436
2771
|
}
|
2437
2772
|
}
|
2438
2773
|
|
2439
2774
|
}
|
2440
2775
|
}
|
2441
2776
|
|
2777
|
+
// Flag if there are ANY vertical slides, anywhere in the deck
|
2778
|
+
if( dom.wrapper.querySelectorAll( '.slides>section>section' ).length ) {
|
2779
|
+
dom.wrapper.classList.add( 'has-vertical-slides' );
|
2780
|
+
}
|
2781
|
+
else {
|
2782
|
+
dom.wrapper.classList.remove( 'has-vertical-slides' );
|
2783
|
+
}
|
2784
|
+
|
2785
|
+
// Flag if there are ANY horizontal slides, anywhere in the deck
|
2786
|
+
if( dom.wrapper.querySelectorAll( '.slides>section' ).length > 1 ) {
|
2787
|
+
dom.wrapper.classList.add( 'has-horizontal-slides' );
|
2788
|
+
}
|
2789
|
+
else {
|
2790
|
+
dom.wrapper.classList.remove( 'has-horizontal-slides' );
|
2791
|
+
}
|
2792
|
+
|
2793
|
+
}
|
2794
|
+
|
2795
|
+
}
|
2796
|
+
|
2797
|
+
/**
|
2798
|
+
* Pick up notes from the current slide and display them
|
2799
|
+
* to the viewer.
|
2800
|
+
*
|
2801
|
+
* @see {@link config.showNotes}
|
2802
|
+
*/
|
2803
|
+
function updateNotes() {
|
2804
|
+
|
2805
|
+
if( config.showNotes && dom.speakerNotes && currentSlide && !isPrintingPDF() ) {
|
2806
|
+
|
2807
|
+
dom.speakerNotes.innerHTML = getSlideNotes() || '<span class="notes-placeholder">No notes on this slide.</span>';
|
2808
|
+
|
2442
2809
|
}
|
2443
2810
|
|
2444
2811
|
}
|
2445
2812
|
|
2813
|
+
/**
|
2814
|
+
* Updates the visibility of the speaker notes sidebar that
|
2815
|
+
* is used to share annotated slides. The notes sidebar is
|
2816
|
+
* only visible if showNotes is true and there are notes on
|
2817
|
+
* one or more slides in the deck.
|
2818
|
+
*/
|
2819
|
+
function updateNotesVisibility() {
|
2820
|
+
|
2821
|
+
if( config.showNotes && hasNotes() ) {
|
2822
|
+
dom.wrapper.classList.add( 'show-notes' );
|
2823
|
+
}
|
2824
|
+
else {
|
2825
|
+
dom.wrapper.classList.remove( 'show-notes' );
|
2826
|
+
}
|
2827
|
+
|
2828
|
+
}
|
2829
|
+
|
2830
|
+
/**
|
2831
|
+
* Checks if there are speaker notes for ANY slide in the
|
2832
|
+
* presentation.
|
2833
|
+
*/
|
2834
|
+
function hasNotes() {
|
2835
|
+
|
2836
|
+
return dom.slides.querySelectorAll( '[data-notes], aside.notes' ).length > 0;
|
2837
|
+
|
2838
|
+
}
|
2839
|
+
|
2446
2840
|
/**
|
2447
2841
|
* Updates the progress bar to reflect the current slide.
|
2448
2842
|
*/
|
@@ -2460,30 +2854,64 @@
|
|
2460
2854
|
/**
|
2461
2855
|
* Updates the slide number div to reflect the current slide.
|
2462
2856
|
*
|
2463
|
-
*
|
2464
|
-
*
|
2465
|
-
* h:
|
2466
|
-
*
|
2467
|
-
* c:
|
2468
|
-
* t: total number of slides (flattened)
|
2857
|
+
* The following slide number formats are available:
|
2858
|
+
* "h.v": horizontal . vertical slide number (default)
|
2859
|
+
* "h/v": horizontal / vertical slide number
|
2860
|
+
* "c": flattened slide number
|
2861
|
+
* "c/t": flattened slide number / total slides
|
2469
2862
|
*/
|
2470
2863
|
function updateSlideNumber() {
|
2471
2864
|
|
2472
2865
|
// Update slide number if enabled
|
2473
|
-
if( config.slideNumber && dom.slideNumber) {
|
2866
|
+
if( config.slideNumber && dom.slideNumber ) {
|
2474
2867
|
|
2475
|
-
|
2476
|
-
var format = '
|
2868
|
+
var value = [];
|
2869
|
+
var format = 'h.v';
|
2477
2870
|
|
2478
|
-
// Check if a custom
|
2871
|
+
// Check if a custom number format is available
|
2479
2872
|
if( typeof config.slideNumber === 'string' ) {
|
2480
2873
|
format = config.slideNumber;
|
2481
2874
|
}
|
2482
2875
|
|
2483
|
-
|
2484
|
-
|
2485
|
-
|
2486
|
-
|
2876
|
+
switch( format ) {
|
2877
|
+
case 'c':
|
2878
|
+
value.push( getSlidePastCount() + 1 );
|
2879
|
+
break;
|
2880
|
+
case 'c/t':
|
2881
|
+
value.push( getSlidePastCount() + 1, '/', getTotalSlides() );
|
2882
|
+
break;
|
2883
|
+
case 'h/v':
|
2884
|
+
value.push( indexh + 1 );
|
2885
|
+
if( isVerticalSlide() ) value.push( '/', indexv + 1 );
|
2886
|
+
break;
|
2887
|
+
default:
|
2888
|
+
value.push( indexh + 1 );
|
2889
|
+
if( isVerticalSlide() ) value.push( '.', indexv + 1 );
|
2890
|
+
}
|
2891
|
+
|
2892
|
+
dom.slideNumber.innerHTML = formatSlideNumber( value[0], value[1], value[2] );
|
2893
|
+
}
|
2894
|
+
|
2895
|
+
}
|
2896
|
+
|
2897
|
+
/**
|
2898
|
+
* Applies HTML formatting to a slide number before it's
|
2899
|
+
* written to the DOM.
|
2900
|
+
*
|
2901
|
+
* @param {number} a Current slide
|
2902
|
+
* @param {string} delimiter Character to separate slide numbers
|
2903
|
+
* @param {(number|*)} b Total slides
|
2904
|
+
* @return {string} HTML string fragment
|
2905
|
+
*/
|
2906
|
+
function formatSlideNumber( a, delimiter, b ) {
|
2907
|
+
|
2908
|
+
if( typeof b === 'number' && !isNaN( b ) ) {
|
2909
|
+
return '<span class="slide-number-a">'+ a +'</span>' +
|
2910
|
+
'<span class="slide-number-delimiter">'+ delimiter +'</span>' +
|
2911
|
+
'<span class="slide-number-b">'+ b +'</span>';
|
2912
|
+
}
|
2913
|
+
else {
|
2914
|
+
return '<span class="slide-number-a">'+ a +'</span>';
|
2487
2915
|
}
|
2488
2916
|
|
2489
2917
|
}
|
@@ -2504,34 +2932,57 @@
|
|
2504
2932
|
.concat( dom.controlsNext ).forEach( function( node ) {
|
2505
2933
|
node.classList.remove( 'enabled' );
|
2506
2934
|
node.classList.remove( 'fragmented' );
|
2935
|
+
|
2936
|
+
// Set 'disabled' attribute on all directions
|
2937
|
+
node.setAttribute( 'disabled', 'disabled' );
|
2507
2938
|
} );
|
2508
2939
|
|
2509
|
-
// Add the 'enabled' class to the available routes
|
2510
|
-
if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' );
|
2511
|
-
if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
2512
|
-
if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' );
|
2513
|
-
if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
2940
|
+
// Add the 'enabled' class to the available routes; remove 'disabled' attribute to enable buttons
|
2941
|
+
if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2942
|
+
if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2943
|
+
if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2944
|
+
if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2514
2945
|
|
2515
2946
|
// Prev/next buttons
|
2516
|
-
if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
2517
|
-
if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
2947
|
+
if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2948
|
+
if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2518
2949
|
|
2519
2950
|
// Highlight fragment directions
|
2520
2951
|
if( currentSlide ) {
|
2521
2952
|
|
2522
2953
|
// Always apply fragment decorator to prev/next buttons
|
2523
|
-
if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2524
|
-
if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2954
|
+
if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2955
|
+
if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2525
2956
|
|
2526
2957
|
// Apply fragment decorators to directional buttons based on
|
2527
2958
|
// what slide axis they are in
|
2528
2959
|
if( isVerticalSlide( currentSlide ) ) {
|
2529
|
-
if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2530
|
-
if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2960
|
+
if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2961
|
+
if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2531
2962
|
}
|
2532
2963
|
else {
|
2533
|
-
if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2534
|
-
if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
2964
|
+
if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2965
|
+
if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
|
2966
|
+
}
|
2967
|
+
|
2968
|
+
}
|
2969
|
+
|
2970
|
+
if( config.controlsTutorial ) {
|
2971
|
+
|
2972
|
+
// Highlight control arrows with an animation to ensure
|
2973
|
+
// that the viewer knows how to navigate
|
2974
|
+
if( !hasNavigatedDown && routes.down ) {
|
2975
|
+
dom.controlsDownArrow.classList.add( 'highlight' );
|
2976
|
+
}
|
2977
|
+
else {
|
2978
|
+
dom.controlsDownArrow.classList.remove( 'highlight' );
|
2979
|
+
|
2980
|
+
if( !hasNavigatedRight && routes.right && indexv === 0 ) {
|
2981
|
+
dom.controlsRightArrow.classList.add( 'highlight' );
|
2982
|
+
}
|
2983
|
+
else {
|
2984
|
+
dom.controlsRightArrow.classList.remove( 'highlight' );
|
2985
|
+
}
|
2535
2986
|
}
|
2536
2987
|
|
2537
2988
|
}
|
@@ -2542,7 +2993,7 @@
|
|
2542
2993
|
* Updates the background elements to reflect the current
|
2543
2994
|
* slide.
|
2544
2995
|
*
|
2545
|
-
* @param {
|
2996
|
+
* @param {boolean} includeAll If true, the backgrounds of
|
2546
2997
|
* all vertical slides (not just the present) will be updated.
|
2547
2998
|
*/
|
2548
2999
|
function updateBackground( includeAll ) {
|
@@ -2599,22 +3050,17 @@
|
|
2599
3050
|
|
2600
3051
|
} );
|
2601
3052
|
|
2602
|
-
// Stop
|
3053
|
+
// Stop content inside of previous backgrounds
|
2603
3054
|
if( previousBackground ) {
|
2604
3055
|
|
2605
|
-
|
2606
|
-
if( previousVideo ) previousVideo.pause();
|
3056
|
+
stopEmbeddedContent( previousBackground );
|
2607
3057
|
|
2608
3058
|
}
|
2609
3059
|
|
3060
|
+
// Start content in the current background
|
2610
3061
|
if( currentBackground ) {
|
2611
3062
|
|
2612
|
-
|
2613
|
-
var currentVideo = currentBackground.querySelector( 'video' );
|
2614
|
-
if( currentVideo ) {
|
2615
|
-
currentVideo.currentTime = 0;
|
2616
|
-
currentVideo.play();
|
2617
|
-
}
|
3063
|
+
startEmbeddedContent( currentBackground );
|
2618
3064
|
|
2619
3065
|
var backgroundImageURL = currentBackground.style.backgroundImage || '';
|
2620
3066
|
|
@@ -2688,7 +3134,7 @@
|
|
2688
3134
|
horizontalOffsetMultiplier = config.parallaxBackgroundHorizontal;
|
2689
3135
|
}
|
2690
3136
|
else {
|
2691
|
-
horizontalOffsetMultiplier = ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 );
|
3137
|
+
horizontalOffsetMultiplier = horizontalSlideCount > 1 ? ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) : 0;
|
2692
3138
|
}
|
2693
3139
|
|
2694
3140
|
horizontalOffset = horizontalOffsetMultiplier * indexh * -1;
|
@@ -2705,7 +3151,7 @@
|
|
2705
3151
|
verticalOffsetMultiplier = ( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 );
|
2706
3152
|
}
|
2707
3153
|
|
2708
|
-
verticalOffset = verticalSlideCount > 0 ? verticalOffsetMultiplier * indexv
|
3154
|
+
verticalOffset = verticalSlideCount > 0 ? verticalOffsetMultiplier * indexv : 0;
|
2709
3155
|
|
2710
3156
|
dom.background.style.backgroundPosition = horizontalOffset + 'px ' + -verticalOffset + 'px';
|
2711
3157
|
|
@@ -2717,15 +3163,20 @@
|
|
2717
3163
|
* Called when the given slide is within the configured view
|
2718
3164
|
* distance. Shows the slide element and loads any content
|
2719
3165
|
* that is set to load lazily (data-src).
|
3166
|
+
*
|
3167
|
+
* @param {HTMLElement} slide Slide to show
|
2720
3168
|
*/
|
2721
|
-
function
|
3169
|
+
function loadSlide( slide, options ) {
|
3170
|
+
|
3171
|
+
options = options || {};
|
2722
3172
|
|
2723
3173
|
// Show the slide element
|
2724
|
-
slide.style.display =
|
3174
|
+
slide.style.display = config.display;
|
2725
3175
|
|
2726
3176
|
// Media elements with data-src attributes
|
2727
3177
|
toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) {
|
2728
3178
|
element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
|
3179
|
+
element.setAttribute( 'data-lazy-loaded', '' );
|
2729
3180
|
element.removeAttribute( 'data-src' );
|
2730
3181
|
} );
|
2731
3182
|
|
@@ -2736,6 +3187,7 @@
|
|
2736
3187
|
toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) {
|
2737
3188
|
source.setAttribute( 'src', source.getAttribute( 'data-src' ) );
|
2738
3189
|
source.removeAttribute( 'data-src' );
|
3190
|
+
source.setAttribute( 'data-lazy-loaded', '' );
|
2739
3191
|
sources += 1;
|
2740
3192
|
} );
|
2741
3193
|
|
@@ -2760,6 +3212,7 @@
|
|
2760
3212
|
var backgroundImage = slide.getAttribute( 'data-background-image' ),
|
2761
3213
|
backgroundVideo = slide.getAttribute( 'data-background-video' ),
|
2762
3214
|
backgroundVideoLoop = slide.hasAttribute( 'data-background-video-loop' ),
|
3215
|
+
backgroundVideoMuted = slide.hasAttribute( 'data-background-video-muted' ),
|
2763
3216
|
backgroundIframe = slide.getAttribute( 'data-background-iframe' );
|
2764
3217
|
|
2765
3218
|
// Images
|
@@ -2774,6 +3227,19 @@
|
|
2774
3227
|
video.setAttribute( 'loop', '' );
|
2775
3228
|
}
|
2776
3229
|
|
3230
|
+
if( backgroundVideoMuted ) {
|
3231
|
+
video.muted = true;
|
3232
|
+
}
|
3233
|
+
|
3234
|
+
// Inline video playback works (at least in Mobile Safari) as
|
3235
|
+
// long as the video is muted and the `playsinline` attribute is
|
3236
|
+
// present
|
3237
|
+
if( isMobileDevice ) {
|
3238
|
+
video.muted = true;
|
3239
|
+
video.autoplay = true;
|
3240
|
+
video.setAttribute( 'playsinline', '' );
|
3241
|
+
}
|
3242
|
+
|
2777
3243
|
// Support comma separated lists of video sources
|
2778
3244
|
backgroundVideo.split( ',' ).forEach( function( source ) {
|
2779
3245
|
video.innerHTML += '<source src="'+ source +'">';
|
@@ -2782,26 +3248,41 @@
|
|
2782
3248
|
background.appendChild( video );
|
2783
3249
|
}
|
2784
3250
|
// Iframes
|
2785
|
-
else if( backgroundIframe ) {
|
3251
|
+
else if( backgroundIframe && options.excludeIframes !== true ) {
|
2786
3252
|
var iframe = document.createElement( 'iframe' );
|
3253
|
+
iframe.setAttribute( 'allowfullscreen', '' );
|
3254
|
+
iframe.setAttribute( 'mozallowfullscreen', '' );
|
3255
|
+
iframe.setAttribute( 'webkitallowfullscreen', '' );
|
3256
|
+
|
3257
|
+
// Only load autoplaying content when the slide is shown to
|
3258
|
+
// avoid having it play in the background
|
3259
|
+
if( /autoplay=(1|true|yes)/gi.test( backgroundIframe ) ) {
|
3260
|
+
iframe.setAttribute( 'data-src', backgroundIframe );
|
3261
|
+
}
|
3262
|
+
else {
|
2787
3263
|
iframe.setAttribute( 'src', backgroundIframe );
|
2788
|
-
|
2789
|
-
|
2790
|
-
|
2791
|
-
|
3264
|
+
}
|
3265
|
+
|
3266
|
+
iframe.style.width = '100%';
|
3267
|
+
iframe.style.height = '100%';
|
3268
|
+
iframe.style.maxHeight = '100%';
|
3269
|
+
iframe.style.maxWidth = '100%';
|
2792
3270
|
|
2793
3271
|
background.appendChild( iframe );
|
2794
3272
|
}
|
2795
3273
|
}
|
3274
|
+
|
2796
3275
|
}
|
2797
3276
|
|
2798
3277
|
}
|
2799
3278
|
|
2800
3279
|
/**
|
2801
|
-
*
|
2802
|
-
* configured view distance.
|
3280
|
+
* Unloads and hides the given slide. This is called when the
|
3281
|
+
* slide is moved outside of the configured view distance.
|
3282
|
+
*
|
3283
|
+
* @param {HTMLElement} slide
|
2803
3284
|
*/
|
2804
|
-
function
|
3285
|
+
function unloadSlide( slide ) {
|
2805
3286
|
|
2806
3287
|
// Hide the slide element
|
2807
3288
|
slide.style.display = 'none';
|
@@ -2813,12 +3294,24 @@
|
|
2813
3294
|
background.style.display = 'none';
|
2814
3295
|
}
|
2815
3296
|
|
3297
|
+
// Reset lazy-loaded media elements with src attributes
|
3298
|
+
toArray( slide.querySelectorAll( 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src]' ) ).forEach( function( element ) {
|
3299
|
+
element.setAttribute( 'data-src', element.getAttribute( 'src' ) );
|
3300
|
+
element.removeAttribute( 'src' );
|
3301
|
+
} );
|
3302
|
+
|
3303
|
+
// Reset lazy-loaded media elements with <source> children
|
3304
|
+
toArray( slide.querySelectorAll( 'video[data-lazy-loaded] source[src], audio source[src]' ) ).forEach( function( source ) {
|
3305
|
+
source.setAttribute( 'data-src', source.getAttribute( 'src' ) );
|
3306
|
+
source.removeAttribute( 'src' );
|
3307
|
+
} );
|
3308
|
+
|
2816
3309
|
}
|
2817
3310
|
|
2818
3311
|
/**
|
2819
3312
|
* Determine what available routes there are for navigation.
|
2820
3313
|
*
|
2821
|
-
* @return {
|
3314
|
+
* @return {{left: boolean, right: boolean, up: boolean, down: boolean}}
|
2822
3315
|
*/
|
2823
3316
|
function availableRoutes() {
|
2824
3317
|
|
@@ -2847,7 +3340,7 @@
|
|
2847
3340
|
* Returns an object describing the available fragment
|
2848
3341
|
* directions.
|
2849
3342
|
*
|
2850
|
-
* @return {
|
3343
|
+
* @return {{prev: boolean, next: boolean}}
|
2851
3344
|
*/
|
2852
3345
|
function availableFragments() {
|
2853
3346
|
|
@@ -2888,65 +3381,147 @@
|
|
2888
3381
|
_appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' );
|
2889
3382
|
_appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' );
|
2890
3383
|
|
3384
|
+
// Always show media controls on mobile devices
|
3385
|
+
if( isMobileDevice ) {
|
3386
|
+
toArray( dom.slides.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
|
3387
|
+
el.controls = true;
|
3388
|
+
} );
|
3389
|
+
}
|
3390
|
+
|
2891
3391
|
}
|
2892
3392
|
|
2893
3393
|
/**
|
2894
3394
|
* Start playback of any embedded content inside of
|
2895
|
-
* the
|
3395
|
+
* the given element.
|
3396
|
+
*
|
3397
|
+
* @param {HTMLElement} element
|
2896
3398
|
*/
|
2897
|
-
function startEmbeddedContent(
|
3399
|
+
function startEmbeddedContent( element ) {
|
3400
|
+
|
3401
|
+
if( element && !isSpeakerNotes() ) {
|
2898
3402
|
|
2899
|
-
if( slide && !isSpeakerNotes() ) {
|
2900
3403
|
// Restart GIFs
|
2901
|
-
toArray(
|
3404
|
+
toArray( element.querySelectorAll( 'img[src$=".gif"]' ) ).forEach( function( el ) {
|
2902
3405
|
// Setting the same unchanged source like this was confirmed
|
2903
3406
|
// to work in Chrome, FF & Safari
|
2904
3407
|
el.setAttribute( 'src', el.getAttribute( 'src' ) );
|
2905
3408
|
} );
|
2906
3409
|
|
2907
3410
|
// HTML5 media elements
|
2908
|
-
toArray(
|
2909
|
-
if(
|
2910
|
-
|
3411
|
+
toArray( element.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
|
3412
|
+
if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {
|
3413
|
+
return;
|
3414
|
+
}
|
3415
|
+
|
3416
|
+
// Prefer an explicit global autoplay setting
|
3417
|
+
var autoplay = config.autoPlayMedia;
|
3418
|
+
|
3419
|
+
// If no global setting is available, fall back on the element's
|
3420
|
+
// own autoplay setting
|
3421
|
+
if( typeof autoplay !== 'boolean' ) {
|
3422
|
+
autoplay = el.hasAttribute( 'data-autoplay' ) || !!closestParent( el, '.slide-background' );
|
3423
|
+
}
|
3424
|
+
|
3425
|
+
if( autoplay && typeof el.play === 'function' ) {
|
3426
|
+
|
3427
|
+
if( el.readyState > 1 ) {
|
3428
|
+
startEmbeddedMedia( { target: el } );
|
3429
|
+
}
|
3430
|
+
else {
|
3431
|
+
el.removeEventListener( 'loadeddata', startEmbeddedMedia ); // remove first to avoid dupes
|
3432
|
+
el.addEventListener( 'loadeddata', startEmbeddedMedia );
|
3433
|
+
}
|
3434
|
+
|
2911
3435
|
}
|
2912
3436
|
} );
|
2913
3437
|
|
2914
3438
|
// Normal iframes
|
2915
|
-
toArray(
|
3439
|
+
toArray( element.querySelectorAll( 'iframe[src]' ) ).forEach( function( el ) {
|
3440
|
+
if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {
|
3441
|
+
return;
|
3442
|
+
}
|
3443
|
+
|
2916
3444
|
startEmbeddedIframe( { target: el } );
|
2917
3445
|
} );
|
2918
3446
|
|
2919
3447
|
// Lazy loading iframes
|
2920
|
-
toArray(
|
3448
|
+
toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
|
3449
|
+
if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {
|
3450
|
+
return;
|
3451
|
+
}
|
3452
|
+
|
2921
3453
|
if( el.getAttribute( 'src' ) !== el.getAttribute( 'data-src' ) ) {
|
2922
3454
|
el.removeEventListener( 'load', startEmbeddedIframe ); // remove first to avoid dupes
|
2923
3455
|
el.addEventListener( 'load', startEmbeddedIframe );
|
2924
3456
|
el.setAttribute( 'src', el.getAttribute( 'data-src' ) );
|
2925
3457
|
}
|
2926
3458
|
} );
|
3459
|
+
|
2927
3460
|
}
|
2928
3461
|
|
2929
3462
|
}
|
2930
3463
|
|
3464
|
+
/**
|
3465
|
+
* Starts playing an embedded video/audio element after
|
3466
|
+
* it has finished loading.
|
3467
|
+
*
|
3468
|
+
* @param {object} event
|
3469
|
+
*/
|
3470
|
+
function startEmbeddedMedia( event ) {
|
3471
|
+
|
3472
|
+
var isAttachedToDOM = !!closestParent( event.target, 'html' ),
|
3473
|
+
isVisible = !!closestParent( event.target, '.present' );
|
3474
|
+
|
3475
|
+
if( isAttachedToDOM && isVisible ) {
|
3476
|
+
event.target.currentTime = 0;
|
3477
|
+
event.target.play();
|
3478
|
+
}
|
3479
|
+
|
3480
|
+
event.target.removeEventListener( 'loadeddata', startEmbeddedMedia );
|
3481
|
+
|
3482
|
+
}
|
3483
|
+
|
2931
3484
|
/**
|
2932
3485
|
* "Starts" the content of an embedded iframe using the
|
2933
|
-
*
|
3486
|
+
* postMessage API.
|
3487
|
+
*
|
3488
|
+
* @param {object} event
|
2934
3489
|
*/
|
2935
3490
|
function startEmbeddedIframe( event ) {
|
2936
3491
|
|
2937
3492
|
var iframe = event.target;
|
2938
3493
|
|
2939
|
-
|
2940
|
-
|
2941
|
-
|
2942
|
-
|
2943
|
-
|
2944
|
-
|
2945
|
-
|
2946
|
-
|
2947
|
-
|
2948
|
-
|
2949
|
-
|
3494
|
+
if( iframe && iframe.contentWindow ) {
|
3495
|
+
|
3496
|
+
var isAttachedToDOM = !!closestParent( event.target, 'html' ),
|
3497
|
+
isVisible = !!closestParent( event.target, '.present' );
|
3498
|
+
|
3499
|
+
if( isAttachedToDOM && isVisible ) {
|
3500
|
+
|
3501
|
+
// Prefer an explicit global autoplay setting
|
3502
|
+
var autoplay = config.autoPlayMedia;
|
3503
|
+
|
3504
|
+
// If no global setting is available, fall back on the element's
|
3505
|
+
// own autoplay setting
|
3506
|
+
if( typeof autoplay !== 'boolean' ) {
|
3507
|
+
autoplay = iframe.hasAttribute( 'data-autoplay' ) || !!closestParent( iframe, '.slide-background' );
|
3508
|
+
}
|
3509
|
+
|
3510
|
+
// YouTube postMessage API
|
3511
|
+
if( /youtube\.com\/embed\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) {
|
3512
|
+
iframe.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
|
3513
|
+
}
|
3514
|
+
// Vimeo postMessage API
|
3515
|
+
else if( /player\.vimeo\.com\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) {
|
3516
|
+
iframe.contentWindow.postMessage( '{"method":"play"}', '*' );
|
3517
|
+
}
|
3518
|
+
// Generic postMessage API
|
3519
|
+
else {
|
3520
|
+
iframe.contentWindow.postMessage( 'slide:start', '*' );
|
3521
|
+
}
|
3522
|
+
|
3523
|
+
}
|
3524
|
+
|
2950
3525
|
}
|
2951
3526
|
|
2952
3527
|
}
|
@@ -2954,44 +3529,54 @@
|
|
2954
3529
|
/**
|
2955
3530
|
* Stop playback of any embedded content inside of
|
2956
3531
|
* the targeted slide.
|
3532
|
+
*
|
3533
|
+
* @param {HTMLElement} element
|
2957
3534
|
*/
|
2958
|
-
function stopEmbeddedContent(
|
3535
|
+
function stopEmbeddedContent( element, options ) {
|
3536
|
+
|
3537
|
+
options = extend( {
|
3538
|
+
// Defaults
|
3539
|
+
unloadIframes: true
|
3540
|
+
}, options || {} );
|
2959
3541
|
|
2960
|
-
if(
|
3542
|
+
if( element && element.parentNode ) {
|
2961
3543
|
// HTML5 media elements
|
2962
|
-
toArray(
|
3544
|
+
toArray( element.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
|
2963
3545
|
if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {
|
3546
|
+
el.setAttribute('data-paused-by-reveal', '');
|
2964
3547
|
el.pause();
|
2965
3548
|
}
|
2966
3549
|
} );
|
2967
3550
|
|
2968
3551
|
// Generic postMessage API for non-lazy loaded iframes
|
2969
|
-
toArray(
|
2970
|
-
el.contentWindow.postMessage( 'slide:stop', '*' );
|
3552
|
+
toArray( element.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
|
3553
|
+
if( el.contentWindow ) el.contentWindow.postMessage( 'slide:stop', '*' );
|
2971
3554
|
el.removeEventListener( 'load', startEmbeddedIframe );
|
2972
3555
|
});
|
2973
3556
|
|
2974
3557
|
// YouTube postMessage API
|
2975
|
-
toArray(
|
2976
|
-
if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
|
3558
|
+
toArray( element.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
|
3559
|
+
if( !el.hasAttribute( 'data-ignore' ) && el.contentWindow && typeof el.contentWindow.postMessage === 'function' ) {
|
2977
3560
|
el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
|
2978
3561
|
}
|
2979
3562
|
});
|
2980
3563
|
|
2981
3564
|
// Vimeo postMessage API
|
2982
|
-
toArray(
|
2983
|
-
if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
|
3565
|
+
toArray( element.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
|
3566
|
+
if( !el.hasAttribute( 'data-ignore' ) && el.contentWindow && typeof el.contentWindow.postMessage === 'function' ) {
|
2984
3567
|
el.contentWindow.postMessage( '{"method":"pause"}', '*' );
|
2985
3568
|
}
|
2986
3569
|
});
|
2987
3570
|
|
2988
|
-
|
2989
|
-
|
2990
|
-
|
2991
|
-
|
2992
|
-
|
2993
|
-
|
2994
|
-
|
3571
|
+
if( options.unloadIframes === true ) {
|
3572
|
+
// Unload lazy-loaded iframes
|
3573
|
+
toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
|
3574
|
+
// Only removing the src doesn't actually unload the frame
|
3575
|
+
// in all browsers (Firefox) so we set it to blank first
|
3576
|
+
el.setAttribute( 'src', 'about:blank' );
|
3577
|
+
el.removeAttribute( 'src' );
|
3578
|
+
} );
|
3579
|
+
}
|
2995
3580
|
}
|
2996
3581
|
|
2997
3582
|
}
|
@@ -2999,6 +3584,8 @@
|
|
2999
3584
|
/**
|
3000
3585
|
* Returns the number of past slides. This can be used as a global
|
3001
3586
|
* flattened index for slides.
|
3587
|
+
*
|
3588
|
+
* @return {number} Past slide count
|
3002
3589
|
*/
|
3003
3590
|
function getSlidePastCount() {
|
3004
3591
|
|
@@ -3043,6 +3630,8 @@
|
|
3043
3630
|
/**
|
3044
3631
|
* Returns a value ranging from 0-1 that represents
|
3045
3632
|
* how far into the presentation we have navigated.
|
3633
|
+
*
|
3634
|
+
* @return {number}
|
3046
3635
|
*/
|
3047
3636
|
function getProgress() {
|
3048
3637
|
|
@@ -3076,6 +3665,8 @@
|
|
3076
3665
|
/**
|
3077
3666
|
* Checks if this presentation is running inside of the
|
3078
3667
|
* speaker notes window.
|
3668
|
+
*
|
3669
|
+
* @return {boolean}
|
3079
3670
|
*/
|
3080
3671
|
function isSpeakerNotes() {
|
3081
3672
|
|
@@ -3131,7 +3722,7 @@
|
|
3131
3722
|
* Updates the page URL (hash) to reflect the current
|
3132
3723
|
* state.
|
3133
3724
|
*
|
3134
|
-
* @param {
|
3725
|
+
* @param {number} delay The time in ms to wait before
|
3135
3726
|
* writing the hash
|
3136
3727
|
*/
|
3137
3728
|
function writeURL( delay ) {
|
@@ -3151,7 +3742,6 @@
|
|
3151
3742
|
// Attempt to create a named link based on the slide's ID
|
3152
3743
|
var id = currentSlide.getAttribute( 'id' );
|
3153
3744
|
if( id ) {
|
3154
|
-
id = id.toLowerCase();
|
3155
3745
|
id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
|
3156
3746
|
}
|
3157
3747
|
|
@@ -3170,16 +3760,15 @@
|
|
3170
3760
|
}
|
3171
3761
|
|
3172
3762
|
}
|
3173
|
-
|
3174
3763
|
/**
|
3175
|
-
* Retrieves the h/v location of the current,
|
3176
|
-
* slide.
|
3764
|
+
* Retrieves the h/v location and fragment of the current,
|
3765
|
+
* or specified, slide.
|
3177
3766
|
*
|
3178
|
-
* @param {HTMLElement} slide If specified, the returned
|
3767
|
+
* @param {HTMLElement} [slide] If specified, the returned
|
3179
3768
|
* index will be for this slide rather than the currently
|
3180
3769
|
* active one
|
3181
3770
|
*
|
3182
|
-
* @return {
|
3771
|
+
* @return {{h: number, v: number, f: number}}
|
3183
3772
|
*/
|
3184
3773
|
function getIndices( slide ) {
|
3185
3774
|
|
@@ -3225,17 +3814,30 @@
|
|
3225
3814
|
|
3226
3815
|
}
|
3227
3816
|
|
3817
|
+
/**
|
3818
|
+
* Retrieves all slides in this presentation.
|
3819
|
+
*/
|
3820
|
+
function getSlides() {
|
3821
|
+
|
3822
|
+
return toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ));
|
3823
|
+
|
3824
|
+
}
|
3825
|
+
|
3228
3826
|
/**
|
3229
3827
|
* Retrieves the total number of slides in this presentation.
|
3828
|
+
*
|
3829
|
+
* @return {number}
|
3230
3830
|
*/
|
3231
3831
|
function getTotalSlides() {
|
3232
3832
|
|
3233
|
-
return
|
3833
|
+
return getSlides().length;
|
3234
3834
|
|
3235
3835
|
}
|
3236
3836
|
|
3237
3837
|
/**
|
3238
3838
|
* Returns the slide element matching the specified index.
|
3839
|
+
*
|
3840
|
+
* @return {HTMLElement}
|
3239
3841
|
*/
|
3240
3842
|
function getSlide( x, y ) {
|
3241
3843
|
|
@@ -3255,31 +3857,48 @@
|
|
3255
3857
|
* All slides, even the ones with no background properties
|
3256
3858
|
* defined, have a background element so as long as the
|
3257
3859
|
* index is valid an element will be returned.
|
3860
|
+
*
|
3861
|
+
* @param {number} x Horizontal background index
|
3862
|
+
* @param {number} y Vertical background index
|
3863
|
+
* @return {(HTMLElement[]|*)}
|
3258
3864
|
*/
|
3259
3865
|
function getSlideBackground( x, y ) {
|
3260
3866
|
|
3261
|
-
|
3262
|
-
|
3263
|
-
|
3264
|
-
var slide = getSlide( x, y );
|
3265
|
-
if( slide ) {
|
3266
|
-
var background = slide.querySelector( '.slide-background' );
|
3267
|
-
if( background && background.parentNode === slide ) {
|
3268
|
-
return background;
|
3269
|
-
}
|
3270
|
-
}
|
3271
|
-
|
3272
|
-
return undefined;
|
3867
|
+
var slide = getSlide( x, y );
|
3868
|
+
if( slide ) {
|
3869
|
+
return slide.slideBackgroundElement;
|
3273
3870
|
}
|
3274
3871
|
|
3275
|
-
|
3276
|
-
var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' );
|
3872
|
+
return undefined;
|
3277
3873
|
|
3278
|
-
|
3279
|
-
|
3874
|
+
}
|
3875
|
+
|
3876
|
+
/**
|
3877
|
+
* Retrieves the speaker notes from a slide. Notes can be
|
3878
|
+
* defined in two ways:
|
3879
|
+
* 1. As a data-notes attribute on the slide <section>
|
3880
|
+
* 2. As an <aside class="notes"> inside of the slide
|
3881
|
+
*
|
3882
|
+
* @param {HTMLElement} [slide=currentSlide]
|
3883
|
+
* @return {(string|null)}
|
3884
|
+
*/
|
3885
|
+
function getSlideNotes( slide ) {
|
3886
|
+
|
3887
|
+
// Default to the current slide
|
3888
|
+
slide = slide || currentSlide;
|
3889
|
+
|
3890
|
+
// Notes can be specified via the data-notes attribute...
|
3891
|
+
if( slide.hasAttribute( 'data-notes' ) ) {
|
3892
|
+
return slide.getAttribute( 'data-notes' );
|
3280
3893
|
}
|
3281
3894
|
|
3282
|
-
|
3895
|
+
// ... or using an <aside class="notes"> element
|
3896
|
+
var notesElement = slide.querySelector( 'aside.notes' );
|
3897
|
+
if( notesElement ) {
|
3898
|
+
return notesElement.innerHTML;
|
3899
|
+
}
|
3900
|
+
|
3901
|
+
return null;
|
3283
3902
|
|
3284
3903
|
}
|
3285
3904
|
|
@@ -3287,6 +3906,8 @@
|
|
3287
3906
|
* Retrieves the current state of the presentation as
|
3288
3907
|
* an object. This state can then be restored at any
|
3289
3908
|
* time.
|
3909
|
+
*
|
3910
|
+
* @return {{indexh: number, indexv: number, indexf: number, paused: boolean, overview: boolean}}
|
3290
3911
|
*/
|
3291
3912
|
function getState() {
|
3292
3913
|
|
@@ -3305,7 +3926,8 @@
|
|
3305
3926
|
/**
|
3306
3927
|
* Restores the presentation to the given state.
|
3307
3928
|
*
|
3308
|
-
* @param {
|
3929
|
+
* @param {object} state As generated by getState()
|
3930
|
+
* @see {@link getState} generates the parameter `state`
|
3309
3931
|
*/
|
3310
3932
|
function setState( state ) {
|
3311
3933
|
|
@@ -3339,6 +3961,9 @@
|
|
3339
3961
|
* attribute to each node if such an attribute is not already present,
|
3340
3962
|
* and sets that attribute to an integer value which is the position of
|
3341
3963
|
* the fragment within the fragments list.
|
3964
|
+
*
|
3965
|
+
* @param {object[]|*} fragments
|
3966
|
+
* @return {object[]} sorted Sorted array of fragments
|
3342
3967
|
*/
|
3343
3968
|
function sortFragments( fragments ) {
|
3344
3969
|
|
@@ -3390,12 +4015,12 @@
|
|
3390
4015
|
/**
|
3391
4016
|
* Navigate to the specified slide fragment.
|
3392
4017
|
*
|
3393
|
-
* @param {
|
4018
|
+
* @param {?number} index The index of the fragment that
|
3394
4019
|
* should be shown, -1 means all are invisible
|
3395
|
-
* @param {
|
4020
|
+
* @param {number} offset Integer offset to apply to the
|
3396
4021
|
* fragment index
|
3397
4022
|
*
|
3398
|
-
* @return {
|
4023
|
+
* @return {boolean} true if a change was made in any
|
3399
4024
|
* fragments visibility as part of this call
|
3400
4025
|
*/
|
3401
4026
|
function navigateFragment( index, offset ) {
|
@@ -3438,10 +4063,11 @@
|
|
3438
4063
|
element.classList.remove( 'current-fragment' );
|
3439
4064
|
|
3440
4065
|
// Announce the fragments one by one to the Screen Reader
|
3441
|
-
dom.statusDiv.textContent = element
|
4066
|
+
dom.statusDiv.textContent = getStatusText( element );
|
3442
4067
|
|
3443
4068
|
if( i === index ) {
|
3444
4069
|
element.classList.add( 'current-fragment' );
|
4070
|
+
startEmbeddedContent( element );
|
3445
4071
|
}
|
3446
4072
|
}
|
3447
4073
|
// Hidden fragments
|
@@ -3451,7 +4077,6 @@
|
|
3451
4077
|
element.classList.remove( 'current-fragment' );
|
3452
4078
|
}
|
3453
4079
|
|
3454
|
-
|
3455
4080
|
} );
|
3456
4081
|
|
3457
4082
|
if( fragmentsHidden.length ) {
|
@@ -3478,7 +4103,7 @@
|
|
3478
4103
|
/**
|
3479
4104
|
* Navigate to the next slide fragment.
|
3480
4105
|
*
|
3481
|
-
* @return {
|
4106
|
+
* @return {boolean} true if there was a next fragment,
|
3482
4107
|
* false otherwise
|
3483
4108
|
*/
|
3484
4109
|
function nextFragment() {
|
@@ -3490,7 +4115,7 @@
|
|
3490
4115
|
/**
|
3491
4116
|
* Navigate to the previous slide fragment.
|
3492
4117
|
*
|
3493
|
-
* @return {
|
4118
|
+
* @return {boolean} true if there was a previous fragment,
|
3494
4119
|
* false otherwise
|
3495
4120
|
*/
|
3496
4121
|
function previousFragment() {
|
@@ -3506,11 +4131,15 @@
|
|
3506
4131
|
|
3507
4132
|
cancelAutoSlide();
|
3508
4133
|
|
3509
|
-
if( currentSlide ) {
|
4134
|
+
if( currentSlide && config.autoSlide !== false ) {
|
3510
4135
|
|
3511
|
-
var
|
4136
|
+
var fragment = currentSlide.querySelector( '.current-fragment' );
|
3512
4137
|
|
3513
|
-
|
4138
|
+
// When the slide first appears there is no "current" fragment so
|
4139
|
+
// we look for a data-autoslide timing on the first fragment
|
4140
|
+
if( !fragment ) fragment = currentSlide.querySelector( '.fragment' );
|
4141
|
+
|
4142
|
+
var fragmentAutoSlide = fragment ? fragment.getAttribute( 'data-autoslide' ) : null;
|
3514
4143
|
var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
|
3515
4144
|
var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
|
3516
4145
|
|
@@ -3536,11 +4165,12 @@
|
|
3536
4165
|
// automatically set the autoSlide duration to the
|
3537
4166
|
// length of that media. Not applicable if the slide
|
3538
4167
|
// is divided up into fragments.
|
4168
|
+
// playbackRate is accounted for in the duration.
|
3539
4169
|
if( currentSlide.querySelectorAll( '.fragment' ).length === 0 ) {
|
3540
4170
|
toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
|
3541
4171
|
if( el.hasAttribute( 'data-autoplay' ) ) {
|
3542
|
-
if( autoSlide && el.duration * 1000 > autoSlide ) {
|
3543
|
-
autoSlide = ( el.duration * 1000 ) + 1000;
|
4172
|
+
if( autoSlide && (el.duration * 1000 / el.playbackRate ) > autoSlide ) {
|
4173
|
+
autoSlide = ( el.duration * 1000 / el.playbackRate ) + 1000;
|
3544
4174
|
}
|
3545
4175
|
}
|
3546
4176
|
} );
|
@@ -3553,7 +4183,10 @@
|
|
3553
4183
|
// - The overview isn't active
|
3554
4184
|
// - The presentation isn't over
|
3555
4185
|
if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || availableFragments().next || config.loop === true ) ) {
|
3556
|
-
autoSlideTimeout = setTimeout(
|
4186
|
+
autoSlideTimeout = setTimeout( function() {
|
4187
|
+
typeof config.autoSlideMethod === 'function' ? config.autoSlideMethod() : navigateNext();
|
4188
|
+
cueAutoSlide();
|
4189
|
+
}, autoSlide );
|
3557
4190
|
autoSlideStartTime = Date.now();
|
3558
4191
|
}
|
3559
4192
|
|
@@ -3616,6 +4249,8 @@
|
|
3616
4249
|
|
3617
4250
|
function navigateRight() {
|
3618
4251
|
|
4252
|
+
hasNavigatedRight = true;
|
4253
|
+
|
3619
4254
|
// Reverse for RTL
|
3620
4255
|
if( config.rtl ) {
|
3621
4256
|
if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {
|
@@ -3640,6 +4275,8 @@
|
|
3640
4275
|
|
3641
4276
|
function navigateDown() {
|
3642
4277
|
|
4278
|
+
hasNavigatedDown = true;
|
4279
|
+
|
3643
4280
|
// Prioritize revealing fragments
|
3644
4281
|
if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) {
|
3645
4282
|
slide( indexh, indexv + 1 );
|
@@ -3686,6 +4323,9 @@
|
|
3686
4323
|
*/
|
3687
4324
|
function navigateNext() {
|
3688
4325
|
|
4326
|
+
hasNavigatedRight = true;
|
4327
|
+
hasNavigatedDown = true;
|
4328
|
+
|
3689
4329
|
// Prioritize revealing fragments
|
3690
4330
|
if( nextFragment() === false ) {
|
3691
4331
|
if( availableRoutes().down ) {
|
@@ -3699,9 +4339,20 @@
|
|
3699
4339
|
}
|
3700
4340
|
}
|
3701
4341
|
|
3702
|
-
|
3703
|
-
|
3704
|
-
|
4342
|
+
}
|
4343
|
+
|
4344
|
+
/**
|
4345
|
+
* Checks if the target element prevents the triggering of
|
4346
|
+
* swipe navigation.
|
4347
|
+
*/
|
4348
|
+
function isSwipePrevented( target ) {
|
4349
|
+
|
4350
|
+
while( target && typeof target.hasAttribute === 'function' ) {
|
4351
|
+
if( target.hasAttribute( 'data-prevent-swipe' ) ) return true;
|
4352
|
+
target = target.parentNode;
|
4353
|
+
}
|
4354
|
+
|
4355
|
+
return false;
|
3705
4356
|
|
3706
4357
|
}
|
3707
4358
|
|
@@ -3713,6 +4364,8 @@
|
|
3713
4364
|
/**
|
3714
4365
|
* Called by all event handlers that are based on user
|
3715
4366
|
* input.
|
4367
|
+
*
|
4368
|
+
* @param {object} [event]
|
3716
4369
|
*/
|
3717
4370
|
function onUserInput( event ) {
|
3718
4371
|
|
@@ -3724,23 +4377,22 @@
|
|
3724
4377
|
|
3725
4378
|
/**
|
3726
4379
|
* Handler for the document level 'keypress' event.
|
4380
|
+
*
|
4381
|
+
* @param {object} event
|
3727
4382
|
*/
|
3728
4383
|
function onDocumentKeyPress( event ) {
|
3729
4384
|
|
3730
4385
|
// Check if the pressed key is question mark
|
3731
4386
|
if( event.shiftKey && event.charCode === 63 ) {
|
3732
|
-
|
3733
|
-
closeOverlay();
|
3734
|
-
}
|
3735
|
-
else {
|
3736
|
-
showHelp( true );
|
3737
|
-
}
|
4387
|
+
toggleHelp();
|
3738
4388
|
}
|
3739
4389
|
|
3740
4390
|
}
|
3741
4391
|
|
3742
4392
|
/**
|
3743
4393
|
* Handler for the document level 'keydown' event.
|
4394
|
+
*
|
4395
|
+
* @param {object} event
|
3744
4396
|
*/
|
3745
4397
|
function onDocumentKeyDown( event ) {
|
3746
4398
|
|
@@ -3759,13 +4411,26 @@
|
|
3759
4411
|
// the keyboard
|
3760
4412
|
var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';
|
3761
4413
|
var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
|
4414
|
+
var activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className);
|
3762
4415
|
|
3763
4416
|
// Disregard the event if there's a focused element or a
|
3764
4417
|
// keyboard modifier key is present
|
3765
|
-
if( activeElementIsCE || activeElementIsInput || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
|
4418
|
+
if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
|
4419
|
+
|
4420
|
+
// While paused only allow resume keyboard events; 'b', 'v', '.'
|
4421
|
+
var resumeKeyCodes = [66,86,190,191];
|
4422
|
+
var key;
|
3766
4423
|
|
3767
|
-
//
|
3768
|
-
if(
|
4424
|
+
// Custom key bindings for togglePause should be able to resume
|
4425
|
+
if( typeof config.keyboard === 'object' ) {
|
4426
|
+
for( key in config.keyboard ) {
|
4427
|
+
if( config.keyboard[key] === 'togglePause' ) {
|
4428
|
+
resumeKeyCodes.push( parseInt( key, 10 ) );
|
4429
|
+
}
|
4430
|
+
}
|
4431
|
+
}
|
4432
|
+
|
4433
|
+
if( isPaused() && resumeKeyCodes.indexOf( event.keyCode ) === -1 ) {
|
3769
4434
|
return false;
|
3770
4435
|
}
|
3771
4436
|
|
@@ -3774,7 +4439,7 @@
|
|
3774
4439
|
// 1. User defined key bindings
|
3775
4440
|
if( typeof config.keyboard === 'object' ) {
|
3776
4441
|
|
3777
|
-
for(
|
4442
|
+
for( key in config.keyboard ) {
|
3778
4443
|
|
3779
4444
|
// Check if this binding matches the pressed key
|
3780
4445
|
if( parseInt( key, 10 ) === event.keyCode ) {
|
@@ -3825,8 +4490,8 @@
|
|
3825
4490
|
case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
|
3826
4491
|
// return
|
3827
4492
|
case 13: isOverview() ? deactivateOverview() : triggered = false; break;
|
3828
|
-
// two-spot, semicolon, b, period, Logitech presenter tools "black screen" button
|
3829
|
-
case 58: case 59: case 66: case 190: case 191: togglePause(); break;
|
4493
|
+
// two-spot, semicolon, b, v, period, Logitech presenter tools "black screen" button
|
4494
|
+
case 58: case 59: case 66: case 86: case 190: case 191: togglePause(); break;
|
3830
4495
|
// f
|
3831
4496
|
case 70: enterFullscreen(); break;
|
3832
4497
|
// a
|
@@ -3863,9 +4528,13 @@
|
|
3863
4528
|
/**
|
3864
4529
|
* Handler for the 'touchstart' event, enables support for
|
3865
4530
|
* swipe and pinch gestures.
|
4531
|
+
*
|
4532
|
+
* @param {object} event
|
3866
4533
|
*/
|
3867
4534
|
function onTouchStart( event ) {
|
3868
4535
|
|
4536
|
+
if( isSwipePrevented( event.target ) ) return true;
|
4537
|
+
|
3869
4538
|
touch.startX = event.touches[0].clientX;
|
3870
4539
|
touch.startY = event.touches[0].clientY;
|
3871
4540
|
touch.startCount = event.touches.length;
|
@@ -3886,9 +4555,13 @@
|
|
3886
4555
|
|
3887
4556
|
/**
|
3888
4557
|
* Handler for the 'touchmove' event.
|
4558
|
+
*
|
4559
|
+
* @param {object} event
|
3889
4560
|
*/
|
3890
4561
|
function onTouchMove( event ) {
|
3891
4562
|
|
4563
|
+
if( isSwipePrevented( event.target ) ) return true;
|
4564
|
+
|
3892
4565
|
// Each touch should only trigger one action
|
3893
4566
|
if( !touch.captured ) {
|
3894
4567
|
onUserInput( event );
|
@@ -3965,7 +4638,7 @@
|
|
3965
4638
|
}
|
3966
4639
|
// There's a bug with swiping on some Android devices unless
|
3967
4640
|
// the default action is always prevented
|
3968
|
-
else if(
|
4641
|
+
else if( UA.match( /android/gi ) ) {
|
3969
4642
|
event.preventDefault();
|
3970
4643
|
}
|
3971
4644
|
|
@@ -3973,6 +4646,8 @@
|
|
3973
4646
|
|
3974
4647
|
/**
|
3975
4648
|
* Handler for the 'touchend' event.
|
4649
|
+
*
|
4650
|
+
* @param {object} event
|
3976
4651
|
*/
|
3977
4652
|
function onTouchEnd( event ) {
|
3978
4653
|
|
@@ -3982,6 +4657,8 @@
|
|
3982
4657
|
|
3983
4658
|
/**
|
3984
4659
|
* Convert pointer down to touch start.
|
4660
|
+
*
|
4661
|
+
* @param {object} event
|
3985
4662
|
*/
|
3986
4663
|
function onPointerDown( event ) {
|
3987
4664
|
|
@@ -3994,6 +4671,8 @@
|
|
3994
4671
|
|
3995
4672
|
/**
|
3996
4673
|
* Convert pointer move to touch move.
|
4674
|
+
*
|
4675
|
+
* @param {object} event
|
3997
4676
|
*/
|
3998
4677
|
function onPointerMove( event ) {
|
3999
4678
|
|
@@ -4006,6 +4685,8 @@
|
|
4006
4685
|
|
4007
4686
|
/**
|
4008
4687
|
* Convert pointer up to touch end.
|
4688
|
+
*
|
4689
|
+
* @param {object} event
|
4009
4690
|
*/
|
4010
4691
|
function onPointerUp( event ) {
|
4011
4692
|
|
@@ -4019,6 +4700,8 @@
|
|
4019
4700
|
/**
|
4020
4701
|
* Handles mouse wheel scrolling, throttled to avoid skipping
|
4021
4702
|
* multiple slides.
|
4703
|
+
*
|
4704
|
+
* @param {object} event
|
4022
4705
|
*/
|
4023
4706
|
function onDocumentMouseScroll( event ) {
|
4024
4707
|
|
@@ -4030,7 +4713,7 @@
|
|
4030
4713
|
if( delta > 0 ) {
|
4031
4714
|
navigateNext();
|
4032
4715
|
}
|
4033
|
-
else {
|
4716
|
+
else if( delta < 0 ) {
|
4034
4717
|
navigatePrev();
|
4035
4718
|
}
|
4036
4719
|
|
@@ -4043,6 +4726,8 @@
|
|
4043
4726
|
* closest approximate horizontal slide using this equation:
|
4044
4727
|
*
|
4045
4728
|
* ( clickX / presentationWidth ) * numberOfSlides
|
4729
|
+
*
|
4730
|
+
* @param {object} event
|
4046
4731
|
*/
|
4047
4732
|
function onProgressClicked( event ) {
|
4048
4733
|
|
@@ -4073,6 +4758,8 @@
|
|
4073
4758
|
|
4074
4759
|
/**
|
4075
4760
|
* Handler for the window level 'hashchange' event.
|
4761
|
+
*
|
4762
|
+
* @param {object} [event]
|
4076
4763
|
*/
|
4077
4764
|
function onWindowHashChange( event ) {
|
4078
4765
|
|
@@ -4082,6 +4769,8 @@
|
|
4082
4769
|
|
4083
4770
|
/**
|
4084
4771
|
* Handler for the window level 'resize' event.
|
4772
|
+
*
|
4773
|
+
* @param {object} [event]
|
4085
4774
|
*/
|
4086
4775
|
function onWindowResize( event ) {
|
4087
4776
|
|
@@ -4091,6 +4780,8 @@
|
|
4091
4780
|
|
4092
4781
|
/**
|
4093
4782
|
* Handle for the window level 'visibilitychange' event.
|
4783
|
+
*
|
4784
|
+
* @param {object} [event]
|
4094
4785
|
*/
|
4095
4786
|
function onPageVisibilityChange( event ) {
|
4096
4787
|
|
@@ -4112,6 +4803,8 @@
|
|
4112
4803
|
|
4113
4804
|
/**
|
4114
4805
|
* Invoked when a slide is and we're in the overview.
|
4806
|
+
*
|
4807
|
+
* @param {object} event
|
4115
4808
|
*/
|
4116
4809
|
function onOverviewSlideClicked( event ) {
|
4117
4810
|
|
@@ -4145,6 +4838,8 @@
|
|
4145
4838
|
/**
|
4146
4839
|
* Handles clicks on links that are set to preview in the
|
4147
4840
|
* iframe overlay.
|
4841
|
+
*
|
4842
|
+
* @param {object} event
|
4148
4843
|
*/
|
4149
4844
|
function onPreviewLinkClicked( event ) {
|
4150
4845
|
|
@@ -4160,6 +4855,8 @@
|
|
4160
4855
|
|
4161
4856
|
/**
|
4162
4857
|
* Handles click on the auto-sliding controls element.
|
4858
|
+
*
|
4859
|
+
* @param {object} [event]
|
4163
4860
|
*/
|
4164
4861
|
function onAutoSlidePlayerClick( event ) {
|
4165
4862
|
|
@@ -4191,15 +4888,16 @@
|
|
4191
4888
|
*
|
4192
4889
|
* @param {HTMLElement} container The component will append
|
4193
4890
|
* itself to this
|
4194
|
-
* @param {
|
4891
|
+
* @param {function} progressCheck A method which will be
|
4195
4892
|
* called frequently to get the current progress on a range
|
4196
4893
|
* of 0-1
|
4197
4894
|
*/
|
4198
4895
|
function Playback( container, progressCheck ) {
|
4199
4896
|
|
4200
4897
|
// Cosmetics
|
4201
|
-
this.diameter =
|
4202
|
-
this.
|
4898
|
+
this.diameter = 100;
|
4899
|
+
this.diameter2 = this.diameter/2;
|
4900
|
+
this.thickness = 6;
|
4203
4901
|
|
4204
4902
|
// Flags if we are currently playing
|
4205
4903
|
this.playing = false;
|
@@ -4217,6 +4915,8 @@
|
|
4217
4915
|
this.canvas.className = 'playback';
|
4218
4916
|
this.canvas.width = this.diameter;
|
4219
4917
|
this.canvas.height = this.diameter;
|
4918
|
+
this.canvas.style.width = this.diameter2 + 'px';
|
4919
|
+
this.canvas.style.height = this.diameter2 + 'px';
|
4220
4920
|
this.context = this.canvas.getContext( '2d' );
|
4221
4921
|
|
4222
4922
|
this.container.appendChild( this.canvas );
|
@@ -4225,6 +4925,9 @@
|
|
4225
4925
|
|
4226
4926
|
}
|
4227
4927
|
|
4928
|
+
/**
|
4929
|
+
* @param value
|
4930
|
+
*/
|
4228
4931
|
Playback.prototype.setPlaying = function( value ) {
|
4229
4932
|
|
4230
4933
|
var wasPlaying = this.playing;
|
@@ -4267,10 +4970,10 @@
|
|
4267
4970
|
Playback.prototype.render = function() {
|
4268
4971
|
|
4269
4972
|
var progress = this.playing ? this.progress : 0,
|
4270
|
-
radius = ( this.
|
4271
|
-
x = this.
|
4272
|
-
y = this.
|
4273
|
-
iconSize =
|
4973
|
+
radius = ( this.diameter2 ) - this.thickness,
|
4974
|
+
x = this.diameter2,
|
4975
|
+
y = this.diameter2,
|
4976
|
+
iconSize = 28;
|
4274
4977
|
|
4275
4978
|
// Ease towards 1
|
4276
4979
|
this.progressOffset += ( 1 - this.progressOffset ) * 0.1;
|
@@ -4283,7 +4986,7 @@
|
|
4283
4986
|
|
4284
4987
|
// Solid background color
|
4285
4988
|
this.context.beginPath();
|
4286
|
-
this.context.arc( x, y, radius +
|
4989
|
+
this.context.arc( x, y, radius + 4, 0, Math.PI * 2, false );
|
4287
4990
|
this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )';
|
4288
4991
|
this.context.fill();
|
4289
4992
|
|
@@ -4291,7 +4994,7 @@
|
|
4291
4994
|
this.context.beginPath();
|
4292
4995
|
this.context.arc( x, y, radius, 0, Math.PI * 2, false );
|
4293
4996
|
this.context.lineWidth = this.thickness;
|
4294
|
-
this.context.strokeStyle = '
|
4997
|
+
this.context.strokeStyle = 'rgba( 255, 255, 255, 0.2 )';
|
4295
4998
|
this.context.stroke();
|
4296
4999
|
|
4297
5000
|
if( this.playing ) {
|
@@ -4308,14 +5011,14 @@
|
|
4308
5011
|
// Draw play/pause icons
|
4309
5012
|
if( this.playing ) {
|
4310
5013
|
this.context.fillStyle = '#fff';
|
4311
|
-
this.context.fillRect( 0, 0, iconSize / 2 -
|
4312
|
-
this.context.fillRect( iconSize / 2 +
|
5014
|
+
this.context.fillRect( 0, 0, iconSize / 2 - 4, iconSize );
|
5015
|
+
this.context.fillRect( iconSize / 2 + 4, 0, iconSize / 2 - 4, iconSize );
|
4313
5016
|
}
|
4314
5017
|
else {
|
4315
5018
|
this.context.beginPath();
|
4316
|
-
this.context.translate(
|
5019
|
+
this.context.translate( 4, 0 );
|
4317
5020
|
this.context.moveTo( 0, 0 );
|
4318
|
-
this.context.lineTo( iconSize -
|
5021
|
+
this.context.lineTo( iconSize - 4, iconSize / 2 );
|
4319
5022
|
this.context.lineTo( 0, iconSize );
|
4320
5023
|
this.context.fillStyle = '#fff';
|
4321
5024
|
this.context.fill();
|
@@ -4350,6 +5053,8 @@
|
|
4350
5053
|
|
4351
5054
|
|
4352
5055
|
Reveal = {
|
5056
|
+
VERSION: VERSION,
|
5057
|
+
|
4353
5058
|
initialize: initialize,
|
4354
5059
|
configure: configure,
|
4355
5060
|
sync: sync,
|
@@ -4380,12 +5085,18 @@
|
|
4380
5085
|
// Forces an update in slide layout
|
4381
5086
|
layout: layout,
|
4382
5087
|
|
5088
|
+
// Randomizes the order of slides
|
5089
|
+
shuffle: shuffle,
|
5090
|
+
|
4383
5091
|
// Returns an object with the available routes as booleans (left/right/top/bottom)
|
4384
5092
|
availableRoutes: availableRoutes,
|
4385
5093
|
|
4386
5094
|
// Returns an object with the available fragments as booleans (prev/next)
|
4387
5095
|
availableFragments: availableFragments,
|
4388
5096
|
|
5097
|
+
// Toggles a help overlay with keyboard shortcuts
|
5098
|
+
toggleHelp: toggleHelp,
|
5099
|
+
|
4389
5100
|
// Toggles the overview mode on/off
|
4390
5101
|
toggleOverview: toggleOverview,
|
4391
5102
|
|
@@ -4399,6 +5110,11 @@
|
|
4399
5110
|
isOverview: isOverview,
|
4400
5111
|
isPaused: isPaused,
|
4401
5112
|
isAutoSliding: isAutoSliding,
|
5113
|
+
isSpeakerNotes: isSpeakerNotes,
|
5114
|
+
|
5115
|
+
// Slide preloading
|
5116
|
+
loadSlide: loadSlide,
|
5117
|
+
unloadSlide: unloadSlide,
|
4402
5118
|
|
4403
5119
|
// Adds or removes all internal event listeners (such as keyboard)
|
4404
5120
|
addEventListeners: addEventListeners,
|
@@ -4408,12 +5124,19 @@
|
|
4408
5124
|
getState: getState,
|
4409
5125
|
setState: setState,
|
4410
5126
|
|
5127
|
+
// Presentation progress
|
5128
|
+
getSlidePastCount: getSlidePastCount,
|
5129
|
+
|
4411
5130
|
// Presentation progress on range of 0-1
|
4412
5131
|
getProgress: getProgress,
|
4413
5132
|
|
4414
5133
|
// Returns the indices of the current, or specified, slide
|
4415
5134
|
getIndices: getIndices,
|
4416
5135
|
|
5136
|
+
// Returns an Array of all slides
|
5137
|
+
getSlides: getSlides,
|
5138
|
+
|
5139
|
+
// Returns the total number of slides
|
4417
5140
|
getTotalSlides: getTotalSlides,
|
4418
5141
|
|
4419
5142
|
// Returns the slide element at the specified index
|
@@ -4422,6 +5145,9 @@
|
|
4422
5145
|
// Returns the slide background element at the specified index
|
4423
5146
|
getSlideBackground: getSlideBackground,
|
4424
5147
|
|
5148
|
+
// Returns the speaker notes string for a slide, or null
|
5149
|
+
getSlideNotes: getSlideNotes,
|
5150
|
+
|
4425
5151
|
// Returns the previous slide element, may be null
|
4426
5152
|
getPreviousSlide: function() {
|
4427
5153
|
return previousSlide;
|
@@ -4500,6 +5226,11 @@
|
|
4500
5226
|
// Programatically triggers a keyboard event
|
4501
5227
|
triggerKey: function( keyCode ) {
|
4502
5228
|
onDocumentKeyDown( { keyCode: keyCode } );
|
5229
|
+
},
|
5230
|
+
|
5231
|
+
// Registers a new shortcut to include in the help overlay
|
5232
|
+
registerKeyboardShortcut: function( key, value ) {
|
5233
|
+
keyboardShortcuts[key] = value;
|
4503
5234
|
}
|
4504
5235
|
};
|
4505
5236
|
|