<commit_before>#!/usr/bin/env node

'use strict';

const program = require('commander');
const fs = require('fs');
const path = require('upath');
const getPort = require('get-port');
const ora = require('ora');
const opn = require('opn');
const { inspect } = require('util');
const colors = require('colors/safe');
const chalk = require('chalk');
const pkgJson = require('./package');
const pingy = require('./pingy');
const init = require('./init');
const { getPingyJson, setPingyJson } = require('./pingyJson');
const Configstore = require('configstore');

const conf = new Configstore(pkgJson.name, {});
// `+` below will convert numeric string to number
if (Number.isFinite(+conf.get('version'))) conf.delete('lastInit');

// Version config so we can easily invalidate it in future
conf.set('version', 2);
conf.set('cliVersion', pkgJson.version);
global.conf = conf;

const pingyAscii = fs.readFileSync(
  require.resolve('./pingy-ascii.txt'),
  'utf8'
);

function run() {
  try {
    console.log(colors.rainbow(pingyAscii));
  } catch (err) {
    console.log(colors.red(`logo ${err}`));
  }

  program.version(pkgJson.version);

  program
    .command('init')
    .description('Initialise a new or existing website using Pingy')
    .option('--yarn', 'Use Yarn instead of NPM for installing packages')
    .option(
      '--global-pingy',
      "Don't install local version of Pingy CLI, use global version instead"
    )
    .option(
      '--ask',
      "Ask for all init options (don't prompt to use existing init options)"
    )
    .action(options => {
      init(options);
    });

  program
    .command('scaffold <url>')
    .description(
      [
        'Scaffold a new website using a third-party project template',
        '',
        '  <url> can be:',
        '',
        "    Alias: 'bootstrap' (View alias registry at: https://github.com/pingyhq/scaffolds)",
        "    Git URL: 'https://github.com/pingyhq/pingy-scaffold-bootstrap.git'",
        "    Shorthand GitHub URL: 'pingyhq/bootstrap'",
        "    Filesystem path: '/Users/dave/code/pingy-scaffolds/bootstrap'"
      ].join('\n')
    )
    .option('--yarn', 'Use Yarn instead of NPM for installing packages')
    .option(
      '--global-pingy',
      "Don't install local version of Pingy CLI, use global version instead"
    )
    .action((url, options) => {
      init.scaffold(url, options);
    });

  program
    .command('dev')
    .description('Serve local development version of website')
    .option(
      '-p, --port [port]',
      'Use chosen port (otherwise random port will be used)'
    )
    .option('-q, --no-open', "Don't automatically launch site in web browser")
    .action(options => {
      const pingyJson = getPingyJson();
      if (!pingyJson) return;
      const jsonPort = pingyJson.json.port;
      const customPort = Number(options.port || jsonPort);
      const port = customPort || null;
      getPort(port).then(freePort => {
        if (typeof port === 'number' && port !== freePort) {
          console.log(
            chalk.red.bold(
              `Port ${port} is not available, using random port ${
                freePort
              } instead\n`
            )
          );
        }
        const serveOptions = { port: freePort };
        const { url } = pingy.serveSite(
          pingyJson.dir,
          Object.assign({}, pingyJson.json, serveOptions)
        );
        console.log(`Serving at ${url}`);
        if (options.open) opn(url, { wait: false });
        if (jsonPort !== freePort) setPingyJson(serveOptions);
      });
    });

  program
    .command('export')
    .description('Export website to a folder for distribution')
    .action(() => {
      const pingyJson = getPingyJson();
      if (!pingyJson) return;
      const inputDir = pingyJson.dir;
      if (!pingyJson.json.exportDir) {
        console.error(
          chalk.red.bold(
            'Please add an "exportDir" property to your pingy.json file.'
          )
        );
        return;
      }
      const outputDir = path.join(inputDir, pingyJson.json.exportDir);

      const exportingSpinner = ora(
        `Exporting to ${chalk.bold(outputDir)}`
      ).start();
      const exporting = pingy.exportSite(inputDir, outputDir, pingyJson.json);

      const spinners = {};
      let currentFile = null;

      exporting.then(
        () => {
          exportingSpinner.succeed();
          setTimeout(() => {
            process.exit(0);
          }, 10);
        },
        err => {
          if (currentFile) spinners[currentFile].fail();
          console.log(inspect(err));
          exportingSpinner.fail(`Failed export to ${chalk.bold(outputDir)}`);
          setTimeout(() => {
            process.exit(1);
          }, 10);
        }
      );

      exporting.events.on('compile-start', file => {
        spinners[file.path] = ora(`Compiling ${file.name}`).start();
        currentFile = file.path;
      });
      exporting.events.on('compile-done', file => {
        spinners[file.path].succeed();
        currentFile = null;
      });
    });

  if (!process.argv.slice(2).length) program.outputHelp();

  program.parse(process.argv);
}

module.exports = {
  run,
};
<commit_msg>[cli] fix getting old config version<commit_after>#!/usr/bin/env node

'use strict';

const program = require('commander');
const fs = require('fs');
const path = require('upath');
const getPort = require('get-port');
const ora = require('ora');
const opn = require('opn');
const { inspect } = require('util');
const colors = require('colors/safe');
const chalk = require('chalk');
const pkgJson = require('./package');
const pingy = require('./pingy');
const init = require('./init');
const { getPingyJson, setPingyJson } = require('./pingyJson');
const Configstore = require('configstore');

const conf = new Configstore(pkgJson.name, {});
// `+` below will convert numeric string to number
if (!Number.isFinite(+conf.get('version'))) conf.delete('lastInit');

// Version config so we can easily invalidate it in future
conf.set('version', 2);
conf.set('cliVersion', pkgJson.version);
global.conf = conf;

const pingyAscii = fs.readFileSync(
  require.resolve('./pingy-ascii.txt'),
  'utf8'
);

function run() {
  try {
    console.log(colors.rainbow(pingyAscii));
  } catch (err) {
    console.log(colors.red(`logo ${err}`));
  }

  program.version(pkgJson.version);

  program
    .command('init')
    .description('Initialise a new or existing website using Pingy')
    .option('--yarn', 'Use Yarn instead of NPM for installing packages')
    .option(
      '--global-pingy',
      "Don't install local version of Pingy CLI, use global version instead"
    )
    .option(
      '--ask',
      "Ask for all init options (don't prompt to use existing init options)"
    )
    .action(options => {
      init(options);
    });

  program
    .command('scaffold <url>')
    .description(
      [
        'Scaffold a new website using a third-party project template',
        '',
        '  <url> can be:',
        '',
        "    Alias: 'bootstrap' (View alias registry at: https://github.com/pingyhq/scaffolds)",
        "    Git URL: 'https://github.com/pingyhq/pingy-scaffold-bootstrap.git'",
        "    Shorthand GitHub URL: 'pingyhq/bootstrap'",
        "    Filesystem path: '/Users/dave/code/pingy-scaffolds/bootstrap'"
      ].join('\n')
    )
    .option('--yarn', 'Use Yarn instead of NPM for installing packages')
    .option(
      '--global-pingy',
      "Don't install local version of Pingy CLI, use global version instead"
    )
    .action((url, options) => {
      init.scaffold(url, options);
    });

  program
    .command('dev')
    .description('Serve local development version of website')
    .option(
      '-p, --port [port]',
      'Use chosen port (otherwise random port will be used)'
    )
    .option('-q, --no-open', "Don't automatically launch site in web browser")
    .action(options => {
      const pingyJson = getPingyJson();
      if (!pingyJson) return;
      const jsonPort = pingyJson.json.port;
      const customPort = Number(options.port || jsonPort);
      const port = customPort || null;
      getPort(port).then(freePort => {
        if (typeof port === 'number' && port !== freePort) {
          console.log(
            chalk.red.bold(
              `Port ${port} is not available, using random port ${
                freePort
              } instead\n`
            )
          );
        }
        const serveOptions = { port: freePort };
        const { url } = pingy.serveSite(
          pingyJson.dir,
          Object.assign({}, pingyJson.json, serveOptions)
        );
        console.log(`Serving at ${url}`);
        if (options.open) opn(url, { wait: false });
        if (jsonPort !== freePort) setPingyJson(serveOptions);
      });
    });

  program
    .command('export')
    .description('Export website to a folder for distribution')
    .action(() => {
      const pingyJson = getPingyJson();
      if (!pingyJson) return;
      const inputDir = pingyJson.dir;
      if (!pingyJson.json.exportDir) {
        console.error(
          chalk.red.bold(
            'Please add an "exportDir" property to your pingy.json file.'
          )
        );
        return;
      }
      const outputDir = path.join(inputDir, pingyJson.json.exportDir);

      const exportingSpinner = ora(
        `Exporting to ${chalk.bold(outputDir)}`
      ).start();
      const exporting = pingy.exportSite(inputDir, outputDir, pingyJson.json);

      const spinners = {};
      let currentFile = null;

      exporting.then(
        () => {
          exportingSpinner.succeed();
          setTimeout(() => {
            process.exit(0);
          }, 10);
        },
        err => {
          if (currentFile) spinners[currentFile].fail();
          console.log(inspect(err));
          exportingSpinner.fail(`Failed export to ${chalk.bold(outputDir)}`);
          setTimeout(() => {
            process.exit(1);
          }, 10);
        }
      );

      exporting.events.on('compile-start', file => {
        spinners[file.path] = ora(`Compiling ${file.name}`).start();
        currentFile = file.path;
      });
      exporting.events.on('compile-done', file => {
        spinners[file.path].succeed();
        currentFile = null;
      });
    });

  if (!process.argv.slice(2).length) program.outputHelp();

  program.parse(process.argv);
}

module.exports = {
  run,
};
<|endoftext|>


<commit_before>const config = require('./config')
const BugsnagReport = require('./report')
const BugsnagBreadcrumb = require('./breadcrumb')
const BugsnagSession = require('./session')
const { map, includes, isArray } = require('./lib/es-utils')
const inferReleaseStage = require('./lib/infer-release-stage')
const isError = require('iserror')
const some = require('./lib/async-some')
const runBeforeSend = require('./lib/run-before-send')

const LOG_USAGE_ERR_PREFIX = `Usage error.`
const REPORT_USAGE_ERR_PREFIX = `Bugsnag usage error.`

class BugsnagClient {
  constructor (notifier) {
    if (!notifier || !notifier.name || !notifier.version || !notifier.url) {
      throw new Error('`notifier` argument is required')
    }

    // notifier id
    this.notifier = notifier

    // configure() should be called before notify()
    this._configured = false

    // intialise opts and config
    this._opts = {}
    this.config = {}

    // // i/o
    this._delivery = { sendSession: () => {}, sendReport: () => {} }
    this._logger = { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} }

    // plugins
    this._plugins = {}

    this._session = null

    this.breadcrumbs = []

    // setable props
    this.app = {}
    this.context = undefined
    this.device = undefined
    this.metaData = undefined
    this.request = undefined
    this.user = {}

