<commit_before>'use strict';

/**
 * !!! This is an undocumented "private" service !!!
 *
 * @name ng.$sniffer
 * @requires $window
 * @requires $document
 *
 * @property {boolean} history Does the browser support html5 history api ?
 * @property {boolean} hashchange Does the browser support hashchange event ?
 * @property {boolean} transitions Does the browser support CSS transition events ?
 * @property {boolean} animations Does the browser support CSS animation events ?
 *
 * @description
 * This is very simple implementation of testing browser's features.
 */
function $SnifferProvider() {
  this.$get = ['$window', '$document', function($window, $document) {
    var eventSupport = {},
        android =
          int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
        boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
        document = $document[0] || {},
        documentMode = document.documentMode,
        vendorPrefix,
        vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
        bodyStyle = document.body && document.body.style,
        transitions = false,
        animations = false,
        match;

    if (bodyStyle) {
      for(var prop in bodyStyle) {
        if(match = vendorRegex.exec(prop)) {
          vendorPrefix = match[0];
          vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
          break;
        }
      }

      if(!vendorPrefix) {
        vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
      }

      transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
      animations  = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));

      if (android && (!transitions||!animations)) {
        transitions = isString(document.body.style.webkitTransition);
        animations = isString(document.body.style.webkitAnimation);
      }
    }

    return {
      // Android has history.pushState, but it does not update location correctly
      // so let's not use the history API at all.
      // http://code.google.com/p/android/issues/detail?id=17471
      // https://github.com/angular/angular.js/issues/904

      // older webit browser (533.9) on Boxee box has exactly the same problem as Android has
      // so let's not use the history API also
      // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
      // jshint -W018
      history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
      // jshint +W018
      hashchange: 'onhashchange' in $window &&
                  // IE8 compatible mode lies
                  (!documentMode || documentMode > 7),
      hasEvent: function(event) {
        // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
        // it. In particular the event is not fired when backspace or delete key are pressed or
        // when cut operation is performed.
        if (event == 'input' && msie == 9) return false;

        if (isUndefined(eventSupport[event])) {
          var divElm = document.createElement('div');
          eventSupport[event] = 'on' + event in divElm;
        }

        return eventSupport[event];
      },
      csp: csp(),
      vendorPrefix: vendorPrefix,
      transitions : transitions,
      animations : animations,
      android: android,
      msie : msie,
      msieDocumentMode: documentMode
    };
  }];
}
<commit_msg>docs(\$sniffer): fix minor typo<commit_after>'use strict';

/**
 * !!! This is an undocumented "private" service !!!
 *
 * @name ng.$sniffer
 * @requires $window
 * @requires $document
 *
 * @property {boolean} history Does the browser support html5 history api ?
 * @property {boolean} hashchange Does the browser support hashchange event ?
 * @property {boolean} transitions Does the browser support CSS transition events ?
 * @property {boolean} animations Does the browser support CSS animation events ?
 *
 * @description
 * This is very simple implementation of testing browser's features.
 */
function $SnifferProvider() {
  this.$get = ['$window', '$document', function($window, $document) {
    var eventSupport = {},
        android =
          int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
        boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
        document = $document[0] || {},
        documentMode = document.documentMode,
        vendorPrefix,
        vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
        bodyStyle = document.body && document.body.style,
        transitions = false,
        animations = false,
        match;

    if (bodyStyle) {
      for(var prop in bodyStyle) {
        if(match = vendorRegex.exec(prop)) {
          vendorPrefix = match[0];
          vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
          break;
        }
      }

      if(!vendorPrefix) {
        vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
      }

      transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
      animations  = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));

      if (android && (!transitions||!animations)) {
        transitions = isString(document.body.style.webkitTransition);
        animations = isString(document.body.style.webkitAnimation);
      }
    }

    return {
      // Android has history.pushState, but it does not update location correctly
      // so let's not use the history API at all.
      // http://code.google.com/p/android/issues/detail?id=17471
      // https://github.com/angular/angular.js/issues/904

      // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
      // so let's not use the history API also
      // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
      // jshint -W018
      history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
      // jshint +W018
      hashchange: 'onhashchange' in $window &&
                  // IE8 compatible mode lies
                  (!documentMode || documentMode > 7),
      hasEvent: function(event) {
        // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
        // it. In particular the event is not fired when backspace or delete key are pressed or
        // when cut operation is performed.
        if (event == 'input' && msie == 9) return false;

        if (isUndefined(eventSupport[event])) {
          var divElm = document.createElement('div');
          eventSupport[event] = 'on' + event in divElm;
        }

        return eventSupport[event];
      },
      csp: csp(),
      vendorPrefix: vendorPrefix,
      transitions : transitions,
      animations : animations,
      android: android,
      msie : msie,
      msieDocumentMode: documentMode
    };
  }];
}
<|endoftext|>


<commit_before>/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Class PuppetWeb
 *
 * use to control wechat web.
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */

/**************************************
 *
 * Class PuppetWeb
 *
 ***************************************/
const util = require('util')
const fs  = require('fs')
const co  = require('co')

const log = require('./npmlog-env')

const Puppet = require('./puppet')
const Message = require('./message')
const Contact = require('./contact')
const Room    = require('./room')

const Server  = require('./puppet-web-server')
const Browser = require('./puppet-web-browser')
const Bridge  = require('./puppet-web-bridge')

class PuppetWeb extends Puppet {
  constructor(options) {
    super()
    options = options || {}
    this.port = options.port || 8788 // W(87) X(88), ascii char code ;-]
    this.head = options.head
    this.session = options.session  // if not set session, then dont store session.

    this.user = null  // <Contact> of user self
  }

  toString() { return `Class PuppetWeb({browser:${this.browser},port:${this.port}})` }

  init() {
    log.verbose('PuppetWeb', `init() with port:${this.port}, head:${this.head}, session:${this.session}`)

    return co.call(this, function* () {

      yield this.initAttach(this)
      log.verbose('PuppetWeb', 'initAttach() done')

      yield this.initServer()
      log.verbose('PuppetWeb', 'initServer() done')

      yield this.initBrowser()
      log.verbose('PuppetWeb', 'initBrowser() done')

      yield this.initBridge()
      log.verbose('PuppetWeb', 'initBridge() done')

      return this
    })
    .catch(e => {                 // Reject
      log.error('PuppetWeb', e)
      throw e
    })
    .then(() => {                  // Finally
      log.verbose('PuppetWeb', 'init() done')
      return this   // for Chaining
    })
  }

  quit() {
    log.verbose('PuppetWeb', 'quit()')

    return co.call(this, function* () {
      if (this.bridge)  {
        yield this.bridge.quit()
        this.bridge = null
      } else { log.warn('PuppetWeb', 'quit() without bridge') }

      if (this.server) {
        yield this.server.quit()
        this.server = null
      } else { log.warn('PuppetWeb', 'quit() without server') }

      if (this.browser) {
        yield this.browser.quit()
        this.browser = null
      } else { log.warn('PuppetWeb', 'quit() without browser') }

      yield this.initAttach(null)
    })
    .catch(e => {                 // Reject
      log.error('PuppetWeb', e)
      throw e
    })
    .then(() => {                  // Finally, Fall Safe
      log.verbose('PuppetWeb', 'quit() done')
      return this   // for Chaining
    })
  }

  initAttach(puppet) {
    log.verbose('PuppetWeb', 'initAttach()')
    Contact.attach(puppet)
    Room.attach(puppet)
    return Promise.resolve(true)
  }
  initBrowser() {
    log.verbose('PuppetWeb', 'initBrowser')
    this.browser  = new Browser({ head: this.head })

    return co.call(this, function* () {
      yield this.browser.init()
      yield this.browser.open('https://res.wx.qq.com/zh_CN/htmledition/v2/images/icon/ico_loading28a2f7.gif')
      yield this.checkSession()
      yield this.loadSession().catch(e => { log.verbose('PuppetWeb', 'loadSession rejected: %s', e) /* fail safe */ })
      yield this.checkSession()
      yield this.browser.open()
      yield this.checkSession()
    }).catch(e => {
      log.error('PuppetWeb', 'initBrowser rejected: %s', e)
      throw e
    })
  }
  initBridge() {
    log.verbose('PuppetWeb', 'initBridge()')
    this.bridge = new Bridge({
      browser:  this.browser
      , port:   this.port
    })
    return this.bridge.init()
  }
  initServer() {
    log.verbose('PuppetWeb', 'initServer()')
    const server = new Server({port: this.port})

    server.on('login',   this.onServerLogin.bind(this))
    server.on('logout',  this.onServerLogout.bind(this))
    server.on('message', this.onServerMessage.bind(this))
    server.on('unload',  this.onServerUnload.bind(this))

    server.on('connection', this.onServerConnection.bind(this))
    server.on('disconnect', this.onServerDisconnect.bind(this))
    server.on('log', this.onServerLog.bind(this))

    ;[  // Public events to end user
      , 'scan'
      , 'dong'
    ].map(e => {
      server.on(e, data => {
        log.verbose('PuppetWeb', 'Server event[%s]: %s', e, data)
        this.emit(e, data)
      })
    })

    this.server = server
    return this.server.init()
  }

  onServerConnection(data) {
    log.verbose('PuppetWeb', 'onServerConnection: %s', data.constructor.name)
  }
  onServerDisconnect(data) {
    log.verbose('PuppetWeb', 'onServerDisconnect: %s', data)
    log.verbose('PuppetWeb', 'onServerDisconnect: unloaded? call onServerUnload to try to fix connection')
    this.onServerUnload(data)
  }
  onServerLog(data) {
    log.verbose('PuppetWeb', 'onServerLog: %s', data)
  }

  onServerLogin(data) {
    co.call(this, function* () {
      // co.call to make `this` context work inside generator.
      // See also: https://github.com/tj/co/issues/274
      const userName = yield this.bridge.getUserName()
      if (!userName) {
        log.verbose('PuppetWeb', 'onServerLogin: browser not full loaded, retry later.')
        setTimeout(this.onServerLogin.bind(this), 300)
        return
      }
      log.silly('PuppetWeb', 'userName: %s', userName)
      this.user = yield Contact.load(userName).ready()
      log.verbose('PuppetWeb', `user ${this.user.name()} logined`)
      this.emit('login', this.user)

      yield this.saveSession().catch(() => {/* fall safe */})

    }).catch(e => log.error('PuppetWeb', 'onServerLogin co rejected: %s', e))
  }
  onServerLogout(data) {
    if (this.user) {
      this.emit('logout', this.user)
      this.user = null
    } else { log.verbose('PuppetWeb', 'onServerLogout without this.user. still not logined?') }
  }
  onServerMessage(data) {
    const m = new Message(data)
    if (this.user) {
      m.set('self', this.user.id)
    } else {
      log.warn('PuppetWeb', 'onServerMessage() without this.user')
    }
    m.ready().then(() => this.emit('message', m))
  }
  /**
   * `unload` event is sent from js@browser to webserver via socketio
   * after received `unload`, we re-inject the Wechaty js code into browser.
   */
  onServerUnload(data) {
    log.verbose('PuppetWeb', 'server received unload event')
    this.onServerLogout(data) // XXX: should emit event[logout] from browser

    if (!this.browser || !this.bridge) {
      log.verbose('PuppetWeb', 'bridge gone, should be quiting now')
      return
    }

    return this.browser.quit()
    .then(r  => log.verbose('PuppetWeb', 'browser.quit()ed:' + r))
    .then(r => this.browser.init())
    .then(r  => log.verbose('PuppetWeb', 'browser.re-init()ed:' + r))
    .then(r => this.browser.open())
    .then(r  => log.verbose('PuppetWeb', 'browser.re-open()ed:' + r))
    .then(r => this.bridge.init())
    .then(r  => log.verbose('PuppetWeb', 'bridge.re-init()ed:' + r))
    .catch(e => log.error('PuppetWeb', 'onServerUnload() err: ' + e))
  }

