feat(slides): upgrade reveal.js from v3.8.0 to v4.1.0 (#2018)

This commit is contained in:
koutbo6 2021-01-20 02:22:34 +03:00 committed by GitHub
commit de6860b8b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 204 additions and 1085 deletions

3
.gitignore vendored
View file

@ -10,3 +10,6 @@ node_modules/
# Hugo
resources/
# Mac
.DS_Store

View file

@ -12,7 +12,30 @@ slides:
theme: black
# Choose a code highlighting style (if highlighting enabled in `params.toml`)
# Light style: github. Dark style: dracula (default).
# Available highlight themes listed in: https://highlightjs.org/static/demo/
# Use lower case names and replace space with hyphen '-'
highlight_style: dracula
diagram: true
diagram_options:
# Mermaid diagram themes include: default,base,dark,neutral,forest
theme: base
# RevealJS slide options.
# Options are named using the snake case equivalent of those in the RevealJS docs.
reveal_options:
controls: true
progress: true
slide_number: c/t # true | false | h.v | h/v | c | c/t
center: true
rtl: false
mouse_wheel: true
transition: fade # none/fade/slide/convex/concave/zoom
transitionSpeed: default # default/fast/slow
background_transition: slide # none/fade/slide/convex/concave/zoom
touch: true
loop: false
menu_enabled: true
---
# Title

View file

@ -1,31 +0,0 @@
/*************************************************
* Reveal JS
**************************************************/
/* This is a copy of MathJax's `.mjx-chtml` with font-family added to override `.reveal span`. */
/* See https://github.com/hakimel/reveal.js/issues/1924 */
.reveal span.mjx-chtml {
display: inline-block;
line-height: 0;
text-indent: 0;
text-align: left;
text-transform: none;
font-style: normal;
font-weight: normal;
font-size: 100%;
font-size-adjust: none;
letter-spacing: normal;
word-wrap: normal;
word-spacing: normal;
white-space: nowrap;
float: none;
direction: ltr;
max-width: none;
max-height: none;
min-width: 0;
min-height: 0;
border: 0;
margin: 0;
padding: 1px 0;
font-family: MJXc-TeX-math-I,MJXc-TeX-math-Ix,MJXc-TeX-math-Iw;
}

View file

@ -0,0 +1,148 @@
/*
global RevealMarkdown, RevealHighlight, RevealSearch, RevealNotes, RevealMath, RevealZoom, Reveal, mermaid, RevealMenu
*/
import * as params from '@params';
import {fixMermaid} from './wowchemy-utils';
// Enable core slide features.
var enabledPlugins = [RevealMarkdown, RevealHighlight, RevealSearch, RevealNotes, RevealMath, RevealZoom];
const isObject = function (o) {
return o === Object(o) && !isArray(o) && typeof o !== 'function';
};
const isArray = function (a) {
return Array.isArray(a);
};
const toCamelCase = function (s) {
return s.replace(/([-_][a-z])/gi, function (term) {
return term.toUpperCase().replace('-', '').replace('_', '');
});
};
const keysToCamelCase = function (o) {
if (isObject(o)) {
const n = {};
Object.keys(o).forEach(function (k) {
n[toCamelCase(k)] = keysToCamelCase(o[k]);
});
return n;
} else if (isArray(o)) {
return o.map(function (i) {
return keysToCamelCase(i);
});
}
return o;
};
// reveal configurations can be included in front matter under slides.reveal
var pluginOptions = {};
if (typeof params.slides.reveal_options !== 'undefined') {
pluginOptions = params.slides.reveal_options;
}
pluginOptions = keysToCamelCase(pluginOptions);
//enable menu by default if not set
if (typeof pluginOptions.menu_enabled !== 'undefined') {
pluginOptions.menu_enabled = true;
}
// configure menu if enabled
if (pluginOptions.menu_enabled) {
enabledPlugins.push(RevealMenu);
}
pluginOptions['plugins'] = enabledPlugins;
Reveal.initialize(pluginOptions);
// The following functions are used to render Mermaid diagrams
// after Reveal slides have been successfully loaded
// since content of slides is lazy loaded, if diagrams are
// rendered at start of presentation their sizes will be off
// get all slides that are:
// 1- data loaded
// 2- display set to block
// 3- has a mermaid element that is not processed (data-processed dne)
function mermaidSlidesReadyToRender(mslide) {
var diag = mslide.querySelector('.mermaid');
if (diag) {
var background = mslide.slideBackgroundElement;
// render if we are 1 slide away horizontally
// current visible slide index
var currentHorizontalIndex = Reveal.getState()['indexh'];
// mermaid slide index
var diagramSlideIndex = Reveal.getIndices(mslide)['h'];
if (
// find slides with non-rendered mermaid tags
// these will not have the attribute data-processed
!diag.hasAttribute('data-processed') &&
// check also that reveal slide is already loaded
// reveal slides seem to be lazily loaded
// things could be easier if reveal had a slide-loaded event
background.hasAttribute('data-loaded') &&
// loaded slides must also have the display attribute set to block
background.style.display === 'block' &&
// render diagrams that are 1 slide away
diagramSlideIndex - currentHorizontalIndex <= 1
)
return mslide;
}
return null;
}
function renderMermaidSlides() {
// find all slides with diagrams that are ready to render
var diagramSlides = Reveal.getSlides().filter(mermaidSlidesReadyToRender);
// render the diagram for each slide with ready to render diagrams
diagramSlides.forEach(function (item) {
mermaid.init(item.querySelector('.mermaid'));
});
}
// render mermaid slides for slides that are ready
Reveal.on('slidechanged', function () {
renderMermaidSlides();
});
// render mermaid slides for slides that are ready on startup
Reveal.on('Ready', function () {
if (Reveal.isReady()) {
renderMermaidSlides();
}
});
// Disable Mermaid by default.
if (typeof params.slides.diagram === 'undefined') {
params.slides.diagram = false;
}
// Configure Mermaid only if diagrams are enabled.
if (params.slides.diagram) {
//mermaid options
// mermaid: front matter configuration can be used to set mermaid options
// You can also use directives (see mermaid documentation)
var mermaidOptions = {};
if (typeof params.slides.diagram_options !== 'undefined') {
mermaidOptions = params.slides.diagram_options;
}
// `startOnLoad` must be false since diagrams are lazily rendered.
mermaidOptions['startOnLoad'] = false;
mermaid.initialize(mermaidOptions);
// Fix Mermaid conflict with Hightlight JS.
document.addEventListener('DOMContentLoaded', function () {
fixMermaid();
});
}

View file

@ -187,7 +187,7 @@ function renderThemeVariation(isDarkTheme, themeMode = 2, init = false) {
console.debug('Initializing Mermaid with light theme');
if (init) {
/** @namespace window.mermaid **/
window.mermaid.initialize({theme: 'default', securityLevel: 'loose'});
window.mermaid.initialize({startOnLoad: true, theme: 'default', securityLevel: 'loose'});
} else {
// Have to reload to re-initialise Mermaid with the new theme and re-parse the Mermaid code blocks.
location.reload();
@ -213,7 +213,7 @@ function renderThemeVariation(isDarkTheme, themeMode = 2, init = false) {
console.debug('Initializing Mermaid with dark theme');
if (init) {
/** @namespace window.mermaid **/
window.mermaid.initialize({theme: 'dark', securityLevel: 'loose'});
window.mermaid.initialize({startOnLoad: true, theme: 'dark', securityLevel: 'loose'});
} else {
// Have to reload to re-initialise Mermaid with the new theme and re-parse the Mermaid code blocks.
location.reload();

View file

@ -57,8 +57,8 @@
sri = "sha512-I7w3ZdSFzw5j3jU3ZkNikBNeIrl3i+hEuEdwNmqUJvwNcaBUNcijnP2gd9DtGlgVYDplfjGoD8vTNsID+lCjqg=="
url = "https://cdnjs.cloudflare.com/ajax/libs/anchor-js/%s/anchor.min.js"
[js.mermaid]
version = "8.8.0"
sri = "sha512-ja+hSBi4JDtjSqc4LTBsSwuBT3tdZ3oKYKd07lTVYmCnTCor56AnRql00ssqnTOR9Ss4gOP/ROGB3SfcJnZkeg=="
version = "8.8.4"
sri = "sha512-as1BF4+iHZ3BVO6LLDQ7zrbvTXM+c/1iZ1qII/c3c4L8Rn5tHLpFUtpaEtBNS92f+xGsCzsD7b62XP3XYap6oA=="
url = "https://cdnjs.cloudflare.com/ajax/libs/mermaid/%s/mermaid.min.js"
[js.lazysizes]
version = "5.2.2"

View file

@ -5,12 +5,13 @@
{{ .Scratch.Set "media_dir" $media_dir }}
{{ $css := site.Data.assets.css }}
{{ $cdn_url_reveal := "https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.8.0" }}
{{ $cdn_url_reveal := "https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.1.0" }}
{{ $js := site.Data.assets.js }}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="generator" content="Source Themes Academic {{ site.Data.academic.version }}">
<meta name="generator" content="Wowchemy {{ site.Data.wowchemy.version }} for Hugo">
{{ if site.Home.OutputFormats.Get "WebAppManifest" }}
<link rel="manifest" href="{{ "index.webmanifest" | relLangURL }}">
@ -23,55 +24,42 @@
<title>{{ .Title }} | {{ site.Title }}</title>
<link rel="stylesheet" href="{{ $cdn_url_reveal }}/css/reveal.min.css">
<link rel="stylesheet" href="{{ $cdn_url_reveal }}/reveal.min.css">
{{- $theme := $.Param "slides.theme" | default "black" -}}
<link rel="stylesheet" href="{{ $cdn_url_reveal }}/css/theme/{{ $theme }}.min.css">
<link rel="stylesheet" href="{{ $cdn_url_reveal }}/theme/{{ $theme }}.min.css">
{{- $highlight_style := $.Param "slides.highlight_style" | default "dracula" -}}
{{ printf "<link rel=\"stylesheet\" href=\"%s\" crossorigin=\"anonymous\">" (printf $css.highlight.url $css.highlight.version $highlight_style) | safeHTML }}
{{ printf "<link rel=\"stylesheet\" href=\"%s\" crossorigin=\"anonymous\" id=\"highlight-theme\">" (printf $css.highlight.url $css.highlight.version $highlight_style) | safeHTML }}
{{ $css := resources.Get "css/reveal.css" }}
{{ $css_custom := resources.Get "css/reveal_custom.css" }}
{{ $style := slice $css $css_custom | resources.Concat "css/reveal_custom.css" | resources.Minify }}
{{ $style := slice $css_custom | resources.Concat "css/reveal_custom.css" | resources.Minify }}
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
<!-- Printing and PDF exports -->
<script>
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = window.location.search.match( /print-pdf/gi ) ? '{{ $cdn_url_reveal }}/css/print/pdf.css' : '{{ $cdn_url_reveal }}/css/print/paper.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
</script>
</head>
<body>
{{ block "main" . }}{{ end }}
<script src="{{ $cdn_url_reveal }}/js/reveal.min.js"></script>
<script src="{{ $cdn_url_reveal }}/reveal.min.js" integrity="sha512-Xu/cezKABTI81MGnaBm64vdiS7XkttHeYGOgr2Mdga0bTplSBGongLq2lhK2HwL79wefKM0u4uTCLD0ha1sRzQ==" crossorigin="anonymous"></script>
<script src="{{ $cdn_url_reveal }}/plugin/markdown/markdown.min.js" integrity="sha512-eZZqO4ECmVvGhCt+6VZ7ian2bCu4S6yrjSFH9fXLY1zTokpAWsxAxQwM4x6+7G+G4ha5tFIe0jY0XjpBUqS49Q==" crossorigin="anonymous"></script>
<script src="{{ $cdn_url_reveal }}/plugin/highlight/highlight.min.js" integrity="sha512-NA5UCab7xDKQPXGsmIp8iEuId5BAKGPiqHZsZQcBuySfp1n3dZrwBDKpPNL23Db5upay1nULxU14JV1ggFOD2A==" crossorigin="anonymous"></script>
<script src="{{ $cdn_url_reveal }}/plugin/notes/notes.min.js" integrity="sha512-FYeeQscKqibmYKr0+nE2+fN5prBsFwgjsBVwkrA88O6mN2+ai6EvRkSi6txuhXlWsyK1MUfoV+94+q6HLouJSQ==" crossorigin="anonymous"></script>
<script src="{{ $cdn_url_reveal }}/plugin/search/search.min.js" integrity="sha512-2yh3Y2gEdboEnZb9d0QZP05N3e0jTkcjhbG9xYL97mbnZ53IXzF5R2TCTmSrtuspDyJ5FWBSh+8izjiGjVdLWw==" crossorigin="anonymous"></script>
<script src="{{ $cdn_url_reveal }}/plugin/math/math.min.js" integrity="sha512-FUzQmRJDLL111zqJg/vN1YzQFQtZNWfBH2VaOiv30dXRXgaTRn3F/Ibda92klSAVjfz3Q9UqS88R4RF4Ip01fQ==" crossorigin="anonymous"></script>
<script src="{{ $cdn_url_reveal }}/plugin/zoom/zoom.min.js" integrity="sha512-zPYOPjR7Hg9BPUYkfNlvVtrC37QlYwq/7mI42VTuXKTcNBp7QvMfuqUTMesOf74OrZ3AEdxJGndGSrJG9O2j5Q==" crossorigin="anonymous"></script>
<script>
window.revealPlugins = { dependencies: [
// Interpret Markdown in <section> elements.
{ src: '{{ $cdn_url_reveal }}/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: '{{ $cdn_url_reveal }}/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
// Enable code highlighting.
{ src: '{{ $cdn_url_reveal }}/plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
// Enable zooming.
{ src: '{{ $cdn_url_reveal }}/plugin/zoom-js/zoom.js', async: true },
// Enable math.
{ src: '{{ $cdn_url_reveal }}/plugin/math/math.js', async: true },
// Enable exporting deck to PDF.
{ src: '{{ $cdn_url_reveal }}/plugin/print-pdf/print-pdf.js', async: true },
// Enable speaker notes. Notes plugin must be loaded locally as CDN is missing `notes.html`.
{ src: '{{ "js/vendor/reveal.js/plugin/notes/notes.js" | relURL }}', async: true }
]};
{{/* Third-party Reveal plugins. */}}
{{ if $.Param "slides.reveal_options.menu_enabled" | default true}}
<script src="https://cdn.jsdelivr.net/npm/reveal.js-menu@2.1.0/plugin.js" integrity="sha256-M6JwAjnRAWmi+sbXURR/yAhWZKYhAw7YXnnLvIxrdGs=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/reveal.js-menu@2.1.0/menu.js" integrity="sha256-l14dklFcW5mWar6w/9KaW0fWVerf3mYr7Wt0+rXzFAA=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js-menu@2.1.0/menu.css" integrity="sha256-0fU8HKLaTjgzfaV9CgSqbsN8ilA3zo6zK1M6rlgULd8=" crossorigin="anonymous">
{{ end }}
let revealDefaults = { center: true, controls: true, history: true, progress: true, transition: 'slide', mouseWheel: true };
let revealOptions = Object.assign({}, revealDefaults, revealPlugins);
Reveal.initialize(revealOptions);
</script>
{{ if $.Param "slides.diagram" | default false}}
{{ printf "<script src=\"%s\" integrity=\"%s\" crossorigin=\"anonymous\" title=\"mermaid\"></script>" (printf $js.mermaid.url $js.mermaid.version) $js.mermaid.sri | safeHTML }}
{{ end }}
{{ $slidejs := resources.Get "js/wowchemy-slides.js" | js.Build (dict "params" (dict "slides" $.Params.slides )) }}
<script src="{{ $slidejs.RelPermalink }}"></script>
</body>
</html>

View file

@ -1,834 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Speaker Notes</title>
<style>
body {
font-family: Helvetica;
font-size: 18px;
}
#current-slide,
#upcoming-slide,
#speaker-controls {
padding: 6px;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
#current-slide iframe,
#upcoming-slide iframe {
width: 100%;
height: 100%;
border: 1px solid #ddd;
}
#current-slide .label,
#upcoming-slide .label {
position: absolute;
top: 10px;
left: 10px;
z-index: 2;
}
#connection-status {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 20;
padding: 30% 20% 20% 20%;
font-size: 18px;
color: #222;
background: #fff;
text-align: center;
box-sizing: border-box;
line-height: 1.4;
}
.overlay-element {
height: 34px;
line-height: 34px;
padding: 0 10px;
text-shadow: none;
background: rgba( 220, 220, 220, 0.8 );
color: #222;
font-size: 14px;
}
.overlay-element.interactive:hover {
background: rgba( 220, 220, 220, 1 );
}
#current-slide {
position: absolute;
width: 60%;
height: 100%;
top: 0;
left: 0;
padding-right: 0;
}
#upcoming-slide {
position: absolute;
width: 40%;
height: 40%;
right: 0;
top: 0;
}
/* Speaker controls */
#speaker-controls {
position: absolute;
top: 40%;
right: 0;
width: 40%;
height: 60%;
overflow: auto;
font-size: 18px;
}
.speaker-controls-time.hidden,
.speaker-controls-notes.hidden {
display: none;
}
.speaker-controls-time .label,
.speaker-controls-pace .label,
.speaker-controls-notes .label {
text-transform: uppercase;
font-weight: normal;
font-size: 0.66em;
color: #666;
margin: 0;
}
.speaker-controls-time, .speaker-controls-pace {
border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
margin-bottom: 10px;
padding: 10px 16px;
padding-bottom: 20px;
cursor: pointer;
}
.speaker-controls-time .reset-button {
opacity: 0;
float: right;
color: #666;
text-decoration: none;
}
.speaker-controls-time:hover .reset-button {
opacity: 1;
}
.speaker-controls-time .timer,
.speaker-controls-time .clock {
width: 50%;
}
.speaker-controls-time .timer,
.speaker-controls-time .clock,
.speaker-controls-time .pacing .hours-value,
.speaker-controls-time .pacing .minutes-value,
.speaker-controls-time .pacing .seconds-value {
font-size: 1.9em;
}
.speaker-controls-time .timer {
float: left;
}
.speaker-controls-time .clock {
float: right;
text-align: right;
}
.speaker-controls-time span.mute {
opacity: 0.3;
}
.speaker-controls-time .pacing-title {
margin-top: 5px;
}
.speaker-controls-time .pacing.ahead {
color: blue;
}
.speaker-controls-time .pacing.on-track {
color: green;
}
.speaker-controls-time .pacing.behind {
color: red;
}
.speaker-controls-notes {
padding: 10px 16px;
}
.speaker-controls-notes .value {
margin-top: 5px;
line-height: 1.4;
font-size: 1.2em;
}
/* Layout selector */
#speaker-layout {
position: absolute;
top: 10px;
right: 10px;
color: #222;
z-index: 10;
}
#speaker-layout select {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
border: 0;
box-shadow: 0;
cursor: pointer;
opacity: 0;
font-size: 1em;
background-color: transparent;
-moz-appearance: none;
-webkit-appearance: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
#speaker-layout select:focus {
outline: none;
box-shadow: none;
}
.clear {
clear: both;
}
/* Speaker layout: Wide */
body[data-speaker-layout="wide"] #current-slide,
body[data-speaker-layout="wide"] #upcoming-slide {
width: 50%;
height: 45%;
padding: 6px;
}
body[data-speaker-layout="wide"] #current-slide {
top: 0;
left: 0;
}
body[data-speaker-layout="wide"] #upcoming-slide {
top: 0;
left: 50%;
}
body[data-speaker-layout="wide"] #speaker-controls {
top: 45%;
left: 0;
width: 100%;
height: 50%;
font-size: 1.25em;
}
/* Speaker layout: Tall */
body[data-speaker-layout="tall"] #current-slide,
body[data-speaker-layout="tall"] #upcoming-slide {
width: 45%;
height: 50%;
padding: 6px;
}
body[data-speaker-layout="tall"] #current-slide {
top: 0;
left: 0;
}
body[data-speaker-layout="tall"] #upcoming-slide {
top: 50%;
left: 0;
}
body[data-speaker-layout="tall"] #speaker-controls {
padding-top: 40px;
top: 0;
left: 45%;
width: 55%;
height: 100%;
font-size: 1.25em;
}
/* Speaker layout: Notes only */
body[data-speaker-layout="notes-only"] #current-slide,
body[data-speaker-layout="notes-only"] #upcoming-slide {
display: none;
}
body[data-speaker-layout="notes-only"] #speaker-controls {
padding-top: 40px;
top: 0;
left: 0;
width: 100%;
height: 100%;
font-size: 1.25em;
}
@media screen and (max-width: 1080px) {
body[data-speaker-layout="default"] #speaker-controls {
font-size: 16px;
}
}
@media screen and (max-width: 900px) {
body[data-speaker-layout="default"] #speaker-controls {
font-size: 14px;
}
}
@media screen and (max-width: 800px) {
body[data-speaker-layout="default"] #speaker-controls {
font-size: 12px;
}
}
</style>
</head>
<body>
<div id="connection-status">Loading speaker view...</div>
<div id="current-slide"></div>
<div id="upcoming-slide"><span class="overlay-element label">Upcoming</span></div>
<div id="speaker-controls">
<div class="speaker-controls-time">
<h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
<div class="clock">
<span class="clock-value">0:00 AM</span>
</div>
<div class="timer">
<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
</div>
<div class="clear"></div>
<h4 class="label pacing-title" style="display: none">Pacing Time to finish current slide</h4>
<div class="pacing" style="display: none">
<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
</div>
</div>
<div class="speaker-controls-notes hidden">
<h4 class="label">Notes</h4>
<div class="value"></div>
</div>
</div>
<div id="speaker-layout" class="overlay-element interactive">
<span class="speaker-layout-label"></span>
<select class="speaker-layout-dropdown"></select>
</div>
<script src="../../plugin/markdown/marked.js"></script>
<script>
(function() {
var notes,
notesValue,
currentState,
currentSlide,
upcomingSlide,
layoutLabel,
layoutDropdown,
pendingCalls = {},
lastRevealApiCallId = 0,
connected = false;
var SPEAKER_LAYOUTS = {
'default': 'Default',
'wide': 'Wide',
'tall': 'Tall',
'notes-only': 'Notes only'
};
setupLayout();
var connectionStatus = document.querySelector( '#connection-status' );
var connectionTimeout = setTimeout( function() {
connectionStatus.innerHTML = 'Error connecting to main window.<br>Please try closing and reopening the speaker view.';
}, 5000 );
window.addEventListener( 'message', function( event ) {
clearTimeout( connectionTimeout );
connectionStatus.style.display = 'none';
var data = JSON.parse( event.data );
// The overview mode is only useful to the reveal.js instance
// where navigation occurs so we don't sync it
if( data.state ) delete data.state.overview;
// Messages sent by the notes plugin inside of the main window
if( data && data.namespace === 'reveal-notes' ) {
if( data.type === 'connect' ) {
handleConnectMessage( data );
}
else if( data.type === 'state' ) {
handleStateMessage( data );
}
else if( data.type === 'return' ) {
pendingCalls[data.callId](data.result);
delete pendingCalls[data.callId];
}
}
// Messages sent by the reveal.js inside of the current slide preview
else if( data && data.namespace === 'reveal' ) {
if( /ready/.test( data.eventName ) ) {
// Send a message back to notify that the handshake is complete
window.opener.postMessage( JSON.stringify({ namespace: 'reveal-notes', type: 'connected'} ), '*' );
}
else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
window.opener.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ]} ), '*' );
}
}
} );
/**
* Asynchronously calls the Reveal.js API of the main frame.
*/
function callRevealApi( methodName, methodArguments, callback ) {
var callId = ++lastRevealApiCallId;
pendingCalls[callId] = callback;
window.opener.postMessage( JSON.stringify( {
namespace: 'reveal-notes',
type: 'call',
callId: callId,
methodName: methodName,
arguments: methodArguments
} ), '*' );
}
/**
* Called when the main window is trying to establish a
* connection.
*/
function handleConnectMessage( data ) {
if( connected === false ) {
connected = true;
setupIframes( data );
setupKeyboard();
setupNotes();
setupTimer();
}
}
/**
* Called when the main window sends an updated state.
*/
function handleStateMessage( data ) {
// Store the most recently set state to avoid circular loops
// applying the same state
currentState = JSON.stringify( data.state );
// No need for updating the notes in case of fragment changes
if ( data.notes ) {
notes.classList.remove( 'hidden' );
notesValue.style.whiteSpace = data.whitespace;
if( data.markdown ) {
notesValue.innerHTML = marked( data.notes );
}
else {
notesValue.innerHTML = data.notes;
}
}
else {
notes.classList.add( 'hidden' );
}
// Update the note slides
currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );
}
// Limit to max one state update per X ms
handleStateMessage = debounce( handleStateMessage, 200 );
/**
* Forward keyboard events to the current slide window.
* This enables keyboard events to work even if focus
* isn't set on the current slide iframe.
*
* Block F5 default handling, it reloads and disconnects
* the speaker notes window.
*/
function setupKeyboard() {
document.addEventListener( 'keydown', function( event ) {
if( event.keyCode === 116 || ( event.metaKey && event.keyCode === 82 ) ) {
event.preventDefault();
return false;
}
currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
} );
}
/**
* Creates the preview iframes.
*/
function setupIframes( data ) {
var params = [
'receiver',
'progress=false',
'history=false',
'transition=none',
'autoSlide=0',
'backgroundTransition=none'
].join( '&' );
var urlSeparator = /\?/.test(data.url) ? '&' : '?';
var hash = '#/' + data.state.indexh + '/' + data.state.indexv;
var currentURL = data.url + urlSeparator + params + '&postMessageEvents=true' + hash;
var upcomingURL = data.url + urlSeparator + params + '&controls=false' + hash;
currentSlide = document.createElement( 'iframe' );
currentSlide.setAttribute( 'width', 1280 );
currentSlide.setAttribute( 'height', 1024 );
currentSlide.setAttribute( 'src', currentURL );
document.querySelector( '#current-slide' ).appendChild( currentSlide );
upcomingSlide = document.createElement( 'iframe' );
upcomingSlide.setAttribute( 'width', 640 );
upcomingSlide.setAttribute( 'height', 512 );
upcomingSlide.setAttribute( 'src', upcomingURL );
document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide );
}
/**
* Setup the notes UI.
*/
function setupNotes() {
notes = document.querySelector( '.speaker-controls-notes' );
notesValue = document.querySelector( '.speaker-controls-notes .value' );
}
function getTimings( callback ) {
callRevealApi( 'getSlidesAttributes', [], function ( slideAttributes ) {
callRevealApi( 'getConfig', [], function ( config ) {
var defaultTiming = config.defaultTiming;
if (defaultTiming == null) {
callback(null);
return;
}
var timings = [];
for ( var i in slideAttributes ) {
var slide = slideAttributes[ i ];
var timing = defaultTiming;
if( slide.hasOwnProperty( 'data-timing' )) {
var t = slide[ 'data-timing' ];
timing = parseInt(t);
if( isNaN(timing) ) {
console.warn("Could not parse timing '" + t + "' of slide " + i + "; using default of " + defaultTiming);
timing = defaultTiming;
}
}
timings.push(timing);
}
callback( timings );
} );
} );
}
/**
* Return the number of seconds allocated for presenting
* all slides up to and including this one.
*/
function getTimeAllocated( timings, callback ) {
callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
var allocated = 0;
for (var i in timings.slice(0, currentSlide + 1)) {
allocated += timings[i];
}
callback( allocated );
} );
}
/**
* Create the timer and clock and start updating them
* at an interval.
*/
function setupTimer() {
var start = new Date(),
timeEl = document.querySelector( '.speaker-controls-time' ),
clockEl = timeEl.querySelector( '.clock-value' ),
hoursEl = timeEl.querySelector( '.hours-value' ),
minutesEl = timeEl.querySelector( '.minutes-value' ),
secondsEl = timeEl.querySelector( '.seconds-value' ),
pacingTitleEl = timeEl.querySelector( '.pacing-title' ),
pacingEl = timeEl.querySelector( '.pacing' ),
pacingHoursEl = pacingEl.querySelector( '.hours-value' ),
pacingMinutesEl = pacingEl.querySelector( '.minutes-value' ),
pacingSecondsEl = pacingEl.querySelector( '.seconds-value' );
var timings = null;
getTimings( function ( _timings ) {
timings = _timings;
if (_timings !== null) {
pacingTitleEl.style.removeProperty('display');
pacingEl.style.removeProperty('display');
}
// Update once directly
_updateTimer();
// Then update every second
setInterval( _updateTimer, 1000 );
} );
function _resetTimer() {
if (timings == null) {
start = new Date();
_updateTimer();
}
else {
// Reset timer to beginning of current slide
getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
var slideEndTiming = slideEndTimingSeconds * 1000;
callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
var currentSlideTiming = timings[currentSlide] * 1000;
var previousSlidesTiming = slideEndTiming - currentSlideTiming;
var now = new Date();
start = new Date(now.getTime() - previousSlidesTiming);
_updateTimer();
} );
} );
}
}
timeEl.addEventListener( 'click', function() {
_resetTimer();
return false;
} );
function _displayTime( hrEl, minEl, secEl, time) {
var sign = Math.sign(time) == -1 ? "-" : "";
time = Math.abs(Math.round(time / 1000));
var seconds = time % 60;
var minutes = Math.floor( time / 60 ) % 60 ;
var hours = Math.floor( time / ( 60 * 60 )) ;
hrEl.innerHTML = sign + zeroPadInteger( hours );
if (hours == 0) {
hrEl.classList.add( 'mute' );
}
else {
hrEl.classList.remove( 'mute' );
}
minEl.innerHTML = ':' + zeroPadInteger( minutes );
if (hours == 0 && minutes == 0) {
minEl.classList.add( 'mute' );
}
else {
minEl.classList.remove( 'mute' );
}
secEl.innerHTML = ':' + zeroPadInteger( seconds );
}
function _updateTimer() {
var diff, hours, minutes, seconds,
now = new Date();
diff = now.getTime() - start.getTime();
clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
_displayTime( hoursEl, minutesEl, secondsEl, diff );
if (timings !== null) {
_updatePacing(diff);
}
}
function _updatePacing(diff) {
getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
var slideEndTiming = slideEndTimingSeconds * 1000;
callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
var currentSlideTiming = timings[currentSlide] * 1000;
var timeLeftCurrentSlide = slideEndTiming - diff;
if (timeLeftCurrentSlide < 0) {
pacingEl.className = 'pacing behind';
}
else if (timeLeftCurrentSlide < currentSlideTiming) {
pacingEl.className = 'pacing on-track';
}
else {
pacingEl.className = 'pacing ahead';
}
_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
} );
} );
}
}
/**
* Sets up the speaker view layout and layout selector.
*/
function setupLayout() {
layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
layoutLabel = document.querySelector( '.speaker-layout-label' );
// Render the list of available layouts
for( var id in SPEAKER_LAYOUTS ) {
var option = document.createElement( 'option' );
option.setAttribute( 'value', id );
option.textContent = SPEAKER_LAYOUTS[ id ];
layoutDropdown.appendChild( option );
}
// Monitor the dropdown for changes
layoutDropdown.addEventListener( 'change', function( event ) {
setLayout( layoutDropdown.value );
}, false );
// Restore any currently persisted layout
setLayout( getLayout() );
}
/**
* Sets a new speaker view layout. The layout is persisted
* in local storage.
*/
function setLayout( value ) {
var title = SPEAKER_LAYOUTS[ value ];
layoutLabel.innerHTML = 'Layout' + ( title ? ( ': ' + title ) : '' );
layoutDropdown.value = value;
document.body.setAttribute( 'data-speaker-layout', value );
// Persist locally
if( supportsLocalStorage() ) {
window.localStorage.setItem( 'reveal-speaker-layout', value );
}
}
/**
* Returns the ID of the most recently set speaker layout
* or our default layout if none has been set.
*/
function getLayout() {
if( supportsLocalStorage() ) {
var layout = window.localStorage.getItem( 'reveal-speaker-layout' );
if( layout ) {
return layout;
}
}
// Default to the first record in the layouts hash
for( var id in SPEAKER_LAYOUTS ) {
return id;
}
}
function supportsLocalStorage() {
try {
localStorage.setItem('test', 'test');
localStorage.removeItem('test');
return true;
}
catch( e ) {
return false;
}
}
function zeroPadInteger( num ) {
var str = '00' + parseInt( num );
return str.substring( str.length - 2 );
}
/**
* Limits the frequency at which a function can be called.
*/
function debounce( fn, ms ) {
var lastTime = 0,
timeout;
return function() {
var args = arguments;
var context = this;
clearTimeout( timeout );
var timeSinceLastCall = Date.now() - lastTime;
if( timeSinceLastCall > ms ) {
fn.apply( context, args );
lastTime = Date.now();
}
else {
timeout = setTimeout( function() {
fn.apply( context, args );
lastTime = Date.now();
}, ms - timeSinceLastCall );
}
}
}
})();
</script>
</body>
</html>

