<commit_before>import Bluebird, {promisify} from 'bluebird'
import Collection from 'xo-collection'
import createDebug from 'debug'
import forEach from 'lodash.foreach'
import startsWith from 'lodash.startswith'
import {BaseError} from 'make-error'
import {
  createClient as createXmlRpcClient,
  createSecureClient as createSecureXmlRpcClient
} from 'xmlrpc'
import {EventEmitter} from 'events'

const debug = createDebug('xen-api')

// ===================================================================

// http://www.gnu.org/software/libc/manual/html_node/Error-Codes.html
const NETWORK_ERRORS = {
  // Connection has been closed outside of our control.
  ECONNRESET: true,

  // Connection has been aborted locally.
  ECONNABORTED: true,

  // Host is up but refuses connection (typically: no such service).
  ECONNREFUSED: true,

  // TODO: ??
  EINVAL: true,

  // Host is not reachable (does not respond).
  EHOSTUNREACH: true,

  // Connection configured timed out has been reach.
  ETIMEDOUT: true
}

const isNetworkError = (error) => NETWORK_ERRORS[error.code]

// -------------------------------------------------------------------

const XAPI_NETWORK_ERRORS = {
  HOST_STILL_BOOTING: true,
  HOST_HAS_NO_MANAGEMENT_IP: true
}

const isXapiNetworkError = (error) => XAPI_NETWORK_ERRORS[error.code]

// -------------------------------------------------------------------

const areEventsLost = (error) => error.code === 'EVENTS_LOST'

const isHostSlave = (error) => error.code === 'HOST_IS_SLAVE'

const isSessionInvalid = (error) => error.code === 'SESSION_INVALID'

// -------------------------------------------------------------------

class XapiError extends BaseError {
  constructor (error) {
    super(error[0])

    this.code = error[0]
    this.params = error.slice(1)
  }
}

// ===================================================================

const URL_RE = /^(http(s)?:\/\/)?([^/]+?)(?::([0-9]+))?(?:\/.*)?$/
function parseUrl (url) {
  const matches = URL_RE.exec(url)
  if (!matches) {
    throw new Error('invalid URL: ' + url)
  }

  const [, protocol, , host, port] = matches
  let [, , isSecure] = matches

  if (!protocol) {
    isSecure = true
  }

  return {
    isSecure: Boolean(isSecure),
    host,
    port: port !== undefined ?
      +port :
      isSecure ? 443 : 80
  }
}

// -------------------------------------------------------------------

const noop = () => {}

// -------------------------------------------------------------------

const notConnectedPromise = Bluebird.reject(new Error('not connected'))

// Does nothing but avoid a Bluebird message error.
notConnectedPromise.catch(noop)

// ===================================================================

const MAX_TRIES = 5

// -------------------------------------------------------------------

export class Xapi extends EventEmitter {
  constructor (opts) {
    super()

    this._url = parseUrl(opts.url)
    this._auth = opts.auth

    this._sessionId = notConnectedPromise

    this._init()

    this._pool = null
    this._objectsByRefs = Object.create(null)
    this._objectsByRefs['OpaqueRef:NULL'] = null
    this._objects = new Collection()
    this._objects.getId = (object) => object.$id

    this._fromToken = ''
    this.on('connected', this._watchEvents)
    this.on('disconnected', () => {
      this._fromToken = ''
      this._objects.clear()
    })
  }

  get sessionId () {
    if (this.status !== 'connected') {
      throw new Error('sessionId is only available when connected')
    }

    return this._sessionId.value()
  }

  get status () {
    const {_sessionId: sessionId} = this

    if (sessionId.isFulfilled()) {
      return 'connected'
    }

    if (sessionId.isPending()) {
      return 'connecting'
    }

    return 'disconnected'
  }

  get _humanId () {
    return `${this._auth.user}@${this._url.host}`
  }

  connect () {
    const {status} = this

    if (status === 'connected') {
      return Bluebird.reject(new Error('already connected'))
    }

    if (status === 'connecting') {
      return Bluebird.reject(new Error('already connecting'))
    }

    this._sessionId = this._transportCall('session.login_with_password', [
      this._auth.user,
      this._auth.password
    ])

    return this._sessionId.then(() => {
      debug('%s: connected', this._humanId)

      this.emit('connected')
    })
  }

  disconnect () {
    const {status} = this

    if (status === 'disconnected') {
      return Bluebird.reject('already disconnected')
    }

    if (status === 'connecting') {
      return this._sessionId.cancel().catch(Bluebird.CancellationError, () => {
        debug('%s: disconnected', this._humanId)

        this.emit('disconnected')
      })
    }

    this._sessionId = notConnectedPromise

    return Bluebird.resolve().then(() => {
      debug('%s: disconnected', this._humanId)

      this.emit('disconnected')
    })
  }

  // High level calls.
  call (method, ...args) {
    return this._sessionCall(method, args)
  }

  get pool () {
    return this._pool
  }

  get objects () {
    return this._objects
  }

  // Medium level call: handle session errors.
  _sessionCall (method, args) {
    if (startsWith(method, 'session.')) {
      return Bluebird.reject(
        new Error('session.*() methods are disabled from this interface')
      )
    }

    return this._sessionId.then((sessionId) => {
      return this._transportCall(method, [sessionId].concat(args))
    }).catch(isSessionInvalid, () => {
      // XAPI is sometimes reinitialized and sessions are lost.
      // Try to login again.
      debug('%s: the session has been reinitialized', this._humanId)

      this._sessionId = notConnectedPromise

      return this._sessionCall(method, args)
    })
  }

  // Low level call: handle transport errors.
  _transportCall (method, args, tries = 1) {
    debug('%s: %s(...)', this._humanId, method)

    return this._rawCall(method, args)
      .catch(isNetworkError, isXapiNetworkError, error => {
        debug('%s: network error %s', this._humanId, error.code)

        if (!(tries < MAX_TRIES)) {
          debug('%s too many network errors (%s), give up', this._humanId, tries)

          throw error
        }

        // TODO: ability to cancel the connection
        // TODO: ability to force immediate reconnection
        // TODO: implement back-off

        return Bluebird.delay(5e3).then(() => {
          // TODO: handling not responding host.

          return this._transportCall(method, args, tries + 1)
        })
      })
      .catch(isHostSlave, ({params: [master]}) => {
        debug('%s: host is slave, attempting to connect at %s', this._humanId, master)

        this._url.host = master
        this._init()

        return this._transportCall(method, args)
      })
  }

  // Lowest level call: do not handle any errors.
  _rawCall (method, args) {
    return this._xmlRpcCall(method, args)
      .then(result => {
        const {Status: status} = result

        // Return the plain result if it does not have a valid XAPI
        // format.
        if (!status) {
          return result
        }

        if (status === 'Success') {
          return result.Value
        }

        throw new XapiError(result.ErrorDescription)
      })
      .cancellable()
  }

  _init () {
    const {isSecure, host, port} = this._url

    const client = (isSecure ?
      createSecureXmlRpcClient :
      createXmlRpcClient
    )({
      host,
      port,
      rejectUnauthorized: false,
      timeout: 10
    })

    this._xmlRpcCall = promisify(client.methodCall, client)
  }

  _normalizeObject (type, ref, object) {
    const {_objectsByRefs: objectsByRefs} = this
    const REF_RE = /^OpaqueRef:/

    forEach(object, function resolveIfLink (value, key, object) {
      if (typeof value === 'string' && REF_RE.test(value)) {
        Object.defineProperty(object, key, {
          enumerable: true,
          get: () => objectsByRefs[value]
        })
      } else if (typeof value === 'object') {
        forEach(value, resolveIfLink)
      }
    })

    object.$id = object.uuid || ref
    object.$ref = ref
    object.$type = type

    Object.defineProperty(object, '$pool', {
      enumerable: true,
      get: () => this._pool
    })
  }

  _watchEvents () {
    this.call(
      'event.from', ['*'], this._fromToken, 1e3 + 0.1
    ).then(({token, events}) => {
      this._fromToken = token

      const {
        _objects: objects,
        _objectsByRefs: objectsByRefs
      } = this

      forEach(events, event => {
        const {operation: op} = event

        const {ref} = event
        if (op === 'del') {
          const object = objectsByRefs[ref]

          if (object) {
            objects.remove(object.$id)
            delete objectsByRefs[ref]
          }
        } else {
          const {class: type, snapshot: object} = event

          this._normalizeObject(type, ref, object)
          objects.set(object)
          objectsByRefs[ref] = object

          if (object.$type === 'pool') {
            this._pool = object
          }
        }
      })
    }).catch(areEventsLost, () => {
      this._objects.clear()
    }).then(() => {
      this._watchEvents()
    }).catch(Bluebird.CancellationError, noop)
  }
}

// ===================================================================

// The default value is a factory function.
export const createClient = (opts) => new Xapi(opts)
<commit_msg>Export wrapError().<commit_after>import Bluebird, {promisify} from 'bluebird'
import Collection from 'xo-collection'
import createDebug from 'debug'
import forEach from 'lodash.foreach'
import startsWith from 'lodash.startswith'
import {BaseError} from 'make-error'
import {
  createClient as createXmlRpcClient,
  createSecureClient as createSecureXmlRpcClient
} from 'xmlrpc'
import {EventEmitter} from 'events'

const debug = createDebug('xen-api')

// ===================================================================

// http://www.gnu.org/software/libc/manual/html_node/Error-Codes.html
const NETWORK_ERRORS = {
  // Connection has been closed outside of our control.
  ECONNRESET: true,

  // Connection has been aborted locally.
  ECONNABORTED: true,

  // Host is up but refuses connection (typically: no such service).
  ECONNREFUSED: true,

  // TODO: ??
  EINVAL: true,

  // Host is not reachable (does not respond).
  EHOSTUNREACH: true,

  // Connection configured timed out has been reach.
  ETIMEDOUT: true
}

const isNetworkError = (error) => NETWORK_ERRORS[error.code]

// -------------------------------------------------------------------

const XAPI_NETWORK_ERRORS = {
  HOST_STILL_BOOTING: true,
  HOST_HAS_NO_MANAGEMENT_IP: true
}

const isXapiNetworkError = (error) => XAPI_NETWORK_ERRORS[error.code]

// -------------------------------------------------------------------

const areEventsLost = (error) => error.code === 'EVENTS_LOST'

const isHostSlave = (error) => error.code === 'HOST_IS_SLAVE'

const isSessionInvalid = (error) => error.code === 'SESSION_INVALID'

// -------------------------------------------------------------------

class XapiError extends BaseError {
  constructor (error) {
    super(error[0])

    this.code = error[0]
    this.params = error.slice(1)
  }
}

export const wrapError = error => new XapiError(error)

// ===================================================================

const URL_RE = /^(http(s)?:\/\/)?([^/]+?)(?::([0-9]+))?(?:\/.*)?$/
function parseUrl (url) {
  const matches = URL_RE.exec(url)
  if (!matches) {
    throw new Error('invalid URL: ' + url)
  }

  const [, protocol, , host, port] = matches
  let [, , isSecure] = matches

  if (!protocol) {
    isSecure = true
  }

  return {
    isSecure: Boolean(isSecure),
    host,
    port: port !== undefined ?
      +port :
      isSecure ? 443 : 80
  }
}

// -------------------------------------------------------------------

const noop = () => {}

