merge in multiplex (#98)

This commit is contained in:
Hakim El Hattab 2013-03-08 18:49:28 -05:00
commit 2bd5e8b65b
9 changed files with 241 additions and 10 deletions

View File

@ -344,6 +344,17 @@ Reveal.toggleOverview();
### Fullscreen mode ### Fullscreen mode
Just press »F« on your keyboard to show your presentation in fullscreen mode. Press the »ESC« key to exit fullscreen mode. Just press »F« on your keyboard to show your presentation in fullscreen mode. Press the »ESC« key to exit fullscreen mode.
## Multiplexing
The multiplex plugin allows your audience to view the slides on their own phone, tablet or laptop. As the master navigates the slides, all clients will update in real time. See a demo at [http://revealjs.jit.su/](http://revealjs.jit.su)
Configuration is via the multiplex object in index.html. To generate unique secret and token values, visit [revealjs.jit.su/token](revealjs.jit.su/token)
multiplex.secret should only be configured on those pages you wish to be able to control slide navigatoin for all clients. Multi-master configurations work, but if you don't wish your audience to be able to control your slides, set the secret to null. In this master/slave setup, you should create a publicly accessible page with secret set to null, and a protected page containing your secret.
You are very welcome to use the server running at reveal.jit.su, however availability and stability are not guaranteed. For anything mission critical I recommend you run your own server. It is simple to deploy to nodejitsu or run on your own environment.
### Known Issues
## PDF Export ## PDF Export

View File

@ -358,6 +358,14 @@ function linkify( selector ) {
theme: Reveal.getQueryHash().theme, // available themes are in /css/theme theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
transition: Reveal.getQueryHash().transition || 'default', // default/cube/page/concave/zoom/linear/fade/none transition: Reveal.getQueryHash().transition || 'default', // default/cube/page/concave/zoom/linear/fade/none
globals: {
// Generate a unique id and secret at http://revealjs.jit.su/token
multiplex: {
id: '7d10234555b923e2',
secret: '13627859051503309760',
url: 'revealjs.jit.su:80'
}
},
// Optional libraries used to extend on reveal.js // Optional libraries used to extend on reveal.js
dependencies: [ dependencies: [
@ -366,8 +374,11 @@ function linkify( selector ) {
{ src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } }, { src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }, { src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: 'plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } }, { src: 'plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
// { src: 'plugin/remotes/remotes.js', async: true, condition: function() { return !!document.body.classList; } },
// { src: 'socket.io/socket.io.js', async: true, condition: function() { return !!document.body.classList; } },
// { src: 'plugin/multiplex/client.js', async: true, condition: function() { return !!document.body.classList; } },
// { src: 'plugin/multiplex/master.js', async: true, condition: function() { return !!document.body.classList; } },
{ src: 'plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } } { src: 'plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }
// { src: 'plugin/remotes/remotes.js', async: true, condition: function() { return !!document.body.classList; } }
] ]
}); });

View File

