<commit_before>#!/usr/bin/env node
'use strict'

const opener = require('opener')
const node = process.execPath
const fs = require('fs')
const fg = require('foreground-child')
const spawn = require('child_process').spawn
const nycBin = require.resolve(
  'nyc/' + require('nyc/package.json').bin.nyc
)
const glob = require('glob')
const isexe = require('isexe')
const yaml = require('tap-yaml')
const path = require('path')
const exists = require('fs-exists-cached').sync
const os = require('os')
const isTTY = process.stdin.isTTY || process.env._TAP_IS_TTY === '1'
const tsNode = require.resolve(
  'ts-node/' + require('ts-node/package.json').bin['ts-node']
)
const esm = require.resolve('esm')

const main = options => {
  // tell chalk if we want color or not.
  if (!options.color)
    process.argv.push('--no-color')
  else {
    const c = process.argv.indexOf('--no-color')
    if (c !== -1)
      process.argv.splice(c, 1)
  }

  const rc = parseRcFile(options.rcfile)
  for (let i in rc) {
    if (!options._.explicit.has(i))
      options[i] = rc[i]
  }

  const pj = parsePackageJson()
  for (let i in pj) {
    if (!options._.explicit.has(i))
      options[i] = pj[i]
  }

  if (options.reporter === null)
    options.reporter = options.color ? 'classic' : 'tap'

  if (options['dump-config']) {
    console.log(yaml.stringify(Object.keys(options).filter(k =>
      k !== 'dump-config' && k !== '_' && !/^[A-Z_]+$/.test(k)
    ).sort().reduce((set, k) =>
      (set[k] = options[k], set), {})))
    return
  }

  if (options.versions) {
    return console.log(yaml.stringify({
      tap: require('../package.json').version,
      'tap-parser': require('tap-parser/package.json').version,
      nyc: require('nyc/package.json').version,
    }))
  }

  if (options.version)
    return console.log(require('../package.json').version)

  if (options['parser-version'])
    return console.log(require('tap-parser/package.json').version)

  if (options['nyc-version'])
    return console.log(require('nyc/package.json').version)

  if (options['nyc-help'])
    return nycHelp()

  options.files = globFiles(options._)

  process.stdout.on('error', er => {
    /* istanbul ignore else */
    if (er.code === 'EPIPE')
      process.exit()
    else
      throw er
  })

  options.grep = options.grep.map(strToRegExp)

  // this is only testable by escaping from the covered environment
  /* istanbul ignore next */
  if ((options._.explicit.has('coverage-report') ||
       options._.explicit.has('check-coverage')) &&
      options.files.length === 0)
    return runCoverageReportOnly(options)

  if (options.files.length === 0) {
    if (isTTY) {
      console.error(
        `Reading TAP data from stdin
(use '-' argument to suppress this notice or -h for usage)`)
    }
    options.files.push('-')
  }

  if (options.files.length === 1 && options.files[0] === '-') {
    if (options.coverage)
      console.error('Coverage disabled (stdin cannot be instrumented)')
    setupTapEnv(options)
    stdinOnly(options)
    return
  }

  /* istanbul ignore next */
  if (options.coverage && !process.env.NYC_CONFIG)
    respawnWithCoverage(options)
  else {
    setupTapEnv(options)
    runTests(options)
  }
}

/* istanbul ignore next */
const nycReporter = options =>
  options['coverage-report'] === 'html' ? 'lcov'
  : (options['coverage-report'] || 'text')

/* istanbul ignore next */
const runNyc = (cmd, programArgs, options, spawnOpts) => {
  const reporter = nycReporter(options)

  const args = [
    nycBin,
    ...cmd,
    ...(options['show-process-tree'] ? ['--show-process-tree'] : []),
    '--cache=true',
    '--branches=' + options.branches,
    '--functions=' + options.functions,
    '--lines=' + options.lines,
    '--statements=' + options.statements,
    '--reporter=' + reporter,
    ...options['nyc-arg'],
  ]
  if (options['check-coverage'])
    args.push('--check-coverage')

  args.push.apply(args, programArgs)

  const child = fg(node, args, spawnOpts)

  if (reporter === 'lcov' && options.browser) {
    child.removeAllListeners('close')
    child.on('close', (code, signal) =>
      openHtmlCoverageReport(options, code, signal))
  }
  return child
}

/* istanbul ignore next */
const runCoverageReportOnly = options => {
  runNyc(['report'], [], options)
}

/* istanbul ignore next */
const pipeToCoveralls = options => {

  const reporter = runNyc(['report'], [], {
    ...options,
    'coverage-report': 'text-lcov'
  }, {
    stdio: [0, 'pipe', 2]
  })
  reporter.removeAllListeners('close')

  /* istanbul ignore next */
  const bin = process.env.__TAP_COVERALLS_TEST__
    || require.resolve('coveralls/bin/coveralls.js')

  const ca = fg(node, [bin], { stdio: ['pipe', 1, 2] })
  reporter.stdout.pipe(ca.stdin)
}

/* istanbul ignore next */
const respawnWithCoverage = options => {
  runNyc([], [
    '--',
    node,
    ...process.execArgv,
    ...process.argv.slice(1)
  ], options)
}

/* istanbul ignore next */
const openHtmlCoverageReport = (options, code, signal) => {
  opener('coverage/lcov-report/index.html')
  if (signal) {
    setTimeout(() => {}, 200)
    process.kill(process.pid, signal)
  } else if (code) {
    process.exitCode = code
  }
}

const nycHelp = _ => fg(node, [nycBin, '--help'])

const setupTapEnv = options => {
  process.env.TAP_TIMEOUT = options.timeout
  if (options.color)
    process.env.TAP_COLORS = '1'
  else
    process.env.TAP_COLORS = '0'

  if (options.snapshot)
    process.env.TAP_SNAPSHOT = '1'

  if (options.bail)
    process.env.TAP_BAIL = '1'

  if (options.invert)
    process.env.TAP_GREP_INVERT = '1'

  if (options.grep.length)
    process.env.TAP_GREP = options.grep.map(p => p.toString())
      .join('\n')

  if (options.only)
    process.env.TAP_ONLY = '1'
}

const globFiles = files => Array.from(files.reduce((acc, f) =>
  acc.concat(f === '-' ? f : glob.sync(f, { nonull: true })), [])
  .reduce((set, f) => {
    set.add(f)
    return set
  }, new Set()))

const makeReporter = options =>
  new (require('tap-mocha-reporter'))(options.reporter)

const stdinOnly = options => {
  // if we didn't specify any files, then just passthrough
  // to the reporter, so we don't get '/dev/stdin' in the suite list.
  // We have to pause() before piping to switch streams2 into old-mode
  process.stdin.pause()
  const reporter = makeReporter(options)
  process.stdin.pipe(reporter)
  if (options['output-file'] !== null)
    process.stdin.pipe(fs.createWriteStream(options['output-file']))
  process.stdin.resume()
}

const readSaveFile = options => {
  if (options.save)
    try {
      const s = fs.readFileSync(options.save, 'utf8').trim()
      if (s)
        return s.split('\n')
    } catch (er) {}

  return null
}

const saveFails = (options, tap) => {
  if (!options.save)
    return

  let fails = []
  const successes = []
  tap.on('result', res => {
    // we will continue to re-run todo tests, even though they're
    // not technically "failures".
    if (!res.ok && !res.extra.skip)
      fails.push(res.extra.file)
    else
      successes.push(res.extra.file)
  })

  const save = () => {
    fails = fails.reduce((set, f) => {
      f = f.replace(/\\/g, '/')
      if (set.indexOf(f) === -1)
        set.push(f)
      return set
    }, [])

    if (!fails.length)
      try {
        fs.unlinkSync(options.save)
      } catch (er) {}
    else
      try {
        fs.writeFileSync(options.save, fails.join('\n') + '\n')
      } catch (er) {}
  }

  tap.on('bailout', reason => {
    // add any pending test files to the fails list.
    fails.push.apply(fails, options.files.filter(file =>
      successes.indexOf(file) === -1))
    save()
  })

  tap.on('end', save)
}

const filterFiles = (files, saved, parallelOk) =>
  files.filter(file =>
    path.basename(file) === 'tap-parallel-ok' ?
      ((parallelOk[path.resolve(path.dirname(file))] = true), false)
    : path.basename(file) === 'tap-parallel-not-ok' ?
      parallelOk[path.resolve(path.dirname(file))] = false
    : onSavedList(saved, file)
  )

// check if the file is on the list, or if it's a parent dir of
// any items that are on the list.
const onSavedList = (saved, file) =>
  !saved || !saved.length ? true
  : saved.indexOf(file) !== -1 ? true
  : saved.some(f => f.indexOf(file + '/') === 0)

const isParallelOk = (parallelOk, file) => {
  const dir = path.resolve(path.dirname(file))
  return (dir in parallelOk) ? parallelOk[dir]
    : exists(dir + '/tap-parallel-ok')
    ? parallelOk[dir] = true
    : exists(dir + '/tap-parallel-not-ok')
    ? parallelOk[dir] = false
    : dir.length >= process.cwd().length
    ? isParallelOk(parallelOk, dir)
    : true
}