// -------------------------------------------------------------------

const notConnectedPromise = Bluebird.reject(new Error('not connected'))

// Does nothing but avoid a Bluebird message error.
notConnectedPromise.catch(noop)

// ===================================================================

const MAX_TRIES = 5

// -------------------------------------------------------------------

export class Xapi extends EventEmitter {
  constructor (opts) {
    super()

    this._url = parseUrl(opts.url)
    this._auth = opts.auth

    this._sessionId = notConnectedPromise

    this._init()

    this._pool = null
    this._objectsByRefs = Object.create(null)
    this._objectsByRefs['OpaqueRef:NULL'] = null
    this._objects = new Collection()
    this._objects.getId = (object) => object.$id

    this._fromToken = ''
    this.on('connected', this._watchEvents)
    this.on('disconnected', () => {
      this._fromToken = ''
      this._objects.clear()
    })
  }

  get sessionId () {
    if (this.status !== 'connected') {
      throw new Error('sessionId is only available when connected')
    }

    return this._sessionId.value()
  }

  get status () {
    const {_sessionId: sessionId} = this

    if (sessionId.isFulfilled()) {
      return 'connected'
    }

    if (sessionId.isPending()) {
      return 'connecting'
    }

    return 'disconnected'
  }

  get _humanId () {
    return `${this._auth.user}@${this._url.host}`
  }

  connect () {
    const {status} = this

    if (status === 'connected') {
      return Bluebird.reject(new Error('already connected'))
    }

    if (status === 'connecting') {
      return Bluebird.reject(new Error('already connecting'))
    }

    this._sessionId = this._transportCall('session.login_with_password', [
      this._auth.user,
      this._auth.password
    ])

    return this._sessionId.then(() => {
      debug('%s: connected', this._humanId)

      this.emit('connected')
    })
  }

  disconnect () {
    const {status} = this

    if (status === 'disconnected') {
      return Bluebird.reject('already disconnected')
    }

    if (status === 'connecting') {
      return this._sessionId.cancel().catch(Bluebird.CancellationError, () => {
        debug('%s: disconnected', this._humanId)

        this.emit('disconnected')
      })
    }

    this._sessionId = notConnectedPromise

    return Bluebird.resolve().then(() => {
      debug('%s: disconnected', this._humanId)

      this.emit('disconnected')
    })
  }

  // High level calls.
  call (method, ...args) {
    return this._sessionCall(method, args)
  }

  get pool () {
    return this._pool
  }

  get objects () {
    return this._objects
  }

  // Medium level call: handle session errors.
  _sessionCall (method, args) {
    if (startsWith(method, 'session.')) {
      return Bluebird.reject(
        new Error('session.*() methods are disabled from this interface')
      )
    }

    return this._sessionId.then((sessionId) => {
      return this._transportCall(method, [sessionId].concat(args))
    }).catch(isSessionInvalid, () => {
      // XAPI is sometimes reinitialized and sessions are lost.
      // Try to login again.
      debug('%s: the session has been reinitialized', this._humanId)

      this._sessionId = notConnectedPromise

      return this._sessionCall(method, args)
    })
  }

  // Low level call: handle transport errors.
  _transportCall (method, args, tries = 1) {
    debug('%s: %s(...)', this._humanId, method)

    return this._rawCall(method, args)
      .catch(isNetworkError, isXapiNetworkError, error => {
        debug('%s: network error %s', this._humanId, error.code)

        if (!(tries < MAX_TRIES)) {
          debug('%s too many network errors (%s), give up', this._humanId, tries)

          throw error
        }

        // TODO: ability to cancel the connection
        // TODO: ability to force immediate reconnection
        // TODO: implement back-off

        return Bluebird.delay(5e3).then(() => {
          // TODO: handling not responding host.

          return this._transportCall(method, args, tries + 1)
        })
      })
      .catch(isHostSlave, ({params: [master]}) => {
        debug('%s: host is slave, attempting to connect at %s', this._humanId, master)

        this._url.host = master
        this._init()

        return this._transportCall(method, args)
      })
  }

  // Lowest level call: do not handle any errors.
  _rawCall (method, args) {
    return this._xmlRpcCall(method, args)
      .then(result => {
        const {Status: status} = result

        // Return the plain result if it does not have a valid XAPI
        // format.
        if (!status) {
          return result
        }

        if (status === 'Success') {
          return result.Value
        }

        wrapError(result.ErrorDescription)
      })
      .cancellable()
  }

  _init () {
    const {isSecure, host, port} = this._url

    const client = (isSecure ?
      createSecureXmlRpcClient :
      createXmlRpcClient
    )({
      host,
      port,
      rejectUnauthorized: false,
      timeout: 10
    })

    this._xmlRpcCall = promisify(client.methodCall, client)
  }

  _normalizeObject (type, ref, object) {
    const {_objectsByRefs: objectsByRefs} = this
    const REF_RE = /^OpaqueRef:/

    forEach(object, function resolveIfLink (value, key, object) {
      if (typeof value === 'string' && REF_RE.test(value)) {
        Object.defineProperty(object, key, {
          enumerable: true,
          get: () => objectsByRefs[value]
        })
      } else if (typeof value === 'object') {
        forEach(value, resolveIfLink)
      }
    })

    object.$id = object.uuid || ref
    object.$ref = ref
    object.$type = type

    Object.defineProperty(object, '$pool', {
      enumerable: true,
      get: () => this._pool
    })
  }

  _watchEvents () {
    this.call(
      'event.from', ['*'], this._fromToken, 1e3 + 0.1
    ).then(({token, events}) => {
      this._fromToken = token

      const {
        _objects: objects,
        _objectsByRefs: objectsByRefs
      } = this

      forEach(events, event => {
        const {operation: op} = event

        const {ref} = event
        if (op === 'del') {
          const object = objectsByRefs[ref]

          if (object) {
            objects.remove(object.$id)
            delete objectsByRefs[ref]
          }
        } else {
          const {class: type, snapshot: object} = event

          this._normalizeObject(type, ref, object)
          objects.set(object)
          objectsByRefs[ref] = object

          if (object.$type === 'pool') {
            this._pool = object
          }
        }
      })
    }).catch(areEventsLost, () => {
      this._objects.clear()
    }).then(() => {
      this._watchEvents()
    }).catch(Bluebird.CancellationError, noop)
  }
}

// ===================================================================

// The default value is a factory function.
export const createClient = (opts) => new Xapi(opts)
<|endoftext|>


<commit_before>/*
 * typeahead.js
 * https://github.com/twitter/typeahead.js
 * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
 */