@ -160,6 +160,9 @@ var Reveal = (function(){
// Copy options over to our config object // Copy options over to our config object
extend( config, options ); extend( config, options );
// Push up globals
window.globals = config.globals;
// Hide the address bar in mobile browsers // Hide the address bar in mobile browsers
hideAddressBar(); hideAddressBar();
@ -1019,8 +1022,9 @@ var Reveal = (function(){
* @param {int} v Vertical index of the target slide * @param {int} v Vertical index of the target slide
* @param {int} f Optional index of a fragment within the * @param {int} f Optional index of a fragment within the
* target slide to activate * target slide to activate
* @param {int} o Optional origin for use in multimaster environments
*/ */
function slide( h, v, f ) { function slide( h, v, f, o ) {
// Remember where we were at before // Remember where we were at before
previousSlide = currentSlide; previousSlide = currentSlide;
@ -1115,7 +1119,8 @@ var Reveal = (function(){
'indexh': indexh, 'indexh': indexh,
'indexv': indexv, 'indexv': indexv,
'previousSlide': previousSlide, 'previousSlide': previousSlide,
'currentSlide': currentSlide 'currentSlide': currentSlide,
'origin': o
} ); } );
} }
else { else {

4
js/reveal.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,11 @@
"version": "2.3.0", "version": "2.3.0",
"description": "The HTML Presentation Framework", "description": "The HTML Presentation Framework",
"homepage": "http://lab.hakim.se/reveal-js", "homepage": "http://lab.hakim.se/reveal-js",
"subdomain": "revealjs",
"scripts": {
"test": "grunt jshint",
"start": ""
},
"author": { "author": {
"name": "Hakim El Hattab", "name": "Hakim El Hattab",
"email": "hakim.elhattab@gmail.com", "email": "hakim.elhattab@gmail.com",
@ -15,14 +20,11 @@
"engines": { "engines": {
"node": "~0.8.0" "node": "~0.8.0"
}, },
"scripts": {
"test": "grunt jshint"
},
"dependencies": { "dependencies": {
"underscore": "~1.3.3", "underscore": "~1.3.3",
"express": "~2.5.9", "express": "~2.5.9",
"socket.io": "~0.9.6", "mustache": "~0.4.0",
"mustache": "~0.4.0" "socket.io": "~0.9.13"
}, },
"devDependencies": { "devDependencies": {
"grunt-contrib-jshint": "~0.2.0", "grunt-contrib-jshint": "~0.2.0",

View File

@ -0,0 +1,13 @@
(function() {
var multiplex = window.globals.multiplex;
var socketId = multiplex.id;
var socket = io.connect(multiplex.url);
socket.on(multiplex.id, function(data) {
// ignore data from sockets that aren't ours
if (data.socketId !== socketId) { return; }
if( window.location.host === 'localhost:1947' ) return;
Reveal.slide(data.indexh, data.indexv, null, 'remote');
});
}());

48
plugin/multiplex/index.js Normal file
View File

@ -0,0 +1,48 @@
var express = require('express');
var fs = require('fs');
var io = require('socket.io');
var crypto = require('crypto');
var app = express.createServer();
var staticDir = express.static;
io = io.listen(app);
var opts = {
port: 1948,
baseDir : __dirname + '/../../'
};
io.sockets.on('connection', function(socket) {
socket.on('slidechanged', function(slideData) {
if (createHash(slideData.secret) === slideData.socketId) {
slideData.secret = null;
socket.broadcast.emit(slideData.socketId, slideData);
};
});
});
app.configure(function() {
[ 'css', 'js', '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("/token", function(req,res) {
var ts = new Date().getTime();
var rand = Math.floor(Math.random()*9999999);
var secret = ts.toString() + rand.toString();
res.send({secret: secret, socketId: createHash(secret)});
});
var createHash = function(secret) {
var cipher = crypto.createCipher('blowfish', secret);
return(cipher.final('hex'));
};
// Actually listen
app.listen(opts.port || null);

View File

@ -0,0 +1,32 @@
(function() {
// don't emit events from inside the previews themselves
if ( window.location.search.match( /receiver/gi ) ) { return; }
var multiplex = window.globals.multiplex;
var socket = io.connect(multiplex.url);
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 slideData = {
indexh : event.indexh,
indexv : event.indexv,
nextindexh : nextindexh,
nextindexv : nextindexv,
secret: multiplex.secret,
socketId : multiplex.id
};
if( typeof event.origin === 'undefined' && event.origin !== 'remote' ) socket.emit('slidechanged', slideData);
} );
}());

109
plugin/multiplex/notes.html Normal file
View File

@ -0,0 +1,109 @@
<!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;
-moz-transform: scale(0.5);
-moz-transform-origin: 0 0;
-o-transform: scale(0.5);
-o-transform-origin: 0 0;
-webkit-transform: scale(0.5);
-webkit-transform-origin: 0 0;
}
#wrap-next-slide {
width: 320px;
height: 256px;
float: left;
margin: 0 0 0 10px;
overflow: hidden;
}
#next-slide {
width: 1280px;
height: 1024px;
border: none;
-moz-transform: scale(0.25);
-moz-transform-origin: 0 0;
-o-transform: scale(0.25);
-o-transform-origin: 0 0;
-webkit-transform: scale(0.25);
-webkit-transform-origin: 0 0;
}
.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="/?receiver" width="1280" height="1024" id="current-slide"></iframe>
</div>
<div id="wrap-next-slide" class="slides">
<iframe src="/?receiver" width="640" height="512" id="next-slide"></iframe>
<span>UPCOMING:</span>
</div>
<div id="notes"></div>
<script src="/socket.io/socket.io.js"></script>
<script>
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; }
notes.innerHTML = data.notes;
currentSlide.contentWindow.Reveal.navigateTo(data.indexh, data.indexv);
nextSlide.contentWindow.Reveal.navigateTo(data.nextindexh, data.nextindexv);
});
</script>
</body>
</html>