const runAllFiles = (options, saved, tap) => {
  let doStdin = false
  let parallelOk = Object.create(null)

  options.files = filterFiles(options.files, saved, parallelOk)
  let tapChildId = 0

  for (let i = 0; i < options.files.length; i++) {
    const opt = {}
    const file = options.files[i]

    // Pick up stdin after all the other files are handled.
    if (file === '-') {
      doStdin = true
      continue
    }

    let st
    try {
      st = fs.statSync(file)
    } catch (er) {
      continue
    }

    if (options.timeout)
      opt.timeout = options.timeout * 1000

    opt.file = file
    opt.childId = tapChildId++
    if (st.isDirectory()) {
      const dir = filterFiles(fs.readdirSync(file).map(f =>
        file.replace(/[\/\\]+$/, '') + '/' + f), saved, parallelOk)
      options.files.splice(i, 1, ...dir)
      i--
    } else {
      if (options.jobs > 1)
        opt.buffered = isParallelOk(parallelOk, file) !== false

      if (file.match(/\.m?js$/)) {
        const args = [
          ...(options.esm ? ['-r', esm] : []),
          ...options['node-arg'],
          file,
          ...options['test-arg']
        ]
        tap.spawn(node, args, opt, file)
      } else if (file.match(/\.ts$/)) {
        const args = [
          ...options['node-arg'],
          file,
          ...options['test-arg']
        ]
        tap.spawn(tsNode, args, opt, file)
      } else if (isexe.sync(options.files[i]))
        tap.spawn(options.files[i], options['test-arg'], opt, file)
    }
  }

  if (doStdin)
    tap.stdin()
}