var Typeahead = (function() {
  var attrsKey = 'ttAttrs';

  // constructor
  // -----------

  // THOUGHT: what if datasets could dynamically be added/removed?
  function Typeahead(o) {
    var $menu, $input, $hint, datasets;

    o = o || {};

    if (!o.input) {
      $.error('missing input');
    }

    this.autoselect = !!o.autoselect;
    this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
    this.$node = buildDomStructure(o.input, o.withHint);

    $menu = this.$node.find('.tt-dropdown-menu');
    $input = this.$node.find('.tt-input');
    $hint = this.$node.find('.tt-hint');

    this.eventBus = o.eventBus || new EventBus({ el: $input });

    this.dropdown = new Dropdown({ menu: $menu, datasets: o.datasets })
    .onSync('suggestionClicked', this._onSuggestionClicked, this)
    .onSync('cursorMoved', this._onCursorMoved, this)
    .onSync('cursorRemoved', this._onCursorRemoved, this)
    .onSync('opened', this._onOpened, this)
    .onSync('closed', this._onClosed, this)
    .onAsync('datasetRendered', this._onDatasetRendered, this);

    this.input = new Input({ input: $input, hint: $hint })
    .onSync('focused', this._onFocused, this)
    .onSync('blurred', this._onBlurred, this)
    .onSync('enterKeyed', this._onEnterKeyed, this)
    .onSync('tabKeyed', this._onTabKeyed, this)
    .onSync('escKeyed', this._onEscKeyed, this)
    .onSync('upKeyed', this._onUpKeyed, this)
    .onSync('downKeyed', this._onDownKeyed, this)
    .onSync('leftKeyed', this._onLeftKeyed, this)
    .onSync('rightKeyed', this._onRightKeyed, this)
    .onSync('queryChanged', this._onQueryChanged, this)
    .onSync('whitespaceChanged', this._onWhitespaceChanged, this);

    // #351: prevents input blur on menu click
    $menu.on('mousedown.tt', function($e) {
      if (_.isMsie() && _.isMsie() < 9) {
        $input[0].onbeforedeactivate = function() {
          window.event.returnValue = false;
          $input[0].onbeforedeactivate = null;
        };
      }

      // ie 9+ and other browsers
      $e.preventDefault();
    });
  }

  // instance methods
  // ----------------

  _.mixin(Typeahead.prototype, {

    // ### private

    _onSuggestionClicked: function onSuggestionClicked(type, $el) {
      var datum;

      if (datum = this.dropdown.getDatumForSuggestion($el)) {
        this._select(datum);
      }
    },

    _onCursorMoved: function onCursorMoved() {
      var datum = this.dropdown.getDatumForCursor();

      this.input.clearHint();
      this.input.setInputValue(datum.value, true);

      this.eventBus.trigger('cursorchanged', datum.raw, datum.datasetName);
    },

    _onCursorRemoved: function onCursorRemoved() {
      this.input.resetInputValue();
      this._updateHint();
    },

    _onDatasetRendered: function onDatasetRendered() {
      this._updateHint();
    },

    _onOpened: function onOpened() {
      this._updateHint();

      this.eventBus.trigger('opened');
    },

    _onClosed: function onClosed() {
      this.input.clearHint();

      this.eventBus.trigger('closed');
    },

    _onFocused: function onFocused() {
      this.dropdown.open();
    },

    _onBlurred: function onBlurred() {
      this.dropdown.close();
    },

    _onEnterKeyed: function onEnterKeyed(type, $e) {
      var cursorDatum, topSuggestionDatum;

      cursorDatum = this.dropdown.getDatumForCursor();
      topSuggestionDatum = this.dropdown.getDatumForTopSuggestion();

      if (cursorDatum) {
        this._select(cursorDatum);
        $e.preventDefault();
      }

      else if (this.autoselect && topSuggestionDatum) {
        this._select(topSuggestionDatum);
        $e.preventDefault();
      }
    },

    _onTabKeyed: function onTabKeyed(type, $e) {
      var datum;

      if (datum = this.dropdown.getDatumForCursor()) {
        this._select(datum);
        $e.preventDefault();
      }

      else {
        this._autocomplete();
      }
    },

    _onEscKeyed: function onEscKeyed() {
      this.dropdown.close();
      this.input.resetInputValue();
    },

    _onUpKeyed: function onUpKeyed() {
      var query = this.input.getQuery();

      if(!this.dropdown.isOpen && query.length >= this.minLength) {
        this.dropdown.update(query);
      }

      this.dropdown.open();
      this.dropdown.moveCursorUp();
    },

    _onDownKeyed: function onDownKeyed() {
      var query = this.input.getQuery();

      if( !this.dropdown.isOpen && query.length >= this.minLength) {
        this.dropdown.update(query);
      }

      this.dropdown.open();
      this.dropdown.moveCursorDown();
    },

    _onLeftKeyed: function onLeftKeyed() {
      this.dir === 'rtl' && this._autocomplete();
    },

    _onRightKeyed: function onRightKeyed() {
      this.dir === 'ltr' && this._autocomplete();
    },

    _onQueryChanged: function onQueryChanged(e, query) {
      this.input.clearHint();
      this.dropdown.empty();
      query.length >= this.minLength && this.dropdown.update(query);
      this.dropdown.open();
      this._setLanguageDirection();
    },

    _onWhitespaceChanged: function onWhitespaceChanged() {
      this._updateHint();
      this.dropdown.open();
    },

    _setLanguageDirection: function setLanguageDirection() {
      var dir;

      if (this.dir !== (dir = this.input.getLanguageDirection())) {
        this.dir = dir;
        this.$node.css('direction', dir);
        this.dropdown.setLanguageDirection(dir);
      }
    },

    _updateHint: function updateHint() {
      var datum, inputValue, query, escapedQuery, frontMatchRegEx, match;

      datum = this.dropdown.getDatumForTopSuggestion();

      if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) {
        inputValue = this.input.getInputValue();
        query = Input.normalizeQuery(inputValue);
        escapedQuery = _.escapeRegExChars(query);

        frontMatchRegEx = new RegExp('^(?:' + escapedQuery + ')(.*$)', 'i');
        match = frontMatchRegEx.exec(datum.value);

        this.input.setHintValue(inputValue + (match ? match[1] : ''));
      }
    },

    _autocomplete: function autocomplete() {
      var hint, query, datum;

      hint = this.input.getHintValue();
      query = this.input.getQuery();

      if (hint && query !== hint && this.input.isCursorAtEnd()) {
        datum = this.dropdown.getDatumForTopSuggestion();
        datum && this.input.setInputValue(datum.value);

        this.eventBus.trigger('autocompleted', datum.raw, datum.datasetName);
      }
    },

    _select: function select(datum) {
      this.input.clearHint();
      this.input.setQuery(datum.value);
      this.input.setInputValue(datum.value, true);
      this.dropdown.close();

      this._setLanguageDirection();

      // #118: allow click event to bubble up to the body before removing
      // the suggestions otherwise we break event delegation
      _.defer(_.bind(this.dropdown.empty, this.dropdown));

      this.eventBus.trigger('selected', datum.raw, datum.datasetName);
    },

    // ### public

    open: function open() {
      this.dropdown.open();
    },

    close: function close() {
      this.dropdown.close();
    },

    getQuery: function getQuery() {
      return this.input.getQuery();
    },

    setQuery: function setQuery(val) {
      this.input.setInputValue(val);
    },

    destroy: function destroy() {
      this.input.destroy();
      this.dropdown.destroy();

      destroyDomStructure(this.$node);

      this.$node = null;
    }
  });

  return Typeahead;

  function buildDomStructure(input, withHint) {
    var $input, $wrapper, $dropdown, $hint;

    $input = $(input);
    $wrapper = $(html.wrapper).css(css.wrapper);
    $dropdown = $(html.dropdown).css(css.dropdown);
    $hint = $input.clone().css(css.hint).css(getBackgroundStyles($input));

    $hint
    .removeData()
    .addClass('tt-hint')
    .removeAttr('id name placeholder')
    .prop('disabled', true)
    .attr({ autocomplete: 'off', spellcheck: 'false' });

    // store the original values of the attrs that get modified
    // so modifications can be reverted on destroy
    $input.data(attrsKey, {
      dir: $input.attr('dir'),
      autocomplete: $input.attr('autocomplete'),
      spellcheck: $input.attr('spellcheck'),
      style: $input.attr('style')
    });

    $input
    .addClass('tt-input')
    .attr({ autocomplete: 'off', spellcheck: false })
    .css(withHint ? css.input : css.inputWithNoHint);

    // ie7 does not like it when dir is set to auto
    try { !$input.attr('dir') && $input.attr('dir', 'auto'); } catch (e) {}

    return $input
    .wrap($wrapper)
    .parent()
    .prepend(withHint ? $hint : null)
    .append($dropdown);
  }

  function getBackgroundStyles($el) {
    return {
      backgroundAttachment: $el.css('background-attachment'),
      backgroundClip: $el.css('background-clip'),
      backgroundColor: $el.css('background-color'),
      backgroundImage: $el.css('background-image'),
      backgroundOrigin: $el.css('background-origin'),
      backgroundPosition: $el.css('background-position'),
      backgroundRepeat: $el.css('background-repeat'),
      backgroundSize: $el.css('background-size')
    };
  }

  function destroyDomStructure($node) {
    var $input = $node.find('.tt-input');

    // need to remove attrs that weren't previously defined and
    // revert attrs that originally had a value
    _.each($input.data(attrsKey), function(val, key) {
      _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
    });

    $input
    .detach()
    .removeData(attrsKey)
    .removeClass('tt-input')
    .insertAfter($node);

    $node.remove();
  }
})();
<commit_msg>Trigger close after selected. Related to #566.<commit_after>/*
 * typeahead.js
 * https://github.com/twitter/typeahead.js
 * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
 */

var Typeahead = (function() {
  var attrsKey = 'ttAttrs';

  // constructor
  // -----------

  // THOUGHT: what if datasets could dynamically be added/removed?
  function Typeahead(o) {
    var $menu, $input, $hint, datasets;

    o = o || {};

    if (!o.input) {
      $.error('missing input');
    }

    this.autoselect = !!o.autoselect;
    this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
    this.$node = buildDomStructure(o.input, o.withHint);

    $menu = this.$node.find('.tt-dropdown-menu');
    $input = this.$node.find('.tt-input');
    $hint = this.$node.find('.tt-hint');

    this.eventBus = o.eventBus || new EventBus({ el: $input });

    this.dropdown = new Dropdown({ menu: $menu, datasets: o.datasets })
    .onSync('suggestionClicked', this._onSuggestionClicked, this)
    .onSync('cursorMoved', this._onCursorMoved, this)
    .onSync('cursorRemoved', this._onCursorRemoved, this)
    .onSync('opened', this._onOpened, this)
    .onSync('closed', this._onClosed, this)
    .onAsync('datasetRendered', this._onDatasetRendered, this);

    this.input = new Input({ input: $input, hint: $hint })
    .onSync('focused', this._onFocused, this)
    .onSync('blurred', this._onBlurred, this)
    .onSync('enterKeyed', this._onEnterKeyed, this)
    .onSync('tabKeyed', this._onTabKeyed, this)
    .onSync('escKeyed', this._onEscKeyed, this)
    .onSync('upKeyed', this._onUpKeyed, this)
    .onSync('downKeyed', this._onDownKeyed, this)
    .onSync('leftKeyed', this._onLeftKeyed, this)
    .onSync('rightKeyed', this._onRightKeyed, this)
    .onSync('queryChanged', this._onQueryChanged, this)
    .onSync('whitespaceChanged', this._onWhitespaceChanged, this);

    // #351: prevents input blur on menu click
    $menu.on('mousedown.tt', function($e) {
      if (_.isMsie() && _.isMsie() < 9) {
        $input[0].onbeforedeactivate = function() {
          window.event.returnValue = false;
          $input[0].onbeforedeactivate = null;
        };
      }

      // ie 9+ and other browsers
      $e.preventDefault();
    });
  }

  // instance methods
  // ----------------

  _.mixin(Typeahead.prototype, {

    // ### private

    _onSuggestionClicked: function onSuggestionClicked(type, $el) {
      var datum;

      if (datum = this.dropdown.getDatumForSuggestion($el)) {
        this._select(datum);
      }
    },

    _onCursorMoved: function onCursorMoved() {
      var datum = this.dropdown.getDatumForCursor();

      this.input.clearHint();
      this.input.setInputValue(datum.value, true);

      this.eventBus.trigger('cursorchanged', datum.raw, datum.datasetName);
    },

    _onCursorRemoved: function onCursorRemoved() {
      this.input.resetInputValue();
      this._updateHint();
    },

    _onDatasetRendered: function onDatasetRendered() {
      this._updateHint();
    },

    _onOpened: function onOpened() {
      this._updateHint();

      this.eventBus.trigger('opened');
    },

    _onClosed: function onClosed() {
      this.input.clearHint();

      this.eventBus.trigger('closed');
    },

    _onFocused: function onFocused() {
      this.dropdown.open();
    },

    _onBlurred: function onBlurred() {
      this.dropdown.close();
    },

    _onEnterKeyed: function onEnterKeyed(type, $e) {
      var cursorDatum, topSuggestionDatum;

      cursorDatum = this.dropdown.getDatumForCursor();
      topSuggestionDatum = this.dropdown.getDatumForTopSuggestion();

      if (cursorDatum) {
        this._select(cursorDatum);
        $e.preventDefault();
      }

      else if (this.autoselect && topSuggestionDatum) {
        this._select(topSuggestionDatum);
        $e.preventDefault();
      }
    },

    _onTabKeyed: function onTabKeyed(type, $e) {
      var datum;

      if (datum = this.dropdown.getDatumForCursor()) {
        this._select(datum);
        $e.preventDefault();
      }

      else {
        this._autocomplete();
      }
    },

    _onEscKeyed: function onEscKeyed() {
      this.dropdown.close();
      this.input.resetInputValue();
    },

    _onUpKeyed: function onUpKeyed() {
      var query = this.input.getQuery();

      if(!this.dropdown.isOpen && query.length >= this.minLength) {
        this.dropdown.update(query);
      }

      this.dropdown.open();
      this.dropdown.moveCursorUp();
    },

    _onDownKeyed: function onDownKeyed() {
      var query = this.input.getQuery();

      if( !this.dropdown.isOpen && query.length >= this.minLength) {
        this.dropdown.update(query);
      }

      this.dropdown.open();
      this.dropdown.moveCursorDown();
    },

    _onLeftKeyed: function onLeftKeyed() {
      this.dir === 'rtl' && this._autocomplete();
    },

    _onRightKeyed: function onRightKeyed() {
      this.dir === 'ltr' && this._autocomplete();
    },

    _onQueryChanged: function onQueryChanged(e, query) {
      this.input.clearHint();
      this.dropdown.empty();
      query.length >= this.minLength && this.dropdown.update(query);
      this.dropdown.open();
      this._setLanguageDirection();
    },

    _onWhitespaceChanged: function onWhitespaceChanged() {
      this._updateHint();
      this.dropdown.open();
    },

    _setLanguageDirection: function setLanguageDirection() {
      var dir;

      if (this.dir !== (dir = this.input.getLanguageDirection())) {
        this.dir = dir;
        this.$node.css('direction', dir);
        this.dropdown.setLanguageDirection(dir);
      }
    },

    _updateHint: function updateHint() {
      var datum, inputValue, query, escapedQuery, frontMatchRegEx, match;

      datum = this.dropdown.getDatumForTopSuggestion();

      if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) {
        inputValue = this.input.getInputValue();
        query = Input.normalizeQuery(inputValue);
        escapedQuery = _.escapeRegExChars(query);

        frontMatchRegEx = new RegExp('^(?:' + escapedQuery + ')(.*$)', 'i');
        match = frontMatchRegEx.exec(datum.value);

        this.input.setHintValue(inputValue + (match ? match[1] : ''));
      }
    },

    _autocomplete: function autocomplete() {
      var hint, query, datum;

      hint = this.input.getHintValue();
      query = this.input.getQuery();

      if (hint && query !== hint && this.input.isCursorAtEnd()) {
        datum = this.dropdown.getDatumForTopSuggestion();
        datum && this.input.setInputValue(datum.value);

        this.eventBus.trigger('autocompleted', datum.raw, datum.datasetName);
      }
    },

    _select: function select(datum) {
      this.input.clearHint();
      this.input.setQuery(datum.value);
      this.input.setInputValue(datum.value, true);

      this._setLanguageDirection();

      this.eventBus.trigger('selected', datum.raw, datum.datasetName);
      this.dropdown.close();

      // #118: allow click event to bubble up to the body before removing
      // the suggestions otherwise we break event delegation
      _.defer(_.bind(this.dropdown.empty, this.dropdown));
    },

    // ### public

    open: function open() {
      this.dropdown.open();
    },

    close: function close() {
      this.dropdown.close();
    },

    getQuery: function getQuery() {
      return this.input.getQuery();
    },

    setQuery: function setQuery(val) {
      this.input.setInputValue(val);
    },

    destroy: function destroy() {
      this.input.destroy();
      this.dropdown.destroy();

      destroyDomStructure(this.$node);

      this.$node = null;
    }
  });

  return Typeahead;

  function buildDomStructure(input, withHint) {
    var $input, $wrapper, $dropdown, $hint;

    $input = $(input);
    $wrapper = $(html.wrapper).css(css.wrapper);
    $dropdown = $(html.dropdown).css(css.dropdown);
    $hint = $input.clone().css(css.hint).css(getBackgroundStyles($input));

    $hint
    .removeData()
    .addClass('tt-hint')
    .removeAttr('id name placeholder')
    .prop('disabled', true)
    .attr({ autocomplete: 'off', spellcheck: 'false' });

    // store the original values of the attrs that get modified
    // so modifications can be reverted on destroy
    $input.data(attrsKey, {
      dir: $input.attr('dir'),
      autocomplete: $input.attr('autocomplete'),
      spellcheck: $input.attr('spellcheck'),
      style: $input.attr('style')
    });

    $input
    .addClass('tt-input')
    .attr({ autocomplete: 'off', spellcheck: false })
    .css(withHint ? css.input : css.inputWithNoHint);

    // ie7 does not like it when dir is set to auto
    try { !$input.attr('dir') && $input.attr('dir', 'auto'); } catch (e) {}

    return $input
    .wrap($wrapper)
    .parent()
    .prepend(withHint ? $hint : null)
    .append($dropdown);
  }

  function getBackgroundStyles($el) {
    return {
      backgroundAttachment: $el.css('background-attachment'),
      backgroundClip: $el.css('background-clip'),
      backgroundColor: $el.css('background-color'),
      backgroundImage: $el.css('background-image'),
      backgroundOrigin: $el.css('background-origin'),
      backgroundPosition: $el.css('background-position'),
      backgroundRepeat: $el.css('background-repeat'),
      backgroundSize: $el.css('background-size')
    };
  }

  function destroyDomStructure($node) {
    var $input = $node.find('.tt-input');

    // need to remove attrs that weren't previously defined and
    // revert attrs that originally had a value
    _.each($input.data(attrsKey), function(val, key) {
      _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
    });

    $input
    .detach()
    .removeData(attrsKey)
    .removeClass('tt-input')
    .insertAfter($node);

    $node.remove();
  }
})();
<|endoftext|>