  send(message) {
    const to      = message.get('to')
    const room    = message.get('room')

    let content     = message.get('content')
    let destination = to

    if (room) {
      destination = room
      // if (to && to!==room) {
      //   content = `@[${to}] ${content}`
      // }
    }

    log.silly('PuppetWeb', `send(${destination}, ${content})`)
    return this.bridge.send(destination, content)
  }
  reply(message, replyContent) {
    if (message.self()) {
      throw new Error('dont reply message send by myself')
    }
    const m = new Message()
    .set('content'  , replyContent)

    .set('from'     , message.obj.to)
    .set('to'       , message.obj.from)
    .set('room'     , message.obj.room)

    // FIXME: find a alternate way to check a message create by `self`
    .set('self'     , this.user.id)

    log.verbose('PuppetWeb', 'reply() not sending message: %s', util.inspect(m))
    return this.send(m)
  }

  /**
   * logout from browser, then server will emit `logout` event
   */
  logout() { return this.bridge.logout() }

  getContact(id)  { return this.bridge.getContact(id) }
  logined() { return !!(this.user) }

  checkSession() {
    log.verbose('PuppetWeb', `checkSession(${this.session})`)
    return this.browser.driver.manage().getCookies()
    .then(cookies => {
      log.silly('PuppetWeb', 'checkSession %s', require('util').inspect(cookies/*.map(c => { return {name: c.name, value: c.value, expiresType: typeof c.expires, expires: c.expires} })*/))
      return cookies
    })
  }
  saveSession() {
    log.verbose('PuppetWeb', `saveSession(${this.session})`)
    if (!this.session) { return Promise.reject('saveSession() no session') }
    const filename = this.session

    return new Promise((resolve, reject) => {
      this.browser.driver.manage().getCookies()
      .then(cookies => {
        const skipNames = [
          'ChromeDriver'
          , 'MM_WX_SOUND_STATE'
          , 'MM_WX_NOTIFY_STATE'
        ]
        const skipNamesRegex = new RegExp(skipNames.join('|'), 'i')
        const filteredCookies = cookies.filter(c => {
          if (skipNamesRegex.test(c.name)) { return false }
          // else if (!/wx\.qq\.com/i.test(c.domain))  { return false }
          else                        { return true }
        })
        log.silly('PuppetWeb', 'saving %d cookies for session: %s', cookies.length
          , util.inspect(filteredCookies/*.map(c => { return {name: c.name, value: c.value, expiresType: typeof c.expires, expires: c.expires} })*/))

        const jsonStr = JSON.stringify(filteredCookies)
        fs.writeFile(filename, jsonStr, function(err) {
          if(err) {
            log.error('PuppetWeb', 'saveSession() fail to write file %s: %s', filename, err.Error)
            return reject(err)
          }
          log.verbose('PuppetWeb', 'saved session to %s(%d cookies)', this.session, filteredCookies.length)
          return resolve(filteredCookies)
        }.bind(this))
      })
    })
  }

  loadSession() {
    log.verbose('PuppetWeb', `loadSession(${this.session})`)
    if (!this.session) { return Promise.reject('loadSession() no session') }
    const filename = this.session

    return new Promise((resolve, reject) => {
      fs.readFile(filename, (err, jsonStr) => {
        if (err) {
          if (err) { log.silly('PuppetWeb', 'loadSession(%s) skipped because error code: %s', this.session, err.code) }
          return reject('error code:' + err.code)
        }
        const cookies = JSON.parse(jsonStr)

        const ps = this.browser.addCookies(cookies)
        return Promise.all(ps)
        .then(() => {
          log.verbose('PuppetWeb', 'loaded session from %s(%d cookies)', this.session, cookies.length)
          resolve(cookies)
        })
        .catch(e => {
          log.error('PuppetWeb', 'loadSession rejected: %s', e)
          reject(e)
        })
      })
    })
  }
}

module.exports = PuppetWeb
<commit_msg>minor changes<commit_after>/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Class PuppetWeb
 *
 * use to control wechat web.
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */

/**************************************
 *
 * Class PuppetWeb
 *
 ***************************************/
const util = require('util')
const fs  = require('fs')
const co  = require('co')

const log = require('./npmlog-env')

const Puppet = require('./puppet')
const Message = require('./message')
const Contact = require('./contact')
const Room    = require('./room')

const Server  = require('./puppet-web-server')
const Browser = require('./puppet-web-browser')
const Bridge  = require('./puppet-web-bridge')

class PuppetWeb extends Puppet {
  constructor(options) {
    super()
    options = options || {}
    this.port = options.port || 8788 // W(87) X(88), ascii char code ;-]
    this.head = options.head
    this.session = options.session  // if not set session, then dont store session.

    this.user = null  // <Contact> of user self
  }

  toString() { return `Class PuppetWeb({browser:${this.browser},port:${this.port}})` }

  init() {
    log.verbose('PuppetWeb', `init() with port:${this.port}, head:${this.head}, session:${this.session}`)

    return co.call(this, function* () {

      yield this.initAttach(this)
      log.verbose('PuppetWeb', 'initAttach() done')

      yield this.initServer()
      log.verbose('PuppetWeb', 'initServer() done')

      yield this.initBrowser()
      log.verbose('PuppetWeb', 'initBrowser() done')

      yield this.initBridge()
      log.verbose('PuppetWeb', 'initBridge() done')

      return this
    })
    .catch(e => {                 // Reject
      log.error('PuppetWeb', e)
      throw e
    })
    .then(() => {                  // Finally
      log.verbose('PuppetWeb', 'init() done')
      return this   // for Chaining
    })
  }

  quit() {
    log.verbose('PuppetWeb', 'quit()')

    return co.call(this, function* () {
      if (this.bridge)  {
        yield this.bridge.quit()
        this.bridge = null
      } else { log.warn('PuppetWeb', 'quit() without bridge') }

      if (this.server) {
        yield this.server.quit()
        this.server = null
      } else { log.warn('PuppetWeb', 'quit() without server') }

      if (this.browser) {
        yield this.browser.quit()
        this.browser = null
      } else { log.warn('PuppetWeb', 'quit() without browser') }

      yield this.initAttach(null)
    })
    .catch(e => {                 // Reject
      log.error('PuppetWeb', e)
      throw e
    })
    .then(() => {                  // Finally, Fall Safe
      log.verbose('PuppetWeb', 'quit() done')
      return this   // for Chaining
    })
  }

  initAttach(puppet) {
    log.verbose('PuppetWeb', 'initAttach()')
    Contact.attach(puppet)
    Room.attach(puppet)
    return Promise.resolve(true)
  }
  initBrowser() {
    log.verbose('PuppetWeb', 'initBrowser')
    this.browser  = new Browser({ head: this.head })

    return co.call(this, function* () {
      yield this.browser.init()
      yield this.browser.open('https://res.wx.qq.com/zh_CN/htmledition/v2/images/icon/ico_loading28a2f7.gif')
      yield this.checkSession()
      yield this.loadSession().catch(e => { log.verbose('PuppetWeb', 'loadSession rejected: %s', e) /* fail safe */ })
      yield this.checkSession()
      yield this.browser.open()
      yield this.checkSession()
    }).catch(e => {
      log.error('PuppetWeb', 'initBrowser rejected: %s', e)
      throw e
    })
  }
  initBridge() {
    log.verbose('PuppetWeb', 'initBridge()')
    this.bridge = new Bridge({
      browser:  this.browser
      , port:   this.port
    })
    return this.bridge.init()
  }
  initServer() {
    log.verbose('PuppetWeb', 'initServer()')
    const server = new Server({port: this.port})

    server.on('login',   this.onServerLogin.bind(this))
    server.on('logout',  this.onServerLogout.bind(this))
    server.on('message', this.onServerMessage.bind(this))
    server.on('unload',  this.onServerUnload.bind(this))

    server.on('connection', this.onServerConnection.bind(this))
    server.on('disconnect', this.onServerDisconnect.bind(this))
    server.on('log', this.onServerLog.bind(this))

    ;[  // Public events to end user
      , 'scan'
      , 'dong'
    ].map(e => {
      server.on(e, data => {
        log.verbose('PuppetWeb', 'Server event[%s]: %s', e, data)
        this.emit(e, data)
      })
    })

    this.server = server
    return this.server.init()
  }

  onServerConnection(data) {
    log.verbose('PuppetWeb', 'onServerConnection: %s', data.constructor.name)
  }
  onServerDisconnect(data) {
    log.verbose('PuppetWeb', 'onServerDisconnect: %s', data)
    log.verbose('PuppetWeb', 'onServerDisconnect: unloaded? call onServerUnload to try to fix connection')
    this.onServerUnload(data)
  }
  onServerLog(data) {
    log.verbose('PuppetWeb', 'onServerLog: %s', data)
  }

  onServerLogin(data) {
    co.call(this, function* () {
      // co.call to make `this` context work inside generator.
      // See also: https://github.com/tj/co/issues/274
      const userName = yield this.bridge.getUserName()
      if (!userName) {
        log.verbose('PuppetWeb', 'onServerLogin: browser not full loaded, retry later.')
        setTimeout(this.onServerLogin.bind(this), 300)
        return
      }
      log.silly('PuppetWeb', 'userName: %s', userName)
      this.user = yield Contact.load(userName).ready()
      log.verbose('PuppetWeb', `user ${this.user.name()} logined`)
      this.emit('login', this.user)

      yield this.saveSession().catch(() => {/* fall safe */})

    }).catch(e => log.error('PuppetWeb', 'onServerLogin co rejected: %s', e))
  }
  onServerLogout(data) {
    if (this.user) {
      this.emit('logout', this.user)
      this.user = null
    } else { log.verbose('PuppetWeb', 'onServerLogout without this.user. still not logined?') }
  }
  onServerMessage(data) {
    const m = new Message(data)
    if (this.user) {
      m.set('self', this.user.id)
    } else {
      log.warn('PuppetWeb', 'onServerMessage() without this.user')
    }
    m.ready().then(() => this.emit('message', m))
  }
  /**
   * `unload` event is sent from js@browser to webserver via socketio
   * after received `unload`, we re-inject the Wechaty js code into browser.
   */
  onServerUnload(data) {
    log.verbose('PuppetWeb', 'server received unload event')
    this.onServerLogout(data) // XXX: should emit event[logout] from browser

    if (!this.browser || !this.bridge) {
      log.verbose('PuppetWeb', 'bridge gone, should be quiting now')
      return
    }

    return this.browser.quit()
    .then(r  => log.verbose('PuppetWeb', 'browser.quit()ed:' + r))
    .then(r => this.browser.init())
    .then(r  => log.verbose('PuppetWeb', 'browser.re-init()ed:' + r))
    .then(r => this.browser.open())
    .then(r  => log.verbose('PuppetWeb', 'browser.re-open()ed:' + r))
    .then(r => this.bridge.init())
    .then(r  => log.verbose('PuppetWeb', 'bridge.re-init()ed:' + r))
    .catch(e => log.error('PuppetWeb', 'onServerUnload() err: ' + e))
  }

