new postMessage-based notes plugin, moved node-based notes to notes-server (#190)
authorHakim El Hattab <hakim.elhattab@gmail.com>
Sun, 21 Oct 2012 00:40:52 +0000 (20:40 -0400)
committerHakim El Hattab <hakim.elhattab@gmail.com>
Sun, 21 Oct 2012 00:40:52 +0000 (20:40 -0400)
1  2 
README.md
index.html
plugin/notes-server/client.js
plugin/notes-server/index.js
plugin/notes-server/notes.html
plugin/notes/notes.html
plugin/notes/notes.js

diff --cc README.md
index 1e83ccb4b6285d7dcf4318dcb4cf0564ca1b5925,91c4e06ea48710ba33e4665533482f665a6d3d15..a56b6e012419a6b64ed9b22e20f17633917295eb
+++ b/README.md
@@@ -105,10 -105,10 +105,9 @@@ Reveal.initialize(
                { 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; } }
        ]
  });
  ```
@@@ -230,27 -200,27 +229,23 @@@ Here's an example of an exported presen
  
  ![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
diff --cc index.html
index 6a3510ed71637c5f7116f014e4844fa80906c0ee,43d8b38e45d995efe64eebf856d391ae5debc928..670eb5e3022a8f783341f8436582ace1aca88bad
@@@ -51,7 -53,7 +51,7 @@@
                                        </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>
                                
@@@ -356,12 -345,41 +356,10 @@@ function linkify( selector ) 
                                        { 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>
index ad1bd4616ebe6e8ea20d37b0d30c6928705734ab,0000000000000000000000000000000000000000..d8056019dca1c7766ab55d1da2fcc3e2ae3be1af
mode 100644,000000..100644
--- /dev/null
@@@ -1,38 -1,0 +1,38 @@@
- }());
 +(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);
 +      } );
++}());
index 17314f331888a8c2e23c4f4268bbda542fbc94fb,0000000000000000000000000000000000000000..1fe5bab932f2c5f74f0794f96a6f8bf0660e4b1e
mode 100644,000000..100644
--- /dev/null
@@@ -1,55 -1,0 +1,55 @@@
-       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" );
index 13f043d8a4d8e1cd493fe875380a6ddba791a064,4dbc98650c802bb6e66e4eee42070069c967cc0a..548bc122043c9f777d53354df15a4f5b91e748b9
                </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>
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..485edecd933e53c99fb74b0bef6141ae190ece5e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,157 @@@
++<!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>
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..58c26e03e02aefae7814e23678c5d45bd157a80d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,74 @@@
++/**
++ * 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 }
++})();