<commit_before>/* eslint-disable react/prop-types, react/destructuring-assignment, import/no-extraneous-dependencies */
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import Events from '@storybook/core-events';
import Swatch from './Swatch';
import Constants from './constants';

const codeSample = `
import { storiesOf } from '@storybook/react-native';
import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds';

addDecorator(withBackgrounds);

storiesOf('First Component', module)
  .addParameters({
    backgrounds: [
      { name: 'warm', value: 'hotpink', default: true },
      { name: 'cool', value: 'deepskyblue' },
    ],
  })
  .add("First Button", () => <Button>Click me</Button>);
`.trim();

const Instructions = () => (
  <View>
    <Text style={{ fontSize: 16 }}>Setup Instructions</Text>
    <Text>
      Please add the background decorator definition to your story. The background decorate accepts
      an array of items, which should include a name for your color (preferably the css class name)
      and the corresponding color / image value.
    </Text>
    <Text>
      Below is an example of how to add the background decorator to your story definition.
    </Text>
    <Text>{codeSample}</Text>
  </View>
);

export default class BackgroundPanel extends Component {
  componentDidMount() {
    this.props.channel.on(Events.SELECT_STORY, this.onStorySelected);
  }

  componentWillUnmount() {
    this.props.channel.removeListener(Events.SELECT_STORY, this.onStorySelected);
  }

  setBackgroundFromSwatch = background => {
    this.props.channel.emit(Constants.UPDATE_BACKGROUND, background);
  };

  onStorySelected = selection => {
    this.setState({ selection });
  };

  render() {
    const { active, api } = this.props;

    if (!active) {
      return null;
    }

    const story = api
      .store()
      .getStoryAndParameters(this.state.selection.kind, this.state.selection.story);
    const backgrounds = story.parameters[Constants.PARAM_KEY];

    return (
      <View>
        {backgrounds ? (
          backgrounds.map(({ value, name }) => (
            <View key={`${name} ${value}`}>
              <Swatch value={value} name={name} setBackground={this.setBackgroundFromSwatch} />
            </View>
          ))
        ) : (
          <Instructions />
        )}
      </View>
    );
  }
}
<commit_msg>FIX linting<commit_after>/* eslint-disable react/prop-types, react/destructuring-assignment, import/no-extraneous-dependencies */
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import Events from '@storybook/core-events';
import Swatch from './Swatch';
import BackGroundEvents, { PARAM_KEY } from './constants';

const codeSample = `
import { storiesOf } from '@storybook/react-native';
import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds';

addDecorator(withBackgrounds);

storiesOf('First Component', module)
  .addParameters({
    backgrounds: [
      { name: 'warm', value: 'hotpink', default: true },
      { name: 'cool', value: 'deepskyblue' },
    ],
  })
  .add("First Button", () => <Button>Click me</Button>);
`.trim();

const Instructions = () => (
  <View>
    <Text style={{ fontSize: 16 }}>Setup Instructions</Text>
    <Text>
      Please add the background decorator definition to your story. The background decorate accepts
      an array of items, which should include a name for your color (preferably the css class name)
      and the corresponding color / image value.
    </Text>
    <Text>
      Below is an example of how to add the background decorator to your story definition.
    </Text>
    <Text>{codeSample}</Text>
  </View>
);

export default class BackgroundPanel extends Component {
  componentDidMount() {
    this.props.channel.on(Events.SELECT_STORY, this.onStorySelected);
  }

  componentWillUnmount() {
    this.props.channel.removeListener(Events.SELECT_STORY, this.onStorySelected);
  }

  setBackgroundFromSwatch = background => {
    this.props.channel.emit(BackGroundEvents.UPDATE_BACKGROUND, background);
  };

  onStorySelected = selection => {
    this.setState({ selection });
  };

  render() {
    const { active, api } = this.props;

    if (!active) {
      return null;
    }

    const story = api
      .store()
      .getStoryAndParameters(this.state.selection.kind, this.state.selection.story);
    const backgrounds = story.parameters[PARAM_KEY];

    return (
      <View>
        {backgrounds ? (
          backgrounds.map(({ value, name }) => (
            <View key={`${name} ${value}`}>
              <Swatch value={value} name={name} setBackground={this.setBackgroundFromSwatch} />
            </View>
          ))
        ) : (
          <Instructions />
        )}
      </View>
    );
  }
}
<|endoftext|>


<commit_before>var addressPoints = [
  [
    "New Orleans, USA",
    29.9511,
    90.0715
  ],
  [
    "New York, USA",
    40.7128,
    74.0059
  ],
  [
    "Las Vegas, USA",
    36.1699,
    115.1398
  ],
  [
    "Philadelphia, USA",
    39.9526,
    75.1652
  ],
  [
    "Chicago, USA",
    41.8781,
    87.6298
  ],
  [
    "San Juan, Puerto Rico",
    18.4655,
    66.1057
  ],
  [
    "Santiago, Chile",
    33.4489,
    70.6693
  ],

];<commit_msg>fixes coordinates<commit_after>var addressPoints = [
  [
    "New Orleans, USA",
    29.9511,
    -90.0715
  ],
  [
    "New York, USA",
    40.7128,
    -74.0059
  ],
  [
    "Las Vegas, USA",
    36.1699,
    -115.1398
  ],
  [
    "Philadelphia, USA",
    39.9526,
    -75.1652
  ],
  [
    "Chicago, USA",
    41.8781,
    -87.6298
  ],
  [
    "San Juan, Puerto Rico",
    18.4655,
    -66.1057
  ],
  [
    "Santiago, Chile",
    -33.4489,
    -70.6693
  ],

];<|endoftext|>


<commit_before>const select = require(`unist-util-select`)
const path = require(`path`)
const isRelativeUrl = require(`is-relative-url`)
const _ = require(`lodash`)
const { responsiveSizes } = require(`gatsby-plugin-sharp`)
const Promise = require(`bluebird`)