  send(message) {
    const to      = message.get('to')
    const room    = message.get('room')

    let content     = message.get('content')
    let destination = to

    if (room) {
      destination = room
      // if (to && to!==room) {
      //   content = `@[${to}] ${content}`
      // }
    }

    log.silly('PuppetWeb', `send(${destination}, ${content})`)
    return this.bridge.send(destination, content)
  }
  reply(message, replyContent) {
    if (message.self()) {
      throw new Error('dont reply message send by myself')
    }
    const m = new Message()
    .set('content'  , replyContent)

    .set('from'     , message.obj.to)
    .set('to'       , message.obj.from)
    .set('room'     , message.obj.room)

    // FIXME: find a alternate way to check a message create by `self`
    .set('self'     , this.user.id)

    log.verbose('PuppetWeb', 'reply() not sending message: %s', util.inspect(m))
    return this.send(m)
  }

  /**
   * logout from browser, then server will emit `logout` event
   */
  logout() { return this.bridge.logout() }

  getContact(id)  { return this.bridge.getContact(id) }
  logined() { return !!(this.user) }

  checkSession() {
    log.verbose('PuppetWeb', `checkSession(${this.session})`)
    return this.browser.driver.manage().getCookies()
    .then(cookies => {
      log.silly('PuppetWeb', 'checkSession %s', require('util').inspect(cookies/*.map(c => { return {name: c.name, value: c.value, expiresType: typeof c.expires, expires: c.expires} })*/))
      return cookies
    })
  }
  saveSession() {
    log.verbose('PuppetWeb', `saveSession(${this.session})`)
    if (!this.session) { return Promise.reject('saveSession() no session') }
    const filename = this.session

    return new Promise((resolve, reject) => {
      this.browser.driver.manage().getCookies()
      .then(cookies => {
        const skipNames = [
          'ChromeDriver'
          , 'MM_WX_SOUND_STATE'
          , 'MM_WX_NOTIFY_STATE'
        ]
        const skipNamesRegex = new RegExp(skipNames.join('|'), 'i')
        const filteredCookies = cookies.filter(c => {
          if (skipNamesRegex.test(c.name)) { return false }
          // else if (!/wx\.qq\.com/i.test(c.domain))  { return false }
          else                        { return true }
        })
        log.silly('PuppetWeb', 'saving %d cookies for session: %s', cookies.length
          , util.inspect(filteredCookies/*.map(c => { return {name: c.name, value: c.value, expiresType: typeof c.expires, expires: c.expires} })*/))

        const jsonStr = JSON.stringify(filteredCookies)
        fs.writeFile(filename, jsonStr, function(err) {
          if(err) {
            log.error('PuppetWeb', 'saveSession() fail to write file %s: %s', filename, err.Error)
            return reject(err)
          }
          log.verbose('PuppetWeb', 'saved session(%d cookies) to %s', filteredCookies.length, this.session)
          return resolve(filteredCookies)
        }.bind(this))
      })
    })
  }

  loadSession() {
    log.verbose('PuppetWeb', `loadSession(${this.session})`)
    if (!this.session) { return Promise.reject('loadSession() no session') }
    const filename = this.session

    return new Promise((resolve, reject) => {
      fs.readFile(filename, (err, jsonStr) => {
        if (err) {
          if (err) { log.silly('PuppetWeb', 'loadSession(%s) skipped because error code: %s', this.session, err.code) }
          return reject('error code:' + err.code)
        }
        const cookies = JSON.parse(jsonStr)

        const ps = this.browser.addCookies(cookies)
        return Promise.all(ps)
        .then(() => {
          log.verbose('PuppetWeb', 'loaded session(%d cookies) from %s', cookies.length, this.session)
          resolve(cookies)
        })
        .catch(e => {
          log.error('PuppetWeb', 'loadSession rejected: %s', e)
          reject(e)
        })
      })
    })
  }
}

module.exports = PuppetWeb
<|endoftext|>


<commit_before>/**
 * Объект для работы с историей и адресной строкой браузера.
 *
 * Методы:
 *
 * `ns.history.pushState(url)`
 *   Меняет URL в адресной строке, запоминая его в истории браузера.
 * `ns.history.replaceState(url)`
 *   Меняет URL в адресной строке, заменяя текущую запись в истории браузера.
 * `ns.history.adapt()`
 *   Адаптирует хешовые URL для браузеров, поддерживающих HTML5 History API,
 *   выполняя трансформации типа `/#/message/123/` в `/message/123/`.
 * `ns.history.legacy`
 *   {Boolean}
 *   Флаг использования HTML5 History API или отката на `hashchange`.
 */
ns.history = (function(window, ns) {

    var history = window.history;
    var loc = window.location;

    var legacy = !(history.pushState && history.replaceState);

    // API для браузеров с поддержкой HTML5 History API.
    if (!legacy) {
        return {
            pushState: history.pushState.bind(history, null, 'mail'),
            replaceState: history.replaceState.bind(history, null, 'mail'),

            // Редирект с хешового урла на его полноценный аналог.
            adapt: function() {
                var hash = loc.hash.substr(1);

                // Если в хеше уже есть какие-то query параметры,
                // текущие надо к ним прибавить.
                var search = hash.indexOf('?') !== -1 ? loc.search.replace(/^\?/, '&') : loc.search;

                var hashRoute = ns.router(hash);
                var pathRoute = ns.router(loc.pathname);

                if (hash.length &&
                    hashRoute && hashRoute.page !== ns.L.NOT_FOUND &&

                    // TODO: При добавлении способа задания корневого урла заменить
                    // слеш на нужное свойство/метод.
                    (loc.pathname === '/' || pathRoute.page === ns.L.NOT_FOUND)) {
                    this.replaceState(hash + search);
                }

                window.addEventListener('popstate', function(e) {
                    // прибиваем событие, чтобы не дергалась адресная строка
                    e.preventDefault();
                    e.stopPropagation();

                    ns.page.go();
                }, false);

                // Здесь `ns.page.go` можно не вызывать, потому что после `ns.init`
                // `ns.page.go` все равно вызывается.
            },
            legacy: false
        };

    // Откат на `hashchange`.
    } else {
        // Редирект с текущего полноценного урла на его хешовый аналог.
        //
        // TODO: При добавлении способа задания корневого урла заменить
        // слеш на нужное свойство/метод.
        if ((loc.pathname + loc.search) !== '/') {
            return loc.replace('/#' + loc.pathname + loc.search);
        }

        // Заставляет `hashchange` игнорировать смену URL через вызов legacy
        // `pushState` или `replaceState`.
        var ignore = false;

        window.addEventListener('hashchange', function() {
            if (!ignore) {
                ns.page.go();
            }

            ignore = false;
        }, false);

        return {
            pushState: function(url) {
                ignore = true;
                loc.hash = url;
            },
            replaceState: function(url) {
                ignore = true;
                loc.replace(loc.pathname + loc.search + '#' + url);
            },
            adapt: no.nop,
            legacy: true
        };
    }

}(window, ns));
<commit_msg>Поправил константы<commit_after>/**
 * Объект для работы с историей и адресной строкой браузера.
 *
 * Методы:
 *
 * `ns.history.pushState(url)`
 *   Меняет URL в адресной строке, запоминая его в истории браузера.
 * `ns.history.replaceState(url)`
 *   Меняет URL в адресной строке, заменяя текущую запись в истории браузера.
 * `ns.history.adapt()`
 *   Адаптирует хешовые URL для браузеров, поддерживающих HTML5 History API,
 *   выполняя трансформации типа `/#/message/123/` в `/message/123/`.
 * `ns.history.legacy`
 *   {Boolean}
 *   Флаг использования HTML5 History API или отката на `hashchange`.
 */
ns.history = (function(window, ns) {

    var history = window.history;
    var loc = window.location;

    var legacy = !(history.pushState && history.replaceState);

    // API для браузеров с поддержкой HTML5 History API.
    if (!legacy) {
        return {
            pushState: history.pushState.bind(history, null, 'mail'),
            replaceState: history.replaceState.bind(history, null, 'mail'),

            // Редирект с хешового урла на его полноценный аналог.
            adapt: function() {
                var hash = loc.hash.substr(1);

                // Если в хеше уже есть какие-то query параметры,
                // текущие надо к ним прибавить.
                var search = hash.indexOf('?') !== -1 ? loc.search.replace(/^\?/, '&') : loc.search;

                var hashRoute = ns.router(hash);
                var pathRoute = ns.router(loc.pathname);

                if (hash.length &&
                    hashRoute && hashRoute.page !== ns.R.NOT_FOUND &&

                    // TODO: При добавлении способа задания корневого урла заменить
                    // слеш на нужное свойство/метод.
                    (loc.pathname === '/' || pathRoute.page === ns.R.NOT_FOUND)) {
                    this.replaceState(hash + search);
                }

                window.addEventListener('popstate', function(e) {
                    // прибиваем событие, чтобы не дергалась адресная строка
                    e.preventDefault();
                    e.stopPropagation();

                    ns.page.go();
                }, false);

                // Здесь `ns.page.go` можно не вызывать, потому что после `ns.init`
                // `ns.page.go` все равно вызывается.
            },
            legacy: false
        };

    // Откат на `hashchange`.
    } else {
        // Редирект с текущего полноценного урла на его хешовый аналог.
        //
        // TODO: При добавлении способа задания корневого урла заменить
        // слеш на нужное свойство/метод.
        if ((loc.pathname + loc.search) !== '/') {
            return loc.replace('/#' + loc.pathname + loc.search);
        }

        // Заставляет `hashchange` игнорировать смену URL через вызов legacy
        // `pushState` или `replaceState`.
        var ignore = false;

        window.addEventListener('hashchange', function() {
            if (!ignore) {
                ns.page.go();
            }

            ignore = false;
        }, false);

        return {
            pushState: function(url) {
                ignore = true;
                loc.hash = url;
            },
            replaceState: function(url) {
                ignore = true;
                loc.replace(loc.pathname + loc.search + '#' + url);
            },
            adapt: no.nop,
            legacy: true
        };
    }

}(window, ns));
<|endoftext|>


<commit_before>// FIXME add tests for browser features (Modernizr?)

goog.provide('ol.dom');
goog.provide('ol.dom.BrowserFeature');

goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.userAgent');
goog.require('goog.vec.Mat4');
goog.require('ol');

