1
0
mirror of https://github.com/hakimel/reveal.js.git synced 2025-09-13 16:12:03 +02:00

Compare commits

...

41 Commits

Author SHA1 Message Date
Hakim El Hattab
27e1dfbc1e initial react wrapper exploration 2024-03-25 10:09:49 +01:00
Hakim El Hattab
6410c756ea auto-animate demo tweak 2024-03-25 09:10:26 +01:00
Hakim El Hattab
62297e6259 nil check slides before running auto-animate transition #3592 2024-03-25 09:01:53 +01:00
Hakim El Hattab
ade53094b9 Merge pull request #3598 from alifeee/fix/r-stack-chrome
Fix `r-stack` overflow behaviour on Chromium browsers
2024-03-25 08:52:32 +01:00
alifeee
164254655b update build 2024-03-24 23:13:36 +00:00
alifeee
e2344787c4 fix r-stack with grid-template-rows: 100%; 2024-03-24 23:11:22 +00:00
Hakim El Hattab
334abff10f Merge pull request #3595 from jokester/mathjax3-fix-for-multiple-instances 2024-03-23 12:27:59 +01:00
Wang Guan
19c1bca1e4 MathJax3: allow non-singleton Reveal instance 2024-03-23 17:22:23 +09:00
Hakim El Hattab
0799c8f674 fix exception when destroying uninitialized reveal instance (closes #3593) 2024-03-22 14:29:35 +01:00
Hakim El Hattab
924bdb6305 fix vertical swipe navigation not blocking page scrolling in embedded decks 2024-03-19 09:09:44 +01:00
Hakim El Hattab
d4e5c39fe4 Merge pull request #3588 from NatKarmios/notes-error-catch
Fix error when the notes plugin receives a non-string message
2024-03-15 09:02:32 +01:00
Nat Karmios
2fb4b46307 Notes: don't error on non-string message 2024-03-14 18:18:33 +00:00
Hakim El Hattab
488c5c8f94 fix rtl prev/next navigation on slides with fragments 2024-03-13 15:15:59 +01:00
Hakim El Hattab
421da63750 fix previous bg video playing in background 2024-03-12 10:54:28 +01:00
Hakim El Hattab
62b1ea302c don't start video bgs if autoPlayMedia config is set to false 2024-03-11 14:20:01 +01:00
Hakim El Hattab
76ec60a137 fix issue when disabling autoPlay config flag at runtime 2024-03-11 13:24:27 +01:00
Hakim El Hattab
1748a55ece don't restart media when it's already playing #2882 2024-03-11 11:24:00 +01:00
Hakim El Hattab
a29a9c71ae allow same background video to continue playing across multiple slides #3189 #2882
Co-authored-by: Chi Vong <chivongv@gmail.com>
2024-03-08 14:02:26 +01:00
Hakim El Hattab
6ef138b61f dont prevent swipe navigation on video backgrounds #3584 2024-03-07 10:26:01 +01:00
Hakim El Hattab
63e0a37a88 fix broken backwards navigation in rtl mode 2024-03-06 10:55:09 +01:00
Hakim El Hattab
2927be34d8 new .enter-fullscreen class lets you add shortcuts to fullscreen mode 2024-02-28 11:08:56 +01:00
Hakim El Hattab
9d4b4362e9 5.0.5 2024-02-26 11:17:44 +01:00
Hakim El Hattab
8efd7af37c fix fragment events not firing in scroll view + add tests #3580 2024-02-26 10:54:16 +01:00
Hakim El Hattab
66fa4350e1 indentation tweak 2024-02-26 10:53:34 +01:00
Hakim El Hattab
f149d1f7ca Merge pull request #3570 from gchriz/toggleHelp
add F1 key to toggleHelp for non-english keyboards
2024-02-06 09:51:42 +01:00
Christian Ziemski
0951ce2b4f add F1 key to toggleHelp for non-english keyyboards 2024-02-05 16:56:45 +01:00
Hakim El Hattab
18ec38a6b1 tweaks for #3568 2024-02-05 11:27:57 +01:00
Hakim El Hattab
67b5ec1773 Merge pull request #3568 from bouzidanas/master
fix vertical stack background not working in scroll view
2024-02-05 11:14:13 +01:00
Anas Bouzid
50580c37c2 add comment for background fix 2024-02-04 22:16:42 -06:00
Anas Bouzid
ec4eeab478 fix selector constant 2024-02-04 21:59:46 -06:00
Anas Bouzid
4e353b207d remove console logs 2024-02-04 21:49:24 -06:00
Anas Bouzid
608e0eefcd fix selector constant 2024-02-04 21:32:55 -06:00
Anas Bouzid
aa31cab9e3 fix slide backgrounds being replaced by global background 2024-02-04 21:26:06 -06:00
Anas Bouzid
28aee42e8e update build 2024-02-04 19:35:35 -06:00
Anas Bouzid
ebca26e1f9 update build 2024-02-04 19:23:37 -06:00
Anas Bouzid
d61b375bf8 add logs to scrollview.js 2024-02-04 19:17:06 -06:00
Anas Bouzid
dcc21516dd Merge pull request #1 from hakimel/master
update repo
2024-02-04 18:37:16 -06:00
Hakim El Hattab
16f6633014 fix xss issue reported by @realansgar, regression from 3dade61176 2024-01-30 14:14:40 +01:00
Hakim El Hattab
5d131cea20 add support for keyboard navigation in scroll view #3515 2024-01-10 14:13:54 +01:00
Hakim El Hattab
52480157a1 2024 2024-01-09 11:38:35 +01:00
Hakim El Hattab
5ee1f729bd fix missing backgrounds when scroll view is actived responsively (fixes #3554) 2023-12-22 13:18:06 +01:00
41 changed files with 3296 additions and 112 deletions

View File

@@ -1,4 +1,4 @@
Copyright (C) 2011-2023 Hakim El Hattab, http://hakim.se, and reveal.js contributors
Copyright (C) 2011-2024 Hakim El Hattab, http://hakim.se, and reveal.js contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -46,5 +46,5 @@ Hakim's open source work is supported by <a href="https://github.com/sponsors/ha
---
<div align="center">
MIT licensed | Copyright © 2011-2023 Hakim El Hattab, https://hakim.se
MIT licensed | Copyright © 2011-2024 Hakim El Hattab, https://hakim.se
</div>

View File

@@ -25,6 +25,7 @@
// Stack multiple elements on top of each other
.reveal .r-stack {
display: grid;
grid-template-rows: 100%;
}
.reveal .r-stack > * {

View File

@@ -631,11 +631,16 @@ $controlsArrowAngleActive: 36deg;
touch-action: pinch-zoom;
}
// Swiping on an embedded deck should not block page scrolling
// Swiping on an embedded deck should not block page scrolling...
.reveal.embedded {
touch-action: pan-y;
}
// ... unless we're on a vertical slide
.reveal.embedded.is-vertical-slide {
touch-action: none;
}
.reveal .slides {
position: absolute;
width: 100%;

View File

@@ -165,9 +165,9 @@
</section>
<section data-auto-animate data-auto-animate-easing="cubic-bezier(0.770, 0.000, 0.175, 1.000)">
<div class="r-stack">
<div data-id="box1" style="background: cyan; width: 300px; height: 300px; border-radius: 200px;"></div>
<div data-id="box2" style="background: magenta; width: 200px; height: 200px; border-radius: 200px;"></div>
<div data-id="box3" style="background: yellow; width: 100px; height: 100px; border-radius: 200px;"></div>
<div data-id="box1" style="background: cyan; width: 300px; height: 300px;"></div>
<div data-id="box2" style="background: magenta; width: 200px; height: 200px;"></div>
<div data-id="box3" style="background: yellow; width: 100px; height: 100px;"></div>
</div>
<h2 style="margin-top: 20px;">Auto-Animate</h2>
</section>

6
dist/reveal.css vendored

File diff suppressed because one or more lines are too long

6
dist/reveal.esm.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6
dist/reveal.js vendored

File diff suppressed because one or more lines are too long

2
dist/reveal.js.map vendored

File diff suppressed because one or more lines are too long

View File

@@ -106,13 +106,13 @@
<script src="../plugin/markdown/markdown.js"></script>
<script src="../plugin/highlight/highlight.js"></script>
<script>
Reveal.initialize({
view: 'scroll',
hash: true,
Reveal.initialize({
view: 'scroll',
hash: true,
plugins: [ RevealMarkdown, RevealHighlight, RevealNotes ]
});
</script>
</script>
</body>
</html>

View File

@@ -29,7 +29,7 @@ const banner = `/*!
* ${pkg.homepage}
* MIT licensed
*
* Copyright (C) 2011-2023 Hakim El Hattab, https://hakim.se
* Copyright (C) 2011-2024 Hakim El Hattab, https://hakim.se
*/\n`
// Prevents warnings from opening too many test pages

View File

@@ -31,10 +31,13 @@ export default class AutoAnimate {
let toSlideIndex = allSlides.indexOf( toSlide );
let fromSlideIndex = allSlides.indexOf( fromSlide );
// Ensure that both slides are auto-animate targets with the same data-auto-animate-id value
// (including null if absent on both) and that data-auto-animate-restart isn't set on the
// physically latter slide (independent of slide direction)
if( fromSlide.hasAttribute( 'data-auto-animate' ) && toSlide.hasAttribute( 'data-auto-animate' )
// Ensure that;
// 1. Both slides exist.
// 2. Both slides are auto-animate targets with the same
// data-auto-animate-id value (including null if absent on both).
// 3. data-auto-animate-restart isn't set on the physically latter
// slide (independent of slide direction).
if( fromSlide && toSlide && fromSlide.hasAttribute( 'data-auto-animate' ) && toSlide.hasAttribute( 'data-auto-animate' )
&& fromSlide.getAttribute( 'data-auto-animate-id' ) === toSlide.getAttribute( 'data-auto-animate-id' )
&& !( toSlideIndex > fromSlideIndex ? toSlide : fromSlide ).hasAttribute( 'data-auto-animate-restart' ) ) {

View File

@@ -268,14 +268,15 @@ export default class Backgrounds {
*/
update( includeAll = false ) {
let config = this.Reveal.getConfig();
let currentSlide = this.Reveal.getCurrentSlide();
let indices = this.Reveal.getIndices();
let currentBackground = null;
// Reverse past/future classes when in RTL mode
let horizontalPast = this.Reveal.getConfig().rtl ? 'future' : 'past',
horizontalFuture = this.Reveal.getConfig().rtl ? 'past' : 'future';
let horizontalPast = config.rtl ? 'future' : 'past',
horizontalFuture = config.rtl ? 'past' : 'future';
// Update the classes of all backgrounds to match the
// states of their slides (past/present/future)
@@ -321,6 +322,42 @@ export default class Backgrounds {
} );
// The previous background may refer to a DOM element that has
// been removed after a presentation is synced & bgs are recreated
if( this.previousBackground && !this.previousBackground.closest( 'body' ) ) {
this.previousBackground = null;
}
if( currentBackground && this.previousBackground ) {
// Don't transition between identical backgrounds. This
// prevents unwanted flicker.
let previousBackgroundHash = this.previousBackground.getAttribute( 'data-background-hash' );
let currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== this.previousBackground ) {
this.element.classList.add( 'no-transition' );
// If multiple slides have the same background video, carry
// the <video> element forward so that it plays continuously
// across multiple slides
const currentVideo = currentBackground.querySelector( 'video' );
const previousVideo = this.previousBackground.querySelector( 'video' );
if( currentVideo && previousVideo ) {
const currentVideoParent = currentVideo.parentNode;
const previousVideoParent = previousVideo.parentNode;
// Swap the two videos
previousVideoParent.appendChild( currentVideo );
currentVideoParent.appendChild( previousVideo );
}
}
}
// Stop content inside of previous backgrounds
if( this.previousBackground ) {
@@ -347,14 +384,6 @@ export default class Backgrounds {
}
// Don't transition between identical backgrounds. This
// prevents unwanted flicker.
let previousBackgroundHash = this.previousBackground ? this.previousBackground.getAttribute( 'data-background-hash' ) : null;
let currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== this.previousBackground ) {
this.element.classList.add( 'no-transition' );
}
this.previousBackground = currentBackground;
}
@@ -368,7 +397,7 @@ export default class Backgrounds {
// Allow the first background to apply without transition
setTimeout( () => {
this.element.classList.remove( 'no-transition' );
}, 1 );
}, 10 );
}

View File

@@ -1,4 +1,4 @@
import { queryAll } from '../utils/util.js'
import { queryAll, enterFullscreen } from '../utils/util.js'
import { isAndroid } from '../utils/device.js'
/**
@@ -12,6 +12,7 @@ import { isAndroid } from '../utils/device.js'
* - .navigate-left
* - .navigate-next
* - .navigate-prev
* - .enter-fullscreen
*/
export default class Controls {
@@ -25,6 +26,7 @@ export default class Controls {
this.onNavigateDownClicked = this.onNavigateDownClicked.bind( this );
this.onNavigatePrevClicked = this.onNavigatePrevClicked.bind( this );
this.onNavigateNextClicked = this.onNavigateNextClicked.bind( this );
this.onEnterFullscreen = this.onEnterFullscreen.bind( this );
}
@@ -50,6 +52,7 @@ export default class Controls {
this.controlsDown = queryAll( revealElement, '.navigate-down' );
this.controlsPrev = queryAll( revealElement, '.navigate-prev' );
this.controlsNext = queryAll( revealElement, '.navigate-next' );
this.controlsFullscreen = queryAll( revealElement, '.enter-fullscreen' );
// The left, right and down arrows in the standard reveal.js controls
this.controlsRightArrow = this.element.querySelector( '.navigate-right' );
@@ -89,6 +92,7 @@ export default class Controls {
this.controlsDown.forEach( el => el.addEventListener( eventName, this.onNavigateDownClicked, false ) );
this.controlsPrev.forEach( el => el.addEventListener( eventName, this.onNavigatePrevClicked, false ) );
this.controlsNext.forEach( el => el.addEventListener( eventName, this.onNavigateNextClicked, false ) );
this.controlsFullscreen.forEach( el => el.addEventListener( eventName, this.onEnterFullscreen, false ) );
} );
}
@@ -102,6 +106,7 @@ export default class Controls {
this.controlsDown.forEach( el => el.removeEventListener( eventName, this.onNavigateDownClicked, false ) );
this.controlsPrev.forEach( el => el.removeEventListener( eventName, this.onNavigatePrevClicked, false ) );
this.controlsNext.forEach( el => el.removeEventListener( eventName, this.onNavigateNextClicked, false ) );
this.controlsFullscreen.forEach( el => el.removeEventListener( eventName, this.onEnterFullscreen, false ) );
} );
}
@@ -262,5 +267,13 @@ export default class Controls {
}
onEnterFullscreen( event ) {
const config = this.Reveal.getConfig();
const viewport = this.Reveal.getViewportElement();
enterFullscreen( config.embedded ? viewport : viewport.parentElement );
}
}

View File

@@ -257,6 +257,26 @@ export default class Fragments {
}
if( changedFragments.hidden.length ) {
this.Reveal.dispatchEvent({
type: 'fragmenthidden',
data: {
fragment: changedFragments.hidden[0],
fragments: changedFragments.hidden
}
});
}
if( changedFragments.shown.length ) {
this.Reveal.dispatchEvent({
type: 'fragmentshown',
data: {
fragment: changedFragments.shown[0],
fragments: changedFragments.shown
}
});
}
return changedFragments;
}
@@ -311,26 +331,6 @@ export default class Fragments {
let changedFragments = this.update( index, fragments );
if( changedFragments.hidden.length ) {
this.Reveal.dispatchEvent({
type: 'fragmenthidden',
data: {
fragment: changedFragments.hidden[0],
fragments: changedFragments.hidden
}
});
}
if( changedFragments.shown.length ) {
this.Reveal.dispatchEvent({
type: 'fragmentshown',
data: {
fragment: changedFragments.shown[0],
fragments: changedFragments.shown
}
});
}
this.Reveal.controls.update();
this.Reveal.progress.update();

View File

@@ -178,7 +178,7 @@ export default class Keyboard {
if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || unusedModifier ) return;
// While paused only allow resume keyboard events; 'b', 'v', '.'
let resumeKeyCodes = [66,86,190,191];
let resumeKeyCodes = [66,86,190,191,112];
let key;
// Custom key bindings for togglePause should be able to resume
@@ -271,7 +271,12 @@ export default class Keyboard {
this.Reveal.slide( 0 );
}
else if( !this.Reveal.overview.isActive() && useLinearMode ) {
this.Reveal.prev({skipFragments: event.altKey});
if( config.rtl ) {
this.Reveal.next({skipFragments: event.altKey});
}
else {
this.Reveal.prev({skipFragments: event.altKey});
}
}
else {
this.Reveal.left({skipFragments: event.altKey});
@@ -283,7 +288,12 @@ export default class Keyboard {
this.Reveal.slide( this.Reveal.getHorizontalSlides().length - 1 );
}
else if( !this.Reveal.overview.isActive() && useLinearMode ) {
this.Reveal.next({skipFragments: event.altKey});
if( config.rtl ) {
this.Reveal.prev({skipFragments: event.altKey});
}
else {
this.Reveal.next({skipFragments: event.altKey});
}
}
else {
this.Reveal.right({skipFragments: event.altKey});
@@ -357,6 +367,10 @@ export default class Keyboard {
else if( keyCode === 191 && event.shiftKey ) {
this.Reveal.toggleHelp();
}
// F1
else if( keyCode === 112 ) {
this.Reveal.toggleHelp();
}
else {
triggered = false;
}
@@ -383,4 +397,4 @@ export default class Keyboard {
}
}
}

View File

@@ -1,4 +1,4 @@
import { HORIZONTAL_SLIDES_SELECTOR } from '../utils/constants.js'
import { HORIZONTAL_SLIDES_SELECTOR, HORIZONTAL_BACKGROUNDS_SELECTOR } from '../utils/constants.js'
import { queryAll } from '../utils/util.js'
const HIDE_SCROLLBAR_TIMEOUT = 500;
@@ -40,6 +40,7 @@ export default class ScrollView {
this.slideHTMLBeforeActivation = this.Reveal.getSlidesElement().innerHTML;
const horizontalSlides = queryAll( this.Reveal.getRevealElement(), HORIZONTAL_SLIDES_SELECTOR );
const horizontalBackgrounds = queryAll( this.Reveal.getRevealElement(), HORIZONTAL_BACKGROUNDS_SELECTOR );
this.viewportElement.classList.add( 'loading-scroll-mode', 'reveal-scroll' );
@@ -57,7 +58,7 @@ export default class ScrollView {
// Creates a new page element and appends the given slide/bg
// to it.
const createPageElement = ( slide, h, v ) => {
const createPageElement = ( slide, h, v, isVertical ) => {
let contentContainer;
@@ -76,8 +77,20 @@ export default class ScrollView {
page.className = 'scroll-page';
pageElements.push( page );
// Copy the presentation-wide background to each page
if( presentationBackground ) {
// This transfers over the background of the vertical stack containing
// the slide if it exists. Otherwise, it uses the presentation-wide
// background.
if( isVertical && horizontalBackgrounds.length > h ) {
const slideBackground = horizontalBackgrounds[h];
const pageBackground = window.getComputedStyle( slideBackground );
if( pageBackground && pageBackground.background ) {
page.style.background = pageBackground.background;
}
else if( presentationBackground ) {
page.style.background = presentationBackground;
}
} else if( presentationBackground ) {
page.style.background = presentationBackground;
}
@@ -110,7 +123,7 @@ export default class ScrollView {
if( this.Reveal.isVerticalStack( horizontalSlide ) ) {
horizontalSlide.querySelectorAll( 'section' ).forEach( ( verticalSlide, v ) => {
createPageElement( verticalSlide, h, v );
createPageElement( verticalSlide, h, v, true );
});
}
else {
@@ -277,7 +290,7 @@ export default class ScrollView {
const pageHeight = useCompactLayout ? compactHeight : viewportHeight;
// The height that needs to be scrolled between scroll triggers
const scrollTriggerHeight = useCompactLayout ? compactHeight : viewportHeight;
this.scrollTriggerHeight = useCompactLayout ? compactHeight : viewportHeight;
this.viewportElement.style.setProperty( '--page-height', pageHeight + 'px' );
this.viewportElement.style.scrollSnapType = typeof config.scrollSnap === 'string' ? `y ${config.scrollSnap}` : '';
@@ -333,12 +346,12 @@ export default class ScrollView {
for( let i = 0; i < totalScrollTriggerCount + 1; i++ ) {
const triggerStick = document.createElement( 'div' );
triggerStick.className = 'scroll-snap-point';
triggerStick.style.height = scrollTriggerHeight + 'px';
triggerStick.style.height = this.scrollTriggerHeight + 'px';
triggerStick.style.scrollSnapAlign = useCompactLayout ? 'center' : 'start';
page.pageElement.appendChild( triggerStick );
if( i === 0 ) {
triggerStick.style.marginTop = -scrollTriggerHeight + 'px';
triggerStick.style.marginTop = -this.scrollTriggerHeight + 'px';
}
}
@@ -355,7 +368,7 @@ export default class ScrollView {
}
// Add scroll padding based on how many scroll triggers we have
page.scrollPadding = scrollTriggerHeight * totalScrollTriggerCount;
page.scrollPadding = this.scrollTriggerHeight * totalScrollTriggerCount;
// The total height including scrollable space
page.totalHeight = page.pageHeight + page.scrollPadding;
@@ -699,6 +712,24 @@ export default class ScrollView {
}
/**
* Scroll to the previous page.
*/
prev() {
this.viewportElement.scrollTop -= this.scrollTriggerHeight;
}
/**
* Scroll to the next page.
*/
next() {
this.viewportElement.scrollTop += this.scrollTriggerHeight;
}
/**
* Scrolls the given slide element into view.
*

View File

@@ -375,8 +375,11 @@ export default class SlideContent {
isVisible = !!closest( event.target, '.present' );
if( isAttachedToDOM && isVisible ) {
event.target.currentTime = 0;
event.target.play();
// Don't restart if media is already playing
if( event.target.paused || event.target.ended ) {
event.target.currentTime = 0;
event.target.play();
}
}
event.target.removeEventListener( 'loadeddata', this.startEmbeddedMedia );

View File

@@ -84,7 +84,7 @@ export default class Touch {
isSwipePrevented( target ) {
// Prevent accidental swipes when scrubbing timelines
if( matches( target, 'video, audio' ) ) return true;
if( matches( target, 'video[controls], audio[controls]' ) ) return true;
while( target && typeof target.hasAttribute === 'function' ) {
if( target.hasAttribute( 'data-prevent-swipe' ) ) return true;
@@ -103,6 +103,8 @@ export default class Touch {
*/
onTouchStart( event ) {
this.touchCaptured = false;
if( this.isSwipePrevented( event.target ) ) return true;
this.touchStartX = event.touches[0].clientX;

View File

@@ -28,7 +28,7 @@ import {
} from './utils/constants.js'
// The reveal.js version
export const VERSION = '5.0.1';
export const VERSION = '5.0.5';
/**
* reveal.js
@@ -51,6 +51,9 @@ export default function( revealElement, options ) {
// Configuration defaults, can be overridden at initialization time
let config = {},
// Flags if initialize() has been invoked for this reveal instance
initialized = false,
// Flags if reveal.js is loaded (has dispatched the 'ready' event)
ready = false,
@@ -127,6 +130,8 @@ export default function( revealElement, options ) {
if( !revealElement ) throw 'Unable to find presentation root (<div class="reveal">).';
initialized = true;
// Cache references to key DOM elements
dom.wrapper = revealElement;
dom.slides = revealElement.querySelector( '.slides' );
@@ -604,6 +609,10 @@ export default function( revealElement, options ) {
*/
function destroy() {
// There's nothing to destroy if this instance hasn't been
// initialized yet
if( initialized === false ) return;
removeEventListeners();
cancelAutoSlide();
disablePreviewLinks();
@@ -1016,20 +1025,10 @@ export default function( revealElement, options ) {
}
});
}
// Responsively turn on the scroll mode if there is an activation
// width configured. Ignore if we're configured to always be in
// scroll mode.
if( typeof config.scrollActivationWidth === 'number' && config.view !== 'scroll' ) {
if( size.presentationWidth > 0 && size.presentationWidth <= config.scrollActivationWidth ) {
if( !scrollView.isActive() ) scrollView.activate();
}
else {
if( scrollView.isActive() ) scrollView.deactivate();
}
}
}
checkResponsiveScrollView();
dom.viewport.style.setProperty( '--slide-scale', scale );
dom.viewport.style.setProperty( '--viewport-width', viewportWidth + 'px' );
dom.viewport.style.setProperty( '--viewport-height', viewportHeight + 'px' );
@@ -1081,6 +1080,40 @@ export default function( revealElement, options ) {
}
/**
* Responsively activates the scroll mode when we reach the configured
* activation width.
*/
function checkResponsiveScrollView() {
// Only proceed if...
// 1. The DOM is ready
// 2. Layouts aren't disabled via config
// 3. We're not currently printing
// 4. There is a scrollActivationWidth set
// 5. The deck isn't configured to always use the scroll view
if(
dom.wrapper &&
!config.disableLayout &&
!printView.isActive() &&
typeof config.scrollActivationWidth === 'number' &&
config.view !== 'scroll'
) {
const size = getComputedSlideSize();
if( size.presentationWidth > 0 && size.presentationWidth <= config.scrollActivationWidth ) {
if( !scrollView.isActive() ) {
backgrounds.create();
scrollView.activate()
};
}
else {
if( scrollView.isActive() ) scrollView.deactivate();
}
}
}
/**
* Calculates the computed pixel size of our slides. These
* values are based on the width and height configuration
@@ -1356,6 +1389,8 @@ export default function( revealElement, options ) {
*/
function slide( h, v, f, origin ) {
if( Reveal.isReady() === false ) return;
// Dispatch an event before the slide
const slidechange = dispatchEvent({
type: 'beforeslidechange',
@@ -1422,6 +1457,9 @@ export default function( revealElement, options ) {
let currentHorizontalSlide = horizontalSlides[ indexh ],
currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );
// Indicate when we're on a vertical slide
revealElement.classList.toggle( 'is-vertical-slide', currentVerticalSlides.length > 1 );
// Store references to the previous and current slides
currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
@@ -1617,6 +1655,8 @@ export default function( revealElement, options ) {
*/
function sync() {
if( Reveal.isReady() === false ) return;
// Subscribe to input
removeEventListeners();
addEventListeners();
@@ -2475,6 +2515,9 @@ export default function( revealElement, options ) {
navigationHistory.hasNavigatedHorizontally = true;
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.prev();
// Reverse for RTL
if( config.rtl ) {
if( ( overview.isActive() || skipFragments || fragments.next() === false ) && availableRoutes().left ) {
@@ -2492,6 +2535,9 @@ export default function( revealElement, options ) {
navigationHistory.hasNavigatedHorizontally = true;
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.next();
// Reverse for RTL
if( config.rtl ) {
if( ( overview.isActive() || skipFragments || fragments.prev() === false ) && availableRoutes().right ) {
@@ -2507,6 +2553,9 @@ export default function( revealElement, options ) {
function navigateUp({skipFragments=false}={}) {
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.prev();
// Prioritize hiding fragments
if( ( overview.isActive() || skipFragments || fragments.prev() === false ) && availableRoutes().up ) {
slide( indexh, indexv - 1 );
@@ -2518,6 +2567,9 @@ export default function( revealElement, options ) {
navigationHistory.hasNavigatedVertically = true;
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.next();
// Prioritize revealing fragments
if( ( overview.isActive() || skipFragments || fragments.next() === false ) && availableRoutes().down ) {
slide( indexh, indexv + 1 );
@@ -2533,6 +2585,9 @@ export default function( revealElement, options ) {
*/
function navigatePrev({skipFragments=false}={}) {
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.prev();
// Prioritize revealing fragments
if( skipFragments || fragments.prev() === false ) {
if( availableRoutes().up ) {
@@ -2556,6 +2611,9 @@ export default function( revealElement, options ) {
let h = indexh - 1;
slide( h, v );
}
else if( config.rtl ) {
navigateRight({skipFragments});
}
else {
navigateLeft({skipFragments});
}
@@ -2572,6 +2630,9 @@ export default function( revealElement, options ) {
navigationHistory.hasNavigatedHorizontally = true;
navigationHistory.hasNavigatedVertically = true;
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.next();
// Prioritize revealing fragments
if( skipFragments || fragments.next() === false ) {
@@ -2700,7 +2761,6 @@ export default function( revealElement, options ) {
function onWindowResize( event ) {
layout();
}
/**
@@ -2879,7 +2939,7 @@ export default function( revealElement, options ) {
loadSlide: slideContent.load.bind( slideContent ),
unloadSlide: slideContent.unload.bind( slideContent ),
// Media playback
// Start/stop all media inside of the current slide
startEmbeddedContent: () => slideContent.startEmbeddedContent( currentSlide ),
stopEmbeddedContent: () => slideContent.stopEmbeddedContent( currentSlide, { unloadIframes: false } ),

View File

@@ -2,6 +2,7 @@
export const SLIDES_SELECTOR = '.slides section';
export const HORIZONTAL_SLIDES_SELECTOR = '.slides>section';
export const VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section';
export const HORIZONTAL_BACKGROUNDS_SELECTOR = '.backgrounds>.slide-background';
// Methods that may not be invoked via the postMessage API
export const POST_MESSAGE_METHOD_BLACKLIST = /registerPlugin|registerKeyboardShortcut|addKeyBinding|addEventListener|showPreview/;

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "reveal.js",
"version": "5.0.2",
"version": "5.0.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "reveal.js",
"version": "5.0.2",
"version": "5.0.5",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.23.2",

View File

@@ -1,6 +1,6 @@
{
"name": "reveal.js",
"version": "5.0.3",
"version": "5.0.5",
"description": "The HTML Presentation Framework",
"homepage": "https://revealjs.com",
"subdomain": "revealjs",
@@ -10,7 +10,8 @@
"scripts": {
"test": "gulp test",
"start": "gulp serve",
"build": "gulp build"
"build": "gulp build",
"dev:react": "npm --prefix ./react run dev"
},
"author": {
"name": "Hakim El Hattab",

View File

@@ -21,7 +21,7 @@ export const MathJax3 = () => {
ready: () => {
MathJax.startup.defaultReady();
MathJax.startup.promise.then(() => {
Reveal.layout();
deck.layout();
});
}
}
@@ -66,7 +66,7 @@ export const MathJax3 = () => {
loadScript( url, function() {
// Reprocess equations in slides when they turn visible
Reveal.addEventListener( 'slidechanged', function( event ) {
deck.addEventListener( 'slidechanged', function( event ) {
MathJax.typeset();
} );
} );

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -180,14 +180,16 @@ const Plugin = () => {
// (added 12/5/22 as a XSS safeguard)
if( isSameOriginEvent( event ) ) {
let data = JSON.parse( event.data );
if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) {
clearInterval( connectInterval );
onConnected();
}
else if( data && data.namespace === 'reveal-notes' && data.type === 'call' ) {
callRevealApi( data.methodName, data.arguments, data.callId );
}
try {
let data = JSON.parse( event.data );
if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) {
clearInterval( connectInterval );
onConnected();
}
else if( data && data.namespace === 'reveal-notes' && data.type === 'call' ) {
callRevealApi( data.methodName, data.arguments, data.callId );
}
} catch (e) {}
}

View File

@@ -383,6 +383,13 @@
window.addEventListener( 'message', function( event ) {
// Validate the origin of all messages to avoid parsing messages
// that aren't meant for us. Ignore when running off file:// so
// that the speaker view continues to work without a web server.
if( window.location.origin !== event.origin && window.location.origin !== 'file://' ) {
return
}
clearTimeout( connectionTimeout );
connectionStatus.style.display = 'none';

18
react/.eslintrc.cjs Normal file
View File

@@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

24
react/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

12
react/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>reveal.js/react</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/demo.tsx"></script>
</body>
</html>

2737
react/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
react/package.json Normal file
View File

@@ -0,0 +1,28 @@
{
"name": "reveal-js-react",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"typescript": "^5.2.2",
"vite": "^5.2.0"
}
}

44
react/src/Reveal.tsx Normal file
View File

@@ -0,0 +1,44 @@
import '../../dist/reveal.css'
import '../../dist/theme/black.css'
import _Reveal from '../../dist/reveal.esm.js';
import { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
const Reveal = forwardRef((props: {children: React.ReactNode, [key: string]: any}, ref) => {
const {children, ...revealProps} = props;
const deckDivRef = useRef<HTMLDivElement>(null);
const deckRef = useRef<any>(null);
useImperativeHandle(ref, () => deckRef.current);
useEffect(() => {
// Prevents double initialization in strict mode
if (deckRef.current) return;
deckRef.current = new _Reveal(deckDivRef.current!, revealProps);
deckRef.current.initialize();
return () => {
try {
if (deckRef.current) {
deckRef.current.destroy();
deckRef.current = null;
}
} catch (e) {
console.warn("Reveal.js destroy call failed.");
}
};
}, []);
return (
// The presentation is sized based on the width and height of
// our parent element. Make sure the parent is not 0-height.
<div className="reveal" ref={deckDivRef}>
<div className="slides">
{children}
</div>
</div>
);
});
export default Reveal;

58
react/src/demo.tsx Normal file
View File

@@ -0,0 +1,58 @@
import React, { ReactElement, useEffect, useState } from 'react'
import ReactDOM from 'react-dom/client'
import Reveal from './Reveal.tsx'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Demo2 />
</React.StrictMode>,
)
function Demo1() {
return (
<div style={{width: 600, height: 600}}>
<Reveal embedded={true}>
<section>slide 1</section>
<section>slide 2</section>
</Reveal>
</div>
);
}
function Demo2() {
const revealApiRef = React.useRef<any>(null);
const [slides, setSlides] = useState<string[]>([
'slide 1',
'slide 2',
]);
useEffect(() => {
if (revealApiRef.current) {
revealApiRef.current.sync();
// This should not be necessary, the sync() method needs to
// be revised to call updateSlides() internally.
revealApiRef.current.slide();
}
}, [slides]);
return (
<div style={{width: 600, height: 600}}>
<Reveal embedded={true} ref={revealApiRef}>
{slides.map((slide, i) => (
<section key={i}>
{slide}
</section>
))}
</Reveal>
<button onClick={() => {
setSlides([...slides, `slide ${slides.length + 1}`]);
}}>Append slide</button>
<button onClick={() => {
revealApiRef.current.next();
}}>Next slide</button>
</div>
);
}

1
react/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

25
react/tsconfig.json Normal file
View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

11
react/tsconfig.node.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

7
react/vite.config.ts Normal file
View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})

View File

@@ -97,14 +97,61 @@
return new Promise( resolve => {
let callback = ( event ) => {
Reveal.off( 'slidechanged', callback );
assert.ok( true, 'slidechanged event fired' );
assert.ok( event.currentSlide.classList.contains( 'present' ), 'slidechanged provides reference to currentSlide' );
resolve();
}
Reveal.off( 'slidechanged', callback );
assert.ok( true, 'slidechanged event fired' );
assert.ok( event.currentSlide.classList.contains( 'present' ), 'slidechanged provides reference to currentSlide' );
resolve();
}
Reveal.on( 'slidechanged', callback );
Reveal.getViewportElement().scrollTop = getViewportHeight() * 2;
Reveal.on( 'slidechanged', callback );
Reveal.getViewportElement().scrollTop = getViewportHeight() * 2;
});
});
QUnit.test( 'Fires fragmentshown event when scrolling', assert => {
assert.timeout( 200 );
assert.expect( 2 );
const slides = document.querySelectorAll( '.reveal .slides section' );
return new Promise( resolve => {
let callback = ( event ) => {
Reveal.off( 'fragmentshown', callback );
assert.ok( true, 'fragmentshown event fired' );
assert.ok( event.fragments.length > 0, 'fragmentshown provides reference to fragment nodes' );
resolve();
}
Reveal.on( 'fragmentshown', callback );
Reveal.getViewportElement().scrollTop = 0;
Reveal.next();
Reveal.next();
Reveal.getViewportElement().scrollTop += getViewportHeight();
});
});
QUnit.test( 'Fires fragmenthidden event when scrolling', assert => {
assert.timeout( 200 );
assert.expect( 2 );
const slides = document.querySelectorAll( '.reveal .slides section' );
return new Promise( resolve => {
let callback = ( event ) => {
Reveal.off( 'fragmenthidden', callback );
assert.ok( true, 'fragmenthidden event fired' );
assert.ok( event.fragments.length > 0, 'fragmenthidden provides reference to fragment nodes' );
resolve();
}
Reveal.on( 'fragmenthidden', callback );
Reveal.getViewportElement().scrollTop = 0;
Reveal.next();
Reveal.next();
Reveal.next();
Reveal.getViewportElement().scrollTop -= getViewportHeight();
});
});