    // expose internal constructors
    this.BugsnagClient = BugsnagClient
    this.BugsnagReport = BugsnagReport
    this.BugsnagBreadcrumb = BugsnagBreadcrumb
    this.BugsnagSession = BugsnagSession
  }

  setOptions (opts) {
    this._opts = { ...this._opts, ...opts }
  }

  configure (partialSchema = config.schema) {
    const conf = config.mergeDefaults(this._opts, partialSchema)
    const validity = config.validate(conf, partialSchema)

    if (!validity.valid === true) throw new Error(generateConfigErrorMessage(validity.errors))

    // update and elevate some special options if they were passed in at this point
    if (typeof conf.beforeSend === 'function') conf.beforeSend = [ conf.beforeSend ]
    if (conf.appVersion) this.app.version = conf.appVersion
    if (conf.appType) this.app.type = conf.appType
    if (conf.metaData) this.metaData = conf.metaData
    if (conf.user) this.user = conf.user
    if (conf.logger) this.logger(conf.logger)

    // merge with existing config
    this.config = { ...this.config, ...conf }

    this._configured = true

    return this
  }

  use (plugin, ...args) {
    if (!this._configured) throw new Error('client not configured')
    if (plugin.configSchema) this.configure(plugin.configSchema)
    const result = plugin.init(this, ...args)
    // JS objects are not the safest way to store arbitrarily keyed values,
    // so bookend the key with some characters that prevent tampering with
    // stuff like __proto__ etc. (only store the result if the plugin had a
    // name)
    if (plugin.name) this._plugins[`~${plugin.name}~`] = result
    return this
  }

  getPlugin (name) {
    return this._plugins[`~${name}~`]
  }

  delivery (d) {
    this._delivery = d
    return this
  }

  logger (l, sid) {
    this._logger = l
    return this
  }

  sessionDelegate (s) {
    this._sessionDelegate = s
    return this
  }

  startSession () {
    if (!this._sessionDelegate) {
      this._logger.warn('No session implementation is installed')
      return this
    }
    return this._sessionDelegate.startSession(this)
  }

  leaveBreadcrumb (name, metaData, type, timestamp) {
    if (!this._configured) throw new Error('client not configured')

    // coerce bad values so that the defaults get set
    name = name || undefined
    type = typeof type === 'string' ? type : undefined
    timestamp = typeof timestamp === 'string' ? timestamp : undefined
    metaData = typeof metaData === 'object' && metaData !== null ? metaData : undefined

    // if no name and no metaData, usefulness of this crumb is questionable at best so discard
    if (typeof name !== 'string' && !metaData) return

    const crumb = new BugsnagBreadcrumb(name, metaData, type, timestamp)

    // push the valid crumb onto the queue and maintain the length
    this.breadcrumbs.push(crumb)
    if (this.breadcrumbs.length > this.config.maxBreadcrumbs) {
      this.breadcrumbs = this.breadcrumbs.slice(this.breadcrumbs.length - this.config.maxBreadcrumbs)
    }

    return this
  }

  notify (error, opts = {}, cb = () => {}) {
    if (!this._configured) throw new Error('client not configured')

    // releaseStage can be set via config.releaseStage or client.app.releaseStage
    const releaseStage = inferReleaseStage(this)

    // ensure we have an error (or a reasonable object representation of an error)
    let { err, errorFramesToSkip, _opts } = normaliseError(error, opts, this._logger)
    if (_opts) opts = _opts

    // if we have something falsey at this point, report usage error
    if (!err) {
      const msg = generateNotifyUsageMessage('nothing')
      this._logger.warn(`${LOG_USAGE_ERR_PREFIX} ${msg}`)
      err = new Error(`${REPORT_USAGE_ERR_PREFIX} ${msg}`)
    }

    // ensure opts is an object
    if (typeof opts !== 'object' || opts === null) opts = {}

    // create a report from the error, if it isn't one already
    const report = BugsnagReport.ensureReport(err, errorFramesToSkip, 1)

    report.app = { ...{ releaseStage }, ...report.app, ...this.app }
    report.context = report.context || opts.context || this.context || undefined
    report.device = { ...report.device, ...this.device, ...opts.device }
    report.request = { ...report.request, ...this.request, ...opts.request }
    report.user = { ...report.user, ...this.user, ...opts.user }
    report.metaData = { ...report.metaData, ...this.metaData, ...opts.metaData }
    report.breadcrumbs = this.breadcrumbs.slice(0)

    if (this._session) {
      this._session.trackError(report)
      report.session = this._session
    }

    // set severity if supplied
    if (opts.severity !== undefined) {
      report.severity = opts.severity
      report._handledState.severityReason = { type: 'userSpecifiedSeverity' }
    }

    // exit early if the reports should not be sent on the current releaseStage
    if (isArray(this.config.notifyReleaseStages) && !includes(this.config.notifyReleaseStages, releaseStage)) {
      this._logger.warn(`Report not sent due to releaseStage/notifyReleaseStages configuration`)
      return false
    }

    const originalSeverity = report.severity

    const beforeSend = [].concat(opts.beforeSend).concat(this.config.beforeSend)
    const onBeforeSendErr = err => {
      this._logger.error(`Error occurred in beforeSend callback, continuing anyway…`)
      this._logger.error(err)
    }

    some(beforeSend, runBeforeSend(report, onBeforeSendErr), (err, preventSend) => {
      if (err) onBeforeSendErr(err)

      if (preventSend) {
        this._logger.debug(`Report not sent due to beforeSend callback`)
        return false
      }

      // only leave a crumb for the error if actually got sent
      if (this.config.autoBreadcrumbs) {
        this.leaveBreadcrumb(report.errorClass, {
          errorClass: report.errorClass,
          errorMessage: report.errorMessage,
          severity: report.severity,
          stacktrace: report.stacktrace
        }, 'error')
      }

      if (originalSeverity !== report.severity) {
        report._handledState.severityReason = { type: 'userCallbackSetSeverity' }
      }

      this._delivery.sendReport(this._logger, this.config, {
        apiKey: report.apiKey || this.config.apiKey,
        notifier: this.notifier,
        events: [ report ]
      }, (err) => cb(err, report))
    })
  }
}

const normaliseError = (error, opts, logger) => {
  let err
  let errorFramesToSkip = 0
  let _opts
  switch (typeof error) {
    case 'string':
      if (typeof opts === 'string') {
        // ≤v3 used to have a notify('ErrorName', 'Error message') interface
        // report usage/deprecation errors if this function is called like that
        const msg = generateNotifyUsageMessage('string/string')
        logger.warn(`${LOG_USAGE_ERR_PREFIX} ${msg}`)
        err = new Error(`${REPORT_USAGE_ERR_PREFIX} ${msg}`)
        _opts = { metaData: { notifier: { notifyArgs: [ error, opts ] } } }
      } else {
        err = new Error(String(error))
        errorFramesToSkip += 2
      }
      break
    case 'number':
    case 'boolean':
      err = new Error(String(error))
      break
    case 'function':
      const msg = generateNotifyUsageMessage('function')
      logger.warn(`${LOG_USAGE_ERR_PREFIX} ${msg}`)
      err = new Error(`${REPORT_USAGE_ERR_PREFIX} ${msg}`)
      break
    case 'object':
      if (error !== null && (isError(error) || error.__isBugsnagReport)) {
        err = error
      } else if (error !== null && hasNecessaryFields(error)) {
        err = new Error(error.message || error.errorMessage)
        err.name = error.name || error.errorClass
        errorFramesToSkip += 2
      } else {
        const msg = generateNotifyUsageMessage('unsupported object')
        logger.warn(`${LOG_USAGE_ERR_PREFIX} ${msg}`)
        err = new Error(`${REPORT_USAGE_ERR_PREFIX} ${msg}`)
      }
      break
  }
  return { err, errorFramesToSkip, _opts }
}

const hasNecessaryFields = error =>
  (typeof error.name === 'string' || typeof error.errorClass === 'string') &&
  (typeof error.message === 'string' || typeof error.errorMessage === 'string')

const generateConfigErrorMessage = errors =>
  `Bugsnag configuration error\n${map(errors, (err) => `"${err.key}" ${err.message} \n    got ${stringify(err.value)}`).join('\n\n')}`

const generateNotifyUsageMessage = actual =>
  `notify() expected error/opts parameters, got ${actual}`

const stringify = val => typeof val === 'object' ? JSON.stringify(val) : String(val)

module.exports = BugsnagClient
<commit_msg>feat(core): Allow client.notify to be passed directly to callbacks<commit_after>const config = require('./config')
const BugsnagReport = require('./report')
const BugsnagBreadcrumb = require('./breadcrumb')
const BugsnagSession = require('./session')
const { map, includes, isArray } = require('./lib/es-utils')
const inferReleaseStage = require('./lib/infer-release-stage')
const isError = require('iserror')
const some = require('./lib/async-some')
const runBeforeSend = require('./lib/run-before-send')

const LOG_USAGE_ERR_PREFIX = `Usage error.`
const REPORT_USAGE_ERR_PREFIX = `Bugsnag usage error.`

class BugsnagClient {
  constructor (notifier) {
    if (!notifier || !notifier.name || !notifier.version || !notifier.url) {
      throw new Error('`notifier` argument is required')
    }

    // notifier id
    this.notifier = notifier

    // configure() should be called before notify()
    this._configured = false

    // intialise opts and config
    this._opts = {}
    this.config = {}

    // // i/o
    this._delivery = { sendSession: () => {}, sendReport: () => {} }
    this._logger = { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} }

    // plugins
    this._plugins = {}

    this._session = null

    this.breadcrumbs = []

    // setable props
    this.app = {}
    this.context = undefined
    this.device = undefined
    this.metaData = undefined
    this.request = undefined
    this.user = {}

    // expose internal constructors
    this.BugsnagClient = BugsnagClient
    this.BugsnagReport = BugsnagReport
    this.BugsnagBreadcrumb = BugsnagBreadcrumb
    this.BugsnagSession = BugsnagSession

