jamestomasino/stutter

View on GitHub
src-content/lib/block.js

Summary

Maintainability
A
0 mins
Test Coverage
import Word from './word'
import Parts from './parts'
import Locale from './locales.js'
const locale = new Locale()

export default class Block {
  constructor (val, settings) {
    this.parts = new Parts()
    this.words = []
    this.index = 0
    this.settings = settings

    // textContent collapses some sentences which were separated by DOM
    // elements alone. We attempt to restore spaces between paragraphs.

    // punctution sandwiched between two words
    val = val.replace(/([.?!,:;])(?=[a-z][a-z])/ig, '$1 ')

    // two quotes in a row
    val = val.replace(/([”"])(?=["“])/ig, '$1 ')

    // punctuation close-quote word
    val = val.replace(/([.?!,:;])(["”])(?=\w)/ig, '$1$2 ')

    // punctuation open-quote word
    val = val.replace(/([.?!,:;])“(?=\w)/ig, '$1 “')

    // Build word chain
    let rawWords = val.match(locale.wordRegex)

    // temporary variables for building up text fragment phrases
    let phrase = ''
    let wordQueue = []
    let purgeQueue = false

    rawWords.map(word => {
      // retroactively apply phrase to words in queue
      if (purgeQueue) {
        this.setTextFragmentToWords(wordQueue, phrase.trim())
        purgeQueue = false
        phrase = ''
      }

      // Extra splits on odd punctuation situations
      let brokenWord = this.parts.puncBreak(word)

      // Calculate phrase or sentence fragment
      if (brokenWord !== '\n') {
        phrase += (phrase) ? ' ' + brokenWord : '' + brokenWord
      }

      let subWords = brokenWord.match(locale.wordRegex)
      subWords.map(subWord => {
        // break long words
        let maxWordLength = (settings.getProp('maxWordLength') || 13)
        let w
        if (subWord.length > maxWordLength) {
          let brokenSubWord = this.parts.breakLongWord(subWord, maxWordLength)
          let subSubWords = brokenSubWord.match(locale.wordRegex)
          subSubWords.map(subSubWord => {
            w = new Word(subSubWord)
            this.words.push(w)
            wordQueue.push(w)
          })
        } else {
          w = new Word(subWord)
          this.words.push(w)
          wordQueue.push(w)
        }

        // If this word contains punctuation, set the phrase to end
        if (w.hasPeriod || w.hasOtherPunc) {
          purgeQueue = true
        }
      })
    })

    // Handle text fragments in the final phrase
    this.setTextFragmentToWords(wordQueue, phrase)
  }

  setTextFragmentToWords (wordQueue, fragment) {
    while (wordQueue.length) {
      let retroWord = wordQueue.pop()
      retroWord.textFragment = fragment
    }
  }

  get word () {
    if (this.words.length && this.index < this.words.length) {
      return this.words[this.index]
    } else {
      return null
    }
  }

  get nextWord () {
    if (this.words.length && this.index + 1 < this.words.length) {
      return this.words[this.index + 1]
    } else {
      return null
    }
  }

  get time () {
    return this.getTime(this.words[this.index])
  }

  get duration () {
    return this.words.reduce((acc, cur) => {
      return acc + this.getTime(cur)
    }, 0)
  }

  getTime (word) {
    var time = this.settings.delay
    if (word.hasPeriod) time *= this.settings.getProp('sentenceDelay')
    if (word.hasOtherPunc) time *= this.settings.getProp('otherPuncDelay')
    if (word.isShort) time *= this.settings.getProp('shortWordDelay')
    if (word.isLong) time *= this.settings.getProp('longWordDelay')
    if (word.isNumeric) time *= this.settings.getProp('numericDelay')
    return time
  }

  next () {
    this.index = Math.min(this.index + 1, this.words.length)
  }

  prev () {
    this.index = Math.max(this.index - 1, 0)
  }

  restart () {
    this.index = 0
  }

  get progress () {
    return this.index / this.words.length
  }
}