Commit 8a437530 authored by Hakim El Hattab's avatar Hakim El Hattab
Browse files

move fragments to separate controller

parent bdedc56d
This diff is collapsed.
import { extend, toArray } from '../utils/util.js'
/**
*
*/
export default class Fragments {
constructor( Reveal ) {
this.Reveal = Reveal;
}
/**
* Shows all fragments in the presentation. Used when
* fragments are disabled presentation-wide.
*/
showAll() {
toArray( this.Reveal.getSlidesElement().querySelectorAll( '.fragment' ) ).forEach( element => {
element.classList.add( 'visible' );
element.classList.remove( 'current-fragment' );
} );
}
/**
* Returns an object describing the available fragment
* directions.
*
* @return {{prev: boolean, next: boolean}}
*/
availableRoutes() {
let currentSlide = this.Reveal.getCurrentSlide();
if( currentSlide && this.Reveal.getConfig().fragments ) {
let fragments = currentSlide.querySelectorAll( '.fragment' );
let hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.visible)' );
return {
prev: fragments.length - hiddenFragments.length > 0,
next: !!hiddenFragments.length
};
}
else {
return { prev: false, next: false };
}
}
/**
* Return a sorted fragments list, ordered by an increasing
* "data-fragment-index" attribute.
*
* Fragments will be revealed in the order that they are returned by
* this function, so you can use the index attributes to control the
* order of fragment appearance.
*
* To maintain a sensible default fragment order, fragments are presumed
* to be passed in document order. This function adds a "fragment-index"
* attribute to each node if such an attribute is not already present,
* and sets that attribute to an integer value which is the position of
* the fragment within the fragments list.
*
* @param {object[]|*} fragments
* @param {boolean} grouped If true the returned array will contain
* nested arrays for all fragments with the same index
* @return {object[]} sorted Sorted array of fragments
*/
sort( fragments, grouped = false ) {
fragments = toArray( fragments );
let ordered = [],
unordered = [],
sorted = [];
// Group ordered and unordered elements
fragments.forEach( fragment => {
if( fragment.hasAttribute( 'data-fragment-index' ) ) {
let index = parseInt( fragment.getAttribute( 'data-fragment-index' ), 10 );
if( !ordered[index] ) {
ordered[index] = [];
}
ordered[index].push( fragment );
}
else {
unordered.push( [ fragment ] );
}
} );
// Append fragments without explicit indices in their
// DOM order
ordered = ordered.concat( unordered );
// Manually count the index up per group to ensure there
// are no gaps
let index = 0;
// Push all fragments in their sorted order to an array,
// this flattens the groups
ordered.forEach( group => {
group.forEach( fragment => {
sorted.push( fragment );
fragment.setAttribute( 'data-fragment-index', index );
} );
index ++;
} );
return grouped === true ? ordered : sorted;
}
/**
* Sorts and formats all of fragments in the
* presentation.
*/
sortAll() {
this.Reveal.getHorizontalSlides().forEach( horizontalSlide => {
let verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
verticalSlides.forEach( ( verticalSlide, y ) => {
this.sort( verticalSlide.querySelectorAll( '.fragment' ) );
}, this );
if( verticalSlides.length === 0 ) this.sort( horizontalSlide.querySelectorAll( '.fragment' ) );
} );
}
/**
* Refreshes the fragments on the current slide so that they
* have the appropriate classes (.visible + .current-fragment).
*
* @param {number} [index] The index of the current fragment
* @param {array} [fragments] Array containing all fragments
* in the current slide
*
* @return {{shown: array, hidden: array}}
*/
update( index, fragments ) {
let changedFragments = {
shown: [],
hidden: []
};
let currentSlide = this.Reveal.getCurrentSlide();
if( currentSlide && this.Reveal.getConfig().fragments ) {
fragments = fragments || this.sort( currentSlide.querySelectorAll( '.fragment' ) );
if( fragments.length ) {
let maxIndex = 0;
if( typeof index !== 'number' ) {
let currentFragment = this.sort( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();
if( currentFragment ) {
index = parseInt( currentFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
}
}
toArray( fragments ).forEach( ( el, i ) => {
if( el.hasAttribute( 'data-fragment-index' ) ) {
i = parseInt( el.getAttribute( 'data-fragment-index' ), 10 );
}
maxIndex = Math.max( maxIndex, i );
// Visible fragments
if( i <= index ) {
if( !el.classList.contains( 'visible' ) ) changedFragments.shown.push( el );
el.classList.add( 'visible' );
el.classList.remove( 'current-fragment' );
// Announce the fragments one by one to the Screen Reader
this.Reveal.announceStatus( this.Reveal.getStatusText( el ) );
if( i === index ) {
el.classList.add( 'current-fragment' );
this.Reveal.slideContent.startEmbeddedContent( el );
}
}
// Hidden fragments
else {
if( el.classList.contains( 'visible' ) ) changedFragments.hidden.push( el );
el.classList.remove( 'visible' );
el.classList.remove( 'current-fragment' );
}
} );
// Write the current fragment index to the slide <section>.
// This can be used by end users to apply styles based on
// the current fragment index.
index = typeof index === 'number' ? index : -1;
index = Math.max( Math.min( index, maxIndex ), -1 );
currentSlide.setAttribute( 'data-fragment', index );
}
}
return changedFragments;
}
/**
* Navigate to the specified slide fragment.
*
* @param {?number} index The index of the fragment that
* should be shown, -1 means all are invisible
* @param {number} offset Integer offset to apply to the
* fragment index
*
* @return {boolean} true if a change was made in any
* fragments visibility as part of this call
*/
goto( index, offset = 0 ) {
let currentSlide = this.Reveal.getCurrentSlide();
if( currentSlide && this.Reveal.getConfig().fragments ) {
let fragments = this.sort( currentSlide.querySelectorAll( '.fragment' ) );
if( fragments.length ) {
// If no index is specified, find the current
if( typeof index !== 'number' ) {
let lastVisibleFragment = this.sort( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();
if( lastVisibleFragment ) {
index = parseInt( lastVisibleFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
}
else {
index = -1;
}
}
// Apply the offset if there is one
index += offset;
let changedFragments = this.update( index, fragments );
if( changedFragments.hidden.length ) {
this.Reveal.dispatchEvent( 'fragmenthidden', { fragment: changedFragments.hidden[0], fragments: changedFragments.hidden } );
}
if( changedFragments.shown.length ) {
this.Reveal.dispatchEvent( 'fragmentshown', { fragment: changedFragments.shown[0], fragments: changedFragments.shown } );
}
this.Reveal.updateControls();
this.Reveal.updateProgress();
if( this.Reveal.getConfig().fragmentInURL ) {
this.Reveal.writeURL();
}
return !!( changedFragments.shown.length || changedFragments.hidden.length );
}
}
return false;
}
/**
* Navigate to the next slide fragment.
*
* @return {boolean} true if there was a next fragment,
* false otherwise
*/
next() {
return this.goto( null, 1 );
}
/**
* Navigate to the previous slide fragment.
*
* @return {boolean} true if there was a previous fragment,
* false otherwise
*/
prev() {
return this.goto( null, -1 );
}
}
\ No newline at end of file
import { HORIZONTAL_SLIDES_SELECTOR, VERTICAL_SLIDES_SELECTOR } from '../utils/constants.js'
import { extend, toArray, closestParent } from '../utils/util.js'
import { isMobile } from '../utils/device.js'
/**
* Handles loading, unloading and playback of slide
......@@ -13,6 +14,26 @@ export default class SlideContent {
}
/**
* Should the given element be preloaded?
* Decides based on local element attributes and global config.
*
* @param {HTMLElement} element
*/
shouldPreload( element ) {
// Prefer an explicit global preload setting
let preload = this.Reveal.getConfig().preloadIframes;
// If no global setting is available, fall back on the element's
// own preload setting
if( typeof preload !== 'boolean' ) {
preload = element.hasAttribute( 'data-preload' );
}
return preload;
}
/**
* Called when the given slide is within the configured view
* distance. Shows the slide element and loads any content
......@@ -27,7 +48,7 @@ export default class SlideContent {
// Media elements with data-src attributes
toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ) ).forEach( element => {
if( element.tagName !== 'IFRAME' || shouldPreload( element ) ) {
if( element.tagName !== 'IFRAME' || this.shouldPreload( element ) ) {
element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
element.setAttribute( 'data-lazy-loaded', '' );
element.removeAttribute( 'data-src' );
......@@ -89,7 +110,7 @@ export default class SlideContent {
// Inline video playback works (at least in Mobile Safari) as
// long as the video is muted and the `playsinline` attribute is
// present
if( isMobileDevice ) {
if( isMobile ) {
video.muted = true;
video.autoplay = true;
video.setAttribute( 'playsinline', '' );
......@@ -126,7 +147,7 @@ export default class SlideContent {
if( backgroundIframeElement ) {
// Check if this iframe is eligible to be preloaded
if( shouldPreload( background ) && !/autoplay=(1|true|yes)/gi.test( backgroundIframe ) ) {
if( this.shouldPreload( background ) && !/autoplay=(1|true|yes)/gi.test( backgroundIframe ) ) {
if( backgroundIframeElement.getAttribute( 'src' ) !== backgroundIframe ) {
backgroundIframeElement.setAttribute( 'src', backgroundIframe );
}
......@@ -222,7 +243,7 @@ export default class SlideContent {
}
// Prefer an explicit global autoplay setting
let autoplay = config.autoPlayMedia;
let autoplay = this.Reveal.getConfig().autoPlayMedia;
// If no global setting is available, fall back on the element's
// own autoplay setting
......@@ -238,7 +259,7 @@ export default class SlideContent {
}
// Mobile devices never fire a loaded event so instead
// of waiting, we initiate playback
else if( isMobileDevice ) {
else if( isMobile ) {
let promise = el.play();
// If autoplay does not work, ensure that the controls are visible so
......@@ -327,7 +348,7 @@ export default class SlideContent {
if( isAttachedToDOM && isVisible ) {
// Prefer an explicit global autoplay setting
let autoplay = config.autoPlayMedia;
let autoplay = this.Reveal.getConfig().autoPlayMedia;
// If no global setting is available, fall back on the element's
// own autoplay setting
......
This diff is collapsed.
const UA = navigator.userAgent;
const testElement = document.createElement( 'div' );
export const isMobile = /(iphone|ipod|ipad|android)/gi.test( UA ) ||
( navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 ); // iPadOS
export const isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA );
export const isAndroid = /android/gi.test( UA );
// Flags if we should use zoom instead of transform to scale
// up slides. Zoom produces crisper results but has a lot of
// xbrowser quirks so we only use it in whitelsited browsers.
export const supportsZoom = 'zoom' in testElement.style && !isMobile &&
( isChrome || /Version\/[\d\.]+.*Safari/.test( UA ) );
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment