diff --git a/.gitignore b/.gitignore index 9ffdbc7345b..a5df3133d5d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ log/*.log tmp/** node_modules/ -.sass-cache \ No newline at end of file +.sass-cache +css/reveal.min.css +js/reveal.min.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd37e5ad72d..421543667a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Please keep the [issue tracker](http://github.com/hakimel/reveal.js/issues) limi ### Personal Support -If you have personal support or setup questions the best place to ask those are [StackOverflow](http://stackoverflow.com/questions/tagged/reveal.js). +If you have personal support or setup questions the best place to ask those are [StackOverflow](http://stackoverflow.com/questions/tagged/reveal.js). ### Bug Reports @@ -17,4 +17,3 @@ When reporting a bug make sure to include information about which browser and op - Single-quoted strings - Should be made towards the **dev branch** - Should be submitted from a feature/topic branch (not your master) -- Should not include the minified **reveal.min.js** or **reveal.min.css** files diff --git a/Gruntfile.js b/Gruntfile.js index 1baf9661ff3..3e67b9f05d8 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -11,7 +11,7 @@ module.exports = function(grunt) { ' * http://lab.hakim.se/reveal-js\n' + ' * MIT licensed\n' + ' *\n' + - ' * Copyright (C) 2014 Hakim El Hattab, http://hakim.se\n' + + ' * Copyright (C) 2015 Hakim El Hattab, http://hakim.se\n' + ' */' }, @@ -29,18 +29,17 @@ module.exports = function(grunt) { } }, - cssmin: { - compress: { + sass: { + core: { files: { - 'css/reveal.min.css': [ 'css/reveal.css' ] + 'css/reveal.css': 'css/reveal.scss', } - } - }, - - sass: { - main: { + }, + themes: { files: { - 'css/theme/default.css': 'css/theme/source/default.scss', + 'css/theme/black.css': 'css/theme/source/black.scss', + 'css/theme/white.css': 'css/theme/source/white.scss', + 'css/theme/league.css': 'css/theme/source/league.scss', 'css/theme/beige.css': 'css/theme/source/beige.scss', 'css/theme/night.css': 'css/theme/source/night.scss', 'css/theme/serif.css': 'css/theme/source/serif.scss', @@ -53,6 +52,20 @@ module.exports = function(grunt) { } }, + autoprefixer: { + dist: { + src: 'css/reveal.css' + } + }, + + cssmin: { + compress: { + files: { + 'css/reveal.min.css': [ 'css/reveal.css' ] + } + } + }, + jshint: { options: { curly: false, @@ -70,7 +83,9 @@ module.exports = function(grunt) { head: false, module: false, console: false, - unescape: false + unescape: false, + define: false, + exports: false } }, files: [ 'Gruntfile.js', 'js/reveal.js' ] @@ -80,7 +95,9 @@ module.exports = function(grunt) { server: { options: { port: port, - base: '.' + base: '.', + livereload: true, + open: true } } }, @@ -97,14 +114,24 @@ module.exports = function(grunt) { }, watch: { - main: { - files: [ 'Gruntfile.js', 'js/reveal.js', 'css/reveal.css' ], - tasks: 'default' + options: { + livereload: true + }, + js: { + files: [ 'Gruntfile.js', 'js/reveal.js' ], + tasks: 'js' }, theme: { files: [ 'css/theme/source/*.scss', 'css/theme/template/*.scss' ], - tasks: 'themes' - } + tasks: 'css-themes' + }, + css: { + files: [ 'css/reveal.scss' ], + tasks: 'css-core' + }, + html: { + files: [ 'index.html'] + } } }); @@ -115,15 +142,25 @@ module.exports = function(grunt) { grunt.loadNpmTasks( 'grunt-contrib-cssmin' ); grunt.loadNpmTasks( 'grunt-contrib-uglify' ); grunt.loadNpmTasks( 'grunt-contrib-watch' ); - grunt.loadNpmTasks( 'grunt-contrib-sass' ); + grunt.loadNpmTasks( 'grunt-sass' ); grunt.loadNpmTasks( 'grunt-contrib-connect' ); + grunt.loadNpmTasks( 'grunt-autoprefixer' ); grunt.loadNpmTasks( 'grunt-zip' ); // Default task - grunt.registerTask( 'default', [ 'jshint', 'cssmin', 'uglify', 'qunit' ] ); + grunt.registerTask( 'default', [ 'css', 'js' ] ); + + // JS task + grunt.registerTask( 'js', [ 'jshint', 'uglify', 'qunit' ] ); + + // Theme CSS + grunt.registerTask( 'css-themes', [ 'sass:themes' ] ); + + // Core framework CSS + grunt.registerTask( 'css-core', [ 'sass:core', 'autoprefixer', 'cssmin' ] ); - // Theme task - grunt.registerTask( 'themes', [ 'sass' ] ); + // All CSS + grunt.registerTask( 'css', [ 'sass', 'autoprefixer', 'cssmin' ] ); // Package presentation to archive grunt.registerTask( 'package', [ 'default', 'zip' ] ); diff --git a/LICENSE b/LICENSE index 3866d13e153..09623076fb3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2014 Hakim El Hattab, http://hakim.se +Copyright (C) 2015 Hakim El Hattab, http://hakim.se Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 40d56801c6f..753290890e6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A framework for easily creating beautiful presentations using HTML. [Check out the live demo](http://lab.hakim.se/reveal-js/). -reveal.js comes with a broad range of features including [nested slides](https://github.com/hakimel/reveal.js#markup), [markdown contents](https://github.com/hakimel/reveal.js#markdown), [PDF export](https://github.com/hakimel/reveal.js#pdf-export), [speaker notes](https://github.com/hakimel/reveal.js#speaker-notes) and a [JavaScript API](https://github.com/hakimel/reveal.js#api). It's best viewed in a browser with support for CSS 3D transforms but [fallbacks](https://github.com/hakimel/reveal.js/wiki/Browser-Support) are available to make sure your presentation can still be viewed elsewhere. +reveal.js comes with a broad range of features including [nested slides](https://github.com/hakimel/reveal.js#markup), [Markdown contents](https://github.com/hakimel/reveal.js#markdown), [PDF export](https://github.com/hakimel/reveal.js#pdf-export), [speaker notes](https://github.com/hakimel/reveal.js#speaker-notes) and a [JavaScript API](https://github.com/hakimel/reveal.js#api). It's best viewed in a modern browser but [fallbacks](https://github.com/hakimel/reveal.js/wiki/Browser-Support) are available to make sure your presentation can still be viewed elsewhere. #### More reading: @@ -13,7 +13,7 @@ reveal.js comes with a broad range of features including [nested slides](https:/ ## Online Editor -Presentations are written using HTML or markdown but there's also an online editor for those of you who prefer a graphical interface. Give it a try at [http://slid.es](http://slid.es). +Presentations are written using HTML or Markdown but there's also an online editor for those of you who prefer a graphical interface. Give it a try at [http://slides.com](http://slides.com). ## Instructions @@ -59,8 +59,8 @@ When used locally, this feature requires that reveal.js [runs from a local web s ```html
``` @@ -136,6 +136,10 @@ Reveal.initialize({ // i.e. contained within a limited portion of the screen embedded: false, + // Flags if we should show a help overlay when the questionmark + // key is pressed + help: true, + // 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 @@ -154,13 +158,13 @@ Reveal.initialize({ previewLinks: false, // Transition style - transition: 'default', // default/cube/page/concave/zoom/linear/fade/none + transition: 'default', // none/fade/slide/convex/concave/zoom // Transition speed transitionSpeed: 'default', // default/fast/slow // Transition style for full page slide backgrounds - backgroundTransition: 'default', // default/none/slide/concave/convex/zoom + backgroundTransition: 'default', // none/fade/slide/convex/concave/zoom // Number of slides away from the current that are visible viewDistance: 3, @@ -175,8 +179,6 @@ Reveal.initialize({ }); ``` -Note that the new default vertical centering option will break compatibility with slides that were using transitions with backgrounds (`cube` and `page`). To restore the previous behavior, set `center` to `false`. - The configuration can be updated after initialization using the ```configure``` method: @@ -207,13 +209,13 @@ Reveal.initialize({ { src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }, // Zoom in and out with Alt+click - { src: 'plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } }, + { src: 'plugin/zoom-js/zoom.js', async: true }, // Speaker notes - { src: 'plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }, + { src: 'plugin/notes/notes.js', async: true }, // Remote control your reveal.js presentation using a touch device - { src: 'plugin/remotes/remotes.js', async: true, condition: function() { return !!document.body.classList; } }, + { src: 'plugin/remotes/remotes.js', async: true }, // MathJax { src: 'plugin/math/math.js', async: true } @@ -266,15 +268,20 @@ Reveal.configure({ autoSlide: 5000 }); ``` +When this is turned on a control element will appear that enables users to pause and resume auto-sliding. Alternatively, sliding can be paused or resumed by pressing »a« on the keyboard. Sliding is paused automatically as soon as the user starts navigating. You can disable these controls by specifying ```autoSlideStoppable: false``` in your reveal.js config. -When this is turned on a control element will appear that enables users to pause and resume auto-sliding. Sliding is also paused automatically as soon as the user starts navigating. You can disable these controls by specifying ```autoSlideStoppable: false``` in your reveal.js config. - -You can also override the slide duration for individual slides by using the ```data-autoslide``` attribute on individual sections: +You can also override the slide duration for individual slides and fragments by using the ```data-autoslide``` attribute: ```html -
This will remain on screen for 10 seconds
+
+

After 2 seconds the first fragment will be shown.

+

After 10 seconds the next fragment will be shown.

+

Now, the fragment is displayed for 2 seconds before the next slide is shown.

+
``` +Whenever the auto-slide mode is resumed or paused the ```autoslideresumed``` and ```autoslidepaused``` events are fired. + ### Keyboard Bindings @@ -290,6 +297,23 @@ Reveal.configure({ }); ``` +### Lazy Loading + +When working on presentation with a lot of media or iframe content it's important to load lazily. Lazy loading means that reveal.js will only load content for the few slides nearest to the current slide. The number of slides that are preloaded is determined by the `viewDistance` configuration option. + +To enable lazy loading all you need to do is change your "src" attributes to "data-src" as shown below. This is supported for image, video, audio and iframe elements. + +```html +
+ +
-

Export to PDF

-

Presentations can be exported to PDF, below is an example that's been uploaded to SlideShare.

- - +

Global State

+

+ Set data-state="something" on a slide and "something" + will be added as a class to the document element when the slide is open. This lets you + apply broader style changes, like switching the page background. +

+
+ +
+

State Events

+

+ Additionally custom events can be triggered on a per slide basis by binding to the data-state name. +

+

+Reveal.addEventListener( 'customevent', function() {
+	console.log( '"customevent" has fired' );
+} );
+					

Take a Moment

- Press b or period on your keyboard to enter the 'paused' mode. This mode is helpful when you want to take distracting slides off the screen - during a presentation. + Press B or . on your keyboard to pause the presentation. This is helpful when you're on stage and want to take distracting slides off the screen.

-

Stellar Links

+

Much more

-
+

THE END

-

BY Hakim El Hattab / hakim.se

+

+ - Try the online editor
+ - Source code & documentation +

@@ -358,11 +377,11 @@

BY Hakim El Hattab / hakim.se

- + diff --git a/plugin/notes/notes.html b/plugin/notes/notes.html index 847499d2b95..0cc8cf61276 100644 --- a/plugin/notes/notes.html +++ b/plugin/notes/notes.html @@ -10,127 +10,141 @@ font-family: Helvetica; } - #notes { - font-size: 24px; - width: 640px; - margin-top: 5px; - clear: left; + #current-slide, + #upcoming-slide, + #speaker-controls { + padding: 6px; + box-sizing: border-box; + -moz-box-sizing: border-box; } - #wrap-current-slide { - width: 640px; - height: 512px; - float: left; - overflow: hidden; + #current-slide iframe, + #upcoming-slide iframe { + width: 100%; + height: 100%; + border: 1px solid #ddd; } - #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; + #current-slide .label, + #upcoming-slide .label { + position: absolute; + top: 10px; + left: 10px; + font-weight: bold; + font-size: 14px; + z-index: 2; + color: rgba( 255, 255, 255, 0.9 ); } - #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); + #current-slide { + position: absolute; + width: 65%; + height: 100%; + top: 0; + left: 0; + padding-right: 0; } - .slides { - position: relative; - margin-bottom: 10px; - border: 1px solid black; - border-radius: 2px; - background: rgb(28, 30, 32); + #upcoming-slide { + position: absolute; + width: 35%; + height: 40%; + right: 0; + top: 0; } - .slides span { + #speaker-controls { position: absolute; - top: 3px; - left: 3px; - font-weight: bold; - font-size: 14px; - color: rgba( 255, 255, 255, 0.9 ); - } + top: 40%; + right: 0; + width: 35%; + height: 60%; + overflow: auto; - .error { - font-weight: bold; - color: red; - font-size: 1.5em; - text-align: center; - margin-top: 10%; + font-size: 18px; } - .error code { - font-family: monospace; - } + .speaker-controls-time.hidden, + .speaker-controls-notes.hidden { + display: none; + } + + .speaker-controls-time .label, + .speaker-controls-notes .label { + text-transform: uppercase; + font-weight: normal; + font-size: 0.66em; + color: #666; + margin: 0; + } - .time { - width: 448px; - margin: 30px 0 0 10px; - float: left; - text-align: center; - opacity: 0; - - -webkit-transition: opacity 0.4s; - -moz-transition: opacity 0.4s; - -o-transition: opacity 0.4s; - transition: opacity 0.4s; + .speaker-controls-time { + 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%; + font-size: 1.9em; + } + + .speaker-controls-time .timer { + float: left; + } + + .speaker-controls-time .clock { + float: right; + text-align: right; + } + + .speaker-controls-time span.mute { + color: #bbb; + } + + .speaker-controls-notes { + padding: 10px 16px; + } + + .speaker-controls-notes .value { + margin-top: 5px; + line-height: 1.4; + font-size: 1.2em; + } + + .clear { + clear: both; } - .elapsed, - .clock { - color: #333; - font-size: 2em; - text-align: center; - display: inline-block; - padding: 0.5em; - background-color: #eee; - border-radius: 10px; + @media screen and (max-width: 1080px) { + #speaker-controls { + font-size: 16px; + } } - .elapsed h2, - .clock h2 { - font-size: 0.8em; - line-height: 100%; - margin: 0; - color: #aaa; + @media screen and (max-width: 900px) { + #speaker-controls { + font-size: 14px; + } } - .elapsed .mute { - color: #ddd; + @media screen and (max-width: 800px) { + #speaker-controls { + font-size: 12px; + } } @@ -138,81 +152,183 @@ - - -
- -
- -
- - UPCOMING: -
- -
-
-

Time

- 0:00:00 AM +
+
UPCOMING:
+
+
+

Time Click to Reset

+
+ 0:00 AM +
+
+ 00:00:00 +
+
-
-

Elapsed

- 00:00:00 + +
-
- diff --git a/plugin/notes/notes.js b/plugin/notes/notes.js index 9a82c3c44e4..27199af8799 100644 --- a/plugin/notes/notes.js +++ b/plugin/notes/notes.js @@ -1,78 +1,122 @@ /** * 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() { function openNotes() { var jsFileLocation = document.querySelector('script[src$="notes.js"]').src; // this js file path jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, ''); // the js folder path - var notesPopup = window.open( jsFileLocation + 'notes.html', 'reveal.js - Notes', 'width=1120,height=850' ); + var notesPopup = window.open( jsFileLocation + 'notes.html', 'reveal.js - Notes', 'width=1100,height=700' ); - // Fires when slide is changed - Reveal.addEventListener( 'slidechanged', post ); - - // Fires when a fragment is shown - Reveal.addEventListener( 'fragmentshown', post ); + /** + * 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, + state: Reveal.getState() + } ), '*' ); + }, 500 ); - // Fires when a fragment is hidden - Reveal.addEventListener( 'fragmenthidden', post ); + window.addEventListener( 'message', function( event ) { + var data = JSON.parse( event.data ); + if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) { + clearInterval( connectInterval ); + onConnected(); + } + } ); + } /** * Posts the current slide data to the notes window */ function post() { + var slideElement = Reveal.getCurrentSlide(), - slideIndices = Reveal.getIndices(), - messageData; - - var notes = slideElement.querySelector( 'aside.notes' ), - nextindexh, - nextindexv; - - if( slideElement.nextElementSibling && slideElement.parentNode.nodeName == 'SECTION' ) { - nextindexh = slideIndices.h; - nextindexv = slideIndices.v + 1; - } else { - nextindexh = slideIndices.h + 1; - nextindexv = 0; - } + notesElement = slideElement.querySelector( 'aside.notes' ); - messageData = { - notes : notes ? notes.innerHTML : '', - indexh : slideIndices.h, - indexv : slideIndices.v, - indexf : slideIndices.f, - nextindexh : nextindexh, - nextindexv : nextindexv, - markdown : notes ? typeof notes.getAttribute( 'data-markdown' ) === 'string' : false + var messageData = { + namespace: 'reveal-notes', + type: 'state', + notes: '', + markdown: false, + state: Reveal.getState() }; + // Look for notes defined in a slide attribute + if( slideElement.hasAttribute( 'data-notes' ) ) { + messageData.notes = slideElement.getAttribute( 'data-notes' ); + } + + // 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 ), '*' ); + } - // Navigate to the current slide when the notes are loaded - notesPopup.addEventListener( 'load', function( event ) { + /** + * 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(); - }, false ); - } - // If the there's a 'notes' query set, open directly - if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) { - openNotes(); + } + + connect(); } - // 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( !/receiver/i.test( window.location.search ) ) { - if( event.keyCode === 83 ) { - event.preventDefault(); + // If the there's a 'notes' query set, open directly + if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) { openNotes(); } - }, false ); + + // 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 }; + })(); diff --git a/plugin/postmessage/example.html b/plugin/postmessage/example.html deleted file mode 100644 index cc57a7bb1e0..00000000000 --- a/plugin/postmessage/example.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - -
- - - -
- - - - - diff --git a/plugin/postmessage/postmessage.js b/plugin/postmessage/postmessage.js deleted file mode 100644 index d0f41407857..00000000000 --- a/plugin/postmessage/postmessage.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - - simple postmessage plugin - - Useful when a reveal slideshow is inside an iframe. - It allows to call reveal methods from outside. - - Example: - var reveal = window.frames[0]; - - // Reveal.prev(); - reveal.postMessage(JSON.stringify({method: 'prev', args: []}), '*'); - // Reveal.next(); - reveal.postMessage(JSON.stringify({method: 'next', args: []}), '*'); - // Reveal.slide(2, 2); - reveal.postMessage(JSON.stringify({method: 'slide', args: [2,2]}), '*'); - - Add to the slideshow: - - dependencies: [ - ... - { src: 'plugin/postmessage/postmessage.js', async: true, condition: function() { return !!document.body.classList; } } - ] - -*/ - -(function (){ - - window.addEventListener( "message", function ( event ) { - var data = JSON.parse( event.data ), - method = data.method, - args = data.args; - - if( typeof Reveal[method] === 'function' ) { - Reveal[method].apply( Reveal, data.args ); - } - }, false); - -}()); - - - diff --git a/plugin/print-pdf/print-pdf.js b/plugin/print-pdf/print-pdf.js index 6b6cad66efe..86dc4df8a6b 100644 --- a/plugin/print-pdf/print-pdf.js +++ b/plugin/print-pdf/print-pdf.js @@ -11,34 +11,38 @@ var page = new WebPage(); var system = require( 'system' ); -page.viewportSize = { - width: 1024, - height: 768 +var slideWidth = system.args[3] ? system.args[3].split( 'x' )[0] : 960; +var slideHeight = system.args[3] ? system.args[3].split( 'x' )[1] : 700; + +page.viewportSize = { + width: slideWidth, + height: slideHeight }; +// TODO +// Something is wrong with these config values. An input +// paper width of 1920px actually results in a 756px wide +// PDF. page.paperSize = { - format: 'letter', - orientation: 'landscape', - margin: { - left: '0', - right: '0', - top: '0', - bottom: '0' - } + width: Math.round( slideWidth * 2 ), + height: Math.round( slideHeight * 2 ), + border: 0 }; -var revealFile = system.args[1] || 'index.html?print-pdf'; -var slideFile = system.args[2] || 'slides.pdf'; +var inputFile = system.args[1] || 'index.html?print-pdf'; +var outputFile = system.args[2] || 'slides.pdf'; -if( slideFile.match( /\.pdf$/gi ) === null ) { - slideFile += '.pdf'; +if( outputFile.match( /\.pdf$/gi ) === null ) { + outputFile += '.pdf'; } -console.log( 'Printing PDF...' ); +console.log( 'Printing PDF (Paper size: '+ page.paperSize.width + 'x' + page.paperSize.height +')' ); -page.open( revealFile, function( status ) { - console.log( 'Printed succesfully' ); - page.render( slideFile ); - phantom.exit(); +page.open( inputFile, function( status ) { + window.setTimeout( function() { + console.log( 'Printed succesfully' ); + page.render( outputFile ); + phantom.exit(); + }, 1000 ); } ); diff --git a/plugin/zoom-js/zoom.js b/plugin/zoom-js/zoom.js index cd5b06ff886..da2c10acbd1 100644 --- a/plugin/zoom-js/zoom.js +++ b/plugin/zoom-js/zoom.js @@ -5,9 +5,21 @@ document.querySelector( '.reveal' ).addEventListener( 'mousedown', function( event ) { var modifier = ( Reveal.getConfig().zoomKey ? Reveal.getConfig().zoomKey : 'alt' ) + 'Key'; + var zoomPadding = 20; + var revealScale = Reveal.getScale(); + if( event[ modifier ] && isEnabled ) { event.preventDefault(); - zoom.to({ element: event.target, pan: false }); + + var bounds = event.target.getBoundingClientRect(); + + zoom.to({ + x: ( bounds.left * revealScale ) - zoomPadding, + y: ( bounds.top * revealScale ) - zoomPadding, + width: ( bounds.width * revealScale ) + ( zoomPadding * 2 ), + height: ( bounds.height * revealScale ) + ( zoomPadding * 2 ), + pan: false + }); } } ); @@ -16,11 +28,11 @@ })(); /*! - * zoom.js 0.2 (modified version for use with reveal.js) + * zoom.js 0.3 (modified for use with reveal.js) * http://lab.hakim.se/zoom-js * MIT licensed * - * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se + * Copyright (C) 2011-2014 Hakim El Hattab, http://hakim.se */ var zoom = (function(){ @@ -35,8 +47,6 @@ var zoom = (function(){ var panEngageTimeout = -1, panUpdateInterval = -1; - var currentOptions = null; - // Check for transform support so that we can fallback otherwise var supportsTransforms = 'WebkitTransform' in document.body.style || 'MozTransform' in document.body.style || @@ -58,7 +68,7 @@ var zoom = (function(){ if( level !== 1 && event.keyCode === 27 ) { zoom.out(); } - }, false ); + } ); // Monitor mouse movement for panning document.addEventListener( 'mousemove', function( event ) { @@ -66,38 +76,56 @@ var zoom = (function(){ mouseX = event.clientX; mouseY = event.clientY; } - }, false ); + } ); /** - * Applies the CSS required to zoom in, prioritizes use of CSS3 + * Applies the CSS required to zoom in, prefers the use of CSS3 * transforms but falls back on zoom for IE. * - * @param {Number} pageOffsetX - * @param {Number} pageOffsetY - * @param {Number} elementOffsetX - * @param {Number} elementOffsetY + * @param {Object} rect * @param {Number} scale */ - function magnify( pageOffsetX, pageOffsetY, elementOffsetX, elementOffsetY, scale ) { + function magnify( rect, scale ) { + + var scrollOffset = getScrollOffset(); + + // Ensure a width/height is set + rect.width = rect.width || 1; + rect.height = rect.height || 1; + + // Center the rect within the zoomed viewport + rect.x -= ( window.innerWidth - ( rect.width * scale ) ) / 2; + rect.y -= ( window.innerHeight - ( rect.height * scale ) ) / 2; if( supportsTransforms ) { - var origin = pageOffsetX +'px '+ pageOffsetY +'px', - transform = 'translate('+ -elementOffsetX +'px,'+ -elementOffsetY +'px) scale('+ scale +')'; - - document.body.style.transformOrigin = origin; - document.body.style.OTransformOrigin = origin; - document.body.style.msTransformOrigin = origin; - document.body.style.MozTransformOrigin = origin; - document.body.style.WebkitTransformOrigin = origin; - - document.body.style.transform = transform; - document.body.style.OTransform = transform; - document.body.style.msTransform = transform; - document.body.style.MozTransform = transform; - document.body.style.WebkitTransform = transform; + // Reset + if( scale === 1 ) { + document.body.style.transform = ''; + document.body.style.OTransform = ''; + document.body.style.msTransform = ''; + document.body.style.MozTransform = ''; + document.body.style.WebkitTransform = ''; + } + // Scale + else { + var origin = scrollOffset.x +'px '+ scrollOffset.y +'px', + transform = 'translate('+ -rect.x +'px,'+ -rect.y +'px) scale('+ scale +')'; + + document.body.style.transformOrigin = origin; + document.body.style.OTransformOrigin = origin; + document.body.style.msTransformOrigin = origin; + document.body.style.MozTransformOrigin = origin; + document.body.style.WebkitTransformOrigin = origin; + + document.body.style.transform = transform; + document.body.style.OTransform = transform; + document.body.style.msTransform = transform; + document.body.style.MozTransform = transform; + document.body.style.WebkitTransform = transform; + } } else { - // Reset all values + // Reset if( scale === 1 ) { document.body.style.position = ''; document.body.style.left = ''; @@ -106,11 +134,11 @@ var zoom = (function(){ document.body.style.height = ''; document.body.style.zoom = ''; } - // Apply scale + // Scale else { document.body.style.position = 'relative'; - document.body.style.left = ( - ( pageOffsetX + elementOffsetX ) / scale ) + 'px'; - document.body.style.top = ( - ( pageOffsetY + elementOffsetY ) / scale ) + 'px'; + document.body.style.left = ( - ( scrollOffset.x + rect.x ) / scale ) + 'px'; + document.body.style.top = ( - ( scrollOffset.y + rect.y ) / scale ) + 'px'; document.body.style.width = ( scale * 100 ) + '%'; document.body.style.height = ( scale * 100 ) + '%'; document.body.style.zoom = scale; @@ -119,11 +147,13 @@ var zoom = (function(){ level = scale; - if( level !== 1 && document.documentElement.classList ) { - document.documentElement.classList.add( 'zoomed' ); - } - else { - document.documentElement.classList.remove( 'zoomed' ); + if( document.documentElement.classList ) { + if( level !== 1 ) { + document.documentElement.classList.add( 'zoomed' ); + } + else { + document.documentElement.classList.remove( 'zoomed' ); + } } } @@ -159,7 +189,7 @@ var zoom = (function(){ function getScrollOffset() { return { x: window.scrollX !== undefined ? window.scrollX : window.pageXOffset, - y: window.scrollY !== undefined ? window.scrollY : window.pageXYffset + y: window.scrollY !== undefined ? window.scrollY : window.pageYOffset } } @@ -175,6 +205,7 @@ var zoom = (function(){ * - scale: can be used instead of width/height to explicitly set scale */ to: function( options ) { + // Due to an implementation limitation we can't zoom in // to another element without zooming out first if( level !== 1 ) { @@ -188,11 +219,12 @@ var zoom = (function(){ if( !!options.element ) { // Space around the zoomed in element to leave on screen var padding = 20; + var bounds = options.element.getBoundingClientRect(); - options.width = options.element.getBoundingClientRect().width + ( padding * 2 ); - options.height = options.element.getBoundingClientRect().height + ( padding * 2 ); - options.x = options.element.getBoundingClientRect().left - padding; - options.y = options.element.getBoundingClientRect().top - padding; + options.x = bounds.left - padding; + options.y = bounds.top - padding; + options.width = bounds.width + ( padding * 2 ); + options.height = bounds.height + ( padding * 2 ); } // If width/height values are set, calculate scale from those values @@ -204,13 +236,7 @@ var zoom = (function(){ options.x *= options.scale; options.y *= options.scale; - var scrollOffset = getScrollOffset(); - - if( options.element ) { - scrollOffset.x -= ( window.innerWidth - ( options.width * options.scale ) ) / 2; - } - - magnify( scrollOffset.x, scrollOffset.y, options.x, options.y, options.scale ); + magnify( options, options.scale ); if( options.pan !== false ) { @@ -222,8 +248,6 @@ var zoom = (function(){ } } - - currentOptions = options; } }, @@ -234,13 +258,7 @@ var zoom = (function(){ clearTimeout( panEngageTimeout ); clearInterval( panUpdateInterval ); - var scrollOffset = getScrollOffset(); - - if( currentOptions && currentOptions.element ) { - scrollOffset.x -= ( window.innerWidth - ( currentOptions.width * currentOptions.scale ) ) / 2; - } - - magnify( scrollOffset.x, scrollOffset.y, 0, 0, 1 ); + magnify( { x: 0, y: 0 }, 1 ); level = 1; }, @@ -256,3 +274,5 @@ var zoom = (function(){ })(); + + diff --git a/test/examples/barebones.html b/test/examples/barebones.html index c948d004897..2bee3cb02c7 100644 --- a/test/examples/barebones.html +++ b/test/examples/barebones.html @@ -6,7 +6,7 @@ reveal.js - Barebones - + @@ -29,7 +29,7 @@

No Theme

- + - + - + - + - + diff --git a/test/test-markdown-slide-attributes.html b/test/test-markdown-slide-attributes.html index 3b91784762b..ab6ece4ed49 100644 --- a/test/test-markdown-slide-attributes.html +++ b/test/test-markdown-slide-attributes.html @@ -6,7 +6,7 @@ reveal.js - Test Markdown Attributes - + @@ -19,12 +19,12 @@
- +
- + diff --git a/test/test-markdown.html b/test/test-markdown.html index c89af30d2c6..7ff0efe8aed 100644 --- a/test/test-markdown.html +++ b/test/test-markdown.html @@ -6,7 +6,7 @@ reveal.js - Test Markdown - + @@ -19,10 +19,10 @@
- + -
+
- + diff --git a/test/test-pdf.html b/test/test-pdf.html new file mode 100644 index 00000000000..751ed26eaf7 --- /dev/null +++ b/test/test-pdf.html @@ -0,0 +1,83 @@ + + + + + + + reveal.js - Test PDF exports + + + + + + + + +
+
+ + + + + + + + + + + diff --git a/test/test-pdf.js b/test/test-pdf.js new file mode 100644 index 00000000000..8ec34fd460c --- /dev/null +++ b/test/test-pdf.js @@ -0,0 +1,15 @@ + +Reveal.addEventListener( 'ready', function() { + + // Only one test for now, we're mainly ensuring that there + // are no execution errors when running PDF mode + + test( 'Reveal.isReady', function() { + strictEqual( Reveal.isReady(), true, 'returns true' ); + }); + + +} ); + +Reveal.initialize({ pdf: true }); + diff --git a/test/test.html b/test/test.html index 094f3c72ee7..29d02a9305d 100644 --- a/test/test.html +++ b/test/test.html @@ -6,7 +6,7 @@ reveal.js - Tests - + @@ -19,12 +19,13 @@
-
+

1

+
-
+

2.1

@@ -72,7 +73,7 @@

4

- + diff --git a/test/test.js b/test/test.js index f620b5bd9b2..3f93d3c03a8 100644 --- a/test/test.js +++ b/test/test.js @@ -68,6 +68,12 @@ Reveal.addEventListener( 'ready', function() { strictEqual( Reveal.isFirstSlide(), true, 'true after Reveal.slide( 0, 0 )' ); }); + test( 'Reveal.isFirstSlide after vertical slide', function() { + Reveal.slide( 1, 1 ); + Reveal.slide( 0, 0 ); + strictEqual( Reveal.isFirstSlide(), true, 'true after Reveal.slide( 1, 1 ) and then Reveal.slide( 0, 0 )' ); + }); + test( 'Reveal.isLastSlide', function() { Reveal.slide( 0, 0 ); strictEqual( Reveal.isLastSlide(), false, 'false after Reveal.slide( 0, 0 )' ); @@ -75,34 +81,62 @@ Reveal.addEventListener( 'ready', function() { var lastSlideIndex = document.querySelectorAll( '.reveal .slides>section' ).length - 1; Reveal.slide( lastSlideIndex, 0 ); - strictEqual( Reveal.isLastSlide(), true, 'true after Reveal.slide( ', 0+ lastSlideIndex +' )' ); + strictEqual( Reveal.isLastSlide(), true, 'true after Reveal.slide( '+ lastSlideIndex +', 0 )' ); Reveal.slide( 0, 0 ); strictEqual( Reveal.isLastSlide(), false, 'false after Reveal.slide( 0, 0 )' ); }); + test( 'Reveal.isLastSlide after vertical slide', function() { + var lastSlideIndex = document.querySelectorAll( '.reveal .slides>section' ).length - 1; + + Reveal.slide( 1, 1 ); + Reveal.slide( lastSlideIndex ); + strictEqual( Reveal.isLastSlide(), true, 'true after Reveal.slide( 1, 1 ) and then Reveal.slide( '+ lastSlideIndex +', 0 )' ); + }); + + test( 'Reveal.getTotalSlides', function() { + strictEqual( Reveal.getTotalSlides(), 8, 'eight slides in total' ); + }); + test( 'Reveal.getIndices', function() { var indices = Reveal.getIndices(); - ok( typeof indices.hasOwnProperty( 'h' ), 'h exists' ); - ok( typeof indices.hasOwnProperty( 'v' ), 'v exists' ); - ok( typeof indices.hasOwnProperty( 'f' ), 'f exists' ); + ok( indices.hasOwnProperty( 'h' ), 'h exists' ); + ok( indices.hasOwnProperty( 'v' ), 'v exists' ); + ok( indices.hasOwnProperty( 'f' ), 'f exists' ); Reveal.slide( 1, 0 ); - ok( Reveal.getIndices().h === 1 && Reveal.getIndices().v === 0, 'h 1, v 0' ); + strictEqual( Reveal.getIndices().h, 1, 'h 1' ); + strictEqual( Reveal.getIndices().v, 0, 'v 0' ); Reveal.slide( 1, 2 ); - ok( Reveal.getIndices().h === 1 && Reveal.getIndices().v === 2, 'h 1, v 2' ); + strictEqual( Reveal.getIndices().h, 1, 'h 1' ); + strictEqual( Reveal.getIndices().v, 2, 'v 2' ); Reveal.slide( 0, 0 ); + strictEqual( Reveal.getIndices().h, 0, 'h 0' ); + strictEqual( Reveal.getIndices().v, 0, 'v 0' ); }); test( 'Reveal.getSlide', function() { - var firstSlide = document.querySelector( '.reveal .slides>section:first-child' ); + equal( Reveal.getSlide( 0 ), document.querySelector( '.reveal .slides>section:first-child' ), 'gets correct first slide' ); + equal( Reveal.getSlide( 1 ), document.querySelector( '.reveal .slides>section:nth-child(2)' ), 'no v index returns stack' ); + equal( Reveal.getSlide( 1, 0 ), document.querySelector( '.reveal .slides>section:nth-child(2)>section:nth-child(1)' ), 'v index 0 returns first vertical child' ); + equal( Reveal.getSlide( 1, 1 ), document.querySelector( '.reveal .slides>section:nth-child(2)>section:nth-child(2)' ), 'v index 1 returns second vertical child' ); + + strictEqual( Reveal.getSlide( 100 ), undefined, 'undefined when out of horizontal bounds' ); + strictEqual( Reveal.getSlide( 1, 100 ), undefined, 'undefined when out of vertical bounds' ); + }); - equal( Reveal.getSlide( 0 ), firstSlide, 'gets correct first slide' ); + test( 'Reveal.getSlideBackground', function() { + equal( Reveal.getSlideBackground( 0 ), document.querySelector( '.reveal .backgrounds>.slide-background:first-child' ), 'gets correct first background' ); + equal( Reveal.getSlideBackground( 1 ), document.querySelector( '.reveal .backgrounds>.slide-background:nth-child(2)' ), 'no v index returns stack' ); + equal( Reveal.getSlideBackground( 1, 0 ), document.querySelector( '.reveal .backgrounds>.slide-background:nth-child(2) .slide-background:nth-child(1)' ), 'v index 0 returns first vertical child' ); + equal( Reveal.getSlideBackground( 1, 1 ), document.querySelector( '.reveal .backgrounds>.slide-background:nth-child(2) .slide-background:nth-child(2)' ), 'v index 1 returns second vertical child' ); - strictEqual( Reveal.getSlide( 100 ), undefined, 'returns undefined when slide can\'t be found' ); + strictEqual( Reveal.getSlideBackground( 100 ), undefined, 'undefined when out of horizontal bounds' ); + strictEqual( Reveal.getSlideBackground( 1, 100 ), undefined, 'undefined when out of vertical bounds' ); }); test( 'Reveal.getPreviousSlide/getCurrentSlide', function() { @@ -116,6 +150,16 @@ Reveal.addEventListener( 'ready', function() { equal( Reveal.getCurrentSlide(), secondSlide, 'current is slide #1' ); }); + test( 'Reveal.getProgress', function() { + Reveal.slide( 0, 0 ); + strictEqual( Reveal.getProgress(), 0, 'progress is 0 on first slide' ); + + var lastSlideIndex = document.querySelectorAll( '.reveal .slides>section' ).length - 1; + + Reveal.slide( lastSlideIndex, 0 ); + strictEqual( Reveal.getProgress(), 1, 'progress is 1 on last slide' ); + }); + test( 'Reveal.getScale', function() { ok( typeof Reveal.getScale() === 'number', 'has scale' ); }); @@ -269,6 +313,13 @@ Reveal.addEventListener( 'ready', function() { Reveal.slide( 3, 0, 0 ); equal( fragmentSlide.querySelectorAll( '.fragment.visible' ).length, 2, 'both fragments of same index are shown' ); + + // This slide has three fragments, first one is index 0, second and third have index 1 + Reveal.slide( 2, 2, 0 ); + equal( Reveal.getIndices().f, 0, 'returns correct index for first fragment' ); + + Reveal.slide( 2, 2, 1 ); + equal( Reveal.getIndices().f, 1, 'returns correct index for two fragments with same index' ); }); test( 'Index generation', function() { @@ -331,6 +382,70 @@ Reveal.addEventListener( 'ready', function() { }); + // --------------------------------------------------------------- + // AUTO-SLIDE TESTS + + QUnit.module( 'Auto Sliding' ); + + test( 'Reveal.isAutoSliding', function() { + strictEqual( Reveal.isAutoSliding(), false, 'false by default' ); + + Reveal.configure({ autoSlide: 10000 }); + strictEqual( Reveal.isAutoSliding(), true, 'true after starting' ); + + Reveal.configure({ autoSlide: 0 }); + strictEqual( Reveal.isAutoSliding(), false, 'false after setting to 0' ); + }); + + test( 'Reveal.toggleAutoSlide', function() { + Reveal.configure({ autoSlide: 10000 }); + + Reveal.toggleAutoSlide(); + strictEqual( Reveal.isAutoSliding(), false, 'false after first toggle' ); + Reveal.toggleAutoSlide(); + strictEqual( Reveal.isAutoSliding(), true, 'true after second toggle' ); + + Reveal.configure({ autoSlide: 0 }); + }); + + asyncTest( 'autoslidepaused', function() { + expect( 1 ); + + var _onEvent = function( event ) { + ok( true, 'event fired' ); + } + + Reveal.addEventListener( 'autoslidepaused', _onEvent ); + Reveal.configure({ autoSlide: 10000 }); + Reveal.toggleAutoSlide(); + + start(); + + // cleanup + Reveal.configure({ autoSlide: 0 }); + Reveal.removeEventListener( 'autoslidepaused', _onEvent ); + }); + + asyncTest( 'autoslideresumed', function() { + expect( 1 ); + + var _onEvent = function( event ) { + ok( true, 'event fired' ); + } + + Reveal.addEventListener( 'autoslideresumed', _onEvent ); + Reveal.configure({ autoSlide: 10000 }); + Reveal.toggleAutoSlide(); + Reveal.toggleAutoSlide(); + + start(); + + // cleanup + Reveal.configure({ autoSlide: 0 }); + Reveal.removeEventListener( 'autoslideresumed', _onEvent ); + }); + + // --------------------------------------------------------------- // CONFIGURATION VALUES @@ -371,6 +486,25 @@ Reveal.addEventListener( 'ready', function() { }); + // --------------------------------------------------------------- + // LAZY-LOADING TESTS + + QUnit.module( 'Lazy-Loading' ); + + test( 'img with data-src', function() { + strictEqual( document.querySelectorAll( '.reveal section img[src]' ).length, 1, 'Image source has been set' ); + }); + + test( 'background images', function() { + var imageSource1 = Reveal.getSlide( 0 ).getAttribute( 'data-background-image' ); + var imageSource2 = Reveal.getSlide( 1, 0 ).getAttribute( 'data-background' ); + + // check that the images are applied to the background elements + ok( Reveal.getSlideBackground( 0 ).style.backgroundImage.indexOf( imageSource1 ) !== -1, 'data-background-image worked' ); + ok( Reveal.getSlideBackground( 1, 0 ).style.backgroundImage.indexOf( imageSource2 ) !== -1, 'data-background worked' ); + }); + + // --------------------------------------------------------------- // EVENT TESTS