{ src: 'lib/js/data-markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'lib/js/showdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
// Zoom in and out with Alt+click
-- { src: 'plugin/zoom-js/zoom.js', condition: function() { return !!document.body.classList; } },
-- // Speaker notes support
-- { src: 'plugin/speakernotes/client.js', async: true, condition: function() { return window.location.host === 'localhost:1947'; } },
-- { src: '/socket.io/socket.io.js', async: true, condition: function() { return window.location.host === 'localhost:1947'; } },
++ { src: 'plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
++ // Speaker notes
++ { src: 'plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }
]
});
```
![Chrome Print Settings](https://s3.amazonaws.com/hakim-static/reveal-js/pdf-print-settings.png)
--## Speaker Notes
--
--If you're interested in using speaker notes, reveal.js comes with a Node server that allows you to deliver your presentation in one browser while viewing speaker notes in another.
--
--To include speaker notes in your presentation, simply add an `<aside class="notes">` element to any slide. These notes will be hidden in the main presentation view.
--
--It's also possible to write your notes with Markdown. To enable Markdown, add the ```data-markdown``` attribute to your note ```<aside>``` elements.
--
--You'll also need to [install Node.js](http://nodejs.org/); then, install the server dependencies by running `npm install`.
--Once Node.js and the dependencies are installed, run the following command from the root directory:
++## Speaker Notes
-- node plugin/speakernotes
++reveal.js comes with a speaker notes plugin which can be used to present per-slide notes in a separate browser window. The notes window also gives you a preview of the next upcoming slide so it may be helpful even if you haven't written any notes. Append ```?notes``` to presentation URL or press the 's' key on your keyboard to open the notes window.
--By default, the slides will be served at [localhost:1947](http://localhost:1947).
++Notes are written using the following markup structure:
--You can change the appearance of the speaker notes by editing the file at `plugin/speakernotes/notes.html`.
++```html
++<section>
++ <h2>Some Slide</h2>
--### Known Issues
++ <aside class="notes">
++ Oh hey, these are some notes. They'll be hidden in your presentation, but you can see them if you open the speaker notes window (hit 's' on your keyboard).
++ </aside>
++</section>
++```
--- The notes page is supposed to show the current slide and the next slide, but when it first starts, it always shows the first slide in both positions.
## Folder Structure
- **css/** Core styles without which the project does not function
</p>
<aside class="notes">
-- Oh hey, these are some notes. They'll be hidden in your presentation, but you can see them if you run the speaker notes server.
++ Oh hey, these are some notes. They'll be hidden in your presentation, but you can see them if you open the speaker notes window (hit 's' on your keyboard).
</aside>
</section>
{ src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: 'lib/js/showdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'lib/js/data-markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
-- { src: 'plugin/zoom-js/zoom.js', condition: function() { return !!document.body.classList; } },
-- { src: '/socket.io/socket.io.js', async: true, condition: function() { return window.location.host === 'localhost:1947'; } },
-- { src: 'plugin/speakernotes/client.js', async: true, condition: function() { return window.location.host === 'localhost:1947'; } }
++ { src: 'plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
++ { src: 'plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }
]
});
-
- // Set up simple postMessage notes system.
- var url = window.location.href;
- if(url.indexOf('?' + 'notes' + '=') !== 'true') {
- var notesPopup = window.open('notes.html');
- Reveal.addEventListener('slidechanged', function(event) {
- var nextindexh;
- var nextindexv;
- var slideElement = event.currentSlide;
-
- if (slideElement.nextElementSibling && slideElement.parentNode.nodeName == 'SECTION') {
- nextindexh = event.indexh;
- nextindexv = event.indexv + 1;
- } else {
- nextindexh = event.indexh + 1;
- nextindexv = 0;
- }
--
- var notes = slideElement.querySelector('aside.notes');
- var slideData = {
- notes : notes ? notes.innerHTML : '',
- indexh : event.indexh,
- indexv : event.indexv,
- nextindexh : nextindexh,
- nextindexv : nextindexv,
- markdown : notes ? typeof notes.getAttribute('data-markdown') === 'string' : false
- };
- notesPopup.postMessage(JSON.stringify(slideData), '*');
- });
- }
</script>
</body>
--- /dev/null
- }());
+(function() {
+ // don't emit events from inside the previews themselves
+ if ( window.location.search.match( /receiver/gi ) ) { return; }
+
+ var socket = io.connect(window.location.origin);
+ var socketId = Math.random().toString().slice(2);
+
+ console.log('View slide notes at ' + window.location.origin + '/notes/' + socketId);
+ window.open(window.location.origin + '/notes/' + socketId, 'notes-' + socketId)
+
+ Reveal.addEventListener( 'slidechanged', function( event ) {
+ var nextindexh;
+ var nextindexv;
+ var slideElement = event.currentSlide;
+
+ if (slideElement.nextElementSibling && slideElement.parentNode.nodeName == 'SECTION') {
+ nextindexh = event.indexh;
+ nextindexv = event.indexv + 1;
+ } else {
+ nextindexh = event.indexh + 1;
+ nextindexv = 0;
+ }
+
+ var notes = slideElement.querySelector('aside.notes');
+ var slideData = {
+ notes : notes ? notes.innerHTML : '',
+ indexh : event.indexh,
+ indexv : event.indexv,
+ nextindexh : nextindexh,
+ nextindexv : nextindexv,
+ socketId : socketId,
+ markdown : notes ? typeof notes.getAttribute('data-markdown') === 'string' : false
+
+ };
+
+ socket.emit('slidechanged', slideData);
+ } );
++}());
--- /dev/null
- fs.readFile(opts.baseDir + 'plugin/speakernotes/notes.html', function(err, data) {
+var express = require('express');
+var fs = require('fs');
+var io = require('socket.io');
+var _ = require('underscore');
+var Mustache = require('mustache');
+
+var app = express.createServer();
+var staticDir = express.static;
+
+io = io.listen(app);
+
+var opts = {
+ port : 1947,
+ baseDir : __dirname + '/../../'
+};
+
+io.sockets.on('connection', function(socket) {
+ socket.on('slidechanged', function(slideData) {
+ socket.broadcast.emit('slidedata', slideData);
+ });
+});
+
+app.configure(function() {
+ [ 'css', 'js', 'images', 'plugin', 'lib' ].forEach(function(dir) {
+ app.use('/' + dir, staticDir(opts.baseDir + dir));
+ });
+});
+
+app.get("/", function(req, res) {
+ fs.createReadStream(opts.baseDir + '/index.html').pipe(res);
+});
+
+app.get("/notes/:socketId", function(req, res) {
+
- // fs.createReadStream(opts.baseDir + 'speakernotes/notes.html').pipe(res);
++ fs.readFile(opts.baseDir + 'plugin/notes-server/notes.html', function(err, data) {
+ res.send(Mustache.to_html(data.toString(), {
+ socketId : req.params.socketId
+ }));
+ });
- console.log( "3. Advance through your slides and your notes will advance automatically" );
++ // fs.createReadStream(opts.baseDir + 'notes-server/notes.html').pipe(res);
+});
+
+// Actually listen
+app.listen(opts.port || null);
+
+var brown = '\033[33m',
+ green = '\033[32m',
+ reset = '\033[0m';
+
+var slidesLocation = "http://localhost" + ( opts.port ? ( ':' + opts.port ) : '' );
+
+console.log( brown + "reveal.js - Speaker Notes" + reset );
+console.log( "1. Open the slides at " + green + slidesLocation + reset );
+console.log( "2. Click on the link your JS console to go to the notes page" );
++console.log( "3. Advance through your slides and your notes will advance automatically" );
</div>
<div id="notes"></div>
- <script src="lib/js/showdown.js"></script>
+ <script src="/socket.io/socket.io.js"></script>
+ <script src="/lib/js/showdown.js"></script>
+
<script>
- (function (window, undefined) {
- var notes = document.getElementById('notes');
- var currentSlide = document.getElementById('current-slide');
- var nextSlide = document.getElementById('next-slide');
- window.addEventListener("message", function(e){
- var data = JSON.parse(e.data);
- if (data.markdown) {
- notes.innerHTML = (new Showdown.converter()).makeHtml(data.notes);
- }
- else {
- notes.innerHTML = data.notes;
- }
- currentSlide.contentWindow.Reveal.slide(data.indexh, data.indexv);
- nextSlide.contentWindow.Reveal.slide(data.nextindexh, data.nextindexv);
- }, false);
- })(window);
+ var socketId = '{{socketId}}';
+ var socket = io.connect(window.location.origin);
+ var notes = document.getElementById('notes');
+ var currentSlide = document.getElementById('current-slide');
+ var nextSlide = document.getElementById('next-slide');
+
+ socket.on('slidedata', function(data) {
+ // ignore data from sockets that aren't ours
+ if (data.socketId !== socketId) { return; }
+
+ if (data.markdown) {
+ notes.innerHTML = (new Showdown.converter()).makeHtml(data.notes);
+ }
+ else {
+ notes.innerHTML = data.notes;
+ }
+
+ currentSlide.contentWindow.Reveal.slide(data.indexh, data.indexv);
+ nextSlide.contentWindow.Reveal.slide(data.nextindexh, data.nextindexv);
+ });
</script>
+
</body>
--</html>
++</html>
--- /dev/null
--- /dev/null
++<!doctype html>
++<html lang="en">
++ <head>
++ <meta charset="utf-8">
++
++ <title>reveal.js - Slide Notes</title>
++
++ <style>
++ body {
++ font-family: Helvetica;
++ }
++
++ #notes {
++ font-size: 24px;
++ width: 640px;
++ margin-top: 5px;
++ }
++
++ #wrap-current-slide {
++ width: 640px;
++ height: 512px;
++ float: left;
++ overflow: hidden;
++ }
++
++ #current-slide {
++ width: 1280px;
++ height: 1024px;
++ border: none;
++
++ -webkit-transform-origin: 0 0;
++ -moz-transform-origin: 0 0;
++ -ms-transform-origin: 0 0;
++ -o-transform-origin: 0 0;
++ transform-origin: 0 0;
++
++ -webkit-transform: scale(0.5);
++ -moz-transform: scale(0.5);
++ -ms-transform: scale(0.5);
++ -o-transform: scale(0.5);
++ transform: scale(0.5);
++ }
++
++ #wrap-next-slide {
++ width: 448px;
++ height: 358px;
++ float: left;
++ margin: 0 0 0 10px;
++ overflow: hidden;
++ }
++
++ #next-slide {
++ width: 1280px;
++ height: 1024px;
++ border: none;
++
++ -webkit-transform-origin: 0 0;
++ -moz-transform-origin: 0 0;
++ -ms-transform-origin: 0 0;
++ -o-transform-origin: 0 0;
++ transform-origin: 0 0;
++
++ -webkit-transform: scale(0.35);
++ -moz-transform: scale(0.35);
++ -ms-transform: scale(0.35);
++ -o-transform: scale(0.35);
++ transform: scale(0.35);
++ }
++
++ .slides {
++ position: relative;
++ margin-bottom: 10px;
++ border: 1px solid black;
++ border-radius: 2px;
++ background: rgb(28, 30, 32);
++ }
++
++ .slides span {
++ position: absolute;
++ top: 3px;
++ left: 3px;
++ font-weight: bold;
++ font-size: 14px;
++ color: rgba( 255, 255, 255, 0.9 );
++ }
++ </style>
++ </head>
++
++ <body>
++
++ <div id="wrap-current-slide" class="slides">
++ <iframe src="../../index.html" width="1280" height="1024" id="current-slide"></iframe>
++ </div>
++
++ <div id="wrap-next-slide" class="slides">
++ <iframe src="../../index.html" width="640" height="512" id="next-slide"></iframe>
++ <span>UPCOMING:</span>
++ </div>
++ <div id="notes"></div>
++
++ <script src="../../lib/js/showdown.js"></script>
++ <script>
++ window.addEventListener( 'load', function() {
++
++ (function( window, undefined ) {
++ var notes = document.getElementById( 'notes' ),
++ currentSlide = document.getElementById( 'current-slide' ),
++ nextSlide = document.getElementById( 'next-slide' );
++
++ window.addEventListener( 'message', function( event ) {
++ var data = JSON.parse( event.data );
++
++ if( data.markdown ) {
++ notes.innerHTML = (new Showdown.converter()).makeHtml( data.notes );
++ }
++ else {
++ notes.innerHTML = data.notes;
++ }
++
++ // Kill the slide listeners while responding to the event
++ removeSlideListeners();
++
++ // Update the note slides
++ currentSlide.contentWindow.Reveal.slide( data.indexh, data.indexv );
++ nextSlide.contentWindow.Reveal.slide( data.nextindexh, data.nextindexv );
++
++ // Resume listening on the next cycle
++ setTimeout( addSlideListeners, 1 );
++
++ }, false );
++
++ function addSlideListeners() {
++ currentSlide.contentWindow.Reveal.addEventListener( 'slidechanged', onNotesSlideChange, false );
++ nextSlide.contentWindow.Reveal.addEventListener( 'slidechanged', onNotesSlideChange, false );
++ }
++
++ function removeSlideListeners() {
++ currentSlide.contentWindow.Reveal.removeEventListener( 'slidechanged', onNotesSlideChange, false );
++ nextSlide.contentWindow.Reveal.removeEventListener( 'slidechanged', onNotesSlideChange, false );
++ }
++
++ function onNotesSlideChange( event ) {
++ window.opener.postMessage( JSON.stringify({
++ indexh : event.indexh,
++ indexv : event.indexv
++ }), '*' );
++ }
++
++ addSlideListeners();
++
++ })( window );
++
++ }, false );
++
++ </script>
++ </body>
++</html>
--- /dev/null
--- /dev/null
++/**
++ * Handles opening of and synchronization with the reveal.js
++ * notes window.
++ */
++var RevealNotes = (function() {
++
++ function openNotes() {
++ var notesPopup = window.open( 'plugin/notes/notes.html', 'reveal.js - Notes', 'width=1120,height=850' );
++
++ Reveal.addEventListener( 'slidechanged', post );
++
++ // Posts the current slide data to the notes window
++ function post() {
++ var slideElement = Reveal.getCurrentSlide(),
++ indexh = Reveal.getIndices().h,
++ indexv = Reveal.getIndices().v,
++ nextindexh,
++ nextindexv;
++
++ if( slideElement.nextElementSibling && slideElement.parentNode.nodeName == 'SECTION' ) {
++ nextindexh = indexh;
++ nextindexv = indexv + 1;
++ } else {
++ nextindexh = indexh + 1;
++ nextindexv = 0;
++ }
++
++ var notes = slideElement.querySelector( 'aside.notes' );
++
++ var slideData = {
++ notes : notes ? notes.innerHTML : '',
++ indexh : indexh,
++ indexv : indexv,
++ nextindexh : nextindexh,
++ nextindexv : nextindexv,
++ markdown : notes ? typeof notes.getAttribute( 'data-markdown' ) === 'string' : false
++ };
++
++ notesPopup.postMessage( JSON.stringify( slideData ), '*' );
++ }
++
++ // The main presentation is kept in sync when navigating the
++ // note slides so that the popup may be used as a remote
++ window.addEventListener( 'message', function( event ) {
++ var data = JSON.parse( event.data );
++
++ if( data && typeof data.indexh === 'number' && typeof data.indexv === 'number' ) {
++ Reveal.slide( data.indexh, data.indexv );
++ }
++ } );
++
++ // Navigate to the current slide when the notes are loaded
++ notesPopup.addEventListener( 'load', post, false );
++ }
++
++ // 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
++ document.addEventListener( 'keydown', function( event ) {
++ // Disregard the event if the target is editable or a
++ // modifier is present
++ if ( document.querySelector( ':focus' ) !== null || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
++
++ if( event.keyCode === 83 ) {
++ event.preventDefault();
++ openNotes();
++ }
++ }, false );
++
++ return { open: openNotes }
++})();