hakimel/reveal.js

View on GitHub
js/controllers/jumptoslide.js

Summary

Maintainability
A
2 hrs
Test Coverage
import {
    SLIDE_NUMBER_FORMAT_CURRENT,
    SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL
} from "../utils/constants";

/**
 * Makes it possible to jump to a slide by entering its
 * slide number or id.
 */
export default class JumpToSlide {

    constructor( Reveal ) {

        this.Reveal = Reveal;

        this.onInput = this.onInput.bind( this );
        this.onBlur = this.onBlur.bind( this );
        this.onKeyDown = this.onKeyDown.bind( this );

    }

    render() {

        this.element = document.createElement( 'div' );
        this.element.className = 'jump-to-slide';

    this.jumpInput = document.createElement( 'input' );
    this.jumpInput.type = 'text';
    this.jumpInput.className = 'jump-to-slide-input';
    this.jumpInput.placeholder = 'Jump to slide';
        this.jumpInput.addEventListener( 'input', this.onInput );
        this.jumpInput.addEventListener( 'keydown', this.onKeyDown );
        this.jumpInput.addEventListener( 'blur', this.onBlur );

    this.element.appendChild( this.jumpInput );

    }

    show() {

        this.indicesOnShow = this.Reveal.getIndices();

        this.Reveal.getRevealElement().appendChild( this.element );
        this.jumpInput.focus();

    }

    hide() {

        if( this.isVisible() ) {
            this.element.remove();
            this.jumpInput.value = '';

            clearTimeout( this.jumpTimeout );
            delete this.jumpTimeout;
        }

    }

    isVisible() {

        return !!this.element.parentNode;

    }

    /**
     * Parses the current input and jumps to the given slide.
     */
    jump() {

        clearTimeout( this.jumpTimeout );
        delete this.jumpTimeout;

        let query = this.jumpInput.value.trim( '' );
        let indices;

        // When slide numbers are formatted to be a single linear mumber
        // (instead of showing a separate horizontal/vertical index) we
        // use the same format for slide jumps
        if( /^\d+$/.test( query ) ) {
            const slideNumberFormat = this.Reveal.getConfig().slideNumber;
            if( slideNumberFormat === SLIDE_NUMBER_FORMAT_CURRENT || slideNumberFormat === SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL ) {
                const slide = this.Reveal.getSlides()[ parseInt( query, 10 ) - 1 ];
                if( slide ) {
                    indices = this.Reveal.getIndices( slide );
                }
            }
        }

        if( !indices ) {
            // If the query uses "horizontal.vertical" format, convert to
            // "horizontal/vertical" so that our URL parser can understand
            if( /^\d+\.\d+$/.test( query ) ) {
                query = query.replace( '.', '/' );
            }

            indices = this.Reveal.location.getIndicesFromHash( query, { oneBasedIndex: true } );
        }

        // Still no valid index? Fall back on a text search
        if( !indices && /\S+/i.test( query ) && query.length > 1 ) {
            indices = this.search( query );
        }

        if( indices && query !== '' ) {
            this.Reveal.slide( indices.h, indices.v, indices.f );
            return true;
        }
        else {
            this.Reveal.slide( this.indicesOnShow.h, this.indicesOnShow.v, this.indicesOnShow.f );
            return false;
        }

    }

    jumpAfter( delay ) {

        clearTimeout( this.jumpTimeout );
        this.jumpTimeout = setTimeout( () => this.jump(), delay );

    }

    /**
     * A lofi search that looks for the given query in all
     * of our slides and returns the first match.
     */
    search( query ) {

        const regex = new RegExp( '\\b' + query.trim() + '\\b', 'i' );

        const slide = this.Reveal.getSlides().find( ( slide ) => {
            return regex.test( slide.innerText );
        } );

        if( slide ) {
            return this.Reveal.getIndices( slide );
        }
        else {
            return null;
        }

    }

    /**
     * Reverts back to the slide we were on when jump to slide was
     * invoked.
     */
    cancel() {

        this.Reveal.slide( this.indicesOnShow.h, this.indicesOnShow.v, this.indicesOnShow.f );
        this.hide();

    }

    confirm() {

        this.jump();
        this.hide();

    }

    destroy() {

        this.jumpInput.removeEventListener( 'input', this.onInput );
        this.jumpInput.removeEventListener( 'keydown', this.onKeyDown );
        this.jumpInput.removeEventListener( 'blur', this.onBlur );

        this.element.remove();

    }

    onKeyDown( event ) {

        if( event.keyCode === 13 ) {
            this.confirm();
        }
        else if( event.keyCode === 27 ) {
            this.cancel();

            event.stopImmediatePropagation();
        }

    }

    onInput( event ) {

        this.jumpAfter( 200 );

    }

    onBlur() {

        setTimeout( () => this.hide(), 1 );

    }

}