View file

@ -1,178 +0,0 @@
/**
* Handles opening of and synchronization with the reveal.js
* notes window.
*
* Handshake process:
* 1. This window posts 'connect' to notes window
* - Includes URL of presentation to show
* 2. Notes window responds with 'connected' when it is available
* 3. This window proceeds to send the current presentation state
* to the notes window
*/
var RevealNotes = (function() {
var notesPopup = null;
function openNotes( notesFilePath ) {
if (notesPopup && !notesPopup.closed) {
notesPopup.focus();
return;
}
if( !notesFilePath ) {
var jsFileLocation = document.querySelector('script[src$="notes.js"]').src; // this js file path
jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, ''); // the js folder path
notesFilePath = jsFileLocation + 'notes.html';
}
notesPopup = window.open( notesFilePath, 'reveal.js - Notes', 'width=1100,height=700' );
if( !notesPopup ) {
alert( 'Speaker view popup failed to open. Please make sure popups are allowed and reopen the speaker view.' );
return;
}
/**
* Connect to the notes window through a postmessage handshake.
* Using postmessage enables us to work in situations where the
* origins differ, such as a presentation being opened from the
* file system.
*/
function connect() {
// Keep trying to connect until we get a 'connected' message back
var connectInterval = setInterval( function() {
notesPopup.postMessage( JSON.stringify( {
namespace: 'reveal-notes',
type: 'connect',
url: window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search,
state: Reveal.getState()
} ), '*' );
}, 500 );
window.addEventListener( 'message', function( event ) {
var data = JSON.parse( event.data );
if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) {
clearInterval( connectInterval );
onConnected();
}
if( data && data.namespace === 'reveal-notes' && data.type === 'call' ) {
callRevealApi( data.methodName, data.arguments, data.callId );
}
} );
}
/**
* Calls the specified Reveal.js method with the provided argument
* and then pushes the result to the notes frame.
*/
function callRevealApi( methodName, methodArguments, callId ) {
var result = Reveal[methodName].apply( Reveal, methodArguments );
notesPopup.postMessage( JSON.stringify( {
namespace: 'reveal-notes',
type: 'return',
result: result,
callId: callId
} ), '*' );
}
/**
* Posts the current slide data to the notes window
*/
function post( event ) {
var slideElement = Reveal.getCurrentSlide(),
notesElement = slideElement.querySelector( 'aside.notes' ),
fragmentElement = slideElement.querySelector( '.current-fragment' );
var messageData = {
namespace: 'reveal-notes',
type: 'state',
notes: '',
markdown: false,
whitespace: 'normal',
state: Reveal.getState()
};
// Look for notes defined in a slide attribute
if( slideElement.hasAttribute( 'data-notes' ) ) {
messageData.notes = slideElement.getAttribute( 'data-notes' );
messageData.whitespace = 'pre-wrap';
}
// Look for notes defined in a fragment
if( fragmentElement ) {
var fragmentNotes = fragmentElement.querySelector( 'aside.notes' );
if( fragmentNotes ) {
notesElement = fragmentNotes;
}
else if( fragmentElement.hasAttribute( 'data-notes' ) ) {
messageData.notes = fragmentElement.getAttribute( 'data-notes' );
messageData.whitespace = 'pre-wrap';
// In case there are slide notes
notesElement = null;
}
}
// Look for notes defined in an aside element
if( notesElement ) {
messageData.notes = notesElement.innerHTML;
messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string';
}
notesPopup.postMessage( JSON.stringify( messageData ), '*' );
}
/**
* Called once we have established a connection to the notes
* window.
*/
function onConnected() {
// Monitor events that trigger a change in state
Reveal.addEventListener( 'slidechanged', post );
Reveal.addEventListener( 'fragmentshown', post );
Reveal.addEventListener( 'fragmenthidden', post );
Reveal.addEventListener( 'overviewhidden', post );
Reveal.addEventListener( 'overviewshown', post );
Reveal.addEventListener( 'paused', post );
Reveal.addEventListener( 'resumed', post );
// Post the initial state
post();
}
connect();
}
return {
init: function() {
if( !/receiver/i.test( window.location.search ) ) {
// If the there's a 'notes' query set, open directly
if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) {
openNotes();
}
// Open the notes when the 's' key is hit
Reveal.addKeyBinding({keyCode: 83, key: 'S', description: 'Speaker notes view'}, function() {
openNotes();
} );
}
},
open: openNotes
};
})();
Reveal.registerPlugin( 'notes', RevealNotes );