Remove 'self = this' and use coffeescript '@'

--- client.coffee
+++ client.coffee
@@ -5,41 +5,40 @@
 ###

 DHT = (opts) ->
-  self = this
-  if !(self instanceof DHT)
+  if !(@ instanceof DHT)
     return new DHT(opts)
-  EventEmitter.call self
+  EventEmitter.call @
   if !debug.enabled
-    self.setMaxListeners 0
+    @setMaxListeners 0
   if !opts
     opts = {}
-  self.nodeId = idToBuffer(opts.nodeId or hat(160))
-  self.ipv = opts.ipv or 4
-  self._debug 'new DHT %s', idToHexString(self.nodeId)
-  self.ready = false
-  self.listening = false
-  self._binding = false
-  self._destroyed = false
-  self._port = null
+  @nodeId = idToBuffer(opts.nodeId or hat(160))
+  @ipv = opts.ipv or 4
+  @_debug 'new DHT %s', idToHexString(@nodeId)
+  @ready = false
+  @listening = false
+  @_binding = false
+  @_destroyed = false
+  @_port = null

   ###*
   # Query Handlers table
   # @type {Object} string -> function
   ###

-  self.queryHandler =
-    ping: self._onPing
-    find_node: self._onFindNode
-    get_peers: self._onGetPeers
-    announce_peer: self._onAnnouncePeer
+  @queryHandler =
+    ping: @_onPing
+    find_node: @_onFindNode
+    get_peers: @_onGetPeers
+    announce_peer: @_onAnnouncePeer

   ###*
   # Routing table
   # @type {KBucket}
   ###

-  self.nodes = new KBucket(
-    localNodeId: self.nodeId
+  @nodes = new KBucket(
+    localNodeId: @nodeId
     numberOfNodesPerKBucket: K
     numberOfNodesToPing: MAX_CONCURRENCY)

@@ -50,46 +49,46 @@
   # @type {Object} infoHash:string -> KBucket
   ###

-  self.tables = {}
+  @tables = {}

   ###*
   # Pending transactions (unresolved requests to peers)
   # @type {Object} addr:string -> array of pending transactions
   ###

-  self.transactions = {}
+  @transactions = {}

   ###*
   # Peer address data (tracker storage)
   # @type {Object} infoHash:string -> Object {index:Object, list:Array.<Buffer>}
   ###

-  self.peers = {}
+  @peers = {}
   # Create socket and attach listeners
-  self.socket = module.exports.dgram.createSocket('udp' + self.ipv)
-  self.socket.on 'message', self._onData.bind(self)
-  self.socket.on 'listening', self._onListening.bind(self)
-  self.socket.on 'error', ->
+  @socket = module.exports.dgram.createSocket('udp' + @ipv)
+  @socket.on 'message', @_onData.bind(@)
+  @socket.on 'listening', @_onListening.bind(@)
+  @socket.on 'error', ->
   # throw away errors
-  self._rotateSecrets()
-  self._rotateInterval = setInterval(self._rotateSecrets.bind(self), ROTATE_INTERVAL)
-  self._rotateInterval.unref and self._rotateInterval.unref()
-  process.nextTick ->
+  @_rotateSecrets()
+  @_rotateInterval = setInterval(@_rotateSecrets.bind(@), ROTATE_INTERVAL)
+  @_rotateInterval.unref and @_rotateInterval.unref()
+  process.nextTick =>
     if opts.bootstrap == false
       # Emit `ready` right away because the user does not want to bootstrap. Presumably,
       # the user will call addNode() to populate the routing table manually.
-      self.ready = true
-      self.emit 'ready'
+      @ready = true
+      @emit 'ready'
     else if typeof opts.bootstrap == 'string'
-      self._bootstrap [ opts.bootstrap ]
+      @_bootstrap [ opts.bootstrap ]
     else if Array.isArray(opts.bootstrap)
-      self._bootstrap fromArray(opts.bootstrap)
+      @_bootstrap fromArray(opts.bootstrap)
     else
       # opts.bootstrap is undefined or true
-      self._bootstrap BOOTSTRAP_NODES
-    return
-  self.on 'ready', ->
-    self._debug 'emit ready'
+      @_bootstrap BOOTSTRAP_NODES
+    return
+  @on 'ready', ->
+    @_debug 'emit ready'
     return
   return

@@ -263,7 +262,6 @@
 ###

 DHT::listen = (port, address, onlistening) ->
-  self = this
   if typeof port == 'string'
     onlistening = address
     address = port
@@ -276,12 +274,12 @@
     onlistening = address
     address = undefined
   if onlistening
-    self.once 'listening', onlistening
-  if self._destroyed or self._binding or self.listening
-    return
-  self._binding = true
-  self._debug 'listen %s', port
-  self.socket.bind port, address
+    @once 'listening', onlistening
+  if @_destroyed or @_binding or @listening
+    return
+  @_binding = true
+  @_debug 'listen %s', port
+  @socket.bind port, address
   return

 ###*
@@ -289,17 +287,15 @@
 ###

 DHT::_onListening = ->
-  self = this
-  self._binding = false
-  self.listening = true
-  self._port = self.socket.address().port
-  self._debug 'emit listening %s', self._port
-  self.emit 'listening'
+  @_binding = false
+  @listening = true
+  @_port = @socket.address().port
+  @_debug 'emit listening %s', @_port
+  @emit 'listening'
   return

 DHT::address = ->
-  self = this
-  self.socket.address()
+  @socket.address()

 ###*
 # Announce that the peer, controlling the querying node, is downloading a torrent on a
@@ -310,15 +306,14 @@
 ###

 DHT::announce = (infoHash, port, cb) ->
-  self = this
-
-  onClosest = (err, closest) ->
+
+  onClosest = (err, closest) =>
     if err
       return cb(err)
-    closest.forEach (contact) ->
-      self._sendAnnouncePeer contact.addr, infoHash, port, contact.token
+    closest.forEach (contact) =>
+      @_sendAnnouncePeer contact.addr, infoHash, port, contact.token
       return
-    self._debug 'announce end %s %s', infoHash, port
+    @_debug 'announce end %s %s', infoHash, port
     cb null
     return

@@ -326,16 +321,16 @@

     cb = ->

-  if self._destroyed
+  if @_destroyed
     return cb(new Error('dht is destroyed'))
-  self._debug 'announce %s %s', infoHash, port
+  @_debug 'announce %s %s', infoHash, port
   infoHashHex = idToHexString(infoHash)
   # TODO: it would be nice to not use a table when a lookup is in progress
-  table = self.tables[infoHashHex]
+  table = @tables[infoHashHex]
   if table
     onClosest null, table.closest({ id: infoHash }, K)
   else
-    self.lookup infoHash, onClosest
+    @lookup infoHash, onClosest
   return

 ###*
@@ -344,29 +339,28 @@
 ###

 DHT::destroy = (cb) ->
-  self = this
   if !cb

     cb = ->

   cb = once(cb)
-  if self._destroyed
+  if @_destroyed
     return cb(new Error('dht is destroyed'))
-  if self._binding
-    return self.once('listening', self.destroy.bind(self, cb))
-  self._debug 'destroy'
-  self._destroyed = true
-  self.listening = false
+  if @_binding
+    return @once('listening', @destroy.bind(@, cb))
+  @_debug 'destroy'
+  @_destroyed = true
+  @listening = false
   # garbage collect large data structures
-  self.nodes = null
-  self.tables = null
-  self.transactions = null
-  self.peers = null
-  clearTimeout self._bootstrapTimeout
-  clearInterval self._rotateInterval
-  self.socket.on 'close', cb
+  @nodes = null
+  @tables = null
+  @transactions = null
+  @peers = null
+  clearTimeout @_bootstrapTimeout
+  clearInterval @_rotateInterval
+  @socket.on 'close', cb
   try
-    self.socket.close()
+    @socket.close()
   catch err
     # ignore error, socket was either already closed / not yet bound
     cb null
@@ -380,20 +374,19 @@
 ###

 DHT::addNode = (addr, nodeId, from) ->
-  self = this
-  if self._destroyed
+  if @_destroyed
     return
   nodeId = idToBuffer(nodeId)
-  if self._addrIsSelf(addr)
-    # self._debug('skipping adding %s since that is us!', addr)
+  if @_addrIsSelf(addr)
+    # @_debug('skipping adding %s since that is us!', addr)
     return
   contact =
     id: nodeId
     addr: addr
-  self.nodes.add contact
+  @nodes.add contact
   # TODO: only emit this event for new nodes
-  self.emit 'node', addr, nodeId, from
-  self._debug 'addNode %s %s discovered from %s', idToHexString(nodeId), addr, from
+  @emit 'node', addr, nodeId, from
+  @_debug 'addNode %s %s discovered from %s', idToHexString(nodeId), addr, from
   return

 ###*
@@ -402,13 +395,12 @@
 ###

 DHT::removeNode = (nodeId) ->
-  self = this
-  if self._destroyed
-    return
-  contact = self.nodes.get(idToBuffer(nodeId))
+  if @_destroyed
+    return
+  contact = @nodes.get(idToBuffer(nodeId))
   if contact
-    self._debug 'removeNode %s %s', contact.nodeId, contact.addr
-    self.nodes.remove contact
+    @_debug 'removeNode %s %s', contact.nodeId, contact.addr
+    @nodes.remove contact
   return

 ###*
@@ -418,20 +410,19 @@
 ###

 DHT::_addPeer = (addr, infoHash) ->
-  self = this
-  if self._destroyed
+  if @_destroyed
     return
   infoHash = idToHexString(infoHash)
-  peers = self.peers[infoHash]
+  peers = @peers[infoHash]
   if !peers
-    peers = self.peers[infoHash] =
+    peers = @peers[infoHash] =
       index: {}
       list: []
   if !peers.index[addr]
     peers.index[addr] = true
     peers.list.push string2compact(addr)
-    self._debug 'addPeer %s %s', addr, infoHash
-    self.emit 'announce', addr, infoHash
+    @_debug 'addPeer %s %s', addr, infoHash
+    @emit 'announce', addr, infoHash
   return

 ###*
@@ -441,18 +432,17 @@
 ###

 DHT::removePeer = (addr, infoHash) ->
-  self = this
-  if self._destroyed
+  if @_destroyed
     return
   infoHash = idToHexString(infoHash)
-  peers = self.peers[infoHash]
+  peers = @peers[infoHash]
   if peers and peers.index[addr]
     peers.index[addr] = null
     compactPeerInfo = string2compact(addr)
     peers.list.some (peer, index) ->
       if bufferEqual(peer, compactPeerInfo)
         peers.list.splice index, 1
-        self._debug 'removePeer %s %s', addr, infoHash
+        @_debug 'removePeer %s %s', addr, infoHash
         return true
         # abort early
       return
@@ -465,39 +455,38 @@
 ###

 DHT::_bootstrap = (nodes) ->
-  self = this
-  self._debug 'bootstrap with %s', JSON.stringify(nodes)
+  @_debug 'bootstrap with %s', JSON.stringify(nodes)
   contacts = nodes.map((obj) ->
     if typeof obj == 'string'
       { addr: obj }
     else
       obj
   )
-  self._resolveContacts contacts, (err, contacts) ->
-
-    lookup = ->
-      self.lookup self.nodeId, {
+  @_resolveContacts contacts, (err, contacts)=>
+
+    lookup = =>
+      @lookup @nodeId, {
         findNode: true
         addrs: if addrs.length then addrs else null
-      }, (err) ->
+      }, (err) =>
         if err
-          self._debug 'lookup error %s during bootstrap', err.message
+          @_debug 'lookup error %s during bootstrap', err.message
         # emit `ready` once the recursive lookup for our own node ID is finished
         # (successful or not), so that later get_peer lookups will have a good shot at
         # succeeding.
-        if !self.ready
-          self.ready = true
-          self.emit 'ready'
+        if !@ready
+          @ready = true
+          @emit 'ready'
         return
       return

     if err
-      return self.emit('error', err)
+      return @emit('error', err)
     # add all non-bootstrap nodes to routing table
     contacts.filter((contact) ->
       ! !contact.id
-    ).forEach (contact) ->
-      self.addNode contact.addr, contact.id, contact.from
+    ).forEach (contact) =>
+      @addNode contact.addr, contact.id, contact.from
       return
     # get addresses of bootstrap nodes
     addrs = contacts.filter((contact) ->
@@ -507,16 +496,16 @@
     )
     lookup()
     # TODO: keep retrying after one failure