/**
 * Create an html canvas element and returns its 2d context.
 * @param {number=} opt_width Canvas width.
 * @param {number=} opt_height Canvas height.
 * @return {CanvasRenderingContext2D}
 */
ol.dom.createCanvasContext2D = function(opt_width, opt_height) {
  var canvas = goog.dom.createElement(goog.dom.TagName.CANVAS);
  if (opt_width) {
    canvas.width = opt_width;
  }
  if (opt_height) {
    canvas.height = opt_height;
  }
  return canvas.getContext('2d');
};

/**
 * Detect 2d transform.
 * Adapted from http://stackoverflow.com/q/5661671/130442
 * http://caniuse.com/#feat=transforms2d
 * @return {boolean}
 */
ol.dom.canUseCssTransform = (function() {
  var canUseCssTransform;
  return function() {
    if (canUseCssTransform === undefined) {
      goog.asserts.assert(!goog.isNull(document.body),
          'document.body should not be null');
      if (!goog.global.getComputedStyle) {
        // this browser is ancient
        canUseCssTransform = false;
      } else {
        var el = goog.dom.createElement(goog.dom.TagName.P),
            has2d,
            transforms = {
              'webkitTransform': '-webkit-transform',
              'OTransform': '-o-transform',
              'msTransform': '-ms-transform',
              'MozTransform': '-moz-transform',
              'transform': 'transform'
            };
        goog.dom.appendChild(document.body, el);
        for (var t in transforms) {
          if (t in el.style) {
            el.style[t] = 'translate(1px,1px)';
            has2d = goog.global.getComputedStyle(el).getPropertyValue(
                transforms[t]);
          }
        }
        goog.dom.removeNode(el);

        canUseCssTransform = (has2d && has2d !== 'none');
      }
    }
    return canUseCssTransform;
  };
}());

/**
 * Detect 3d transform.
 * Adapted from http://stackoverflow.com/q/5661671/130442
 * http://caniuse.com/#feat=transforms3d
 * @return {boolean}
 */
ol.dom.canUseCssTransform3D = (function() {
  var canUseCssTransform3D;
  return function() {
    if (canUseCssTransform3D === undefined) {
      goog.asserts.assert(!goog.isNull(document.body),
          'document.body should not be null');
      if (!goog.global.getComputedStyle) {
        // this browser is ancient
        canUseCssTransform3D = false;
      } else {
        var el = goog.dom.createElement(goog.dom.TagName.P),
            has3d,
            transforms = {
              'webkitTransform': '-webkit-transform',
              'OTransform': '-o-transform',
              'msTransform': '-ms-transform',
              'MozTransform': '-moz-transform',
              'transform': 'transform'
            };
        goog.dom.appendChild(document.body, el);
        for (var t in transforms) {
          if (t in el.style) {
            el.style[t] = 'translate3d(1px,1px,1px)';
            has3d = goog.global.getComputedStyle(el).getPropertyValue(
                transforms[t]);
          }
        }
        goog.dom.removeNode(el);

        canUseCssTransform3D = (has3d && has3d !== 'none');
      }
    }
    return canUseCssTransform3D;
  };
}());

/**
 * @param {Element} element Element.
 * @param {string} value Value.
 */
ol.dom.setTransform = function(element, value) {
  var style = element.style;
  style.WebkitTransform = value;
  style.MozTransform = value;
  style.OTransform = value;
  style.msTransform = value;
  style.transform = value;

  // IE 9+ seems to assume transform-origin: 100% 100%; for some unknown reason
  if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('9.0')) {
    element.style.transformOrigin = '0 0';
  }
};

/**
 * @param {!Element} element Element.
 * @param {goog.vec.Mat4.Number} transform Matrix.
 * @param {number=} opt_precision Precision.
 */
ol.dom.transformElement2D = function(element, transform, opt_precision) {
  // using matrix() causes gaps in Chrome and Firefox on Mac OS X, so prefer
  // matrix3d()
  var i;
  if (ol.dom.canUseCssTransform3D()) {
    var value3D;

    if (opt_precision !== undefined) {
      /** @type {Array.<string>} */
      var strings3D = new Array(16);
      for (i = 0; i < 16; ++i) {
        strings3D[i] = transform[i].toFixed(opt_precision);
      }
      value3D = strings3D.join(',');
    } else {
      value3D = transform.join(',');
    }
    ol.dom.setTransform(element, 'matrix3d(' + value3D + ')');
  } else if (ol.dom.canUseCssTransform()) {
    /** @type {Array.<number>} */
    var transform2D = [
      goog.vec.Mat4.getElement(transform, 0, 0),
      goog.vec.Mat4.getElement(transform, 1, 0),
      goog.vec.Mat4.getElement(transform, 0, 1),
      goog.vec.Mat4.getElement(transform, 1, 1),
      goog.vec.Mat4.getElement(transform, 0, 3),
      goog.vec.Mat4.getElement(transform, 1, 3)
    ];
    var value2D;
    if (opt_precision !== undefined) {
      /** @type {Array.<string>} */
      var strings2D = new Array(6);
      for (i = 0; i < 6; ++i) {
        strings2D[i] = transform2D[i].toFixed(opt_precision);
      }
      value2D = strings2D.join(',');
    } else {
      value2D = transform2D.join(',');
    }
    ol.dom.setTransform(element, 'matrix(' + value2D + ')');
  } else {
    element.style.left =
        Math.round(goog.vec.Mat4.getElement(transform, 0, 3)) + 'px';
    element.style.top =
        Math.round(goog.vec.Mat4.getElement(transform, 1, 3)) + 'px';

    // TODO: Add scaling here. This isn't quite as simple as multiplying
    // width/height, because that only changes the container size, not the
    // content size.
  }
};

/**
 * Get the current computed width for the given element including margin,
 * padding and border.
 * Equivalent to jQuery's `$(el).outerWidth(true)`.
 * @param {!Element} element Element.
 * @return {number}
 */
ol.dom.outerWidth = function(element) {
  var width = element.offsetWidth;
  var style = element.currentStyle || window.getComputedStyle(element);
  width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);

  return width;
};

/**
 * Get the current computed height for the given element including margin,
 * padding and border.
 * Equivalent to jQuery's `$(el).outerHeight(true)`.
 * @param {!Element} element Element.
 * @return {number}
 */
ol.dom.outerHeight = function(element) {
  var height = element.offsetHeight;
  var style = element.currentStyle || window.getComputedStyle(element);
  height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);

  return height;
};
<commit_msg>Remove goog.isNull in dom class<commit_after>// FIXME add tests for browser features (Modernizr?)

goog.provide('ol.dom');
goog.provide('ol.dom.BrowserFeature');

goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.userAgent');
goog.require('goog.vec.Mat4');
goog.require('ol');

/**
 * Create an html canvas element and returns its 2d context.
 * @param {number=} opt_width Canvas width.
 * @param {number=} opt_height Canvas height.
 * @return {CanvasRenderingContext2D}
 */
ol.dom.createCanvasContext2D = function(opt_width, opt_height) {
  var canvas = goog.dom.createElement(goog.dom.TagName.CANVAS);
  if (opt_width) {
    canvas.width = opt_width;
  }
  if (opt_height) {
    canvas.height = opt_height;
  }
  return canvas.getContext('2d');
};

/**
 * Detect 2d transform.
 * Adapted from http://stackoverflow.com/q/5661671/130442
 * http://caniuse.com/#feat=transforms2d
 * @return {boolean}
 */
ol.dom.canUseCssTransform = (function() {
  var canUseCssTransform;
  return function() {
    if (canUseCssTransform === undefined) {
      goog.asserts.assert(document.body,
          'document.body should not be null');
      if (!goog.global.getComputedStyle) {
        // this browser is ancient
        canUseCssTransform = false;
      } else {
        var el = goog.dom.createElement(goog.dom.TagName.P),
            has2d,
            transforms = {
              'webkitTransform': '-webkit-transform',
              'OTransform': '-o-transform',
              'msTransform': '-ms-transform',
              'MozTransform': '-moz-transform',
              'transform': 'transform'
            };
        goog.dom.appendChild(document.body, el);
        for (var t in transforms) {
          if (t in el.style) {
            el.style[t] = 'translate(1px,1px)';
            has2d = goog.global.getComputedStyle(el).getPropertyValue(
                transforms[t]);
          }
        }
        goog.dom.removeNode(el);

        canUseCssTransform = (has2d && has2d !== 'none');
      }
    }
    return canUseCssTransform;
  };
}());

/**
 * Detect 3d transform.
 * Adapted from http://stackoverflow.com/q/5661671/130442
 * http://caniuse.com/#feat=transforms3d
 * @return {boolean}
 */
ol.dom.canUseCssTransform3D = (function() {
  var canUseCssTransform3D;
  return function() {
    if (canUseCssTransform3D === undefined) {
      goog.asserts.assert(document.body,
          'document.body should not be null');
      if (!goog.global.getComputedStyle) {
        // this browser is ancient
        canUseCssTransform3D = false;
      } else {
        var el = goog.dom.createElement(goog.dom.TagName.P),
            has3d,
            transforms = {
              'webkitTransform': '-webkit-transform',
              'OTransform': '-o-transform',
              'msTransform': '-ms-transform',
              'MozTransform': '-moz-transform',
              'transform': 'transform'
            };
        goog.dom.appendChild(document.body, el);
        for (var t in transforms) {
          if (t in el.style) {
            el.style[t] = 'translate3d(1px,1px,1px)';
            has3d = goog.global.getComputedStyle(el).getPropertyValue(
                transforms[t]);
          }
        }
        goog.dom.removeNode(el);

        canUseCssTransform3D = (has3d && has3d !== 'none');
      }
    }
    return canUseCssTransform3D;
  };
}());

/**
 * @param {Element} element Element.
 * @param {string} value Value.
 */
ol.dom.setTransform = function(element, value) {
  var style = element.style;
  style.WebkitTransform = value;
  style.MozTransform = value;
  style.OTransform = value;
  style.msTransform = value;
  style.transform = value;

  // IE 9+ seems to assume transform-origin: 100% 100%; for some unknown reason
  if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('9.0')) {
    element.style.transformOrigin = '0 0';
  }
};

/**
 * @param {!Element} element Element.
 * @param {goog.vec.Mat4.Number} transform Matrix.
 * @param {number=} opt_precision Precision.
 */