// If the image is relative (not hosted elsewhere)
// 1. Find the image file
// 2. Find the image's size
// 3. Filter out any responsive image sizes that are greater than the image's width
// 4. Create the responsive images.
// 5. Set the html w/ aspect ratio helper.
module.exports = ({
  files,
  markdownNode,
  markdownAST,
  pluginOptions,
  linkPrefix,
  getNode,
}) => {
  const defaults = {
    maxWidth: 650,
    wrapperStyle: ``,
    backgroundColor: `white`,
  }
  const options = _.defaults(pluginOptions, defaults)

  const imageNodes = select(markdownAST, `image`)
  return Promise.all(
    imageNodes.map(
      node =>
        new Promise((resolve, reject) => {
          // Ignore gifs as we can't process them.
          if (isRelativeUrl(node.url) && node.url.slice(-3) !== `gif`) {
            const imagePath = path.posix.join(
              getNode(markdownNode.parent).dir,
              node.url
            )
            const imageNode = _.find(files, file => {
              if (file && file.absolutePath) {
                return file.absolutePath === imagePath
              }
              return null
            })
            if (!imageNode || !imageNode.absolutePath) {
              return resolve()
            }

            responsiveSizes({
              file: imageNode,
              args: options,
            }).then(responsiveSizesResult => {
              // Calculate the paddingBottom %
              const ratio = `${1 / responsiveSizesResult.aspectRatio * 100}%`

              const originalImg = responsiveSizesResult.originalImage
              const fallbackSrc = responsiveSizesResult.src
              const srcSet = responsiveSizesResult.srcSet

              // TODO
              // add support for sub-plugins having a gatsby-node.js so can add a
              // bit of js/css to add blurry fade-in.
              // https://www.perpetual-beta.org/weblog/silky-smooth-image-loading.html
              //
              // TODO make linking to original image optional.

              // Construct new image node w/ aspect ratio placeholder
              const rawHTML = `

          `
              // const rawHTML = `
              //

              // `

              node.data = {
                hChildren: [{ type: `raw`, value: rawHTML }],
              }
              // Set type to unknown so mdast-util-to-hast will treat this node as a
              // div not an image — it gets quite confused otherwise and tries to put
              // the raw html above as a child of the image which browsers
              // justifiably squawk at.
              node.type = `unknown`
              return resolve()
            })
          } else {
            // Image isn't relative so there's nothing for us to do.
            return resolve()
          }
        })
    )
  )
}
<commit_msg>Fix conversion (#1154)<commit_after>const select = require(`unist-util-select`)
const path = require(`path`)
const isRelativeUrl = require(`is-relative-url`)
const _ = require(`lodash`)
const { responsiveSizes } = require(`gatsby-plugin-sharp`)
const Promise = require(`bluebird`)

// If the image is relative (not hosted elsewhere)
// 1. Find the image file
// 2. Find the image's size
// 3. Filter out any responsive image sizes that are greater than the image's width
// 4. Create the responsive images.
// 5. Set the html w/ aspect ratio helper.
module.exports = ({
  files,
  markdownNode,
  markdownAST,
  pluginOptions,
  linkPrefix,
  getNode,
}) => {
  const defaults = {
    maxWidth: 650,
    wrapperStyle: ``,
    backgroundColor: `white`,
  }
  const options = _.defaults(pluginOptions, defaults)

  const imageNodes = select(markdownAST, `image`)
  return Promise.all(
    imageNodes.map(
      node =>
        new Promise((resolve, reject) => {
          // Ignore gifs as we can't process them.
          if (isRelativeUrl(node.url) && node.url.slice(-3) !== `gif`) {
            const imagePath = path.posix.join(
              getNode(markdownNode.parent).dir,
              node.url
            )
            const imageNode = _.find(files, file => {
              if (file && file.absolutePath) {
                return file.absolutePath === imagePath
              }
              return null
            })
            if (!imageNode || !imageNode.absolutePath) {
              return resolve()
            }

            responsiveSizes({
              file: imageNode,
              args: options,
            }).then(responsiveSizesResult => {
              // Calculate the paddingBottom %
              const ratio = `${1 / responsiveSizesResult.aspectRatio * 100}%`

              const originalImg = responsiveSizesResult.originalImage
              const fallbackSrc = responsiveSizesResult.src
              const srcSet = responsiveSizesResult.srcSet

              // TODO
              // add support for sub-plugins having a gatsby-node.js so can add a
              // bit of js/css to add blurry fade-in.
              // https://www.perpetual-beta.org/weblog/silky-smooth-image-loading.html
              //
              // TODO make linking to original image optional.

              // Construct new image node w/ aspect ratio placeholder
              const rawHTML = `

          `
              // const rawHTML = `
              //

              // `

              node.data = {
                hChildren: [{ type: `raw`, value: rawHTML }],
              }
              // Set type to unknown so mdast-util-to-hast will treat this node as a
              // div not an image — it gets quite confused otherwise and tries to put
              // the raw html above as a child of the image which browsers
              // justifiably squawk at.
              node.type = `unknown`
              return resolve()
            })
          } else {
            // Image isn't relative so there's nothing for us to do.
            return resolve()
          }
        })
    )
  )
}
<|endoftext|>


<commit_before>/*jslint browser: true, sloppy: true, todo: true, devel: true */
/*global $, it, describe, xit, xdescribe, expect, beforeEach, spyOn, loadFixtures, ResizableInteractive */

//= require interactive-sizing

describe('InteractiveObject', function () {
    var interactive;

    describe('when the interactive is a video', function () {
        beforeEach(function () {
            loadFixtures('interactive-sizing-video.html');
            interactive = new ResizableInteractive($('video[data-aspect-ratio]'));
        });

        // Attributes
        it('has defined attributes', function () {
            expect(interactive.element).toBe('video');
            expect(interactive.aspectRatio).toBeDefined();
            expect(interactive.currWidth).toBeDefined();
            expect(interactive.targetHeight).toBeDefined();
        });

        describe('fixSize()', function () {
            beforeEach(function () {
                interactive.fixSize();
            });

            it('has height matching targetHeight', function () {
                expect(interactive.element).toHaveAttr('height', interactive.targetHeight);
            });

            it('has width matching currWidth', function () {
                expect(interactive.element).toHaveAttr('width', interactive.currWidth);
            });
        });
    });

    describe('when the interactive is an iframe', function () {
        // NOTE: PhantomJS 1.8.1 didn't like this fixture. PhantomJS 1.9.0 worked. If this
        // set of specs breaks, try upgrading PhantomJS.
        beforeEach(function () {
            loadFixtures('interactive-sizing-iframe.html');
            interactive = new ResizableInteractive($('iframe[data-aspect-ratio]'));
        });

        // Attributes
        it('has defined attributes', function () {
            expect(interactive.element).toBe('iframe');
            expect(interactive.aspectRatio).toBeDefined();
            expect(interactive.currWidth).toBeDefined();
            expect(interactive.targetHeight).toBeDefined();
        });

        describe('fixSize()', function () {
            beforeEach(function () {
                interactive.fixSize();
            });

            it('has height matching targetHeight', function () {
                expect(interactive.element).toHaveAttr('height', interactive.targetHeight);
            });

            it('has width matching currWidth', function () {
                expect(interactive.element).toHaveAttr('width', interactive.currWidth);
            });
        });
    });
});<commit_msg>jasmine-jquery changed matcher<commit_after>/*jslint browser: true, sloppy: true, todo: true, devel: true */
/*global $, it, describe, xit, xdescribe, expect, beforeEach, spyOn, loadFixtures, ResizableInteractive */

//= require interactive-sizing

describe('InteractiveObject', function () {
    var interactive;

    describe('when the interactive is a video', function () {
        beforeEach(function () {
            loadFixtures('interactive-sizing-video.html');
            interactive = new ResizableInteractive($('video[data-aspect-ratio]'));
        });

        // Attributes
        it('has defined attributes', function () {
            expect(interactive.element).toEqual('video');
            expect(interactive.aspectRatio).toBeDefined();
            expect(interactive.currWidth).toBeDefined();
            expect(interactive.targetHeight).toBeDefined();
        });

        describe('fixSize()', function () {
            beforeEach(function () {
                interactive.fixSize();
            });

            it('has height matching targetHeight', function () {
                expect(interactive.element).toHaveAttr('height', interactive.targetHeight);
            });

            it('has width matching currWidth', function () {
                expect(interactive.element).toHaveAttr('width', interactive.currWidth);
            });
        });
    });

    describe('when the interactive is an iframe', function () {
        // NOTE: PhantomJS 1.8.1 didn't like this fixture. PhantomJS 1.9.0 worked. If this
        // set of specs breaks, try upgrading PhantomJS.
        beforeEach(function () {
            loadFixtures('interactive-sizing-iframe.html');
            interactive = new ResizableInteractive($('iframe[data-aspect-ratio]'));
        });

        // Attributes
        it('has defined attributes', function () {
            expect(interactive.element).toBe('iframe');
            expect(interactive.aspectRatio).toBeDefined();
            expect(interactive.currWidth).toBeDefined();
            expect(interactive.targetHeight).toBeDefined();
        });

        describe('fixSize()', function () {
            beforeEach(function () {
                interactive.fixSize();
            });

            it('has height matching targetHeight', function () {
                expect(interactive.element).toHaveAttr('height', interactive.targetHeight);
            });

            it('has width matching currWidth', function () {
                expect(interactive.element).toHaveAttr('width', interactive.currWidth);
            });
        });
    });
});<|endoftext|>


<commit_before>(function(root, factory) {
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else {
        root.canvid = factory();
    }
}(this, function() {

    function canvid(params) {
        var defaultOptions = {
                width : 800,
                height : 450,
                selector: '.canvid-wrapper'
            },
            firstPlay = true,
            control = {
                play: function() {
                    console.log('Cannot play before images are loaded');
                }
            },
            _opts = merge(defaultOptions, params),
            el = typeof _opts.selector === 'string' ? document.querySelector(_opts.selector) : _opts.selector;

        if (!el) {
            return console.warn('Error. No element found for selector', _opts.selector);
        }

        if (!_opts.videos) {
            return console.warn('Error. You need to define at least one video object');
        }

        if (hasCanvas()) {

            loadImages(_opts.videos, function(err, images) {
                if (err) return console.warn('Error while loading video sources.', err);

                var ctx = initCanvas(),
                    requestAnimationFrame = reqAnimFrame();

                control.play = function(key, reverse, fps) {
                    if (control.pause) control.pause(); // pause current vid

                    var img = images[key],
                        opts = _opts.videos[key],
                        frameWidth = img.width / opts.cols,
                        frameHeight = img.height / Math.ceil(opts.frames / opts.cols);

                    var curFps = fps || opts.fps || 15,
                        curFrame = reverse ? opts.frames - 1 : 0,
                        wait = 0,
                        playing = true,
                        loops = 0,
                        delay = 60 / curFps;

                    requestAnimationFrame(frame);

                    control.resume = function() {
                        playing = true;
                        requestAnimationFrame(frame);
                    };

                    control.pause = function() {
                        playing = false;
                        requestAnimationFrame(frame);
                    };

                    control.isPlaying = function() {
                        return playing;
                    };

                    control.destroy = function(){
                        control.pause();
                        removeCanvid();
                    };

                    control.getCurrentFrame = function(){
                        return curFrame;
                    };

                    control.setCurrentFrame = function(frameNumber){
                        if(frameNumber < 0 || frameNumber >= opts.frames){
                            return false;
                        }

                        if(!isPlaying){
                            drawFrame(frameNumber);
                        }

                        curFrame = frameNumber;
                    };

                    if (firstPlay) {
                        firstPlay = false;
                        hideChildren();
                    }

                    function frame() {
                        if (!wait) {
                            drawFrame(curFrame);
                            curFrame = (+curFrame + (reverse ? -1 : 1));
                            if (curFrame < 0) curFrame += +opts.frames;
                            if (curFrame >= opts.frames) curFrame = 0;
                            if (reverse ? curFrame == opts.frames - 1 : !curFrame) loops++;
                            if (opts.loops && loops >= opts.loops){
                                playing = false;
                                if(opts.onEnd && isFunction(opts.onEnd)){
                                    opts.onEnd();
                                }
                            }
                        }
                        wait = (wait + 1) % delay;
                        if (playing && opts.frames > 1) requestAnimationFrame(frame);
                    }

                    function drawFrame(f) {
                        var fx = Math.floor(f % opts.cols) * frameWidth,
                            fy = Math.floor(f / opts.cols) * frameHeight;

                        ctx.clearRect(0, 0, _opts.width, _opts.height); // clear frame
                        ctx.drawImage(img, fx, fy, frameWidth, frameHeight, 0, 0, _opts.width, _opts.height);
                    }

                }; // end control.play

                if (isFunction(_opts.loaded)) {
                    _opts.loaded(control);
                }

            }); // end loadImages

        } else if (opts.srcGif) {
            var fallbackImage = new Image();
            fallbackImage.src = opts.srcGif;

            el.appendChild(fallbackImage);
        }

        function loadImages(imageList, callback) {
            var images = {},
                imagesToLoad = Object.keys(imageList).length;

            if(imagesToLoad === 0) {
                return callback('You need to define at least one video object.');
            }

            for (var key in imageList) {
                images[key] = new Image();
                images[key].onload = checkCallback;
                images[key].onerror = callback;
                images[key].src = imageList[key].src;
            }

            function checkCallback() {
                imagesToLoad--;
                if (imagesToLoad === 0) {
                    callback(null, images);
                }
            }
        }

        function initCanvas() {
            var canvas = document.createElement('canvas');
            canvas.width = _opts.width;
            canvas.height = _opts.height;
            canvas.classList.add('canvid');

            el.appendChild(canvas);

            return canvas.getContext('2d');
        }

        function hideChildren() {
            [].forEach.call(el.children, function(child){
                if(!child.classList.contains('canvid') ){
                    child.style.display = 'none';
                }
            });
        }

        function removeCanvid(){
            [].forEach.call(el.children, function(child){
                if(child.classList.contains('canvid') ){
                    el.removeChild(child);
                }
            });
        }

        function reqAnimFrame() {
            return window.requestAnimationFrame
                || window.webkitRequestAnimationFrame
                || window.mozRequestAnimationFrame
                || window.msRequestAnimationFrame
                || function(callback) {
                    return setTimeout(callback, 1000 / 60);
                };
        }

        function hasCanvas() {
            // taken from Modernizr
            var elem = document.createElement('canvas');
            return !!(elem.getContext && elem.getContext('2d'));
        }

        function isFunction(obj) {
            // taken from jQuery
            return typeof obj === 'function' || !!(obj && obj.constructor && obj.call && obj.apply);
        }

        function merge() {
            var obj = {},
                key;

            for (var i = 0; i < arguments.length; i++) {
                for (key in arguments[i]) {
                    if (arguments[i].hasOwnProperty(key)) {
                        obj[key] = arguments[i][key];
                    }
                }
            }
            return obj;
        }

        return control;
    }; // end canvid function

    return canvid;
})); // end factory function<commit_msg>Fix incorrect reference to isPlaying variable<commit_after>(function(root, factory) {
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else {
        root.canvid = factory();
    }
}(this, function() {

    function canvid(params) {
        var defaultOptions = {
                width : 800,
                height : 450,
                selector: '.canvid-wrapper'
            },
            firstPlay = true,
            control = {
                play: function() {
                    console.log('Cannot play before images are loaded');
                }
            },
            _opts = merge(defaultOptions, params),
            el = typeof _opts.selector === 'string' ? document.querySelector(_opts.selector) : _opts.selector;

        if (!el) {
            return console.warn('Error. No element found for selector', _opts.selector);
        }

        if (!_opts.videos) {
            return console.warn('Error. You need to define at least one video object');
        }

        if (hasCanvas()) {

            loadImages(_opts.videos, function(err, images) {
                if (err) return console.warn('Error while loading video sources.', err);

                var ctx = initCanvas(),
                    requestAnimationFrame = reqAnimFrame();

                control.play = function(key, reverse, fps) {
                    if (control.pause) control.pause(); // pause current vid

                    var img = images[key],
                        opts = _opts.videos[key],
                        frameWidth = img.width / opts.cols,
                        frameHeight = img.height / Math.ceil(opts.frames / opts.cols);

                    var curFps = fps || opts.fps || 15,
                        curFrame = reverse ? opts.frames - 1 : 0,
                        wait = 0,
                        playing = true,
                        loops = 0,
                        delay = 60 / curFps;

                    requestAnimationFrame(frame);

                    control.resume = function() {
                        playing = true;
                        requestAnimationFrame(frame);
                    };

                    control.pause = function() {
                        playing = false;
                        requestAnimationFrame(frame);
                    };

                    control.isPlaying = function() {
                        return playing;
                    };

                    control.destroy = function(){
                        control.pause();
                        removeCanvid();
                    };

                    control.getCurrentFrame = function(){
                        return curFrame;
                    };

                    control.setCurrentFrame = function(frameNumber){
                        if(frameNumber < 0 || frameNumber >= opts.frames){
                            return false;
                        }

                        if(!control.isPlaying){
                            drawFrame(frameNumber);
                        }

                        curFrame = frameNumber;
                    };

                    if (firstPlay) {
                        firstPlay = false;
                        hideChildren();
                    }

                    function frame() {
                        if (!wait) {
                            drawFrame(curFrame);
                            curFrame = (+curFrame + (reverse ? -1 : 1));
                            if (curFrame < 0) curFrame += +opts.frames;
                            if (curFrame >= opts.frames) curFrame = 0;
                            if (reverse ? curFrame == opts.frames - 1 : !curFrame) loops++;
                            if (opts.loops && loops >= opts.loops){
                                playing = false;
                                if(opts.onEnd && isFunction(opts.onEnd)){
                                    opts.onEnd();
                                }
                            }
                        }
                        wait = (wait + 1) % delay;
                        if (playing && opts.frames > 1) requestAnimationFrame(frame);
                    }

                    function drawFrame(f) {
                        var fx = Math.floor(f % opts.cols) * frameWidth,
                            fy = Math.floor(f / opts.cols) * frameHeight;

                        ctx.clearRect(0, 0, _opts.width, _opts.height); // clear frame
                        ctx.drawImage(img, fx, fy, frameWidth, frameHeight, 0, 0, _opts.width, _opts.height);
                    }

                }; // end control.play

                if (isFunction(_opts.loaded)) {
                    _opts.loaded(control);
                }

            }); // end loadImages

        } else if (opts.srcGif) {
            var fallbackImage = new Image();
            fallbackImage.src = opts.srcGif;

            el.appendChild(fallbackImage);
        }

        function loadImages(imageList, callback) {
            var images = {},
                imagesToLoad = Object.keys(imageList).length;

            if(imagesToLoad === 0) {
                return callback('You need to define at least one video object.');
            }

            for (var key in imageList) {
                images[key] = new Image();
                images[key].onload = checkCallback;
                images[key].onerror = callback;
                images[key].src = imageList[key].src;
            }

            function checkCallback() {
                imagesToLoad--;
                if (imagesToLoad === 0) {
                    callback(null, images);
                }
            }
        }

        function initCanvas() {
            var canvas = document.createElement('canvas');
            canvas.width = _opts.width;
            canvas.height = _opts.height;
            canvas.classList.add('canvid');

            el.appendChild(canvas);

            return canvas.getContext('2d');
        }

        function hideChildren() {
            [].forEach.call(el.children, function(child){
                if(!child.classList.contains('canvid') ){
                    child.style.display = 'none';
                }
            });
        }

        function removeCanvid(){
            [].forEach.call(el.children, function(child){
                if(child.classList.contains('canvid') ){
                    el.removeChild(child);
                }
            });
        }

        function reqAnimFrame() {
            return window.requestAnimationFrame
                || window.webkitRequestAnimationFrame
                || window.mozRequestAnimationFrame
                || window.msRequestAnimationFrame
                || function(callback) {
                    return setTimeout(callback, 1000 / 60);
                };
        }

        function hasCanvas() {
            // taken from Modernizr
            var elem = document.createElement('canvas');
            return !!(elem.getContext && elem.getContext('2d'));
        }

        function isFunction(obj) {
            // taken from jQuery
            return typeof obj === 'function' || !!(obj && obj.constructor && obj.call && obj.apply);
        }

        function merge() {
            var obj = {},
                key;

            for (var i = 0; i < arguments.length; i++) {
                for (key in arguments[i]) {
                    if (arguments[i].hasOwnProperty(key)) {
                        obj[key] = arguments[i][key];
                    }
                }
            }
            return obj;
        }

        return control;
    }; // end canvid function

    return canvid;
})); // end factory function<|endoftext|>


