diff --git a/openlp/core/display/html/reveal.css b/openlp/core/display/html/reveal.css index 5f501b12c..eda311e8d 100644 --- a/openlp/core/display/html/reveal.css +++ b/openlp/core/display/html/reveal.css @@ -1,9 +1,9 @@ /*! * reveal.js - * http://lab.hakim.se/reveal-js + * http://revealjs.com * MIT licensed * - * Copyright (C) 2017 Hakim El Hattab, http://hakim.se + * Copyright (C) 2018 Hakim El Hattab, http://hakim.se */ /********************************************* * RESET STYLES @@ -53,8 +53,7 @@ body { .reveal .slides section .fragment { opacity: 0; visibility: hidden; - -webkit-transition: all .2s ease; - transition: all .2s ease; } + transition: all .2s ease; } .reveal .slides section .fragment.visible { opacity: 1; visibility: inherit; } @@ -128,13 +127,25 @@ body { -webkit-transform: translate(0, 0); transform: translate(0, 0); } +.reveal .slides section .fragment.fade-in-then-out, .reveal .slides section .fragment.current-visible { opacity: 0; visibility: hidden; } + .reveal .slides section .fragment.fade-in-then-out.current-fragment, .reveal .slides section .fragment.current-visible.current-fragment { opacity: 1; visibility: inherit; } +.reveal .slides section .fragment.fade-in-then-semi-out { + opacity: 0; + visibility: hidden; } + .reveal .slides section .fragment.fade-in-then-semi-out.visible { + opacity: 0.5; + visibility: inherit; } + .reveal .slides section .fragment.fade-in-then-semi-out.current-fragment { + opacity: 1; + visibility: inherit; } + .reveal .slides section .fragment.highlight-red, .reveal .slides section .fragment.highlight-current-red, .reveal .slides section .fragment.highlight-green, @@ -189,106 +200,267 @@ body { /********************************************* * CONTROLS *********************************************/ +@-webkit-keyframes bounce-right { + 0%, 10%, 25%, 40%, 50% { + -webkit-transform: translateX(0); + transform: translateX(0); } + 20% { + -webkit-transform: translateX(10px); + transform: translateX(10px); } + 30% { + -webkit-transform: translateX(-5px); + transform: translateX(-5px); } } +@keyframes bounce-right { + 0%, 10%, 25%, 40%, 50% { + -webkit-transform: translateX(0); + transform: translateX(0); } + 20% { + -webkit-transform: translateX(10px); + transform: translateX(10px); } + 30% { + -webkit-transform: translateX(-5px); + transform: translateX(-5px); } } + +@-webkit-keyframes bounce-down { + 0%, 10%, 25%, 40%, 50% { + -webkit-transform: translateY(0); + transform: translateY(0); } + 20% { + -webkit-transform: translateY(10px); + transform: translateY(10px); } + 30% { + -webkit-transform: translateY(-5px); + transform: translateY(-5px); } } + +@keyframes bounce-down { + 0%, 10%, 25%, 40%, 50% { + -webkit-transform: translateY(0); + transform: translateY(0); } + 20% { + -webkit-transform: translateY(10px); + transform: translateY(10px); } + 30% { + -webkit-transform: translateY(-5px); + transform: translateY(-5px); } } + .reveal .controls { display: none; - position: fixed; - width: 110px; - height: 110px; - z-index: 30; - right: 10px; - bottom: 10px; - -webkit-user-select: none; } - -.reveal .controls button { - padding: 0; position: absolute; - opacity: 0.05; - width: 0; - height: 0; - background-color: transparent; - border: 12px solid transparent; - -webkit-transform: scale(0.9999); - transform: scale(0.9999); - -webkit-transition: all 0.2s ease; - transition: all 0.2s ease; - -webkit-appearance: none; - -webkit-tap-highlight-color: transparent; } + top: auto; + bottom: 12px; + right: 12px; + left: auto; + z-index: 1; + color: #000; + pointer-events: none; + font-size: 10px; } + .reveal .controls button { + position: absolute; + padding: 0; + background-color: transparent; + border: 0; + outline: 0; + cursor: pointer; + color: currentColor; + -webkit-transform: scale(0.9999); + transform: scale(0.9999); + transition: color 0.2s ease, opacity 0.2s ease, -webkit-transform 0.2s ease; + transition: color 0.2s ease, opacity 0.2s ease, transform 0.2s ease; + z-index: 2; + pointer-events: auto; + font-size: inherit; + visibility: hidden; + opacity: 0; + -webkit-appearance: none; + -webkit-tap-highlight-color: transparent; } + .reveal .controls .controls-arrow:before, + .reveal .controls .controls-arrow:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 2.6em; + height: 0.5em; + border-radius: 0.25em; + background-color: currentColor; + transition: all 0.15s ease, background-color 0.8s ease; + -webkit-transform-origin: 0.2em 50%; + transform-origin: 0.2em 50%; + will-change: transform; } + .reveal .controls .controls-arrow { + position: relative; + width: 3.6em; + height: 3.6em; } + .reveal .controls .controls-arrow:before { + -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(45deg); + transform: translateX(0.5em) translateY(1.55em) rotate(45deg); } + .reveal .controls .controls-arrow:after { + -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-45deg); + transform: translateX(0.5em) translateY(1.55em) rotate(-45deg); } + .reveal .controls .controls-arrow:hover:before { + -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(40deg); + transform: translateX(0.5em) translateY(1.55em) rotate(40deg); } + .reveal .controls .controls-arrow:hover:after { + -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-40deg); + transform: translateX(0.5em) translateY(1.55em) rotate(-40deg); } + .reveal .controls .controls-arrow:active:before { + -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(36deg); + transform: translateX(0.5em) translateY(1.55em) rotate(36deg); } + .reveal .controls .controls-arrow:active:after { + -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-36deg); + transform: translateX(0.5em) translateY(1.55em) rotate(-36deg); } + .reveal .controls .navigate-left { + right: 6.4em; + bottom: 3.2em; + -webkit-transform: translateX(-10px); + transform: translateX(-10px); } + .reveal .controls .navigate-right { + right: 0; + bottom: 3.2em; + -webkit-transform: translateX(10px); + transform: translateX(10px); } + .reveal .controls .navigate-right .controls-arrow { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); } + .reveal .controls .navigate-right.highlight { + -webkit-animation: bounce-right 2s 50 both ease-out; + animation: bounce-right 2s 50 both ease-out; } + .reveal .controls .navigate-up { + right: 3.2em; + bottom: 6.4em; + -webkit-transform: translateY(-10px); + transform: translateY(-10px); } + .reveal .controls .navigate-up .controls-arrow { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); } + .reveal .controls .navigate-down { + right: 3.2em; + bottom: 0; + -webkit-transform: translateY(10px); + transform: translateY(10px); } + .reveal .controls .navigate-down .controls-arrow { + -webkit-transform: rotate(-90deg); + transform: rotate(-90deg); } + .reveal .controls .navigate-down.highlight { + -webkit-animation: bounce-down 2s 50 both ease-out; + animation: bounce-down 2s 50 both ease-out; } + .reveal .controls[data-controls-back-arrows="faded"] .navigate-left.enabled, + .reveal .controls[data-controls-back-arrows="faded"] .navigate-up.enabled { + opacity: 0.3; } + .reveal .controls[data-controls-back-arrows="faded"] .navigate-left.enabled:hover, + .reveal .controls[data-controls-back-arrows="faded"] .navigate-up.enabled:hover { + opacity: 1; } + .reveal .controls[data-controls-back-arrows="hidden"] .navigate-left.enabled, + .reveal .controls[data-controls-back-arrows="hidden"] .navigate-up.enabled { + opacity: 0; + visibility: hidden; } + .reveal .controls .enabled { + visibility: visible; + opacity: 0.9; + cursor: pointer; + -webkit-transform: none; + transform: none; } + .reveal .controls .enabled.fragmented { + opacity: 0.5; } + .reveal .controls .enabled:hover, + .reveal .controls .enabled.fragmented:hover { + opacity: 1; } -.reveal .controls .enabled { - opacity: 0.7; - cursor: pointer; } +.reveal:not(.has-vertical-slides) .controls .navigate-left { + bottom: 1.4em; + right: 5.5em; } -.reveal .controls .enabled:active { - margin-top: 1px; } +.reveal:not(.has-vertical-slides) .controls .navigate-right { + bottom: 1.4em; + right: 0.5em; } -.reveal .controls .navigate-left { - top: 42px; - border-right-width: 22px; - border-right-color: #000; } +.reveal:not(.has-horizontal-slides) .controls .navigate-up { + right: 1.4em; + bottom: 5em; } -.reveal .controls .navigate-left.fragmented { - opacity: 0.3; } +.reveal:not(.has-horizontal-slides) .controls .navigate-down { + right: 1.4em; + bottom: 0.5em; } -.reveal .controls .navigate-right { - left: 74px; - top: 42px; - border-left-width: 22px; - border-left-color: #000; } +.reveal.has-dark-background .controls { + color: #fff; } -.reveal .controls .navigate-right.fragmented { - opacity: 0.3; } +.reveal.has-light-background .controls { + color: #000; } -.reveal .controls .navigate-up { - left: 42px; - border-bottom-width: 22px; - border-bottom-color: #000; } +.reveal.no-hover .controls .controls-arrow:hover:before, +.reveal.no-hover .controls .controls-arrow:active:before { + -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(45deg); + transform: translateX(0.5em) translateY(1.55em) rotate(45deg); } -.reveal .controls .navigate-up.fragmented { - opacity: 0.3; } +.reveal.no-hover .controls .controls-arrow:hover:after, +.reveal.no-hover .controls .controls-arrow:active:after { + -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-45deg); + transform: translateX(0.5em) translateY(1.55em) rotate(-45deg); } -.reveal .controls .navigate-down { - left: 42px; - top: 74px; - border-top-width: 22px; - border-top-color: #000; } - -.reveal .controls .navigate-down.fragmented { - opacity: 0.3; } +@media screen and (min-width: 500px) { + .reveal .controls[data-controls-layout="edges"] { + top: 0; + right: 0; + bottom: 0; + left: 0; } + .reveal .controls[data-controls-layout="edges"] .navigate-left, + .reveal .controls[data-controls-layout="edges"] .navigate-right, + .reveal .controls[data-controls-layout="edges"] .navigate-up, + .reveal .controls[data-controls-layout="edges"] .navigate-down { + bottom: auto; + right: auto; } + .reveal .controls[data-controls-layout="edges"] .navigate-left { + top: 50%; + left: 8px; + margin-top: -1.8em; } + .reveal .controls[data-controls-layout="edges"] .navigate-right { + top: 50%; + right: 8px; + margin-top: -1.8em; } + .reveal .controls[data-controls-layout="edges"] .navigate-up { + top: 8px; + left: 50%; + margin-left: -1.8em; } + .reveal .controls[data-controls-layout="edges"] .navigate-down { + bottom: 8px; + left: 50%; + margin-left: -1.8em; } } /********************************************* * PROGRESS BAR *********************************************/ .reveal .progress { - position: fixed; + position: absolute; display: none; height: 3px; width: 100%; bottom: 0; left: 0; z-index: 10; - background-color: rgba(0, 0, 0, 0.2); } + background-color: rgba(0, 0, 0, 0.2); + color: #fff; } .reveal .progress:after { content: ''; display: block; position: absolute; - height: 20px; + height: 10px; width: 100%; - top: -20px; } + top: -10px; } .reveal .progress span { display: block; height: 100%; width: 0px; - background-color: #000; - -webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); - transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); } + background-color: currentColor; + transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); } /********************************************* * SLIDE NUMBER *********************************************/ .reveal .slide-number { - position: fixed; + position: absolute; display: block; right: 8px; bottom: 8px; @@ -300,6 +472,9 @@ body { background-color: rgba(0, 0, 0, 0.4); padding: 5px; } +.reveal .slide-number a { + color: currentColor; } + .reveal .slide-number-delimiter { margin: 0 3px; } @@ -314,6 +489,10 @@ body { -ms-touch-action: none; touch-action: none; } +@media only screen and (orientation: landscape) { + .reveal.ua-iphone { + position: fixed; } } + .reveal .slides { position: absolute; width: 100%; @@ -345,30 +524,27 @@ body { z-index: 10; -webkit-transform-style: flat; transform-style: flat; - -webkit-transition: -webkit-transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), -webkit-transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); - transition: transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); } + transition: -webkit-transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), -webkit-transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); + transition: transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); } /* Global transition speed settings */ .reveal[data-transition-speed="fast"] .slides section { - -webkit-transition-duration: 400ms; - transition-duration: 400ms; } + transition-duration: 400ms; } .reveal[data-transition-speed="slow"] .slides section { - -webkit-transition-duration: 1200ms; - transition-duration: 1200ms; } + transition-duration: 1200ms; } /* Slide-specific transition speed overrides */ .reveal .slides section[data-transition-speed="fast"] { - -webkit-transition-duration: 400ms; - transition-duration: 400ms; } + transition-duration: 400ms; } .reveal .slides section[data-transition-speed="slow"] { - -webkit-transition-duration: 1200ms; - transition-duration: 1200ms; } + transition-duration: 1200ms; } .reveal .slides > section.stack { padding-top: 0; - padding-bottom: 0; } + padding-bottom: 0; + pointer-events: none; } .reveal .slides > section.present, .reveal .slides > section > section.present { @@ -566,8 +742,7 @@ body { *********************************************/ .reveal .slides section[data-transition=zoom], .reveal.zoom .slides section:not([data-transition]) { - -webkit-transition-timing-function: ease; - transition-timing-function: ease; } + transition-timing-function: ease; } .reveal .slides > section[data-transition=zoom].past, .reveal .slides > section[data-transition~=zoom-out].past, @@ -759,13 +934,11 @@ body { .reveal.fade .slides > section > section:not([data-transition]) { -webkit-transform: none; transform: none; - -webkit-transition: opacity 0.5s; - transition: opacity 0.5s; } + transition: opacity 0.5s; } .reveal.fade.overview .slides section, .reveal.fade.overview .slides > section > section { - -webkit-transition: none; - transition: none; } + transition: none; } /********************************************* * NO TRANSITION @@ -774,8 +947,7 @@ body { .reveal.none .slides section:not([data-transition]) { -webkit-transform: none; transform: none; - -webkit-transition: none; - transition: none; } + transition: none; } /********************************************* * PAUSED MODE @@ -790,8 +962,22 @@ body { visibility: hidden; opacity: 0; z-index: 100; - -webkit-transition: all 1s ease; - transition: all 1s ease; } + transition: all 1s ease; } + +.reveal .pause-overlay .resume-button { + position: absolute; + bottom: 20px; + right: 20px; + color: #ccc; + border-radius: 2px; + padding: 6px 14px; + border: 2px solid #ccc; + font-size: 16px; + background: transparent; + cursor: pointer; } + .reveal .pause-overlay .resume-button:hover { + color: #fff; + border-color: #fff; } .reveal.paused .pause-overlay { visibility: visible; @@ -833,8 +1019,7 @@ body { .reveal .no-transition, .reveal .no-transition * { - -webkit-transition: none !important; - transition: none !important; } + transition: none !important; } /********************************************* * PER-SLIDE BACKGROUNDS @@ -857,11 +1042,15 @@ body { visibility: hidden; overflow: hidden; background-color: transparent; + transition: all 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); } + +.reveal .slide-background-content { + position: absolute; + width: 100%; + height: 100%; background-position: 50% 50%; background-repeat: no-repeat; - background-size: cover; - -webkit-transition: all 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); - transition: all 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); } + background-size: cover; } .reveal .slide-background.stack { display: block; } @@ -894,8 +1083,7 @@ body { /* Immediate transition style */ .reveal[data-background-transition=none] > .backgrounds .slide-background, .reveal > .backgrounds .slide-background[data-background-transition=none] { - -webkit-transition: none; - transition: none; } + transition: none; } /* Slide */ .reveal[data-background-transition=slide] > .backgrounds .slide-background, @@ -977,8 +1165,7 @@ body { /* Zoom */ .reveal[data-background-transition=zoom] > .backgrounds .slide-background, .reveal > .backgrounds .slide-background[data-background-transition=zoom] { - -webkit-transition-timing-function: ease; - transition-timing-function: ease; } + transition-timing-function: ease; } .reveal[data-background-transition=zoom] > .backgrounds .slide-background.past, .reveal > .backgrounds .slide-background.past[data-background-transition=zoom] { @@ -1010,12 +1197,10 @@ body { /* Global transition speed settings */ .reveal[data-transition-speed="fast"] > .backgrounds .slide-background { - -webkit-transition-duration: 400ms; - transition-duration: 400ms; } + transition-duration: 400ms; } .reveal[data-transition-speed="slow"] > .backgrounds .slide-background { - -webkit-transition-duration: 1200ms; - transition-duration: 1200ms; } + transition-duration: 1200ms; } /********************************************* * OVERVIEW @@ -1041,8 +1226,7 @@ body { outline-offset: 10px; } .reveal.overview .slides section .fragment { opacity: 1; - -webkit-transition: none; - transition: none; } + transition: none; } .reveal.overview .slides section:after, .reveal.overview .slides section:before { display: none !important; } @@ -1066,13 +1250,11 @@ body { .reveal.overview .slides section, .reveal.overview-deactivating .slides section { - -webkit-transition: none; - transition: none; } + transition: none; } .reveal.overview .backgrounds .slide-background, .reveal.overview-deactivating .backgrounds .slide-background { - -webkit-transition: none; - transition: none; } + transition: none; } /********************************************* * RTL SUPPORT @@ -1102,17 +1284,14 @@ body { * PARALLAX BACKGROUND *********************************************/ .reveal.has-parallax-background .backgrounds { - -webkit-transition: all 0.8s ease; - transition: all 0.8s ease; } + transition: all 0.8s ease; } /* Global transition speed settings */ .reveal.has-parallax-background[data-transition-speed="fast"] .backgrounds { - -webkit-transition-duration: 400ms; - transition-duration: 400ms; } + transition-duration: 400ms; } .reveal.has-parallax-background[data-transition-speed="slow"] .backgrounds { - -webkit-transition-duration: 1200ms; - transition-duration: 1200ms; } + transition-duration: 1200ms; } /********************************************* * LINK PREVIEW OVERLAY @@ -1127,8 +1306,7 @@ body { background: rgba(0, 0, 0, 0.9); opacity: 0; visibility: hidden; - -webkit-transition: all 0.3s ease; - transition: all 0.3s ease; } + transition: all 0.3s ease; } .reveal .overlay.visible { opacity: 1; @@ -1146,8 +1324,7 @@ body { background-image: url(data:image/gif;base64,R0lGODlhIAAgAPMAAJmZmf%2F%2F%2F6%2Bvr8nJybW1tcDAwOjo6Nvb26ioqKOjo7Ozs%2FLy8vz8%2FAAAAAAAAAAAACH%2FC05FVFNDQVBFMi4wAwEAAAAh%2FhpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh%2BQQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ%2FV%2FnmOM82XiHRLYKhKP1oZmADdEAAAh%2BQQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY%2FCZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB%2BA4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6%2BHo7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq%2BB6QDtuetcaBPnW6%2BO7wDHpIiK9SaVK5GgV543tzjgGcghAgAh%2BQQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK%2B%2BG%2Bw48edZPK%2BM6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE%2BG%2BcD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm%2BFNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk%2BaV%2BoJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0%2FVNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc%2BXiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30%2FiI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE%2FjiuL04RGEBgwWhShRgQExHBAAh%2BQQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR%2BipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY%2BYip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd%2BMFCN6HAAIKgNggY0KtEBAAh%2BQQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1%2BvsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d%2BjYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg%2BygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0%2Bbm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h%2BKr0SJ8MFihpNbx%2B4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX%2BBP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA%3D%3D); visibility: visible; opacity: 0.6; - -webkit-transition: all 0.3s ease; - transition: all 0.3s ease; } + transition: all 0.3s ease; } .reveal .overlay header { position: absolute; @@ -1188,7 +1365,6 @@ body { .reveal .overlay .viewport { position: absolute; display: -webkit-box; - display: -webkit-flex; display: -ms-flexbox; display: flex; top: 40px; @@ -1204,8 +1380,7 @@ body { border: 0; opacity: 0; visibility: hidden; - -webkit-transition: all 0.3s ease; - transition: all 0.3s ease; } + transition: all 0.3s ease; } .reveal .overlay.overlay-preview.loaded .viewport iframe { opacity: 1; @@ -1222,8 +1397,7 @@ body { .reveal .overlay.overlay-preview .x-frame-error { opacity: 0; - -webkit-transition: opacity 0.3s ease 0.3s; - transition: opacity 0.3s ease 0.3s; } + transition: opacity 0.3s ease 0.3s; } .reveal .overlay.overlay-preview.loaded .x-frame-error { opacity: 1; } @@ -1268,13 +1442,13 @@ body { * PLAYBACK COMPONENT *********************************************/ .reveal .playback { - position: fixed; + position: absolute; left: 15px; bottom: 20px; z-index: 30; cursor: pointer; - -webkit-transition: all 400ms ease; - transition: all 400ms ease; } + transition: all 400ms ease; + -webkit-tap-highlight-color: transparent; } .reveal.overview .playback { opacity: 0; @@ -1302,8 +1476,7 @@ body { position: relative; padding: 0 2px; pointer-events: none; - -webkit-transition: all 400ms ease; - transition: all 400ms ease; + transition: all 400ms ease; -webkit-transform-origin: 50% 0%; transform-origin: 50% 0%; -webkit-transform-style: preserve-3d; @@ -1339,33 +1512,64 @@ body { .reveal .speaker-notes { display: none; position: absolute; - width: 70%; - max-height: 15%; - left: 15%; - bottom: 26px; - padding: 10px; + width: 25vw; + height: 100%; + top: 0; + left: 100%; + padding: 14px 18px 14px 18px; z-index: 1; font-size: 18px; line-height: 1.4; - color: #fff; - background-color: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(0, 0, 0, 0.05); + color: #222; + background-color: #f5f5f5; overflow: auto; box-sizing: border-box; text-align: left; font-family: Helvetica, sans-serif; -webkit-overflow-scrolling: touch; } + .reveal .speaker-notes .notes-placeholder { + color: #ccc; + font-style: italic; } + .reveal .speaker-notes:focus { + outline: none; } + .reveal .speaker-notes:before { + content: 'Speaker notes'; + display: block; + margin-bottom: 10px; + opacity: 0.5; } -.reveal .speaker-notes.visible:not(:empty) { +.reveal.show-notes { + max-width: 75vw; + overflow: visible; } + +.reveal.show-notes .speaker-notes { display: block; } -@media screen and (max-width: 1024px) { +@media screen and (min-width: 1600px) { .reveal .speaker-notes { - font-size: 14px; } } + font-size: 20px; } } + +@media screen and (max-width: 1024px) { + .reveal.show-notes { + border-left: 0; + max-width: none; + max-height: 70%; + overflow: visible; } + .reveal.show-notes .speaker-notes { + top: 100%; + left: 0; + width: 100%; + height: 42.8571428571%; } } @media screen and (max-width: 600px) { + .reveal.show-notes { + max-height: 60%; } + .reveal.show-notes .speaker-notes { + top: 100%; + height: 66.6666666667%; } .reveal .speaker-notes { - width: 90%; - left: 5%; } } + font-size: 14px; } } /********************************************* * ZOOM PLUGIN diff --git a/openlp/core/display/html/reveal.js b/openlp/core/display/html/reveal.js index 099505e2c..400b86f4b 100644 --- a/openlp/core/display/html/reveal.js +++ b/openlp/core/display/html/reveal.js @@ -1,9 +1,9 @@ /*! * reveal.js - * http://lab.hakim.se/reveal-js + * http://revealjs.com * MIT licensed * - * Copyright (C) 2017 Hakim El Hattab, http://hakim.se + * Copyright (C) 2018 Hakim El Hattab, http://hakim.se */ (function( root, factory ) { if( typeof define === 'function' && define.amd ) { @@ -26,7 +26,7 @@ var Reveal; // The reveal.js version - var VERSION = '3.5.0'; + var VERSION = '3.7.0'; var SLIDES_SELECTOR = '.slides section', HORIZONTAL_SLIDES_SELECTOR = '.slides>section', @@ -49,15 +49,30 @@ minScale: 0.2, maxScale: 2.0, - // Display controls in the bottom right corner + // Display presentation control arrows controls: true, + // Help the user learn the controls by providing hints, for example by + // bouncing the down arrow when they first encounter a vertical slide + controlsTutorial: true, + + // Determines where controls appear, "edges" or "bottom-right" + controlsLayout: 'bottom-right', + + // Visibility rule for backwards navigation arrows; "faded", "hidden" + // or "visible" + controlsBackArrows: 'faded', + // Display a presentation progress bar progress: true, // Display the page number of the current slide slideNumber: false, + // Use 1 based indexing for # links to match slide number (default is zero + // based) + hashOneBasedIndex: false, + // Determine which displays to show the slide number on showSlideNumber: 'all', @@ -73,6 +88,10 @@ // Enable the slide overview mode overview: true, + // Disables the default reveal.js slide layout so that you can use + // custom CSS layout + disableLayout: false, + // Vertical centering of slides center: true, @@ -91,6 +110,10 @@ // Turns fragments on and off globally fragments: true, + // Flags whether to include the current fragment in the URL, + // so that reloading brings you to the same fragment position + fragmentInURL: false, + // Flags if the presentation is running in an embedded mode, // i.e. contained within a limited portion of the screen embedded: false, @@ -106,14 +129,16 @@ showNotes: false, // Global override for autolaying embedded media (video/audio/iframe) - // - null: Media will only autoplay if data-autoplay is present - // - true: All media will autoplay, regardless of individual setting - // - false: No media will autoplay, regardless of individual setting + // - null: Media will only autoplay if data-autoplay is present + // - true: All media will autoplay, regardless of individual setting + // - false: No media will autoplay, regardless of individual setting autoPlayMedia: null, - // Number of milliseconds between automatically proceeding to the - // next slide, disabled when set to 0, this value can be overwritten - // by using a data-autoslide attribute on your slides + // Controls automatic progression to the next slide + // - 0: Auto-sliding only happens if the data-autoslide HTML attribute + // is present on the current slide or fragment + // - 1+: All slides will progress automatically at the given interval + // - false: No auto-sliding, even if data-autoslide is present autoSlide: 0, // Stop auto-sliding after user input @@ -122,6 +147,11 @@ // Use this method for navigation when auto-sliding (defaults to navigateNext) autoSlideMethod: null, + // Specify the average time in seconds that you think you will spend + // presenting each slide. This is used to show a pacing timer in the + // speaker view + defaultTiming: null, + // Enable slide navigation via mouse wheel mouseWheel: false, @@ -132,6 +162,8 @@ hideAddressBar: true, // Opens links in an iframe preview overlay + // Add `data-preview-link` and `data-preview-link="false"` to customise each link + // individually previewLinks: false, // Exposes the reveal.js API through window.postMessage @@ -158,6 +190,12 @@ // Parallax background size parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px" + // Parallax background repeat + parallaxBackgroundRepeat: '', // repeat/repeat-x/repeat-y/no-repeat/initial/inherit + + // Parallax background position + parallaxBackgroundPosition: '', // CSS syntax, e.g. "top left" + // Amount of pixels to move the parallax background per slide step parallaxBackgroundHorizontal: null, parallaxBackgroundVertical: null, @@ -166,6 +204,9 @@ // to PDF, unlimited by default pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY, + // Prints each fragment on a separate slide + pdfSeparateFragments: true, + // Offset used to reduce the height of content within exported PDF pages. // This exists to account for environment differences based on how you // print to PDF. CLI printing options, like phantomjs and wkpdf, can end @@ -207,6 +248,10 @@ previousBackground, + // Remember which directions that the user has navigated towards + hasNavigatedRight = false, + hasNavigatedDown = false, + // Slides may hold a data-state attribute which we pick up and apply // as a class to the body. This list contains the combined state of // all current slides. @@ -272,7 +317,10 @@ 'B , .': 'Pause', 'F': 'Fullscreen', 'ESC, O': 'Slide overview' - }; + }, + + // Holds custom key code mappings + registeredKeyBindings = {}; /** * Starts up the presentation if the client is capable. @@ -382,13 +430,13 @@ } - /** - * Loads the dependencies of reveal.js. Dependencies are - * defined via the configuration option 'dependencies' - * and will be loaded prior to starting/binding reveal.js. - * Some dependencies may have an 'async' flag, if so they - * will load after reveal.js has been started up. - */ + /** + * Loads the dependencies of reveal.js. Dependencies are + * defined via the configuration option 'dependencies' + * and will be loaded prior to starting/binding reveal.js. + * Some dependencies may have an 'async' flag, if so they + * will load after reveal.js has been started up. + */ function load() { var scripts = [], @@ -406,7 +454,7 @@ } function loadScript( s ) { - head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], function() { + head.ready( s.src.match( /([\w\d_\-]*)\.?js(\?[\w\d.=&]*)?$|[^\\\/]*$/i )[0], function() { // Extension may contain callback functions if( typeof s.callback === 'function' ) { s.callback.apply( this ); @@ -452,6 +500,8 @@ */ function start() { + loaded = true; + // Make sure we've got all the DOM elements we need setupDOM(); @@ -479,8 +529,6 @@ // Enable transitions now that we're loaded dom.slides.classList.remove( 'no-transition' ); - loaded = true; - dom.wrapper.classList.add( 'ready' ); dispatchEvent( 'ready', { @@ -516,6 +564,20 @@ // Prevent transitions while we're loading dom.slides.classList.add( 'no-transition' ); + if( isMobileDevice ) { + dom.wrapper.classList.add( 'no-hover' ); + } + else { + dom.wrapper.classList.remove( 'no-hover' ); + } + + if( /iphone/gi.test( UA ) ) { + dom.wrapper.classList.add( 'ua-iphone' ); + } + else { + dom.wrapper.classList.remove( 'ua-iphone' ); + } + // Background element dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null ); @@ -524,11 +586,11 @@ dom.progressbar = dom.progress.querySelector( 'span' ); // Arrow controls - createSingletonNode( dom.wrapper, 'aside', 'controls', - '' + - '' + - '' + - '' ); + dom.controls = createSingletonNode( dom.wrapper, 'aside', 'controls', + '' + + '' + + '' + + '' ); // Slide number dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' ); @@ -539,10 +601,8 @@ dom.speakerNotes.setAttribute( 'tabindex', '0' ); // Overlay graphic which is displayed during the paused mode - createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null ); - - // Cache references to elements - dom.controls = document.querySelector( '.reveal .controls' ); + dom.pauseOverlay = createSingletonNode( dom.wrapper, 'div', 'pause-overlay', '' ); + dom.resumeButton = dom.pauseOverlay.querySelector( '.resume-button' ); dom.wrapper.setAttribute( 'role', 'application' ); @@ -554,6 +614,10 @@ dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) ); dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) ); + // The right and down arrows in the standard reveal.js controls + dom.controlsRightArrow = dom.controls.querySelector( '.navigate-right' ); + dom.controlsDownArrow = dom.controls.querySelector( '.navigate-down' ); + dom.statusDiv = createStatusDiv(); } @@ -735,15 +799,60 @@ numberElement.innerHTML = formatSlideNumber( slideNumberH, '.', slideNumberV ); page.appendChild( numberElement ); } + + // Copy page and show fragments one after another + if( config.pdfSeparateFragments ) { + + // Each fragment 'group' is an array containing one or more + // fragments. Multiple fragments that appear at the same time + // are part of the same group. + var fragmentGroups = sortFragments( page.querySelectorAll( '.fragment' ), true ); + + var previousFragmentStep; + var previousPage; + + fragmentGroups.forEach( function( fragments ) { + + // Remove 'current-fragment' from the previous group + if( previousFragmentStep ) { + previousFragmentStep.forEach( function( fragment ) { + fragment.classList.remove( 'current-fragment' ); + } ); + } + + // Show the fragments for the current index + fragments.forEach( function( fragment ) { + fragment.classList.add( 'visible', 'current-fragment' ); + } ); + + // Create a separate page for the current fragment state + var clonedPage = page.cloneNode( true ); + page.parentNode.insertBefore( clonedPage, ( previousPage || page ).nextSibling ); + + previousFragmentStep = fragments; + previousPage = clonedPage; + + } ); + + // Reset the first/original page so that all fragments are hidden + fragmentGroups.forEach( function( fragments ) { + fragments.forEach( function( fragment ) { + fragment.classList.remove( 'visible', 'current-fragment' ); + } ); + } ); + + } + // Show all fragments + else { + toArray( page.querySelectorAll( '.fragment:not(.fade-out)' ) ).forEach( function( fragment ) { + fragment.classList.add( 'visible' ); + } ); + } + } } ); - // Show all fragments - toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' .fragment' ) ).forEach( function( fragment ) { - fragment.classList.add( 'visible' ); - } ); - // Notify subscribers that the PDF layout is good to go dispatchEvent( 'pdf-ready' ); @@ -797,7 +906,7 @@ // If no node was found, create it now var node = document.createElement( tagname ); - node.classList.add( classname ); + node.className = classname; if( typeof innerHTML === 'string' ) { node.innerHTML = innerHTML; } @@ -841,6 +950,8 @@ dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")'; dom.background.style.backgroundSize = config.parallaxBackgroundSize; + dom.background.style.backgroundRepeat = config.parallaxBackgroundRepeat; + dom.background.style.backgroundPosition = config.parallaxBackgroundPosition; // Make sure the below properties are set on the element - these properties are // needed for proper transitions to be set on the element via CSS. To remove @@ -870,6 +981,57 @@ */ function createBackground( slide, container ) { + + // Main slide background element + var element = document.createElement( 'div' ); + element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' ); + + // Inner background element that wraps images/videos/iframes + var contentElement = document.createElement( 'div' ); + contentElement.className = 'slide-background-content'; + + element.appendChild( contentElement ); + container.appendChild( element ); + + slide.slideBackgroundElement = element; + slide.slideBackgroundContentElement = contentElement; + + // Syncs the background to reflect all current background settings + syncBackground( slide ); + + return element; + + } + + /** + * Renders all of the visual properties of a slide background + * based on the various background attributes. + * + * @param {HTMLElement} slide + */ + function syncBackground( slide ) { + + var element = slide.slideBackgroundElement, + contentElement = slide.slideBackgroundContentElement; + + // Reset the prior background state in case this is not the + // initial sync + slide.classList.remove( 'has-dark-background' ); + slide.classList.remove( 'has-light-background' ); + + element.removeAttribute( 'data-loaded' ); + element.removeAttribute( 'data-background-hash' ); + element.removeAttribute( 'data-background-size' ); + element.removeAttribute( 'data-background-transition' ); + element.style.backgroundColor = ''; + + contentElement.style.backgroundSize = ''; + contentElement.style.backgroundRepeat = ''; + contentElement.style.backgroundPosition = ''; + contentElement.style.backgroundImage = ''; + contentElement.style.opacity = ''; + contentElement.innerHTML = ''; + var data = { background: slide.getAttribute( 'data-background' ), backgroundSize: slide.getAttribute( 'data-background-size' ), @@ -879,17 +1041,13 @@ backgroundColor: slide.getAttribute( 'data-background-color' ), backgroundRepeat: slide.getAttribute( 'data-background-repeat' ), backgroundPosition: slide.getAttribute( 'data-background-position' ), - backgroundTransition: slide.getAttribute( 'data-background-transition' ) + backgroundTransition: slide.getAttribute( 'data-background-transition' ), + backgroundOpacity: slide.getAttribute( 'data-background-opacity' ) }; - var element = document.createElement( 'div' ); - - // Carry over custom classes from the slide to the background - element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' ); - if( data.background ) { // Auto-wrap image urls in url(...) - if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#]|$)/gi.test( data.background ) ) { + if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#\s]|$)/gi.test( data.background ) ) { slide.setAttribute( 'data-background-image', data.background ); } else { @@ -909,24 +1067,20 @@ data.backgroundColor + data.backgroundRepeat + data.backgroundPosition + - data.backgroundTransition ); + data.backgroundTransition + + data.backgroundOpacity ); } // Additional and optional background properties - if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize; if( data.backgroundSize ) element.setAttribute( 'data-background-size', data.backgroundSize ); if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor; - if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat; - if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition; if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition ); - container.appendChild( element ); - - // If backgrounds are being recreated, clear old classes - slide.classList.remove( 'has-dark-background' ); - slide.classList.remove( 'has-light-background' ); - - slide.slideBackgroundElement = element; + // Background image options are set on the content wrapper + if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize; + if( data.backgroundRepeat ) contentElement.style.backgroundRepeat = data.backgroundRepeat; + if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition; + if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity; // If this slide has a background color, add a class that // signals if it is light or dark. If the slide has no background @@ -948,8 +1102,6 @@ } } - return element; - } /** @@ -990,14 +1142,22 @@ */ function configure( options ) { - var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length; - - dom.wrapper.classList.remove( config.transition ); + var oldTransition = config.transition; // New config options may be passed when this method // is invoked through the API after initialization if( typeof options === 'object' ) extend( config, options ); + // Abort if reveal.js hasn't finished loading, config + // changes will be applied automatically once loading + // finishes + if( loaded === false ) return; + + var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length; + + // Remove the previously configured transition class + dom.wrapper.classList.remove( oldTransition ); + // Force linear transition based on browser capabilities if( features.transforms3d === false ) config.transition = 'linear'; @@ -1009,6 +1169,9 @@ dom.controls.style.display = config.controls ? 'block' : 'none'; dom.progress.style.display = config.progress ? 'block' : 'none'; + dom.controls.setAttribute( 'data-controls-layout', config.controlsLayout ); + dom.controls.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows ); + if( config.shuffle ) { shuffle(); } @@ -1033,12 +1196,8 @@ } if( config.showNotes ) { - dom.speakerNotes.classList.add( 'visible' ); dom.speakerNotes.setAttribute( 'data-layout', typeof config.showNotes === 'string' ? config.showNotes : 'inline' ); } - else { - dom.speakerNotes.classList.remove( 'visible' ); - } if( config.mouseWheel ) { document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF @@ -1119,13 +1278,8 @@ window.addEventListener( 'resize', onWindowResize, false ); if( config.touch ) { - dom.wrapper.addEventListener( 'touchstart', onTouchStart, false ); - dom.wrapper.addEventListener( 'touchmove', onTouchMove, false ); - dom.wrapper.addEventListener( 'touchend', onTouchEnd, false ); - - // Support pointer-style touch interaction as well - if( window.navigator.pointerEnabled ) { - // IE 11 uses un-prefixed version of pointer events + if( 'onpointerdown' in window ) { + // Use W3C pointer events dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false ); dom.wrapper.addEventListener( 'pointermove', onPointerMove, false ); dom.wrapper.addEventListener( 'pointerup', onPointerUp, false ); @@ -1136,6 +1290,12 @@ dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false ); dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false ); } + else { + // Fall back to touch events + dom.wrapper.addEventListener( 'touchstart', onTouchStart, false ); + dom.wrapper.addEventListener( 'touchmove', onTouchMove, false ); + dom.wrapper.addEventListener( 'touchend', onTouchEnd, false ); + } } if( config.keyboard ) { @@ -1147,6 +1307,8 @@ dom.progress.addEventListener( 'click', onProgressClicked, false ); } + dom.resumeButton.addEventListener( 'click', resume, false ); + if( config.focusBodyOnPageVisibilityChange ) { var visibilityChange; @@ -1198,22 +1360,19 @@ window.removeEventListener( 'hashchange', onWindowHashChange, false ); window.removeEventListener( 'resize', onWindowResize, false ); + dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false ); + dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false ); + dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false ); + + dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false ); + dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false ); + dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false ); + dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false ); dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false ); dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false ); - // IE11 - if( window.navigator.pointerEnabled ) { - dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false ); - dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false ); - dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false ); - } - // IE10 - else if( window.navigator.msPointerEnabled ) { - dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false ); - dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false ); - dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false ); - } + dom.resumeButton.removeEventListener( 'click', resume, false ); if ( config.progress && dom.progress ) { dom.progress.removeEventListener( 'click', onProgressClicked, false ); @@ -1230,6 +1389,38 @@ } + /** + * Add a custom key binding with optional description to + * be added to the help screen. + */ + function addKeyBinding( binding, callback ) { + + if( typeof binding === 'object' && binding.keyCode ) { + registeredKeyBindings[binding.keyCode] = { + callback: callback, + key: binding.key, + description: binding.description + }; + } + else { + registeredKeyBindings[binding] = { + callback: callback, + key: null, + description: null + }; + } + + } + + /** + * Removes the specified custom key binding. + */ + function removeKeyBinding( keyCode ) { + + delete registeredKeyBindings[keyCode]; + + } + /** * Extend object a with the properties of object b. * If there's a conflict, object b takes precedence. @@ -1243,6 +1434,8 @@ a[ i ] = b[ i ]; } + return a; + } /** @@ -1269,7 +1462,7 @@ if( value === 'null' ) return null; else if( value === 'true' ) return true; else if( value === 'false' ) return false; - else if( value.match( /^[\d\.]+$/ ) ) return parseFloat( value ); + else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value ); } return value; @@ -1505,6 +1698,15 @@ } + /** + * Check if this instance is being used to print a PDF with fragments. + */ + function isPrintingPDFFragments() { + + return ( /print-pdf-fragments/gi ).test( window.location.search ); + + } + /** * Hides the address bar if we're on a mobile device. */ @@ -1715,6 +1917,13 @@ html += '' + key + '' + keyboardShortcuts[ key ] + ''; } + // Add custom key bindings that have associated descriptions + for( var binding in registeredKeyBindings ) { + if( registeredKeyBindings[binding].key && registeredKeyBindings[binding].description ) { + html += '' + registeredKeyBindings[binding].key + '' + registeredKeyBindings[binding].description + ''; + } + } + html += ''; dom.overlay.innerHTML = [ @@ -1759,76 +1968,80 @@ if( dom.wrapper && !isPrintingPDF() ) { - var size = getComputedSlideSize(); + if( !config.disableLayout ) { - // Layout the contents of the slides - layoutSlideContents( config.width, config.height ); + var size = getComputedSlideSize(); - dom.slides.style.width = size.width + 'px'; - dom.slides.style.height = size.height + 'px'; + // Layout the contents of the slides + layoutSlideContents( config.width, config.height ); - // Determine scale of content to fit within available space - scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height ); + dom.slides.style.width = size.width + 'px'; + dom.slides.style.height = size.height + 'px'; - // Respect max/min scale settings - scale = Math.max( scale, config.minScale ); - scale = Math.min( scale, config.maxScale ); + // Determine scale of content to fit within available space + scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height ); - // Don't apply any scaling styles if scale is 1 - if( scale === 1 ) { - dom.slides.style.zoom = ''; - dom.slides.style.left = ''; - dom.slides.style.top = ''; - dom.slides.style.bottom = ''; - dom.slides.style.right = ''; - transformSlides( { layout: '' } ); - } - else { - // Prefer zoom for scaling up so that content remains crisp. - // Don't use zoom to scale down since that can lead to shifts - // in text layout/line breaks. - if( scale > 1 && features.zoom ) { - dom.slides.style.zoom = scale; + // Respect max/min scale settings + scale = Math.max( scale, config.minScale ); + scale = Math.min( scale, config.maxScale ); + + // Don't apply any scaling styles if scale is 1 + if( scale === 1 ) { + dom.slides.style.zoom = ''; dom.slides.style.left = ''; dom.slides.style.top = ''; dom.slides.style.bottom = ''; dom.slides.style.right = ''; transformSlides( { layout: '' } ); } - // Apply scale transform as a fallback else { - dom.slides.style.zoom = ''; - dom.slides.style.left = '50%'; - dom.slides.style.top = '50%'; - dom.slides.style.bottom = 'auto'; - dom.slides.style.right = 'auto'; - transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } ); - } - } - - // Select all slides, vertical and horizontal - var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ); - - for( var i = 0, len = slides.length; i < len; i++ ) { - var slide = slides[ i ]; - - // Don't bother updating invisible slides - if( slide.style.display === 'none' ) { - continue; + // Prefer zoom for scaling up so that content remains crisp. + // Don't use zoom to scale down since that can lead to shifts + // in text layout/line breaks. + if( scale > 1 && features.zoom ) { + dom.slides.style.zoom = scale; + dom.slides.style.left = ''; + dom.slides.style.top = ''; + dom.slides.style.bottom = ''; + dom.slides.style.right = ''; + transformSlides( { layout: '' } ); + } + // Apply scale transform as a fallback + else { + dom.slides.style.zoom = ''; + dom.slides.style.left = '50%'; + dom.slides.style.top = '50%'; + dom.slides.style.bottom = 'auto'; + dom.slides.style.right = 'auto'; + transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } ); + } } - if( config.center || slide.classList.contains( 'center' ) ) { - // Vertical stacks are not centred since their section - // children will be - if( slide.classList.contains( 'stack' ) ) { - slide.style.top = 0; + // Select all slides, vertical and horizontal + var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ); + + for( var i = 0, len = slides.length; i < len; i++ ) { + var slide = slides[ i ]; + + // Don't bother updating invisible slides + if( slide.style.display === 'none' ) { + continue; + } + + if( config.center || slide.classList.contains( 'center' ) ) { + // Vertical stacks are not centred since their section + // children will be + if( slide.classList.contains( 'stack' ) ) { + slide.style.top = 0; + } + else { + slide.style.top = Math.max( ( size.height - slide.scrollHeight ) / 2, 0 ) + 'px'; + } } else { - slide.style.top = Math.max( ( size.height - slide.scrollHeight ) / 2, 0 ) + 'px'; + slide.style.top = ''; } - } - else { - slide.style.top = ''; + } } @@ -2154,6 +2367,41 @@ } + /** + * Return a hash URL that will resolve to the current slide location. + */ + function locationHash() { + + var url = '/'; + + // Attempt to create a named link based on the slide's ID + var id = currentSlide ? currentSlide.getAttribute( 'id' ) : null; + if( id ) { + id = encodeURIComponent( id ); + } + + var indexf; + if( config.fragmentInURL ) { + indexf = getIndices().f; + } + + // If the current slide has an ID, use that as a named link, + // but we don't support named links with a fragment index + if( typeof id === 'string' && id.length && indexf === undefined ) { + url = '/' + id; + } + // Otherwise use the /h/v index + else { + var hashIndexBase = config.hashOneBasedIndex ? 1 : 0; + if( indexh > 0 || indexv > 0 || indexf !== undefined ) url += indexh + hashIndexBase; + if( indexv > 0 || indexf !== undefined ) url += '/' + (indexv + hashIndexBase ); + if( indexf !== undefined ) url += '/' + indexf; + } + + return url; + + } + /** * Checks if the current or specified slide is vertical * (nested within another slide). @@ -2378,16 +2626,7 @@ // Dispatch an event if the slide changed var slideChanged = ( indexh !== indexhBefore || indexv !== indexvBefore ); - if( slideChanged ) { - dispatchEvent( 'slidechanged', { - 'indexh': indexh, - 'indexv': indexv, - 'previousSlide': previousSlide, - 'currentSlide': currentSlide, - 'origin': o - } ); - } - else { + if (!slideChanged) { // Ensure that the previous slide is never the same as the current previousSlide = null; } @@ -2395,7 +2634,7 @@ // Solves an edge case where the previous slide maintains the // 'present' class when navigating between adjacent vertical // stacks - if( previousSlide ) { + if( previousSlide && previousSlide !== currentSlide ) { previousSlide.classList.remove( 'present' ); previousSlide.setAttribute( 'aria-hidden', 'true' ); @@ -2415,6 +2654,16 @@ } } + if( slideChanged ) { + dispatchEvent( 'slidechanged', { + 'indexh': indexh, + 'indexv': indexv, + 'previousSlide': previousSlide, + 'currentSlide': currentSlide, + 'origin': o + } ); + } + // Handle embedded content if( slideChanged || !previousSlide ) { stopEmbeddedContent( previousSlide ); @@ -2471,13 +2720,14 @@ updateSlideNumber(); updateSlidesVisibility(); updateBackground( true ); + updateNotesVisibility(); updateNotes(); formatEmbeddedContent(); // Start or stop embedded content depending on global config if( config.autoPlayMedia === false ) { - stopEmbeddedContent( currentSlide ); + stopEmbeddedContent( currentSlide, { unloadIframes: false } ); } else { startEmbeddedContent( currentSlide ); @@ -2489,6 +2739,41 @@ } + /** + * Updates reveal.js to keep in sync with new slide attributes. For + * example, if you add a new `data-background-image` you can call + * this to have reveal.js render the new background image. + * + * Similar to #sync() but more efficient when you only need to + * refresh a specific slide. + * + * @param {HTMLElement} slide + */ + function syncSlide( slide ) { + + syncBackground( slide ); + syncFragments( slide ); + + updateBackground(); + updateNotes(); + + loadSlide( slide ); + + } + + /** + * Formats the fragments on the given slide so that they have + * valid indices. Call this if fragments are changed in the DOM + * after reveal.js has already initialized. + * + * @param {HTMLElement} slide + */ + function syncFragments( slide ) { + + sortFragments( slide.querySelectorAll( '.fragment' ) ); + + } + /** * Resets all vertical slides so that only the first * is visible. @@ -2714,10 +2999,10 @@ // Show the horizontal slide if it's within the view distance if( distanceX < viewDistance ) { - showSlide( horizontalSlide ); + loadSlide( horizontalSlide ); } else { - hideSlide( horizontalSlide ); + unloadSlide( horizontalSlide ); } if( verticalSlidesLength ) { @@ -2730,16 +3015,32 @@ distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy ); if( distanceX + distanceY < viewDistance ) { - showSlide( verticalSlide ); + loadSlide( verticalSlide ); } else { - hideSlide( verticalSlide ); + unloadSlide( verticalSlide ); } } } } + // Flag if there are ANY vertical slides, anywhere in the deck + if( dom.wrapper.querySelectorAll( '.slides>section>section' ).length ) { + dom.wrapper.classList.add( 'has-vertical-slides' ); + } + else { + dom.wrapper.classList.remove( 'has-vertical-slides' ); + } + + // Flag if there are ANY horizontal slides, anywhere in the deck + if( dom.wrapper.querySelectorAll( '.slides>section' ).length > 1 ) { + dom.wrapper.classList.add( 'has-horizontal-slides' ); + } + else { + dom.wrapper.classList.remove( 'has-horizontal-slides' ); + } + } } @@ -2754,12 +3055,39 @@ if( config.showNotes && dom.speakerNotes && currentSlide && !isPrintingPDF() ) { - dom.speakerNotes.innerHTML = getSlideNotes() || ''; + dom.speakerNotes.innerHTML = getSlideNotes() || 'No notes on this slide.'; } } + /** + * Updates the visibility of the speaker notes sidebar that + * is used to share annotated slides. The notes sidebar is + * only visible if showNotes is true and there are notes on + * one or more slides in the deck. + */ + function updateNotesVisibility() { + + if( config.showNotes && hasNotes() ) { + dom.wrapper.classList.add( 'show-notes' ); + } + else { + dom.wrapper.classList.remove( 'show-notes' ); + } + + } + + /** + * Checks if there are speaker notes for ANY slide in the + * presentation. + */ + function hasNotes() { + + return dom.slides.querySelectorAll( '[data-notes], aside.notes' ).length > 0; + + } + /** * Updates the progress bar to reflect the current slide. */ @@ -2774,6 +3102,7 @@ } + /** * Updates the slide number div to reflect the current slide. * @@ -2796,6 +3125,12 @@ format = config.slideNumber; } + // If there are ONLY vertical slides in this deck, always use + // a flattened slide number + if( !/c/.test( format ) && dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ).length === 1 ) { + format = 'c'; + } + switch( format ) { case 'c': value.push( getSlidePastCount() + 1 ); @@ -2828,13 +3163,18 @@ */ function formatSlideNumber( a, delimiter, b ) { + var url = '#' + locationHash(); if( typeof b === 'number' && !isNaN( b ) ) { - return ''+ a +'' + + return '' + + ''+ a +'' + ''+ delimiter +'' + - ''+ b +''; + ''+ b +'' + + ''; } else { - return ''+ a +''; + return '' + + ''+ a +'' + + ''; } } @@ -2890,6 +3230,26 @@ } + if( config.controlsTutorial ) { + + // Highlight control arrows with an animation to ensure + // that the viewer knows how to navigate + if( !hasNavigatedDown && routes.down ) { + dom.controlsDownArrow.classList.add( 'highlight' ); + } + else { + dom.controlsDownArrow.classList.remove( 'highlight' ); + + if( !hasNavigatedRight && routes.right && indexv === 0 ) { + dom.controlsRightArrow.classList.add( 'highlight' ); + } + else { + dom.controlsRightArrow.classList.remove( 'highlight' ); + } + } + + } + } /** @@ -2965,13 +3325,18 @@ startEmbeddedContent( currentBackground ); - var backgroundImageURL = currentBackground.style.backgroundImage || ''; + var currentBackgroundContent = currentBackground.querySelector( '.slide-background-content' ); + if( currentBackgroundContent ) { + + var backgroundImageURL = currentBackgroundContent.style.backgroundImage || ''; + + // Restart GIFs (doesn't work in Firefox) + if( /\.gif/i.test( backgroundImageURL ) ) { + currentBackgroundContent.style.backgroundImage = ''; + window.getComputedStyle( currentBackgroundContent ).opacity; + currentBackgroundContent.style.backgroundImage = backgroundImageURL; + } - // Restart GIFs (doesn't work in Firefox) - if( /\.gif/i.test( backgroundImageURL ) ) { - currentBackground.style.backgroundImage = ''; - window.getComputedStyle( currentBackground ).opacity; - currentBackground.style.backgroundImage = backgroundImageURL; } // Don't transition between identical backgrounds. This @@ -3069,14 +3434,9 @@ * * @param {HTMLElement} slide Slide to show */ - /** - * Called when the given slide is within the configured view - * distance. Shows the slide element and loads any content - * that is set to load lazily (data-src). - * - * @param {HTMLElement} slide Slide to show - */ - function showSlide( slide ) { + function loadSlide( slide, options ) { + + options = options || {}; // Show the slide element slide.style.display = config.display; @@ -3084,6 +3444,7 @@ // Media elements with data-src attributes toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) { element.setAttribute( 'src', element.getAttribute( 'data-src' ) ); + element.setAttribute( 'data-lazy-loaded', '' ); element.removeAttribute( 'data-src' ); } ); @@ -3094,6 +3455,7 @@ toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) { source.setAttribute( 'src', source.getAttribute( 'data-src' ) ); source.removeAttribute( 'data-src' ); + source.setAttribute( 'data-lazy-loaded', '' ); sources += 1; } ); @@ -3106,11 +3468,12 @@ // Show the corresponding background element - var indices = getIndices( slide ); - var background = getSlideBackground( indices.h, indices.v ); + var background = slide.slideBackgroundElement; if( background ) { background.style.display = 'block'; + var backgroundContent = slide.slideBackgroundContentElement; + // If the background contains media, load it if( background.hasAttribute( 'data-loaded' ) === false ) { background.setAttribute( 'data-loaded', 'true' ); @@ -3123,7 +3486,7 @@ // Images if( backgroundImage ) { - background.style.backgroundImage = 'url('+ backgroundImage +')'; + backgroundContent.style.backgroundImage = 'url('+ encodeURI( backgroundImage ) +')'; } // Videos else if ( backgroundVideo && !isSpeakerNotes() ) { @@ -3151,10 +3514,10 @@ video.innerHTML += ''; } ); - background.appendChild( video ); + backgroundContent.appendChild( video ); } // Iframes - else if( backgroundIframe ) { + else if( backgroundIframe && options.excludeIframes !== true ) { var iframe = document.createElement( 'iframe' ); iframe.setAttribute( 'allowfullscreen', '' ); iframe.setAttribute( 'mozallowfullscreen', '' ); @@ -3174,7 +3537,7 @@ iframe.style.maxHeight = '100%'; iframe.style.maxWidth = '100%'; - background.appendChild( iframe ); + backgroundContent.appendChild( iframe ); } } @@ -3183,23 +3546,34 @@ } /** - * Called when the given slide is moved outside of the - * configured view distance. + * Unloads and hides the given slide. This is called when the + * slide is moved outside of the configured view distance. * * @param {HTMLElement} slide */ - function hideSlide( slide ) { + function unloadSlide( slide ) { // Hide the slide element slide.style.display = 'none'; // Hide the corresponding background element - var indices = getIndices( slide ); - var background = getSlideBackground( indices.h, indices.v ); + var background = getSlideBackground( slide ); if( background ) { background.style.display = 'none'; } + // Reset lazy-loaded media elements with src attributes + toArray( slide.querySelectorAll( 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src]' ) ).forEach( function( element ) { + element.setAttribute( 'data-src', element.getAttribute( 'src' ) ); + element.removeAttribute( 'src' ); + } ); + + // Reset lazy-loaded media elements with children + toArray( slide.querySelectorAll( 'video[data-lazy-loaded] source[src], audio source[src]' ) ).forEach( function( source ) { + source.setAttribute( 'data-src', source.getAttribute( 'src' ) ); + source.removeAttribute( 'src' ); + } ); + } /** @@ -3213,13 +3587,27 @@ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR ); var routes = { - left: indexh > 0 || config.loop, - right: indexh < horizontalSlides.length - 1 || config.loop, + left: indexh > 0, + right: indexh < horizontalSlides.length - 1, up: indexv > 0, down: indexv < verticalSlides.length - 1 }; - // reverse horizontal controls for rtl + // Looped presentations can always be navigated as long as + // there are slides available + if( config.loop ) { + if( horizontalSlides.length > 1 ) { + routes.left = true; + routes.right = true; + } + + if( verticalSlides.length > 1 ) { + routes.up = true; + routes.down = true; + } + } + + // Reverse horizontal controls for rtl if( config.rtl ) { var left = routes.left; routes.left = routes.right; @@ -3275,6 +3663,13 @@ _appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' ); _appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' ); + // Always show media controls on mobile devices + if( isMobileDevice ) { + toArray( dom.slides.querySelectorAll( 'video, audio' ) ).forEach( function( el ) { + el.controls = true; + } ); + } + } /** @@ -3311,9 +3706,16 @@ if( autoplay && typeof el.play === 'function' ) { + // If the media is ready, start playback if( el.readyState > 1 ) { startEmbeddedMedia( { target: el } ); } + // Mobile devices never fire a loaded event so instead + // of waiting, we initiate playback + else if( isMobileDevice ) { + el.play(); + } + // If the media isn't loaded, wait before playing else { el.removeEventListener( 'loadeddata', startEmbeddedMedia ); // remove first to avoid dupes el.addEventListener( 'loadeddata', startEmbeddedMedia ); @@ -3419,7 +3821,12 @@ * * @param {HTMLElement} element */ - function stopEmbeddedContent( element ) { + function stopEmbeddedContent( element, options ) { + + options = extend( { + // Defaults + unloadIframes: true + }, options || {} ); if( element && element.parentNode ) { // HTML5 media elements @@ -3450,13 +3857,15 @@ } }); - // Lazy loading iframes - toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) { - // Only removing the src doesn't actually unload the frame - // in all browsers (Firefox) so we set it to blank first - el.setAttribute( 'src', 'about:blank' ); - el.removeAttribute( 'src' ); - } ); + if( options.unloadIframes === true ) { + // Unload lazy-loaded iframes + toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) { + // Only removing the src doesn't actually unload the frame + // in all browsers (Firefox) so we set it to blank first + el.setAttribute( 'src', 'about:blank' ); + el.removeAttribute( 'src' ); + } ); + } } } @@ -3571,12 +3980,15 @@ var element; // Ensure the named link is a valid HTML ID attribute - if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) { - // Find the slide with the specified ID - element = document.getElementById( name ); + try { + element = document.getElementById( decodeURIComponent( name ) ); } + catch ( error ) { } - if( element ) { + // Ensure that we're not already on a slide with the same name + var isSameNameAsCurrentSlide = currentSlide ? currentSlide.getAttribute( 'id' ) === name : false; + + if( element && !isSameNameAsCurrentSlide ) { // Find the position of the named slide and navigate to it var indices = Reveal.getIndices( element ); slide( indices.h, indices.v ); @@ -3587,12 +3999,22 @@ } } else { - // Read the index components of the hash - var h = parseInt( bits[0], 10 ) || 0, - v = parseInt( bits[1], 10 ) || 0; + var hashIndexBase = config.hashOneBasedIndex ? 1 : 0; - if( h !== indexh || v !== indexv ) { - slide( h, v ); + // Read the index components of the hash + var h = ( parseInt( bits[0], 10 ) - hashIndexBase ) || 0, + v = ( parseInt( bits[1], 10 ) - hashIndexBase ) || 0, + f; + + if( config.fragmentInURL ) { + f = parseInt( bits[2], 10 ); + if( isNaN( f ) ) { + f = undefined; + } + } + + if( h !== indexh || v !== indexv || f !== undefined ) { + slide( h, v, f ); } } @@ -3617,25 +4039,7 @@ writeURLTimeout = setTimeout( writeURL, delay ); } else if( currentSlide ) { - var url = '/'; - - // Attempt to create a named link based on the slide's ID - var id = currentSlide.getAttribute( 'id' ); - if( id ) { - id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' ); - } - - // If the current slide has an ID, use that as a named link - if( typeof id === 'string' && id.length ) { - url = '/' + id; - } - // Otherwise use the /h/v index - else { - if( indexh > 0 || indexv > 0 ) url += indexh; - if( indexv > 0 ) url += '/' + indexv; - } - - window.location.hash = url; + window.location.hash = locationHash(); } } @@ -3738,31 +4142,19 @@ * defined, have a background element so as long as the * index is valid an element will be returned. * - * @param {number} x Horizontal background index + * @param {mixed} x Horizontal background index OR a slide + * HTML element * @param {number} y Vertical background index * @return {(HTMLElement[]|*)} */ function getSlideBackground( x, y ) { - // When printing to PDF the slide backgrounds are nested - // inside of the slides - if( isPrintingPDF() ) { - var slide = getSlide( x, y ); - if( slide ) { - return slide.slideBackgroundElement; - } - - return undefined; + var slide = typeof x === 'number' ? getSlide( x, y ) : x; + if( slide ) { + return slide.slideBackgroundElement; } - var horizontalBackground = dom.wrapper.querySelectorAll( '.backgrounds>.slide-background' )[ x ]; - var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' ); - - if( verticalBackgrounds && verticalBackgrounds.length && typeof y === 'number' ) { - return verticalBackgrounds ? verticalBackgrounds[ y ] : undefined; - } - - return horizontalBackground; + return undefined; } @@ -3856,9 +4248,11 @@ * the fragment within the fragments list. * * @param {object[]|*} fragments + * @param {boolean} grouped If true the returned array will contain + * nested arrays for all fragments with the same index * @return {object[]} sorted Sorted array of fragments */ - function sortFragments( fragments ) { + function sortFragments( fragments, grouped ) { fragments = toArray( fragments ); @@ -3901,7 +4295,7 @@ index ++; } ); - return sorted; + return grouped === true ? ordered : sorted; } @@ -3982,6 +4376,9 @@ updateControls(); updateProgress(); + if( config.fragmentInURL ) { + writeURL(); + } return !!( fragmentsShown.length || fragmentsHidden.length ); @@ -4024,7 +4421,7 @@ cancelAutoSlide(); - if( currentSlide ) { + if( currentSlide && config.autoSlide !== false ) { var fragment = currentSlide.querySelector( '.current-fragment' ); @@ -4142,6 +4539,8 @@ function navigateRight() { + hasNavigatedRight = true; + // Reverse for RTL if( config.rtl ) { if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) { @@ -4166,6 +4565,8 @@ function navigateDown() { + hasNavigatedDown = true; + // Prioritize revealing fragments if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) { slide( indexh, indexv + 1 ); @@ -4212,9 +4613,22 @@ */ function navigateNext() { + hasNavigatedRight = true; + hasNavigatedDown = true; + // Prioritize revealing fragments if( nextFragment() === false ) { - if( availableRoutes().down ) { + + var routes = availableRoutes(); + + // When looping is enabled `routes.down` is always available + // so we need a separate check for when we've reached the + // end of a stack and should move horizontally + if( routes.down && routes.right && config.loop && Reveal.isLastVerticalSlide( currentSlide ) ) { + routes.down = false; + } + + if( routes.down ) { navigateDown(); } else if( config.rtl ) { @@ -4284,7 +4698,7 @@ // If there's a condition specified and it returns false, // ignore this event - if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) { + if( typeof config.keyboardCondition === 'function' && config.keyboardCondition(event) === false ) { return true; } @@ -4349,7 +4763,31 @@ } - // 2. System defined key bindings + // 2. Registered custom key bindings + if( triggered === false ) { + + for( key in registeredKeyBindings ) { + + // Check if this binding matches the pressed key + if( parseInt( key, 10 ) === event.keyCode ) { + + var action = registeredKeyBindings[ key ].callback; + + // Callback function + if( typeof action === 'function' ) { + action.apply( null, [ event ] ); + } + // String shortcuts to reveal.js API + else if( typeof action === 'string' && typeof Reveal[ action ] === 'function' ) { + Reveal[ action ].call(); + } + + triggered = true; + } + } + } + + // 3. System defined key bindings if( triggered === false ) { // Assume true and try to prove false @@ -4880,7 +5318,7 @@ this.context.beginPath(); this.context.arc( x, y, radius, 0, Math.PI * 2, false ); this.context.lineWidth = this.thickness; - this.context.strokeStyle = '#666'; + this.context.strokeStyle = 'rgba( 255, 255, 255, 0.2 )'; this.context.stroke(); if( this.playing ) { @@ -4944,7 +5382,10 @@ initialize: initialize, reinitialize: reinitialize, configure: configure, + sync: sync, + syncSlide: syncSlide, + syncFragments: syncFragments, // Navigation methods slide: slide, @@ -4997,6 +5438,11 @@ isOverview: isOverview, isPaused: isPaused, isAutoSliding: isAutoSliding, + isSpeakerNotes: isSpeakerNotes, + + // Slide preloading + loadSlide: loadSlide, + unloadSlide: unloadSlide, // Adds or removes all internal event listeners (such as keyboard) addEventListeners: addEventListeners, @@ -5076,7 +5522,7 @@ // Returns true if we're currently on the last slide isLastSlide: function() { if( currentSlide ) { - // Does this slide has next a sibling? + // Does this slide have a next sibling? if( currentSlide.nextElementSibling ) return false; // If it's vertical, does its parent have a next sibling? @@ -5088,6 +5534,19 @@ return false; }, + // Returns true if we're on the last slide in the current + // vertical stack + isLastVerticalSlide: function() { + if( currentSlide && isVerticalSlide( currentSlide ) ) { + // Does this slide have a next sibling? + if( currentSlide.nextElementSibling ) return false; + + return true; + } + + return false; + }, + // Checks if reveal.js has been loaded and is ready for use isReady: function() { return loaded; @@ -5105,6 +5564,12 @@ } }, + // Adds a custom key binding + addKeyBinding: addKeyBinding, + + // Removes a custom key binding + removeKeyBinding: removeKeyBinding, + // Programatically triggers a keyboard event triggerKey: function( keyCode ) { onDocumentKeyDown( { keyCode: keyCode } ); diff --git a/scripts/reveal-js.patch b/scripts/reveal-js.patch new file mode 100644 index 000000000..70306926a --- /dev/null +++ b/scripts/reveal-js.patch @@ -0,0 +1,25 @@ +--- reveal.js.orig 2018-08-01 10:37:51.000000000 +0200 ++++ reveal.js 2019-02-11 21:25:39.396198927 +0100 +@@ -383,6 +383,14 @@ + } + + /** ++ * Restarts up the presentation if the client is capable. ++ */ ++ function reinitialize() { ++ initialized = false; ++ initialize(config); ++ } ++ ++ /** + * Inspect the client to see what it's capable of, this + * should only happens once per runtime. + */ +@@ -5372,6 +5380,7 @@ + VERSION: VERSION, + + initialize: initialize, ++ reinitialize: reinitialize, + configure: configure, + + sync: sync,