Merge remote-tracking branch 'upstream/master'
commit
55f220109c
|
@ -1,6 +1,6 @@
|
||||||
/* global module:false */
|
/* global module:false */
|
||||||
module.exports = function(grunt) {
|
module.exports = function(grunt) {
|
||||||
|
var port = grunt.option('port') || 8000;
|
||||||
// Project configuration
|
// Project configuration
|
||||||
grunt.initConfig({
|
grunt.initConfig({
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
|
@ -78,7 +78,7 @@ module.exports = function(grunt) {
|
||||||
connect: {
|
connect: {
|
||||||
server: {
|
server: {
|
||||||
options: {
|
options: {
|
||||||
port: 8000,
|
port: port,
|
||||||
base: '.'
|
base: '.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
README.md
19
README.md
|
@ -778,6 +778,8 @@ $ grunt serve
|
||||||
|
|
||||||
8. Open <http://localhost:8000> to view your presentation
|
8. Open <http://localhost:8000> to view your presentation
|
||||||
|
|
||||||
|
You can change the port by using `grunt serve --port 8001`.
|
||||||
|
|
||||||
|
|
||||||
### Folder Structure
|
### Folder Structure
|
||||||
- **css/** Core styles without which the project does not function
|
- **css/** Core styles without which the project does not function
|
||||||
|
@ -786,6 +788,23 @@ $ grunt serve
|
||||||
- **lib/** All other third party assets (JavaScript, CSS, fonts)
|
- **lib/** All other third party assets (JavaScript, CSS, fonts)
|
||||||
|
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
Please keep the [issue tracker](github.com/hakimel/reveal.js/issues) limited to **bug reports**, **feature requests** and **pull requests**. If you are reporting a bug make sure to include information about which browser and operating system you are using as well as the necessary steps to reproduce the issue.
|
||||||
|
|
||||||
|
If you have personal support questions use [StackOverflow](http://stackoverflow.com/questions/tagged/reveal.js).
|
||||||
|
|
||||||
|
|
||||||
|
#### Pull requests
|
||||||
|
|
||||||
|
- Should follow the coding style
|
||||||
|
- Tabs to indent
|
||||||
|
- Single-quoted strings
|
||||||
|
- No space between function name and opening argument parenthesis
|
||||||
|
- One space after opening and before closing parenthesis
|
||||||
|
- Should be made towards the **dev branch**
|
||||||
|
- Should be submitted from a feature/topic branch (not your master)
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -517,6 +517,10 @@ body {
|
||||||
perspective-origin: 0px -100px;
|
perspective-origin: 0px -100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reveal .slides>section {
|
||||||
|
-ms-perspective: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
.reveal .slides>section,
|
.reveal .slides>section,
|
||||||
.reveal .slides>section>section {
|
.reveal .slides>section>section {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -1050,8 +1054,8 @@ body {
|
||||||
|
|
||||||
.reveal.fade.overview .slides section,
|
.reveal.fade.overview .slides section,
|
||||||
.reveal.fade.overview .slides>section>section,
|
.reveal.fade.overview .slides>section>section,
|
||||||
.reveal.fade.exit-overview .slides section,
|
.reveal.fade.overview-deactivating .slides section,
|
||||||
.reveal.fade.exit-overview .slides>section>section {
|
.reveal.fade.overview-deactivating .slides>section>section {
|
||||||
-webkit-transition: none;
|
-webkit-transition: none;
|
||||||
-moz-transition: none;
|
-moz-transition: none;
|
||||||
-ms-transition: none;
|
-ms-transition: none;
|
||||||
|
|
File diff suppressed because one or more lines are too long
11
index.html
11
index.html
|
@ -167,12 +167,15 @@
|
||||||
<h2>Themes</h2>
|
<h2>Themes</h2>
|
||||||
<p>
|
<p>
|
||||||
Reveal.js comes with a few themes built in: <br>
|
Reveal.js comes with a few themes built in: <br>
|
||||||
|
<a href="?#/themes">Default</a> -
|
||||||
<a href="?theme=sky#/themes">Sky</a> -
|
<a href="?theme=sky#/themes">Sky</a> -
|
||||||
<a href="?theme=beige#/themes">Beige</a> -
|
<a href="?theme=beige#/themes">Beige</a> -
|
||||||
<a href="?theme=simple#/themes">Simple</a> -
|
<a href="?theme=simple#/themes">Simple</a> -
|
||||||
<a href="?theme=serif#/themes">Serif</a> -
|
<a href="?theme=serif#/themes">Serif</a> -
|
||||||
<a href="?theme=night#/themes">Night</a> -
|
<a href="?theme=night#/themes">Night</a> <br>
|
||||||
<a href="?#/themes">Default</a>
|
<a href="?theme=moon.css#/themes">Moon</a> -
|
||||||
|
<a href="?theme=simple.css#/themes">Simple</a> -
|
||||||
|
<a href="?theme=solarized.css#/themes">Solarized</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<small>
|
<small>
|
||||||
|
@ -259,10 +262,10 @@ function linkify( selector ) {
|
||||||
for( var i = 0, len = nodes.length; i < len; i++ ) {
|
for( var i = 0, len = nodes.length; i < len; i++ ) {
|
||||||
var node = nodes[i];
|
var node = nodes[i];
|
||||||
|
|
||||||
if( !node.className ) ) {
|
if( !node.className ) {
|
||||||
node.className += ' roll';
|
node.className += ' roll';
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
164
js/reveal.js
164
js/reveal.js
|
@ -74,6 +74,9 @@ var Reveal = (function(){
|
||||||
// Apply a 3D roll to links on hover
|
// Apply a 3D roll to links on hover
|
||||||
rollingLinks: false,
|
rollingLinks: false,
|
||||||
|
|
||||||
|
// Hides the address bar on mobile devices
|
||||||
|
hideAddressBar: true,
|
||||||
|
|
||||||
// Opens links in an iframe preview overlay
|
// Opens links in an iframe preview overlay
|
||||||
previewLinks: false,
|
previewLinks: false,
|
||||||
|
|
||||||
|
@ -353,17 +356,15 @@ var Reveal = (function(){
|
||||||
createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
|
createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
|
||||||
|
|
||||||
// Cache references to elements
|
// Cache references to elements
|
||||||
if ( config.controls ) {
|
dom.controls = document.querySelector( '.reveal .controls' );
|
||||||
dom.controls = document.querySelector( '.reveal .controls' );
|
|
||||||
|
|
||||||
// There can be multiple instances of controls throughout the page
|
// There can be multiple instances of controls throughout the page
|
||||||
dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
|
dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
|
||||||
dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
|
dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
|
||||||
dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
|
dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
|
||||||
dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
|
dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
|
||||||
dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
|
dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
|
||||||
dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
|
dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,13 +492,8 @@ var Reveal = (function(){
|
||||||
dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
|
dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
|
||||||
dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
|
dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
|
||||||
|
|
||||||
if( dom.controls ) {
|
dom.controls.style.display = config.controls ? 'block' : 'none';
|
||||||
dom.controls.style.display = ( config.controls && dom.controls ) ? 'block' : 'none';
|
dom.progress.style.display = config.progress ? 'block' : 'none';
|
||||||
}
|
|
||||||
|
|
||||||
if( dom.progress ) {
|
|
||||||
dom.progress.style.display = ( config.progress && dom.progress ) ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
if( config.rtl ) {
|
if( config.rtl ) {
|
||||||
dom.wrapper.classList.add( 'rtl' );
|
dom.wrapper.classList.add( 'rtl' );
|
||||||
|
@ -586,16 +582,14 @@ var Reveal = (function(){
|
||||||
dom.progress.addEventListener( 'click', onProgressClicked, false );
|
dom.progress.addEventListener( 'click', onProgressClicked, false );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( config.controls && dom.controls ) {
|
[ 'touchstart', 'click' ].forEach( function( eventName ) {
|
||||||
[ 'touchstart', 'click' ].forEach( function( eventName ) {
|
dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
|
||||||
dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
|
dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
|
||||||
dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
|
dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
|
||||||
dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
|
dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
|
||||||
dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
|
dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
|
||||||
dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
|
dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
|
||||||
dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
|
} );
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -624,16 +618,14 @@ var Reveal = (function(){
|
||||||
dom.progress.removeEventListener( 'click', onProgressClicked, false );
|
dom.progress.removeEventListener( 'click', onProgressClicked, false );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( config.controls && dom.controls ) {
|
[ 'touchstart', 'click' ].forEach( function( eventName ) {
|
||||||
[ 'touchstart', 'click' ].forEach( function( eventName ) {
|
dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
|
||||||
dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
|
dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
|
||||||
dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
|
dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
|
||||||
dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
|
dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
|
||||||
dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
|
dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
|
||||||
dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
|
dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
|
||||||
dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
|
} );
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -778,7 +770,7 @@ var Reveal = (function(){
|
||||||
*/
|
*/
|
||||||
function hideAddressBar() {
|
function hideAddressBar() {
|
||||||
|
|
||||||
if( /iphone|ipod|android/gi.test( navigator.userAgent ) && !/crios/gi.test( navigator.userAgent ) ) {
|
if( config.hideAddressBar && isMobileDevice ) {
|
||||||
// Events that should trigger the address bar to hide
|
// Events that should trigger the address bar to hide
|
||||||
window.addEventListener( 'load', removeAddressBar, false );
|
window.addEventListener( 'load', removeAddressBar, false );
|
||||||
window.addEventListener( 'orientationchange', removeAddressBar, false );
|
window.addEventListener( 'orientationchange', removeAddressBar, false );
|
||||||
|
@ -792,7 +784,8 @@ var Reveal = (function(){
|
||||||
*/
|
*/
|
||||||
function removeAddressBar() {
|
function removeAddressBar() {
|
||||||
|
|
||||||
if( window.orientation === 0 ) {
|
// Portrait and not Chrome for iOS
|
||||||
|
if( window.orientation === 0 && !/crios/gi.test( navigator.userAgent ) ) {
|
||||||
document.documentElement.style.overflow = 'scroll';
|
document.documentElement.style.overflow = 'scroll';
|
||||||
document.body.style.height = '120%';
|
document.body.style.height = '120%';
|
||||||
}
|
}
|
||||||
|
@ -1156,7 +1149,7 @@ var Reveal = (function(){
|
||||||
var depth = window.innerWidth < 400 ? 1000 : 2500;
|
var depth = window.innerWidth < 400 ? 1000 : 2500;
|
||||||
|
|
||||||
dom.wrapper.classList.add( 'overview' );
|
dom.wrapper.classList.add( 'overview' );
|
||||||
dom.wrapper.classList.remove( 'exit-overview' );
|
dom.wrapper.classList.remove( 'overview-deactivating' );
|
||||||
|
|
||||||
clearTimeout( activateOverviewTimeout );
|
clearTimeout( activateOverviewTimeout );
|
||||||
clearTimeout( deactivateOverviewTimeout );
|
clearTimeout( deactivateOverviewTimeout );
|
||||||
|
@ -1164,7 +1157,7 @@ var Reveal = (function(){
|
||||||
// Not the pretties solution, but need to let the overview
|
// Not the pretties solution, but need to let the overview
|
||||||
// class apply first so that slides are measured accurately
|
// class apply first so that slides are measured accurately
|
||||||
// before we can position them
|
// before we can position them
|
||||||
activateOverviewTimeout = setTimeout( function(){
|
activateOverviewTimeout = setTimeout( function() {
|
||||||
|
|
||||||
var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
|
var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
|
||||||
|
|
||||||
|
@ -1241,25 +1234,19 @@ var Reveal = (function(){
|
||||||
// Temporarily add a class so that transitions can do different things
|
// Temporarily add a class so that transitions can do different things
|
||||||
// depending on whether they are exiting/entering overview, or just
|
// depending on whether they are exiting/entering overview, or just
|
||||||
// moving from slide to slide
|
// moving from slide to slide
|
||||||
dom.wrapper.classList.add( 'exit-overview' );
|
dom.wrapper.classList.add( 'overview-deactivating' );
|
||||||
|
|
||||||
deactivateOverviewTimeout = setTimeout( function () {
|
deactivateOverviewTimeout = setTimeout( function () {
|
||||||
dom.wrapper.classList.remove( 'exit-overview' );
|
dom.wrapper.classList.remove( 'overview-deactivating' );
|
||||||
}, 10);
|
}, 1 );
|
||||||
|
|
||||||
// Select all slides
|
// Select all slides
|
||||||
var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
|
toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
|
||||||
|
|
||||||
for( var i = 0, len = slides.length; i < len; i++ ) {
|
|
||||||
var element = slides[i];
|
|
||||||
|
|
||||||
element.style.display = '';
|
|
||||||
|
|
||||||
// Resets all transforms to use the external styles
|
// Resets all transforms to use the external styles
|
||||||
transformElement( element, '' );
|
transformElement( slide, '' );
|
||||||
|
|
||||||
element.removeEventListener( 'click', onOverviewSlideClicked, true );
|
slide.removeEventListener( 'click', onOverviewSlideClicked, true );
|
||||||
}
|
} );
|
||||||
|
|
||||||
slide( indexh, indexv );
|
slide( indexh, indexv );
|
||||||
|
|
||||||
|
@ -1792,48 +1779,45 @@ var Reveal = (function(){
|
||||||
*/
|
*/
|
||||||
function updateControls() {
|
function updateControls() {
|
||||||
|
|
||||||
if ( config.controls && dom.controls ) {
|
var routes = availableRoutes();
|
||||||
|
var fragments = availableFragments();
|
||||||
|
|
||||||
var routes = availableRoutes();
|
// Remove the 'enabled' class from all directions
|
||||||
var fragments = availableFragments();
|
dom.controlsLeft.concat( dom.controlsRight )
|
||||||
|
.concat( dom.controlsUp )
|
||||||
|
.concat( dom.controlsDown )
|
||||||
|
.concat( dom.controlsPrev )
|
||||||
|
.concat( dom.controlsNext ).forEach( function( node ) {
|
||||||
|
node.classList.remove( 'enabled' );
|
||||||
|
node.classList.remove( 'fragmented' );
|
||||||
|
} );
|
||||||
|
|
||||||
// Remove the 'enabled' class from all directions
|
// Add the 'enabled' class to the available routes
|
||||||
dom.controlsLeft.concat( dom.controlsRight )
|
if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
||||||
.concat( dom.controlsUp )
|
if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
||||||
.concat( dom.controlsDown )
|
if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
||||||
.concat( dom.controlsPrev )
|
if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
||||||
.concat( dom.controlsNext ).forEach( function( node ) {
|
|
||||||
node.classList.remove( 'enabled' );
|
|
||||||
node.classList.remove( 'fragmented' );
|
|
||||||
} );
|
|
||||||
|
|
||||||
// Add the 'enabled' class to the available routes
|
// Prev/next buttons
|
||||||
if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
||||||
if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
||||||
if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
|
||||||
if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
|
||||||
|
|
||||||
// Prev/next buttons
|
// Highlight fragment directions
|
||||||
if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
if( currentSlide ) {
|
||||||
if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
|
|
||||||
|
|
||||||
// Highlight fragment directions
|
// Always apply fragment decorator to prev/next buttons
|
||||||
if( currentSlide ) {
|
if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
||||||
|
if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
||||||
|
|
||||||
// Always apply fragment decorator to prev/next buttons
|
// Apply fragment decorators to directional buttons based on
|
||||||
if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
// what slide axis they are in
|
||||||
if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
if( isVerticalSlide( currentSlide ) ) {
|
||||||
|
if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
||||||
// Apply fragment decorators to directional buttons based on
|
if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
||||||
// what slide axis they are in
|
}
|
||||||
if( isVerticalSlide( currentSlide ) ) {
|
else {
|
||||||
if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
||||||
if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
||||||
}
|
|
||||||
else {
|
|
||||||
if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
|
||||||
if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -13,11 +13,13 @@ pre code {
|
||||||
|
|
||||||
pre .keyword,
|
pre .keyword,
|
||||||
pre .tag,
|
pre .tag,
|
||||||
pre .django .tag,
|
|
||||||
pre .django .keyword,
|
|
||||||
pre .css .class,
|
pre .css .class,
|
||||||
pre .css .id,
|
pre .css .id,
|
||||||
pre .lisp .title {
|
pre .lisp .title,
|
||||||
|
pre .nginx .title,
|
||||||
|
pre .request,
|
||||||
|
pre .status,
|
||||||
|
pre .clojure .attribute {
|
||||||
color: #E3CEAB;
|
color: #E3CEAB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,32 +51,27 @@ pre .tex .special {
|
||||||
}
|
}
|
||||||
|
|
||||||
pre .diff .chunk,
|
pre .diff .chunk,
|
||||||
pre .ruby .subst {
|
pre .subst {
|
||||||
color: #8F8F8F;
|
color: #8F8F8F;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre .dos .keyword,
|
pre .dos .keyword,
|
||||||
pre .python .decorator,
|
pre .python .decorator,
|
||||||
pre .class .title,
|
pre .title,
|
||||||
pre .haskell .label,
|
pre .haskell .type,
|
||||||
pre .function .title,
|
|
||||||
pre .ini .title,
|
|
||||||
pre .diff .header,
|
pre .diff .header,
|
||||||
pre .ruby .class .parent,
|
pre .ruby .class .parent,
|
||||||
pre .apache .tag,
|
pre .apache .tag,
|
||||||
pre .nginx .built_in,
|
pre .nginx .built_in,
|
||||||
pre .tex .command,
|
pre .tex .command,
|
||||||
pre .input_number {
|
pre .prompt {
|
||||||
color: #efef8f;
|
color: #efef8f;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre .dos .winutils,
|
pre .dos .winutils,
|
||||||
pre .ruby .symbol,
|
pre .ruby .symbol,
|
||||||
pre .ruby .symbol .string,
|
pre .ruby .symbol .string,
|
||||||
pre .ruby .symbol .keyword,
|
pre .ruby .string {
|
||||||
pre .ruby .symbol .keymethods,
|
|
||||||
pre .ruby .string,
|
|
||||||
pre .ruby .instancevar {
|
|
||||||
color: #DCA3A3;
|
color: #DCA3A3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,10 +103,12 @@ pre .doctype {
|
||||||
color: #7F9F7F;
|
color: #7F9F7F;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre .xml .css,
|
pre .coffeescript .javascript,
|
||||||
|
pre .javascript .xml,
|
||||||
|
pre .tex .formula,
|
||||||
pre .xml .javascript,
|
pre .xml .javascript,
|
||||||
pre .xml .vbscript,
|
pre .xml .vbscript,
|
||||||
pre .tex .formula {
|
pre .xml .css,
|
||||||
|
pre .xml .cdata {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
14
package.json
14
package.json
|
@ -21,19 +21,19 @@
|
||||||
"node": "~0.8.0"
|
"node": "~0.8.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"underscore": "~1.3.3",
|
"underscore": "~1.5.1",
|
||||||
"express": "~2.5.9",
|
"express": "~2.5.9",
|
||||||
"mustache": "~0.4.0",
|
"mustache": "~0.7.2",
|
||||||
"socket.io": "~0.9.13"
|
"socket.io": "~0.9.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"grunt-contrib-qunit": "~0.2.2",
|
"grunt-contrib-qunit": "~0.2.2",
|
||||||
"grunt-contrib-jshint": "~0.2.0",
|
"grunt-contrib-jshint": "~0.6.4",
|
||||||
"grunt-contrib-cssmin": "~0.4.1",
|
"grunt-contrib-cssmin": "~0.4.1",
|
||||||
"grunt-contrib-uglify": "~0.1.1",
|
"grunt-contrib-uglify": "~0.2.4",
|
||||||
"grunt-contrib-watch": "~0.2.0",
|
"grunt-contrib-watch": "~0.5.3",
|
||||||
"grunt-contrib-sass": "~0.2.2",
|
"grunt-contrib-sass": "~0.5.0",
|
||||||
"grunt-contrib-connect": "~0.2.0",
|
"grunt-contrib-connect": "~0.4.1",
|
||||||
"grunt-zip": "~0.7.0",
|
"grunt-zip": "~0.7.0",
|
||||||
"grunt": "~0.4.0"
|
"grunt": "~0.4.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,16 @@
|
||||||
* markdown inside of presentations as well as loading
|
* markdown inside of presentations as well as loading
|
||||||
* of external markdown documents.
|
* of external markdown documents.
|
||||||
*/
|
*/
|
||||||
(function(){
|
(function( root, factory ) {
|
||||||
|
if( typeof exports === 'object' ) {
|
||||||
|
module.exports = factory( require( './marked' ) );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Browser globals (root is window)
|
||||||
|
root.RevealMarkdown = factory( root.marked );
|
||||||
|
root.RevealMarkdown.initialize();
|
||||||
|
}
|
||||||
|
}( this, function( marked ) {
|
||||||
|
|
||||||
if( typeof marked === 'undefined' ) {
|
if( typeof marked === 'undefined' ) {
|
||||||
throw 'The reveal.js Markdown plugin requires marked to be loaded';
|
throw 'The reveal.js Markdown plugin requires marked to be loaded';
|
||||||
|
@ -17,6 +26,10 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var DEFAULT_SLIDE_SEPARATOR = '^\n---\n$',
|
||||||
|
DEFAULT_NOTES_SEPARATOR = 'note:';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the markdown contents of a slide section
|
* Retrieves the markdown contents of a slide section
|
||||||
* element. Normalizes leading tabs/whitespace.
|
* element. Normalizes leading tabs/whitespace.
|
||||||
|
@ -72,15 +85,32 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspects the given options and fills out default
|
||||||
|
* values for what's not defined.
|
||||||
|
*/
|
||||||
|
function getSlidifyOptions( options ) {
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
|
||||||
|
options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
|
||||||
|
options.attributes = options.attributes || '';
|
||||||
|
|
||||||
|
return options;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function for constructing a markdown slide.
|
* Helper function for constructing a markdown slide.
|
||||||
*/
|
*/
|
||||||
function createMarkdownSlide( data ) {
|
function createMarkdownSlide( content, options ) {
|
||||||
|
|
||||||
var content = data.content || data;
|
options = getSlidifyOptions( options );
|
||||||
|
|
||||||
if( data.notes ) {
|
var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
|
||||||
content += '<aside class="notes" data-markdown>' + data.notes + '</aside>';
|
|
||||||
|
if( notesMatch.length === 2 ) {
|
||||||
|
content = notesMatch[0] + '<aside class="notes" data-markdown>' + notesMatch[1].trim() + '</aside>';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '<script type="text/template">' + content + '</script>';
|
return '<script type="text/template">' + content + '</script>';
|
||||||
|
@ -91,25 +121,18 @@
|
||||||
* Parses a data string into multiple slides based
|
* Parses a data string into multiple slides based
|
||||||
* on the passed in separator arguments.
|
* on the passed in separator arguments.
|
||||||
*/
|
*/
|
||||||
function slidifyMarkdown( markdown, options ) {
|
function slidify( markdown, options ) {
|
||||||
|
|
||||||
options = options || {};
|
options = getSlidifyOptions( options );
|
||||||
options.separator = options.separator || '^\n---\n$';
|
|
||||||
options.notesSeparator = options.notesSeparator || 'note:';
|
|
||||||
options.attributes = options.attributes || '';
|
|
||||||
|
|
||||||
var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
|
var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
|
||||||
horizontalSeparatorRegex = new RegExp( options.separator ),
|
horizontalSeparatorRegex = new RegExp( options.separator );
|
||||||
notesSeparatorRegex = new RegExp( options.notesSeparator, 'mgi' );
|
|
||||||
|
|
||||||
var matches,
|
var matches,
|
||||||
noteMatch,
|
|
||||||
lastIndex = 0,
|
lastIndex = 0,
|
||||||
isHorizontal,
|
isHorizontal,
|
||||||
wasHorizontal = true,
|
wasHorizontal = true,
|
||||||
content,
|
content,
|
||||||
notes,
|
|
||||||
slide,
|
|
||||||
sectionStack = [];
|
sectionStack = [];
|
||||||
|
|
||||||
// iterate until all blocks between separators are stacked up
|
// iterate until all blocks between separators are stacked up
|
||||||
|
@ -126,25 +149,14 @@
|
||||||
|
|
||||||
// pluck slide content from markdown input
|
// pluck slide content from markdown input
|
||||||
content = markdown.substring( lastIndex, matches.index );
|
content = markdown.substring( lastIndex, matches.index );
|
||||||
noteMatch = content.split( notesSeparatorRegex );
|
|
||||||
|
|
||||||
if( noteMatch.length === 2 ) {
|
|
||||||
content = noteMatch[0];
|
|
||||||
notes = noteMatch[1].trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
slide = {
|
|
||||||
content: content,
|
|
||||||
notes: notes || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
if( isHorizontal && wasHorizontal ) {
|
if( isHorizontal && wasHorizontal ) {
|
||||||
// add to horizontal stack
|
// add to horizontal stack
|
||||||
sectionStack.push( slide );
|
sectionStack.push( content );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// add to vertical stack
|
// add to vertical stack
|
||||||
sectionStack[sectionStack.length-1].push( slide );
|
sectionStack[sectionStack.length-1].push( content );
|
||||||
}
|
}
|
||||||
|
|
||||||
lastIndex = separatorRegex.lastIndex;
|
lastIndex = separatorRegex.lastIndex;
|
||||||
|
@ -160,12 +172,16 @@
|
||||||
for( var i = 0, len = sectionStack.length; i < len; i++ ) {
|
for( var i = 0, len = sectionStack.length; i < len; i++ ) {
|
||||||
// vertical
|
// vertical
|
||||||
if( sectionStack[i].propertyIsEnumerable( length ) && typeof sectionStack[i].splice === 'function' ) {
|
if( sectionStack[i].propertyIsEnumerable( length ) && typeof sectionStack[i].splice === 'function' ) {
|
||||||
markdownSections += '<section '+ options.attributes +'>' +
|
markdownSections += '<section '+ options.attributes +'>';
|
||||||
'<section data-markdown>' + sectionStack[i].map( createMarkdownSlide ).join( '</section><section data-markdown>' ) + '</section>' +
|
|
||||||
'</section>';
|
sectionStack[i].forEach( function( child ) {
|
||||||
|
markdownSections += '<section data-markdown>' + createMarkdownSlide( child, options ) + '</section>';
|
||||||
|
} );
|
||||||
|
|
||||||
|
markdownSections += '</section>';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i] ) + '</section>';
|
markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +189,12 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadExternalMarkdown() {
|
/**
|
||||||
|
* Parses any current data-markdown slides, splits
|
||||||
|
* multi-slide markdown into separate sections and
|
||||||
|
* handles loading of external markdown.
|
||||||
|
*/
|
||||||
|
function processSlides() {
|
||||||
|
|
||||||
var sections = document.querySelectorAll( '[data-markdown]'),
|
var sections = document.querySelectorAll( '[data-markdown]'),
|
||||||
section;
|
section;
|
||||||
|
@ -198,7 +219,7 @@
|
||||||
if( xhr.readyState === 4 ) {
|
if( xhr.readyState === 4 ) {
|
||||||
if ( xhr.status >= 200 && xhr.status < 300 ) {
|
if ( xhr.status >= 200 && xhr.status < 300 ) {
|
||||||
|
|
||||||
section.outerHTML = slidifyMarkdown( xhr.responseText, {
|
section.outerHTML = slidify( xhr.responseText, {
|
||||||
separator: section.getAttribute( 'data-separator' ),
|
separator: section.getAttribute( 'data-separator' ),
|
||||||
verticalSeparator: section.getAttribute( 'data-vertical' ),
|
verticalSeparator: section.getAttribute( 'data-vertical' ),
|
||||||
notesSeparator: section.getAttribute( 'data-notes' ),
|
notesSeparator: section.getAttribute( 'data-notes' ),
|
||||||
|
@ -228,9 +249,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else if( section.getAttribute( 'data-separator' ) ) {
|
else if( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-vertical' ) || section.getAttribute( 'data-notes' ) ) {
|
||||||
|
|
||||||
section.outerHTML = slidifyMarkdown( getMarkdownFromSlide( section ), {
|
section.outerHTML = slidify( getMarkdownFromSlide( section ), {
|
||||||
separator: section.getAttribute( 'data-separator' ),
|
separator: section.getAttribute( 'data-separator' ),
|
||||||
verticalSeparator: section.getAttribute( 'data-vertical' ),
|
verticalSeparator: section.getAttribute( 'data-vertical' ),
|
||||||
notesSeparator: section.getAttribute( 'data-notes' ),
|
notesSeparator: section.getAttribute( 'data-notes' ),
|
||||||
|
@ -238,11 +259,20 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) );
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertMarkdownToHTML() {
|
/**
|
||||||
|
* Converts any current data-markdown slides in the
|
||||||
|
* DOM to HTML.
|
||||||
|
*/
|
||||||
|
function convertSlides() {
|
||||||
|
|
||||||
var sections = document.querySelectorAll( '[data-markdown]');
|
var sections = document.querySelectorAll( '[data-markdown]');
|
||||||
|
|
||||||
|
@ -250,22 +280,41 @@
|
||||||
|
|
||||||
var section = sections[i];
|
var section = sections[i];
|
||||||
|
|
||||||
var notes = section.querySelector( 'aside.notes' );
|
// Only parse the same slide once
|
||||||
var markdown = getMarkdownFromSlide( section );
|
if( !section.getAttribute( 'data-markdown-parsed' ) ) {
|
||||||
|
|
||||||
section.innerHTML = marked( markdown );
|
section.setAttribute( 'data-markdown-parsed', true )
|
||||||
|
|
||||||
|
var notes = section.querySelector( 'aside.notes' );
|
||||||
|
var markdown = getMarkdownFromSlide( section );
|
||||||
|
|
||||||
|
section.innerHTML = marked( markdown );
|
||||||
|
|
||||||
|
// If there were notes, we need to re-add them after
|
||||||
|
// having overwritten the section's HTML
|
||||||
|
if( notes ) {
|
||||||
|
section.appendChild( notes );
|
||||||
|
}
|
||||||
|
|
||||||
// If there were notes, we need to re-add them after
|
|
||||||
// having overwritten the section's HTML
|
|
||||||
if( notes ) {
|
|
||||||
section.appendChild( notes );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadExternalMarkdown();
|
// API
|
||||||
convertMarkdownToHTML();
|
return {
|
||||||
|
|
||||||
})();
|
initialize: function() {
|
||||||
|
processSlides();
|
||||||
|
convertSlides();
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: Do these belong in the API?
|
||||||
|
processSlides: processSlides,
|
||||||
|
convertSlides: convertSlides,
|
||||||
|
slidify: slidify
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
(function() {
|
(function() {
|
||||||
// don't emit events from inside the previews themselves
|
// Don't emit events from inside of notes windows
|
||||||
if ( window.location.search.match( /receiver/gi ) ) { return; }
|
if ( window.location.search.match( /receiver/gi ) ) { return; }
|
||||||
|
|
||||||
var multiplex = Reveal.getConfig().multiplex;
|
var multiplex = Reveal.getConfig().multiplex;
|
||||||
|
|
||||||
var socket = io.connect(multiplex.url);
|
var socket = io.connect(multiplex.url);
|
||||||
|
|
|
@ -139,11 +139,11 @@
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="wrap-current-slide" class="slides">
|
<div id="wrap-current-slide" class="slides">
|
||||||
<script>document.write( '<iframe width="1280" height="1024" id="current-slide" src="'+ window.opener.location.href +'"></iframe>' );</script>
|
<script>document.write( '<iframe width="1280" height="1024" id="current-slide" src="'+ window.opener.location.href +'?receiver"></iframe>' );</script>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="wrap-next-slide" class="slides">
|
<div id="wrap-next-slide" class="slides">
|
||||||
<script>document.write( '<iframe width="640" height="512" id="next-slide" src="'+ window.opener.location.href +'"></iframe>' );</script>
|
<script>document.write( '<iframe width="640" height="512" id="next-slide" src="'+ window.opener.location.href +'?receiver"></iframe>' );</script>
|
||||||
<span>UPCOMING:</span>
|
<span>UPCOMING:</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -239,6 +239,10 @@
|
||||||
currentSlide.contentWindow.Reveal.addEventListener( 'fragmentshown', synchronizeMainWindow );
|
currentSlide.contentWindow.Reveal.addEventListener( 'fragmentshown', synchronizeMainWindow );
|
||||||
currentSlide.contentWindow.Reveal.addEventListener( 'fragmenthidden', synchronizeMainWindow );
|
currentSlide.contentWindow.Reveal.addEventListener( 'fragmenthidden', synchronizeMainWindow );
|
||||||
|
|
||||||
|
// Reconfigure the notes window to remove needless UI
|
||||||
|
currentSlide.contentWindow.Reveal.configure({ controls: false, progress: false, overview: false });
|
||||||
|
nextSlide.contentWindow.Reveal.configure({ controls: false, progress: false, overview: false });
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
* Detects if notes are enable and the current page is opened inside an /iframe
|
* Detects if notes are enable and the current page is opened inside an /iframe
|
||||||
* this prevents loading Remotes.io several times
|
* this prevents loading Remotes.io several times
|
||||||
*/
|
*/
|
||||||
var remotesAndIsNotes = (function(){
|
var isNotesAndIframe = (function(){
|
||||||
return !(window.RevealNotes && self == top);
|
return window.RevealNotes && !(self == top);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if(!hasTouch && !remotesAndIsNotes){
|
if(!hasTouch && !isNotesAndIframe){
|
||||||
head.ready( 'remotes.ne.min.js', function() {
|
head.ready( 'remotes.ne.min.js', function() {
|
||||||
new Remotes("preview")
|
new Remotes("preview")
|
||||||
.on("swipe-left", function(e){ Reveal.right(); })
|
.on("swipe-left", function(e){ Reveal.right(); })
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<title>reveal.js - Test Markdown</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../css/reveal.min.css">
|
||||||
|
<link rel="stylesheet" href="qunit-1.12.0.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="overflow: auto;">
|
||||||
|
|
||||||
|
<div id="qunit"></div>
|
||||||
|
<div id="qunit-fixture"></div>
|
||||||
|
|
||||||
|
<div class="reveal" style="display: none;">
|
||||||
|
|
||||||
|
<div class="slides">
|
||||||
|
|
||||||
|
<!-- <section data-markdown="example.md" data-separator="^\n\n\n" data-vertical="^\n\n"></section> -->
|
||||||
|
|
||||||
|
<!-- Slides are separated by newline + three dashes + newline, vertical slides identical but two dashes -->
|
||||||
|
<section data-markdown data-separator="^\n---\n$" data-vertical="^\n--\n$">
|
||||||
|
<script type="text/template">
|
||||||
|
## Slide 1.1
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
## Slide 1.2
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 2
|
||||||
|
</script>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../lib/js/head.min.js"></script>
|
||||||
|
<script src="../js/reveal.min.js"></script>
|
||||||
|
<script src="../plugin/markdown/marked.js"></script>
|
||||||
|
<script src="../plugin/markdown/markdown.js"></script>
|
||||||
|
<script src="qunit-1.12.0.js"></script>
|
||||||
|
|
||||||
|
<script src="test-markdown.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
|
||||||
|
Reveal.addEventListener( 'ready', function() {
|
||||||
|
|
||||||
|
QUnit.module( 'Markdown' );
|
||||||
|
|
||||||
|
test( 'Vertical separator', function() {
|
||||||
|
strictEqual( document.querySelectorAll( '.reveal .slides>section>section' ).length, 2, 'found two slides' );
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
Reveal.initialize();
|
||||||
|
|
Loading…
Reference in New Issue