ol.dom.transformElement2D = function(element, transform, opt_precision) {
  // using matrix() causes gaps in Chrome and Firefox on Mac OS X, so prefer
  // matrix3d()
  var i;
  if (ol.dom.canUseCssTransform3D()) {
    var value3D;

    if (opt_precision !== undefined) {
      /** @type {Array.<string>} */
      var strings3D = new Array(16);
      for (i = 0; i < 16; ++i) {
        strings3D[i] = transform[i].toFixed(opt_precision);
      }
      value3D = strings3D.join(',');
    } else {
      value3D = transform.join(',');
    }
    ol.dom.setTransform(element, 'matrix3d(' + value3D + ')');
  } else if (ol.dom.canUseCssTransform()) {
    /** @type {Array.<number>} */
    var transform2D = [
      goog.vec.Mat4.getElement(transform, 0, 0),
      goog.vec.Mat4.getElement(transform, 1, 0),
      goog.vec.Mat4.getElement(transform, 0, 1),
      goog.vec.Mat4.getElement(transform, 1, 1),
      goog.vec.Mat4.getElement(transform, 0, 3),
      goog.vec.Mat4.getElement(transform, 1, 3)
    ];
    var value2D;
    if (opt_precision !== undefined) {
      /** @type {Array.<string>} */
      var strings2D = new Array(6);
      for (i = 0; i < 6; ++i) {
        strings2D[i] = transform2D[i].toFixed(opt_precision);
      }
      value2D = strings2D.join(',');
    } else {
      value2D = transform2D.join(',');
    }
    ol.dom.setTransform(element, 'matrix(' + value2D + ')');
  } else {
    element.style.left =
        Math.round(goog.vec.Mat4.getElement(transform, 0, 3)) + 'px';
    element.style.top =
        Math.round(goog.vec.Mat4.getElement(transform, 1, 3)) + 'px';

    // TODO: Add scaling here. This isn't quite as simple as multiplying
    // width/height, because that only changes the container size, not the
    // content size.
  }
};

/**
 * Get the current computed width for the given element including margin,
 * padding and border.
 * Equivalent to jQuery's `$(el).outerWidth(true)`.
 * @param {!Element} element Element.
 * @return {number}
 */
ol.dom.outerWidth = function(element) {
  var width = element.offsetWidth;
  var style = element.currentStyle || window.getComputedStyle(element);
  width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);

  return width;
};

/**
 * Get the current computed height for the given element including margin,
 * padding and border.
 * Equivalent to jQuery's `$(el).outerHeight(true)`.
 * @param {!Element} element Element.
 * @return {number}
 */
ol.dom.outerHeight = function(element) {
  var height = element.offsetHeight;
  var style = element.currentStyle || window.getComputedStyle(element);
  height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);

  return height;
};
<|endoftext|>


<commit_before>'use strict';

module.exports = function unique(key) {
  let collection;

  if (key === undefined) {
    collection = this.items.filter(function(element, index, self) {
      return self.indexOf(element) === index;
    });
  } else {
    collection = [];

    const usedKeys = [];

    for (let iterator = 0; iterator < this.items.length; iterator++) {
      let uniqueKey;
      if (typeof key === 'function') {
        uniqueKey = key(this.items[iterator]);
      } else {
        uniqueKey = this.items[iterator][key];
      }

      if (usedKeys.indexOf(uniqueKey) === -1) {
        collection.push(this.items[iterator]);
        usedKeys.push(uniqueKey);
      }
    }
  }

  return new this.constructor(collection);
};
<commit_msg>improvement<commit_after>'use strict';

module.exports = function unique(key) {
  let collection;

  if (key === undefined) {
    collection = this.items.filter(function(element, index, self) {
      return self.indexOf(element) === index;
    });
  } else {
    collection = [];

    const usedKeys = [];

    for (let iterator = 0, length = this.items.length; iterator < length; iterator++) {
      let uniqueKey;
      if (typeof key === 'function') {
        uniqueKey = key(this.items[iterator]);
      } else {
        uniqueKey = this.items[iterator][key];
      }

      if (usedKeys.indexOf(uniqueKey) === -1) {
        collection.push(this.items[iterator]);
        usedKeys.push(uniqueKey);
      }
    }
  }

  return new this.constructor(collection);
};
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter
  , highlighter = require('./highlighter')
  , Slideshow = require('./models/slideshow')
  , SlideshowView = require('./views/slideshowView')
  , Controller = require('./controller')
  , utils = require('./utils')
  ;

// Expose highlighter to allow enumerating available styles and
// including external language grammars
module.exports.highlighter = highlighter;

// Creates slideshow initialized from options
module.exports.create = function (options) {
  var events
    , slideshow
    , slideshowView
    , controller
    ;

  options = applyDefaults(options);

  events = new EventEmitter();
  events.setMaxListeners(0);

  slideshow = new Slideshow(events, options);
  slideshowView = new SlideshowView(events, options.container, slideshow);
  controller = options.controller || new Controller(events, slideshowView,
    options.navigation);

  return slideshow;
};

function applyDefaults (options) {
  var sourceElement;

  options = options || {};

  if (!options.hasOwnProperty('source')) {
    sourceElement = document.getElementById('source');
    if (sourceElement) {
      options.source = unescape(sourceElement.innerHTML);
      sourceElement.style.display = 'none';
    }
  }

  if (!(options.container instanceof window.HTMLElement)) {
    options.container = utils.getBodyElement();
  }

  return options;
}

function unescape (source) {
  source = source.replace(/&[l|g]t;/g,
    function (match) {
      return match === '<' ? '<' : '>';
    });

  source = source.replace(/&/g, '&');
  source = source.replace(/"/g, '"');

  return source;
}
<commit_msg>Add support for sourceUrl (fixes previous tests)<commit_after>var EventEmitter = require('events').EventEmitter
  , highlighter = require('./highlighter')
  , Slideshow = require('./models/slideshow')
  , SlideshowView = require('./views/slideshowView')
  , Controller = require('./controller')
  , utils = require('./utils')
  ;

// Expose highlighter to allow enumerating available styles and
// including external language grammars
module.exports.highlighter = highlighter;

// Creates slideshow initialized from options
module.exports.create = function (options) {
  var events
    , slideshow
    , slideshowView
    , controller
    ;

  options = applyDefaults(options);

  events = new EventEmitter();
  events.setMaxListeners(0);

  slideshow = new Slideshow(events, options);
  slideshowView = new SlideshowView(events, options.container, slideshow);
  controller = options.controller || new Controller(events, slideshowView,
    options.navigation);

  return slideshow;
};

function applyDefaults (options) {
  var sourceElement;

  options = options || {};

  if (options.hasOwnProperty('sourceUrl')) {
    var req = new XMLHttpRequest();
    req.open('GET', options.sourceUrl, false);
    req.send();
    options.source = req.responseText;
  }
  else if (!options.hasOwnProperty('source')) {
    sourceElement = document.getElementById('source');
    if (sourceElement) {
      options.source = unescape(sourceElement.innerHTML);
      sourceElement.style.display = 'none';
    }
  }

  if (!(options.container instanceof window.HTMLElement)) {
    options.container = utils.getBodyElement();
  }

  return options;
}

function unescape (source) {
  source = source.replace(/&[l|g]t;/g,
    function (match) {
      return match === '<' ? '<' : '>';
    });

  source = source.replace(/&/g, '&');
  source = source.replace(/"/g, '"');

  return source;
}
<|endoftext|>


<commit_before>/*!
 * Sizzle CSS Selector Engine v1.10.20-pre
 * http://sizzlejs.com/
 *
 * Copyright 2013 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2014-04-21
 */

/**
 * Fizzle.js
 * Adapted from Sizzle.js
 */

var _throwError = function(selector) {
    throw new Error(selector);
};

var _CACHE_SIZE = 50;

/**
 * Create key-value caches of limited size
 * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
 *	deleting the oldest entry
 */
var createCache = function() {
    var keys = [];

    function cache(key, value) {
        // Use (key + ' ') to avoid collision with native prototype properties (see Issue #157)
        if ( keys.push(key + ' ') > _CACHE_SIZE ) {
            // Only keep the most recent entries
            delete cache[keys.shift()];
        }
        return (cache[key + ' '] = value);
    }

    return {
        get: function(key) { return cache[key + ' ']; },
        set: function(key, value) { return cache(key, value); }
    };
};

var tokenCache = createCache();

var booleans = 'checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped',

    // http://www.w3.org/TR/css3-selectors/#whitespace
    whitespace = "[\\x20\\t\\r\\n\\f]",

    // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
    identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",

    // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
    attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
        // Operator (capture 2)
        "*([*^$|!~]?=)" + whitespace +
        // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
        "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
        "*\\]",

    pseudos = ":(" + identifier + ")(?:\\((" +
        // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
        // 1. quoted (capture 3; capture 4 or capture 5)
        "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
        // 2. simple (capture 6)
        "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
        // 3. anything else (capture 2)
        ".*" +
        ")\\)|)",

    // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
    rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"),

    rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*"),
    rcombinators = new RegExp("^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*"),

    rpseudo = new RegExp(pseudos),

    matchExpr = {
        ID:     new RegExp("^#(" + identifier + ")"),
        CLASS:  new RegExp("^\\.(" + identifier + ")"),
        TAG:    new RegExp("^(" + identifier + "|[*])"),
        ATTR:   new RegExp("^" + attributes),
        PSEUDO: new RegExp("^" + pseudos),
        CHILD:  new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
            "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
            "*(\\d+)|))" + whitespace + "*\\)|)", "i"),

        bool:   new RegExp("^(?:" + booleans + ")$", "i"),

        // For use in libraries implementing .is()
        // We use this for POS matching in `select`
        needsContext: new RegExp("^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
            whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i")
    },

    // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
    runescape = new RegExp("\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig"),
    funescape = function(_, escaped, escapedWhitespace) {
        var high = '0x' + escaped - 0x10000;
        // NaN means non-codepoint
        // Support: Firefox<24
        // Workaround erroneous numeric interpretation of +'0x'
        return high !== high || escapedWhitespace ?
            escaped :
            high < 0 ?
                // BMP codepoint
                String.fromCharCode( high + 0x10000 ) :
                // Supplemental Plane codepoint (surrogate pair)
                String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
    };

var preFilter = {
    'ATTR': function(match) {
        match[1] = match[1].replace(runescape, funescape);

        // Move the given value to match[3] whether quoted or unquoted
        match[3] = ( match[3] || match[4] || match[5] || '' ).replace(runescape, funescape);

        if (match[2] === '~=') {
            match[3] = ' ' + match[3] + ' ';
        }

        return match.slice(0, 4);
    },

    'CHILD': function(match) {
        /* matches from matchExpr['CHILD']
            1 type (only|nth|...)
            2 what (child|of-type)
            3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
            4 xn-component of xn+y argument ([+-]?\d*n|)
            5 sign of xn-component
            6 x of xn-component
            7 sign of y-component
            8 y of y-component
         */
        match[1] = match[1].toLowerCase();

        if (match[1].slice(0, 3) === 'nth') {
            // nth-* requires argument
            if (!match[3]) {
                throw new Error(match[0]);
            }

            // numeric x and y parameters for Expr.filter.CHILD
            // remember that false/true cast respectively to 0/1
            match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === 'even' || match[3] === 'odd' ) );
            match[5] = +( ( match[7] + match[8] ) || match[3] === 'odd' );

        // other types prohibit arguments
        } else if (match[3]) {
            throw new Error(match[0]);
        }

        return match;
    },

    'PSEUDO': function(match) {
        var excess,
            unquoted = !match[6] && match[2];

        if (matchExpr['CHILD'].test(match[0])) {
            return null;
        }

        // Accept quoted arguments as-is
        if (match[3]) {
            match[2] = match[4] || match[5] || '';

        // Strip excess characters from unquoted arguments
        } else if (unquoted && rpseudo.test(unquoted) &&
            // Get excess from tokenize (recursively)
            (excess = tokenize(unquoted, true)) &&
            // advance to the next closing parenthesis
            (excess = unquoted.indexOf(')', unquoted.length - excess) - unquoted.length)) {

            // excess is a negative index
            match[0] = match[0].slice(0, excess);
            match[2] = unquoted.slice(0, excess);
        }

        // Return only captures needed by the pseudo filter method (type and argument)
        return match.slice(0, 3);
    }
};