const runTests = options => {
  const saved = readSaveFile(options)

  // At this point, we know we need to use the tap root,
  // because there are 1 or more files to spawn.
  const tap = require('../lib/tap.js')
  if (options.comments) {
    const onComment = c => {
      if (!c.match(/^# (time=[0-9\.]+m?s|Subtest(: .+))\n$/))
        console.error(c.substr(2).trim())
    }
    const onChild = p => {
      p.on('comment', onComment)
      p.on('child', onChild)
    }
    tap.parser.on('comment', onComment)
    tap.parser.on('child', onChild)
  }

  tap.runOnly = false

  // greps are passed to children, but not the runner itself
  tap.grep = []
  tap.jobs = options.jobs
  tap.patchProcess()

  // if not -Rtap, then output what the user wants.
  // otherwise just dump to stdout
  tap.pipe(options.reporter === 'tap' ? process.stdout: makeReporter(options))

  // need to replay the first version line, because the previous
  // line will have flushed it out to stdout or the reporter already.
  if (options['output-file'] !== null)
    tap.pipe(fs.createWriteStream(options['output-file'])).write('TAP version 13\n')

  saveFails(options, tap)

  runAllFiles(options, saved, tap)

  tap.end()
}

const parsePackageJson = () => {
  try {
    return JSON.parse(fs.readFileSync('package.json', 'utf8')).tap || {}
  } catch (er) {
    return {}
  }
}

const parseRcFile = path => {
  try {
    const contents = fs.readFileSync(path, 'utf8')
    return yaml.parse(contents) || {}
  } catch (er) {
    // if no dotfile exists, or invalid yaml, fail gracefully
    return {}
  }
}

const strToRegExp = g => {
  const p = g.match(/^\/(.*)\/([a-z]*)$/)
  g = p ? p[1] : g
  const flags = p ? p[2] : ''
  return new RegExp(g, flags)
}

require('./jack.js')(main)
<commit_msg>Handle unnamed subtests in comment proxying<commit_after>#!/usr/bin/env node
'use strict'

const opener = require('opener')
const node = process.execPath
const fs = require('fs')
const fg = require('foreground-child')
const spawn = require('child_process').spawn
const nycBin = require.resolve(
  'nyc/' + require('nyc/package.json').bin.nyc
)
const glob = require('glob')
const isexe = require('isexe')
const yaml = require('tap-yaml')
const path = require('path')
const exists = require('fs-exists-cached').sync
const os = require('os')
const isTTY = process.stdin.isTTY || process.env._TAP_IS_TTY === '1'
const tsNode = require.resolve(
  'ts-node/' + require('ts-node/package.json').bin['ts-node']
)
const esm = require.resolve('esm')

const main = options => {
  // tell chalk if we want color or not.
  if (!options.color)
    process.argv.push('--no-color')
  else {
    const c = process.argv.indexOf('--no-color')
    if (c !== -1)
      process.argv.splice(c, 1)
  }

  const rc = parseRcFile(options.rcfile)
  for (let i in rc) {
    if (!options._.explicit.has(i))
      options[i] = rc[i]
  }

  const pj = parsePackageJson()
  for (let i in pj) {
    if (!options._.explicit.has(i))
      options[i] = pj[i]
  }

  if (options.reporter === null)
    options.reporter = options.color ? 'classic' : 'tap'

  if (options['dump-config']) {
    console.log(yaml.stringify(Object.keys(options).filter(k =>
      k !== 'dump-config' && k !== '_' && !/^[A-Z_]+$/.test(k)
    ).sort().reduce((set, k) =>
      (set[k] = options[k], set), {})))
    return
  }

  if (options.versions) {
    return console.log(yaml.stringify({
      tap: require('../package.json').version,
      'tap-parser': require('tap-parser/package.json').version,
      nyc: require('nyc/package.json').version,
    }))
  }

  if (options.version)
    return console.log(require('../package.json').version)

  if (options['parser-version'])
    return console.log(require('tap-parser/package.json').version)

  if (options['nyc-version'])
    return console.log(require('nyc/package.json').version)

  if (options['nyc-help'])
    return nycHelp()

  options.files = globFiles(options._)

  process.stdout.on('error', er => {
    /* istanbul ignore else */
    if (er.code === 'EPIPE')
      process.exit()
    else
      throw er
  })

  options.grep = options.grep.map(strToRegExp)

  // this is only testable by escaping from the covered environment
  /* istanbul ignore next */
  if ((options._.explicit.has('coverage-report') ||
       options._.explicit.has('check-coverage')) &&
      options.files.length === 0)
    return runCoverageReportOnly(options)

  if (options.files.length === 0) {
    if (isTTY) {
      console.error(
        `Reading TAP data from stdin
(use '-' argument to suppress this notice or -h for usage)`)
    }
    options.files.push('-')
  }

  if (options.files.length === 1 && options.files[0] === '-') {
    if (options.coverage)
      console.error('Coverage disabled (stdin cannot be instrumented)')
    setupTapEnv(options)
    stdinOnly(options)
    return
  }

  /* istanbul ignore next */
  if (options.coverage && !process.env.NYC_CONFIG)
    respawnWithCoverage(options)
  else {
    setupTapEnv(options)
    runTests(options)
  }
}

/* istanbul ignore next */
const nycReporter = options =>
  options['coverage-report'] === 'html' ? 'lcov'
  : (options['coverage-report'] || 'text')

/* istanbul ignore next */
const runNyc = (cmd, programArgs, options, spawnOpts) => {
  const reporter = nycReporter(options)

  const args = [
    nycBin,
    ...cmd,
    ...(options['show-process-tree'] ? ['--show-process-tree'] : []),
    '--cache=true',
    '--branches=' + options.branches,
    '--functions=' + options.functions,
    '--lines=' + options.lines,
    '--statements=' + options.statements,
    '--reporter=' + reporter,
    ...options['nyc-arg'],
  ]
  if (options['check-coverage'])
    args.push('--check-coverage')

  args.push.apply(args, programArgs)

  const child = fg(node, args, spawnOpts)

  if (reporter === 'lcov' && options.browser) {
    child.removeAllListeners('close')
    child.on('close', (code, signal) =>
      openHtmlCoverageReport(options, code, signal))
  }
  return child
}

/* istanbul ignore next */
const runCoverageReportOnly = options => {
  runNyc(['report'], [], options)
}

/* istanbul ignore next */
const pipeToCoveralls = options => {

  const reporter = runNyc(['report'], [], {
    ...options,
    'coverage-report': 'text-lcov'
  }, {
    stdio: [0, 'pipe', 2]
  })
  reporter.removeAllListeners('close')

  /* istanbul ignore next */
  const bin = process.env.__TAP_COVERALLS_TEST__
    || require.resolve('coveralls/bin/coveralls.js')

  const ca = fg(node, [bin], { stdio: ['pipe', 1, 2] })
  reporter.stdout.pipe(ca.stdin)
}

/* istanbul ignore next */
const respawnWithCoverage = options => {
  runNyc([], [
    '--',
    node,
    ...process.execArgv,
    ...process.argv.slice(1)
  ], options)
}

/* istanbul ignore next */
const openHtmlCoverageReport = (options, code, signal) => {
  opener('coverage/lcov-report/index.html')
  if (signal) {
    setTimeout(() => {}, 200)
    process.kill(process.pid, signal)
  } else if (code) {
    process.exitCode = code
  }
}

const nycHelp = _ => fg(node, [nycBin, '--help'])

const setupTapEnv = options => {
  process.env.TAP_TIMEOUT = options.timeout
  if (options.color)
    process.env.TAP_COLORS = '1'
  else
    process.env.TAP_COLORS = '0'

  if (options.snapshot)
    process.env.TAP_SNAPSHOT = '1'

  if (options.bail)
    process.env.TAP_BAIL = '1'

  if (options.invert)
    process.env.TAP_GREP_INVERT = '1'

  if (options.grep.length)
    process.env.TAP_GREP = options.grep.map(p => p.toString())
      .join('\n')

  if (options.only)
    process.env.TAP_ONLY = '1'
}

const globFiles = files => Array.from(files.reduce((acc, f) =>
  acc.concat(f === '-' ? f : glob.sync(f, { nonull: true })), [])
  .reduce((set, f) => {
    set.add(f)
    return set
  }, new Set()))

const makeReporter = options =>
  new (require('tap-mocha-reporter'))(options.reporter)

const stdinOnly = options => {
  // if we didn't specify any files, then just passthrough
  // to the reporter, so we don't get '/dev/stdin' in the suite list.
  // We have to pause() before piping to switch streams2 into old-mode
  process.stdin.pause()
  const reporter = makeReporter(options)
  process.stdin.pipe(reporter)
  if (options['output-file'] !== null)
    process.stdin.pipe(fs.createWriteStream(options['output-file']))
  process.stdin.resume()
}

const readSaveFile = options => {
  if (options.save)
    try {
      const s = fs.readFileSync(options.save, 'utf8').trim()
      if (s)
        return s.split('\n')
    } catch (er) {}

  return null
}

const saveFails = (options, tap) => {
  if (!options.save)
    return

  let fails = []
  const successes = []
  tap.on('result', res => {
    // we will continue to re-run todo tests, even though they're
    // not technically "failures".
    if (!res.ok && !res.extra.skip)
      fails.push(res.extra.file)
    else
      successes.push(res.extra.file)
  })

  const save = () => {
    fails = fails.reduce((set, f) => {
      f = f.replace(/\\/g, '/')
      if (set.indexOf(f) === -1)
        set.push(f)
      return set
    }, [])

    if (!fails.length)
      try {
        fs.unlinkSync(options.save)
      } catch (er) {}
    else
      try {
        fs.writeFileSync(options.save, fails.join('\n') + '\n')
      } catch (er) {}
  }

  tap.on('bailout', reason => {
    // add any pending test files to the fails list.
    fails.push.apply(fails, options.files.filter(file =>
      successes.indexOf(file) === -1))
    save()
  })

  tap.on('end', save)
}

const filterFiles = (files, saved, parallelOk) =>
  files.filter(file =>
    path.basename(file) === 'tap-parallel-ok' ?
      ((parallelOk[path.resolve(path.dirname(file))] = true), false)
    : path.basename(file) === 'tap-parallel-not-ok' ?
      parallelOk[path.resolve(path.dirname(file))] = false
    : onSavedList(saved, file)
  )

// check if the file is on the list, or if it's a parent dir of
// any items that are on the list.
const onSavedList = (saved, file) =>
  !saved || !saved.length ? true
  : saved.indexOf(file) !== -1 ? true
  : saved.some(f => f.indexOf(file + '/') === 0)

const isParallelOk = (parallelOk, file) => {
  const dir = path.resolve(path.dirname(file))
  return (dir in parallelOk) ? parallelOk[dir]
    : exists(dir + '/tap-parallel-ok')
    ? parallelOk[dir] = true
    : exists(dir + '/tap-parallel-not-ok')
    ? parallelOk[dir] = false
    : dir.length >= process.cwd().length
    ? isParallelOk(parallelOk, dir)
    : true
}

const runAllFiles = (options, saved, tap) => {
  let doStdin = false
  let parallelOk = Object.create(null)

  options.files = filterFiles(options.files, saved, parallelOk)
  let tapChildId = 0

  for (let i = 0; i < options.files.length; i++) {
    const opt = {}
    const file = options.files[i]

    // Pick up stdin after all the other files are handled.
    if (file === '-') {
      doStdin = true
      continue
    }

    let st
    try {
      st = fs.statSync(file)
    } catch (er) {
      continue
    }

    if (options.timeout)
      opt.timeout = options.timeout * 1000

    opt.file = file
    opt.childId = tapChildId++
    if (st.isDirectory()) {
      const dir = filterFiles(fs.readdirSync(file).map(f =>
        file.replace(/[\/\\]+$/, '') + '/' + f), saved, parallelOk)
      options.files.splice(i, 1, ...dir)
      i--
    } else {
      if (options.jobs > 1)
        opt.buffered = isParallelOk(parallelOk, file) !== false

      if (file.match(/\.m?js$/)) {
        const args = [
          ...(options.esm ? ['-r', esm] : []),
          ...options['node-arg'],
          file,
          ...options['test-arg']
        ]
        tap.spawn(node, args, opt, file)
      } else if (file.match(/\.ts$/)) {
        const args = [
          ...options['node-arg'],
          file,
          ...options['test-arg']
        ]
        tap.spawn(tsNode, args, opt, file)
      } else if (isexe.sync(options.files[i]))
        tap.spawn(options.files[i], options['test-arg'], opt, file)
    }
  }

  if (doStdin)
    tap.stdin()
}

const runTests = options => {
  const saved = readSaveFile(options)

  // At this point, we know we need to use the tap root,
  // because there are 1 or more files to spawn.
  const tap = require('../lib/tap.js')
  if (options.comments) {
    const onComment = c => {
      if (!c.match(/^# (time=[0-9\.]+m?s|Subtest(: .+)?)\n$/))
        console.error(c.substr(2).trim())
    }
    const onChild = p => {
      p.on('comment', onComment)
      p.on('child', onChild)
    }
    tap.parser.on('comment', onComment)
    tap.parser.on('child', onChild)
  }

  tap.runOnly = false

  // greps are passed to children, but not the runner itself
  tap.grep = []
  tap.jobs = options.jobs
  tap.patchProcess()

  // if not -Rtap, then output what the user wants.
  // otherwise just dump to stdout
  tap.pipe(options.reporter === 'tap' ? process.stdout: makeReporter(options))

  // need to replay the first version line, because the previous
  // line will have flushed it out to stdout or the reporter already.
  if (options['output-file'] !== null)
    tap.pipe(fs.createWriteStream(options['output-file'])).write('TAP version 13\n')

  saveFails(options, tap)

  runAllFiles(options, saved, tap)

  tap.end()
}

const parsePackageJson = () => {
  try {
    return JSON.parse(fs.readFileSync('package.json', 'utf8')).tap || {}
  } catch (er) {
    return {}
  }
}

const parseRcFile = path => {
  try {
    const contents = fs.readFileSync(path, 'utf8')
    return yaml.parse(contents) || {}
  } catch (er) {
    // if no dotfile exists, or invalid yaml, fail gracefully
    return {}
  }
}

const strToRegExp = g => {
  const p = g.match(/^\/(.*)\/([a-z]*)$/)
  g = p ? p[1] : g
  const flags = p ? p[2] : ''
  return new RegExp(g, flags)
}

require('./jack.js')(main)
<|endoftext|>


<commit_before>//Require react
var React = require('react');
var Router = require('react-router');
var socket = io.connect();

//Bring in your Helpers and components
var helpers = require('../utils/helpers');
var Toolbar = require('./Toolbar');
var CueList = require('./Cuelist');
var LiveView = require('./LiveView');
var SelectedFixture = require('./SelectedFixture');

var Console = React.createClass({
  getInitialState: function(){
    return {
        cues: [],
        patch: [],
        fixtures: [],
        selectedFixture: {},
        channelParameters: []
    };
  },
  componentDidMount: function(){
    this.getCues();
    this.getPatch();
    this.getFixtures();
    this.getChannelParameters();
  },

  getCues: function(){
    helpers.getCues()
      .then(function(cueData) {
        // console.log(cueData.data);
        if (cueData.data) {
          this.setState({
            cues: cueData.data
          });
        }
        // console.log(this.state.cues);
      }.bind(this));
  },

  getPatch: function(){
    helpers.getPatch()
      .then(function(patchData) {
        // console.log(patchData.data);
        if (patchData.data) {
          this.setState({
            patch: patchData.data
          });
          if (this.props.liveView.length < 1){
            helpers.generateLiveView(this.state.patch, this.props.setDmx);
          }
          helpers.startSlickSlider();
        }
        // console.log(this.state.patch);
      }.bind(this));
  },

  getFixtures: function(){
    helpers.getFixtures()
      .then(function(fixturesData) {
        // console.log(fixturesData.data);
        if (fixturesData.data) {
          this.setState({
            fixtures: fixturesData.data
          });
        }
        // console.log(this.state.fixtures);
      }.bind(this));
  },

  getChannelParameters: function(){
    helpers.getChannelParameters()
      .then(function(channelParametersData) {
        // console.log(channelgetChannelParametersData.data);
        if (channelParametersData.data) {
          this.setState({
            channelParameters: channelParametersData.data
          });
        }
        // console.log(this.state.channelgetChannelParameters);
      }.bind(this));
  },

  handleSubmit: function(item, event){
    // console.log(item);
  },
  handleChange: function(event){

  },
  render: function(){
    // console.log(this.props.liveView);
    return(

            <CueList
              cues={this.state.cues}
              setDmx={this.props.setDmx}
              liveDMX={this.props.liveView}
            />

                <LiveView liveDMX={this.props.liveView}/>
                <SelectedFixture
                  patch={this.state.patch}
                  setChannelValue={this.props.setDmxOne}
                  liveDMX={this.props.liveView}
                />

            <Toolbar
              liveDMX={this.props.liveView}
              channelParameters={this.state.channelParameters}
              fixtures={this.state.fixtures}
              getCues={this.getCues}
              getPatch={this.getPatch}
              getFixtures={this.getFixtures}
              cues={this.state.cues}
              patch={this.state.patch}
            />

    )
  }
});

module.exports = Console;
<commit_msg>Case sensitive...<commit_after>//Require react
var React = require('react');
var Router = require('react-router');
var socket = io.connect();

//Bring in your Helpers and components
var helpers = require('../utils/helpers');
var Toolbar = require('./ToolBar');
var CueList = require('./Cuelist');
var LiveView = require('./LiveView');
var SelectedFixture = require('./SelectedFixture');

var Console = React.createClass({
  getInitialState: function(){
    return {
        cues: [],
        patch: [],
        fixtures: [],
        selectedFixture: {},
        channelParameters: []
    };
  },
  componentDidMount: function(){
    this.getCues();
    this.getPatch();
    this.getFixtures();
    this.getChannelParameters();
  },

  getCues: function(){
    helpers.getCues()
      .then(function(cueData) {
        // console.log(cueData.data);
        if (cueData.data) {
          this.setState({
            cues: cueData.data
          });
        }
        // console.log(this.state.cues);
      }.bind(this));
  },

  getPatch: function(){
    helpers.getPatch()
      .then(function(patchData) {
        // console.log(patchData.data);
        if (patchData.data) {
          this.setState({
            patch: patchData.data
          });
          if (this.props.liveView.length < 1){
            helpers.generateLiveView(this.state.patch, this.props.setDmx);
          }
          helpers.startSlickSlider();
        }
        // console.log(this.state.patch);
      }.bind(this));
  },

  getFixtures: function(){
    helpers.getFixtures()
      .then(function(fixturesData) {
        // console.log(fixturesData.data);
        if (fixturesData.data) {
          this.setState({
            fixtures: fixturesData.data
          });
        }
        // console.log(this.state.fixtures);
      }.bind(this));
  },

  getChannelParameters: function(){
    helpers.getChannelParameters()
      .then(function(channelParametersData) {
        // console.log(channelgetChannelParametersData.data);
        if (channelParametersData.data) {
          this.setState({
            channelParameters: channelParametersData.data
          });
        }
        // console.log(this.state.channelgetChannelParameters);
      }.bind(this));
  },

  handleSubmit: function(item, event){
    // console.log(item);
  },
  handleChange: function(event){

  },
  render: function(){
    // console.log(this.props.liveView);
    return(

            <CueList
              cues={this.state.cues}
              setDmx={this.props.setDmx}
              liveDMX={this.props.liveView}
            />

                <LiveView liveDMX={this.props.liveView}/>
                <SelectedFixture
                  patch={this.state.patch}
                  setChannelValue={this.props.setDmxOne}
                  liveDMX={this.props.liveView}
                />

            <Toolbar
              liveDMX={this.props.liveView}
              channelParameters={this.state.channelParameters}
              fixtures={this.state.fixtures}
              getCues={this.getCues}
              getPatch={this.getPatch}
              getFixtures={this.getFixtures}
              cues={this.state.cues}
              patch={this.state.patch}
            />

    )
  }
});

module.exports = Console;
<|endoftext|>


<commit_before>angular.module('orderCloud')
    .controller('BaseCtrl', BaseController)
;

function BaseController($rootScope, $state, OrderCloudSDK, ocProductSearch, ocLineItems, CurrentUser, CurrentOrder, TotalQuantity) {
    var vm = this;
    vm.currentUser = CurrentUser;
    vm.currentOrder = CurrentOrder;
    vm.totalQuantity = TotalQuantity;

    vm.mobileSearch = mobileSearch;

    function mobileSearch() {
        return ocProductSearch.Open()
            .then(function(data) {
                if (data.productID) {
                    $state.go('productDetail', {productid: data.productID});
                } else {
                    $state.go('productSearchResults', {search: data.search});
                }
            });
    }

    $rootScope.$on('OC:UpdateOrder', function(event, OrderID, message) {
        vm.orderLoading = {
            message: message
        };
        vm.orderLoading.promise = OrderCloudSDK.Orders.Get('outgoing', OrderID)
            .then(function(order) {
                vm.currentOrder = order;
            });
    });

    $rootScope.$on('OC:UpdateTotalQuantity', function(event, lineItems, add, difference) {
        if (lineItems.length >= 1) {
            var quantities = _.pluck(lineItems, 'Quantity');
            return vm.totalQuantity = quantities.reduce(function(a, b) {return a + b}, 0);
        } else {
            var li = lineItems[0] || lineItems;
            if (vm.totalQuantity) {
                if (add) {
                    var newQuantity = difference ? difference : li.Quantity;
                    return vm.totalQuantity = newQuantity + vm.totalQuantity || 0;
                } else {
                    var newQuantity = difference ? difference : li.Quantity;
                    return vm.totalQuantity = vm.totalQuantity - newQuantity || 0;
                }
            } else {
                return vm.totalQuantity = li.Quantity || 0;
            }
        }
    })
}<commit_msg>F51-95 refactor: remove returns from event listener<commit_after>angular.module('orderCloud')
    .controller('BaseCtrl', BaseController)
;

function BaseController($rootScope, $state, OrderCloudSDK, ocProductSearch, ocLineItems, CurrentUser, CurrentOrder, TotalQuantity) {
    var vm = this;
    vm.currentUser = CurrentUser;
    vm.currentOrder = CurrentOrder;
    vm.totalQuantity = TotalQuantity;

    vm.mobileSearch = mobileSearch;

    function mobileSearch() {
        return ocProductSearch.Open()
            .then(function(data) {
                if (data.productID) {
                    $state.go('productDetail', {productid: data.productID});
                } else {
                    $state.go('productSearchResults', {search: data.search});
                }
            });
    }

    $rootScope.$on('OC:UpdateOrder', function(event, OrderID, message) {
        vm.orderLoading = {
            message: message
        };
        vm.orderLoading.promise = OrderCloudSDK.Orders.Get('outgoing', OrderID)
            .then(function(order) {
                vm.currentOrder = order;
            });
    });

    $rootScope.$on('OC:UpdateTotalQuantity', function(event, lineItems, add, difference) {
        if (lineItems.length >= 1) {
            var quantities = _.pluck(lineItems, 'Quantity');
            vm.totalQuantity = quantities.reduce(function(a, b) {return a + b}, 0);
        } else {
            var li = lineItems[0] || lineItems;
            if (vm.totalQuantity) {
                if (add) {
                    var newQuantity = difference ? difference : li.Quantity;
                    vm.totalQuantity = newQuantity + vm.totalQuantity || 0;
                } else {
                    var newQuantity = difference ? difference : li.Quantity;
                    vm.totalQuantity = vm.totalQuantity - newQuantity || 0;
                }
            } else {
                vm.totalQuantity = li.Quantity || 0;
            }
        }
    })
}<|endoftext|>


<commit_before>const CONTROL_GROUP = '
 \

 \
		 \
		 \
		<input type="text" class="input-nano abbreviation" placeholder="" maxlength="2"> \

 \
			 \

 \
		<input type="color" class="color"> \
		<button class="btn reset">Reset Color</button> \
		<input type="hidden" name="customColorSet" value="false"> \

 \

';

const NO_TILES_ADDED_TEXT = "No tiles added.";

const SUBMIT_BUTTON_SAVING_TEXT = "Saving…";
const SUBMIT_BUTTON_SUBMIT_TEXT = "Save";

const FAVICON_LOAD_FAIL_TITLE = "Failed to retrieve favicon!";
const FAVICON_LOAD_FAIL_URL_REPLACE = "#{url}";
const FAVICON_LOAD_FAIL_MESSAGE = "Could not retrieve favicon for #{url}. Check the URL and ensure the site does not redirect.";

var makeSitesTimeout;
const MAKE_SITES_TIMEOUT_DURATION = 500;

$(document).ready(function() {
	chrome.storage.sync.remove("sites");

	chrome.storage.sync.get(null, function(items) {
		console.log(items);
	});

	var sites = [];

	sitesReload();

	chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
		console.log('Message received: ' + request.message);

		if (request.message == "saved") {
			sitesReload();
		}
	});

	$("#color-regenerate-btn").on("click", function(e) {
		e.preventDefault();

		makeSites(true);
	});

	function sitesReload() {
		getSites(function (items) {
			$("#sites").html("");

			if (!items || items.length == 0) {
				$("#sites").prepend(NO_TILES_ADDED_TEXT);
			} else {
				sites = items;

				sites = sites.reverse();

				for (var i = 0; i < sites.length; i++) {
					(function() {
						var newControlGroup = CONTROL_GROUP;
						var site = sites[i];

						newControlGroup = $(newControlGroup);
						newControlGroup.find('span.url').html(site.url);
						newControlGroup.find('input.abbreviation').val(site.abbreviation);

						// remove button click event
						newControlGroup.find('a.remove').on('click', function(e) {
							removeControlGroup(e.target);
						});

						// abbreviation change event
						var abbreviationField = newControlGroup.find('input.abbreviation');
						abbreviationField.data('oldVal', abbreviationField.val());
						newControlGroup.find('input.abbreviation').on("propertychange keyup input paste", function() {
							if (abbreviationField.data('oldVal') != abbreviationField.val()
								&& abbreviationField.val() != '') {
								abbreviationField.data('oldVal', abbreviationField.val());
								makeSites();
						     }

						     if (abbreviationField.val() == '') {
						     	clearTimeout(makeSitesTimeout);
						     }
						});

						// set up custom color, if exists
						if (site.customColor != undefined) {
							newControlGroup.find('button.reset').show();
							newControlGroup.find('input[name="customColorSet"]').val("true");
							newControlGroup.find('input.color').val(rgbToHex(site.customColor["red"], site.customColor["green"], site.customColor["blue"]));
						} else {
							newControlGroup.find('button.reset').hide();
							newControlGroup.find('input[name="customColorSet"]').val("false");
							newControlGroup.find('input.color').val(rgbToHex(site.color["red"], site.color["green"], site.color["blue"]));
						}

						// color change event
						newControlGroup.find('input.color').on("change", function() {
							newControlGroup.find('button.reset').show();
							newControlGroup.find('input[name="customColorSet"]').val("true");

							makeSites();
						});

						// color reset click event
						newControlGroup.find('button.reset').on("click", function(e) {
							e.preventDefault();
							$(this).hide();
							newControlGroup.find('input[name="customColorSet"]').val("false");
							newControlGroup.find('input.color').val(rgbToHex(site.color["red"], site.color["green"], site.color["blue"]));

							makeSites();
						});

						$("#sites").prepend(newControlGroup);
					})();
				}
			}

			$("#sites").sortable({
				handle: '.handle',
				axis: 'y',
				update: function(e, ui) {
					makeSites();
				}
			});

			$(".container").removeClass("hidden");
		});
	}

	function removeControlGroup(element) {
		parent = $(element).parents('.control-group');
		chrome.extension.sendMessage({ message: "delete", url:parent.find('span.url').html() }, function(response) { });
	}

	function makeSites(forceColorRegeneration) {
		if (forceColorRegeneration == undefined) {
			forceColorRegeneration = false;
		}

		if (makeSitesTimeout) {
			clearTimeout(makeSitesTimeout);
		}

		makeSitesTimeout = setTimeout(function() {
			perform(forceColorRegeneration);
		}, MAKE_SITES_TIMEOUT_DURATION);

		function perform(force) {
			$("#color-regenerate-btn").attr("disabled", "disabled");

			var fields = [];

			$('#sites .site-controls').each(function(index, element) {
				urlField = $(this).children('span.url').eq(0);
				abbreviationField = $(this).children('input:text.abbreviation').eq(0);

				var url = urlField.html();
				var abbreviation = abbreviationField.val();

				if (!url.match(/^(http|https):\/\//)) {
					url = "http://" + url;

					urlField.html(url);
				}

				if (!abbreviation) {
					abbreviation = makeAbbreviation(getHostname(url));

					abbreviationField.val(abbreviation);
				}

				fields[index] = {};
				fields[index].url = url;
				fields[index].abbreviation = abbreviation;
				if ($(this).children('input[name="customColorSet"]').val() == "true") {
					fields[index].customColor = hexToRgb($(this).children('input[type=color]').val());
				}
			});

			getSites(function(sites) {
				for (var i = 0; i < fields.length; i++) {
					fields[i].color = null;
				}

				var numberOfSitesRequiringColor = 0;

			 	if (!force) {
					if (sites != null && sites.length != 0) {
						for (var i = 0; i < sites.length; i++) {
							for (var j = 0; j < fields.length; j++) {
								if (sites[i].url == fields[j].url) {
									fields[j].color = sites[i].color;
									break;
								}
							}
						}
					}
				} else {
					numberOfSitesRequiringColor = fields.length;

					console.log(Math.min(numberOfSitesRequiringColor, 0) + " sites require a color check");
				}

				var siteSaved = false;

				function saveIfReady() {
					if (!siteSaved && numberOfSitesRequiringColor <= 0) {
						saveSites(fields);

						siteSaved = true;

						$("#color-regenerate-btn").removeAttr("disabled");
					}
				}

				for (var i = 0; i < fields.length; i++) {
					(function() {
						var site = fields[i];

						if (!site.color) {
							setSiteColor(site, function(site, error) {
								numberOfSitesRequiringColor--;

								console.log((fields.length - numberOfSitesRequiringColor) + " / " + fields.length + " – Got color for " + site.url);

								saveIfReady();
							});
						} else {
							saveIfReady();
						}
					})();
				}
			});
		}
	}
});<commit_msg>Force abbreviation capitalization on input in options page.<commit_after>const CONTROL_GROUP = '
 \

 \
		 \
		 \
		<input type="text" class="input-nano abbreviation" placeholder="" maxlength="2"> \

 \
			 \

 \
		<input type="color" class="color"> \
		<button class="btn reset">Reset Color</button> \
		<input type="hidden" name="customColorSet" value="false"> \

 \

';

const NO_TILES_ADDED_TEXT = "No tiles added.";

const SUBMIT_BUTTON_SAVING_TEXT = "Saving…";
const SUBMIT_BUTTON_SUBMIT_TEXT = "Save";

const FAVICON_LOAD_FAIL_TITLE = "Failed to retrieve favicon!";
const FAVICON_LOAD_FAIL_URL_REPLACE = "#{url}";
const FAVICON_LOAD_FAIL_MESSAGE = "Could not retrieve favicon for #{url}. Check the URL and ensure the site does not redirect.";

var makeSitesTimeout;
const MAKE_SITES_TIMEOUT_DURATION = 500;

$(document).ready(function() {
	chrome.storage.sync.remove("sites");

	chrome.storage.sync.get(null, function(items) {
		console.log(items);
	});

	var sites = [];

	sitesReload();

	chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
		console.log('Message received: ' + request.message);

		if (request.message == "saved") {
			sitesReload();
		}
	});

	$("#color-regenerate-btn").on("click", function(e) {
		e.preventDefault();

		makeSites(true);
	});

	function sitesReload() {
		getSites(function (items) {
			$("#sites").html("");

			if (!items || items.length == 0) {
				$("#sites").prepend(NO_TILES_ADDED_TEXT);
			} else {
				sites = items;

				sites = sites.reverse();

				for (var i = 0; i < sites.length; i++) {
					(function() {
						var newControlGroup = CONTROL_GROUP;
						var site = sites[i];

						newControlGroup = $(newControlGroup);
						newControlGroup.find('span.url').html(site.url);
						newControlGroup.find('input.abbreviation').val(site.abbreviation);

						// remove button click event
						newControlGroup.find('a.remove').on('click', function(e) {
							removeControlGroup(e.target);
						});

						// abbreviation change event
						var abbreviationField = newControlGroup.find('input.abbreviation');
						abbreviationField.data('oldVal', abbreviationField.val());
						newControlGroup.find('input.abbreviation').on("propertychange keyup input paste", function() {
							if (abbreviationField.data('oldVal') != abbreviationField.val()
								&& abbreviationField.val() != '') {
								abbreviationField.val(makeAbbreviation(abbreviationField.val()));

								abbreviationField.data('oldVal', abbreviationField.val());
								makeSites();
						     }

						     if (abbreviationField.val() == '') {
						     	clearTimeout(makeSitesTimeout);
						     }
						});

						// set up custom color, if exists
						if (site.customColor != undefined) {
							newControlGroup.find('button.reset').show();
							newControlGroup.find('input[name="customColorSet"]').val("true");
							newControlGroup.find('input.color').val(rgbToHex(site.customColor["red"], site.customColor["green"], site.customColor["blue"]));
						} else {
							newControlGroup.find('button.reset').hide();
							newControlGroup.find('input[name="customColorSet"]').val("false");
							newControlGroup.find('input.color').val(rgbToHex(site.color["red"], site.color["green"], site.color["blue"]));
						}

						// color change event
						newControlGroup.find('input.color').on("change", function() {
							newControlGroup.find('button.reset').show();
							newControlGroup.find('input[name="customColorSet"]').val("true");

							makeSites();
						});

						// color reset click event
						newControlGroup.find('button.reset').on("click", function(e) {
							e.preventDefault();
							$(this).hide();
							newControlGroup.find('input[name="customColorSet"]').val("false");
							newControlGroup.find('input.color').val(rgbToHex(site.color["red"], site.color["green"], site.color["blue"]));

							makeSites();
						});

						$("#sites").prepend(newControlGroup);
					})();
				}
			}

			$("#sites").sortable({
				handle: '.handle',
				axis: 'y',
				update: function(e, ui) {
					makeSites();
				}
			});

			$(".container").removeClass("hidden");
		});
	}

	function removeControlGroup(element) {
		parent = $(element).parents('.control-group');
		chrome.extension.sendMessage({ message: "delete", url:parent.find('span.url').html() }, function(response) { });
	}

	function makeSites(forceColorRegeneration) {
		if (forceColorRegeneration == undefined) {
			forceColorRegeneration = false;
		}

		if (makeSitesTimeout) {
			clearTimeout(makeSitesTimeout);
		}

		makeSitesTimeout = setTimeout(function() {
			perform(forceColorRegeneration);
		}, MAKE_SITES_TIMEOUT_DURATION);

		function perform(force) {
			$("#color-regenerate-btn").attr("disabled", "disabled");

			var fields = [];

			$('#sites .site-controls').each(function(index, element) {
				urlField = $(this).children('span.url').eq(0);
				abbreviationField = $(this).children('input:text.abbreviation').eq(0);

				var url = urlField.html();
				var abbreviation = abbreviationField.val();

				if (!url.match(/^(http|https):\/\//)) {
					url = "http://" + url;

					urlField.html(url);
				}

				if (!abbreviation) {
					abbreviation = makeAbbreviation(getHostname(url));

					abbreviationField.val(abbreviation);
				}

				fields[index] = {};
				fields[index].url = url;
				fields[index].abbreviation = abbreviation;
				if ($(this).children('input[name="customColorSet"]').val() == "true") {
					fields[index].customColor = hexToRgb($(this).children('input[type=color]').val());
				}
			});

			getSites(function(sites) {
				for (var i = 0; i < fields.length; i++) {
					fields[i].color = null;
				}

				var numberOfSitesRequiringColor = 0;

			 	if (!force) {
					if (sites != null && sites.length != 0) {
						for (var i = 0; i < sites.length; i++) {
							for (var j = 0; j < fields.length; j++) {
								if (sites[i].url == fields[j].url) {
									fields[j].color = sites[i].color;
									break;
								}
							}
						}
					}
				} else {
					numberOfSitesRequiringColor = fields.length;

					console.log(Math.min(numberOfSitesRequiringColor, 0) + " sites require a color check");
				}

				var siteSaved = false;

				function saveIfReady() {
					if (!siteSaved && numberOfSitesRequiringColor <= 0) {
						saveSites(fields);

						siteSaved = true;

						$("#color-regenerate-btn").removeAttr("disabled");
					}
				}

				for (var i = 0; i < fields.length; i++) {
					(function() {
						var site = fields[i];

						if (!site.color) {
							setSiteColor(site, function(site, error) {
								numberOfSitesRequiringColor--;

								console.log((fields.length - numberOfSitesRequiringColor) + " / " + fields.length + " – Got color for " + site.url);

								saveIfReady();
							});
						} else {
							saveIfReady();
						}
					})();
				}
			});
		}
	}
});<|endoftext|>


<commit_before>function restoreOptions() {
    browser.storage.local.get([
        "linkedImagesRegexp",
        "minWidth",
        "minHeight",
    ]).then(
        result => {
            document.querySelector("#linkedImagesRegexp").value = result.linkedImagesRegexp || "";
            document.querySelector("#minWidth").value = result.minWidth || "";
            document.querySelector("#minHeight").value = result.minHeight || "";
        }
    );
}

function enableAutosave() {
    for (let input of document.querySelectorAll("input:not([type=radio]):not([type=checkbox]), textarea")) {
        input.addEventListener("input", saveOptions);
    }
    for (let input of document.querySelectorAll("input[type=radio], input[type=checkbox]")) {
        input.addEventListener("change", saveOptions);
    }
}

function saveOptions(event) {
    event.preventDefault();
    browser.storage.local.set({
        linkedImagesRegexp: document.querySelector("#linkedImagesRegexp").value,
        minWidth: document.querySelector("#minWidth").value,
        minHeight: document.querySelector("#minHeight").value,
    });
}

document.addEventListener("DOMContentLoaded", restoreOptions);
document.addEventListener("DOMContentLoaded", enableAutosave);
document.querySelector("form").addEventListener("submit", saveOptions);

browser.storage.onChanged.addListener(restoreOptions);
<commit_msg>Do no update option inputs unless there is a change<commit_after>function restoreOptions() {
    browser.storage.local.get([
        "linkedImagesRegexp",
        "minWidth",
        "minHeight",
    ]).then(
        result => {
            setTextValue("linkedImagesRegexp", result.linkedImagesRegexp || "");
            setTextValue("minWidth", result.minWidth || "");
            setTextValue("minHeight", result.minHeight || "");
        }
    );
}

function enableAutosave() {
    for (let input of document.querySelectorAll("input:not([type=radio]):not([type=checkbox]), textarea")) {
        input.addEventListener("input", saveOptions);
    }
    for (let input of document.querySelectorAll("input[type=radio], input[type=checkbox]")) {
        input.addEventListener("change", saveOptions);
    }
}

function setTextValue(elementID, newValue) {
    let oldValue = document.getElementById(elementID).value;

    if (oldValue !== newValue) {
        document.getElementById(elementID).value = newValue;
    }
}

function saveOptions(event) {
    event.preventDefault();
    browser.storage.local.set({
        linkedImagesRegexp: document.querySelector("#linkedImagesRegexp").value,
        minWidth: document.querySelector("#minWidth").value,
        minHeight: document.querySelector("#minHeight").value,
    });
}

document.addEventListener("DOMContentLoaded", restoreOptions);
document.addEventListener("DOMContentLoaded", enableAutosave);
document.querySelector("form").addEventListener("submit", saveOptions);

browser.storage.onChanged.addListener(restoreOptions);
<|endoftext|>


<commit_before>/**
 * Use a static list of Twitch stream qualities recognized by livestreamer:
 * source, high, medium, low, mobile
 *
 * Specific qualities are available in addition to the old named qualities:
 * 144p30, 240p30, 360p30, 480p30, 540p30, 720p30, 720p60, 1080p30, 1080p60
 * https://blog.twitch.tv/-705404e95cc2
 * https://twitter.com/Twitch/status/781969741053243392
 *
 * Always add an ultimate fallback quality to the end of the list. This is needed for
 * watching unpartnered channels that are only available in source quality when a
 * different default quality has been selected by the user.
 */
export default [
	// Source
	// Use "source" and all quality names in descending order in case "source" is unavailable,
	// but use "best" before using any static quality names. This is necessary because of a bug
	// in Livestreamer, which doesn't match quality names with a refresh rate and is therefore
	// unable to detect 1080p60 as the best quality.
	{
		id: "source",
		label: "Source",
		value: "source,best,1080p60,720p60,1080p30,720p30,540p30,480p30,360p30,240p30,144p30"
	},

	// High
	// 720p30 @ ~1.25 Mbit/s
	// use the same framerate first and exclude high framerates
	{
		id: "high",
		label: "High",
		value: "high,720p30,540p30,best"
	},

	// Medium
	// 480p30 @ ~0.75 Mbit/s
	// use the same bitrate first
	{
		id: "medium",
		label: "Medium",
		value: "medium,480p30,540p30,worst"
	},

	// Low
	// 360p30 @ ~0.50 Mbit/s
	// use the same resolution first
	{
		id: "low",
		label: "Low",
		value: "low,360p30,240p30,144p30,mobile,worst"
	},

	// Audio
	// no fallback qualities
	{
		id: "audio",
		label: "Audio only",
		value: "audio"
	}
];
<commit_msg>Update stream quality presets<commit_after>/**
 * Use a static list of Twitch stream qualities recognized by livestreamer:
 * source, high, medium, low, mobile
 *
 * Specific qualities are available in addition to the old named qualities:
 * 144p30, 240p30, 360p30, 480p30, 540p30, 720p30, 720p60, 1080p30, 1080p60
 * https://blog.twitch.tv/-705404e95cc2
 * https://twitter.com/Twitch/status/781969741053243392
 *
 * Always add an ultimate fallback quality to the end of the list. This is needed for
 * watching unpartnered channels that are only available in source quality when a
 * different default quality has been selected by the user.
 *
 * Update:
 * Qualities are now also listed without a framerate description.
 */
export default [
	// Source
	// Use "source" and all quality names in descending order in case "source" is unavailable.
	// Use "best" right after high framerate qualities, because those are most likely the "source".
	// This is necessary because of a bug in Livestreamer, which doesn't match quality names with a
	// refresh rate and is therefore unable to detect 1080p60 as the best quality.
	{
		id: "source",
		label: "Source",
		value: [
			"source",
			"1080p60",
			"720p60",
			"best",
			"1080p30",
			"720p30",
			"540p30",
			"480p30",
			"360p30",
			"240p30",
			"160p30"
		].join( "," )
	},

	// High
	// 720p30 @ ~1.25 Mbit/s
	// Use the same framerate first and exclude high framerates.
	// Include qualities without framerates, so they have higher priority than best at the end.
	{
		id: "high",
		label: "High",
		value: [
			"high",
			"720p30",
			"720p",
			"540p30",
			"540p",
			"best"
		].join( "," )
	},

	// Medium
	// 480p30 @ ~0.75 Mbit/s
	// Use the same bitrate first.
	// Include qualities without framerates, so they have higher priority than worst at the end.
	{
		id: "medium",
		label: "Medium",
		value: [
			"medium",
			"480p30",
			"480p",
			"540p30",
			"540p",
			"worst"
		].join( "," )
	},

	// Low
	// 360p30 @ ~0.50 Mbit/s
	// Use the same resolution first.
	// Include qualities without framerates, so they have higher priority than worst at the end.
	{
		id: "low",
		label: "Low",
		value: [
			"low",
			"360p30",
			"360p",
			"240p30",
			"240p",
			"160p30",
			"160p",
			"worst"
		].join( "," )
	},

	// Audio
	// No fallback qualities.
	{
		id: "audio",
		label: "Audio only",
		value: [
			"audio_only",
			"audio"
		].join( "," )
	}
];
<|endoftext|>


<commit_before>
var Observers = require("./observers");
var autoCancelPrevious = Observers.autoCancelPrevious;
var once = Observers.once;

exports.makePropertyBinder = makePropertyBinder;
function makePropertyBinder(observeObject, observeKey) {
    return function (observeValue, source, target, parameters, descriptor, trace) {
        return observeObject(autoCancelPrevious(function (object) {
            if (!object) return;
            return observeKey(autoCancelPrevious(function (key) {
                return observeValue(autoCancelPrevious(function (value) {
                    if (descriptor.isActive) {
                        trace && console.log("IGNORED SET", trace.targetPath, "TO", value, "ON", object, "BECAUSE", trace.sourcePath, "ALREADY ACTIVE");
                        return;
                    }
                    try {
                        descriptor.isActive = true;
                        trace && console.log("SET", trace.targetPath, "TO", value, "ON", object, "BECAUSE", trace.sourcePath);
                        if (Array.isArray(object) && key >>> 0 === key) {
                            // TODO spec this case
                            object.set(key, value);
                        } else {
                            object[key] = value;
                        }
                    } finally {
                        descriptor.isActive = false;
                    }
                }), source, parameters);
            }), target, parameters);
        }), target, parameters);
    };
}

exports.makeGetBinder = makeGetBinder;
function makeGetBinder(observeCollection, observeKey) {
    return function bindGet(observeValue, source, target, parameters, descriptor, trace) {
        return observeCollection(autoCancelPrevious(function replaceCollection(collection) {
            if (!collection) return;
            return observeKey(autoCancelPrevious(function replaceKey(key) {
                return observeValue(autoCancelPrevious(function replaceValue(value) {
                    if (descriptor.isActive) {
                        trace && console.log("IGNORED SET FOR KEY", key, "OF", trace.targetPath, "TO", value, "ON", collection, "BECAUSE", trace.sourcePath, "ALREADY ACTIVE");
                        return;
                    }
                    try {
                        descriptor.isActive = true;
                        trace && console.log("SET FOR KEY", key, "TO", value, "ON", collection, "BECAUSE", trace.sourcePath);
                        collection.set(key, value);
                    } finally {
                        descriptor.isActive = false;
                    }
                }), source, parameters);
            }), target, parameters);
        }), target, parameters);
    };
}

exports.makeHasBinder = makeHasBinder;
function makeHasBinder(observeSet, observeValue) {
    return function (observeHas, source, target, parameters, descriptor, trace) {
        return observeSet(autoCancelPrevious(function (set) {
            if (!set) return;
            return observeValue(autoCancelPrevious(function (value) {
                return observeHas(autoCancelPrevious(function (has) {
                    // wait for the initial value to be updated by the
                    // other-way binding
                    if (has) { // should be in set
                        if (!(set.has || set.contains).call(set, value)) {
                            trace && console.log("ADD", value, "TO", trace.targetPath, "BECAUSE", trace.sourcePath);
                            set.add(value);
                        }
                    } else { // should not be in set
                        while ((set.has || set.contains).call(set, value)) {
                            trace && console.log("REMOVE", value, "FROM", trace.targetPath, "BECAUSE", trace.sourcePath);
                            (set.remove || set['delete']).call(set, value);
                        }
                    }
                }), target, parameters);
            }), source, parameters);
        }), source, parameters);
    };
}

// a == b <-> c
exports.makeEqualityBinder = makeEqualityBinder;
function makeEqualityBinder(bindLeft, observeRight) {
    return function (observeEquals, source, target, parameters, descriptor, trace) {
        // c
        return observeEquals(autoCancelPrevious(function (equals) {
            if (equals) {
                trace && console.log("BIND", trace.targetPath, "TO", trace.sourcePath);
                // a <-> b
                var cancel = bindLeft(observeRight, source, source, parameters, descriptor, trace);
                return function () {
                    trace && console.log("UNBIND", trace.targetPath, "FROM", trace.sourcePath);
                };
            }
        }), target, parameters);
    };
}

// (a ? b : c) <- d
exports.makeConditionalBinder = makeConditionalBinder;
function makeConditionalBinder(observeCondition, bindConsequent, bindAlternate) {
    return function (observeSource, source, target, parameters, descriptor, trace) {
        // a
        return observeCondition(autoCancelPrevious(function replaceCondition(condition) {
            if (condition == null) return;
            if (condition) {
                // b <- d
                return bindConsequent(observeSource, source, target, parameters, descriptor, trace);
            } else {
                // c <- d
                return bindAlternate(observeSource, source, target, parameters, descriptor, trace);
            }
        }), source, parameters);
    };
}

// a.* <- b.*
exports.makeRangeContentBinder = makeRangeContentBinder;
function makeRangeContentBinder(observeTarget) {
    return function (observeSource, source, target, parameters, descriptor, trace) {
        return observeTarget(autoCancelPrevious(function (target) {
            if (!target) return;
            return observeSource(autoCancelPrevious(function (source) {
                if (!source) {
                    target.clear();
                    return;
                }

                function rangeChange(plus, minus, index) {
                    if (isActive(target))
                        return;
                    if (trace) {
                        console.log("SWAPPING", minus, "FOR", plus, "AT", index);
                    }
                    if (target.swap) {
                        target.swap(index, minus.length, plus);
                    } else if (target.add && (target.remove || target["delete"])) {
                        plus.forEach(target.add, target);
                        minus.forEach(target.remove || target["delete"], target);
                    }
                }
                source.addRangeChangeListener(rangeChange);
                rangeChange(Array.from(source), Array.from(target), 0);
                return once(function () {
                    source.removeRangeChangeListener(rangeChange);
                });
            }), source, parameters);
        }), target, parameters);
    };
}

exports.makeMapContentBinder = makeMapContentBinder;
function makeMapContentBinder(observeTarget) {
    return function (observeSource, source, target, parameters, descriptor, trace) {
        return observeTarget(autoCancelPrevious(function (target) {
            if (!target) return;
            return observeSource(autoCancelPrevious(function (source) {
                if (!source) {
                    target.clear();
                    return;
                }

                function mapChange(value, key) {
                    if (descriptor.isActive) {
                        if (trace) {
                            console.log("IGNORED MAP CHANGE", trace.targetPath, "TO", value, "ON", target, "BECAUSE", trace.sourcePath, "ALREADY ACTIVE");
                        }
                        return;
                    }
                    try {
                        descriptor.isActive = true;
                        if (value === undefined) {
                            if (trace) {
                                trace && console.log("DELETED", trace.targetPath, "FOR KEY", key, "ON", target, "BECAUSE", trace.sourcePath);
                            }
                            if (Array.isArray(target)) {
                                target.splice(key, 1);
                            } else {
                                target["delete"](key);
                            }
                        } else {
                            if (trace) {
                                trace && console.log("SET", trace.targetPath, "FOR KEY", key, "TO", value, "ON", target, "BECAUSE", trace.sourcePath);
                            }
                            target.set(key, value);
                        }
                    } finally {
                        descriptor.isActive = false;
                    }
                }
                target.clear();
                source.forEach(mapChange);
                return source.addMapChangeListener(mapChange);
            }), source, parameters);
        }), target, parameters);
    };
}

// a.reversed() <-> b
exports.makeReversedBinder = makeReversedBinder;
function makeReversedBinder(observeTarget) {
    return function (observeSource, source, target, parameters, descriptor, trace) {
        return observeTarget(autoCancelPrevious(function (target) {
            if (!target) return;
            return observeSource(autoCancelPrevious(function (source) {
                if (!source) {
                    target.clear();
                    return;
                }

                function rangeChange(plus, minus, index) {
                    if (isActive(target))
                        return;
                    var reflected = target.length - index - minus.length;
                    target.swap(reflected, minus.length, plus.reversed());
                }
                source.addRangeChangeListener(rangeChange);
                rangeChange(source, target, 0);
                return once(function () {
                    source.removeRangeChangeListener(rangeChange);
                });
            }), source, parameters);
        }), target, parameters);
    };
}

function isActive(target) {
    return (
        target.getRangeChangeDescriptor &&
        target.getRangeChangeDescriptor().isActive
    );
}

<commit_msg>Fix bug in source/target mismatch of has binding<commit_after>
var Observers = require("./observers");
var autoCancelPrevious = Observers.autoCancelPrevious;
var once = Observers.once;

exports.makePropertyBinder = makePropertyBinder;
function makePropertyBinder(observeObject, observeKey) {
    return function (observeValue, source, target, parameters, descriptor, trace) {
        return observeObject(autoCancelPrevious(function (object) {
            if (!object) return;
            return observeKey(autoCancelPrevious(function (key) {
                return observeValue(autoCancelPrevious(function (value) {
                    if (descriptor.isActive) {
                        trace && console.log("IGNORED SET", trace.targetPath, "TO", value, "ON", object, "BECAUSE", trace.sourcePath, "ALREADY ACTIVE");
                        return;
                    }
                    try {
                        descriptor.isActive = true;
                        trace && console.log("SET", trace.targetPath, "TO", value, "ON", object, "BECAUSE", trace.sourcePath);
                        if (Array.isArray(object) && key >>> 0 === key) {
                            // TODO spec this case
                            object.set(key, value);
                        } else {
                            object[key] = value;
                        }
                    } finally {
                        descriptor.isActive = false;
                    }
                }), source, parameters);
            }), target, parameters);
        }), target, parameters);
    };
}

exports.makeGetBinder = makeGetBinder;
function makeGetBinder(observeCollection, observeKey) {
    return function bindGet(observeValue, source, target, parameters, descriptor, trace) {
        return observeCollection(autoCancelPrevious(function replaceCollection(collection) {
            if (!collection) return;
            return observeKey(autoCancelPrevious(function replaceKey(key) {
                return observeValue(autoCancelPrevious(function replaceValue(value) {
                    if (descriptor.isActive) {
                        trace && console.log("IGNORED SET FOR KEY", key, "OF", trace.targetPath, "TO", value, "ON", collection, "BECAUSE", trace.sourcePath, "ALREADY ACTIVE");
                        return;
                    }
                    try {
                        descriptor.isActive = true;
                        trace && console.log("SET FOR KEY", key, "TO", value, "ON", collection, "BECAUSE", trace.sourcePath);
                        collection.set(key, value);
                    } finally {
                        descriptor.isActive = false;
                    }
                }), source, parameters);
            }), target, parameters);
        }), target, parameters);
    };
}

exports.makeHasBinder = makeHasBinder;
function makeHasBinder(observeSet, observeValue) {
    return function (observeHas, source, target, parameters, descriptor, trace) {
        return observeSet(autoCancelPrevious(function (set) {
            if (!set) return;
            return observeValue(autoCancelPrevious(function (value) {
                return observeHas(autoCancelPrevious(function (has) {
                    // wait for the initial value to be updated by the
                    // other-way binding
                    if (has) { // should be in set
                        if (!(set.has || set.contains).call(set, value)) {
                            trace && console.log("ADD", value, "TO", trace.targetPath, "BECAUSE", trace.sourcePath);
                            set.add(value);
                        }
                    } else { // should not be in set
                        while ((set.has || set.contains).call(set, value)) {
                            trace && console.log("REMOVE", value, "FROM", trace.targetPath, "BECAUSE", trace.sourcePath);
                            (set.remove || set['delete']).call(set, value);
                        }
                    }
                }), source, parameters);
            }), target, parameters);
        }), target, parameters);
    };
}

// a == b <-> c
exports.makeEqualityBinder = makeEqualityBinder;
function makeEqualityBinder(bindLeft, observeRight) {
    return function (observeEquals, source, target, parameters, descriptor, trace) {
        // c
        return observeEquals(autoCancelPrevious(function (equals) {
            if (equals) {
                trace && console.log("BIND", trace.targetPath, "TO", trace.sourcePath);
                // a <-> b
                var cancel = bindLeft(observeRight, source, source, parameters, descriptor, trace);
                return function () {
                    trace && console.log("UNBIND", trace.targetPath, "FROM", trace.sourcePath);
                };
            }
        }), target, parameters);
    };
}

// (a ? b : c) <- d
exports.makeConditionalBinder = makeConditionalBinder;
function makeConditionalBinder(observeCondition, bindConsequent, bindAlternate) {
    return function (observeSource, source, target, parameters, descriptor, trace) {
        // a
        return observeCondition(autoCancelPrevious(function replaceCondition(condition) {
            if (condition == null) return;
            if (condition) {
                // b <- d
                return bindConsequent(observeSource, source, target, parameters, descriptor, trace);
            } else {
                // c <- d
                return bindAlternate(observeSource, source, target, parameters, descriptor, trace);
            }
        }), source, parameters);
    };
}

// a.* <- b.*
exports.makeRangeContentBinder = makeRangeContentBinder;
function makeRangeContentBinder(observeTarget) {
    return function (observeSource, source, target, parameters, descriptor, trace) {
        return observeTarget(autoCancelPrevious(function (target) {
            if (!target) return;
            return observeSource(autoCancelPrevious(function (source) {
                if (!source) {
                    target.clear();
                    return;
                }

                function rangeChange(plus, minus, index) {
                    if (isActive(target))
                        return;
                    if (trace) {
                        console.log("SWAPPING", minus, "FOR", plus, "AT", index);
                    }
                    if (target.swap) {
                        target.swap(index, minus.length, plus);
                    } else if (target.add && (target.remove || target["delete"])) {
                        plus.forEach(target.add, target);
                        minus.forEach(target.remove || target["delete"], target);
                    }
                }
                source.addRangeChangeListener(rangeChange);
                rangeChange(Array.from(source), Array.from(target), 0);
                return once(function () {
                    source.removeRangeChangeListener(rangeChange);
                });
            }), source, parameters);
        }), target, parameters);
    };
}

exports.makeMapContentBinder = makeMapContentBinder;
function makeMapContentBinder(observeTarget) {
    return function (observeSource, source, target, parameters, descriptor, trace) {
        return observeTarget(autoCancelPrevious(function (target) {
            if (!target) return;
            return observeSource(autoCancelPrevious(function (source) {
                if (!source) {
                    target.clear();
                    return;
                }

                function mapChange(value, key) {
                    if (descriptor.isActive) {
                        if (trace) {
                            console.log("IGNORED MAP CHANGE", trace.targetPath, "TO", value, "ON", target, "BECAUSE", trace.sourcePath, "ALREADY ACTIVE");
                        }
                        return;
                    }
                    try {
                        descriptor.isActive = true;
                        if (value === undefined) {
                            if (trace) {
                                trace && console.log("DELETED", trace.targetPath, "FOR KEY", key, "ON", target, "BECAUSE", trace.sourcePath);
                            }
                            if (Array.isArray(target)) {
                                target.splice(key, 1);
                            } else {
                                target["delete"](key);
                            }
                        } else {
                            if (trace) {
                                trace && console.log("SET", trace.targetPath, "FOR KEY", key, "TO", value, "ON", target, "BECAUSE", trace.sourcePath);
                            }
                            target.set(key, value);
                        }
                    } finally {
                        descriptor.isActive = false;
                    }
                }
                target.clear();
                source.forEach(mapChange);
                return source.addMapChangeListener(mapChange);
            }), source, parameters);
        }), target, parameters);
    };
}

// a.reversed() <-> b
exports.makeReversedBinder = makeReversedBinder;
function makeReversedBinder(observeTarget) {
    return function (observeSource, source, target, parameters, descriptor, trace) {
        return observeTarget(autoCancelPrevious(function (target) {
            if (!target) return;
            return observeSource(autoCancelPrevious(function (source) {
                if (!source) {
                    target.clear();
                    return;
                }

                function rangeChange(plus, minus, index) {
                    if (isActive(target))
                        return;
                    var reflected = target.length - index - minus.length;
                    target.swap(reflected, minus.length, plus.reversed());
                }
                source.addRangeChangeListener(rangeChange);
                rangeChange(source, target, 0);
                return once(function () {
                    source.removeRangeChangeListener(rangeChange);
                });
            }), source, parameters);
        }), target, parameters);
    };
}

function isActive(target) {
    return (
        target.getRangeChangeDescriptor &&
        target.getRangeChangeDescriptor().isActive
    );
}

<|endoftext|>


<commit_before>import { fromJS } from 'immutable'
import * as actionTypes from '../actions/actionTypes'

const initialState = fromJS({
  isValid: true,
  canBeAdded: false,
  skipassNumber: '',
  skipass: null
})

export default (state = initialState, action) => {
  switch (action.type) {
    case actionTypes.CHANGE_SEARCH_SKIPASS_NUMBER:
      return state.merge({
        skipassNumber: action.skipassNumber,
        isValid: action.isValid
      })
    case actionTypes.TOGGLE_SKIPASS_CAN_BE_ADDED:
      return state.set('canBeAdded', action.canBeAdded)
    case actionTypes.FETCH_SKIPASS_REQUEST:
      return state.merge({
        isFetching: true,
        canBeAdded: false
      })
    case actionTypes.FETCH_SKIPASS_SUCCESS:
      return state.merge({
        isFetching: false,
        error: null,
        skipass: action.skipass
      })
    case actionTypes.FETCH_SKIPASS_FAILURE:
      return state.merge({
        isFetching: false,
        canBeAdded: false,
        skipass: null,
        error: action.error
      })
    case actionTypes.STORE_SKIPASS_SUCCESS:
      return state.set('canBeAdded', false)
    default:
      return state
  }
}
<commit_msg>import only used actions<commit_after>import { fromJS } from 'immutable'
import {
  CHANGE_SEARCH_SKIPASS_NUMBER,
  TOGGLE_SKIPASS_CAN_BE_ADDED,
  FETCH_SKIPASS_REQUEST,
  FETCH_SKIPASS_SUCCESS,
  FETCH_SKIPASS_FAILURE,
  STORE_SKIPASS_SUCCESS
} from '../actions/actionTypes'

const initialState = fromJS({
  isValid: true,
  canBeAdded: false,
  skipassNumber: '',
  skipass: null
})

export default (state = initialState, action) => {
  switch (action.type) {
    case CHANGE_SEARCH_SKIPASS_NUMBER:
      return state.merge({
        skipassNumber: action.skipassNumber,
        isValid: action.isValid
      })
    case TOGGLE_SKIPASS_CAN_BE_ADDED:
      return state.set('canBeAdded', action.canBeAdded)
    case FETCH_SKIPASS_REQUEST:
      return state.merge({
        isFetching: true,
        canBeAdded: false
      })
    case FETCH_SKIPASS_SUCCESS:
      return state.merge({
        isFetching: false,
        error: null,
        skipass: action.skipass
      })
    case FETCH_SKIPASS_FAILURE:
      return state.merge({
        isFetching: false,
        canBeAdded: false,
        skipass: null,
        error: action.error
      })
    case STORE_SKIPASS_SUCCESS:
      return state.set('canBeAdded', false)
    default:
      return state
  }
}
<|endoftext|>