-    self._bootstrapTimeout = setTimeout((->
-      if self._destroyed
+    @_bootstrapTimeout = setTimeout((=>
+      if @_destroyed
         return
       # If 0 nodes are in the table after a timeout, retry with bootstrap nodes
-      if self.nodes.count() == 0
-        self._debug 'No DHT bootstrap nodes replied, retry'
+      if @nodes.count() == 0
+        @_debug 'No DHT bootstrap nodes replied, retry'
         lookup()
       return
     ), BOOTSTRAP_TIMEOUT)
-    self._bootstrapTimeout.unref and self._bootstrapTimeout.unref()
+    @_bootstrapTimeout.unref and @_bootstrapTimeout.unref()
     return
   return

@@ -528,14 +517,13 @@
 ###

 DHT::_resolveContacts = (contacts, done) ->
-  self = this
   tasks = contacts.map((contact) ->
     (cb) ->
       addrData = addrToIPPort(contact.addr)
       if isIP(addrData[0])
         cb null, contact
       else
-        dns.lookup addrData[0], self.ipv, (err, host) ->
+        dns.lookup addrData[0], @ipv, (err, host) ->
           if err
             return cb(null, null)
           contact.addr = host + ':' + addrData[1]
@@ -565,27 +553,26 @@
 ###

 DHT::lookup = (id, opts, cb) ->
-  self = this
-
-  add = (contact) ->
-    if self._addrIsSelf(contact.addr)
+
+  add = (contact) =>
+    if @_addrIsSelf(contact.addr)
       return
     if contact.token
       tokenful.add contact
     table.add contact
     return

-  query = (addr) ->
+  query = (addr) =>
     pending += 1
     queried[addr] = true
     if opts.findNode
-      self._sendFindNode addr, id, onResponse.bind(null, addr)
+      @_sendFindNode addr, id, onResponse.bind(null, addr)
     else
-      self._sendGetPeers addr, id, onResponse.bind(null, addr)
-    return
-
-  queryClosest = ->
-    self.nodes.closest({ id: id }, K).forEach (contact) ->
+      @_sendGetPeers addr, id, onResponse.bind(null, addr)
+    return
+
+  queryClosest = =>
+    @nodes.closest({ id: id }, K).forEach (contact) ->
       query contact.addr
       return
     return
@@ -593,17 +580,17 @@
   # Note: `_sendFindNode` and `_sendGetPeers` will insert newly discovered nodes into
   # the routing table, so that's not done here.

-  onResponse = (addr, err, res) ->
-    if self._destroyed
+  onResponse = (addr, err, res) =>
+    if @_destroyed
       return cb(new Error('dht is destroyed'))
     pending -= 1
     nodeId = res and res.id
     nodeIdHex = idToHexString(nodeId)
     # ignore errors - they are just timeouts
     if err
-      self._debug 'got lookup error: %s', err.message
+      @_debug 'got lookup error: %s', err.message
     else
-      self._debug 'got lookup response from %s', nodeIdHex
+      @_debug 'got lookup response from %s', nodeIdHex
       # add node that sent this response
       contact = table.get(nodeId) or
         id: nodeId
@@ -624,11 +611,11 @@
       query candidates.pop().addr
     if pending == 0 and candidates.length == 0
       # recursive lookup should terminate because there are no closer nodes to find
-      self._debug 'terminating lookup %s %s', (if opts.findNode then '(find_node)' else '(get_peers)'), idHex
+      @_debug 'terminating lookup %s %s', (if opts.findNode then '(find_node)' else '(get_peers)'), idHex
       closest = (if opts.findNode then table else tokenful).closest({ id: id }, K)
-      self._debug 'K closest nodes are:'
-      closest.forEach (contact) ->
-        self._debug '  ' + contact.addr + ' ' + idToHexString(contact.id)
+      @_debug 'K closest nodes are:'
+      closest.forEach (contact) =>
+        @_debug '  ' + contact.addr + ' ' + idToHexString(contact.id)
         return
       cb null, closest
     return
@@ -644,31 +631,31 @@
     cb = ->

   cb = once(cb)
-  if self._destroyed
+  if @_destroyed
     return cb(new Error('dht is destroyed'))
-  if !self.listening
-    return self.listen(self.lookup.bind(self, id, opts, cb))
+  if !@listening
+    return @listen(@lookup.bind(@, id, opts, cb))
   idHex = idToHexString(id)
-  self._debug 'lookup %s %s', (if opts.findNode then '(find_node)' else '(get_peers)'), idHex
+  @_debug 'lookup %s %s', (if opts.findNode then '(find_node)' else '(get_peers)'), idHex
   # Return local peers, if we have any in our table
-  peers = self.peers[idHex] and self.peers[idHex]
+  peers = @peers[idHex] and @peers[idHex]
   if peers
     peers = parsePeerInfo(peers.list)
-    peers.forEach (peerAddr) ->
-      self._debug 'emit peer %s %s from %s', peerAddr, idHex, 'local'
-      self.emit 'peer', peerAddr, idHex, 'local'
+    peers.forEach (peerAddr) =>
+      @_debug 'emit peer %s %s from %s', peerAddr, idHex, 'local'
+      @emit 'peer', peerAddr, idHex, 'local'
       return
   table = new KBucket(
     localNodeId: id
     numberOfNodesPerKBucket: K
     numberOfNodesToPing: MAX_CONCURRENCY)
   # NOT the same table as the one used for the lookup, as that table may have nodes without tokens
-  if !self.tables[idHex]
-    self.tables[idHex] = new KBucket(
+  if !@tables[idHex]
+    @tables[idHex] = new KBucket(
       localNodeId: id
       numberOfNodesPerKBucket: K
       numberOfNodesToPing: MAX_CONCURRENCY)
-  tokenful = self.tables[idHex]
+  tokenful = @tables[idHex]
   queried = {}
   pending = 0
   # pending queries
@@ -687,7 +674,6 @@
 ###

 DHT::_onData = (data, rinfo) ->
-  self = this
   addr = rinfo.address + ':' + rinfo.port
   message = undefined
   errMessage = undefined
@@ -697,27 +683,27 @@
       throw new Error('message is empty')
   catch err
     errMessage = err.message + ' from ' + addr + ' (' + data + ')'
-    self._debug errMessage
-    self.emit 'warning', new Error(errMessage)
+    @_debug errMessage
+    @emit 'warning', new Error(errMessage)
     return
   type = message.y and message.y.toString()
   if type != MESSAGE_TYPE.QUERY and type != MESSAGE_TYPE.RESPONSE and type != MESSAGE_TYPE.ERROR
     errMessage = 'unknown message type ' + type + ' from ' + addr
-    self._debug errMessage
-    self.emit 'warning', new Error(errMessage)
-    return
-  # self._debug('got data %s from %s', JSON.stringify(message), addr)
+    @_debug errMessage
+    @emit 'warning', new Error(errMessage)
+    return
+  # @_debug('got data %s from %s', JSON.stringify(message), addr)
   # Attempt to add every (valid) node that we see to the routing table.
   # TODO: If they node is already in the table, just update the "last heard from" time
   nodeId = message.r and message.r.id or message.a and message.a.id
   if nodeId
     # TODO: verify that this a valid length for a nodeId
-    # self._debug('adding (potentially) new node %s %s', idToHexString(nodeId), addr)
-    self.addNode addr, nodeId, addr
+    # @_debug('adding (potentially) new node %s %s', idToHexString(nodeId), addr)
+    @addNode addr, nodeId, addr
   if type == MESSAGE_TYPE.QUERY
-    self._onQuery addr, message
+    @_onQuery addr, message
   else if type == MESSAGE_TYPE.RESPONSE or type == MESSAGE_TYPE.ERROR
-    self._onResponseOrError addr, type, message
+    @_onResponseOrError addr, type, message
   return

 ###*
@@ -727,14 +713,13 @@
 ###

 DHT::_onQuery = (addr, message) ->
-  self = this
   query = message.q.toString()
-  if typeof self.queryHandler[query] == 'function'
-    self.queryHandler[query].call self, addr, message
+  if typeof @queryHandler[query] == 'function'
+    @queryHandler[query].call @, addr, message
   else
     errMessage = 'unexpected query type'
-    self._debug errMessage
-    self._sendError addr, message.t, ERROR_TYPE.METHOD_UNKNOWN, errMessage
+    @_debug errMessage
+    @_sendError addr, message.t, ERROR_TYPE.METHOD_UNKNOWN, errMessage
   return

 ###*
@@ -745,9 +730,8 @@
 ###

 DHT::_onResponseOrError = (addr, type, message) ->
-  self = this
   transactionId = Buffer.isBuffer(message.t) and message.t.length == 2 and message.t.readUInt16BE(0)
-  transaction = self.transactions and self.transactions[addr] and self.transactions[addr][transactionId]
+  transaction = @transactions and @transactions[addr] and @transactions[addr][transactionId]
   err = null
   if type == MESSAGE_TYPE.ERROR
     err = new Error(if Array.isArray(message.e) then message.e.join(' ') else undefined)
@@ -755,11 +739,11 @@
     # unexpected message!
     if err
       errMessage = 'got unexpected error from ' + addr + ' ' + err.message
-      self._debug errMessage
-      self.emit 'warning', new Error(errMessage)
+      @_debug errMessage
+      @emit 'warning', new Error(errMessage)
     else
-      self._debug 'got unexpected message from ' + addr + ' ' + JSON.stringify(message)
-      self.emit 'warning', new Error(errMessage)
+      @_debug 'got unexpected message from ' + addr + ' ' + JSON.stringify(message)
+      @emit 'warning', new Error(errMessage)
     return
   transaction.cb err, message.r
   return
@@ -772,9 +756,8 @@
 ###

 DHT::_send = (addr, message, cb) ->
-  self = this
-  if !self.listening
-    return self.listen(self._send.bind(self, addr, message, cb))
+  if !@listening
+    return @listen(@_send.bind(@, addr, message, cb))
   if !cb

     cb = ->
@@ -784,28 +767,27 @@
   port = addrData[1]
   if !(port > 0 and port < 65535)
     return
-  # self._debug('send %s to %s', JSON.stringify(message), addr)
+  # @_debug('send %s to %s', JSON.stringify(message), addr)
   message = bencode.encode(message)
-  self.socket.send message, 0, message.length, port, host, cb
+  @socket.send message, 0, message.length, port, host, cb
   return

 DHT::_query = (data, addr, cb) ->
-  self = this
   if !data.a
     data.a = {}
   if !data.a.id
-    data.a.id = self.nodeId
-  transactionId = self._getTransactionId(addr, cb)
+    data.a.id = @nodeId
+  transactionId = @_getTransactionId(addr, cb)
   message =
     t: transactionIdToBuffer(transactionId)
     y: MESSAGE_TYPE.QUERY
     q: data.q
     a: data.a
   if data.q == 'find_node'
-    self._debug 'sent find_node %s to %s', data.a.target.toString('hex'), addr
+    @_debug 'sent find_node %s to %s', data.a.target.toString('hex'), addr
   else if data.q == 'get_peers'
-    self._debug 'sent get_peers %s to %s', data.a.info_hash.toString('hex'), addr
-  self._send addr, message
+    @_debug 'sent get_peers %s to %s', data.a.info_hash.toString('hex'), addr
+  @_send addr, message
   return

 ###*
@@ -815,8 +797,7 @@
 ###

 DHT::_sendPing = (addr, cb) ->
-  self = this
-  self._query { q: 'ping' }, addr, cb
+  @_query { q: 'ping' }, addr, cb
   return

 ###*
@@ -826,13 +807,12 @@
 ###

 DHT::_onPing = (addr, message) ->
-  self = this
   res =
     t: message.t
     y: MESSAGE_TYPE.RESPONSE
-    r: id: self.nodeId
-  self._debug 'got ping from %s', addr
-  self._send addr, res
+    r: id: @nodeId
+  @_debug 'got ping from %s', addr
+  @_send addr, res
   return

 ###*
@@ -843,25 +823,24 @@
 ###

 DHT::_sendFindNode = (addr, nodeId, cb) ->
-  self = this
   data =
     q: 'find_node'
     a:
-      id: self.nodeId
+      id: @nodeId
       target: nodeId

-  onResponse = (err, res) ->
+  onResponse = (err, res) =>
     if err
       return cb(err)
     if res.nodes
       res.nodes = parseNodeInfo(res.nodes)
-      res.nodes.forEach (node) ->
-        self.addNode node.addr, node.id, addr
+      res.nodes.forEach (node) =>
+        @addNode node.addr, node.id, addr
         return
     cb null, res
     return

-  self._query data, addr, onResponse
+  @_query data, addr, onResponse
   return

 ###*
@@ -871,23 +850,22 @@
 ###

 DHT::_onFindNode = (addr, message) ->
-  self = this
   nodeId = message.a and message.a.target
   if !nodeId
     errMessage = '`find_node` missing required `a.target` field'
-    self._debug errMessage
-    self._sendError addr, message.t, ERROR_TYPE.PROTOCOL, errMessage
-    return
-  self._debug 'got find_node %s from %s', idToHexString(nodeId), addr
+    @_debug errMessage
+    @_sendError addr, message.t, ERROR_TYPE.PROTOCOL, errMessage
+    return
+  @_debug 'got find_node %s from %s', idToHexString(nodeId), addr
   # Convert nodes to "compact node info" representation
-  nodes = convertToNodeInfo(self.nodes.closest({ id: nodeId }, K))
+  nodes = convertToNodeInfo(@nodes.closest({ id: nodeId }, K))
   res =
     t: message.t
     y: MESSAGE_TYPE.RESPONSE
     r:
-      id: self.nodeId
+      id: @nodeId
       nodes: nodes
-  self._send addr, res
+  @_send addr, res
   return

 ###*
@@ -898,21 +876,20 @@
 ###

 DHT::_sendGetPeers = (addr, infoHash, cb) ->
-  self = this
-
-  onResponse = (err, res) ->
+
+  onResponse = (err, res) =>
     if err
       return cb(err)
     if res.nodes
       res.nodes = parseNodeInfo(res.nodes)
-      res.nodes.forEach (node) ->
-        self.addNode node.addr, node.id, addr
+      res.nodes.forEach (node) =>
+        @addNode node.addr, node.id, addr
         return
     if res.values
       res.values = parsePeerInfo(res.values)
-      res.values.forEach (peerAddr) ->
-        self._debug 'emit peer %s %s from %s', peerAddr, infoHashHex, addr
-        self.emit 'peer', peerAddr, infoHashHex, addr
+      res.values.forEach (peerAddr) =>
+        @_debug 'emit peer %s %s from %s', peerAddr, infoHashHex, addr
+        @emit 'peer', peerAddr, infoHashHex, addr
         return
     cb null, res
     return
@@ -922,9 +899,9 @@
   data =
     q: 'get_peers'
     a:
-      id: self.nodeId
+      id: @nodeId
       info_hash: infoHash
-  self._query data, addr, onResponse
+  @_query data, addr, onResponse
   return

 ###*
@@ -934,23 +911,22 @@
 ###

 DHT::_onGetPeers = (addr, message) ->
-  self = this
   addrData = addrToIPPort(addr)
   infoHash = message.a and message.a.info_hash
   if !infoHash
     errMessage = '`get_peers` missing required `a.info_hash` field'
-    self._debug errMessage
-    self._sendError addr, message.t, ERROR_TYPE.PROTOCOL, errMessage
+    @_debug errMessage
+    @_sendError addr, message.t, ERROR_TYPE.PROTOCOL, errMessage
     return
   infoHashHex = idToHexString(infoHash)
-  self._debug 'got get_peers %s from %s', infoHashHex, addr
+  @_debug 'got get_peers %s from %s', infoHashHex, addr
   res =
     t: message.t
     y: MESSAGE_TYPE.RESPONSE
     r:
-      id: self.nodeId
-      token: self._generateToken(addrData[0])
-  peers = self.peers[infoHashHex] and self.peers[infoHashHex].list
+      id: @nodeId
+      token: @_generateToken(addrData[0])
+  peers = @peers[infoHashHex] and @peers[infoHashHex].list
   if peers
     # We know of peers for the target info hash. Peers are stored as an array of
     # compact peer info, so return it as-is.
@@ -958,8 +934,8 @@
   else
     # No peers, so return the K closest nodes instead. Convert nodes to "compact node
     # info" representation
-    res.r.nodes = convertToNodeInfo(self.nodes.closest({ id: infoHash }, K))
-  self._send addr, res
+    res.r.nodes = convertToNodeInfo(@nodes.closest({ id: infoHash }, K))
+  @_send addr, res
   return

 ###*
@@ -972,7 +948,6 @@
 ###

 DHT::_sendAnnouncePeer = (addr, infoHash, port, token, cb) ->
-  self = this
   infoHash = idToBuffer(infoHash)
   if !cb

@@ -981,12 +956,12 @@
   data =
     q: 'announce_peer'
     a:
-      id: self.nodeId
+      id: @nodeId
       info_hash: infoHash
       port: port
       token: token
       implied_port: 0
-  self._query data, addr, cb
+  @_query data, addr, cb
   return

 ###*
@@ -996,30 +971,29 @@
 ###

 DHT::_onAnnouncePeer = (addr, message) ->
-  self = this
   errMessage = undefined
   addrData = addrToIPPort(addr)
   infoHash = idToHexString(message.a and message.a.info_hash)
   if !infoHash
     errMessage = '`announce_peer` missing required `a.info_hash` field'
-    self._debug errMessage
-    self._sendError addr, message.t, ERROR_TYPE.PROTOCOL, errMessage
+    @_debug errMessage
+    @_sendError addr, message.t, ERROR_TYPE.PROTOCOL, errMessage
     return
   token = message.a and message.a.token
-  if !self._isValidToken(token, addrData[0])
+  if !@_isValidToken(token, addrData[0])
     errMessage = 'cannot `announce_peer` with bad token'
-    self._sendError addr, message.t, ERROR_TYPE.PROTOCOL, errMessage
+    @_sendError addr, message.t, ERROR_TYPE.PROTOCOL, errMessage
     return
   port = if message.a.implied_port != 0 then addrData[1] else message.a.port
   # use port in `announce_peer` message
-  self._debug 'got announce_peer %s %s from %s with token %s', idToHexString(infoHash), port, addr, idToHexString(token)
-  self._addPeer addrData[0] + ':' + port, infoHash
+  @_debug 'got announce_peer %s %s from %s with token %s', idToHexString(infoHash), port, addr, idToHexString(token)
+  @_addPeer addrData[0] + ':' + port, infoHash
   # send acknowledgement
   res =
     t: message.t
     y: MESSAGE_TYPE.RESPONSE
-    r: id: self.nodeId
-  self._send addr, res
+    r: id: @nodeId
+  @_send addr, res
   return

 ###*
@@ -1031,7 +1005,6 @@
 ###

 DHT::_sendError = (addr, transactionId, code, errMessage) ->
-  self = this
   if transactionId and !Buffer.isBuffer(transactionId)
     transactionId = transactionIdToBuffer(transactionId)
   message =
@@ -1042,8 +1015,8 @@
     ]
   if transactionId
     message.t = transactionId
-  self._debug 'sent error %s to %s', JSON.stringify(message), addr
-  self._send addr, message
+  @_debug 'sent error %s to %s', JSON.stringify(message), addr
+  @_send addr, message
   return

 ###*
@@ -1053,7 +1026,6 @@
 ###

 DHT::_getTransactionId = (addr, fn) ->
-  self = this

   onTimeout = ->
     reqs[transactionId] = null
@@ -1067,9 +1039,9 @@
     return

   fn = once(fn)
-  reqs = self.transactions[addr]
+  reqs = @transactions[addr]
   if !reqs
-    reqs = self.transactions[addr] = {}
+    reqs = @transactions[addr] = {}
     reqs.nextTransactionId = 0
   transactionId = reqs.nextTransactionId
   reqs.nextTransactionId += 1
@@ -1088,9 +1060,8 @@
 ###

 DHT::_generateToken = (host, secret) ->
-  self = this
   if !secret
-    secret = self.secrets[0]
+    secret = @secrets[0]
   sha1 Buffer.concat([
     new Buffer(host, 'utf8')
     secret
@@ -1105,9 +1076,8 @@
 ###

 DHT::_isValidToken = (token, host) ->
-  self = this
-  validToken0 = self._generateToken(host, self.secrets[0])
-  validToken1 = self._generateToken(host, self.secrets[1])
+  validToken0 = @_generateToken(host, @secrets[0])
+  validToken1 = @_generateToken(host, @secrets[1])
   bufferEqual(token, validToken0) or bufferEqual(token, validToken1)

 ###*
@@ -1116,22 +1086,21 @@
 ###

 DHT::_rotateSecrets = ->
-  self = this
   # Initialize secrets array
-  # self.secrets[0] is the current secret, used to generate new tokens
-  # self.secrets[1] is the last secret, which is still accepted
+  # @secrets[0] is the current secret, used to generate new tokens
+  # @secrets[1] is the last secret, which is still accepted

   createSecret = ->
     new Buffer(hat(SECRET_ENTROPY), 'hex')

-  if !self.secrets
-    self.secrets = [
+  if !@secrets
+    @secrets = [
       createSecret()
       createSecret()
     ]
     return
-  self.secrets[1] = self.secrets[0]
-  self.secrets[0] = createSecret()
+  @secrets[1] = @secrets[0]
+  @secrets[0] = createSecret()
   return

 ###*
@@ -1141,8 +1110,7 @@
 ###

 DHT::toArray = ->
-  self = this
-  nodes = self.nodes.toArray().map((contact) ->
+  nodes = @nodes.toArray().map((contact) ->
     # to remove properties added by k-bucket, like `distance`, etc.
     {
       id: contact.id.toString('hex')
@@ -1152,14 +1120,12 @@
   nodes

 DHT::_addrIsSelf = (addr) ->
-  self = this
-  self._port and LOCAL_HOSTS[self.ipv].some((host) ->
-    host + ':' + self._port == addr
+  @_port and LOCAL_HOSTS[@ipv].some((host) ->
+    host + ':' + @_port == addr
   )

 DHT::_debug = ->
-  self = this
   args = [].slice.call(arguments)
-  args[0] = '[' + idToHexString(self.nodeId).substring(0, 7) + '] ' + args[0]
+  args[0] = '[' + idToHexString(@nodeId).substring(0, 7) + '] ' + args[0]
   debug.apply null, args
   return


Split into 2 lines

Split into 2 lines

--- app/scripts/components/player/PlayerSpaceship.coffee
+++ app/scripts/components/player/PlayerSpaceship.coffee
@@ -1,6 +1,7 @@
 Crafty.c 'PlayerSpaceship',
   init: ->
-    @requires '2D, WebGL, playerShip, ColorEffects, Listener, Collision, SunBlock, WaterSplashes, PlayerControlledShip, Acceleration, InventoryWeapons'
+    @requires '2D, WebGL, playerShip, ColorEffects, Listener, Collision, SunBlock, ' +
+      'WaterSplashes, PlayerControlledShip, Acceleration, InventoryWeapons'
     @attr w: 71, h: 45
     @collision [
       21, 13


:fire: Remove deprecations from PaneContainer

--- src/pane-container.coffee
+++ src/pane-container.coffee
@@ -1,5 +1,4 @@
 {find, flatten} = require 'underscore-plus'
-Grim = require 'grim'
 {Emitter, CompositeDisposable} = require 'event-kit'
 Serializable = require 'serializable'
 Gutter = require './gutter'
@@ -19,8 +18,7 @@
   constructor: (params) ->
     super

-    unless Grim.includeDeprecatedAPIs
-      @activePane = params?.activePane
+    @activePane = params?.activePane

     @emitter = new Emitter
     @subscriptions = new CompositeDisposable
@@ -222,12 +220,3 @@

   removedPaneItem: (item) ->
     @itemRegistry.removeItem(item)
-
-if Grim.includeDeprecatedAPIs
-  PaneContainer.properties
-    activePane: null
-
-  PaneContainer.behavior 'activePaneItem', ->
-    @$activePane
-      .switch((activePane) -> activePane?.$activeItem)
-      .distinctUntilChanged()


OX-120 hotfixed

OX-120 hotfixed

--- app/assets/javascripts/model/binary/cube.coffee
+++ app/assets/javascripts/model/binary/cube.coffee
@@ -77,7 +77,7 @@

     { cube } = @

-    bucketIndex = @bucketIndexByAddress3(bucket_x, bucket_y, bucket_z, "getRequestedZoomStepOfBucketByAddress3")
+    bucketIndex = @bucketIndexByAddress3(bucket_x, bucket_y, bucket_z)

     if cube[bucketIndex]
       cube[bucketIndex].requestedZoomStep
@@ -98,7 +98,7 @@

     { cube } = @

-    bucketIndex = @bucketIndexByAddress3(bucket_x, bucket_y, bucket_z, "getZoomStepOfBucketByAddress3")
+    bucketIndex = @bucketIndexByAddress3(bucket_x, bucket_y, bucket_z)

     if bucketIndex? and cube[bucketIndex]
       cube[bucketIndex].zoomStep
@@ -118,7 +118,7 @@

     { cube } = @

-    bucketIndex = @bucketIndexByAddress3(bucket_x, bucket_y, bucket_z, "getBucketByAddress3")
+    bucketIndex = @bucketIndexByAddress3(bucket_x, bucket_y, bucket_z)

     if bucketIndex?
       cube[bucketIndex]
@@ -150,35 +150,31 @@
         for dy in [0...width] by 1
           for dz in [0...width] by 1

-            bucketIndex = @bucketIndexByAddress3(x + dx, y + dy, z + dz, "setBucketByZoomedAddress")
-
-            if bucketIndex == undefined
-              console.log "ERROR"
-            bucket = cube[bucketIndex]
-
-            if bucketData
-              if zoomStep < bucket.zoomStep
-                bucket.data = bucketData
-                @trigger("bucketLoaded", [x + dx, y + dy, z + dz], zoomStep, bucket.zoomStep)
-                bucket.zoomStep = zoomStep
-            else
-              bucket.requestedZoomStep = bucket.zoomStep
-
-    else
-      bucketIndex = @bucketIndexByAddress3(bucket_x, bucket_y, bucket_z, "setBucketByZoomedAddress")
-
-      if bucketIndex == undefined
-        console.log "ERROR"
-      bucket = cube[bucketIndex]
-      unless bucket
-        return 0
-      if bucketData
-        if zoomStep < bucket.zoomStep
-          bucket.data = bucketData
-          @trigger("bucketLoaded", [bucket_x, bucket_y, bucket_z], 0, bucket.zoomStep)
-          bucket.zoomStep = 0
-      else
-        bucket.requestedZoomStep = bucket.zoomStep
+            bucketIndex = @bucketIndexByAddress3(x + dx, y + dy, z + dz)
+
+            if bucketIndex
+              bucket = cube[bucketIndex]
+              if bucketData
+                if zoomStep < bucket.zoomStep
+                  bucket.data = bucketData
+                  @trigger("bucketLoaded", [x + dx, y + dy, z + dz], zoomStep, bucket.zoomStep)
+                  bucket.zoomStep = zoomStep
+              else
+                bucket.requestedZoomStep = bucket.zoomStep
+
+    else
+      bucketIndex = @bucketIndexByAddress3(bucket_x, bucket_y, bucket_z)
+      if bucketIndex
+        bucket = cube[bucketIndex]
+        unless bucket
+          return 0
+        if bucketData
+          if zoomStep < bucket.zoomStep
+            bucket.data = bucketData
+            @trigger("bucketLoaded", [bucket_x, bucket_y, bucket_z], 0, bucket.zoomStep)
+            bucket.zoomStep = 0
+        else
+          bucket.requestedZoomStep = bucket.zoomStep

     return

@@ -210,8 +206,6 @@

             bucketIndex = @bucketIndexByAddress3(x + dx, y + dy, z + dz)

-            if bucketIndex == undefined
-              console.log "ERROR"
             if cube[bucketIndex]
               cube[bucketIndex].requestedZoomStep = Math.min(zoomStep, cube[bucketIndex].requestedZoomStep)
             else
@@ -220,9 +214,6 @@
     else

       bucketIndex = @bucketIndexByAddress3(bucket_x, bucket_y, bucket_z)
-
-      if bucketIndex == undefined
-        console.log "ERROR"

       if cube[bucketIndex]
         cube[bucketIndex].requestedZoomStep = 0
@@ -261,7 +252,6 @@
       bucketIndexByAddress3Macro(bucket_x, bucket_y, bucket_z)

     else
-
       undefined


Bug #40423587

Bug #40423587

Fixes issue with method getUnassignedUsers.

--- app/assets/javascripts/models/project.js.coffee
+++ app/assets/javascripts/models/project.js.coffee
@@ -23,4 +23,8 @@
   getUsers: -> new StaffPlan.Collections.Users @getAssignments().map (assignment) ->
     assignment.get("user_id")

-  getUnassignedUsers: -> new StaffPlan.Collections.Users StaffPlan.users.without @getUsers()
+  getUnassignedUsers: ->
+    userModels = StaffPlan.users.select (user) =>
+      _.all user.getAssignments().map (assignment) =>
+        assignment.get('project_id') isnt @id
+    new StaffPlan.Collections.Users userModels


Update FB API to v2.7

Update FB API to v2.7

We were on v2.0, which is being deprecated on August 8th

--- app/core/social-handlers/FacebookHandler.coffee
+++ app/core/social-handlers/FacebookHandler.coffee
@@ -55,7 +55,7 @@
         js = d.createElement('script')
         js.id = id
         js.async = true
-        js.src = '//connect.facebook.net/en_US/all.js'
+        js.src = '//connect.facebook.net/en_US/sdk.js'

         #js.src = '//connect.facebook.net/en_US/all/debug.js'
         ref.parentNode.insertBefore js, ref
@@ -63,12 +63,13 @@
       )(document)

       window.fbAsyncInit = =>
-        FB.init
+        FB.init({
           appId: (if document.location.origin is 'http://localhost:3000' then '607435142676437' else '148832601965463') # App ID
           channelUrl: document.location.origin + '/channel.html' # Channel File
           cookie: true # enable cookies to allow the server to access the session
           xfbml: true # parse XFBML
-
+          version: 'v2.7'
+        })
         FB.getLoginStatus (response) =>
           if response.status is 'connected'
             @connected = true


Removed throwing when URI parsing fails

Removed throwing when URI parsing fails

--- src/expand-uri-template-with-parameters.coffee
+++ src/expand-uri-template-with-parameters.coffee
@@ -8,14 +8,10 @@
   try
     parsed = ut.parse uriTemplate
   catch e
-    if e['name'] == 'SyntaxError'
-      text = 'SyntaxError in URI template'
-      result['errors'].push text
-      return result
-    else
-      throw e
-
-
+    text = 'Failed to parse URI template'
+    result['errors'].push text
+    return result
+
   # get parameters from expression object
   uriParameters = []
   for expression in parsed['expressions']


template autoreload for development

template autoreload for development

--- logic/templates.coffee
+++ logic/templates.coffee
@@ -17,7 +17,7 @@
   path = require.resolve("../templates/#{filename}.coffee")
   if config.env and config.env == 'development'
     stat = require('fs').statSync(path)
-    if template_mtimes[path] != stat.mtime
+    if not template_mtimes[path] or template_mtimes[path] < stat.mtime
       console.log "loading templates/#{filename}.coffee template..."
       template_mtimes[path] = stat.mtime
       delete require.cache[path]


Fix bug in voting layout

Fix bug in voting layout

--- client.coffee
+++ client.coffee
@@ -301,7 +301,7 @@
             Dom.div !->
                 Dom.style
                     Box: 'center middle'
-                    display: 'inline-block'
+                    display: 'inline-block !important'
                     position: 'relative'
                     padding: '6px 4px'
                     margin: '1px'


Fix algebraic data, now properly type checking polymorphism

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1213,29 +1213,22 @@
     # Syntax
     [names, typeArgLists] = unzip defs
     map (syntaxNewName 'Type constructor name required'), names
-    fieldTypes = []
-    for typeArgs in typeArgLists
-      if isRecord typeArgs
-        for type in _snd unzip _labeled _terms typeArgs
-          fieldTypes.push type
-          syntaxType type
-      else
-        malformed typeArgs, 'Required a record of types'
     if not hasName
       return 'malformed'

     dataName = ctx.definitionName()

     # Types, Arity
-    dataType = findDataType ctx, fieldTypes, typeParams, dataName
-    for [constr, params] in defs
-      paramTypes = typesCompile ctx, (map _snd, (_labeled _terms params or []))
+    {fieldTypes, dataType} = findDataType ctx, typeArgLists, typeParams, dataName
+    for [constr, params], i in defs
+      paramTypes = fieldTypes[i]
       constrType = if params
         typeFn (join paramTypes, [dataType])...
       else
         dataType
       paramLabels = (_labeled _terms params or []).map(_fst).map(_labelName)
       # log "Adding constructor #{constr.symbol}", constrType
+      log printType constrType
       ctx.declare constr.symbol,
         type: quantifyUnbound ctx, toConstrained constrType
         arity: (paramLabels if params)
@@ -1266,29 +1259,44 @@
           (jsNew identifier, []))
       (join (translateDict identifier, paramNames), [constrValue]))

-findDataType = (ctx, types, typeParams, dataName) ->
+findDataType = (ctx, typeArgLists, typeParams, dataName) ->
   varNames = map _symbol, typeParams
   varNameSet = arrayToSet varNames
   kinds = newMap()
-  for type in types
-    for name, kind of values findFree typeCompile ctx, type
-      if not inSet varNameSet, name
-        malformed type, "Type variable #{name} not declared"
-      else
-        if foundKind = lookupInMap kinds, name
-          if not kindsEq foundKind, kind
-            malformed type, "Type variable #{name} must have the same kind"
-        else
-          addToMap kinds, name, kind
+
+  # TODO: I need to figure out the error handling, we should bail out
+  #       if an undeclared type var is used
+  fieldTypes = for typeArgs in typeArgLists
+    if isRecord typeArgs
+      for type in _snd unzip _labeled _terms typeArgs
+        type = typeCompile ctx, type
+        for name, kind of values findFree type
+          if not inSet varNameSet, name
+            malformed type, "Type variable #{name} not declared"
+            throw new Error "Type variable #{name} not declared"
+          else
+            if foundKind = lookupInMap kinds, name
+              if not kindsEq foundKind, kind
+                malformed type, "Type variable #{name} must have the same kind"
+            else
+              addToMap kinds, name, kind
+        type
+    else
+      malformed typeArgs, 'Required a record of types'
+      null

   for typeParam in typeParams
     if not lookupInMap kinds, (_symbol typeParam)
       malformed typeParam, 'Data type parameter not used'
+      throw new Error 'Data type parameter not used'
+
+  freshingSub = mapToSubstitution mapMap ((kind) -> ctx.freshTypeVariable kind), kinds

   dataKind = kindFnOfArgs (map ((name) -> lookupInMap kinds, name), varNames)...
-  typeVars = mapToArrayVia ((name, kind) -> new TypeVariable name, kind), kinds
-  applyKindFn (new TypeConstr dataName, dataKind), typeVars...
-
+  typeVars = map ((name) -> new TypeVariable name, (lookupInMap kinds, name)), varNames
+  dataType: (substitute freshingSub,
+    (applyKindFn (new TypeConstr dataName, dataKind), typeVars...))
+  fieldTypes: (map ((types) -> if types then substituteList freshingSub, types), fieldTypes)

 ms.record = ms_record = (ctx, call) ->
     args = _arguments call
@@ -3407,6 +3415,10 @@

 substituteList = (substitution, list) ->
   map ((t) -> substitute substitution, t), list
+
+# Only valid for substitute
+mapToSubstitution = (map) ->
+  vars: map.values

 findFree = (type) ->
   if type instanceof TypeVariable


Improved type constraint compilation and fixed order of type class dictionary parameters in definitions

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -906,7 +906,7 @@
   [type, constraints...] = _arguments call
   new Constrained (typeConstraintsCompile ctx, constraints), (typeCompile ctx, type)

-typeCompile = (ctx, expression) ->
+typeCompile = (ctx, expression, expectedKind) ->
   throw new Error "invalid typeCompile args" unless expression
   (if isAtom expression
     typeNameCompile
@@ -916,10 +916,10 @@
     typeConstructorCompile
   else
     malformed ctx, expression, 'not a valid type'
-  )? ctx, expression
-
-typesCompile = (ctx, expressions) ->
-  typeCompile ctx, e for e in expressions
+  )? ctx, expression, expectedKind
+
+typesCompile = (ctx, expressions, expectedKinds = []) ->
+  typeCompile ctx, e, expectedKinds[i] for e, i in expressions

 typeNameCompile = (ctx, atom, expectedKind) ->
   expanded = ctx.resolveTypeAliases atom.symbol
@@ -973,13 +973,17 @@
   else
     malformed ctx, op, 'Expected a type constructor instead'

+# Will have to defer if class doesn't exist yet
 typeConstraintCompile = (ctx, expression) ->
   op = _operator expression
   args = _arguments expression
   if isCall expression
     if isAtom op
       (labelOperator op)
-      new ClassConstraint op.symbol, new Types (typesCompile ctx, args)
+      className = op.symbol
+      {constraint} = ctx.classNamed className
+      paramKinds = (map kind, constraint.types.types)
+      new ClassConstraint op.symbol, new Types (typesCompile ctx, args, paramKinds)
     else
       malformed ctx, expression, 'Class name required in a constraint'
   else
@@ -1034,7 +1038,6 @@
   assignCompileAs ctx, expression, translatedExpression, yes

 patternCompile = (ctx, pattern, matched, polymorphic) ->
-
   # caching can occur while compiling the pattern
   # precs are {cond}s and {cache}s, sorted in order they need to be executed
   {precs, assigns} = expressionCompile ctx, pattern
@@ -1414,8 +1417,6 @@
         malformed ctx, type, 'Label is required'
       if not type
         malformed ctx, name, 'Missing type'
-      if name and type
-        syntaxType type
     if args.length is 0
       malformed ctx, call, 'Missing field declarations'
     # TS: (data #{ctx.definitionName()} [#{_arguments form}])
@@ -2286,7 +2287,7 @@
   else if isTuple expression
     map syntaxType, (_terms expression)
   else if isCall expression
-    syntaxNameAs ctx, 'Constructor name required', 'typecons', (_operator expression)
+    (_operator expression).label = 'typecons'
     map syntaxType, (_arguments expression)

 syntaxNewName = (ctx, message, atom) ->
@@ -2351,16 +2352,16 @@
   # TODO: what about the class dictionaries order?
   counter = {}
   classParams = newMap()
-  for constraint in reducedConstraints when not isAlreadyParametrized ctx, constraint
+  classParamNames = for constraint in reducedConstraints when not isAlreadyParametrized ctx, constraint
     {className} = constraint
     names = typeNamesOfNormalized constraint
     typeMap = nestedLookupInMap classParams, names
     if not typeMap
       nestedAddToMap classParams, names, (typeMap = newMap())
-    addToMap typeMap, className,
-      "_#{className}_#{counter[className] ?= 0; ++counter[className]}"
+    dictName = "_#{className}_#{counter[className] ?= 0; ++counter[className]}"
+    addToMap typeMap, className, dictName
+    dictName
   ctx.addClassParams classParams
-  classParamNames = concatMap mapToArray, (mapToArray classParams)
   if _notEmpty classParamNames
     if expression.ir is irFunctionTranslate
       (irFunctionTranslate ctx,
@@ -2456,7 +2457,7 @@

 typeNamesOfNormalized = (constraint) ->
   #map (({name}) -> name), constraint.types.types
-  [constraint.types.types[0].name]
+  [printType constraint.types.types[0]]

 accessList = (what, list) ->
   [first, rest...] = list
@@ -3810,7 +3811,7 @@
     else if type.TypeConstr
       no
     else if type.TypeApp
-      isNormalizedConstraintArgument type.op.type
+      isNormalizedConstraintArgument type.op

 typeEq = (a, b) ->
@@ -5278,6 +5279,58 @@
   """
   "(first (concat {{1} {2} {3}}))", 1

+  'mixing constructor classes with fundeps'
+  """
+  Mappable (class [wrapper]
+    map (fn [what onto]
+      (: (Fn (Fn a b) (wrapper a) (wrapper b)))
+      (# Apply what to every value inside onto .)))
+
+  Bag (class [bag item]
+    size (fn [bag]
+      (: (Fn bag Num))
+      (# The number of items in the bag .))
+
+    empty (: bag)
+
+    fold (fn [with initial over]
+      (: (Fn (Fn item a a) a bag a))
+      (# Fold over with using initial ...))
+
+    join (fn [what with]
+      (: (Fn bag bag bag))
+      (# Fold over with using initial ...)))
+
+  array-mappable (instance (Mappable Array)
+    map (macro [what over]
+      (: (Fn (Fn a b) (Array a) (Array b)))
+      (Js.method over "map" {what})))
+
+  array-bag (instance (Bag (Array a) a)
+    size (macro [list]
+      (: (Fn (List a) Num))
+      (Js.access list "size"))
+
+    empty {}
+
+    fold (macro [with initial list]
+      (: (Fn (Fn a b b) b (Array a) b))
+      (Js.method list "reduce"
+        {(fn [acc x] (with x acc)) initial}))
+
+    join (macro [what with]
+      (: (Fn (Array a) (Array a) (Array a)))
+      (Js.method what "concat" {with})))
+
+  concat (fn [bag-of-bags]
+    (fold join empty bag-of-bags))
+
+  concat-map (fn [what over]
+    (concat (map what over)))
+
+  """
+  "(size (concat-map (fn [x] {}) {1 2 3}))", 0
+
   # The following doesn't work because the Collection type class specifies
   # that the constructor takes only one argument.
   #


Highlight examples

Highlight examples

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -5737,7 +5737,7 @@
   lookupInMap compiledModules, name

-compileTopLevel = (source, moduleName = '@unnamed', requiredMap = newMap()) ->
+compileModuleTopLevel = (source, moduleName = '@unnamed', requiredMap = newMap()) ->
   addToMap requiredMap, 'Prelude', yes # TODO: Hardcoded prelude dependency
   removeFromMap requiredMap, moduleName
   for requiredModuleName of values requiredMap
@@ -5752,7 +5752,7 @@
   {request, ast, ir, js} = compileCtxAstToJs compilationFn, ctx, (astFromSource "(#{source})", -1, -1)
   if request
     if not allInjected request, requiredMap
-      return compileTopLevel source, moduleName, request
+      return compileModuleTopLevel source, moduleName, request
     else
       {js} = compileCtxIrToJs ctx, ir
   errors = checkTypes ctx
@@ -5765,27 +5765,39 @@
   # types: typeEnumaration ctx
   errors: errors

-compileModule = (moduleName = '@unnamed') ->
+compileModuleWithDependencies = (moduleName = '@unnamed') ->
   js: library + immutable + (listOfLines map lookupJs, (setToArray runtimeDependencies moduleName))

 compileExpression = (source, moduleName = '@unnamed') ->
+  {js} = parsed = parseExpression source, moduleName
+  extend parsed,
+    (js:  library + immutable + (listOfLines map lookupJs, (setToArray runtimeDependencies moduleName)) + '\n;' + js)
+
+parseTopLevel = (source, moduleName = '@unnamed') ->
   ast = (astFromSource "(#{source})", -1, -1)
-  if _empty _validTerms ast
+  parseWith ast, ast, topLevel, moduleName
+
+parseExpression = (source, moduleName = '@unnamed') ->
+  ast = (astFromSource "(#{source})", -1, -1)
+  [expression] = _terms ast
+  compilationFn = (topLevelExpressionInModule importsFor moduleDependencies moduleName)
+  parseWith ast, expression, compilationFn, moduleName
+
+parseWith = (originalAst, ast, compilationFn, moduleName) ->
+  if _empty _validTerms originalAst
     {
-      ast: ast
+      ast: originalAst
       js: ''
     }
   else
     {modules, ctx} = contextWithDependencies moduleDependencies moduleName
-    [expression] = _terms ast
-    compilationFn = (topLevelExpressionInModule importsFor moduleDependencies moduleName)
-    {js} = compileCtxAstToJs compilationFn, ctx, expression
+    {js} = compileCtxAstToJs compilationFn, ctx, ast
     errors = checkTypes ctx
-    (finalizeTypes ctx, expression)
-    js: library + immutable + (listOfLines map lookupJs, (setToArray runtimeDependencies moduleName)) + '\n;' + js
-    ast: ast
+    (finalizeTypes ctx, ast)
+    ast: originalAst
     errors: errors
     malformed: ctx.isMalformed
+    js: js

 expandCall = (moduleName, call) ->
   {modules, ctx} = contextWithDependencies moduleDependencies moduleName
@@ -6271,7 +6283,21 @@
 __ = (fna, fnb) ->
   (x) -> fna fnb x

+
 # end of Utils
+
+
+# Object utils
+
+merge = (objects) ->
+  c = {}
+  for a in objects
+    for key, value of a
+      c[key] = value
+  c
+
+extend = (a, b) ->
+  merge [a, b]

 # Unit tests
 test = (testName, teaSource, result) ->
@@ -7602,9 +7628,11 @@
 # end of tests

-exports.compileTopLevel = compileTopLevel
-exports.compileModule = compileModule
+exports.compileModuleTopLevel = compileModuleTopLevel
+exports.compileModuleWithDependencies = compileModuleWithDependencies
 exports.compileExpression = compileExpression
+exports.parseTopLevel = parseTopLevel
+exports.parseExpression = parseExpression
 exports.expand = expandCall
 exports.findAvailableTypes = findAvailableTypes
 exports.findMatchingDefinitions = findMatchingDefinitions


add git-blame

add git-blame

--- atom/packages.cson
+++ atom/packages.cson
@@ -19,6 +19,7 @@
   "ember-snippets"
   "emmet"
   "ex-mode"
+  "git-blame"
   "git-history"
   "gruvbox"
   "jshint"


Assigning params correct scope index

Assigning params correct scope index

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1697,12 +1697,12 @@
               type: ctx.preDeclaredType name # if any
               docs: documentation

+      ctx.newLateScope()
       newParamType = (param) ->
         param.scope = ctx.currentScopeIndex()
         withOrigin (ctx.freshTypeVariable star), param
       paramTypeVars = map newParamType, params
       paramTypes = map (__ toForAll, toConstrained), paramTypeVars
-      ctx.newLateScope()
       ctx.bindTypeVariables paramTypeVars
       # log "adding types", (map _symbol, params), paramTypes
       ctx.declareTyped paramNames, paramTypes


Add Apache snippets

Add Apache snippets

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -706,6 +706,8 @@
     'prefix': 'innerjoin'
     'body': 'INNER JOIN ${1:tname} ON ${2:condition}'

+# Liquid templating engine
+
 '.text.html.liquid,
  .source.gfm':

@@ -744,3 +746,39 @@
   'endfor':
     'prefix': 'endfor'
     'body': '{% endfor %}'
+
+# Apache configuration
+
+'.source.apache-config':
+
+  'IfModule':
+    'prefix': 'ifmodule'
+    'body': '<IfModule ${1:modname.c}>\n\t\n</IfModule>'
+
+  'ErrorDocument':
+    'prefix': 'errordocument'
+    'body': 'ErrorDocument ${1:404} ${1:url}'
+
+  'Header':
+    'prefix': 'header'
+    'body': 'Header ${1:set} ${2:name} ${3:value}$'
+
+  'Cache-Control':
+    'prefix': 'cachecontrol'
+    'body': 'Header set Cache-Control "$2"'
+
+  'RewriteEngine':
+    'prefix': 'rewriteengine'
+    'body': 'RewriteEngine ${1:On}'
+
+  'RewriteBase':
+    'prefix': 'rewritebase'
+    'body': 'RewriteBase ${1:/}'
+
+  'RewriteRule':
+    'prefix': 'rewriterule'
+    'body': 'RewriteRule ${1:pattern} ${2:url}'
+
+  'RewriteCond':
+    'prefix': 'rewritecond'
+    'body': 'RewriteCond ${1:%{HTTP_HOST}} ${2:pattern}'


Add snippets for instanceOf assertions

Add snippets for instanceOf assertions

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -361,6 +361,14 @@
     'prefix': 'nottohaveprop'
     'body': 'not.to.have.property($1);$2'

+  'to be instance of':
+    'prefix': 'tobeinstanceof'
+    'body': 'to.be.instanceOf($1);$2'
+
+  'not to be instance of':
+    'prefix': 'nottobeinstanceof'
+    'body': 'not.to.be.instanceOf($1);$2'
+
   # Debugging

   'console.log':


Update remainder of QA specs

Update remainder of QA specs

--- tutor/specs/components/qa.spec.cjsx
+++ tutor/specs/components/qa.spec.cjsx
@@ -1,4 +1,4 @@
-{Testing, _, ReactTestUtils} = require './helpers/component-testing'
+{React, Testing, _, ReactTestUtils} = require './helpers/component-testing'

 ld = require 'lodash'

@@ -30,36 +30,44 @@
     _.defer(done) # defer done signal so it fires after exercise load emits

   it 'displays the exercise questions', ->
-    Testing.renderComponent( Exercises, props: @props ).then ({dom}) ->
-      questions = _.map EXERCISES.items, (e) ->
-        e.content.questions[0].stem_html
-      renderedQs = _.pluck dom.querySelectorAll('.panel-body .question-stem'), 'textContent'
-      expect(renderedQs).to.deep.equal(questions)
+    wrapper = mount(<Exercises {...@props} />)
+    stems = wrapper.find('.panel-body .question-stem').map (stem) -> stem.text()
+    questions = _.map EXERCISES.items, (e) ->
+      e.content.questions[0].stem_html
+    expect(stems).to.deep.equal(questions)
+    undefined

   it 'displays free-response box when previewing 2-step', ->
     wrapper = mount(<Exercises {...@props} />)
     expect(wrapper.find('.exercise-free-response-preview')).to.have.length(0)
-    wrapper.find('.preview2step').simulate('change', checked: true)
+    wrapper.find('.preview2step').simulate('change', target: checked: true)
     freeResponseCount = _.reduce(EXERCISES.items, (count, ex) ->
       count + (if ExerciseStore.hasQuestionWithFormat('free-response', ex) then 1 else 0)
     , 0)
+    wrapper.update()
+    # console.log wrapper.debug()
     expect(wrapper.find('.exercise-free-response-preview'))
       .to.have.length(freeResponseCount)
+    undefined

   it 'renders exercises even if they dont have tags', ->
     ex = ld.cloneDeep EXERCISES
     _.each ex.items, (e) -> e.content.tags = []
     ExerciseActions.loadedForCourse(ex, COURSE_ID, ['146'])
-    Testing.renderComponent( Exercises, props: @props ).then ({dom, element}) ->
-      expect( dom.querySelectorAll('.exercise').length ).to.equal(5)
+    wrapper = mount(<Exercises {...@props} />)
+    expect(wrapper.find('.exercise')).to.have.length(5)
+    undefined

   it 'hides exercise lo tags that don\'t belong to current book', ->
     ex = ld.cloneDeep EXERCISES
     _.each ex.items, (e) -> e.content.tags.push("lo:uknown-fake-uuid")
     ExerciseActions.loadedForCourse(ex, COURSE_ID, ['146'])
-    Testing.renderComponent( Exercises, props: @props ).then ({dom, element}) ->
-      expect( dom.textContent ).not.to.contain('uknown-fake-uuid')
+    wrapper = mount(<Exercises {...@props} />)
+    tags = wrapper.find('.lo-tag').map (tag) -> tag.text()
+    expect(tags).to.not.include('LO: uknown-fake-uuid')
+    undefined

   it 'displays question formats', ->
-    Testing.renderComponent( Exercises, props: @props ).then ({dom}) ->
-      expect(dom.querySelector('.formats-listing')).to.exist
+    wrapper = mount(<Exercises {...@props} />)
+    expect(wrapper.find('.formats-listing')).to.have.length(EXERCISES.items.length)
+    undefined


Update site redirected to if user doesn't agree on the vex confirm popup

--- app/assets/javascripts/plugins/cookie_adult_validation.js.coffee
+++ app/assets/javascripts/plugins/cookie_adult_validation.js.coffee
@@ -7,5 +7,5 @@
         $.extend({}, vex.dialog.buttons.NO, text: I18n.t('false', locale: gon.language))
       ]
       callback: (value) ->
-        window.location.href = 'http://google.fr' if value is false
+        window.location.href = 'http://lr-agenceweb.fr' if value is false
         Cookies.set('adult', 'validated') if value is true


add admins to edge

add admins to edge

--- components/split_test/running_tests.coffee
+++ components/split_test/running_tests.coffee
@@ -31,6 +31,7 @@

   commercial_filtering:
     key: 'commercial_filtering'
+    edge: 'collect'
     outcomes:
       browse: 0.5
       collect: 0.5


fix stripColons for null

fix stripColons for null

--- vue/src/shared/helpers/emojis.coffee
+++ vue/src/shared/helpers/emojis.coffee
@@ -12,8 +12,8 @@
 export { emojis, emojisByCategory }

 export stripColons = (colons) ->
-  if (colons[0] == ':') && (colons[colons.length-1] == ':')
-    colons.substring(1, colons.length-1)
+  if colons && (colons[0] == ':') && (colons[colons.length - 1] == ':')
+    colons.substring(1, colons.length - 1)
   else
     colons


Remove unused import `marked`

Remove unused import `marked`

--- spec/atom-reporter.coffee
+++ spec/atom-reporter.coffee
@@ -2,7 +2,6 @@
 process = require 'process'
 _ = require 'underscore-plus'
 grim = require 'grim'
-marked = require 'marked'
 listen = require '../src/delegated-listener'
 ipcHelpers = require '../src/ipc-helpers'


Add support for trackingEndpoint in remote config

--- src/remote/gateway.coffee
+++ src/remote/gateway.coffee
@@ -15,6 +15,7 @@
 API_PATH_REGEXP = /^\/?api\/v1\//
 RESPONSE_HEADERS = ['Hull-Auth-Scope', 'Hull-Track', 'Hull-User-Id', 'Hull-User-Sig', 'X-Hits-Count', 'Link']

+TRACKING_PATHS = ["/api/v1/t", "/api/v1/me/traits", "/api/v1/me/alias"];

 normalizePath = (path) ->
   return path.replace(API_PATH_REGEXP, API_PATH) if API_PATH_REGEXP.test(path)
@@ -73,6 +74,7 @@
   constructor: (config={}) ->
     { batching, appId, identify } = config
     @apiEndpoint = config.apiEndpoint
+    @trackingEndpoint = config.trackingEndpoint
     @identify = identify
     @options = _.defaults({}, batching, { min:1, max:1, delay:2 })
     @queue = batchable @options.delay, (requests) -> @flush(requests)
@@ -92,8 +94,9 @@
     method = (method||'get').toUpperCase()

     headers = assign(@identifyBrowserAndSession(), RemoteHeaderStore.getState(), headers)
-
-    if @apiEndpoint
+    if @trackingEndpoint && TRACKING_PATHS.includes(path)
+      endpoint = [@trackingEndpoint, path].join('')
+    else if @apiEndpoint
       endpoint = [@apiEndpoint, path].join('')
     else
       endpoint = path


Fix entabulation

Fix entabulation

--- ui/scripts/adapters/password.coffee
+++ ui/scripts/adapters/password.coffee
@@ -40,10 +40,10 @@
         if state in ['password', 'email']
             @form = new FormView({fields: @getFields(state)})
         else
-        @form = new View({
-            template: -> return "Check your inbox. " +
-                                "If not, check your spam folder."
-        })
+            @form = new View({
+                template: -> return "Check your inbox. " +
+                                    "If not, check your spam folder."
+            })
         @form.render()
         @view.render({
             title: 'Change Password'


[bugfix] reporter didn't have an account

--- server/models/account_imap.coffee
+++ server/models/account_imap.coffee
@@ -39,7 +39,7 @@
 Account::imap_fetchMails = (limitByBox) ->
     reporter = null
     Mailbox.getBoxes @id
-    .tap (boxes) ->
+    .tap (boxes) =>
         reporter = ImapReporter.accountFetch this, boxes.length
     .serie (box) =>
         box.imap_fetchMails limitByBox


server: clear double set cookie

server: clear double set cookie

--- servers/lib/server/helpers.coffee
+++ servers/lib/server/helpers.coffee
@@ -316,6 +316,8 @@
   options.secure  = sessionCookie.secure
   options.expires = new Date(Date.now() + sessionCookie.maxAge)

+  # somehow we are sending two clientId cookies in some cases, last writer wins.
+  res.clearCookie 'clientId', options
   res.cookie 'clientId', sessionId, options


server: helpers minor code styling

server: helpers minor code styling

--- servers/lib/server/helpers.coffee
+++ servers/lib/server/helpers.coffee
@@ -46,25 +46,20 @@
 findUsernameFromSession = (req, res, callback) ->

   {clientId} = req.cookies
-
   unless clientId?
-
     return process.nextTick -> callback null

-  else
-
-    koding.models.JSession.fetchSession clientId, (err, result)->
-
-      return if err
-      then callback err
-      else unless result?
-      then callback null
-
-      {session} = result
-
-      return unless session?
-      then callback null
-      else callback null, session.username
+  koding.models.JSession.fetchSession clientId, (err, result) ->
+
+    if err
+      return callback err
+    else unless result?
+      return callback null
+
+    {session} = result
+    unless session?
+    then callback null
+    else callback null, session.username

 fetchJAccountByKiteUserNameAndKey = (req, callback)->
   if req.fields
@@ -186,12 +181,10 @@
 getClientId = (req, res)->
   return req.cookies.clientId or req.pendingCookies.clientId

-isInAppRoute = (name)->
+isInAppRoute = (name) ->
   [firstLetter] = name
-  # user nicknames can start with numbers
-  intRegex = /^\d/
-  return false if intRegex.test firstLetter
-  return true  if firstLetter.toUpperCase() is firstLetter
+  return false  if /^[0-9]/.test firstLetter # user nicknames can start with numbers
+  return true   if firstLetter.toUpperCase() is firstLetter
   return false

 module.exports = {


Remove unusued cluster.coffee

Remove unusued cluster.coffee

--- services/filestore/cluster.coffee
+++ services/filestore/cluster.coffee
@@ -1,9 +0,0 @@
-recluster = require "recluster" # https://github.com/doxout/recluster
-path = require "path"
-
-cluster = recluster path.join(__dirname, 'app.js'), {
-		workers: 2,
-		backoff: 0,
-		readyWhen: "listening"
-}
-cluster.run()


client: welcome: buildStep description updated

--- client/home/lib/flux/stores/welcomestepsstore.coffee
+++ client/home/lib/flux/stores/welcomestepsstore.coffee
@@ -33,7 +33,7 @@
           title: 'Build Your Stack'
           actionTitle: 'Build'
           videoLink: ''
-          description: 'You have a stack ready to go, go ahead and build it.'
+          description: 'To access your VMs you need to build your stack.'
           isDone: no
           order: 3
         inviteTeam :
@@ -61,7 +61,7 @@
           title: 'Build Your Stack'
           actionTitle: 'Build'
           videoLink: ''
-          description: 'Your team admins have already created your stack, it is ready for you to build.'
+          description: 'To access your VMs you need to build your stack.'
           isDone: no
           order: 2
         stackCreation :


[Ban User] changed function signature to get account id or nickname

--- client/landing/app/MainApp/activitycontroller.coffee
+++ client/landing/app/MainApp/activitycontroller.coffee
@@ -30,7 +30,7 @@
   blockUser:(accountId, duration, callback)->
     KD.whoami().blockUser accountId, duration, callback

-  openBlockUserModal:(data)->
+  openBlockUserModal:(nicknameOrAccountId)->
     @modal = modal = new KDModalViewWithForms
       title                   : "Block User For a Time Period"
       content                 : """
@@ -49,7 +49,7 @@
           BlockUser           :
             callback          : =>
               blockingTime = calculateBlockingTime modal.modalTabs.forms.BlockUser.inputs.duration.getValue()
-              @blockUser data.originId, blockingTime, (err, res)->
+              @blockUser nicknameOrAccountId, blockingTime, (err, res)->
                 if err
                   warn err
                   modal.modalTabs.forms.BlockUser.buttons.blockUser.hideLoader()
@@ -211,4 +211,4 @@
     return no if @flags.liveUpdates and not isOnActivityPage

     @newItemsCount = 0
-    @emit "NewItemsCounterCleared"+    @emit "NewItemsCounterCleared"


Fixing ambush.coffee annoying error on every hear

This fixes an error on every call to hear, ambush.coffee is erroring if no ambushes are set.

--- src/scripts/ambush.coffee
+++ src/scripts/ambush.coffee
@@ -35,6 +35,8 @@
       msg.send "#{msg.match[1]}? Never heard of 'em"

   robot.hear /./i, (msg) ->
+    if (!robot.brain.data.ambushes)
+      return
     if (ambushes = robot.brain.data.ambushes[msg.message.user.name])
       msg.send "Hey, " + msg.message.user.name + ", while you were out:"
       for ambush in ambushes


Added tooltip to preview icon

Added tooltip to preview icon

--- client/Social/Activity/views/inputwidget.coffee
+++ client/Social/Activity/views/inputwidget.coffee
@@ -40,6 +40,8 @@
     @previewIcon = new KDCustomHTMLView
       tagName  : "span"
       cssClass : "preview-icon"
+      tooltip  :
+        title     : "Preview"
       click    : =>
         if not @preview then @showPreview()
         else @hidePreview()


Activity: removed unused methods

Activity: removed unused methods

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -143,29 +143,8 @@

     return scrollTop + height >= scrollHeight

+
   scrollDown: ->
-
-
-  scrollUp: ->
-
-    return  unless @active
-
-    window.scrollTo 0, 0
-
-
-  setScrollTops: ->
-
-    super
-
-    @lastScrollTops.window = window.scrollY
-
-
-  applyScrollTops: ->
-
-    super
-
-    KD.utils.defer =>
-      window.scrollTo 0, @lastScrollTops.window

   createChannelTitle: ->


Squirtle haters gonna gate

Squirtle haters gonna gate

--- src/scripts/haters.coffee
+++ src/scripts/haters.coffee
@@ -10,6 +10,7 @@
 , "http://i671.photobucket.com/albums/vv78/Sinsei55/HatersGonnaHatePanda.jpg"
 , "http://24.media.tumblr.com/tumblr_lltwmdVpoL1qekprfo1_500.gif"
 , "http://s3.amazonaws.com/kym-assets/photos/images/newsfeed/000/087/536/1292102239519.gif"
+, "http://i391.photobucket.com/albums/oo351/PikaPow3/squirtle.gif"
 ]

 hatin = (msg) ->


activity/flux: add missing ensureMessage action docs

--- client/activity/lib/flux/actions/message.coffee
+++ client/activity/lib/flux/actions/message.coffee
@@ -113,6 +113,13 @@
     return { message }

+###*
+ * Ensures a message is there and it also has enough surrounding message
+ * siblings so that scrolling into a single post would make much more sense.
+ *
+ * @param {string} messageId
+ * @return {Promise}
+###
 ensureMessage = (messageId) ->

   { reactor } = kd.singletons


Only search for specific types

Only search for specific types

--- client/apps/edit/components/layout/index.coffee
+++ client/apps/edit/components/layout/index.coffee
@@ -143,10 +143,12 @@

   autolinkText: =>
     linkableText = @getLinkableText()
+    searchTypes = ['artist', 'profile', 'show', 'gene']
+    searchQueryParam = searchTypes.map((t) -> "type[]=#{t}").join("&")
     async.mapSeries linkableText, (findText, cb) =>
       text = findText.split('==').join('')
       request
-        .get("#{sd.ARTSY_URL}/api/search?q=#{encodeURIComponent(text)}")
+        .get("#{sd.ARTSY_URL}/api/search?#{searchQueryParam}&q=#{encodeURIComponent(text)}")
         .set('X-Access-Token': sd.ACCESS_TOKEN)
         .end (err, res) =>
           if err or res.body.total_count < 1


fix duplicate gesture setup and remove debug

--- client/js/view_models/picture_view_model.coffee
+++ client/js/view_models/picture_view_model.coffee
@@ -28,10 +28,9 @@
     @setupGestures()

   setupKeyBindings: ->
-    $(document).keydown (event) => @onKeyDown event
+    $(document).keydown @onKeyDown

-  onKeyDown: (event) ->
-    console?.log 'onKeyDown', event
+  onKeyDown: (event) =>
     return true if event.altKey or event.ctrlKey or event.metaKey

     if event.keyCode in NEXT_PICTURE_KEYCODES
@@ -45,9 +44,7 @@
     hammer.on 'swipeleft', => @goTo 'previous'

   goTo: (prevNext) ->
-    console?.log 'goTo', prevNext
     href = @picture[prevNext]()
-    console?.log 'href', href
     if href
       page href
       false
@@ -60,10 +57,3 @@

     original = theOtherMediaHelper.getOriginal picture
     ko.mapping.fromJS original, {}, @original
-
-$ ->
-  # TODO encapsulate this $('#picture .prev-link:visible').click hackery!
-  hammer = $('#picture').hammer()
-  hammer.on 'swiperight', -> $('#picture .prev-link:visible').click()
-  hammer.on 'swipeleft', -> $('#picture .next-link:visible').click()
-


Was not updating the window title on profile update, fixed.

--- client/landing/app/CommonViews/FormViews.coffee
+++ client/landing/app/CommonViews/FormViews.coffee
@@ -134,7 +134,10 @@
         new KDNotificationView
           title     : "Success!"
           duration  : 500
-        @unsetClass 'active'
+
+        @unsetClass "active"
+        # Set window title too
+        document.title = "#{firstName} #{lastName}"

 class PersonalFormAboutView extends AbstractPersonalFormView


Tests: Fixed makeAlwaysOnForNotPaidUser test.

--- client/test/lib/environments/paidaccount.coffee
+++ client/test/lib/environments/paidaccount.coffee
@@ -26,8 +26,9 @@

   makeAlwaysOnForNotPaidUser: (browser) ->

-    buttonSelector = '.more-form .alwayson'
-    vmSelector     = 'a[href="/IDE/koding-vm-1"]'
+    buttonSelector    = '.machine-settings-modal .alwayson .koding-on-off a'
+    vmSelector        = 'a[href="/IDE/koding-vm-1"]'
+    freeModalSelector = '.computeplan-modal.env-modal.free-plan'

     helpers.beginTest(browser)

@@ -37,13 +38,13 @@
       else
         helpers.waitForVMRunning(browser)
         environmentHelpers.openVmSettingsModal(browser)
-        environmentHelpers.clickMoreButtonInVMSettingsModal(browser)

         browser
           .waitForElementVisible  buttonSelector, 20000
-          .click                  buttonSelector + ' .input-wrapper .koding-on-off a.knob'
+          .click                  buttonSelector
           .pause                  2000
-          .waitForElementVisible  '.kdmodal-content a.custom-link-view', 20000 # Assertion
+          .waitForElementVisible  freeModalSelector, 20000 # Assertion
+          .assert.containsText    freeModalSelector, 'Please upgrade to enable this feature.' # Assertion
           .end()


neo4j: fix bug in fetch newly installer apps

--- server/lib/server/graph/graph.coffee
+++ server/lib/server/graph/graph.coffee
@@ -106,7 +106,7 @@
     query = [
       'start koding=node:koding(\'id:*\')'
       'match koding-[r:user]->users'
-      'where koding.name="JApp" and r.createdAtEpoch > "2012-11-14T23:56:48Z"'
+      'where koding.name="JApp"'
       'and koding.`meta.createdAtEpoch` < {startDate}'
       'return *'
       'order by r.createdAtEpoch DESC'


Define session store before middleware

Define session store before middleware

--- server/packages/express/index.coffee
+++ server/packages/express/index.coffee
@@ -27,9 +27,6 @@
 		# } Create the Express instance.
 		@_app = express()

-		# } Register middleware.
-		@registerMiddleware()
-
 		# } Cache the session store.
 		@_sessionStore = switch @_config.sessions.db
 			when 'redis'
@@ -38,6 +35,9 @@

 				RedisStore = require('connect-redis') express
 				new RedisStore client: module.createClient()
+
+		# } Register middleware.
+		@registerMiddleware()

 		# } Handlebars!
 		# } `TODO`: Do we really need to use Express's theme system..?


Behaviour: Fix on_pointer_out.

Behaviour: Fix on_pointer_out.

--- engine/behaviour.coffee
+++ engine/behaviour.coffee
@@ -309,7 +309,6 @@
     _create_events: ->
         root = @_root
         @on_pointer_over? and addListener root, 'pointerover', @_on_pointer_over
-        @on_pointer_out? and addListener root, 'pointerout', @_on_pointer_out
         if @on_pointer_down? or @on_object_pointer_down?
             addListener root, 'pointerdown', @_on_pointer_down
         if @on_pointer_up? or @on_object_pointer_up?
@@ -329,8 +328,6 @@
         root = @_root
         if @on_pointer_over?
             removeListener root, 'pointerover', @_on_pointer_over
-        if @on_pointer_out?
-            removeListener root, 'pointerout', @_on_pointer_out
         if @on_pointer_down? or @on_object_pointer_down?
             removeListener root, 'pointerdown', @_on_pointer_down
         if @on_pointer_up? or @on_object_pointer_up?


test: fix unit tests to work with the latest connect

--- test/unit/middleware/runner.spec.coffee
+++ test/unit/middleware/runner.spec.coffee
@@ -72,15 +72,16 @@
       expect(config.client.args).to.deep.equal ['arg1', 'arg2']
       done()

+    RAW_MESSAGE = '{"args": ["arg1", "arg2"]}'
+
     request = new HttpRequestMock '/__run__', {
       'content-type': 'application/json'
-      'content-length': 1
+      'content-length': RAW_MESSAGE.length
     }
-    request.setEncoding = -> null

     handler request, response, nextSpy

-    request.emit 'data', '{"args": ["arg1", "arg2"]}'
+    request.emit 'data', RAW_MESSAGE
     request.emit 'end'

@@ -92,19 +93,19 @@
     sinon.stub fileListMock, 'changeFile'
     sinon.stub fileListMock, 'removeFile'

-    request = new HttpRequestMock '/__run__', {
-      'content-type': 'application/json'
-      'content-length': 1
-    }
-    request.setEncoding = -> null
-
-    handler request, response, nextSpy
-    message =
+    RAW_MESSAGE = JSON.stringify
       addedFiles: ['/new.js']
       removedFiles: ['/foo.js', '/bar.js']
       changedFiles: ['/changed.js']

-    request.emit 'data', JSON.stringify(message)
+    request = new HttpRequestMock '/__run__', {
+      'content-type': 'application/json'
+      'content-length': RAW_MESSAGE.length
+    }
+
+    handler request, response, nextSpy
+
+    request.emit 'data', RAW_MESSAGE
     request.emit 'end'

     process.nextTick ->
@@ -122,15 +123,16 @@
     sinon.stub fileListMock, 'refresh'
     sinon.stub executor, 'schedule'

+    RAW_MESSAGE = JSON.stringify {refresh: false}
+
     request = new HttpRequestMock '/__run__', {
       'content-type': 'application/json'
-      'content-length': 1
+      'content-length': RAW_MESSAGE.length
     }
-    request.setEncoding = -> null

     handler request, response, nextSpy

-    request.emit 'data', JSON.stringify {refresh: false}
+    request.emit 'data', RAW_MESSAGE
     request.emit 'end'

     process.nextTick ->


Add specs for library.

Add specs for library.

--- spec/reporter.spec.coffee
+++ spec/reporter.spec.coffee
@@ -0,0 +1,355 @@
+describe "Jasmine Webkit Reporter", () ->
+
+  it "should be defined", () ->
+    expect(jasmine.WebkitReporter).toBeDefined()
+    return
+
+  #-----------------------------------------------------------------------------
+  #--- constructor
+
+  describe "when create new reporter instance", () ->
+
+    describe "when options not given", () ->
+
+      instance = null
+
+      beforeEach () ->
+        instance = new jasmine.WebkitReporter()
+        return
+
+      it "should have default title", () ->
+        expect(instance.title).toEqual("Jasmine")
+        return
+
+      it "should have default replaceId", () ->
+        expect(instance.replaceId).toEqual("jasmine")
+        return
+
+      it "should have default timeout", () ->
+        expect(instance.timeout).toEqual(0)
+        return
+
+      it "should have default icons", () ->
+        expect(instance.passedIcon).toEqual("")
+        expect(instance.failedIcon).toEqual("")
+        expect(instance.runningIcon).toEqual("")
+        return
+
+      return
+
+    describe "when custom options given", () ->
+
+      instance = null
+
+      beforeEach () ->
+        instance = new jasmine.WebkitReporter
+          title: "Webkit Reporter"
+          replaceId: "webkit-reporter"
+          timeout: 3000
+          passedIcon: "passed.png"
+          failedIcon: "failed.png"
+          runningIcon: "running.png"
+        return
+
+      it "should have custom title", () ->
+        expect(instance.title).toEqual("Webkit Reporter")
+        return
+
+      it "should have custom replaceId", () ->
+        expect(instance.replaceId).toEqual("webkit-reporter")
+        return
+
+      it "should have custom timeout", () ->
+        expect(instance.timeout).toEqual(3000)
+        return
+
+      it "should have custom icons", () ->
+        expect(instance.passedIcon).toEqual("passed.png")
+        expect(instance.failedIcon).toEqual("failed.png")
+        expect(instance.runningIcon).toEqual("running.png")
+        return
+
+      return
+
+    it "should have empty startedAt and finishedAt", () ->
+      instance = new jasmine.WebkitReporter()
+      expect(instance.startedAt).toEqual(null)
+      expect(instance.finishedAt).toEqual(null)
+      return
+
+    return
+
+  it "should have default methods of jasmine.Reporter", () ->
+    instance = new jasmine.WebkitReporter()
+    methods = [
+      "reportRunnerStarting",
+      "reportRunnerResults",
+      "reportSuiteResults",
+      "reportSpecStarting",
+      "reportSpecResults",
+      "log"
+    ]
+    for method in methods
+      expect(instance[method]).toBeDefined()
+    return
+
+  #-----------------------------------------------------------------------------
+  #--- reportRunnerStarting
+
+  describe "have method reportRunnerStarting method", () ->
+
+    instance = null
+    showReportSpy = null
+    checkPermissionStub = null
+
+    beforeEach () ->
+      instance = new jasmine.WebkitReporter()
+      showReportSpy = sinon.spy(instance, "showReport")
+      checkPermissionStub = sinon.stub(window.webkitNotifications, 'checkPermission')
+      return
+
+    afterEach () ->
+      instance.showReport.restore()
+      checkPermissionStub.restore()
+      return
+
+    describe "if notifications permission is allowed", () ->
+
+      beforeEach () ->
+        checkPermissionStub.returns(0)
+        instance.reportRunnerStarting(null)
+        return
+
+      it "should mark startedAt", () ->
+        expect(instance.startedAt).not.toEqual(null)
+        return
+
+      it "should reset finishedAt", () ->
+        expect(instance.finishedAt).toEqual(null)
+        return
+
+      it "should call showReport method", () ->
+        expect(showReportSpy).toHaveBeenCalledWith("", "Jasmine: RUNNING", "Running specs. Please wait...")
+        return
+
+      it "should call showReport method with empty title", () ->
+        instance.title = ""
+        instance.reportRunnerStarting(null)
+        expect(showReportSpy).toHaveBeenCalledWith("", "RUNNING", "Running specs. Please wait...")
+        return
+
+      it "should call showReport method with custom icon", () ->
+        instance.runningIcon = "running.png"
+        instance.reportRunnerStarting(null)
+        expect(showReportSpy).toHaveBeenCalledWith("running.png", "Jasmine: RUNNING", "Running specs. Please wait...")
+        return
+
+      it "should call showReport method with custom title", () ->
+        instance.title = "WebkitReporter"
+        instance.reportRunnerStarting(null)
+        expect(showReportSpy).toHaveBeenCalledWith("", "WebkitReporter: RUNNING", "Running specs. Please wait...")
+        return
+
+      return
+
+    describe "if notifications permission isn't allowed", () ->
+
+      spyRequestPermission = null
+
+      beforeEach () ->
+        spyRequestPermission = sinon.spy(window.webkitNotifications, "requestPermission")
+        checkPermissionStub.returns(1)
+        return
+
+      afterEach () ->
+        window.webkitNotifications.requestPermission.restore()
+        return
+
+      it "should call requestPermission", () ->
+        instance.reportRunnerStarting(null)
+        expect(spyRequestPermission).toHaveBeenCalled()
+        expect(showReportSpy).not.toHaveBeenCalled()
+        return
+
+      return
+
+    return
+
+  #-----------------------------------------------------------------------------
+  #--- reportRunnerResults
+
+  describe "have method reportRunnerResults method", () ->
+
+    instance = null
+    showReportSpy = null
+    checkPermissionStub = null
+    passedRunner =
+      results: () ->
+        totalCount: 10
+        failedCount: 0
+    failedRunner =
+      results: () ->
+        totalCount: 10
+        failedCount: 5
+
+    beforeEach () ->
+      instance = new jasmine.WebkitReporter
+        passedIcon: "passed.png"
+        failedIcon: "failed.png"
+      instance.reportRunnerStarting(null)
+      showReportSpy = sinon.spy(instance, "showReport")
+      checkPermissionStub = sinon.stub(window.webkitNotifications, 'checkPermission')
+      return
+
+    afterEach () ->
+      instance.showReport.restore()
+      checkPermissionStub.restore()
+      return
+
+    describe "if notifications permission is allowed", () ->
+
+      time = null
+
+      beforeEach () ->
+        checkPermissionStub.returns(0)
+        return
+
+      describe "if given passed runner", () ->
+
+        beforeEach () ->
+          instance.reportRunnerResults(passedRunner)
+          time = (instance.finishedAt.getTime() - instance.startedAt.getTime()) / 1000
+          return
+
+        it "should call showReport with passed icon", () ->
+          expect(showReportSpy.args[0][0]).toEqual("passed.png")
+          return
+
+        it "should call showReport with passes title", () ->
+          expect(showReportSpy.args[0][1]).toEqual("Jasmine: PASSED")
+          return
+
+        it "should call showReport with correct message", () ->
+          expect(showReportSpy.args[0][2]).toEqual("10 specs, 0 failures in #{time}s")
+          return
+
+        return
+
+      describe "if given failed runner", () ->
+
+        beforeEach () ->
+          instance.reportRunnerResults(failedRunner)
+          time = (instance.finishedAt.getTime() - instance.startedAt.getTime()) / 1000
+          return
+
+        it "should call showReport with failed icon", () ->
+          expect(showReportSpy.args[0][0]).toEqual("failed.png")
+          return
+
+        it "should call showReport with failed title", () ->
+          expect(showReportSpy.args[0][1]).toEqual("Jasmine: FAILED")
+          return
+
+        it "should call showReport with correct message", () ->
+          expect(showReportSpy.args[0][2]).toEqual("10 specs, 5 failures in #{time}s")
+          return
+
+        return
+
+      return
+
+    describe "if notifications permission isn't allowed", () ->
+
+      spyRequestPermission = null
+
+      beforeEach () ->
+        spyRequestPermission = sinon.spy(window.webkitNotifications, "requestPermission")
+        checkPermissionStub.returns(1)
+        return
+
+      afterEach () ->
+        window.webkitNotifications.requestPermission.restore()
+        return
+
+      it "should call requestPermission", () ->
+        instance.reportRunnerResults(passedRunner)
+        expect(spyRequestPermission).toHaveBeenCalled()
+        expect(showReportSpy).not.toHaveBeenCalled()
+        return
+
+      return
+
+    return
+
+  #-----------------------------------------------------------------------------
+  #--- showReport
+
+  describe "have method showReport", () ->
+
+    instance = new jasmine.WebkitReporter()
+    notify =
+      icon: ""
+      title: ""
+      message: ""
+      replaceId: ""
+      created: false
+      show: () ->
+      cancel: () ->
+    createNotificationStub = null
+    createNotificationSpy = null
+    showSpy = null
+    cancelSpy = null
+    setTimeoutStub = null
+
+    beforeEach () ->
+      notify.title = ""
+      notify.replaceId = ""
+      notify.created = false
+      createNotificationStub = sinon.stub window.webkitNotifications, "createNotification", (icon, title, message) ->
+        notify.icon = icon
+        notify.title = title
+        notify.message = message
+        notify.created = true
+        return notify
+      setTimeoutStub = sinon.stub window, "setTimeout", (func, timeout) ->
+        func.call(this)
+        return
+      showSpy = sinon.spy(notify, "show")
+      cancelSpy = sinon.spy(notify, "cancel")
+
+      instance.showReport("icon.png", "title", "message")
+      return
+
+    afterEach () ->
+      createNotificationStub.restore()
+      setTimeoutStub.restore()
+      showSpy.restore()
+      cancelSpy.restore()
+      return
+
+    it "should call createNotification with args", () ->
+      expect(notify.created).toBeTruthy()
+      expect(notify.icon).toEqual("icon.png")
+      expect(notify.title).toEqual("title")
+      expect(notify.message).toEqual("message")
+      return
+
+    it "should call show on notification", () ->
+      expect(showSpy).toHaveBeenCalled()
+      return
+
+    it "should set replaceId for notification", () ->
+      expect(notify.replaceId).toEqual("jasmine")
+      return
+
+    it "should not close message by timeout", () ->
+      expect(cancelSpy).not.toHaveBeenCalled()
+      return
+
+    it "should close message by timeout if timeout not 0", () ->
+      instance.timeout = 1000
+      instance.showReport("icon.png", "title", "message")
+      expect(cancelSpy).toHaveBeenCalled()
+      return
+
+  return


[master] fix

[master] fix

--- utils/round-robin-dispatcher.coffee
+++ utils/round-robin-dispatcher.coffee
@@ -28,15 +28,18 @@
     lastScheduled = (@toSchedule - 1) %% @pairs.length
     check = (toSchedule) =>
       @pairs[toSchedule].bucket.get()
+    incr = =>
+      @toSchedule = (@toSchedule + 1) % @pairs.length
     while @toSchedule != lastScheduled and not check(@toSchedule)
-      @toSchedule = (@toSchedule + 1) % @pairs.length
+      incr()
     if @toSchedule == lastScheduled and not check(@toSchedule)
       @counter[@counter.length - 1] += 1
+      incr()
       @fallback
     else
       @counter[@toSchedule] += 1
+      incr()
       @pairs[@toSchedule].action
-    @toSchedule = (@toSchedule + 1) % @pairs.length

   status: ->
     s = _.sum @counter


Update package id for fixture with broken snippets

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -235,7 +235,7 @@

   describe "snippet loading", ->
     beforeEach ->
-      atom.loadPackage('package-with-broken-snippets-tmbundle', sync: true)
+      atom.loadPackage('package-with-broken-snippets.tmbundle', sync: true)
       atom.loadPackage('package-with-snippets')

       jasmine.unspy(window, "setTimeout")


oops - i forgot one console.log

oops - i forgot one console.log

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -1317,7 +1317,6 @@
         cm = @codemirror
         for x in cm.getAllMarks()
             t = $(x.replacedWith).find(selector)
-            console.log(t)
             if t.length > 0
                 cm.scrollIntoView(x.find().from)
                 return


team: login: show register link for the teams which allow @domains for registration

--- client/landing/site.landing/coffee/team/tabs/teamlogintab.coffee
+++ client/landing/site.landing/coffee/team/tabs/teamlogintab.coffee
@@ -55,6 +55,19 @@
       testPath    : 'landing-recover-password'
       href        : '/Recover'

+    @inviteDesc = new KDCustomHTMLView
+      tagName : 'p'
+      partial : "
To be able to login to #{KD.config.groupName}.koding.com, you need to be invited by team administrators.
"
+
+    domains = group.allowedDomains
+
+    return  unless domains
+
+    @inviteDesc.updatePartial if domains.length > 1
+      domainsPartial = ('' + d + ', ' for d in domains).join('').replace(/,\s$/, '.')
+      "Click here to register if you have an email address from one of the following domains: #{domainsPartial}"
+    else "Click here to register if you have an email address from #{domains.first}"
+

   pistachio: ->

@@ -66,12 +79,8 @@
       {{> @form}}

-

-      To be able to login to #{KD.config.groupName}.koding.com, you need to be invited by team administrators.
-

-

-      Trying to create a team? Sign up on the home page to get started.
-

+      {{> @inviteDesc}}
+
Trying to create a team? Sign up on the home page to get started.

       Acceptable user policyCopyright/DMCA guidelinesTerms of servicePrivacy policy


comments about optimistic loading

comments about optimistic loading

--- services/docstore/app/coffee/DocManager.coffee
+++ services/docstore/app/coffee/DocManager.coffee
@@ -65,6 +65,7 @@
 				callback(null, docs)

 	getAllNonDeletedDocs: (project_id, filter, callback = (error, docs) ->) ->
+		# load docs optimistically, only unarchive if results are incomplete
 		DocManager._getAllDocs project_id, filter, (error, docs) ->
 			return callback(error) if error?
 			# check if any docs have been archived
@@ -74,8 +75,10 @@
 					if error?
 						logger.err err:error, project_id:project_id, "error unarchiving docs"
 						return callback(error)
+					# now reload the docs after unarchiving
 					DocManager._getAllDocs project_id, filter, callback
 			else
+				# return docs immediately, nothing in s3
 				return callback(null, docs)

 	updateDoc: (project_id, doc_id, lines, version, ranges, callback = (error, modified, rev) ->) ->


Revert "rethinkdb: discover pool"

Revert "rethinkdb: discover pool"

This reverts commit 8b6e278e59f5f70f4b95e1075f5e99ab3c7331df.

--- services/rethinkdb.coffee
+++ services/rethinkdb.coffee
@@ -8,6 +8,5 @@
 r = require('rethinkdbdash')
   host: HOST
   db: DB
-  discovery: true

 module.exports = r


Make bracket context aware of addressable inports

--- src/lib/Component.coffee
+++ src/lib/Component.coffee
@@ -188,7 +188,7 @@
           return unless buf[0] is ip
           # Remove from buffer
           port.get ip.scope
-          context = @bracketContext[port.name][ip.scope].pop()
+          context = @getBracketContext(port.name, ip.scope, ip.index).pop()
           context.closeIp = ip
           debug "#{@nodeId} closeBracket-C #{ip.data} #{context.ports}"
           result =
@@ -212,7 +212,10 @@
       output.sendDone e

     unless input.activated
-      debug "#{@nodeId} #{ip.type} packet on #{port.name} didn't match preconditions"
+      if port.isAddressable()
+        debug "#{@nodeId} #{ip.type} packet on #{port.name}[#{ip.index}] didn't match preconditions"
+      else
+        debug "#{@nodeId} #{ip.type} packet on #{port.name} didn't match preconditions"
       return

     # Component fired
@@ -221,6 +224,15 @@
       @outputQ.push result
       do @processOutputQueue
     return
+
+  getBracketContext: (port, scope, idx) ->
+    portname = port
+    if @inPorts[port].isAddressable()
+      portname = "#{port}[#{idx}]"
+    # Ensure we have a bracket context for the current scope
+    @bracketContext[portname] = {} unless @bracketContext[portname]
+    @bracketContext[portname][scope] = [] unless @bracketContext[portname][scope]
+    return @bracketContext[portname][scope]

   addToResult: (result, port, ip, before = false) ->
     if port.indexOf('[') is -1
@@ -374,7 +386,10 @@
   # When preconditions are met, set component state to `activated`
   activate: ->
     return if @context.activated
-    debug "#{@nodeInstance.nodeId} #{@ip.type} packet on #{@port.name} caused activation #{@nodeInstance.load}"
+    if @port.isAddressable()
+      debug "#{@nodeInstance.nodeId} #{@ip.type} packet on #{@port.name}[#{@ip.index}] caused activation #{@nodeInstance.load}"
+    else
+      debug "#{@nodeInstance.nodeId} #{@ip.type} packet on #{@port.name} caused activation #{@nodeInstance.load}"
     if @nodeInstance.isOrdered()
       # We're handling packets in order. Set the result as non-resolved
       # so that it can be send when the order comes up
@@ -453,9 +468,6 @@
     if args.length is 1 then res[0] else res

   __getForForwarding: (port) ->
-    # Ensure we have a bracket context for the current scope
-    @nodeInstance.bracketContext[port] = {} unless @nodeInstance.bracketContext[port]
-    @nodeInstance.bracketContext[port][@scope] = [] unless @nodeInstance.bracketContext[port][@scope]
     prefix = []
     dataIp = null
     # Read IPs until we hit data
@@ -478,13 +490,13 @@
       if ip.type is 'closeBracket'
         # Bracket closings before data should remove bracket context
         @result.__bracketClosingBefore = [] unless @result.__bracketClosingBefore
-        context = @nodeInstance.bracketContext[port][@scope].pop()
+        context = @nodeInstance.getBracketContext(port, @scope, ip.index).pop()
         context.closeIp = ip
         @result.__bracketClosingBefore.push context
         continue
       if ip.type is 'openBracket'
         # Bracket openings need to go to bracket context
-        @nodeInstance.bracketContext[port][@scope].push
+        @nodeInstance.getBracketContext(port, @scope, ip.index).push
           ip: ip
           ports: []
           source: port
@@ -493,7 +505,7 @@
     # Add current bracket context to the result so that when we send
     # to ports we can also add the surrounding brackets
     @result.__bracketContext = {} unless @result.__bracketContext
-    @result.__bracketContext[port] = @nodeInstance.bracketContext[port][@scope].slice 0
+    @result.__bracketContext[port] = @nodeInstance.getBracketContext(port, @scope, dataIp.index).slice 0
     # Bracket closings that were in buffer after the data packet need to
     # be added to result for done() to read them from
     return dataIp


payment: product edit form should use decoded values from bongo

--- client/app/Applications/Dashboard.kdapplication/views/producteditform.coffee
+++ client/app/Applications/Dashboard.kdapplication/views/producteditform.coffee
@@ -1,6 +1,6 @@
 class GroupProductEditForm extends KDFormViewWithFields

-  constructor: (options = {}, data = {}) ->
+  constructor: (options = {}, data = new KD.remote.api.JPaymentProduct) ->

     model = data  if data.planCode

@@ -23,12 +23,12 @@
       title             :
         label           : "Title"
         placeholder     : options.placeholders?.title
-        defaultValue    : data.title
+        defaultValue    : data.decoded 'title'

       description       :
         label           : "Description"
         placeholder     : options.placeholders?.description or "(optional)"
-        defaultValue    : data.description
+        defaultValue    : data.decoded 'description'

       subscriptionType  :


discussion hyperlink problem

discussion hyperlink problem

--- common/static/coffee/src/discussion/views/discussion_thread_show_view.coffee
+++ common/static/coffee/src/discussion/views/discussion_thread_show_view.coffee
@@ -30,8 +30,8 @@
       @renderPinned()
       @renderAttrs()
       @$("span.timeago").timeago()
-      #@convertMath()
-      @$(".post-body").html(@$(".post-body").text())
+      @convertMath()
+      #@$(".post-body").html(@$(".post-body").text())
       @highlight @$(".post-body")
       @highlight @$("h1,h3")
       @$el.find('.username').attr('href','javascript:void(0);')
@@ -78,8 +78,9 @@

     convertMath: ->
       element = @$(".post-body")
+      element.html(element.text())
       element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.text()
-      MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
+      #MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]

     toggleVote: (event) ->
       event.preventDefault()