    var self = this
    var notify = this.notify
    this.notify = function () {
      return notify.apply(self, arguments)
    }
  }

  setOptions (opts) {
    this._opts = { ...this._opts, ...opts }
  }

  configure (partialSchema = config.schema) {
    const conf = config.mergeDefaults(this._opts, partialSchema)
    const validity = config.validate(conf, partialSchema)

    if (!validity.valid === true) throw new Error(generateConfigErrorMessage(validity.errors))

    // update and elevate some special options if they were passed in at this point
    if (typeof conf.beforeSend === 'function') conf.beforeSend = [ conf.beforeSend ]
    if (conf.appVersion) this.app.version = conf.appVersion
    if (conf.appType) this.app.type = conf.appType
    if (conf.metaData) this.metaData = conf.metaData
    if (conf.user) this.user = conf.user
    if (conf.logger) this.logger(conf.logger)

    // merge with existing config
    this.config = { ...this.config, ...conf }

    this._configured = true

    return this
  }

  use (plugin, ...args) {
    if (!this._configured) throw new Error('client not configured')
    if (plugin.configSchema) this.configure(plugin.configSchema)
    const result = plugin.init(this, ...args)
    // JS objects are not the safest way to store arbitrarily keyed values,
    // so bookend the key with some characters that prevent tampering with
    // stuff like __proto__ etc. (only store the result if the plugin had a
    // name)
    if (plugin.name) this._plugins[`~${plugin.name}~`] = result
    return this
  }

  getPlugin (name) {
    return this._plugins[`~${name}~`]
  }

  delivery (d) {
    this._delivery = d
    return this
  }

  logger (l, sid) {
    this._logger = l
    return this
  }

  sessionDelegate (s) {
    this._sessionDelegate = s
    return this
  }

  startSession () {
    if (!this._sessionDelegate) {
      this._logger.warn('No session implementation is installed')
      return this
    }
    return this._sessionDelegate.startSession(this)
  }

  leaveBreadcrumb (name, metaData, type, timestamp) {
    if (!this._configured) throw new Error('client not configured')

    // coerce bad values so that the defaults get set
    name = name || undefined
    type = typeof type === 'string' ? type : undefined
    timestamp = typeof timestamp === 'string' ? timestamp : undefined
    metaData = typeof metaData === 'object' && metaData !== null ? metaData : undefined

    // if no name and no metaData, usefulness of this crumb is questionable at best so discard
    if (typeof name !== 'string' && !metaData) return

    const crumb = new BugsnagBreadcrumb(name, metaData, type, timestamp)

    // push the valid crumb onto the queue and maintain the length
    this.breadcrumbs.push(crumb)
    if (this.breadcrumbs.length > this.config.maxBreadcrumbs) {
      this.breadcrumbs = this.breadcrumbs.slice(this.breadcrumbs.length - this.config.maxBreadcrumbs)
    }

    return this
  }

  notify (error, opts = {}, cb = () => {}) {
    if (!this._configured) throw new Error('client not configured')

    // releaseStage can be set via config.releaseStage or client.app.releaseStage
    const releaseStage = inferReleaseStage(this)

    // ensure we have an error (or a reasonable object representation of an error)
    let { err, errorFramesToSkip, _opts } = normaliseError(error, opts, this._logger)
    if (_opts) opts = _opts

    // if we have something falsey at this point, report usage error
    if (!err) {
      const msg = generateNotifyUsageMessage('nothing')
      this._logger.warn(`${LOG_USAGE_ERR_PREFIX} ${msg}`)
      err = new Error(`${REPORT_USAGE_ERR_PREFIX} ${msg}`)
    }

    // ensure opts is an object
    if (typeof opts !== 'object' || opts === null) opts = {}

    // create a report from the error, if it isn't one already
    const report = BugsnagReport.ensureReport(err, errorFramesToSkip, 1)

    report.app = { ...{ releaseStage }, ...report.app, ...this.app }
    report.context = report.context || opts.context || this.context || undefined
    report.device = { ...report.device, ...this.device, ...opts.device }
    report.request = { ...report.request, ...this.request, ...opts.request }
    report.user = { ...report.user, ...this.user, ...opts.user }
    report.metaData = { ...report.metaData, ...this.metaData, ...opts.metaData }
    report.breadcrumbs = this.breadcrumbs.slice(0)

    if (this._session) {
      this._session.trackError(report)
      report.session = this._session
    }

    // set severity if supplied
    if (opts.severity !== undefined) {
      report.severity = opts.severity
      report._handledState.severityReason = { type: 'userSpecifiedSeverity' }
    }

    // exit early if the reports should not be sent on the current releaseStage
    if (isArray(this.config.notifyReleaseStages) && !includes(this.config.notifyReleaseStages, releaseStage)) {
      this._logger.warn(`Report not sent due to releaseStage/notifyReleaseStages configuration`)
      return false
    }

    const originalSeverity = report.severity

    const beforeSend = [].concat(opts.beforeSend).concat(this.config.beforeSend)
    const onBeforeSendErr = err => {
      this._logger.error(`Error occurred in beforeSend callback, continuing anyway…`)
      this._logger.error(err)
    }

    some(beforeSend, runBeforeSend(report, onBeforeSendErr), (err, preventSend) => {
      if (err) onBeforeSendErr(err)

      if (preventSend) {
        this._logger.debug(`Report not sent due to beforeSend callback`)
        return false
      }

      // only leave a crumb for the error if actually got sent
      if (this.config.autoBreadcrumbs) {
        this.leaveBreadcrumb(report.errorClass, {
          errorClass: report.errorClass,
          errorMessage: report.errorMessage,
          severity: report.severity,
          stacktrace: report.stacktrace
        }, 'error')
      }

      if (originalSeverity !== report.severity) {
        report._handledState.severityReason = { type: 'userCallbackSetSeverity' }
      }

      this._delivery.sendReport(this._logger, this.config, {
        apiKey: report.apiKey || this.config.apiKey,
        notifier: this.notifier,
        events: [ report ]
      }, (err) => cb(err, report))
    })
  }
}

const normaliseError = (error, opts, logger) => {
  let err
  let errorFramesToSkip = 0
  let _opts
  switch (typeof error) {
    case 'string':
      if (typeof opts === 'string') {
        // ≤v3 used to have a notify('ErrorName', 'Error message') interface
        // report usage/deprecation errors if this function is called like that
        const msg = generateNotifyUsageMessage('string/string')
        logger.warn(`${LOG_USAGE_ERR_PREFIX} ${msg}`)
        err = new Error(`${REPORT_USAGE_ERR_PREFIX} ${msg}`)
        _opts = { metaData: { notifier: { notifyArgs: [ error, opts ] } } }
      } else {
        err = new Error(String(error))
        errorFramesToSkip += 2
      }
      break
    case 'number':
    case 'boolean':
      err = new Error(String(error))
      break
    case 'function':
      const msg = generateNotifyUsageMessage('function')
      logger.warn(`${LOG_USAGE_ERR_PREFIX} ${msg}`)
      err = new Error(`${REPORT_USAGE_ERR_PREFIX} ${msg}`)
      break
    case 'object':
      if (error !== null && (isError(error) || error.__isBugsnagReport)) {
        err = error
      } else if (error !== null && hasNecessaryFields(error)) {
        err = new Error(error.message || error.errorMessage)
        err.name = error.name || error.errorClass
        errorFramesToSkip += 2
      } else {
        const msg = generateNotifyUsageMessage('unsupported object')
        logger.warn(`${LOG_USAGE_ERR_PREFIX} ${msg}`)
        err = new Error(`${REPORT_USAGE_ERR_PREFIX} ${msg}`)
      }
      break
  }
  return { err, errorFramesToSkip, _opts }
}

const hasNecessaryFields = error =>
  (typeof error.name === 'string' || typeof error.errorClass === 'string') &&
  (typeof error.message === 'string' || typeof error.errorMessage === 'string')

const generateConfigErrorMessage = errors =>
  `Bugsnag configuration error\n${map(errors, (err) => `"${err.key}" ${err.message} \n    got ${stringify(err.value)}`).join('\n\n')}`

const generateNotifyUsageMessage = actual =>
  `notify() expected error/opts parameters, got ${actual}`

const stringify = val => typeof val === 'object' ? JSON.stringify(val) : String(val)

module.exports = BugsnagClient
<|endoftext|>


<commit_before>var doEncryptDomains = [
    "facebook.com",
    "mail.google.com"
];

var EMAIL_WINDOW_SELECTOR = '.I5';
var TEXTAREA_SELECTOR = '.Am[role="textbox"][contenteditable="true"]';
var TOOLBAR_SELECT = ".n1tfz";
var FORMAT_BUTTON_SELECT = ".az5";

function usernameGetter($usernameField) {
    return $usernameField.find('*').filter(function(i, elm) {
        return $(elm).attr('email') !== undefined;
    }).toArray().map(function(emailSpan) {
        return $(emailSpan).attr('email');
    });
}