/**
 * Splits the given comma-separated CSS selector into separate sub-queries.
 * @param  {String} selector Full CSS selector (e.g., 'a, input:focus, div[attr="value"]').
 * @param  {Boolean} [parseOnly]
 * @return {String[]|Number} Array of sub-queries (e.g., [ 'a', 'input:focus', 'div[attr="(value1),[value2]"]').
 */
var tokenize = function(selector, parseOnly) {
    var cached = tokenCache.get(selector);

    if (cached) {
        return parseOnly ? 0 : cached.slice( 0 );
    }

    // TODO: Use utils
    // TODO: Use cache
    var soFar = selector.trim();

    var type,
        regex,
        match,
        matched,
        groups = [],
        tokens = [];

    while (soFar) {
        // Comma and first run
        if (!matched || (match = rcomma.exec(soFar))) {
            if (match) {
                // Don't consume trailing commas as valid
                soFar = soFar.slice(match[0].length) || soFar;
            }
            groups.push((tokens = []));
        }

        matched = null;

        // Combinators
        if ((match = rcombinators.exec(soFar))) {
            matched = match.shift();
            tokens.push({
                value: matched,
                match: match,
                // Cast descendant combinators to space
                type: match[0].replace(rtrim, ' ')
            });
            soFar = soFar.slice(matched.length);
        }

        // Filters
        for (type in matchExpr) {
            regex = matchExpr[type];
            match = regex.exec(soFar);

            if (match && (!preFilter[type] || (match = preFilter[type](match)))) {
                matched = match.shift();
                tokens.push({
                    value: matched,
                    match: match,
                    type: type
                });
                soFar = soFar.slice(matched.length);

                break;
            }
        }

        if (!matched) {
            break;
        }
    }

    // Return the length of the invalid excess
    // if we're just parsing
    // Otherwise, throw an error or return tokens
    return parseOnly ?
        soFar.length :
        soFar ?
            _throwError( selector ) :
            // Cache the tokens
            tokenCache.set(selector, groups).slice();
};

module.exports = {
    subqueries: tokenize
};
<commit_msg>Add a todo<commit_after>// TODO: Move into "query" dir and rename "qeuery" to "fizzle". Make those scripts all one engine
/*!
 * Sizzle CSS Selector Engine v1.10.20-pre
 * http://sizzlejs.com/
 *
 * Copyright 2013 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2014-04-21
 */

/**
 * Fizzle.js
 * Adapted from Sizzle.js
 */

var _throwError = function(selector) {
    throw new Error(selector);
};

var _CACHE_SIZE = 50;

/**
 * Create key-value caches of limited size
 * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
 *	deleting the oldest entry
 */
var createCache = function() {
    var keys = [];

    function cache(key, value) {
        // Use (key + ' ') to avoid collision with native prototype properties (see Issue #157)
        if ( keys.push(key + ' ') > _CACHE_SIZE ) {
            // Only keep the most recent entries
            delete cache[keys.shift()];
        }
        return (cache[key + ' '] = value);
    }

    return {
        get: function(key) { return cache[key + ' ']; },
        set: function(key, value) { return cache(key, value); }
    };
};

var tokenCache = createCache();

var booleans = 'checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped',

    // http://www.w3.org/TR/css3-selectors/#whitespace
    whitespace = "[\\x20\\t\\r\\n\\f]",

    // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
    identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",

    // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
    attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
        // Operator (capture 2)
        "*([*^$|!~]?=)" + whitespace +
        // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
        "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
        "*\\]",

    pseudos = ":(" + identifier + ")(?:\\((" +
        // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
        // 1. quoted (capture 3; capture 4 or capture 5)
        "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
        // 2. simple (capture 6)
        "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
        // 3. anything else (capture 2)
        ".*" +
        ")\\)|)",

    // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
    rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"),

    rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*"),
    rcombinators = new RegExp("^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*"),

    rpseudo = new RegExp(pseudos),

    matchExpr = {
        ID:     new RegExp("^#(" + identifier + ")"),
        CLASS:  new RegExp("^\\.(" + identifier + ")"),
        TAG:    new RegExp("^(" + identifier + "|[*])"),
        ATTR:   new RegExp("^" + attributes),
        PSEUDO: new RegExp("^" + pseudos),
        CHILD:  new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
            "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
            "*(\\d+)|))" + whitespace + "*\\)|)", "i"),

        bool:   new RegExp("^(?:" + booleans + ")$", "i"),

        // For use in libraries implementing .is()
        // We use this for POS matching in `select`
        needsContext: new RegExp("^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
            whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i")
    },

    // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
    runescape = new RegExp("\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig"),
    funescape = function(_, escaped, escapedWhitespace) {
        var high = '0x' + escaped - 0x10000;
        // NaN means non-codepoint
        // Support: Firefox<24
        // Workaround erroneous numeric interpretation of +'0x'
        return high !== high || escapedWhitespace ?
            escaped :
            high < 0 ?
                // BMP codepoint
                String.fromCharCode( high + 0x10000 ) :
                // Supplemental Plane codepoint (surrogate pair)
                String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
    };

var preFilter = {
    'ATTR': function(match) {
        match[1] = match[1].replace(runescape, funescape);

        // Move the given value to match[3] whether quoted or unquoted
        match[3] = ( match[3] || match[4] || match[5] || '' ).replace(runescape, funescape);

        if (match[2] === '~=') {
            match[3] = ' ' + match[3] + ' ';
        }

        return match.slice(0, 4);
    },

    'CHILD': function(match) {
        /* matches from matchExpr['CHILD']
            1 type (only|nth|...)
            2 what (child|of-type)
            3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
            4 xn-component of xn+y argument ([+-]?\d*n|)
            5 sign of xn-component
            6 x of xn-component
            7 sign of y-component
            8 y of y-component
         */
        match[1] = match[1].toLowerCase();

        if (match[1].slice(0, 3) === 'nth') {
            // nth-* requires argument
            if (!match[3]) {
                throw new Error(match[0]);
            }

            // numeric x and y parameters for Expr.filter.CHILD
            // remember that false/true cast respectively to 0/1
            match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === 'even' || match[3] === 'odd' ) );
            match[5] = +( ( match[7] + match[8] ) || match[3] === 'odd' );

        // other types prohibit arguments
        } else if (match[3]) {
            throw new Error(match[0]);
        }

        return match;
    },

    'PSEUDO': function(match) {
        var excess,
            unquoted = !match[6] && match[2];

        if (matchExpr['CHILD'].test(match[0])) {
            return null;
        }

        // Accept quoted arguments as-is
        if (match[3]) {
            match[2] = match[4] || match[5] || '';

        // Strip excess characters from unquoted arguments
        } else if (unquoted && rpseudo.test(unquoted) &&
            // Get excess from tokenize (recursively)
            (excess = tokenize(unquoted, true)) &&
            // advance to the next closing parenthesis
            (excess = unquoted.indexOf(')', unquoted.length - excess) - unquoted.length)) {

            // excess is a negative index
            match[0] = match[0].slice(0, excess);
            match[2] = unquoted.slice(0, excess);
        }

        // Return only captures needed by the pseudo filter method (type and argument)
        return match.slice(0, 3);
    }
};

/**
 * Splits the given comma-separated CSS selector into separate sub-queries.
 * @param  {String} selector Full CSS selector (e.g., 'a, input:focus, div[attr="value"]').
 * @param  {Boolean} [parseOnly]
 * @return {String[]|Number} Array of sub-queries (e.g., [ 'a', 'input:focus', 'div[attr="(value1),[value2]"]').
 */
var tokenize = function(selector, parseOnly) {
    var cached = tokenCache.get(selector);

    if (cached) {
        return parseOnly ? 0 : cached.slice( 0 );
    }

    // TODO: Use utils
    // TODO: Use cache
    var soFar = selector.trim();

    var type,
        regex,
        match,
        matched,
        groups = [],
        tokens = [];

    while (soFar) {
        // Comma and first run
        if (!matched || (match = rcomma.exec(soFar))) {
            if (match) {
                // Don't consume trailing commas as valid
                soFar = soFar.slice(match[0].length) || soFar;
            }
            groups.push((tokens = []));
        }

        matched = null;

        // Combinators
        if ((match = rcombinators.exec(soFar))) {
            matched = match.shift();
            tokens.push({
                value: matched,
                match: match,
                // Cast descendant combinators to space
                type: match[0].replace(rtrim, ' ')
            });
            soFar = soFar.slice(matched.length);
        }

        // Filters
        for (type in matchExpr) {
            regex = matchExpr[type];
            match = regex.exec(soFar);

            if (match && (!preFilter[type] || (match = preFilter[type](match)))) {
                matched = match.shift();
                tokens.push({
                    value: matched,
                    match: match,
                    type: type
                });
                soFar = soFar.slice(matched.length);

                break;
            }
        }

        if (!matched) {
            break;
        }
    }

    // Return the length of the invalid excess
    // if we're just parsing
    // Otherwise, throw an error or return tokens
    return parseOnly ?
        soFar.length :
        soFar ?
            _throwError( selector ) :
            // Cache the tokens
            tokenCache.set(selector, groups).slice();
};

module.exports = {
    subqueries: tokenize
};
<|endoftext|>