<commit_before>var src_pref;
var msg_read;
var username;
var MSG_QUEUE_SIZE = 1000;

// Show notification
chrome.runtime.sendMessage({
  type: 'notification',
  message: { title: '消息捕获模块已加载', text: '至页面' + window.location.protocol + '//' + window.location.host + window.location.pathname }
});

// Whether is the target page
if (document.getElementById('chatArea')) {
  // Initialize
  src_pref = window.location.protocol + '//' + window.location.host;
  msg_read = new Array(MSG_QUEUE_SIZE);

  // Send heartbeats
  setInterval(function() {
    if ($('#chatArea').css('visibility') == 'visible') {
      chrome.runtime.sendMessage({ type: 'heartbeat' });
    }
  }, 3000);

  // Listen to DOM mutations
  new MutationObserver(function() {
    sendMessage(captureMessage());
  }).observe(document.getElementById('chatArea'), { 'childList': true, 'subtree': true });
}

function captureMessage() {
  username = username || /username=(@\w+)&/.exec($('.header .avatar img').attr('src'))[1];

  var msg = {
    id: '',
    user: {
      name: $('#chatArea .message:last .avatar').attr('title'),
      avatar: src_pref + $('#chatArea .message:last .avatar').attr('src'),
      isSelf: false
    },
    content: {
      text: '',
      image: ''
    },
    selector: $('#chatArea .message:last > .content > .ng-scope[data-cm]').attr('data-cm')
  };

  // Get unique selector
  var json = $.parseJSON(msg.selector);
  msg.id = json.msgId;
  msg.user.isSelf = username === json.actualSender;

  // HACK for issue #18
  if (msg.id.length < 18) return null;

  // Check the message if read
  for (i = 0; i < msg_read.length; i++) {
    if (msg_read[i] === msg.id) return null;
  }

  // Mark as read
  msg_read.shift();
  msg_read.push(msg.id);

  // Handle text message
  if ($('#chatArea .message:last .plain').length) {
    if ($('#chatArea .message:last .plain pre img').length) {
      $('#chatArea .message:last .plain pre img').each(function() {
        if (/^\//.test($(this).attr('src'))) {
          $(this).attr('src', src_pref + $(this).attr('src'));
        }
      });
    }
    if ($('#chatArea .message:last .plain pre img.emoji').length) {
      $('#chatArea .message:last .plain pre img.emoji').each(function() {
        $(this).replaceWith('');
      });
    }
    msg.content.text = $('#chatArea .message:last .plain pre').html();

    // HACK for aidistan/browser-weixin-danmu#1
    if (
      /^\[Received a sticker. View on phone\]$/.test(msg.content.text) ||
      /^\[Sent a sticker. View on phone\]$/.test(msg.content.text)
    ) {
      return null;
    }

  // Handle image message
  } else if ($('#chatArea .message:last .picture .msg-img').length &&
  /^\//.test($('#chatArea .message:last .picture .msg-img').attr('src'))) {
    msg.content.img = src_pref + $('#chatArea .message:last .picture .msg-img').attr('src').split('&type=slave')[0];

  // Handle custom_emoji message
  } else if ($('#chatArea .message:last .emoticon .custom_emoji').length) {
    msg.content.img = src_pref + $('#chatArea .message:last .custom_emoji').attr('src');

  // Capture nothing
  } else {
    return null;
  }

  return msg;
}

function sendMessage(msg) {
  if (msg) {
    setTimeout(function() {
      msg.room = $("[data-cm='" + msg.selector + "']").length > 0 ? 'inside' : 'outside';
      chrome.runtime.sendMessage({ type: 'weixin', message: msg });
    }, 3000);
  }
}
<commit_msg>Fix the image display issue<commit_after>var src_pref;
var msg_read;
var username;
var MSG_QUEUE_SIZE = 1000;

// Show notification
chrome.runtime.sendMessage({
  type: 'notification',
  message: { title: '消息捕获模块已加载', text: '至页面' + window.location.protocol + '//' + window.location.host + window.location.pathname }
});