$(function() {
    if (doEncryptDomains.indexOf(document.domain) < 0) {
        return
    }

    $(document).on('DOMNodeInserted', function(e) {
        if ($(e.target).closest(NON_FAKEBLOCK_TEXTAREA_SELECTOR).length > 0) {
            //don't allow insert events for objects appended to the unencrypted area to propagate
            return;
        } else if ($(e.target).parent().hasClass(NON_FAKEBLOCK_TEXTAREA_CLASS) &&
            $(e.target).hasClass('gmail_extra')) {
            //if an old email thread gets revealed in the original textarea
            //append it to associated unencrypted area instead and reencrypt
            var $unencryptedArea = $(e.target).parent().data('unencryptedArea');
            $unencryptedArea.append($(e.target));
            return;
        }

        //gmail makes the first matching textarea found into the compose textarea
        //move the unencrypted textarea after the encrypted textarea
        if ($('.aO7').children(TEXTAREA_SELECTOR).length == 1) {
            $('.aO7').append($(NON_FAKEBLOCK_TEXTAREA_SELECTOR));
        }

        //make overlay if the email window and the associated textarea are found, and if doesn't already have overlay
        var overlayable = $(e.target).closest(EMAIL_WINDOW_SELECTOR).find(TEXTAREA_SELECTOR);
        var email_toolbar = $(e.target).closest(EMAIL_WINDOW_SELECTOR).find(TOOLBAR_SELECT);

        if (overlayable.length > 0) {
            if (! overlayable.hasClass('has-overlay')) {
                var $email = overlayable.closest(EMAIL_WINDOW_SELECTOR);
                makeOverlay($email);
            } else if (overlayable.hasClass('pre-draft')) {
                if (overlayable.data('draft') === overlayable.html() ||
                    overlayable.html().indexOf('class="gmail_extra"') >= 0) {
                    //if draft has finished loading
                    overlayable.data('unencryptedArea').html(
                        overlayable.data('draft')
                    );
                    overlayable.removeClass('pre-draft');
                } else {
                    //draft hasn't finished loading into compose window
                    overlayable.data('draft', overlayable.html());
                }

            }
            if (email_toolbar.length > 0) {
                var format_button = email_toolbar.find(FORMAT_BUTTON_SELECT);
                if (!(format_button.hasClass("yupper"))) {
                    format_button.addClass("yupper");
                    var $ptButton = $('
' +
                    '
' +
                    '        ' +
                    '
' +
                    '
' +
                    '        ' +
                    '
' +
                    '
');
                    format_button.before($ptButton);
                    $ptButton.find(".pt-button").css(
                        {
//                            "top":" 1px",
                            "padding":" 10px 7px 5px 8px",
                            "width":" 22px",
                            "height":" 25px",
                            "border":" 1px solid #f5f5f5",
                            "border-top":"1px solid rgba(128, 128, 128, 0.32)",
                            "float":"right"
                        });
                    $ptButton.find('.pt-button').hover(
//                        function(){ $(this).css('border', '1px solid gray') },
//                        function(){ $(this).css(
//                            {'border':'1px solid #f5f5f5',
//                            "border-top":"1px solid rgba(128, 128, 128, 0.32)"
//                            }) }
                        function(){ $(this).css('cursor', 'pointer') }
                    );
                    $ptButton.find('.pt_unlocked').click(function(e) { // they clicked the snake, stuff should be encrypted now
                        togglePtButton($(this));
                    });
                    $ptButton.find('.pt_locked').click(function(e) { // they clicked the lock, stuff should be unencrypted now
                        togglePtButton($(this));
                    });
                    bindPtButtons(overlayable, $ptButton);
                }
            }
        }

        //update usernames if (possibly) a new username has been added
        usernameHandler(e.target);
    });

    $(document).on('DOMNodeRemoved', function(e) {
        usernameHandler(e.target, true);
    });
});

function bindPtButtons($encryptedArea, $ptButtons) {
    /*
    initialize parseltongue buttons
    binds parseltongue buttons to the proper unencrypted textarea
    sets the default encrypt/decrypt option based on usermeta.defaultEncrypt
    shows or hides all parseltongue buttons if can encrypt for current usernames
    */
    var $unencryptedArea = $encryptedArea.data('unencryptedArea');
    $unencryptedArea.data('ptButtons', $ptButtons);
    $ptButtons.data('unencryptedArea', $unencryptedArea);
    requestDefaultEncrypt($ptButtons);
    requestCanEncryptFor($unencryptedArea);
}

function togglePtButton($ptButton) {
    $ptButton.hide();
    $ptButton.siblings().show();
    encryptHandler($ptButton.closest('.pt-buttons-wrapper').data('unencryptedArea'));
}

function makeOverlay($email) {
    /* given the top-level email window, make an overlay for it */

    var $textarea = $email.find(TEXTAREA_SELECTOR);
    var $unencryptedArea = makeOverlayHtml($textarea);

    $textarea.after($unencryptedArea);
    $textarea.hide();

    //set doencrypt to false since usernames should be empty when email first opens
    $unencryptedArea.data('doEncrypt', false);

    $unencryptedArea.keyup(function(e) {
        encryptHandler($(this));
    });
    $textarea.addClass(FAKEBLOCK_TEXTAREA_CLASS);
    $textarea.addClass('has-overlay pre-draft');
    $textarea.data('unencryptedArea', $unencryptedArea);
}

function makeOverlayHtml($textarea) {
    /* lots of shit here to make the overlay look like the original */

    var $unencryptedArea = $textarea.clone();
    var unencrypted_id = $unencryptedArea.prop('id') + '_unencrypted';
    $unencryptedArea.attr({
        id : unencrypted_id,
        role : 'textbox',
        contenteditable : true
    });

    $unencryptedArea.click(function(evt) {
        evt.stopPropagation();
    }).focus(function(evt) {
            evt.stopPropagation();
        });

    $unencryptedArea.css({
        'min-height' : '85px',
        'direction' : 'ltr'
    });
    $unencryptedArea.addClass(['Al', 'LW-avf', NON_FAKEBLOCK_TEXTAREA_CLASS].join(' '));

    $unencryptedArea.data('encryptedArea', $textarea);

    return $unencryptedArea;
}

function encryptHandler($unencryptedArea) {
    /*
     handler for keyup in an unencrypted area
     uses doEncrypt in unencrypted area's data to check if should update with ciphertext or plaintext
     */
    var $encryptedArea = $unencryptedArea.data('encryptedArea');
    var message = $unencryptedArea.html();
    var usernames = $unencryptedArea.data('usernames');

    //if can find button, check if button is visible and set to lock
    //otherwise do not encrypt
    var $ptButton = $unencryptedArea.data('ptButtons');
    var canEncryptButton = $ptButton ?
        $ptButton.find('.pt_locked').css('display') !== 'none' && $ptButton.css('display') !== 'none' :
        false;
    if ($unencryptedArea.data('doEncrypt') && canEncryptButton) {
        requestEncrypt($encryptedArea, message, usernames);
    } else {
        $encryptedArea.html(message);
    }

}

function usernameHandler(emailSpan, deleteEmail) {
    /*
     handler for an email address getting added or removed from the addressees
     updates the usernames list for the appropriate unencrypted textarea
     */
    var emailSpans = $(emailSpan).children().add($(emailSpan)).filter(function(i, elm) {
        return $(elm).attr('email') !== undefined;
    });
    //TODO: what happens if more than one email span? is this possible?
    //TODO: normalize emails in frontend too?
    if (emailSpans.length == 1) {
        $usernameField = emailSpans.parent().parent();
        var usernameToDelete = null;
        if (deleteEmail) {
            usernameToDelete = emailSpans.attr('email');
        }
        updateUsernames($usernameField, usernameToDelete);
    }
}

function updateUsernames($usernameField, usernameToDelete) {
    /*
     call to check if the usernames are all still valid parseltongue users
     if yes/no, update the doEncrypt field of the corresponding unencrypted textarea
     */

    //check if username field has been associated with a textarea yet
    var $unencryptedArea = $usernameField.data('unencryptedArea');
    if ($unencryptedArea === undefined) {
        $unencryptedArea = $usernameField.closest(EMAIL_WINDOW_SELECTOR).find(
            NON_FAKEBLOCK_TEXTAREA_SELECTOR
        );
        $usernameField.data('unencryptedArea', $unencryptedArea);
    }

    var usernames = usernameGetter($usernameField);
    var usernameDeleteIndex = usernames.indexOf(usernameToDelete);
    if (usernameDeleteIndex >= 0) {
        usernames.splice(usernameDeleteIndex, 1);
    }
    $unencryptedArea.data('usernames', usernames);
    requestCanEncryptFor($unencryptedArea);
}

function requestEncrypt($encryptedArea, message, usernames) {
    /*
     send message to back asking for the encrypted message
     updates the textarea with the ciphertext
     */
    sendMessage({
        "action" : "encrypt",
        "message" : message,
        "usernames" : usernames,
    }, function(response) {
        var res = $.parseJSON(response).res;
        var msg;
        if (typeof res === "string") {
            msg = res;
        } else {
            var json_encoded = JSON.stringify(res);
            msg = FAKEBLOCK_OPEN_TAG +
                encodeString(json_encoded) +
                FAKEBLOCK_CLOSE_TAG;
        }
        $encryptedArea.html(msg);
    });
}

function requestCanEncryptFor($unencryptedArea) {
    /*
     send message to back asking if the usernames can be encrypted for (are valid parseltongue users)
     if yes or no, updates the doEncrypt state of the corresponding unencrypted textarea, switching encryption on or off
     parseltongue buttons will show or hide accordingly
     */
    var usernames = $unencryptedArea.data('usernames') ?
        $unencryptedArea.data('usernames') : [];
    sendMessage({
        "action" : "canEncryptFor",
        "usernames" : usernames,
    }, function(response) {
        var res = $.parseJSON(response).res;
        $unencryptedArea.data('doEncrypt', res.can_encrypt);
        var ptButtonsWrapper = $unencryptedArea.data('ptButtons');
        if (ptButtonsWrapper) {
            if (res.can_encrypt) {
                ptButtonsWrapper.show();
            } else {
                ptButtonsWrapper.hide();
            }
        }
        encryptHandler($unencryptedArea);
    });
}

function requestDefaultEncrypt($ptButtons) {
    /*
    get default setting for encrypt on or off
    show or hide $unencryptedArea's lock/snake buttons on opening compose window
    */
    sendMessage({
       "action": "getUserMeta",
   }, function(res) {
        userMeta = JSON.parse(res).res;
        if (userMeta.defaultEncrypt) {
            togglePtButton($ptButtons.find('.pt_unlocked'));
        } else {
            togglePtButton($ptButtons.find('.pt_locked'));
        }
   });
}

chrome.runtime.onMessage.addListener(function(message) {
    if ('defaultEncrypt' in message) {
        var classToToggle = message.defaultEncrypt ? '.pt_unlocked' : '.pt_locked';
        $.each($(classToToggle), function() {
            togglePtButton($(this));
        });
    }
});<commit_msg>can now tab to the overlay instead of the original email. more annoying front-end-y stuff to come :(<commit_after>var doEncryptDomains = [
    "facebook.com",
    "mail.google.com"
];

var EMAIL_WINDOW_SELECTOR = '.I5';
var TEXTAREA_SELECTOR = '.Am[role="textbox"][contenteditable="true"]';
var TOOLBAR_SELECT = ".n1tfz";
var FORMAT_BUTTON_SELECT = ".az5";

function usernameGetter($usernameField) {
    return $usernameField.find('*').filter(function(i, elm) {
        return $(elm).attr('email') !== undefined;
    }).toArray().map(function(emailSpan) {
        return $(emailSpan).attr('email');
    });
}

$(function() {
    if (doEncryptDomains.indexOf(document.domain) < 0) {
        return
    }

    $(document).on('DOMNodeInserted', function(e) {
        if ($(e.target).closest(NON_FAKEBLOCK_TEXTAREA_SELECTOR).length > 0) {
            //don't allow insert events for objects appended to the unencrypted area to propagate
            return;
        } else if ($(e.target).parent().hasClass(NON_FAKEBLOCK_TEXTAREA_CLASS) &&
            $(e.target).hasClass('gmail_extra')) {
            //if an old email thread gets revealed in the original textarea
            //append it to associated unencrypted area instead and reencrypt
            var $unencryptedArea = $(e.target).parent().data('unencryptedArea');
            $unencryptedArea.append($(e.target));
            return;
        }

        //gmail makes the first matching textarea found into the compose textarea
        //move the unencrypted textarea after the encrypted textarea
        if ($('.aO7').children(TEXTAREA_SELECTOR).length == 1) {
            $('.aO7').append($(NON_FAKEBLOCK_TEXTAREA_SELECTOR));
        }

        //make overlay if the email window and the associated textarea are found, and if doesn't already have overlay
        var overlayable = $(e.target).closest(EMAIL_WINDOW_SELECTOR).find(TEXTAREA_SELECTOR);
        var email_toolbar = $(e.target).closest(EMAIL_WINDOW_SELECTOR).find(TOOLBAR_SELECT);

        //this is awkward, but we have to wait until the textarea's tabindex is set
        //so can tab to overlay but can't tab to original email
        if (overlayable.length > 0 && overlayable.attr('tabindex')) {
            if (! overlayable.hasClass('has-overlay')) {
                var $email = overlayable.closest(EMAIL_WINDOW_SELECTOR);
                makeOverlay($email);
            } else if (overlayable.hasClass('pre-draft')) {
                if (overlayable.data('draft') === overlayable.html() ||
                    overlayable.html().indexOf('class="gmail_extra"') >= 0) {
                    //if draft has finished loading
                    overlayable.data('unencryptedArea').html(
                        overlayable.data('draft')
                    );
                    overlayable.removeClass('pre-draft');
                } else {
                    //draft hasn't finished loading into compose window
                    overlayable.data('draft', overlayable.html());
                }

            }
            if (email_toolbar.length > 0) {
                var format_button = email_toolbar.find(FORMAT_BUTTON_SELECT);
                if (!(format_button.hasClass("yupper"))) {
                    format_button.addClass("yupper");
                    var $ptButton = $('
' +
                    '
' +
                    '        ' +
                    '
' +
                    '
' +
                    '        ' +
                    '
' +
                    '
');
                    format_button.before($ptButton);
                    $ptButton.find(".pt-button").css(
                        {
//                            "top":" 1px",
                            "padding":" 10px 7px 5px 8px",
                            "width":" 22px",
                            "height":" 25px",
                            "border":" 1px solid #f5f5f5",
                            "border-top":"1px solid rgba(128, 128, 128, 0.32)",
                            "float":"right"
                        });
                    $ptButton.find('.pt-button').hover(
//                        function(){ $(this).css('border', '1px solid gray') },
//                        function(){ $(this).css(
//                            {'border':'1px solid #f5f5f5',
//                            "border-top":"1px solid rgba(128, 128, 128, 0.32)"
//                            }) }
                        function(){ $(this).css('cursor', 'pointer') }
                    );
                    $ptButton.find('.pt_unlocked').click(function(e) { // they clicked the snake, stuff should be encrypted now
                        togglePtButton($(this));
                    });
                    $ptButton.find('.pt_locked').click(function(e) { // they clicked the lock, stuff should be unencrypted now
                        togglePtButton($(this));
                    });
                    bindPtButtons(overlayable, $ptButton);
                }
            }
        }

        //update usernames if (possibly) a new username has been added
        usernameHandler(e.target);
    });

    $(document).on('DOMNodeRemoved', function(e) {
        usernameHandler(e.target, true);
    });
});

function bindPtButtons($encryptedArea, $ptButtons) {
    /*
    initialize parseltongue buttons
    binds parseltongue buttons to the proper unencrypted textarea
    sets the default encrypt/decrypt option based on usermeta.defaultEncrypt
    shows or hides all parseltongue buttons if can encrypt for current usernames
    */
    var $unencryptedArea = $encryptedArea.data('unencryptedArea');
    $unencryptedArea.data('ptButtons', $ptButtons);
    $ptButtons.data('unencryptedArea', $unencryptedArea);
    requestDefaultEncrypt($ptButtons);
    requestCanEncryptFor($unencryptedArea);
}

function togglePtButton($ptButton) {
    $ptButton.hide();
    $ptButton.siblings().show();
    encryptHandler($ptButton.closest('.pt-buttons-wrapper').data('unencryptedArea'));
}

function makeOverlay($email) {
    /* given the top-level email window, make an overlay for it */

    var $textarea = $email.find(TEXTAREA_SELECTOR);
    var $unencryptedArea = makeOverlayHtml($textarea);

    $textarea.after($unencryptedArea);
    $textarea.hide();

    //set doencrypt to false since usernames should be empty when email first opens
    $unencryptedArea.data('doEncrypt', false);

    $unencryptedArea.keyup(function(e) {
        encryptHandler($(this));
    });
    $textarea.addClass(FAKEBLOCK_TEXTAREA_CLASS);
    $textarea.addClass('has-overlay pre-draft');
    $textarea.data('unencryptedArea', $unencryptedArea);
}

function makeOverlayHtml($textarea) {
    /* lots of shit here to make the overlay look like the original */

    var $unencryptedArea = $textarea.clone();
    var unencrypted_id = $unencryptedArea.prop('id') + '_unencrypted';
    $unencryptedArea.attr({
        id : unencrypted_id,
        role : 'textbox',
        contenteditable : true,
        tabindex : $textarea.attr('tabindex')
    });
    $textarea.removeAttr('tabindex');

    $unencryptedArea.click(function(evt) {
        evt.stopPropagation();
    }).focus(function(evt) {
            evt.stopPropagation();
        });

    $unencryptedArea.css({
        'min-height' : '85px',
        'direction' : 'ltr'
    });
    $unencryptedArea.addClass(['Al', 'LW-avf', NON_FAKEBLOCK_TEXTAREA_CLASS].join(' '));

    $unencryptedArea.data('encryptedArea', $textarea);

    return $unencryptedArea;
}

function encryptHandler($unencryptedArea) {
    /*
     handler for keyup in an unencrypted area
     uses doEncrypt in unencrypted area's data to check if should update with ciphertext or plaintext
     */
    var $encryptedArea = $unencryptedArea.data('encryptedArea');
    var message = $unencryptedArea.html();
    var usernames = $unencryptedArea.data('usernames');

    //if can find button, check if button is visible and set to lock
    //otherwise do not encrypt
    var $ptButton = $unencryptedArea.data('ptButtons');
    var canEncryptButton = $ptButton ?
        $ptButton.find('.pt_locked').css('display') !== 'none' && $ptButton.css('display') !== 'none' :
        false;
    if ($unencryptedArea.data('doEncrypt') && canEncryptButton) {
        requestEncrypt($encryptedArea, message, usernames);
    } else {
        $encryptedArea.html(message);
    }

}

function usernameHandler(emailSpan, deleteEmail) {
    /*
     handler for an email address getting added or removed from the addressees
     updates the usernames list for the appropriate unencrypted textarea
     */
    var emailSpans = $(emailSpan).children().add($(emailSpan)).filter(function(i, elm) {
        return $(elm).attr('email') !== undefined;
    });
    //TODO: what happens if more than one email span? is this possible?
    //TODO: normalize emails in frontend too?
    if (emailSpans.length == 1) {
        $usernameField = emailSpans.parent().parent();
        var usernameToDelete = null;
        if (deleteEmail) {
            usernameToDelete = emailSpans.attr('email');
        }
        updateUsernames($usernameField, usernameToDelete);
    }
}

function updateUsernames($usernameField, usernameToDelete) {
    /*
     call to check if the usernames are all still valid parseltongue users
     if yes/no, update the doEncrypt field of the corresponding unencrypted textarea
     */

    //check if username field has been associated with a textarea yet
    var $unencryptedArea = $usernameField.data('unencryptedArea');
    if ($unencryptedArea === undefined) {
        $unencryptedArea = $usernameField.closest(EMAIL_WINDOW_SELECTOR).find(
            NON_FAKEBLOCK_TEXTAREA_SELECTOR
        );
        $usernameField.data('unencryptedArea', $unencryptedArea);
    }

    var usernames = usernameGetter($usernameField);
    var usernameDeleteIndex = usernames.indexOf(usernameToDelete);
    if (usernameDeleteIndex >= 0) {
        usernames.splice(usernameDeleteIndex, 1);
    }
    $unencryptedArea.data('usernames', usernames);
    requestCanEncryptFor($unencryptedArea);
}

function requestEncrypt($encryptedArea, message, usernames) {
    /*
     send message to back asking for the encrypted message
     updates the textarea with the ciphertext
     */
    sendMessage({
        "action" : "encrypt",
        "message" : message,
        "usernames" : usernames,
    }, function(response) {
        var res = $.parseJSON(response).res;
        var msg;
        if (typeof res === "string") {
            msg = res;
        } else {
            var json_encoded = JSON.stringify(res);
            msg = FAKEBLOCK_OPEN_TAG +
                encodeString(json_encoded) +
                FAKEBLOCK_CLOSE_TAG;
        }
        $encryptedArea.html(msg);
    });
}

function requestCanEncryptFor($unencryptedArea) {
    /*
     send message to back asking if the usernames can be encrypted for (are valid parseltongue users)
     if yes or no, updates the doEncrypt state of the corresponding unencrypted textarea, switching encryption on or off
     parseltongue buttons will show or hide accordingly
     */
    var usernames = $unencryptedArea.data('usernames') ?
        $unencryptedArea.data('usernames') : [];
    sendMessage({
        "action" : "canEncryptFor",
        "usernames" : usernames,
    }, function(response) {
        var res = $.parseJSON(response).res;
        $unencryptedArea.data('doEncrypt', res.can_encrypt);
        var ptButtonsWrapper = $unencryptedArea.data('ptButtons');
        if (ptButtonsWrapper) {
            if (res.can_encrypt) {
                ptButtonsWrapper.show();
            } else {
                ptButtonsWrapper.hide();
            }
        }
        encryptHandler($unencryptedArea);
    });
}

function requestDefaultEncrypt($ptButtons) {
    /*
    get default setting for encrypt on or off
    show or hide $unencryptedArea's lock/snake buttons on opening compose window
    */
    sendMessage({
       "action": "getUserMeta",
   }, function(res) {
        userMeta = JSON.parse(res).res;
        if (userMeta.defaultEncrypt) {
            togglePtButton($ptButtons.find('.pt_unlocked'));
        } else {
            togglePtButton($ptButtons.find('.pt_locked'));
        }
   });
}

chrome.runtime.onMessage.addListener(function(message) {
    if ('defaultEncrypt' in message) {
        var classToToggle = message.defaultEncrypt ? '.pt_unlocked' : '.pt_locked';
        $.each($(classToToggle), function() {
            togglePtButton($(this));
        });
    }
});<|endoftext|>


<commit_before>import { Component, FontAwesomeIcon as Icon } from 'substance'
const refTypes = [
  {id: 'joural', label: 'Journal article'},
  {id: 'book', label: 'Book chapter'},
]

export default class EditRef extends Component {

  /*
    TODO:
      - Add missing forms
      - We need different forms for different ref types
      - Implement only publication-type="journal" and publication-type="book"
  */
  render($$) {
    return $$('div').addClass('sc-edit-ref').append(
      this._renderJournalRefForm($$)
    )
  }

  _renderJournalRefForm($$) {
    let el = $$('div').addClass('se-journal-ref')

    el.append(
      this._renderRefTypeSwitcher($$),
      this._renderArticleTitle($$),
      this._renderAuthors($$),
      this._renderJournalTitle($$),
      this._renderPeriodical($$),
      this._renderPages($$),
      this._renderDOI($$),
      this._renderPMID($$),
      this._renderNote($$)
    )

    return el
  }

  /*
    TODO: implement book reference form
  */
  _renderBookRefForm($$) {
    let el = $$('div').addClass('se-book-ref')
    return el
  }

  _renderRefTypeSwitcher($$) {
    let node = this.props.node
    let publicationType = node.getAttribute('publication-type')
    let el = $$('div').addClass('se-ref-type')

    let switcher = $$('select').on('change', this._onRefTypeChange)
    refTypes.forEach(refType => {
      let option = $$('option').attr('value', refType.id).append(refType.label)
      if(publicationType === refType.id) option.attr('selected', 'selected')
      switcher.append(option)
    })

    el.append(
      $$('div').addClass('se-label').append('Type'),
      $$('div').addClass('se-select').append(switcher)
    )

    return el
  }

  _renderArticleTitle($$) {
    let articleTitle = this.props.node.find('article-title')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    return $$('div').addClass('se-article-title').append(
      $$('div').addClass('se-label').append('Article Title'),
      $$(TextPropertyEditor, {
        placeholder: 'Article title',
        path: articleTitle.getTextPath(),
        disabled: this.props.disabled
      }).ref(articleTitle.id).addClass('se-text-input')
    )
  }

  _renderAuthors($$) {
    let TextPropertyEditor = this.getComponent('text-property-editor')
    let authorNames = this.props.node.findAll('person-group[person-group-type=author] name')
    let el = $$('div').addClass('se-authors').append(
      $$('div').addClass('se-label').append('Authors')
    )

    authorNames.forEach(author => {
      let surname = author.find('surname')
      let givenName = author.find('given-names')
      let authorEl = $$('div').addClass('se-author se-form').append(
        $$(TextPropertyEditor, {
          placeholder: 'Given Names',
          path: givenName.getTextPath(),
          disabled: this.props.disabled
        }).ref(givenName.id).addClass('se-text-input'),
        $$(TextPropertyEditor, {
          placeholder: 'Surname',
          path: surname.getTextPath(),
          disabled: this.props.disabled
        }).ref(surname.id).addClass('se-text-input'),
        $$(Icon, {icon: 'fa-trash'})
          .addClass('se-remove-author')
          .on('click', this._removeAuthor.bind(this, author.id))
      )
      el.append(authorEl)
    })

    el.append(
      $$('button').addClass('sg-big-button')
        .append('Add Author')
        .on('click', this._addAuthor)
    )

    return el
  }

  _renderJournalTitle($$) {
    let journalTitle = this.props.node.find('source')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    return $$('div').addClass('se-journal-title').append(
      $$('div').addClass('se-label').append('Journal Title'),
      $$(TextPropertyEditor, {
        placeholder: 'Journal title',
        path: journalTitle.getTextPath(),
        disabled: this.props.disabled
      }).ref(journalTitle.id).addClass('se-text-input')
    )
  }

  _renderPeriodical($$) {
    let TextPropertyEditor = this.getComponent('text-property-editor')
    let el = $$('div').addClass('se-pereodical')
    let form = $$('div').addClass('se-form')

    let volume = this.props.node.find('volume')
    if(volume) {
      form.append(
        $$(TextPropertyEditor, {
          placeholder: 'Volume',
          path: volume.getTextPath(),
          disabled: this.props.disabled
        }).ref(volume.id).addClass('se-text-input')
      )
    }

    let issue = this.props.node.find('issue')
    if(issue) {
      form.append(
        $$(TextPropertyEditor, {
          placeholder: 'Issue',
          path: issue.getTextPath(),
          disabled: this.props.disabled
        }).ref(issue.id).addClass('se-text-input')
      )
    }

    let year = this.props.node.find('year')
    if(year) {
      form.append(
        $$(TextPropertyEditor, {
          placeholder: 'Year',
          path: year.getTextPath(),
          disabled: this.props.disabled
        }).ref(year.id).addClass('se-text-input')
      )
    }

    el.append(
      $$('div').addClass('se-label').append('Volume / Issue / Year'),
      form
    )

    return el
  }

  _renderPages($$) {
    let TextPropertyEditor = this.getComponent('text-property-editor')
    let el = $$('div').addClass('se-pages')

    let firstPage = this.props.node.find('fpage')
    let firstPageEditor = $$(TextPropertyEditor, {
      placeholder: 'From',
      path: firstPage.getTextPath(),
      disabled: this.props.disabled
    }).ref(firstPage.id).addClass('se-text-input')

    let lastPage = this.props.node.find('lpage')
    let lastPageEditor = $$(TextPropertyEditor, {
      placeholder: 'To',
      path: lastPage.getTextPath(),
      disabled: this.props.disabled
    }).ref(lastPage.id).addClass('se-text-input')

    el.append(
      $$('div').addClass('se-label').append('Pages (from-to)'),
      $$('div').addClass('se-form').append(
        firstPageEditor,
        lastPageEditor
      )
    )

    return el
  }

  _renderDOI($$) {
    let doi = this.props.node.find('pub-id[pub-id-type=doi]')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    if(doi) {
      return $$('div').addClass('se-doi').append(
        $$('div').addClass('se-label').append('DOI'),
        $$(TextPropertyEditor, {
          placeholder: 'DOI of article',
          path: doi.getTextPath(),
          disabled: this.props.disabled
        }).ref(doi.id).addClass('se-text-input')
      )
    } else {
      return
    }
  }

  _renderPMID($$) {
    let pmid = this.props.node.find('pub-id[pub-id-type=pmid]')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    if(pmid) {
      return $$('div').addClass('se-pmid').append(
        $$('div').addClass('se-label').append('PMID'),
        $$(TextPropertyEditor, {
          placeholder: 'PubMed identifier',
          path: pmid.getTextPath(),
          disabled: this.props.disabled
        }).ref(pmid.id).addClass('se-text-input')
      )
    } else {
      return
    }
  }

  _renderNote($$) {
    let note = this.props.node.find('comment')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    if(note) {
      return $$('div').addClass('se-note').append(
        $$('div').addClass('se-label').append('Note'),
        $$(TextPropertyEditor, {
          placeholder: 'Additional notes',
          path: note.getTextPath(),
          disabled: this.props.disabled
        }).ref(note.id).addClass('se-text-input')
      )
    } else {
      return
    }
  }

  _addAuthor() {
    const node = this.props.node
    const personGroupId = node.find('person-group[person-group-type=author]').id
    const editorSession = this.context.editorSession
    editorSession.transaction((doc) => {
      let personGroup = doc.get(personGroupId)
      let name = doc.createElement('name').append(
        doc.createElement('given-names'),
        doc.createElement('surname')
      )

      personGroup.append(name)
    })

    this.rerender()
  }

  _removeAuthor(authorId) {
    const node = this.props.node
    const personGroupId = node.find('person-group[person-group-type=author]').id
    const editorSession = this.context.editorSession
    editorSession.transaction((doc) => {
      let personGroup = doc.get(personGroupId)
      let author = personGroup.find(`name#${authorId}`)
      personGroup.removeChild(author)
    })

    this.rerender()
  }

  _onRefTypeChange(e) {
    const node = this.props.node
    let value = e.target.value

    let editorSession = this.context.editorSession
    editorSession.transaction(doc => {
      let elementCitation = doc.get(node.id)
      elementCitation.setAttribute('publication-type', value)
      // TODO: switch form
    })
  }

  /*
    TODO: Rewrite ElementCitation with new data
  */
  _updateRef() {

  }

}
<commit_msg>Book reference form.<commit_after>import { Component, FontAwesomeIcon as Icon } from 'substance'
const refTypes = [
  {id: 'joural', label: 'Journal article'},
  {id: 'book', label: 'Book chapter'},
]

export default class EditRef extends Component {

  /*
    TODO:
      - Add missing forms
      - We need different forms for different ref types
      - Implement only publication-type="journal" and publication-type="book"
  */
  render($$) {
    let elementCitation = this.props.node.find('element-citation')
    let publicationType = elementCitation.getAttribute('publication-type')

    let el = $$('div').addClass('sc-edit-ref')

    if(publicationType === 'journal') {
      el.append(this._renderJournalRefForm($$))
    } else if (publicationType === 'book') {
      el.append(this._renderBookRefForm($$))
    }

    return el
  }

  _renderJournalRefForm($$) {
    let el = $$('div').addClass('se-journal-ref')

    el.append(
      this._renderRefTypeSwitcher($$),
      this._renderArticleTitle($$),
      this._renderAuthors($$),
      this._renderSource($$, 'Jounal Title'),
      this._renderPeriodical($$),
      this._renderPages($$),
      this._renderDOI($$),
      this._renderPMID($$),
      this._renderNote($$)
    )

    return el
  }

  _renderBookRefForm($$) {
    let el = $$('div').addClass('se-book-ref')

    el.append(
      this._renderRefTypeSwitcher($$),
      this._renderSource($$, 'Book Title'),
      this._renderAuthors($$),
      this._renderChapterTitle($$),
      this._renderPublisherLocation($$),
      this._renderPublisherName($$),
      this._renderPublishingYear($$),
      this._renderPages($$),
      this._renderNote($$)
    )

    return el
  }

  _renderRefTypeSwitcher($$) {
    let elementCitation = this.props.node.find('element-citation')
    let publicationType = elementCitation.getAttribute('publication-type')
    let el = $$('div').addClass('se-ref-type')

    let switcher = $$('select').on('change', this._onRefTypeChange)
    refTypes.forEach(refType => {
      let option = $$('option').attr('value', refType.id).append(refType.label)
      if(publicationType === refType.id) option.attr('selected', 'selected')
      switcher.append(option)
    })

    el.append(
      $$('div').addClass('se-label').append('Type'),
      $$('div').addClass('se-select').append(switcher)
    )

    return el
  }

  _renderArticleTitle($$) {
    let articleTitle = this.props.node.find('article-title')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    return $$('div').addClass('se-article-title').append(
      $$('div').addClass('se-label').append('Article Title'),
      $$(TextPropertyEditor, {
        placeholder: 'Article title',
        path: articleTitle.getTextPath(),
        disabled: this.props.disabled
      }).ref(articleTitle.id).addClass('se-text-input')
    )
  }

  _renderAuthors($$) {
    let TextPropertyEditor = this.getComponent('text-property-editor')
    let authorNames = this.props.node.findAll('person-group[person-group-type=author] name')
    let el = $$('div').addClass('se-authors').append(
      $$('div').addClass('se-label').append('Authors')
    )

    authorNames.forEach(author => {
      let surname = author.find('surname')
      let givenName = author.find('given-names')
      let authorEl = $$('div').addClass('se-author se-form').append(
        $$(TextPropertyEditor, {
          placeholder: 'Given Names',
          path: givenName.getTextPath(),
          disabled: this.props.disabled
        }).ref(givenName.id).addClass('se-text-input'),
        $$(TextPropertyEditor, {
          placeholder: 'Surname',
          path: surname.getTextPath(),
          disabled: this.props.disabled
        }).ref(surname.id).addClass('se-text-input'),
        $$(Icon, {icon: 'fa-trash'})
          .addClass('se-remove-author')
          .on('click', this._removeAuthor.bind(this, author.id))
      )
      el.append(authorEl)
    })

    el.append(
      $$('button').addClass('sg-big-button')
        .append('Add Author')
        .on('click', this._addAuthor)
    )

    return el
  }

  _renderSource($$, label) {
    let source = this.props.node.find('source')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    return $$('div').addClass('se-source').append(
      $$('div').addClass('se-label').append(label),
      $$(TextPropertyEditor, {
        placeholder: label,
        path: source.getTextPath(),
        disabled: this.props.disabled
      }).ref(source.id).addClass('se-text-input')
    )
  }

  _renderChapterTitle($$) {
    let chapterTitle = this.props.node.find('chapter-title')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    if(chapterTitle) {
      return $$('div').addClass('se-chapter-title').append(
        $$('div').addClass('se-label').append('Chapter Title'),
        $$(TextPropertyEditor, {
          placeholder: 'Chapter title',
          path: chapterTitle.getTextPath(),
          disabled: this.props.disabled
        }).ref(chapterTitle.id).addClass('se-text-input')
      )
    } else {
      return
    }
  }

  _renderPublisherLocation($$) {
    let publisherLoc = this.props.node.find('publisher-loc')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    if(publisherLoc) {
      return $$('div').addClass('se-chapter-title').append(
        $$('div').addClass('se-label').append('Publisher Location'),
        $$(TextPropertyEditor, {
          placeholder: 'Publisher location',
          path: publisherLoc.getTextPath(),
          disabled: this.props.disabled
        }).ref(publisherLoc.id).addClass('se-text-input')
      )
    } else {
      return
    }
  }

  _renderPublisherName($$) {
    let publisherName = this.props.node.find('publisher-name')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    if(publisherName) {
      return $$('div').addClass('se-chapter-title').append(
        $$('div').addClass('se-label').append('Publisher Name'),
        $$(TextPropertyEditor, {
          placeholder: 'Publisher name',
          path: publisherName.getTextPath(),
          disabled: this.props.disabled
        }).ref(publisherName.id).addClass('se-text-input')
      )
    } else {
      return
    }
  }

  _renderPublishingYear($$) {
    let publisherYear = this.props.node.find('year')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    if(publisherYear) {
      return $$('div').addClass('se-chapter-title').append(
        $$('div').addClass('se-label').append('Publishing Year'),
        $$(TextPropertyEditor, {
          placeholder: 'Publishing year',
          path: publisherYear.getTextPath(),
          disabled: this.props.disabled
        }).ref(publisherYear.id).addClass('se-text-input')
      )
    } else {
      return
    }
  }

  _renderPeriodical($$) {
    let TextPropertyEditor = this.getComponent('text-property-editor')
    let el = $$('div').addClass('se-pereodical')
    let form = $$('div').addClass('se-form')

    let volume = this.props.node.find('volume')
    if(volume) {
      form.append(
        $$(TextPropertyEditor, {
          placeholder: 'Volume',
          path: volume.getTextPath(),
          disabled: this.props.disabled
        }).ref(volume.id).addClass('se-text-input')
      )
    }

    let issue = this.props.node.find('issue')
    if(issue) {
      form.append(
        $$(TextPropertyEditor, {
          placeholder: 'Issue',
          path: issue.getTextPath(),
          disabled: this.props.disabled
        }).ref(issue.id).addClass('se-text-input')
      )
    }

    let year = this.props.node.find('year')
    if(year) {
      form.append(
        $$(TextPropertyEditor, {
          placeholder: 'Year',
          path: year.getTextPath(),
          disabled: this.props.disabled
        }).ref(year.id).addClass('se-text-input')
      )
    }

    el.append(
      $$('div').addClass('se-label').append('Volume / Issue / Year'),
      form
    )

    return el
  }

  _renderPages($$) {
    let TextPropertyEditor = this.getComponent('text-property-editor')
    let el = $$('div').addClass('se-pages')

    let firstPage = this.props.node.find('fpage')
    let firstPageEditor = $$(TextPropertyEditor, {
      placeholder: 'From',
      path: firstPage.getTextPath(),
      disabled: this.props.disabled
    }).ref(firstPage.id).addClass('se-text-input')

    let lastPage = this.props.node.find('lpage')
    let lastPageEditor = $$(TextPropertyEditor, {
      placeholder: 'To',
      path: lastPage.getTextPath(),
      disabled: this.props.disabled
    }).ref(lastPage.id).addClass('se-text-input')

    el.append(
      $$('div').addClass('se-label').append('Pages (from-to)'),
      $$('div').addClass('se-form').append(
        firstPageEditor,
        lastPageEditor
      )
    )

    return el
  }

  _renderDOI($$) {
    let doi = this.props.node.find('pub-id[pub-id-type=doi]')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    if(doi) {
      return $$('div').addClass('se-doi').append(
        $$('div').addClass('se-label').append('DOI'),
        $$(TextPropertyEditor, {
          placeholder: 'DOI of article',
          path: doi.getTextPath(),
          disabled: this.props.disabled
        }).ref(doi.id).addClass('se-text-input')
      )
    } else {
      return
    }
  }

  _renderPMID($$) {
    let pmid = this.props.node.find('pub-id[pub-id-type=pmid]')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    if(pmid) {
      return $$('div').addClass('se-pmid').append(
        $$('div').addClass('se-label').append('PMID'),
        $$(TextPropertyEditor, {
          placeholder: 'PubMed identifier',
          path: pmid.getTextPath(),
          disabled: this.props.disabled
        }).ref(pmid.id).addClass('se-text-input')
      )
    } else {
      return
    }
  }

  _renderNote($$) {
    let note = this.props.node.find('comment')
    let TextPropertyEditor = this.getComponent('text-property-editor')

    if(note) {
      return $$('div').addClass('se-note').append(
        $$('div').addClass('se-label').append('Note'),
        $$(TextPropertyEditor, {
          placeholder: 'Additional notes',
          path: note.getTextPath(),
          disabled: this.props.disabled
        }).ref(note.id).addClass('se-text-input')
      )
    } else {
      return
    }
  }

  _addAuthor() {
    const node = this.props.node
    const personGroupId = node.find('person-group[person-group-type=author]').id
    const editorSession = this.context.editorSession
    editorSession.transaction((doc) => {
      let personGroup = doc.get(personGroupId)
      let name = doc.createElement('name').append(
        doc.createElement('given-names'),
        doc.createElement('surname')
      )

      personGroup.append(name)
    })

    this.rerender()
  }

  _removeAuthor(authorId) {
    const node = this.props.node
    const personGroupId = node.find('person-group[person-group-type=author]').id
    const editorSession = this.context.editorSession
    editorSession.transaction((doc) => {
      let personGroup = doc.get(personGroupId)
      let author = personGroup.find(`name#${authorId}`)
      personGroup.removeChild(author)
    })

    this.rerender()
  }

  _onRefTypeChange(e) {
    const node = this.props.node
    let value = e.target.value

    let editorSession = this.context.editorSession
    editorSession.transaction(doc => {
      let elementCitation = doc.get(node.id).find('element-citation')
      elementCitation.setAttribute('publication-type', value)
      // TODO: switch form
    })
  }

  /*
    TODO: Rewrite ElementCitation with new data
  */
  _updateRef() {

  }

}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import Toolbar from '../index';

describe('Toolbar', () => {
  it('allows children to override the content', (done) => {
    const structure = [class Child extends Component {
      componentDidMount() {
        setTimeout(() => {
          this.props.onOverrideContent(() => {
            setTimeout(() => {
              this.props.onOverrideContent(undefined);
            });
            return ;
          });
        });
      }
      render() {
        return ;
      }
    }];

    const theme = { toolbarStyles: {} };

    const store = {
      subscribeToItem() {},
      unsubscribeFromItem() {},
      getItem: (name) => ({
        getEditorState: () => ({
          getSelection: () => ({ isCollapsed: () => true, getHasFocus: () => true })
        })
      }[name])
    };

    const wrapper = mount(
      <Toolbar
        store={store}
        theme={theme}
        structure={structure}
      />
    );

    expect(wrapper.find('.initial').length).to.equal(1);

    setTimeout(() => {
      expect(wrapper.find('.initial').length).to.equal(0);
      expect(wrapper.find('.overridden').length).to.equal(1);

      setTimeout(() => {
        expect(wrapper.find('.initial').length).to.equal(1);
        expect(wrapper.find('.overridden').length).to.equal(0);
        done();
      });
    });
  });
});
<commit_msg>test(inline-toolbar): update test after swap structure prop to children prop<commit_after>import React, { Component } from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import Toolbar from '../index';

describe('Toolbar', () => {
  it('allows children to override the content', (done) => {
    class Child extends Component {
      componentDidMount() {
        setTimeout(() => {
          this.props.onOverrideContent(() => {
            setTimeout(() => {
              this.props.onOverrideContent(undefined);
            });
            return ;
          });
        });
      }
      render() {
        return ;
      }
    }

    const theme = { toolbarStyles: {} };

    const store = {
      subscribeToItem() {},
      unsubscribeFromItem() {},
      getItem: (name) => ({
        getEditorState: () => ({
          getSelection: () => ({ isCollapsed: () => true, getHasFocus: () => true })
        })
      }[name])
    };

    const wrapper = mount(
      <Toolbar
        store={store}
        theme={theme}
      >{
        (externalProps) => <Child {...externalProps} />
      }</Toolbar>
    );

    expect(wrapper.find('.initial').length).to.equal(1);

    setTimeout(() => {
      expect(wrapper.find('.initial').length).to.equal(0);
      expect(wrapper.find('.overridden').length).to.equal(1);

      setTimeout(() => {
        expect(wrapper.find('.initial').length).to.equal(1);
        expect(wrapper.find('.overridden').length).to.equal(0);
        done();
      });
    });
  });
});
<|endoftext|>


<commit_before>$(document).ready(function() {

    function init() {
        loadData(settings, results);
    }

    function renderPages(data, conf) {
        var source = $("#page-template").html();
        var template = Handlebars.compile(source);
        var html = template(data);

        html += "

Complete.";

        $("#pageContainer").append(html);
    }

    function loadData(conf, data) {

        var graphs = [];

        for (var i=0, imax=data.pages.length ; i<imax ; i++) {
            var pageData = data.pages[i];

            // Front end time (in ms) calculation
            data.pages[i].timeFrontendInMs = pageData.windowOnLoadTime - pageData.timeToFirstByte;

            // Warnings calculations
            var pageWarnings = [];
            for (var j=0, jmax=conf.warnings.length; j<jmax ; j++) {
                var warning = conf.warnings[j];
                if (warning.variable in pageData && pageData[warning.variable] > warning.limit) {
                    pageWarnings.push({
                        message: warning.message,
                        value: pageData[warning.variable]
                    });
                }
            }
            data.pages[i].warnings = pageWarnings;

            // Graphs data fetching
            var graphSettings = {
                colors: ['cyan', 'white', 'yellow', 'magenta'],
                chart: {
                    type: 'line',
                    backgroundColor: 'transparent',
                    animation: false
                },
                title: {text: ''},
                legend: {enabled: false},
                credits: {enabled: false},
                xAxis: {
                    labels: {enabled: false},
                    lineColor: 'lime',
                    type: 'datetime'
                },
                yAxis: {
                    title: {text: ''},
                    type: 'datetime',
                    labels: {
                        formatter: function() {
                            return this.value / 1000 +'s';
                        },
                        style: {
                            color: 'lime'
                        }
                    },
                    lineColor: 'lime',
                    gridLineColor: 'rgba(0, 255, 0, 0.3)',
                    tickPixelInterval : 25,
                    min: 0
                },
                tooltip: {
                    formatter: function() {
                        var date = (new Date(this.points[0].key)).toLocaleString();
                        var s = ''+ date +'';

                        $.each(this.points, function(i, point) {
                            s += '
' + point.series.name + ': ' + point.y + 'ms';
                        });

                        return s;
                    },
                    shared: true
                },
                plotOptions: {
                    line: {
                        marker: {
                            enabled: false,
                            symbol: 'diamond',
                            radius: 2,
                            states: {
                                hover: {enabled: true}
                            }
                        }
                    },
                    series: {
                        animation: false
                    }
                }
            };

            var ttfb = [];
            var odrt = [];
            var wolt = [];
            var htcd = [];
            for (var j=0, jmax=pageData.timingsHistory.length; j<jmax ; j++) {
                ttfb.push([pageData.timingsHistory[j].timestamp, pageData.timingsHistory[j].timeToFirstByte]);
                odrt.push([pageData.timingsHistory[j].timestamp, pageData.timingsHistory[j].onDOMReadyTime]);
                wolt.push([pageData.timingsHistory[j].timestamp, pageData.timingsHistory[j].windowOnLoadTime]);
                htcd.push([pageData.timingsHistory[j].timestamp, pageData.timingsHistory[j].httpTrafficCompleted]);
            }
            ttfb.reverse();
            odrt.reverse();
            wolt.reverse();
            htcd.reverse();
            graphSettings.series = [{
                name: 'timeToFirstByte',
                data: ttfb
            }, {
                name: 'onDOMReadyTime',
                data: odrt
            }, {
                name: 'windowOnLoadTime',
                data: wolt
            }, {
                name: 'httpTrafficCompleted',
                data: htcd
            }];

            graphs.push(graphSettings);
        }

        renderPages(data, conf);

        // TODO : find a better way with CSS
        $("#interlace").height($(document).height());

        // Load graphs
        for (var i=0, max=graphs.length ; i<max ; i++) {
            $("#graph" + i).highcharts(graphs[i]);
        }
    }

    function showError(message) {
        $("#pageContainer").append('
' + message + '
');
    }

    $(document).ready(init);
});

Handlebars.registerHelper('meter', function(value, max, low, high) {
    var charNumber = 60;
    var percentage = Math.round((value / max) * charNumber);
    var gauge = [];

    var className = "black yellow-background";
    if (value < low) {
        className = "black green-background";
    } else if (value > high) {
        className = "black red-background";
    }

    for (var j=0 ; j<charNumber ; j++) {
        if (charNumber - percentage <= j) {
            gauge.push('.');
        } else {
            gauge.push('.');
        }
    }
    return new Handlebars.SafeString("[" + gauge.join('') + "]");
});

Handlebars.registerHelper('round', function(value) {
    return Math.round(value);
});<commit_msg>Fix 'Invalid date' bug<commit_after>$(document).ready(function() {

    function init() {
        loadData(settings, results);
    }

    function renderPages(data, conf) {
        var source = $("#page-template").html();
        var template = Handlebars.compile(source);
        var html = template(data);

        html += "

Complete.";

        $("#pageContainer").append(html);
    }

    function loadData(conf, data) {

        var graphs = [];

        for (var i=0, imax=data.pages.length ; i<imax ; i++) {
            var pageData = data.pages[i];

            // Front end time (in ms) calculation
            data.pages[i].timeFrontendInMs = pageData.windowOnLoadTime - pageData.timeToFirstByte;

            // Warnings calculations
            var pageWarnings = [];
            for (var j=0, jmax=conf.warnings.length; j<jmax ; j++) {
                var warning = conf.warnings[j];
                if (warning.variable in pageData && pageData[warning.variable] > warning.limit) {
                    pageWarnings.push({
                        message: warning.message,
                        value: pageData[warning.variable]
                    });
                }
            }
            data.pages[i].warnings = pageWarnings;

            // Graphs data fetching
            var graphSettings = {
                colors: ['cyan', 'white', 'yellow', 'magenta'],
                chart: {
                    type: 'line',
                    backgroundColor: 'transparent',
                    animation: false
                },
                title: {text: ''},
                legend: {enabled: false},
                credits: {enabled: false},
                xAxis: {
                    labels: {enabled: false},
                    lineColor: 'lime',
                    type: 'datetime'
                },
                yAxis: {
                    title: {text: ''},
                    type: 'datetime',
                    labels: {
                        formatter: function() {
                            return this.value / 1000 +'s';
                        },
                        style: {
                            color: 'lime'
                        }
                    },
                    lineColor: 'lime',
                    gridLineColor: 'rgba(0, 255, 0, 0.3)',
                    tickPixelInterval : 25,
                    min: 0
                },
                tooltip: {
                    formatter: function() {
                        var date = (new Date(parseInt(this.points[0].key, 10))).toLocaleString();
                        var s = ''+ date +'';

                        $.each(this.points, function(i, point) {
                            s += '
' + point.series.name + ': ' + point.y + 'ms';
                        });

                        return s;
                    },
                    shared: true
                },
                plotOptions: {
                    line: {
                        marker: {
                            enabled: false,
                            symbol: 'diamond',
                            radius: 2,
                            states: {
                                hover: {enabled: true}
                            }
                        }
                    },
                    series: {
                        animation: false
                    }
                }
            };

            var ttfb = [];
            var odrt = [];
            var wolt = [];
            var htcd = [];
            for (var j=0, jmax=pageData.timingsHistory.length; j<jmax ; j++) {
                ttfb.push([pageData.timingsHistory[j].timestamp, pageData.timingsHistory[j].timeToFirstByte]);
                odrt.push([pageData.timingsHistory[j].timestamp, pageData.timingsHistory[j].onDOMReadyTime]);
                wolt.push([pageData.timingsHistory[j].timestamp, pageData.timingsHistory[j].windowOnLoadTime]);
                htcd.push([pageData.timingsHistory[j].timestamp, pageData.timingsHistory[j].httpTrafficCompleted]);
            }
            ttfb.reverse();
            odrt.reverse();
            wolt.reverse();
            htcd.reverse();
            graphSettings.series = [{
                name: 'timeToFirstByte',
                data: ttfb
            }, {
                name: 'onDOMReadyTime',
                data: odrt
            }, {
                name: 'windowOnLoadTime',
                data: wolt
            }, {
                name: 'httpTrafficCompleted',
                data: htcd
            }];

            graphs.push(graphSettings);
        }

        renderPages(data, conf);

        // TODO : find a better way with CSS
        $("#interlace").height($(document).height());

        // Load graphs
        for (var i=0, max=graphs.length ; i<max ; i++) {
            $("#graph" + i).highcharts(graphs[i]);
        }
    }

    function showError(message) {
        $("#pageContainer").append('
' + message + '
');
    }

    $(document).ready(init);
});

Handlebars.registerHelper('meter', function(value, max, low, high) {
    var charNumber = 60;
    var percentage = Math.round((value / max) * charNumber);
    var gauge = [];

    var className = "black yellow-background";
    if (value < low) {
        className = "black green-background";
    } else if (value > high) {
        className = "black red-background";
    }

    for (var j=0 ; j<charNumber ; j++) {
        if (charNumber - percentage <= j) {
            gauge.push('.');
        } else {
            gauge.push('.');
        }
    }
    return new Handlebars.SafeString("[" + gauge.join('') + "]");
});

Handlebars.registerHelper('round', function(value) {
    return Math.round(value);
});<|endoftext|>


<commit_before>var React = require('react');
var f1Chief = require('f1/chief');
var merge = require('deep-extend');

const TARGET_PROP_NAME = 'data-f1';

class Chief extends React.Component {

  constructor(props) {
    super(props);

    this.handleUpdate = this.handleUpdate.bind(this);
    this.handleTargetInState = this.handleTargetInState.bind(this);

    this.state = {};
    this.chiefTargets = {};
  }

  componentWillMount() {

    var chief = f1Chief({
      transitions: this.props.transitions,
      states: this.props.states,
      onUpdate: this.handleUpdate
    });

    this.setState({
      chief: chief
    });
  }

  handleUpdate(state) {
    this.setState({
      chiefState: state
    });
  }

  handleTargetInState() {
    this.countTargetsIn++;

    if(this.countTargetsIn === this.countTargets) {
      this.props.onComplete();
    }
  }

  componentDidMount() {
    this.state.chief.targets(this.chiefTargets);

    this.setState({
      chief: this.state.chief
    });

    this.state.chief.init(this.props.state);
  }

  componentWillReceiveProps(nextProps) {

    if(
      nextProps.state &&
      (this.state.propsState !== nextProps.state || this.state.propsOnComplete !== nextProps.onComplete)
    ) {
      this.state.chief.go(nextProps.state, nextProps.onComplete);

      this.countTargetsIn = 0;
      this.countTargets = Object.keys(nextProps.states[nextProps.state]).length;

      this.setState({
        propsState: nextProps.state,
        propsOnComplete: nextProps.onComplete
      })
    }
  }

  getChildrenWithTargetName(chiefState) {
    return React.Children.map(this.props.children, (child) => {

      var f1Target = child.props[ TARGET_PROP_NAME ];
      var childProps = merge(
        {},
        child.props,
        {
          state: chiefState[ f1Target ],
          onComplete: this.handleTargetInState
        }
      );

      if(f1Target) {
        return React.cloneElement(
          child,
          childProps
        );
      } else {
        return child;
      }
    });
  }

  getChildrenFromFunction(chiefState) {
    var state = {};

    for(var i in chiefState) {
      state[ i ] = {
        state: chiefState[ i ],
        onComplete: this.handleTargetInState
      };
    }

    return this.props.children(state);
  }

  render() {

    var chiefState = this.state.chiefState;
    var children;

    if(chiefState) {

      if(typeof this.props.children === 'function') {
        children = this.getChildrenFromFunction(chiefState);
      } else {
        children = this.getChildrenWithTargetName(chiefState);
      }
    } else {
      children = this.props.children;
    }

    return <div
      {...this.props}
    >
      { children }
    </div>;
  }
};

Chief.defaultProps = {
  onComplete: function() {}
};

module.exports = Chief;<commit_msg>Added a check to see if we're mounted when updates come<commit_after>var React = require('react');
var f1Chief = require('f1/chief');
var merge = require('deep-extend');

const TARGET_PROP_NAME = 'data-f1';

class Chief extends React.Component {

  constructor(props) {
    super(props);

    this.handleUpdate = this.handleUpdate.bind(this);
    this.handleTargetInState = this.handleTargetInState.bind(this);

    this.state = {};
    this.chiefTargets = {};
  }

  componentWillMount() {

    var chief = f1Chief({
      transitions: this.props.transitions,
      states: this.props.states,
      onUpdate: this.handleUpdate
    });

    this.setState({
      chief: chief
    });
  }

  handleUpdate(state) {

    // added this check just to ensure kimi doesnt throw one final update
    if(this.isMounted()) {
      this.setState({
        chiefState: state
      });
    }
  }

  handleTargetInState() {
    this.countTargetsIn++;

    if(this.countTargetsIn === this.countTargets) {
      this.props.onComplete();
    }
  }

  componentDidMount() {
    this.state.chief.targets(this.chiefTargets);

    this.setState({
      chief: this.state.chief
    });

    this.state.chief.init(this.props.state);
  }

  componentWillReceiveProps(nextProps) {

    if(
      nextProps.state &&
      (this.state.propsState !== nextProps.state || this.state.propsOnComplete !== nextProps.onComplete)
    ) {
      this.state.chief.go(nextProps.state, nextProps.onComplete);

      this.countTargetsIn = 0;
      this.countTargets = Object.keys(nextProps.states[nextProps.state]).length;

      this.setState({
        propsState: nextProps.state,
        propsOnComplete: nextProps.onComplete
      })
    }
  }

  getChildrenWithTargetName(chiefState) {
    return React.Children.map(this.props.children, (child) => {

      var f1Target = child.props[ TARGET_PROP_NAME ];
      var childProps = merge(
        {},
        child.props,
        {
          state: chiefState[ f1Target ],
          onComplete: this.handleTargetInState
        }
      );

      if(f1Target) {
        return React.cloneElement(
          child,
          childProps
        );
      } else {
        return child;
      }
    });
  }

  getChildrenFromFunction(chiefState) {
    var state = {};

    for(var i in chiefState) {
      state[ i ] = {
        state: chiefState[ i ],
        onComplete: this.handleTargetInState
      };
    }

    return this.props.children(state);
  }

  render() {

    var chiefState = this.state.chiefState;
    var children;

    if(chiefState) {

      if(typeof this.props.children === 'function') {
        children = this.getChildrenFromFunction(chiefState);
      } else {
        children = this.getChildrenWithTargetName(chiefState);
      }
    } else {
      children = this.props.children;
    }

    return <div
      {...this.props}
    >
      { children }
    </div>;
  }
};

Chief.defaultProps = {
  onComplete: function() {}
};

module.exports = Chief;<|endoftext|>