<commit_before>(function () {
    "use strict";

    var loader = moduler('loader', {
        defaults: {
            url: '',
            contentElement: null,
            event: 'click',
            once: true,
            responseModule: null,
            loadOnInit: false,
            loadingCssClass: 'loading'
        },

        init: function (module) {
            var settings = module.settings;

            if (settings.event) {
                module.$element.on(settings.event + '.loader', module, loader.listen.sendRequest);
            }

            if (settings.loadOnInit) {
                loader.listen.sendRequest(module);
            }
        },

        listen: {
            sendRequest: mo.event(function(module) {
                var settings = module.settings,
                    element = module.$element,
                    $contentElement = module.settings.contentElement !== null ? $(module.settings.contentElement) : module.$element;

                if (settings.once && module.isLoaded)
                    return;

                element.addClass(settings.loadingCssClass);

                $.ajax({
                    url: settings.url
                }).always(function () {
                    element.removeClass(settings.loadingCssClass);

                    // trigger the module prefixed event that other modules can use it.
                    element.trigger('loader-' + settings.event);
                }).done(function (response, status, xhr) {
                    if (module.settings.responseModule) {
                        mo.utils.removeModuleFromElement($contentElement, module.settings.responseModule);
                        mo.utils.addModuleToElement($contentElement, module.settings.responseModule, { response: response });
                    } else if ($contentElement.length && xhr.getResponseHeader('content-type').indexOf('text/html') !== -1) {
                        $contentElement.html(response);
                    }

                    module.isLoaded = true;
                    element.trigger('loader-done', { response: response });
                });

                if (settings.once && settings.event) {
                    $(this).off(settings.event + '.loader').on(settings.event + '.loader', function () {
                        element.trigger('loader.' + settings.event);
                    });
                }
            })
        }
    });
})();<commit_msg>Added insertMode option to loader<commit_after>(function () {
    "use strict";

    var loader = moduler('loader', {
        defaults: {
            url: '',
            contentElement: null,
            event: 'click',
            once: true,
            responseModule: null,
            loadOnInit: false,
            loadingCssClass: 'loading',
            insertMode: 'replace', /* replace|append */
        },

        init: function (module) {
            var settings = module.settings;

            if (settings.event) {
                module.$element.on(settings.event + '.loader', module, loader.listen.sendRequest);
            }

            if (settings.loadOnInit) {
                loader.listen.sendRequest(module);
            }
        },

        listen: {
            sendRequest: mo.event(function(module) {
                var settings = module.settings,
                    element = module.$element,
                    $contentElement = module.settings.contentElement !== null ? $(module.settings.contentElement) : module.$element;

                if (settings.once && module.isLoaded)
                    return;

                element.addClass(settings.loadingCssClass);

                $.ajax({
                    url: settings.url
                }).always(function () {
                    element.removeClass(settings.loadingCssClass);

                    // trigger the module prefixed event that other modules can use it.
                    element.trigger('loader-' + settings.event);
                }).done(function (response, status, xhr) {
                    if (module.settings.responseModule) {
                        mo.utils.removeModuleFromElement($contentElement, module.settings.responseModule);
                        mo.utils.addModuleToElement($contentElement, module.settings.responseModule, { response: response });
                    } else if ($contentElement.length && xhr.getResponseHeader('content-type').indexOf('text/html') !== -1) {
                        if (module.settings.insertMode == 'replace') {
                            $contentElement.html(response);
                        } else if (module.settings.insertMode == 'append') {
                            $contentElement.append(response);
                        }
                    }

                    module.isLoaded = true;
                    element.trigger('loader-done', { response: response });
                });

                if (settings.once && settings.event) {
                    $(this).off(settings.event + '.loader').on(settings.event + '.loader', function () {
                        element.trigger('loader.' + settings.event);
                    });
                }
            })
        }
    });
})();<|endoftext|>


<commit_before>
'use strict';

var TetrisPresenter = require('modules/tetrisPresenterNA.js').TetrisPresenter;
var Gameboard = require('modules/gameboard.js').Gameboard;
var LevelManager = require('modules/levelManager.js');
var getRandomPiece = require('modules/piecesGenerator.js').getRandomPiece;
var utils = require('utils/utils.js');
var TopScoresRepository = require('modules/topScoresRepository.js').TopScoresRepository;

var WITH_GHOST_PIECE = true;

function Tetris(containerElement, driver) {
    this._levelManager = new LevelManager();
    this._presenter = new TetrisPresenter(containerElement);
    this._presenter.init();

    this._oldPosition = [];
    this.initGame(driver);
}

Tetris.prototype.destroy = function() {
    this._presenter.destroy();
};

Tetris.prototype.initGame = function(driver) {
    driver.keyboardState.disableRepeat('UpArrow');
    driver.keyboardState.disableRepeat('Space');

    this._gameboard = new Gameboard();
    this._nextPiece = getRandomPiece();

    this._presenter.setLevel(1);
    this._presenter.setPoints(0);

    this._nextCurrentPiece();

    this._movementFrame = 0;
    this._pause = false;
};

Tetris.prototype.getInterval = function() {
    return 50;
};

Tetris.prototype._nextCurrentPiece = function() {
    this._currentPiece = this._nextPiece;
    this._nextPiece = getRandomPiece();
    this._levelManager.pieceMovedDown();

    this._centerCurrentPiece();

    this._presenter.updateNext(this._nextPiece);
};

Tetris.prototype._centerCurrentPiece = function() {
    var box = this._currentPiece.boundingBox();

    var pieceWidth = box.width();
    var gameboardWidth = this._gameboard.width();

    var offsetCol = utils.getRandomElement([
        Math.floor((gameboardWidth-pieceWidth)/2),
        Math.floor((gameboardWidth-pieceWidth+1)/2)
    ]);
    offsetCol -= box.minCol;

    var offsetRow = (box.height() === 1) ? 1 : 0;
    offsetRow -= this._gameboard.hiddenHeight();

    this._currentPiece.translate(offsetRow, offsetCol);
};

Tetris.prototype._tryRotateCurrentPiece = function()  {
    this._currentPiece.savePositionTo(this._oldPosition);

    this._currentPiece.rotateClockwise();
    if (this._gameboard.hasValidPosition(this._currentPiece)) {
        return true;
    }

    // side kicks from walls
    this._currentPiece.translate(0,-1);
    if (this._gameboard.hasValidPosition(this._currentPiece)) {
        return true;
    }

    this._currentPiece.translate(0, 2);
    if (this._gameboard.hasValidPosition(this._currentPiece)) {
        return true;
    }

    // TODO: Make buzz sound
    this._currentPiece.restorePositionFrom(this._oldPosition);
    return false;
};

Tetris.prototype._canTranslatePiece = function(piece, drow, dcol) {
    piece.savePositionTo(this._oldPosition);

    piece.translate(drow, dcol);
    var result = this._gameboard.hasValidPosition(piece);

    piece.restorePositionFrom(this._oldPosition);

    return result;
};

Tetris.prototype._tryTranslateCurrentPiece = function(drow, dcol) {
    if (this._canTranslatePiece(this._currentPiece, drow, dcol)) {
        this._currentPiece.translate(drow, dcol);
        return true;
    }
    else {
        return false;
    }
};

Tetris.prototype._isLocked = function(category) {
    return this._lock && this._lock[category];
};

Tetris.prototype._lockFor = function(category, timeMs) {
    var that = this;

    var lock = that._lock = (that._lock || {});
    lock[category] = true;

    setTimeout(function() {
        lock[category] = false;
    }, timeMs);

    return this;
};

Tetris.prototype._saveCommand = function(ks) {
    var command = null;

    if (ks.isEscapePressed()) {
        command = 'esc';
    }
    else if (ks.isDownArrowPressed()) {
        command = 'down';
    }
    else if (ks.isUpArrowPressed()) {
        command = 'up';
    }
    else if (ks.isRightArrowPressed()) {
        command = 'right';
    }
    else if (ks.isLeftArrowPressed()) {
        command = 'left';
    }
    else if (ks.isSpacePressed()) {
        command = 'space';
    }
    else {
        return;
    }

    this._command = command;
    ks.clear();
};

Tetris.prototype._getCommand = function() {
    var command = this._command;
    this._command = null;
    return command;
};

Tetris.prototype._hasHiddenParts = function(piece) {
    var box = piece.boundingBox();
    return (box.minRow < 0);
};

Tetris.prototype._gameOver = function() {
    var points = this._levelManager.points();

    this._gameIsOver = true;
    this._presenter.showGameOverScreen(points);

    var topScoresRepo = new TopScoresRepository();
    if (topScoresRepo.canAddScore(points)) {
        var userName = prompt('Whats your name (for Top Scores)?');
        if (userName) {
            topScoresRepo.addTopScore(userName, points);
        }
    }
};

Tetris.prototype._tryLockCurrentPiece = function() {
    if (!this._canTranslatePiece(this._currentPiece, 1, 0)) {
        if (this._hasHiddenParts(this._currentPiece)) {
            this._gameOver();
        }
        else {
            this._gameboard.addPiece(this._currentPiece);
            this._nextCurrentPiece();
            this._lockFor(500);
        }

        return true;
    }

    return false;
};

Tetris.prototype._getGhostPiece = function() {
    var ghostPiece = this._currentPiece.copy();

    while (this._canTranslatePiece(ghostPiece, 1, 0)) {
        ghostPiece.translate(1, 0);
    }

    return ghostPiece;
};

Tetris.prototype._moveCurrentPieceDown = function() {
    if (this._levelManager.shouldMovePieceDown()) {
        this._tryTranslateCurrentPiece(1, 0);
        this._levelManager.pieceMovedDown();
    }
};

Tetris.prototype._tryRemoveFullRow = function() {
    if (this._isLocked('removeRow')) { return; }

    var fullRowRemoved = this._gameboard.removeFirstFullRow();

    if (fullRowRemoved !== null) {
        this._levelManager.addRowRemovedPoints();
        this._presenter.fullRowRemovedAnimation(
            fullRowRemoved.row, fullRowRemoved.colors);

        this._lockFor('game', 1000);
        this._lockFor('removeRow', 200);
        this._redraw();
        return true;
    }

    return false;
};

Tetris.prototype._pauseUnpause = function(keyboardState) {
    if (!keyboardState.isSpacePressed()) {
        this._pausePressed = false;
        return;
    }
    if (this._pausePressed) { return; }

    this._command = null;
    this._pause = !this._pause;
    this._pausePressed = true;

    if (this._pause) {
        this._presenter.showInfoScreen('PAUSE\nPress Space to continue...');
    }
    else {
        this._presenter.hideInfoScreen();
    }
};

Tetris.prototype.run = function(driver, diffMs) {
    if (driver.keyboardState.isEscapePressed()) {
        driver.setModule('MainMenu');
        return;
    }
    if (this._gameIsOver) { return; }

    this._pauseUnpause(driver.keyboardState);
    if (this._pause) { return; }

    this._saveCommand(driver.keyboardState);
    this._levelManager.updateTime(diffMs);

    var rowRemoved = this._tryRemoveFullRow();
    if (this._isLocked('game')) {
        return;
    }

    var command = this._getCommand();
    switch(command) {
        case 'left': case 'right':
            this._tryTranslateCurrentPiece(0, command === 'left' ? -1 : 1);
            break;

        case 'down':
            this._tryTranslateCurrentPiece(1, 0);
            break;

        case 'up':
            this._tryRotateCurrentPiece();
            break;
    }

    if (!rowRemoved && !this._tryLockCurrentPiece()) {
        this._moveCurrentPieceDown();
        this._redraw(WITH_GHOST_PIECE);
    }
    else {
        this._redraw();
    }
};

Tetris.prototype._redraw = function(drawGhost) {
    if (drawGhost) {
        this._presenter.draw(this._getGhostPiece().getBlocks(), { ghost: true });
    }

    this._presenter.draw(this._currentPiece.getBlocks());
    this._presenter.draw(this._gameboard.getBlocks());
    this._presenter.setPoints(this._levelManager.points());
    this._presenter.setLevel(this._levelManager.level());

    this._presenter.swapBuffers();
};

exports.Tetris = Tetris;
<commit_msg>Fix bug: no current piece timeout<commit_after>
'use strict';

var TetrisPresenter = require('modules/tetrisPresenterNA.js').TetrisPresenter;
var Gameboard = require('modules/gameboard.js').Gameboard;
var LevelManager = require('modules/levelManager.js');
var getRandomPiece = require('modules/piecesGenerator.js').getRandomPiece;
var utils = require('utils/utils.js');
var TopScoresRepository = require('modules/topScoresRepository.js').TopScoresRepository;

