613 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			613 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
export var CardPlugin = CardPlugin || {}
 | 
						|
 | 
						|
export class CardPluginBase {
 | 
						|
    apply(context) {
 | 
						|
        this.context = context
 | 
						|
        if (this.verify(context)) {
 | 
						|
            this.append(context)
 | 
						|
            console.log('Plugin ' + this.name + ' was verified successfully.')
 | 
						|
            return true
 | 
						|
        } else console.error('Could not verify module ' + this.name + '.')
 | 
						|
        return false
 | 
						|
    }
 | 
						|
 | 
						|
    get name() {
 | 
						|
        return this.constructor.name
 | 
						|
    }
 | 
						|
 | 
						|
    verify(context) {
 | 
						|
        let funcs = this._getVerificationFunctions(context)
 | 
						|
        for (let func of funcs) {
 | 
						|
            if (!func()) return false
 | 
						|
        }
 | 
						|
        return true
 | 
						|
    }
 | 
						|
 | 
						|
    _verifyElementsExist(context, ...selectors) {
 | 
						|
        let missing = []
 | 
						|
 | 
						|
        for (let selector of selectors) {
 | 
						|
            let requiredElement = context.querySelector(selector)
 | 
						|
            if (requiredElement == null) {
 | 
						|
                missing.push(selector)
 | 
						|
            }
 | 
						|
        }
 | 
						|
        const valid = missing.length == 0
 | 
						|
        if (!valid) console.error('Elements were missing: ', missing.join(', '))
 | 
						|
        return valid
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     *  Appends the Plugin to the context.
 | 
						|
     *
 | 
						|
     * @memberof CardPlugin
 | 
						|
     */
 | 
						|
    append(context) {
 | 
						|
        console.error(
 | 
						|
            'Call of abstract method CardPlugin.prototype.append(context). Plugins need to overwrite the append method!'
 | 
						|
        )
 | 
						|
    }
 | 
						|
 | 
						|
    _getVerificationFunctions(context) {
 | 
						|
        return [this._verifyContext.bind(this, context), this._verifyRequirements.bind(this, context)]
 | 
						|
    }
 | 
						|
 | 
						|
    _verifyContext(context) {
 | 
						|
        if (!(context instanceof HTMLElement)) {
 | 
						|
            console.error('Context is not of type HTML Element.', context)
 | 
						|
            return false
 | 
						|
        } else return true
 | 
						|
    }
 | 
						|
 | 
						|
    _verifyRequirements(context) {
 | 
						|
        let requirements = this._collectAllRequirements()
 | 
						|
        let missing = []
 | 
						|
 | 
						|
        requirements.forEach(module => {
 | 
						|
            if (context.modules.indexOf(module.name) == -1) {
 | 
						|
                missing.push(module.name)
 | 
						|
            }
 | 
						|
        })
 | 
						|
 | 
						|
        const valid = missing.length == 0
 | 
						|
        if (!valid)
 | 
						|
            console.error(
 | 
						|
                "Could not apply module '" +
 | 
						|
                    this.name +
 | 
						|
                    "'. Following modules are required but were missing: " +
 | 
						|
                    missing.join(',')
 | 
						|
            )
 | 
						|
        else console.log('All requirements were met! Well done!')
 | 
						|
        return valid
 | 
						|
    }
 | 
						|
 | 
						|
    _collectAllRequirements() {
 | 
						|
        let requirements = []
 | 
						|
        let klass = this.__proto__
 | 
						|
        while (klass) {
 | 
						|
            if (klass.require != null) {
 | 
						|
                requirements = requirements.concat(klass.require)
 | 
						|
            }
 | 
						|
            klass = klass.__proto__
 | 
						|
        }
 | 
						|
        return requirements
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Called when the card is removed.
 | 
						|
     * Can be used to cleanup the plugin.
 | 
						|
     *
 | 
						|
     * @memberof CardPluginBase
 | 
						|
     */
 | 
						|
    remove() {}
 | 
						|
}
 | 
						|
 | 
						|
CardPlugin.LightBox = class LightBox extends CardPluginBase {
 | 
						|
    constructor(className, style = {}) {
 | 
						|
        super()
 | 
						|
        this.className = className
 | 
						|
        this.style = style
 | 
						|
    }
 | 
						|
 | 
						|
    append(context) {
 | 
						|
        let wrapper = document.createElement('div')
 | 
						|
        wrapper.className = this.className
 | 
						|
 | 
						|
        Object.assign(
 | 
						|
            wrapper.style,
 | 
						|
            {
 | 
						|
                zIndex: 1000,
 | 
						|
                // backgroundColor: "black",
 | 
						|
                top: 0,
 | 
						|
                left: 0,
 | 
						|
                width: '100%',
 | 
						|
                height: '100%'
 | 
						|
            },
 | 
						|
            this.style,
 | 
						|
            {
 | 
						|
                display: 'none',
 | 
						|
                position: 'absolute'
 | 
						|
            }
 | 
						|
        )
 | 
						|
 | 
						|
        context.appendChild(wrapper)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * The Enlargeable Overlay module allows the user to click on the thumbnail image,
 | 
						|
 * and the images gets enlarged inside the card.
 | 
						|
 *
 | 
						|
 * @class EnlargeableThumbnail
 | 
						|
 * @extends {CardPlugin}
 | 
						|
 */
 | 
						|
CardPlugin.EnlargeableThumbnail = class EnlargeableThumbnail extends CardPluginBase {
 | 
						|
    constructor(
 | 
						|
        wrapperSelector,
 | 
						|
        overlaySelector = null,
 | 
						|
        { zoomAnimationDuration = 0.4, fadeAnimationDuration = 0.4, interactionType = 'tap' } = {}
 | 
						|
    ) {
 | 
						|
        super()
 | 
						|
        this.wrapperSelector = wrapperSelector
 | 
						|
        this.overlaySelector = overlaySelector
 | 
						|
 | 
						|
        this.zoomAnimationDuration = zoomAnimationDuration
 | 
						|
        this.fadeAnimationDuration = fadeAnimationDuration
 | 
						|
        this.interactionType = interactionType
 | 
						|
    }
 | 
						|
    get require() {
 | 
						|
        return [CardPlugin.LightBox]
 | 
						|
    }
 | 
						|
 | 
						|
    _getVerificationFunctions(context) {
 | 
						|
        let arr = super._getVerificationFunctions(context)
 | 
						|
        let funcs = [this._verifyElementsExist.bind(this, context, this.wrapperSelector, this.overlaySelector)]
 | 
						|
        return arr.concat(funcs)
 | 
						|
    }
 | 
						|
 | 
						|
    append(context) {
 | 
						|
        let source = this._retrieveSource(context)
 | 
						|
        this.setupEnlargeableThumbnail(context, source)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get the preview image.
 | 
						|
     *
 | 
						|
     * It depends on the fact, that the thumbnail image is in the same directory
 | 
						|
     *
 | 
						|
     *
 | 
						|
     * @param {*} context
 | 
						|
     * @returns
 | 
						|
     * @memberof EnlargeableThumbnail
 | 
						|
     */
 | 
						|
    _retrieveSource(context) {
 | 
						|
        let img = context.querySelector(this.wrapperSelector + ' img')
 | 
						|
        let src = img.getAttribute('src')
 | 
						|
        let parts = src.split('/')
 | 
						|
        parts.pop()
 | 
						|
        parts.push(parts[parts.length - 1])
 | 
						|
        let imagePath = parts.join('/') + '.jpg'
 | 
						|
        return imagePath
 | 
						|
    }
 | 
						|
 | 
						|
    setupEnlargeableThumbnail(context, src) {
 | 
						|
        let wrapper = context.querySelector(this.wrapperSelector)
 | 
						|
        let overlay = context.querySelector(this.overlaySelector)
 | 
						|
 | 
						|
        let icon = document.createElement('div')
 | 
						|
        icon.className = 'button corner-button bottom-right icon zoom'
 | 
						|
        wrapper.appendChild(icon)
 | 
						|
 | 
						|
        Object.assign(wrapper.style, {
 | 
						|
            cursor: 'pointer'
 | 
						|
        })
 | 
						|
 | 
						|
        InteractionMapper.on(this.interactionType, wrapper, () => {
 | 
						|
            this.openThumbnailDetail(context, src)
 | 
						|
        })
 | 
						|
 | 
						|
        InteractionMapper.on(this.interactionType, overlay, () => {
 | 
						|
            this.closeThumnailDetail(context)
 | 
						|
        })
 | 
						|
    }
 | 
						|
 | 
						|
    openThumbnailDetail(context, src) {
 | 
						|
        let overlay = context.querySelector('.img-overlay')
 | 
						|
        overlay.innerHTML = ''
 | 
						|
        let source = context.querySelector(this.wrapperSelector)
 | 
						|
        let sourceStyle = window.getComputedStyle(source)
 | 
						|
        let imageWrapper = source.cloneNode(true)
 | 
						|
        let image = imageWrapper.querySelector('img')
 | 
						|
 | 
						|
        Object.assign(imageWrapper.style, {
 | 
						|
            maxWidth: 'none',
 | 
						|
            maxHeight: 'none'
 | 
						|
        })
 | 
						|
 | 
						|
        Object.assign(image.style, {
 | 
						|
            width: '100%',
 | 
						|
            height: '100%',
 | 
						|
            objectFit: 'cover'
 | 
						|
        })
 | 
						|
 | 
						|
        this._replaceIcon(imageWrapper)
 | 
						|
 | 
						|
        image.onload = () => {
 | 
						|
            let header = context.querySelector('header')
 | 
						|
            let headerStlye = window.getComputedStyle(header)
 | 
						|
 | 
						|
            /**
 | 
						|
             * First the maxFillRatio is considered.
 | 
						|
             * It describes how much the image is allowed to exceed the context element.
 | 
						|
             */
 | 
						|
            const maxFillRatio = 1.5
 | 
						|
 | 
						|
            /**
 | 
						|
             * The minor side should not exceed the height of the context window.
 | 
						|
             */
 | 
						|
            const maxMinorSize =
 | 
						|
                context.offsetHeight - 2 * parseInt(headerStlye.paddingTop) - 2 * parseInt(headerStlye.marginTop)
 | 
						|
 | 
						|
            const max = {
 | 
						|
                width: context.offsetWidth * maxFillRatio,
 | 
						|
                height: context.offsetHeight * maxFillRatio
 | 
						|
            }
 | 
						|
 | 
						|
            let majorSide
 | 
						|
            let minorSide
 | 
						|
            const _width = { name: 'width', axis: 'x' }
 | 
						|
            const _height = { name: 'height', axis: 'y' }
 | 
						|
            if (image.naturalHeight > image.naturalWidth) {
 | 
						|
                majorSide = _height
 | 
						|
                minorSide = _width
 | 
						|
            } else {
 | 
						|
                majorSide = _width
 | 
						|
                minorSide = _height
 | 
						|
            }
 | 
						|
 | 
						|
            function capitalize(string) {
 | 
						|
                return string.charAt(0).toUpperCase() + string.slice(1)
 | 
						|
            }
 | 
						|
            function getImageSize(side) {
 | 
						|
                return image['natural' + capitalize(side.name)]
 | 
						|
            }
 | 
						|
 | 
						|
            const majorImageSize = getImageSize(majorSide)
 | 
						|
            // const minorImageSize = getImageSize(minorSide)
 | 
						|
 | 
						|
            let ratio = getImageSize(minorSide) / getImageSize(majorSide)
 | 
						|
            let size = majorImageSize > max[majorSide.name] ? max[majorSide.name] : majorImageSize
 | 
						|
 | 
						|
            if (size * ratio > maxMinorSize) {
 | 
						|
                size = maxMinorSize / ratio
 | 
						|
            }
 | 
						|
 | 
						|
            let targetDimensions = {
 | 
						|
                width: 0,
 | 
						|
                height: 0
 | 
						|
            }
 | 
						|
 | 
						|
            let position = Points.fromPageToNode(context, Points.fromNodeToPage(source, { x: 0, y: 0 }))
 | 
						|
 | 
						|
            let targetOffset = {
 | 
						|
                x: 0,
 | 
						|
                y: 0
 | 
						|
            }
 | 
						|
 | 
						|
            targetDimensions[majorSide.name] = size
 | 
						|
            targetDimensions[minorSide.name] = size * ratio
 | 
						|
 | 
						|
            targetOffset[majorSide.axis] =
 | 
						|
                (context['offset' + capitalize(majorSide.name)] - targetDimensions[majorSide.name]) / 2
 | 
						|
            targetOffset[minorSide.axis] =
 | 
						|
                (context['offset' + capitalize(minorSide.name)] - targetDimensions[minorSide.name]) / 2
 | 
						|
 | 
						|
            overlay.appendChild(imageWrapper)
 | 
						|
 | 
						|
            TweenMax.set(imageWrapper, {
 | 
						|
                left: 0,
 | 
						|
                top: 0,
 | 
						|
                x: position.x,
 | 
						|
                y: position.y,
 | 
						|
                position: 'absolute',
 | 
						|
                width: parseInt(sourceStyle.width),
 | 
						|
                height: parseInt(sourceStyle.height)
 | 
						|
            })
 | 
						|
 | 
						|
            TweenMax.set(overlay, {
 | 
						|
                display: 'flex',
 | 
						|
                autoAlpha: 0
 | 
						|
            })
 | 
						|
 | 
						|
            TweenMax.to(imageWrapper, this.zoomAnimationDuration, {
 | 
						|
                x: targetOffset.x,
 | 
						|
                y: targetOffset.y,
 | 
						|
                width: targetDimensions.width,
 | 
						|
                height: targetDimensions.height
 | 
						|
            })
 | 
						|
            TweenMax.to(overlay, this.fadeAnimationTime, {
 | 
						|
                autoAlpha: 1
 | 
						|
            })
 | 
						|
        }
 | 
						|
 | 
						|
        image.src = src
 | 
						|
    }
 | 
						|
 | 
						|
    _replaceIcon(clone) {
 | 
						|
        let zoomIcon = clone.querySelector('.icon.zoom')
 | 
						|
        zoomIcon.classList.remove('zoom')
 | 
						|
        zoomIcon.classList.add('close')
 | 
						|
    }
 | 
						|
 | 
						|
    getBorderHeight(style) {
 | 
						|
        const borderWidth = parseInt(style.borderTopWidth) + parseInt(style.borderBottomWidth)
 | 
						|
        const padding = parseInt(style.paddingTop) + parseInt(style.paddingBottom)
 | 
						|
        return parseInt(style.width) + borderWidth + padding
 | 
						|
    }
 | 
						|
 | 
						|
    getBorderWidth(style) {
 | 
						|
        const borderWidth = parseInt(style.borderLeftWidth) + parseInt(style.borderRightWidth)
 | 
						|
        const padding = parseInt(style.paddingLeft) + parseInt(style.paddingRight)
 | 
						|
        return parseInt(style.width) + borderWidth + padding
 | 
						|
    }
 | 
						|
 | 
						|
    closeThumnailDetail(context) {
 | 
						|
        let overlay = context.querySelector('.img-overlay')
 | 
						|
 | 
						|
        let timeline = new TimelineLite()
 | 
						|
 | 
						|
        timeline
 | 
						|
            .to(overlay, this.fadeAnimationDuration, {
 | 
						|
                autoAlpha: 0
 | 
						|
            })
 | 
						|
            .set(overlay, {
 | 
						|
                display: 'none'
 | 
						|
            })
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
CardPlugin.Ui = class UiPlugin extends CardPluginBase {
 | 
						|
    constructor(className, parent = null) {
 | 
						|
        super()
 | 
						|
        this.parent = parent
 | 
						|
        this.className = className
 | 
						|
    }
 | 
						|
 | 
						|
    _getVerificationFunctions(context) {
 | 
						|
        let arr = super._getVerificationFunctions(context)
 | 
						|
        let func = [this._doesParentExist.bind(this, context, this.parent)]
 | 
						|
        return arr.concat(func)
 | 
						|
    }
 | 
						|
 | 
						|
    _doesParentExist(context, parent) {
 | 
						|
        if (parent == null) return true
 | 
						|
        let valid = context.querySelector(parent) != null
 | 
						|
        if (!valid) console.error('Could not find parent on context.', context, parent)
 | 
						|
        return valid
 | 
						|
    }
 | 
						|
 | 
						|
    append(context) {
 | 
						|
        parent = this.parent == null ? context : context.querySelector(this.parent).appendChild(container)
 | 
						|
        let container = document.createElement('div')
 | 
						|
        container.className = this.className
 | 
						|
        parent.appendChild(container)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
CardPlugin.Speech = class SpeechPlugin extends CardPluginBase {
 | 
						|
    constructor(parentSelector, className, interactionType = 'tap') {
 | 
						|
        super()
 | 
						|
        this.className = className
 | 
						|
        this.parentSelector = parentSelector
 | 
						|
        this.interactionType = interactionType
 | 
						|
 | 
						|
        // We directly overwriting the function with a version that has a binded
 | 
						|
        // reference to itself. Doing so provides an easy and reliable way to remove
 | 
						|
        // the event listener using this function. -  SO
 | 
						|
        this._domWasChanged = this._domWasChanged.bind(this)
 | 
						|
 | 
						|
        /* 
 | 
						|
        Speech doesn't stop when page is navigated.
 | 
						|
        Therefore we do it manually here.
 | 
						|
        */
 | 
						|
        window.addEventListener('beforeunload', () => {
 | 
						|
            window.speechSynthesis.cancel()
 | 
						|
        })
 | 
						|
 | 
						|
        // Binding the function beforehand ensures, that the end function is always the same.
 | 
						|
        this._end = this._end.bind(this)
 | 
						|
 | 
						|
        this._setupUtterance()
 | 
						|
        this.utterance.addEventListener('end', event => {
 | 
						|
            this._end()
 | 
						|
        })
 | 
						|
    }
 | 
						|
 | 
						|
    get require() {
 | 
						|
        return [CardPlugin.Ui]
 | 
						|
    }
 | 
						|
 | 
						|
    subcardChanged(closed) {
 | 
						|
        if (this.cardActive) {
 | 
						|
            this._updateText(closed)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    get cardActive() {
 | 
						|
        return this.activeUtterance == this.utterance
 | 
						|
    }
 | 
						|
 | 
						|
    _updateText(ignoreSubcard = false) {
 | 
						|
        let node = this.context
 | 
						|
        let subcard = node.querySelector('.mainview > .subcard')
 | 
						|
 | 
						|
        if (ignoreSubcard) {
 | 
						|
            if (subcard != null) {
 | 
						|
                let clone = node.cloneNode(true)
 | 
						|
                let clonedSubcard = clone.querySelector('.mainview > .subcard')
 | 
						|
                clonedSubcard.parentNode.removeChild(clonedSubcard)
 | 
						|
                node = clone
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            if (subcard) {
 | 
						|
                let clone = subcard.cloneNode(true)
 | 
						|
                clone.querySelectorAll('figure').forEach(figure => {
 | 
						|
                    figure.parentNode.removeChild(figure)
 | 
						|
                })
 | 
						|
 | 
						|
                node = clone
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        let id = this.context.getAttribute('data-id')
 | 
						|
        let src = this.context.getAttribute('data-source')
 | 
						|
        let subcardSource = null
 | 
						|
        if (subcard != null) {
 | 
						|
            subcardSource = subcard.getAttribute('data-source')
 | 
						|
        }
 | 
						|
 | 
						|
        if (!window.speechSynthesis.speaking) {
 | 
						|
            this._start(node)
 | 
						|
            Logging.log(`Started speech on card: id:${id} - source: ${src} - subcard: ${subcardSource}`)
 | 
						|
        } else if (this.cardActive && this._sameText(node)) {
 | 
						|
            Logging.log(`Stopped speech on card: id:${id} - source: ${src} - subcard: ${subcardSource}`)
 | 
						|
            this._stop()
 | 
						|
        } else {
 | 
						|
            Logging.log(`Updated Text on card: id:${id} - source: ${src} - subcard: ${subcardSource}`)
 | 
						|
            this._stop()
 | 
						|
                .then(() => {
 | 
						|
                    this._start(node)
 | 
						|
                })
 | 
						|
                .catch(console.error)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    _sameText(node) {
 | 
						|
        return this.utterance.text == this._cleanupText(node)
 | 
						|
    }
 | 
						|
 | 
						|
    _setupUtterance() {
 | 
						|
        this.utterance = new SpeechSynthesisUtterance()
 | 
						|
        this.utterance.lang = 'de-DE'
 | 
						|
    }
 | 
						|
 | 
						|
    get require() {
 | 
						|
        return [CardPlugin.Ui]
 | 
						|
    }
 | 
						|
 | 
						|
    remove() {
 | 
						|
        this.button = null
 | 
						|
        this._stopThisSpeechIfPlaying()
 | 
						|
        this.context.removeEventListener('DOMNodeRemoved', this._domWasChanged)
 | 
						|
        super.remove()
 | 
						|
    }
 | 
						|
 | 
						|
    append(context) {
 | 
						|
        let container = context.querySelector(this.parentSelector)
 | 
						|
        this.button = document.createElement('div')
 | 
						|
        this.button.className = 'icon button ' + this.className
 | 
						|
        container.appendChild(this.button)
 | 
						|
 | 
						|
        InteractionMapper.on(this.interactionType, this.button, () => {
 | 
						|
            this.speak()
 | 
						|
        })
 | 
						|
 | 
						|
        context.addEventListener('DOMNodeRemoved', this._domWasChanged)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Don't remember why this was required - SO 20-11-2019
 | 
						|
     */
 | 
						|
    _domWasChanged(event) {
 | 
						|
        if (event.target == this.context) this._stopThisSpeechIfPlaying()
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Stops the module if it is set in the context.
 | 
						|
     */
 | 
						|
    _stopThisSpeechIfPlaying() {
 | 
						|
        if (this.context == null || this.context['lastSpeechNode'] == window.speechSynthesis['speechPluginNode']) {
 | 
						|
            this._stop()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    _isSameNode(node) {
 | 
						|
        return this.currentText == node.textContent
 | 
						|
    }
 | 
						|
 | 
						|
    speak() {
 | 
						|
        /**
 | 
						|
     * This is a little bit ugly, but imho the most elegant of all dirty solutions.
 | 
						|
     *
 | 
						|
5ht         * Within the plugins we have no knowledge of other cards and such. But must differentiate the
 | 
						|
     * clicks by their corresponding owner. The SpeechUtterance just takes a text and has no knowledge
 | 
						|
     * about the node that is currently read to the user.
 | 
						|
     *
 | 
						|
     * This means, that we can identify same text, but not differentiate same text on different nodes.
 | 
						|
     * To account for that, we add the node to the speechSynthesis object (#benefitsOfJavaScript) and
 | 
						|
     * have access to the node, by - let's say - expanding the functionality of the SpeechSynthesis object.
 | 
						|
     *
 | 
						|
     * SO -17.07.19
 | 
						|
     */
 | 
						|
 | 
						|
        this._updateText()
 | 
						|
    }
 | 
						|
 | 
						|
    async _stop() {
 | 
						|
        return new Promise(resolve => {
 | 
						|
            if (this.activeUtterance) {
 | 
						|
                this.activeUtterance.addEventListener('end', resolve, {
 | 
						|
                    once: true
 | 
						|
                })
 | 
						|
            }
 | 
						|
 | 
						|
            window.speechSynthesis.cancel()
 | 
						|
        })
 | 
						|
    }
 | 
						|
 | 
						|
    get activeUtterance() {
 | 
						|
        return window.speechSynthesis['speechPluginUtterance']
 | 
						|
    }
 | 
						|
 | 
						|
    _end() {
 | 
						|
        window.speechSynthesis['speechPluginNode'] = null
 | 
						|
        window.speechSynthesis['speechPluginUtterance'] = null
 | 
						|
        this._deactivateButton()
 | 
						|
        this.context.classList.remove('speech-plugin-is-reading')
 | 
						|
    }
 | 
						|
 | 
						|
    _start(node) {
 | 
						|
        window.speechSynthesis.cancel()
 | 
						|
 | 
						|
        window.speechSynthesis['speechPluginUtterance'] = this.utterance
 | 
						|
        window.speechSynthesis['speechPluginNode'] = node
 | 
						|
        this.context['lastSpeechNode'] = node
 | 
						|
 | 
						|
        let cleanText = this._cleanupText(node)
 | 
						|
        this.utterance.text = cleanText
 | 
						|
        window.speechSynthesis.speak(this.utterance)
 | 
						|
        this._activateButton()
 | 
						|
 | 
						|
        this.context.classList.add('speech-plugin-is-reading')
 | 
						|
    }
 | 
						|
 | 
						|
    closed() {}
 | 
						|
 | 
						|
    _cleanupText(node) {
 | 
						|
        let text = node.textContent
 | 
						|
        text = this._removeShy(text)
 | 
						|
        return text
 | 
						|
    }
 | 
						|
 | 
						|
    _removeShy(text) {
 | 
						|
        return text.replace(/\u00AD/g, '')
 | 
						|
    }
 | 
						|
 | 
						|
    _activateButton() {
 | 
						|
        if (this.button) this.button.classList.add('active')
 | 
						|
    }
 | 
						|
    _deactivateButton() {
 | 
						|
        if (this.button) this.button.classList.remove('active')
 | 
						|
    }
 | 
						|
}
 |