// Whether is the target page
if (document.getElementById('chatArea')) {
  // Initialize
  src_pref = window.location.protocol + '//' + window.location.host;
  msg_read = new Array(MSG_QUEUE_SIZE);

  // Send heartbeats
  setInterval(function() {
    if ($('#chatArea').css('visibility') == 'visible') {
      chrome.runtime.sendMessage({ type: 'heartbeat' });
    }
  }, 3000);

  // Listen to DOM mutations
  new MutationObserver(function() {
    sendMessage(captureMessage());
  }).observe(document.getElementById('chatArea'), { 'childList': true, 'subtree': true });
}

function captureMessage() {
  username = username || /username=(@\w+)&/.exec($('.header .avatar img').attr('src'))[1];

  var msg = {
    id: '',
    user: {
      name: $('#chatArea .message:last .avatar').attr('title'),
      avatar: src_pref + $('#chatArea .message:last .avatar').attr('src'),
      isSelf: false
    },
    content: {
      text: '',
      image: ''
    },
    selector: $('#chatArea .message:last > .content > .ng-scope[data-cm]').attr('data-cm')
  };

  // Get unique selector
  var json = $.parseJSON(msg.selector);
  msg.id = json.msgId;
  msg.user.isSelf = username === json.actualSender;

  // HACK for issue #18
  if (msg.id.length < 18) return null;

  // Check the message if read
  for (i = 0; i < msg_read.length; i++) {
    if (msg_read[i] === msg.id) return null;
  }

  // Mark as read
  msg_read.shift();
  msg_read.push(msg.id);

  // Handle text message
  if ($('#chatArea .message:last .plain').length) {
    if ($('#chatArea .message:last .plain pre img').length) {
      $('#chatArea .message:last .plain pre img').each(function() {
        if (/^\//.test($(this).attr('src'))) {
          $(this).attr('src', src_pref + $(this).attr('src'));
        }
      });
    }
    if ($('#chatArea .message:last .plain pre img.emoji').length) {
      $('#chatArea .message:last .plain pre img.emoji').each(function() {
        $(this).replaceWith('');
      });
    }
    msg.content.text = $('#chatArea .message:last .plain pre').html();

    // HACK for aidistan/browser-weixin-danmu#1
    if (
      /^\[Received a sticker. View on phone\]$/.test(msg.content.text) ||
      /^\[Sent a sticker. View on phone\]$/.test(msg.content.text)
    ) {
      return null;
    }

  // Handle image message
  } else if ($('#chatArea .message:last .picture .msg-img').length &&
  /^\//.test($('#chatArea .message:last .picture .msg-img').attr('src'))) {
    msg.content.image = src_pref + $('#chatArea .message:last .picture .msg-img').attr('src').split('&type=slave')[0];

  // Handle custom_emoji message
  } else if ($('#chatArea .message:last .emoticon .custom_emoji').length) {
    msg.content.image = src_pref + $('#chatArea .message:last .custom_emoji').attr('src');

  // Capture nothing
  } else {
    return null;
  }

  return msg;
}

function sendMessage(msg) {
  if (msg) {
    setTimeout(function() {
      msg.room = $("[data-cm='" + msg.selector + "']").length > 0 ? 'inside' : 'outside';
      chrome.runtime.sendMessage({ type: 'weixin', message: msg });
    }, 3000);
  }
}
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var should = require('chai').should();
var request = require('request');
var cacheController = require('../../app/cache/cacheController');

var controller = new cacheController();

var urlBasic = "http://httpbin.org/";
var url304 = "http://httpbin.org/cache";

describe('request', function() {

  this.timeout(10000);

  it('should exist', function(done) {
    request(url304, function(error, response, body) {
      if (!error) {
        should.exist(response);
        done();
      } else {
        console.log("ERROR:");
        done();
      }
    });
  });
  it('should return a response code of 304 and have no body', function(done) {
    request(url304, function(error, response, body) {
      var options = {
        uri: url304,
        headers: {'If-Modified-Since': response.headers['last-modified']}
      };
      request(options, function(error, response, body) {
        expect(response.statusCode).to.equal(304);
        expect(response.body.length).to.equal(0);
        done();
      });
    });
  });
    it('should return a response code of 200', function(done) {
    var res;
    request.get(url304)
    .on('response', function(response) {
      expect(response.statusCode).to.equal(200);
      done();
    });
  });
});

describe('cacheController', function() {

  this.timeout(10000);

  beforeEach(function() {
    controller.clearCache();
  });

  describe('processNewRequest', function() {

    it('should create a new cache element', function(done) {
      controller.processNewRequest(url304, function(response) {
        expect(response.statusCode).to.equal(200);
        done();
      });
    });
    it('should return a cached element', function(done) {

      controller.processNewRequest(url304, function(response, statusCode) {

        controller.processNewRequest(url304, function(response, statusCode) {

          expect(statusCode).to.equal('200 (cache)');
          done();
        });
      });
    });
  });

  describe('checkUpdated', function() {
    var node;

    it('should return a response code of 304', function(done) {
      controller.clearCache();
      request(url304, function(error, response, body) {
        node = controller.Cache.createNewEntry(url304, response.headers['last-modified'], 'data');

        controller.checkUpdated(url304, node, function(response, statusCode) {
          expect(statusCode).to.equal('200 (cache)');
          done();
        });
      });
    });
    it('should return a response code of 200', function(done) {
      controller.clearCache();

      request(urlBasic, function(error, response, body) {

        node = controller.Cache.createNewEntry(url304, response.headers.date, 'data');

        controller.checkUpdated(urlBasic, node, function(response) {
          expect(response.statusCode).to.equal(200);
          done();
        });
      });
    });
  });
});
<commit_msg>Update cacheController_spec.js<commit_after>var expect = require('chai').expect;
var should = require('chai').should();
var request = require('request');
var cacheController = require('../../app/cache/cacheController');

var controller = new cacheController();

var urlBasic = "http://httpbin.org/";
var url304 = "http://httpbin.org/cache";

describe('request', function() {

  this.timeout(10000);

  it('should exist', function(done) {
    request(url304, function(error, response, body) {
      if (!error) {
        should.exist(response);
        done();
      } else {
        console.log("ERROR:");
        done();
      }
    });
  });
  it('should return a response code of 304 and have no body', function(done) {
    request(url304, function(error, response, body) {
      var options = {
        uri: url304,
        headers: {'If-Modified-Since': response.headers['last-modified']}
      };
      request(options, function(error, response, body) {
        expect(response.statusCode).to.equal(304);
        expect(response.body.length).to.equal(0);
        done();
      });
    });
  });
  it('should return a response code of 200', function(done) {
    var res;
    request.get(url304)
    .on('response', function(response) {
      expect(response.statusCode).to.equal(200);
      done();
    });
  });
});