var WITH_GHOST_PIECE = true;

function Tetris(containerElement, driver) {
    this._levelManager = new LevelManager();
    this._presenter = new TetrisPresenter(containerElement);
    this._presenter.init();

    this._oldPosition = [];
    this.initGame(driver);
}

Tetris.prototype.destroy = function() {
    this._presenter.destroy();
};

Tetris.prototype.initGame = function(driver) {
    driver.keyboardState.disableRepeat('UpArrow');
    driver.keyboardState.disableRepeat('Space');

    this._gameboard = new Gameboard();
    this._nextPiece = getRandomPiece();

    this._presenter.setLevel(1);
    this._presenter.setPoints(0);

    this._nextCurrentPiece();

    this._movementFrame = 0;
    this._pause = false;
};

Tetris.prototype.getInterval = function() {
    return 50;
};

Tetris.prototype._nextCurrentPiece = function() {
    this._currentPiece = this._nextPiece;
    this._nextPiece = getRandomPiece();
    this._levelManager.pieceMovedDown();

    this._centerCurrentPiece();

    this._presenter.updateNext(this._nextPiece);
};

Tetris.prototype._centerCurrentPiece = function() {
    var box = this._currentPiece.boundingBox();

    var pieceWidth = box.width();
    var gameboardWidth = this._gameboard.width();

    var offsetCol = utils.getRandomElement([
        Math.floor((gameboardWidth-pieceWidth)/2),
        Math.floor((gameboardWidth-pieceWidth+1)/2)
    ]);
    offsetCol -= box.minCol;

    var offsetRow = (box.height() === 1) ? 1 : 0;
    offsetRow -= this._gameboard.hiddenHeight();

    this._currentPiece.translate(offsetRow, offsetCol);
};

Tetris.prototype._tryRotateCurrentPiece = function()  {
    this._currentPiece.savePositionTo(this._oldPosition);

    this._currentPiece.rotateClockwise();
    if (this._gameboard.hasValidPosition(this._currentPiece)) {
        return true;
    }

    // side kicks from walls
    this._currentPiece.translate(0,-1);
    if (this._gameboard.hasValidPosition(this._currentPiece)) {
        return true;
    }

    this._currentPiece.translate(0, 2);
    if (this._gameboard.hasValidPosition(this._currentPiece)) {
        return true;
    }

    // TODO: Make buzz sound
    this._currentPiece.restorePositionFrom(this._oldPosition);
    return false;
};

Tetris.prototype._canTranslatePiece = function(piece, drow, dcol) {
    piece.savePositionTo(this._oldPosition);

    piece.translate(drow, dcol);
    var result = this._gameboard.hasValidPosition(piece);

    piece.restorePositionFrom(this._oldPosition);

    return result;
};

Tetris.prototype._tryTranslateCurrentPiece = function(drow, dcol) {
    if (this._canTranslatePiece(this._currentPiece, drow, dcol)) {
        this._currentPiece.translate(drow, dcol);
        return true;
    }
    else {
        return false;
    }
};

Tetris.prototype._isLocked = function(category) {
    return this._lock && this._lock[category];
};

Tetris.prototype._lockFor = function(category, timeMs) {
    var that = this;

    var lock = that._lock = (that._lock || {});
    lock[category] = true;

    setTimeout(function() {
        lock[category] = false;
    }, timeMs);

    return this;
};

Tetris.prototype._saveCommand = function(ks) {
    var command = null;

    if (ks.isEscapePressed()) {
        command = 'esc';
    }
    else if (ks.isDownArrowPressed()) {
        command = 'down';
    }
    else if (ks.isUpArrowPressed()) {
        command = 'up';
    }
    else if (ks.isRightArrowPressed()) {
        command = 'right';
    }
    else if (ks.isLeftArrowPressed()) {
        command = 'left';
    }
    else if (ks.isSpacePressed()) {
        command = 'space';
    }
    else {
        return;
    }

    this._command = command;
    ks.clear();
};

Tetris.prototype._getCommand = function() {
    var command = this._command;
    this._command = null;
    return command;
};

Tetris.prototype._hasHiddenParts = function(piece) {
    var box = piece.boundingBox();
    return (box.minRow < 0);
};

Tetris.prototype._gameOver = function() {
    var points = this._levelManager.points();

    this._gameIsOver = true;
    this._presenter.showGameOverScreen(points);

    var topScoresRepo = new TopScoresRepository();
    if (topScoresRepo.canAddScore(points)) {
        var userName = prompt('Whats your name (for Top Scores)?');
        if (userName) {
            topScoresRepo.addTopScore(userName, points);
        }
    }
};

Tetris.prototype._tryLockCurrentPiece = function() {
    if (!this._canTranslatePiece(this._currentPiece, 1, 0)) {
        if (this._hasHiddenParts(this._currentPiece)) {
            this._gameOver();
        }
        else {
            this._gameboard.addPiece(this._currentPiece);
            this._nextCurrentPiece();
            this._lockFor('game', 500);
        }

        return true;
    }

    return false;
};

Tetris.prototype._getGhostPiece = function() {
    var ghostPiece = this._currentPiece.copy();

    while (this._canTranslatePiece(ghostPiece, 1, 0)) {
        ghostPiece.translate(1, 0);
    }

    return ghostPiece;
};

Tetris.prototype._moveCurrentPieceDown = function() {
    if (this._levelManager.shouldMovePieceDown()) {
        this._tryTranslateCurrentPiece(1, 0);
        this._levelManager.pieceMovedDown();
    }
};

Tetris.prototype._tryRemoveFullRow = function() {
    if (this._isLocked('removeRow')) { return; }

    var fullRowRemoved = this._gameboard.removeFirstFullRow();

    if (fullRowRemoved !== null) {
        this._levelManager.addRowRemovedPoints();
        this._presenter.fullRowRemovedAnimation(
            fullRowRemoved.row, fullRowRemoved.colors);

        this._lockFor('game', 1000);
        this._lockFor('removeRow', 200);
        this._redraw();
        return true;
    }

    return false;
};

Tetris.prototype._pauseUnpause = function(keyboardState) {
    if (!keyboardState.isSpacePressed()) {
        this._pausePressed = false;
        return;
    }
    if (this._pausePressed) { return; }

    this._command = null;
    this._pause = !this._pause;
    this._pausePressed = true;

    if (this._pause) {
        this._presenter.showInfoScreen('PAUSE\nPress Space to continue...');
    }
    else {
        this._presenter.hideInfoScreen();
    }
};

Tetris.prototype.run = function(driver, diffMs) {
    if (driver.keyboardState.isEscapePressed()) {
        driver.setModule('MainMenu');
        return;
    }
    if (this._gameIsOver) { return; }

    this._pauseUnpause(driver.keyboardState);
    if (this._pause) { return; }

    this._saveCommand(driver.keyboardState);
    this._levelManager.updateTime(diffMs);

    var rowRemoved = this._tryRemoveFullRow();
    if (this._isLocked('game')) {
        return;
    }

    var command = this._getCommand();
    switch(command) {
        case 'left': case 'right':
            this._tryTranslateCurrentPiece(0, command === 'left' ? -1 : 1);
            break;

        case 'down':
            this._tryTranslateCurrentPiece(1, 0);
            break;

        case 'up':
            this._tryRotateCurrentPiece();
            break;
    }

    if (!rowRemoved && !this._tryLockCurrentPiece()) {
        this._moveCurrentPieceDown();
        this._redraw(WITH_GHOST_PIECE);
    }
    else {
        this._redraw();
    }
};

Tetris.prototype._redraw = function(drawGhost) {
    if (drawGhost) {
        this._presenter.draw(this._getGhostPiece().getBlocks(), { ghost: true });
    }

    this._presenter.draw(this._currentPiece.getBlocks());
    this._presenter.draw(this._gameboard.getBlocks());
    this._presenter.setPoints(this._levelManager.points());
    this._presenter.setLevel(this._levelManager.level());

    this._presenter.swapBuffers();
};

exports.Tetris = Tetris;
<|endoftext|>


<commit_before>var service = require('./../minions/service');
var apiConfig = require('./api-config');
var Q = require('q');
var express = require('express');

var app = express();

apiConfig.setupMiddleware(app);

app.use(function(req, res, next) {
    if(req.eve.solarsystem.id){
        console.log('Tracking... ' + JSON.stringify(req.eve.solarsystem.id))
        var pilotId = req.session.passport.user;
        service.updateTracker(pilotId, req.eve);
    }else{
        console.log('No headers :(')
    }
    next();
})

/* GET get self*/
app.get('/me', function(req, res, next) {
    var pilotId = req.session.passport.user;
    service.findPilot(pilotId)
        .then(function (pilot) {
            pilot.eve = req.eve;    //add request headers
            res.json(pilot);
        })
        .catch(function (e) {
            next(e);
        })
});

app.post('/intel', function(req, res, next) {
    var systemId   = req.eve.solarsystem.id;
    var timestamp  = req.query.timestamp;
    var state      = req.query.state;
    var pilotId = req.session.passport.user;
    service.updateTracker(pilotId, req.eve);
    service.createIntelReport(pilotId, systemId, timestamp, state)
        .then(function () {
            res.status(200);
        })
        .catch(function (e) {
            next(e);
        })
});

app.get('/intel', function(req, res, next) {
    var timestamp   = req.query.last_update;
    var pilotId = req.session.passport.user;
    service.getIntel(pilotId,timestamp)
        .then(function (intel) {
            res.json(intel);
        })
        .catch(function (e) {
            next(e);
        })
});

module.exports = app;
<commit_msg>better tracking<commit_after>var service = require('./../minions/service');
var apiConfig = require('./api-config');
var Q = require('q');
var express = require('express');

var app = express();

apiConfig.setupMiddleware(app);

app.use(function(req, res, next) {
    if(req.eve.solarsystem.id){
        console.log('Tracking... ' + JSON.stringify(req.eve.solarsystem.id))
        var pilotId = req.session.passport.user;
        service.updateTracker(pilotId, req.eve);
    }else{
        console.log('No headers :(')
    }
    next();
})

/* GET get self*/
app.get('/me', function(req, res, next) {
    var pilotId = req.session.passport.user;
    service.findPilot(pilotId)
        .then(function (pilot) {
            pilot.eve = req.eve;    //add request headers
            res.json(pilot);
        })
        .catch(function (e) {
            next(e);
        })
});

app.post('/intel', function(req, res, next) {
    var systemId   = req.eve.solarsystem.id;
    var timestamp  = req.query.timestamp;
    var state      = req.query.state;
    var pilotId = req.session.passport.user;
    service.createIntelReport(pilotId, systemId, timestamp, state)
        .then(function () {
            res.status(200);
        })
        .catch(function (e) {
            next(e);
        })
});

app.get('/intel', function(req, res, next) {
    var timestamp   = req.query.last_update;
    var pilotId = req.session.passport.user;
    service.getIntel(pilotId,timestamp)
        .then(function (intel) {
            res.json(intel);
        })
        .catch(function (e) {
            next(e);
        })
});

module.exports = app;
<|endoftext|>