describe('cacheController', function() {

  this.timeout(10000);

  beforeEach(function() {
    controller.clearCache();
  });

  describe('processNewRequest', function() {

    it('should create a new cache element', function(done) {
      controller.processNewRequest(url304, function(response) {
        expect(response.statusCode).to.equal(200);
        done();
      });
    });
    it('should return a cached element', function(done) {

      controller.processNewRequest(url304, function(response, statusCode) {

        controller.processNewRequest(url304, function(response, statusCode) {

          expect(statusCode).to.equal('200 (cache)');
          done();
        });
      });
    });
  });

  describe('checkUpdated', function() {
    var node;

    it('should return a response code of 304', function(done) {
      controller.clearCache();
      request(url304, function(error, response, body) {
        node = controller.Cache.createNewEntry(url304, response.headers['last-modified'], 'data');

        controller.checkUpdated(url304, node, function(response, statusCode) {
          expect(statusCode).to.equal('200 (cache)');
          done();
        });
      });
    });
    it('should return a response code of 200', function(done) {
      controller.clearCache();

      request(urlBasic, function(error, response, body) {

        node = controller.Cache.createNewEntry(url304, response.headers.date, 'data');

        controller.checkUpdated(urlBasic, node, function(response) {
          expect(response.statusCode).to.equal(200);
          done();
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>import {PolymerElement, html} from '/resources/polymer/@polymer/polymer/polymer-element.js';

import { _timeZoneHeader } from '/resources/reflection/tg-date-utils.js';
import { containsRestictedTags } from '/resources/reflection/tg-polymer-utils.js';

import '/resources/polymer/@polymer/iron-ajax/iron-ajax.js';

export class UnreportableError extends Error {
    constructor(...params) {
        super(params);

        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, UnreportableError);
        }

        this.name = "UnreportableError";
    }
}

/**
 * Augments non-empty catched 'error' state restoration logic with 'restoreStateFunction', that is applicable in current context.
 */
export function enhanceStateRestoration (error, restoreStateFunction) {
    const preRestoreState = error.restoreState;
    error.restoreState = function () {
        preRestoreState && preRestoreState();
        restoreStateFunction();
    };
    return error;
};

/**
 * Replaces newline symbol with \r\n sequence or
 tag based on whether specified input contains restricted tags or not.
 * This is done in order to format input for tg-toast (see tg-toast.js: _showMessageDlg method).
 */
function replaceNewline (input) {
    const newline = "\r\n";
    return input.replace(/\n/gi, containsRestictedTags(input) ? newline : '
');
}

const template = html`
    <iron-ajax id="errorSender" headers="[[_headers]]" url="/error" method="PUT" content-type="text/plain" handle-as="text" on-response="_processResponse"></iron-ajax>`;

class TgGlobalErrorHandler extends PolymerElement {

    static get template() {
        return template;
    }

    static get properties () {
        return {

            //The maximal length of error queue. If more errors happens then they will be handled with alternative handler
            maxErrorQueueLength: {
                type: Number,
                value: 10
            },

            toaster: Object,

            //Queue of errors to log on server.
            _errorQueue: Array,

            /**
             * Additional headers for 'iron-ajax' client-side requests. These only contain
             * our custom 'Time-Zone' header that indicates real time-zone for the client application.
             * The time-zone then is to be assigned to threadlocal 'IDates.timeZone' to be able
             * to compute 'Now' moment properly.
             */
            _headers: {
                type: String,
                value: _timeZoneHeader
            },
        };
    }

    ready () {
        super.ready();

        //Configuring error handler related properties.
        this._errorQueue = [];
        this._handleError = this._handleError.bind(this);
        this._handleUnhandledPromiseError = this._handleUnhandledPromiseError.bind(this);

        //Add error handling errors
        window.addEventListener('error', this._handleError);
        window.addEventListener('rejectionhandled', this._handleUnhandledPromiseError);
        window.addEventListener('unhandledrejection', this._handleUnhandledPromiseError);
    }

    errorHandler (errorMsg) {
        this.$.errorSender.body = errorMsg;
        this.$.errorSender.generateRequest();
    }
    alternativeErrorHandler (errorMsg) {
        console.error(errorMsg);
    }

    _handleUnhandledPromiseError (e) {
        const errorMsg = e.reason.message + "\n" + e.reason.stack;
        this._acceptError(e.composedPath()[0], e.reason, errorMsg);
    }

    _handleError (e) {
        const errorDetail = e.detail ? e.detail : e;
        const errorMsg = errorDetail.message + " Error happened in: " + errorDetail.filename + " at Ln: " + errorDetail.lineno + ", Co: " + errorDetail.colno
                        + "\n" + ((errorDetail.error && errorDetail.error.stack) ?  errorDetail.error.stack : JSON.stringify(errorDetail.error));
        this._acceptError(e.composedPath()[0], errorDetail, errorMsg);
    }

    /**
     * Receives error or error events and then analyses whether they are reportable or have restoreState
     * to perform appropriate tasks with them. Also sends error to the server if possible otherwise adds error to
     * the queue for further processing. Shows toast to the user for errors of type other than UnreportableError.
     *
     * @param {*} from
     * @param {*} e -- event of type ErrorEvent (syntax error, throwing of Error etc.) or promise rejection error
     * @param {*} errorMsg
     */
    _acceptError (from, e, errorMsg) {
        if (from !== this.$.errorSender) {
            const error = e.error || e;
            if (error.restoreState) {
                error.restoreState();
            }
            if ( !(error instanceof UnreportableError)) {
                this.toaster.openToastForError("Unexpected error occurred.", replaceNewline(errorMsg), true);
            }
            if (this._errorQueue.length >= this.maxErrorQueueLength) {
                this.alternativeErrorHandler(errorMsg);
            } else {
                this._errorQueue.push(errorMsg);
            }
            if (!this.$.errorSender.loading) {
                this.errorHandler(this._errorQueue[0]);
            }
        }
    }

    _processResponse (e) {
        this._errorQueue.shift();
        if (this._errorQueue.length > 0) {
            this.errorHandler(this._errorQueue[0]);
        }
    }
}

customElements.define('tg-global-error-handler', TgGlobalErrorHandler);<commit_msg>#1554 Made name property of error more stable to any potential changes in the future.<commit_after>import {PolymerElement, html} from '/resources/polymer/@polymer/polymer/polymer-element.js';

import { _timeZoneHeader } from '/resources/reflection/tg-date-utils.js';
import { containsRestictedTags } from '/resources/reflection/tg-polymer-utils.js';

import '/resources/polymer/@polymer/iron-ajax/iron-ajax.js';

export class UnreportableError extends Error {
    constructor(...params) {
        super(params);

        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, UnreportableError);
        }
        this.name = this.constructor.name;
    }
}

/**
 * Augments non-empty catched 'error' state restoration logic with 'restoreStateFunction', that is applicable in current context.
 */
export function enhanceStateRestoration (error, restoreStateFunction) {
    const preRestoreState = error.restoreState;
    error.restoreState = function () {
        preRestoreState && preRestoreState();
        restoreStateFunction();
    };
    return error;
};

/**
 * Replaces newline symbol with \r\n sequence or
 tag based on whether specified input contains restricted tags or not.
 * This is done in order to format input for tg-toast (see tg-toast.js: _showMessageDlg method).
 */
function replaceNewline (input) {
    const newline = "\r\n";
    return input.replace(/\n/gi, containsRestictedTags(input) ? newline : '
');
}

const template = html`
    <iron-ajax id="errorSender" headers="[[_headers]]" url="/error" method="PUT" content-type="text/plain" handle-as="text" on-response="_processResponse"></iron-ajax>`;

class TgGlobalErrorHandler extends PolymerElement {

    static get template() {
        return template;
    }

    static get properties () {
        return {

            //The maximal length of error queue. If more errors happens then they will be handled with alternative handler
            maxErrorQueueLength: {
                type: Number,
                value: 10
            },

            toaster: Object,

            //Queue of errors to log on server.
            _errorQueue: Array,

            /**
             * Additional headers for 'iron-ajax' client-side requests. These only contain
             * our custom 'Time-Zone' header that indicates real time-zone for the client application.
             * The time-zone then is to be assigned to threadlocal 'IDates.timeZone' to be able
             * to compute 'Now' moment properly.
             */
            _headers: {
                type: String,
                value: _timeZoneHeader
            },
        };
    }

    ready () {
        super.ready();

        //Configuring error handler related properties.
        this._errorQueue = [];
        this._handleError = this._handleError.bind(this);
        this._handleUnhandledPromiseError = this._handleUnhandledPromiseError.bind(this);

        //Add error handling errors
        window.addEventListener('error', this._handleError);
        window.addEventListener('rejectionhandled', this._handleUnhandledPromiseError);
        window.addEventListener('unhandledrejection', this._handleUnhandledPromiseError);
    }

    errorHandler (errorMsg) {
        this.$.errorSender.body = errorMsg;
        this.$.errorSender.generateRequest();
    }
    alternativeErrorHandler (errorMsg) {
        console.error(errorMsg);
    }

    _handleUnhandledPromiseError (e) {
        const errorMsg = e.reason.message + "\n" + e.reason.stack;
        this._acceptError(e.composedPath()[0], e.reason, errorMsg);
    }

    _handleError (e) {
        const errorDetail = e.detail ? e.detail : e;
        const errorMsg = errorDetail.message + " Error happened in: " + errorDetail.filename + " at Ln: " + errorDetail.lineno + ", Co: " + errorDetail.colno
                        + "\n" + ((errorDetail.error && errorDetail.error.stack) ?  errorDetail.error.stack : JSON.stringify(errorDetail.error));
        this._acceptError(e.composedPath()[0], errorDetail, errorMsg);
    }

    /**
     * Receives error or error events and then analyses whether they are reportable or have restoreState
     * to perform appropriate tasks with them. Also sends error to the server if possible otherwise adds error to
     * the queue for further processing. Shows toast to the user for errors of type other than UnreportableError.
     *
     * @param {*} from
     * @param {*} e -- event of type ErrorEvent (syntax error, throwing of Error etc.) or promise rejection error
     * @param {*} errorMsg
     */
    _acceptError (from, e, errorMsg) {
        if (from !== this.$.errorSender) {
            const error = e.error || e;
            if (error.restoreState) {
                error.restoreState();
            }
            if ( !(error instanceof UnreportableError)) {
                this.toaster.openToastForError("Unexpected error occurred.", replaceNewline(errorMsg), true);
            }
            if (this._errorQueue.length >= this.maxErrorQueueLength) {
                this.alternativeErrorHandler(errorMsg);
            } else {
                this._errorQueue.push(errorMsg);
            }
            if (!this.$.errorSender.loading) {
                this.errorHandler(this._errorQueue[0]);
            }
        }
    }

    _processResponse (e) {
        this._errorQueue.shift();
        if (this._errorQueue.length > 0) {
            this.errorHandler(this._errorQueue[0]);
        }
    }
}

customElements.define('tg-global-error-handler', TgGlobalErrorHandler);<|endoftext|>


<commit_before>document.write("<script type='text/javascript' src='http://maps.google.com/maps/api/js?sensor=false'></script>"); // v=3 ?

Ui.Fixed.extend('Extras.Ui.Google.Map', {
	map: undefined,

	constructor: function(config) {
		var latlng = new google.maps.LatLng(0, 0);
		var zoom = 10;
        var maptype = google.maps.MapTypeId.ROADMAP;

		if(('latitude' in config) && ('longitude' in config)) {
			latlng = new google.maps.LatLng(config.latitude, config.longitude);
            console.log('lat' + config.latitude + ', lon' + config.longitude);
        }
		if('zoom' in config)
			zoom = config.zoom;
		if('maptype' in config)
            maptype =  config.maptype;

		var myOptions = {
			zoom: zoom,
			center: latlng,
			mapTypeId: maptype,
		};
		this.map = new google.maps.Map(this.getDrawing(), myOptions);

		// when the map object is alive
		if ('panControl' in config)
			this.showPanControl(config.panControl);
		if ('zoomControl' in config)
			this.showZoomControl(config.zoomControl);
		if ('mapTypeControl' in config)
			this.showMapTypeControl(config.mapTypeControl);
		if ('streetViewControl' in config)
			this.showStreetViewControl(config.streetViewControl);

        // Connect some events to tell google that the div has to be repainted
		this.connect(this, 'load', this.updateSize);
		this.connect(this, 'resize', this.updateSize);
		this.connect(this, 'visible', this.updateSize);
	},

	showPanControl: function(val) {
		this.map.panControl = val;
	},

	showZoomControl: function(val) {
		this.map.zoomControl = val;
	},

	showMapTypeControl: function(val) {
				this.map.mapTypeControl = val;
	},

	showStreetViewControl: function(val) {
		this.map.streetViewControl = val;
	},

	setLatLng: function(latitude, longitude) {
		this.map.setCenter(new google.maps.LatLng(latitude, longitude));
	},

	setMapType: function(type) {
		this.maptype = type;
		if (this.map) {
            console.log('setting maptype to ' + this.maptype);
			this.map.setMapTypeId(this.maptype);
        }
	},

	getMap: function() {
		return this.map;
	},

	getZoom: function() {
		return this.map.getZoom();
	},

	setZoom: function(zoom) {
		this.map.setZoom(zoom);
	},

	updateSize: function() {
		google.maps.event.trigger(this.getMap(), 'resize');
	}
});

<commit_msg>fixed wrong locations on google map initialization<commit_after>document.write("<script type='text/javascript' src='http://maps.google.com/maps/api/js?sensor=false'></script>"); // v=3 ?

Ui.Fixed.extend('Extras.Ui.Google.Map', {
	map: undefined,

	constructor: function(config) {
		var latlng = new google.maps.LatLng(0, 0);
		var zoom = 10;
        var maptype = google.maps.MapTypeId.ROADMAP;

		if(('latitude' in config) && ('longitude' in config)) {
			latlng = new google.maps.LatLng(config.latitude, config.longitude);
            console.log('lat' + config.latitude + ', lon' + config.longitude);
        }
		if('zoom' in config)
			zoom = config.zoom;
		if('maptype' in config)
            maptype =  config.maptype;

		var myOptions = {
			zoom: zoom,
			center: latlng,
			mapTypeId: maptype,
		};
		this.map = new google.maps.Map(this.getDrawing(), myOptions);

		// when the map object is alive
		if ('panControl' in config)
			this.showPanControl(config.panControl);
		if ('zoomControl' in config)
			this.showZoomControl(config.zoomControl);
		if ('mapTypeControl' in config)
			this.showMapTypeControl(config.mapTypeControl);
		if ('streetViewControl' in config)
			this.showStreetViewControl(config.streetViewControl);

        // Connect some events to tell google that the div has to be repainted
		this.connect(this, 'load', this.updateSize);
		this.connect(this, 'resize', this.updateSize);
		this.connect(this, 'visible', this.updateSize);
	},

	showPanControl: function(val) {
		this.map.panControl = val;
	},

	showZoomControl: function(val) {
		this.map.zoomControl = val;
	},

	showMapTypeControl: function(val) {
				this.map.mapTypeControl = val;
	},

	showStreetViewControl: function(val) {
		this.map.streetViewControl = val;
	},

	setLatLng: function(latitude, longitude) {
		this.map.setCenter(new google.maps.LatLng(latitude, longitude));
	},

	setMapType: function(type) {
		this.maptype = type;
		if (this.map) {
            console.log('setting maptype to ' + this.maptype);
			this.map.setMapTypeId(this.maptype);
        }
	},

	getMap: function() {
		return this.map;
	},

	getZoom: function() {
		return this.map.getZoom();
	},

	setZoom: function(zoom) {
		this.map.setZoom(zoom);
	},

	updateSize: function() {
        var center = this.getMap().getCenter();
		google.maps.event.trigger(this.getMap(), 'resize');
        this.getMap().setCenter(center);
	}
});

<|endoftext|>