Remove no longer needed specs

Remove no longer needed specs

--- spec/suggestion-list-element-spec.coffee
+++ spec/suggestion-list-element-spec.coffee
@@ -68,13 +68,13 @@
       html = suggestionListElement.getDisplayHTML(text, snippet, replacementPrefix)
       expect(html).toBe('text(ab, cd)')

-    ffit 'replaces a snippet with escaped right braces', ->
+    it 'replaces a snippet with escaped right braces', ->
       text = ''
       snippet = 'abc(${1:d}, ${2:e})f ${3:interface{\\}}'
       replacementPrefix = 'a'
       expect(suggestionListElement.getDisplayHTML(text, snippet, replacementPrefix)).toBe('abc(d, e)f interface{}')

-    ffit 'replaces a snippet with escaped multiple right braces', ->
+    it 'replaces a snippet with escaped multiple right braces', ->
       text = ''
       snippet = 'abc(${1:d}, ${2:something{ok\\}}, ${3:e})f ${4:interface{\\}}'
       replacementPrefix = 'a'
@@ -91,12 +91,12 @@
       text = suggestionListElement.removeEmptySnippets(text)
       snippets = suggestionListElement.findSnippets(text)
       text = suggestionListElement.removeSnippetsFromText(snippets, text)
-      matches = suggestionListElement.findCharacterMatches(text, replacementPrefix)
+      matches = suggestionListElement.findCharacterMatcheIndices(text, replacementPrefix)
       for i in [0...text.length]
         if truthyIndices.indexOf(i) isnt -1
-          expect(matches["#{i}"]).toBeTruthy()
+          expect(matches?[i]).toBeTruthy()
         else
-          expect(matches["#{i}"]).toBeFalsy()
+          expect(matches?[i]).toBeFalsy()

     it 'finds matches when no snippets exist', ->
       assertMatches('hello', '', [])
@@ -117,127 +117,6 @@
       assertMatches('hello${0:world}', 'h', [0])
       assertMatches('hello${0:world}', 'hw', [0,5])
       assertMatches('hello${0:world}', 'hlw', [0,2,5])
-
-  describe 'findSnippets', ->
-    it 'has no results when no snippets exist', ->
-      expect(suggestionListElement.findSnippets('hello')).toEqual({})
-
-    it 'identifies a single snippet', ->
-      # Without escaped right brace
-      expect(suggestionListElement.findSnippets('${0:hello}')).toBeDefined()
-      expect(suggestionListElement.findSnippets('${0:hello}')['0']).toEqual('start')
-      expect(suggestionListElement.findSnippets('${0:hello}')['1']).toEqual('skip')
-      expect(suggestionListElement.findSnippets('${0:hello}')['2']).toEqual('skip')
-      expect(suggestionListElement.findSnippets('${0:hello}')['3']).toEqual('skip')
-      expect(suggestionListElement.findSnippets('${0:hello}')['4']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('${0:hello}')['5']).toBeUndefined()
-      expect(suggestionListElement.findSnippets('${0:hello}')['6']).toBeUndefined()
-      expect(suggestionListElement.findSnippets('${0:hello}')['7']).toBeUndefined()
-      expect(suggestionListElement.findSnippets('${0:hello}')['8']).toEqual('bodyend')
-      expect(suggestionListElement.findSnippets('${0:hello}')['9']).toEqual('end')
-
-      # With escaped right brace
-      expect(suggestionListElement.findSnippets('${0:hello{\\}}')).toBeDefined()
-      expect(suggestionListElement.findSnippets('${0:hello{\\}}')['0']).toEqual('start')
-      expect(suggestionListElement.findSnippets('${0:hello{\\}}')['12']).toEqual('end')
-      expect(suggestionListElement.findSnippets('${0:hello{\\}}')['4']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('${0:hello{\\}}')['11']).toEqual('bodyend')
-      expect(suggestionListElement.findSnippets('${0:hello{\\}}')['10']).toEqual('skip')
-
-    it 'identifies a snippet surrounded by text', ->
-      # Without escaped right brace
-      expect(suggestionListElement.findSnippets('hello${0:hello}')).toBeDefined()
-      expect(suggestionListElement.findSnippets('hello${0:hello}')['5']).toEqual('start')
-      expect(suggestionListElement.findSnippets('hello${0:hello}')['14']).toEqual('end')
-      expect(suggestionListElement.findSnippets('hello${0:hello}')['9']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('hello${0:hello}')['13']).toEqual('bodyend')
-      expect(suggestionListElement.findSnippets('${0:hello}hello')).toBeDefined()
-      expect(suggestionListElement.findSnippets('${0:hello}hello')['0']).toEqual('start')
-      expect(suggestionListElement.findSnippets('${0:hello}hello')['9']).toEqual('end')
-      expect(suggestionListElement.findSnippets('${0:hello}hello')['4']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('${0:hello}hello')['8']).toEqual('bodyend')
-      expect(suggestionListElement.findSnippets('hello${0:hello}hello')).toBeDefined()
-      expect(suggestionListElement.findSnippets('hello${0:hello}hello')['5']).toEqual('start')
-      expect(suggestionListElement.findSnippets('hello${0:hello}hello')['14']).toEqual('end')
-      expect(suggestionListElement.findSnippets('hello${0:hello}hello')['9']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('hello${0:hello}hello')['13']).toEqual('bodyend')
-
-      # With escaped right brace
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}')).toBeDefined()
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}')['5']).toEqual('start')
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}')['18']).toEqual('end')
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}')['9']).toEqual('bodystart')
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}')['17']).toEqual('bodyend')
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}')['15']).toEqual('skip')
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}')['16']).toEqual('skip')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}hello')).toBeDefined()
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}hello')['0']).toEqual('start')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}hello')['13']).toEqual('end')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}hello')['4']).toEqual('bodystart')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}hello')['12']).toEqual('bodyend')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}hello')['10']).toEqual('skip')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}hello')['11']).toEqual('skip')
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}hello')).toBeDefined()
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}hello')['5']).toEqual('start')
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}hello')['18']).toEqual('end')
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}hello')['9']).toEqual('bodystart')
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}hello')['17']).toEqual('bodyend')
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}hello')['15']).toEqual('skip')
-      # expect(suggestionListElement.findSnippets('hello${0:hello{\\}}hello')['16']).toEqual('skip')
-
-    it 'identifies multiple snippets', ->
-      # Without escaped right brace
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}')).toBeDefined()
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}')['0']).toEqual('start')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}')['9']).toEqual('end')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}')['4']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}')['8']).toEqual('bodyend')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}')['10']).toEqual('start')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}')['19']).toEqual('end')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}')['14']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}')['18']).toEqual('bodyend')
-
-      # With escaped right brace
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}${1:world}')).toBeDefined()
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}${1:world}')['0']).toEqual('start')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}${1:world}')['13']).toEqual('end')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}${1:world}')['4']).toEqual('bodystart')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}${1:world}')['12']).toEqual('bodyend')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}${1:world}')['10']).toEqual('skip')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}${1:world}')['11']).toEqual('skip')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}${1:world}')['14']).toEqual('start')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}${1:world}')['23']).toEqual('end')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}${1:world}')['18']).toEqual('bodystart')
-      # expect(suggestionListElement.findSnippets('${0:hello{\\}}${1:world}')['22']).toEqual('bodyend')
-
-    it 'identifies multiple snippets surrounded by text', ->
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}')).toBeDefined()
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}')['5']).toEqual('start')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}')['14']).toEqual('end')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}')['9']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}')['13']).toEqual('bodyend')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}')['15']).toEqual('start')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}')['24']).toEqual('end')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}')['19']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}')['23']).toEqual('bodyend')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}hello')).toBeDefined()
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}hello')['0']).toEqual('start')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}hello')['9']).toEqual('end')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}hello')['4']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}hello')['8']).toEqual('bodyend')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}hello')['10']).toEqual('start')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}hello')['19']).toEqual('end')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}hello')['14']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('${0:hello}${1:world}hello')['18']).toEqual('bodyend')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}hello')).toBeDefined()
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}hello')['5']).toEqual('start')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}hello')['14']).toEqual('end')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}hello')['9']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}hello')['13']).toEqual('bodyend')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}hello')['15']).toEqual('start')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}hello')['24']).toEqual('end')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}hello')['19']).toEqual('bodystart')
-      expect(suggestionListElement.findSnippets('hello${0:hello}${1:world}hello')['23']).toEqual('bodyend')

   describe 'removeEmptySnippets', ->
     it 'removes an empty snippet group', ->


Revert "Remove _.isEmpty."

Revert "Remove _.isEmpty."

This reverts commit 717314911ce6f33b114b00c68d5103289a5f5ccd.

--- src/chaplin/views/collection_view.coffee
+++ src/chaplin/views/collection_view.coffee
@@ -245,7 +245,7 @@
     filterCallback ?= @filterCallback

     # Show/hide existing views.
-    if _.keys(@getItemViews()).length
+    unless _.isEmpty @getItemViews()
       for item, index in @collection.models

         # Apply filter to the item.


fix format

fix format

--- core/model/account.coffee
+++ core/model/account.coffee
@@ -128,11 +128,11 @@
           created_at: new Date()
           updated_at: new Date()
           attribute: attribute
-    , (err) ->
+    , ->
       callback null, token

 exports.removeToken = (token, callback) ->
-  exports.update 'tokens.token': token,
+  exports.update {'tokens.token': token},
     $pull:
       tokens:
         token: token


[misc] config: add headroom for JSON serialization in maxUpdateSize

--- services/real-time/config/settings.defaults.coffee
+++ services/real-time/config/settings.defaults.coffee
@@ -54,8 +54,9 @@

 	# combine
 	# max_doc_length (2mb see above) * 2 (delete + insert)
-	# max_ranges_size (3mb see MAX_RANGES_SIZE in doc-updater)
-	maxUpdateSize: parseInt(process.env['MAX_UPDATE_SIZE']) or 7 * 1024 * 1024
+	# max_ranges_size (3mb see MAX_RANGES_SIZE in document-updater)
+	# overhead for JSON serialization
+	maxUpdateSize: parseInt(process.env['MAX_UPDATE_SIZE']) or 7 * 1024 * 1024 + 64 * 1024

 	shutdownDrainTimeWindow: process.env['SHUTDOWN_DRAIN_TIME_WINDOW'] or 9


remove duplicate call on request page refresh

--- SingularityUI/app/controllers/RequestDetail.coffee
+++ SingularityUI/app/controllers/RequestDetail.coffee
@@ -85,7 +85,7 @@
         #
         @models.request.on 'sync', =>
             activeDeploy = @models.request.get 'activeDeploy'
-            if activeDeploy?.id?
+            if activeDeploy?.id? and not @models.activeDeployStats.deployId
                 @models.activeDeployStats.deployId = activeDeploy.id
                 @models.activeDeployStats.fetch()


encode and decode object

encode and decode object

--- server/utils/jsonglue.coffee
+++ server/utils/jsonglue.coffee
@@ -1,16 +1,80 @@
-
+_ = window?._ or require 'lodash'
+{isArray, isPlainObject, isDate} = _
 ###
   number
   string
   date
   bool
-
+  blob
 ###

+get_parent_and_obj_for = (obj, path, create = no)->
+  parent = null
+  key = null
+  if path
+    for next_key in path.split('.')
+      next_key = parseInt(next_key, 10) if isArray obj
+      parent = obj
+      key = next_key
+      if key and (typeof obj[key] is 'undefined')
+        if create
+          obj[key] = {}
+        else
+          return [null, null, null]
+      obj = obj[next_key]
+  [parent, key, obj]

-class JSONGlue
+
+get = (obj, path) ->
+  #[parent, key, value] = get_parent_and_obj_for(obj, path, no)
+  #value
+  get_parent_and_obj_for(obj, path, no)[2]
+
+
+set = (obj, path, value) ->
+  [parent, key] = get_parent_and_obj_for(obj, path, yes)
+  if parent
+    parent[key] = value
+  #else
+  #  obj = value
+
+
+getType = (obj)->
+  if isDate obj
+    'date'
+
+JSONGlue =
+  encode: (obj)->
+    paths = {}
+    walk = (curPath, curObj)->
+      if isPlainObject(curObj) or isArray(curObj)
+        _.each curObj, (newObj, key)->
+          walk "#{curPath}.#{key}", newObj
+      else
+        if valueType = getType curObj
+          paths[curPath.substr(1)] = valueType
+
+    walk '', obj
+
+    {
+      value: obj
+      schema: paths
+    }
+
+  decode: (obj)->
+    value = obj.value
+    if obj.schema
+      _.each obj.schema, (type, path)->
+        if type is 'date'
+          v = get value, path
+          set value, path, new Date(v)
+    value
+
+
+  encodeCollection: ->
+  decodeCollection: ->

-module.exports = JSONGlue+#module.exports = JSONGlue


Catching errors when work weeks are filler objs

(i.e. when you switch weeks for example and there's no data to support
it)


Change Player to Character

Change Player to Character

--- game.coffee
+++ game.coffee
@@ -130,7 +130,7 @@

 # ### Game entities

-class Player
+class Character
   constructor: ->
     @x = GRID*7
     @y = GRID*5
@@ -139,7 +139,7 @@
     @direction = 'down'
     @movementFactor = 2

-  # `Player.update()` is called by the higher level `update()` function, as
+  # `Character.update()` is called by the higher level `update()` function, as
   # we'll see below, which is turn called by `mainLoop()`.
   update: ->

@@ -168,8 +168,8 @@
     @x = @x.clamp(0, CANVAS_WIDTH - GRID)
     @y = @y.clamp(0, CANVAS_HEIGHT - GRID)

-  # `Player.draw()` is called every `canvas` update cycle, just like
-  # `Player.update()`. This is what draws and animates the player sprite.
+  # `Character.draw()` is called every `canvas` update cycle, just like
+  # `Character.update()`. This is what draws and animates the player sprite.
   draw: ->

     # `getImageX()` is a function (defined below) that returns the x-coordinate
@@ -180,9 +180,9 @@
     # sheet to use (`@image`), the x-coordinate to draw (`@imageX`).
     context.drawImage(@image, @imageX, 0, 32, 32, @x, @y, 32, 32)

-  # #### Player helpers
-
-  # `Player.getImageX()` used by `Player.draw()`.  It fetches the player's
+  # #### Character helpers
+
+  # `Character.getImageX()` used by `Character.draw()`.  It fetches the player's
   # direction, and then determines if the player is in movement by using moduli.
   # If the player _is_ in movement, it uses additional calculation to decide
   # which section of the player's image to draw.
@@ -216,8 +216,8 @@
           96

-# Create an instance of the `Player` class.
-player = new Player
+# Create an instance of the `Character` class.
+player = new Character

 # ### High-level game methods


Set game dimensions to canvas dimensions

--- game.coffee
+++ game.coffee
@@ -23,8 +23,8 @@
     @EAST = 2
     @WEST = 3

-    @WIDTH:  200
-    @HEIGHT:  300
+    @WIDTH:  window.canvas.width
+    @HEIGHT:  window.canvas.height

     @VELOCITY = 100


<debug> Forgottent next param.

<debug> Forgottent next param.

--- server/controllers/driver.coffee
+++ server/controllers/driver.coffee
@@ -36,7 +36,7 @@
 module.exports.read = (req, res) ->
 	res.send req.driver

-module.exports.create = (req, res) ->
+module.exports.create = (req, res, next) ->

 		# Parse form:
 		form = new multiparty.Form
@@ -56,8 +56,10 @@
 			unless file = files['file']?[0]
 				res.send error: 'No file sent', 400
 				return cleanUp()
+
+			data =
+				path : file

-			data = path : file
 			Driver.create data, (err, driver) ->
 				if err?
 					if err == 'Driver already added'


Fixed: missing dependency

Fixed: missing dependency

--- server/lib/weboob-manager.coffee
+++ server/lib/weboob-manager.coffee
@@ -1,4 +1,5 @@
 util = require 'util'
+moment = require 'moment'
 Client = require('request-json').JsonClient
 async = require 'async'
 BankOperation = require '../models/bankoperation'


[DOC] Add documentation src/ws/websocket.coffee

--- src/ws/websocket.coffee
+++ src/ws/websocket.coffee
@@ -37,6 +37,7 @@
             @rooms      = {}
             @token      = Token

+        # Send informations about the new peer to all the peers connected in the room.
         notifyNewPeer     : (socket) =>
             clientsInfos    = []
             sockets         = @rooms[socket.rid]
@@ -48,6 +49,8 @@

             @socketOnSend socket, ["peers", clientsInfos]

+        # Check in database if the informations sent by the new peer are valid.
+        # If yes the peer is push in the room.
         acceptPeer        : (param) =>
             cid               = generateRandomId()
             socket            = param.socket
@@ -68,6 +71,7 @@
             @socketOnSend socket, ["peer.authentification", socket.cinfo]
             console.log "New client has been accepted as #{cid} in room #{param.rid}"

+        # Check if the clientID sent by the new peer is valid.
         clientValidation  : (param) =>
             Request.get "#{Config.Restapi.Url}/user/#{param.cid}", (e, r, data) =>
                 if e? or r.statusCode > 200
@@ -81,6 +85,7 @@
                         param.cinfo  = client
                         @acceptPeer param

+        # Check if the roomID sent by the new peer is valid.
         roomValidation    : (param) =>
             Request.get "#{Config.Restapi.Url}/room/#{param.rid}", (e, r, data) =>
                 if e? or r.statusCode > 200
@@ -88,6 +93,8 @@
                 else
                     @clientValidation param

+        # Check if the token sent by the new peer is valid.
+        # If yes, we get the roomID and clientID in the database to authentificate the new peer.
         tokenValidation   : (param) =>
             Request.get "#{Config.Restapi.Url}/token/#{param.token}", (e, r, data) =>
                 if e? or r.statusCode > 200
@@ -101,6 +108,7 @@
                     else
                       param.errorCallback param, "Invalid room id"

+        # Remove a peer from the list.
         peerRemove        : (socket) =>
             sockets  = @rooms[socket.rid]

@@ -109,6 +117,7 @@

                 @socketOnSend sock, ["peer.remove", socket.cinfo]

+        # Send a packet to a peer.
         socketOnSend      : (socket, msg) =>
             msg = JSON.stringify(msg)

@@ -116,6 +125,8 @@
                 if (error)
                     console.error error

+        # Function called when a message is received by a peer.
+        # If the peer is not authentificated, it'll call the tokenvalidation function.
         socketOnMessage   : (socket, message) =>
             if not message.data
                 return
@@ -148,6 +159,8 @@

                 @tokenValidation(param)

+        # Called when a socket has been closed and remove the peer from the list if
+        # he's authentificated.
         socketOnClose     : (socket) =>
             if socket.enable == true and @rooms[socket.rid]?
                 console.log "Client #{socket.cinfo.cid} has been disconnected from room #{socket.rid}"
@@ -155,6 +168,7 @@
                 @peerRemove socket
             @sockets.splice @sockets.indexOf(socket), 1

+        # Called when a new peer has arrived.
         serverOnConnection   : (socket) =>
             socket.cid      = -1
             socket.rid      = -1
@@ -167,6 +181,7 @@

             @sockets.push socket

+        # Start the websocket server services.
         start       : () =>
             server      = Http.createServer((req, res) ->).listen Config.Websocket.Port, () =>
                 console.log "Server ready on port #{Config.Websocket.Port}"


Log all RPC errors when not in production

--- server/packages/rpc/index.coffee
+++ server/packages/rpc/index.coffee
@@ -5,6 +5,7 @@
 # [RPC](http://en.wikipedia.org/wiki/Remote_procedure_call#Message_passing)

 _ = require 'underscore'
+nconf = require 'nconf'
 Promise = require 'bluebird'

 audit = require 'audit'
@@ -114,7 +115,14 @@
 						logError error

 					# Transmit the error as it is directly to the client.
-					sendErrorToClient = (error) -> emitError error
+					sendErrorToClient = (error) ->
+						emitError error
+
+						# If we're not running in production, log the full
+						# error stack, because it might help track down any
+						# problem.
+						if 'production' isnt nconf.get 'NODE_ENV'
+							logError error

 					# Validate.
 					endpoint.validators.dispatch routeReq, null, (error) ->


add rollbar job post

add rollbar job post

--- models/job.coffee
+++ models/job.coffee
@@ -102,6 +102,53 @@

     '''
+,
+  slug: "rollbar"
+  image: "prizes/rollbar.jpg"
+  description:
+    '''
+    **[Rollbar](https://rollbar.recruiterbox.com/jobs/fk0h8ho/)**
+
+    **Front-End Engineer**
+
+    We're a scrappy, distributed team (SF, Fort Worth, Syracuse, Barcelona) building tools that make developers' lives better. We're looking for our seventh employee, sixth engineer, and first front-end specialist.
+
+    About us:
+
+    * We help thousands of developers find and fix bugs quickly
+    * Our backend handles billions of errors with low latency and high reliability
+    * Our front-end allows developers to discover and drill down across millions of errors in real-time
+    * Our open source libraries are used by some of the best engineering teams in the world, including Twilio, Heroku, Instacart and Twitch.
+
+    Our tech stack:
+
+    * React, Webpack, Grunt, Engine.IO, Sass
+    * Python, Node.js
+    * GitHub, CircleCI, Rollbar
+
+    You will:
+
+    * Work with PMs, designers, and other developers to ship front-end features
+    * Own Rollbar's front-end architecture and development
+    * Have fun while releasing software that impacts millions of end-users
+
+    You are:
+
+    * A Javascript expert with experience building crisp, quick UIs
+    * Up on the latest trends, like server-side rendering, responsive design and module bundling
+    * Proud of the products you build and the impact they have for our customers
+
+    Bonus points:
+
+    * Experience using React and Webpack to build single-page apps
+    * Experience maintaining open source projects
+    * UI design chops
+    * Desire to grow into a Team Lead role
+
+    [Apply now](https://rollbar.recruiterbox.com/jobs/fk0h8ho/)
+
+    '''
+
 # ,
 #   slug: "paypal"
 #   image: 'prizes/paypal.jpg'


Update paper-elements.cson

Update paper-elements.cson

--- snippets/paper-elements.cson
+++ snippets/paper-elements.cson
@@ -76,9 +76,9 @@
     "prefix": "paper-radio-group"
     "body": """
     <paper-radio-group selected="${1:small}">
-      <paper-radio-button name="${2:small}" label="${3:Small}"></paper-radio-button>
-      <paper-radio-button name="${4:medium}" label="${5:Medium}"></paper-radio-button>
-      <paper-radio-button name="${6:large}" label="${7:Large}"></paper-radio-button>
+      <paper-radio-button name="${2:small}">${3:Small}</paper-radio-button>
+      <paper-radio-button name="${4:medium}">${5:Medium}</paper-radio-button>
+      <paper-radio-button name="${6:large}">${7:Large}</paper-radio-button>
     </paper-radio-group>
     """
   "paper ripple":


mobile

mobile

--- source/javascripts/pgtune.coffee
+++ source/javascripts/pgtune.coffee
@@ -2,6 +2,10 @@
   constructor: (@form, @codeOut, @oldPgkernel) ->
     # submit form with data
     @form.submit @_generateConfigForm
+    # on tab submit form ("Next" button on mobile keyboard)
+    if Modernizr.touch
+      $('#pgtTotalMemValue').on 'keydown', @_mobileNextButton
+      $('#pgtConnectionsValue').on 'keydown', @_mobileNextButton
     # appcache
     @_initAppcache()
     # init foundation
@@ -27,6 +31,10 @@
   _generateConfigForm: (event) =>
     event.preventDefault()
     @_generateConfig()
+
+  # mobile next button
+  _mobileNextButton: (e) =>
+    @form.submit() if e.which? and e.which is 9

   # generate config
   _generateConfig: =>


Client: Sidebar: Deleted unnecessary lines

--- client/app/lib/components/sidebarstacksection/index.coffee
+++ client/app/lib/components/sidebarstacksection/index.coffee
@@ -11,11 +11,6 @@
   @defaultProps   =
     selectedId    : null
     stack         : immutable.Map()
-
-
-  componentDidMount: ->
-
-    @props.onStackRendered()

   renderMachines: ->


sidebarstacksection: member will edit their own stack

--- client/app/lib/components/sidebarstacksection/index.coffee
+++ client/app/lib/components/sidebarstacksection/index.coffee
@@ -113,7 +113,8 @@
     if managedVM
       menuItems['VMs'] = { callback }
     else
-      menuItems['Edit'] = { callback }  if isAdmin()
+      if isAdmin() or @props.stack.get('accessLevel') is 'private'
+        menuItems['Edit'] = { callback }
       ['Reinitialize', 'VMs', 'Destroy VMs'].forEach (name) ->
         menuItems[name] = { callback }


Use shared methods for same actions

Use shared methods for same actions

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -480,6 +480,12 @@
                  you can do that by clicking '<b>Create Default VM</b>' button
                  below."""

+    _runAppAfterStateChanged = (appName)=>
+      return  unless appName
+      @once 'StateChanged', ->
+        KD.utils.wait 2000, ->
+          KD.getSingleton("appManager").open appName
+
     modal = new KDModalView
       title          : if @defaultVmName then "Your VM is turned off" \
                                          else "You don't have any VM"
@@ -490,22 +496,16 @@
         'Turn ON VM' :
           style      : "modal-clean-green"
           callback   : =>
-            @start =>
-              modal.destroy()
-              if appName
-                @once 'StateChanged', ->
-                  KD.getSingleton("appManager").open appName
+            _runAppAfterStateChanged appName
+            @start -> modal.destroy()
         'Create Default VM' :
           style      : "modal-clean-green"
           callback   : =>
+            _runAppAfterStateChanged appName
             @createNewVM()
-            if appName
-              @once 'StateChanged', ->
-                KD.utils.wait 2000, =>
-                  KD.getSingleton("appManager").open appName
             modal.destroy()
         Cancel       :
-          style      : "modal-clean-gray"
+          style      : "modal-cancel"
           callback   : ->
             modal.destroy()
             callback?()


Updated share link to nonalpha

Updated share link to nonalpha


calendar training slot redirect to fetch of training

--- app/assets/javascripts/controllers/calendar.coffee
+++ app/assets/javascripts/controllers/calendar.coffee
@@ -64,6 +64,7 @@
       if event.available_type == 'event'
         $state.go('app.public.events_show', {id: event.event_id})
       else if event.available_type == 'training'
+        $state.go('app.public.training_show', {id: event.training_id})
       else
         $state.go('app.public.machines_show', {id: event.machine_id})


Added setInterested when interacting

Added setInterested when interacting

--- core/app/backbone/views/comments/react_add_anecdote.coffee
+++ core/app/backbone/views/comments/react_add_anecdote.coffee
@@ -43,3 +43,4 @@

     @model().unshift(comment)
     comment.saveWithFactAndWithState()
+    @model().fact.getOpinionators().setInterested true


- Use name of view class instead of requiring it to decouple courses WAD from play WAD.

--- app/views/play/level/modal/HeroVictoryModal.coffee
+++ app/views/play/level/modal/HeroVictoryModal.coffee
@@ -442,19 +442,19 @@
     if @showHoc2016ExploreButton
       # Send players to /play after completing final game-dev activity project level
       nextLevelLink = '/play'
-      viewClass = require 'views/play/CampaignView'
+      viewClass = 'views/play/CampaignView'
       viewArgs = [options]
     else if @level.isType('course') and @nextLevel and not options.returnToCourse
-      viewClass = require 'views/play/level/PlayLevelView'
+      viewClass = 'views/play/level/PlayLevelView'
       options.courseID = @courseID
       options.courseInstanceID = @courseInstanceID
       viewArgs = [options, @nextLevel.get('slug')]
     else if @level.isType('course')
       # TODO: shouldn't set viewClass and route in different places
-      viewClass = require 'views/courses/CoursesView'
+      viewClass = 'views/courses/CoursesView'
       viewArgs = [options]
       if @courseID
-        viewClass = require 'views/courses/CourseDetailsView'
+        viewClass = 'views/courses/CourseDetailsView'
         viewArgs.push @courseID
         viewArgs.push @courseInstanceID if @courseInstanceID
     else if @level.isType('course-ladder')
@@ -467,7 +467,7 @@
     else
       if @level.get('slug') in campaignEndLevels
         options.worldComplete = @level.get('campaign') or true
-      viewClass = require 'views/play/CampaignView'
+      viewClass = 'views/play/CampaignView'
       viewArgs = [options, @getNextLevelCampaign()]
     navigationEvent = route: nextLevelLink, viewClass: viewClass, viewArgs: viewArgs
     if @level.get('slug') is 'lost-viking' and not (me.get('age') in ['0-13', '14-17'])


Spec for cursor placement during the buffer's onDidChange event

--- spec/bracket-matcher-spec.coffee
+++ spec/bracket-matcher-spec.coffee
@@ -802,6 +802,18 @@
         editor.insertText '<'
         expect(buffer.lineForRow(0)).toBe "<>"
         expect(editor.getCursorBufferPosition()).toEqual([0, 1])
+
+      it "emits a buffer change event after the cursor is in place", ->
+        atom.config.set 'bracket-matcher.autocompleteCharacters', ['<>'], scopeSelector: '.source.js'
+
+        lastPosition = null
+        sub = editor.getBuffer().onDidChange ->
+          expect(lastPosition).toBeNull()
+          lastPosition = editor.getLastCursor().getBufferPosition()
+
+        editor.setCursorBufferPosition([0, 0])
+        editor.insertText '<'
+        expect(lastPosition).toEqual([0, 1])

     describe "when there are multiple cursors", ->
       it "inserts ) at each cursor", ->


Remove unused test script

Remove unused test script

--- nudity.coffee
+++ nudity.coffee
@@ -1,7 +0,0 @@
-SightengineClient = require ('nudity-filter')
-
-Sightengine = new SightengineClient(process.env.SIGHTENGINE_USER, process.env.SIGHTENGINE_SECRET)
-console.log "About to hit internet", Sightengine
-Sightengine.checkNudityForFile "./laura.jpg", (error, result) ->
-  if error? then console.dir(error)
-  else console.log(result)


correct location for gridplot toolbars

correct location for gridplot toolbars

--- bokehjs/src/coffee/models/layouts/grid_plot.coffee
+++ bokehjs/src/coffee/models/layouts/grid_plot.coffee
@@ -253,6 +253,7 @@
       @tm_view = new ToolManager.View({
         model: @mget('tool_manager')
         el: @$(toolbar_selector)
+        location: toolbar_location
       })
       @tm_view.render()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -1,5 +1,5 @@
 ###*
-The Bounded module is used to provide basic data about the
+The ActiveBounds module is used to provide basic data about the
 location and dimensions of the including object. This module is included
 by default in GameObject.


:memo: compatible with v1.17.0 (Context)

--- def/context.coffee
+++ def/context.coffee
@@ -28,11 +28,32 @@
     "encoding-selector:show": "エンコーディング選択"
     "spell-check:correct-misspelling": "スペル修正"
     "symbols-view:go-to-declaration": "宣言に移動"
+  ".tree-view li.directory":
+    "project-find:show-in-current-directory": "ディレクトリ内を検索"
+  ".path-details.list-item":
+    "find-and-replace:copy-path": "Copy Path"
   ".overlayer":
     "autocomplete:toggle": "オートコンプリート"
     "grammar-selector:show": "文法を選択"
   ".image-view":
     "image-view:reload": "画像をリロード"
+  ".markdown-preview":
+    "core:copy": "HTMLをコピー"
+    "core:save-as": "HTMLを保存..."
+  ".tree-view .file .name[data-name$=\\.markdown]":
+    "markdown-preview:preview-file": "Markdownプレビュー"
+  ".tree-view .file .name[data-name$=\\.md]":
+    "markdown-preview:preview-file": "Markdownプレビュー"
+  ".tree-view .file .name[data-name$=\\.mdown]":
+    "markdown-preview:preview-file": "Markdownプレビュー"
+  ".tree-view .file .name[data-name$=\\.mkd]":
+    "markdown-preview:preview-file": "Markdownプレビュー"
+  ".tree-view .file .name[data-name$=\\.mkdown]":
+    "markdown-preview:preview-file": "Markdownプレビュー"
+  ".tree-view .file .name[data-name$=\\.ron]":
+    "markdown-preview:preview-file": "Markdownプレビュー"
+  ".tree-view .file .name[data-name$=\\.txt]":
+    "markdown-preview:preview-file": "Markdownプレビュー"
   ".tab":
     "tabs:close-tab": "タブを閉じる"
     "tabs:close-other-tabs": "他のタブをすべて閉じる"
@@ -50,7 +71,7 @@
     "tabs:keep-pending-tab": "プレビュー状態を解除"
   ".tab-bar":
     "pane:reopen-closed-item": "閉じたタブを開く"
-  ".tree-view.full-menu":
+  ".tree-view .full-menu":
     "tree-view:add-file": "新規ファイル"
     "tree-view:add-folder": "新規フォルダ"
     "tree-view:move": "移動・名前を変更..."
@@ -59,20 +80,16 @@
     "tree-view:copy": "コピー"
     "tree-view:cut": "カット"
     "tree-view:paste": "ペースト"
+    "application:add-project-folder": "プロジェクトフォルダを追加..."
+    "tree-view:copy-full-path": "フルパスをコピー"
+    "tree-view:copy-project-path": "プロジェクトパスをコピー"
+    "tree-view:open-in-new-window": "新規ウインドウで開く"
+  '.tree-view .full-menu [is="tree-view-file"]':
     "tree-view:open-selected-entry-up": "ペイン分割 ↑"
     "tree-view:open-selected-entry-down": "ペイン分割 ↓"
     "tree-view:open-selected-entry-left": "ペイン分割 ←"
     "tree-view:open-selected-entry-right": "ペイン分割 →"
-    "application:add-project-folder": "プロジェクトフォルダを追加..."
-    "tree-view:copy-full-path": "フルパスをコピー"
-    "tree-view:copy-project-path": "プロジェクトパスをコピー"
-    "tree-view:open-in-new-window": "新規ウインドウで開く"
-  '.tree-view.full-menu [is="tree-view-file"]':
-    "tree-view:open-selected-entry-up": "ペイン分割 ↑"
-    "tree-view:open-selected-entry-down": "ペイン分割 ↓"
-    "tree-view:open-selected-entry-left": "ペイン分割 ←"
-    "tree-view:open-selected-entry-right": "ペイン分割 →"
-  ".tree-view.full-menu .project-root > .header":
+  ".tree-view .full-menu .project-root > .header":
     "tree-view:add-file": "新規ファイル"
     "tree-view:add-folder": "新規フォルダ"
     "tree-view:move": "移動・名前を変更..."
@@ -81,23 +98,17 @@
     "tree-view:copy": "コピー"
     "tree-view:cut": "カット"
     "tree-view:paste": "ペースト"
-    "tree-view:open-selected-entry-up": "ペイン分割 ↑"
-    "tree-view:open-selected-entry-down": "ペイン分割 ↓"
-    "tree-view:open-selected-entry-left": "ペイン分割 ←"
-    "tree-view:open-selected-entry-right": "ペイン分割 →"
     "application:add-project-folder": "プロジェクトフォルダを追加..."
     "tree-view:remove-project-folder": "プロジェクトフォルダを除去"
     "tree-view:copy-full-path": "フルパスをコピー"
     "tree-view:copy-project-path": "プロジェクトパスをコピー"
     "tree-view:open-in-new-window": "新規ウインドウで開く"
-  ".platform-darwin .tree-view.full-menu":
+  ".platform-darwin .tree-view .full-menu":
     "tree-view:show-in-file-manager": "Finder で表示"
-  ".platform-win32 .tree-view.full-menu":
+  ".platform-win32 .tree-view .full-menu":
     "tree-view:show-in-file-manager": "エクスプローラで表示"
-  ".platform-linux .tree-view.full-menu":
+  ".platform-linux .tree-view .full-menu":
     "tree-view:show-in-file-manager": "ファイルマネージャで表示"
-  ".tree-view > li.directory":
-    "project-find:show-in-current-directory": "ディレクトリ内を検索"
   ".tree-view.multi-select":
     "tree-view:remove": "削除"
     "tree-view:copy": "コピー"


Updated unit tests

Updated unit tests

--- specs/mongoose-store.spec.coffee
+++ specs/mongoose-store.spec.coffee
@@ -11,9 +11,9 @@
     sid: '12ab34cd56ef'
     session:
       cookie:
-        originalMaxAge: new Date().getTime() + 2000
-        maxAge: new Date().getTime() + 2000
-        httpOnly: true
+        expires  : new Date().getTime() + 2000
+        maxAge   : 2000
+        httpOnly : true
       name: 'mongooseStore'


Individual view now renders different senators when passed a parameter.


MachineSettingsModal: if UsageLimitReached error occurs show custom modal

[completes #78425784]

--- client/Main/machinesettingsmodal.coffee
+++ client/Main/machinesettingsmodal.coffee
@@ -82,10 +82,26 @@
               name          : 'alwaysOn'
               itemClass     : KodingSwitch
               defaultValue  : data.alwaysOn
-              callback      : (state) ->
-                alwaysOnToggle = this
-                computeController.setAlwaysOn data, state, (err)->
-                  if KD.showError err then alwaysOnToggle.setOff no
+              callback      : (state) =>
+
+                { alwaysOn } = @modalTabs.forms.Settings.inputs
+                plan = computeController.getUserPlan()
+
+                computeController.setAlwaysOn data, state, (err)=>
+
+                  if err?
+
+                    if err.name is "UsageLimitReached" and plan isnt 'hobbyist'
+
+                      @destroy()
+                      KD.utils.defer => new ComputeErrorModal.Usage { plan }
+
+                    else
+
+                      KD.showError err
+
+                    alwaysOn.setOff no
+
             guides          :
               label         : 'Guides'
               itemClass     : GuidesLinksView


activity: allfollowed channels getter added

--- client/activity/lib/flux/getters.coffee
+++ client/activity/lib/flux/getters.coffee
@@ -156,6 +156,12 @@
     ids.map (id) -> channels.get id
 ]

+allFollowedChannels = [
+  followedPublicChannels
+  followedPrivateChannels
+  (publics, privates) -> publics.concat privates
+]
+
 # Returns the selected thread mapped with selected channel instance.
 selectedChannelThread = [
   channelThreads
@@ -417,5 +423,7 @@
   sidebarPublicChannelsQuery
   sidebarPublicChannelsTab
   sidebarPublicChannels
+
+  allFollowedChannels
 }


Client: Activity: Feed: Defined selectedChannelPopularMessages before selectedChannelThread and set popular messages as messages to thread by showPopularMessagesFlag

--- client/activity/lib/flux/getters.coffee
+++ client/activity/lib/flux/getters.coffee
@@ -39,6 +39,8 @@
 SidebarPublicChannelsQueryStore = ['SidebarPublicChannelsQueryStore']
 SidebarPublicChannelsTabStore   = ['SidebarPublicChannelsTabStore']

+ShowPopularMessagesFlagStore = ['ShowPopularMessagesFlagStore']
+
 FollowedPublicChannelIdsStore = [
   FollowedPublicChannelIdsStore
   (ids) ->
@@ -190,15 +192,29 @@
   (publics, privates) -> publics.concat privates
 ]

+selectedChannelPopularMessages = [
+  channelPopularMessages
+  selectedChannelThreadId
+  (messages, id) -> messages.get id
+]
+
 # Returns the selected thread mapped with selected channel instance.
 selectedChannelThread = [
   channelThreads
   selectedChannel
   MessageLikersStore
+  selectedChannelPopularMessages
+  ShowPopularMessagesFlagStore
   allUsers
-  (threads, channel, likers, users) ->
+  (threads, channel, likers, popularMessages, showPopularMessagesFlag, users) ->
     return null  unless channel
     thread = threads.get channel.get('id')
+    thread = thread.set 'channel', channel
+
+    if showPopularMessagesFlag
+      popularMessages = popularMessages or immutable.Map()
+      thread = thread.set 'messages', popularMessages
+
     thread = thread.update 'messages', (messages) ->
       messages.map (msg) ->
         msg.update 'body', (body) ->
@@ -213,7 +229,7 @@
               .set 'actorsCount', messageLikers.size
               .set 'isInteracted', messageLikers.contains whoami()._id

-    return thread.set 'channel', channel
+    return thread
 ]

 channelByName = (name, types = ['topic', 'group', 'announcement']) ->
@@ -267,12 +283,6 @@
     channels.map (channel) ->
       thread = threads.get channel.get('id')
       return thread.set 'channel', channel
-]
-
-selectedChannelPopularMessages = [
-  channelPopularMessages
-  selectedChannelThreadId
-  (messages, id) -> messages.get id
 ]

 selectedMessageThreadId = SelectedMessageThreadIdStore
@@ -460,5 +470,6 @@
   sidebarPublicChannels

   allFollowedChannels
+  showPopularMessagesFlag : ShowPopularMessagesFlagStore
 }


Disable nickname tooltip

Disable nickname tooltip

--- client/app/CommonViews/LinkViews.coffee
+++ client/app/CommonViews/LinkViews.coffee
@@ -50,31 +50,31 @@

   constructor:(options = {}, data)->

-    options.tooltip =
-      gravity   : "s"
-      delayIn   : 120
-      offset    : 1
-
-    super options, data
-
-    nickname = data?.profile?.nickname
-    @$().attr "href","/#!/member/#{nickname}" if nickname
+    # options.tooltip =
+    #   gravity   : "s"
+    #   delayIn   : 120
+    #   offset    : 1
+
+    super options, data
+
+    # nickname = data?.profile?.nickname
+    # @$().attr "href","/#!/member/#{nickname}" if nickname
     @setClass "profile"

-  render:->
-
-    nickname = @getData().profile?.nickname
-    if nickname
-      @$().attr "href","/#!/member/#{nickname}"
-      @updateTooltip title : "@#{nickname}"
-    super
+  # render:->
+
+  #   nickname = @getData().profile?.nickname
+  #   if nickname
+  #     @$().attr "href","/#!/member/#{nickname}"
+  #     @updateTooltip title : "@#{nickname}"
+  #   super

   pistachio:->

     super "{{#(profile.firstName)+' '+#(profile.lastName)}}"

   click:(event)->
-
+
     appManager.tell "Members", "createContentDisplay", @getData()
     event.preventDefault()
     event.stopPropagation()


Fixed issue with commit time not using timeago

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -174,7 +174,7 @@
   $('.trigger-submit').on 'change', ->
     $(@).parents('form').submit()

-  gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), false)
+  gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true)

   # Flash
   if (flash = $(".flash-container")).length > 0


close subnav when modal opens

close subnav when modal opens

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -60,6 +60,7 @@
     analytics.trackLink($('#logout-link'), "Sign out")

   $('a.open-modal').click ->
+    Curri.SubNav.close()
     $('.error-message').remove()
     $(this).modal(fadeDuration: 250)
     return false


Add askToTurnOn helper method to turn VM on globally

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -37,3 +37,30 @@
     return (rest...)=>
       @info callback? rest...

+  askToTurnOn:(appName='', callback)->
+
+    content = """To #{if appName then 'run' else 'do this'} #{appName}
+                 you need to turn on your VM first, you can do that by
+                 clicking 'Turn ON VM' button below."""
+
+    modal = new KDModalView
+      title          : "Your VM is turned off"
+      content        : "

#{content}

"
+      height         : "auto"
+      overlay        : yes
+      buttons        :
+        'Turn ON VM' :
+          style      : "modal-clean-green"
+          callback   : =>
+            @start =>
+              modal.destroy()
+              if appName
+                @once 'StateChanged', ->
+                  appManager.open appName
+        Cancel       :
+          style      : "modal-clean-gray"
+          callback   : ->
+            modal.destroy()
+            callback?()
+
+    modal.once 'KDModalViewDestroyed', -> callback?()


Forward session connected event intelligently to views

--- client/ide/lib/videocollaborationcontroller.coffee
+++ client/ide/lib/videocollaborationcontroller.coffee
@@ -1,5 +1,6 @@
 VideoCollaborationModel = require 'app/videocollaboration/model'
 socialHelpers           = require './collaboration/helpers/social'
+isVideoFeatureEnabled   = require 'app/util/isVideoFeatureEnabled'

 generatePayloadFromModel = (model) ->
   return {
@@ -17,6 +18,7 @@
       view    : @chat.getVideoView()

     @videoModel
+      .on 'SessionConnected',              @bound 'handleVideoSessionConnected'
       .on 'CameraAccessQuestionAsked',     @bound 'handleVideoAccessQuestionAsked'
       .on 'CameraAccessQuestionAnswered',  @bound 'handleVideoAccessQuestionAnswered'
       .on 'VideoCollaborationActive',      @bound 'handleVideoActive'
@@ -33,7 +35,6 @@
         @handleVideoParticipantTalkingStateChanged participant, on
       .on 'ParticipantStoppedTalking', (participant) =>
         @handleVideoParticipantTalkingStateChanged participant, off
-

     participantEvents = [
       'SelectedParticipantChanged'
@@ -80,6 +81,16 @@
   hasParticipantWithAudio: (nickname, callback) ->

     @videoModel.hasParticipantWithAudio nickname, callback
+
+
+  handleVideoSessionConnected: (session, videoActive) ->
+
+    if isVideoFeatureEnabled()
+      if videoActive
+        @emitToViews 'VideoSessionConnected', { action: 'join' }
+      else
+        if @amIHost
+          @emitToViews 'VideoSessionConnected', { action: 'start' }

   handleVideoAccessQuestionAsked: ->


hotfix: fixed error in memory code view

hotfix: fixed error in memory code view

--- client/src/app/game/memory/memoryController.coffee
+++ client/src/app/game/memory/memoryController.coffee
@@ -53,12 +53,14 @@

     if _.isEqual(memoryModel, editor.get()) then return

-    save = saveExpandedNodes(editor.node)
+    # Editor.node won't exist outside of "tree" view
+    if editor.node? then save = saveExpandedNodes(editor.node)

     # Clone so that the editor doesn't modify our recorded data directly
     editor.set(RW.cloneData(memoryModel))

-    restoreExpandedNodes(editor.node, save)
+    # Editor.node won't exist outside of "tree" view
+    if editor.node? then restoreExpandedNodes(editor.node, save)

   $scope.$watch('gameHistoryMeta', onUpdateMemoryModel, true)
   $scope.$watch((-> circuits.currentCircuitMeta), onUpdateMemoryModel)
@@ -68,9 +70,10 @@
   onUpdateMemoryEditor = ->
     if not gameHistory.data.frames[gameTime.currentFrameNumber]? then return

+    newMemory = RW.cloneData(editor.get())
+
     # Update the frame memory
     if circuits.currentCircuitMeta.id?
-      newMemory = RW.cloneData(editor.get())
       gameHistory.data.frames[gameTime.currentFrameNumber].memory[circuits.currentCircuitMeta.id] = newMemory
       gameHistory.meta.version++


Ensure selection manager doesn't stay paused

--- src/trix/models/selection_manager.coffee
+++ src/trix/models/selection_manager.coffee
@@ -78,16 +78,24 @@
   @proxyMethod "locationMapper.findNodeAndOffsetFromLocation"

   didMouseDown: =>
-    @updatesPaused = true
-    handleEventOnce("mousemove", onElement: @element, withCallback: @didMouseMove)
+    @pause()
+    setTimeout(@resume, 100)

-  didMouseMove: =>
-    if @updatesPaused
-      @updatesPaused = null
+    handlers = for eventName in ["mousemove", "keydown"]
+      handleEvent eventName, onElement: document, withCallback: =>
+        handler.destroy() for handler in handlers
+        @resume()
+
+  pause: ->
+    @paused = true
+
+  resume: =>
+    if @paused
+      @paused = null
       @selectionDidChange()

   selectionDidChange: =>
-    unless @updatesPaused or innerElementIsActive(@element)
+    unless @paused or innerElementIsActive(@element)
       @updateCurrentLocationRange()

   updateCurrentLocationRange: (locationRange) ->


Fix the ill cookie test

Fix the ill cookie test

--- spec/api-session-spec.coffee
+++ spec/api-session-spec.coffee
@@ -46,14 +46,15 @@
     w.loadUrl 'file://' + path.join(fixtures, 'page', 'a.html')
     w.webContents.on 'did-finish-load', ()->
       w.webContents.session.cookies.set {url: url, name: 'key', value: 'dummy2'}, (error) ->
-        throw error if error
+        return done(error) if error
         w.webContents.session.cookies.get {url: url}, (error, cookies_list) ->
-          throw error if error
-          assert.equal 2, cookies_list.length
+          return done(error) if error
           for cookie in cookies_list
             if cookie.name is 'key'
                assert.equal 'dummy2', cookie.value
-               done();
+               done()
+               return
+          done('Can not find cookie')

   it 'should remove cookies', (done) ->
     w.loadUrl 'file://' + path.join(fixtures, 'page', 'a.html')


Allow map key to be hidden

Allow map key to be hidden

--- app/assets/javascripts/map_display.js.coffee
+++ app/assets/javascripts/map_display.js.coffee
@@ -27,7 +27,8 @@
   constructor: (center, opts) ->
     @domId = opts.domid || 'map'
     @map = L.map(@domId, maxZoom: 18)
-    @map.attributionControl.setPrefix('').addAttribution(
+    @map.attributionControl.setPrefix('')
+    @map.attributionControl.addAttribution(
       """
         <a
           href="#{CONSTANTS.images.mapKey}"
@@ -43,7 +44,7 @@
           Map key
         </a>
       """
-    ).setPosition('bottomleft')
+    ).setPosition('bottomleft') unless opts.hidekey?
     @setCenter(center)
     @map.on('baselayerchange', (e) ->
       window.localStorage.setItem('map.baselayer.name', e.name)


slight refactor

slight refactor

--- spec/http-helper-spec.coffee
+++ spec/http-helper-spec.coffee
@@ -13,10 +13,8 @@
   headers: {'Content-Type': 'image/jpeg'}
   bodyFromFile: "./fixtures/awesome-cat-jacket.jpg"

-mimeBoundary = '||BOUNDARY||'
-
 requestUploadMultipart = vh.buildRequest "localhost:#{testPort}",
-  headers: {'Content-Type': "multipart/form-data; boundary=#{mimeBoundary}"}
+  headers: {'Content-Type': "multipart/form-data; boundary=||BOUNDARY||"}
   bodyFromFile: "./fixtures/multipart-form-upload"

 vows.describe('Vows HTTP macros')


Remove screen line logging from spec

Remove screen line logging from spec

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -3398,7 +3398,6 @@
             expect(editor.getCursorBufferPosition()).toEqual([7, 2])

             editor.insertNewline()
-            editor.logScreenLines()
             expect(editor.lineTextForBufferRow(8)).toBe("  ")

         it "does not indent the line preceding the newline", ->


Remove some duplication from setHash specs.

--- spec/hashparamsSpec.coffee
+++ spec/hashparamsSpec.coffee
@@ -29,8 +29,9 @@
     describe 'happy-path construction with', ->
         describeConstructor = (cases, body) ->
             for name, args of cases
-                createParams = -> new HashParams args...
-                describe name, -> body(createParams)
+                do (name, args) ->
+                    createParams = -> new HashParams args...
+                    describe name, -> body(createParams)
         describe 'scalars "foreground" and "background" as', ->
             describeConstructor {
                 'names': ['foreground', 'background'],
@@ -72,40 +73,35 @@
             it 'empty string', -> expect(-> new HashParams('')).toThrowError /Invalid parameter name/
             it 'invalid type', -> expect(-> new HashParams('value:dszquphsbnt')).toThrowError /Invalid parameter type/
     describe '.setHash()', ->
-        describe 'when constructed with foreground and background', ->
-            params = null
+        params = null
+        describe 'with scalars "foreground" and "background"', ->
             beforeEach ->
                 params = new HashParams('foreground', 'background')
-            describe 'resets to empty', ->
+            hashYieldsValues = (hash, expected) ->
+                params.setHash hash
+                expect(params.values).toEqual expected
+            describe 'resets to empty when passed', ->
                 beforeEach -> params.values = {foreground: 'blue', background: 'green'}
-                it 'when passed just a hash character', ->
-                    params.setHash '#'
-                    expect(params.values).toEqual {foreground: '', background: ''}
-                it 'when passed an empty string', ->
-                    params.setHash ''
-                    expect(params.values).toEqual {foreground: '', background: ''}
-                it 'when passed undefined', ->
-                    params.setHash undefined
-                    expect(params.values).toEqual {foreground: '', background: ''}
-                it 'when passed null', ->
-                    params.setHash null
-                    expect(params.values).toEqual {foreground: '', background: ''}
+                cases = {
+                    'just a hash character': '#',
+                    'an empty string': '',
+                    'undefined': undefined,
+                    'null': null
+                }
+                for name, hash of cases
+                    do (name, hash) ->
+                        it name, -> hashYieldsValues hash, {foreground: '', background: ''}
             it 'can set values.foreground', ->
-                params.setHash '#foreground=blue'
-                expect(params.values).toEqual {foreground: 'blue', background: ''}
+                hashYieldsValues '#foreground=blue', {foreground: 'blue', background: ''}
             it 'can set values.background', ->
-                params.setHash '#background=green'
-                expect(params.values).toEqual {foreground: '', background: 'green'}
+                hashYieldsValues '#background=green', {foreground: '', background: 'green'}
             it 'clears other values', ->
                 params.values.foreground = 'magenta'
-                params.setHash '#background=green'
-                expect(params.values).toEqual {foreground: '', background: 'green'}
+                hashYieldsValues '#background=green', {foreground: '', background: 'green'}
             it 'can set both foreground and background', ->
-                params.setHash '#foreground=blue;background=green'
-                expect(params.values).toEqual {foreground: 'blue', background: 'green'}
+                hashYieldsValues '#foreground=blue;background=green', {foreground: 'blue', background: 'green'}
             it 'will not set values.foo', ->
-                params.setHash '#foo=bar'
-                expect(params.values).toEqual {foreground: '', background: ''}
+                hashYieldsValues '#foo=bar', {foreground: '', background: ''}
         describe 'unencoding', ->
             decodes = (encoded, decoded) ->
                 params = new HashParams('name' + decoded)


Setup up share button correctly on both partner shows and posts.

--- components/feed/client/feed_item.coffee
+++ components/feed/client/feed_item.coffee
@@ -7,7 +7,6 @@
 analytics               = require('../../../lib/analytics.coffee')
 SaveControls            = require '../../artwork_item/views/save_controls.coffee'
 artworkColumns          = -> require('../../artwork_columns/template.jade') arguments...
-ShareView               = require '../../share/view.coffee'

 module.exports.FeedItemView = class FeedItemView extends Backbone.View

@@ -55,7 +54,8 @@
       @artworkCollection.syncSavedArtworks()

   setupShareButtons: ->
-    new ShareView el: @$('.feed-item-share-section')
+    el = if @$('.feed-item-share-section').length > 0 then @$('.feed-item-share-section') else @$('.post-actions')
+    new ShareView el: el

   purchase: (event) =>
     $target = $(event.target)


Prevent spec from closing window

Prevent spec from closing window

--- spec/workspace-spec.coffee
+++ spec/workspace-spec.coffee
@@ -1440,6 +1440,7 @@

   describe "::closeActivePaneItemOrEmptyPaneOrWindow", ->
     beforeEach ->
+      spyOn(atom, 'close')
       waitsForPromise -> atom.workspace.open()

     it "closes the active pane item, or the active pane if it is empty, or the current window if there is only the empty root pane", ->
@@ -1467,6 +1468,5 @@
       atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow()
       expect(atom.workspace.getPanes().length).toBe 1

-      spyOn(atom, 'close')
       atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow()
       expect(atom.close).toHaveBeenCalled()


Remove option up.network.abort({ reason })

--- lib/assets/javascripts/unpoly/classes/request/queue.coffee
+++ lib/assets/javascripts/unpoly/classes/request/queue.coffee
@@ -90,11 +90,10 @@

   # Aborting a request will cause its promise to reject, which will also uncache it
   abort: (conditions = true) ->
-    reason = u.pluckKey(conditions, 'reason')
     for list in [@currentRequests, @queuedRequests]
       matches = u.filter list, (request) => @requestMatches(request, conditions)
       matches.forEach (match) ->
-        match.abort(reason)
+        match.abort()
         u.remove(list, match)
       return


allow parsing id in params

allow parsing id in params

--- packages/tower-controller/shared/params.coffee
+++ packages/tower-controller/shared/params.coffee
@@ -42,7 +42,7 @@
     # @todo refactor
     _buildCursorFromGet: (params, cursor) ->
       parsers     = @params()
-
+
       for name, parser of parsers
         if params.hasOwnProperty(name)
           if name == 'sort'
@@ -58,11 +58,6 @@
             object = {}
             object[name] = params[name]
             cursor.where(object)
-        # @todo make more robust, needs to allow for belongsTo
-        else if name.match(/Id$/)
-          object = {}
-          object[name] = params[name]
-          cursor.where(object)

       cursor


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


update transition

update transition

--- src/components/course-settings/remove-teacher.cjsx
+++ src/components/course-settings/remove-teacher.cjsx
@@ -28,9 +28,8 @@
       .value()

   goToDashboard: ->
-    @context.router.transitionTo('dashboard')
+    RosterActions.once 'deleted', @context.router.transitionTo('dashboard')

-
   performDeletion: ->
     {courseId, teacher, courseRoles} = @props
     RosterActions.teacherDelete(teacher.id, courseId, @isRemovalCurrentTeacher())


fix bug with opening settings of a kobomatrix right after adding to formbuilder

--- jsapp/xlform/src/view.row.coffee
+++ jsapp/xlform/src/view.row.coffee
@@ -216,7 +216,7 @@
       @defaultRowDetailParent = @cardSettingsWrap.find('.card__settings__fields--question-options').eq(0)

       # don't display columns that start with a $
-      for [key, val] in @model.attributesArray() when !key.match(/^\$/) and key not in ["label", "type", "select_from_list_name"]
+      for [key, val] in @model.attributesArray() when !key.match(/^\$/) and key not in ["label", "type", "select_from_list_name", 'kobo--matrix_list']
         new $viewRowDetail.DetailView(model: val, rowView: @).render().insertInDOM(@)
       @

@@ -247,7 +247,6 @@
     _expandedRender: ->
       super()
       @$('.xlf-dv-required').hide()
-      @$('.xlf-dv-kobo--matrix_list').hide()
       @$("li[data-card-settings-tab-id='validation-criteria']").hide()
       @$("li[data-card-settings-tab-id='skip-logic']").hide()
     _renderRow: ->


Hydrator: add current target detection spec.

--- src/OVirtResponseHydrator.coffee
+++ src/OVirtResponseHydrator.coffee
@@ -56,12 +56,14 @@
 #
 # ### High level objects hydration.
 #
-# - If current node is a root one then current target should be a hydrator
-#   instance target.
-# - If current node is an owner of collections, resource links or actions
-#   (except the case when the node is a resource)then an API node instance
-#   should be created and set as a current target.
-# - Node is a resource then new resource object instance should be created.
+# - Detect current hydration target
+#     - If current node is a root one then current target should be a hydrator
+#       instance target.
+#     - If current node is an owner of collections, resource links or actions
+#       (except the case when the node is a resource)then an API node instance
+#       should be created and set as a current target.
+#     - If node is a resource then new resource object instance should be
+#       created.
 # - Export attributes from node hash to current target.
 # - Remove attributes from node hash.
 # - Treat remaining node hash as a properties and export them to the current


Fix image width resizer bug

Fix image width resizer bug

--- public/theme/portalshit/script.coffee
+++ public/theme/portalshit/script.coffee
@@ -8,11 +8,15 @@
 fitImageWidthToScreenWidth = ->
   $('.body img').each ->
     maxWidth    = 900
-    deviceWidth = document.body.offsetWidth - 60
+    deviceWidth = document.body.offsetWidth
+    if window.orientation == 0
+      deviceWidth -= 160
+    else
+      deviceWidth -= 60
     imageRatio  = @naturalHeight / @naturalWidth
     maxWidth    = if deviceWidth > maxWidth then maxWidth else deviceWidth

-    if @naturalHeight > maxWidth
+    if @naturalWidth > maxWidth
       @width  = maxWidth
       @height = maxWidth * imageRatio


fixed app reference

fixed app reference

--- tasks/shimmer-templates/bootstrap.coffee
+++ tasks/shimmer-templates/bootstrap.coffee
@@ -1,11 +1,11 @@
 onDeviceReady = ->
-  angular.bootstrap document, [ app.name ]
+  angular.bootstrap document, [ 'app' ]
 document.addEventListener 'deviceready', onDeviceReady, false
 if typeof cordova == 'undefined'
   $html = angular.element(document.getElementsByTagName('html')[0])
   angular.element().ready ->
     try
-      angular.bootstrap document, [ app.name ]
+      angular.bootstrap document, [ 'app' ]
     catch e
       console.error e.stack or e.message or e
     return


Change URL based on what tab you click on summary page

--- app/assets/javascripts/games.js.coffee
+++ app/assets/javascripts/games.js.coffee
@@ -6,3 +6,9 @@
   if ids.length > 1
     id = ids.pop(1)
     $("#summaryTabs a[href=\"##{id}\"]").tab('show')
+
+  $('#summaryTabs').on 'click', 'li', (event) ->
+    activeTab = $(this).find('a').attr('href')
+    console.log activeTab
+    base = window.location.href.split('#')[0]
+    window.location.href = base + activeTab


Fix layout errors

Fix layout errors

--- src/coffee/layout.coffee
+++ src/coffee/layout.coffee
@@ -63,12 +63,13 @@
     ).join ''

   @buildContents: (element, options) ->
-    contents = options.contents
+    contents = options.contents || ''
     if _.isPlainObject(contents)
       contents = [contents]
     return @buildArray element, contents if _.isArray(contents)

-    if _.isString(contents)
+    if !(contents instanceof HTMLElement)
+      contents = contents.toString()
       contents = _.escape(contents) unless options.rawHtml
       contents = document.createTextNode contents
     element.appendChild contents


adding debug mode

adding debug mode

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -31,8 +31,11 @@

 env = if $.util.env.mode then $.util.env.mode else 'development'
 compressed = env == 'production'
+debug = $.util.env.debug?
 paths =
-  bower: 'src/main/vendors/bower_components', web: 'src/main/web', build: 'build'
+  bower: 'src/main/vendors/bower_components'
+  web: 'src/main/web'
+  build: 'build'
 reload = browserSync.reload

@@ -44,6 +47,7 @@
   ]
   .pipe do $.sourcemaps.init
   .pipe css.fromSASS
+    errLogToConsole: debug
     includePaths: [
       paths.bower + '/bourbon/app/assets/stylesheets'
       paths.bower + '/bitters/app/assets/stylesheets'
@@ -51,7 +55,7 @@
       paths.bower + '/fontawesome/scss'
       paths.web + '/**/*.scss'
     ]
-  .pipe do $.sourcemaps.write
+  .pipe $.sourcemaps.write debug: debug
   .pipe $.concat 'styles.css'
   .pipe $.if compressed, css.minify
   .pipe $.size title: 'styles'


Convert manual rest client to use ember-rest

--- app/assets/javascripts/votelist.coffee
+++ app/assets/javascripts/votelist.coffee
@@ -3,38 +3,29 @@

 # Model object indicating one potential vote recipient
-VoteList.Candidate = Ember.Object.extend
-  name: 'somebody'
+VoteList.Candidate = Ember.Resource.extend
+  url: 'candidate'
+  name: 'candidate'
+  properties: ['displayName', 'score']
+
+  displayName: 'somebody'
   score: 0
-  id: -1

   # Increment the score by one
   upvote: ->
     this.set 'score', this.get('score') + 1
-    this.update()
+    this.save()

   # Decrement the score by one
   downvote: ->
     this.set 'score', this.get('score') - 1
-    this.update()
-
-  # Add this model to the server
-  create: ->
-    $.ajax 'candidate',
-      data: {candidate: {name: this.get('name'), score: this.get('score')}}
-      success: (data) -> console.log(data)
-
-  # Save changes from the model to the server
-  update: ->
-    $.ajax 'candidate/' + this.id,
-      type: 'PUT'
-      data: {candidate: {name: this.get('name'), score: this.get('score')}}
-      success: (data) -> console.log(data)
+    this.save()

 # Controller for candidate models
-VoteList.CandidateController = Ember.ArrayController.create
-  content: []
+VoteList.CandidateController = Ember.ResourceController.create
+  type: VoteList.Candidate
+  url: 'candidate'

   # Computed property to keep content sorted
   sortedContent:(() ->
@@ -50,11 +41,14 @@
   ).property('@each.score').cacheable()

   # Creation method for adding a new canddiate
-  createCandidate: (name, shouldSave = true, score = 0, id) ->
-    cand = VoteList.Candidate.create name: name, score: score, id: id
+  createCandidate: (displayName, shouldSave = true, score = 0, id) ->
+    console.log displayName
+    cand = VoteList.Candidate.create displayName: displayName, score: score, id: id
+
     this.pushObject(cand)
+
     if (shouldSave)
-      cand.create()
+      cand.save()

 # View for creating a new candidate
@@ -85,22 +79,14 @@

 # Basic setup on first page load
 jQuery ->
-  console.log('ready')
-
   # Setup common AJAX settings for most requests
   $.ajaxSetup
     type: 'POST'
     headers:
       'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
     accept: 'application/json'
-
-  # Load the candidates to use initially
-  $.ajax
-    type: 'GET'
-    url: 'candidate'
-    success: (data) ->
-      jQuery.each data, (i, elem) ->
-        VoteList.CandidateController.createCandidate(elem.name, false, elem.score, elem.id)
+
+  VoteList.CandidateController.findAll()

 # Send VoteList out to the rest of the system


Don't serve up static files

Don't serve up static files

--- src/application.coffee
+++ src/application.coffee
@@ -36,8 +36,7 @@
     accounts_api_controller = new AccountsApiController account_table: AccountTable

     # Register URLs
-    app.get  '/', (request, response) ->
-      response.sendfile path.join(__dirname, '../public/index.html')
+    app.get  '/', (request, response) -> response.send status: 'online'

     app.post '/accounts', accounts_controller.create
     app.get  '/accounts/:account_id/login', accounts_controller.login
@@ -45,10 +44,6 @@
     app.get  '/api/v1/account', accounts_api_controller.show
     app.put  '/api/v1/account', accounts_api_controller.update

-    app.use (req, res) ->
-      return res.send 404 unless req.method == 'GET'
-      res.sendfile path.join(__dirname, '../public/index.html')
-
     port = process.env.PORT ? 3004
     server.listen port, ->
       console.log "Express server listening on port #{server.address().port}"


Started prototyping a new branch of functionality for the controller. Previously the controller has employed an exclusive strategy when it comes to child states, only one child could be activated at any given time. I'm not adding an alternative mode where any number of children can be activated/deactivated independently and simultaneously.

--- controller.coffee
+++ controller.coffee
@@ -6,12 +6,14 @@
 		unless @lineageIsBusy(c)
 			if !c.isActive()
 				return ControllerHelper.activateController(c)
+		false

 	@deactivate: (c) ->
 		unless @lineageIsBusy(c)
 			if c.isActive()
 				return ControllerHelper.deactivateController(c)
-
+		false
+
 	@toggle: (c) ->
 		if c.isActive()
 			return @deactivate c
@@ -23,12 +25,15 @@

 		if parent
 			if parent.isActive()
-				siblings = parent.getChildren()
-				#console.log "Siblings: #{siblings}"
-				for sibling in siblings
-					if sibling.isActive()
-						sibling.setOnDeactivatedAction new ControllerAction(c, ControllerAction.TYPE_ACTIVATE)
-						return ControllerHelper.deactivateController(sibling)
+
+				# We only care about sibling states if the parent is in exlusive mode. Multi mode is usful in implementations like lists, where it's desired to mange the transitions of many concurrent children.
+				if parent.mode == "exclusive"
+					siblings = parent.getChildren()
+					#console.log "Siblings: #{siblings}"
+					for sibling in siblings
+						if sibling.isActive()
+							sibling.setOnDeactivatedAction new ControllerAction(c, ControllerAction.TYPE_ACTIVATE)
+							return ControllerHelper.deactivateController(sibling)
 			else
 				parent.setOnActivatedAction new ControllerAction(c, ControllerAction.TYPE_ACTIVATE)
 				return ControllerHelper.activateController parent
@@ -140,16 +145,21 @@
 	@STATE_ACTIVATED = "activated"
 	@STATE_DEACTIVATED = "deactivated"

+	@MODE_EXCLUSIVE = 'exclusive'
+	@MODE_MULTI = 'multi'
+
 	#
 	# Options:
 	# defaultChild		Controller
 	#

 	constructor: (options={}) ->
+		@mode = options.mode ||= Controller.MODE_EXCLUSIVE
+
 		@sm = new StateMachine

 		defaultState = options.defaultState
-
+
 		@sm.addState Controller.STATE_DEACTIVATED, defaultState == Controller.STATE_ACTIVATED ? false : true
 		@sm.addState Controller.STATE_ACTIVATED, defaultState == Controller.STATE_ACTIVATED ? true : false

@@ -235,10 +245,13 @@
 		@sm.getNextState() != null

 	isBusy: ->
-		return true if @isTransitioning() or @onActivatedAction or @onDeactivatedAction
-
-		for child in @children
-			return true if child.isBusy()
+		return true if @isTransitioning()
+
+		if @mode == 'exclusive'
+			return true if @onActivatedAction or @onDeactivatedAction
+
+			for child in @children
+				return true if child.isBusy()

 		return false


[backport] Parallelizing userstories and tag_colors for backlog

--- app/coffee/modules/backlog/main.coffee
+++ app/coffee/modules/backlog/main.coffee
@@ -174,10 +174,10 @@
         @scope.httpParams = @.getUrlFilters()
         @rs.userstories.storeQueryParams(@scope.projectId, @scope.httpParams)

-        promise = @.refreshTagsColors().then =>
-            return @rs.userstories.listUnassigned(@scope.projectId, @scope.httpParams)
-
-        return promise.then (userstories) =>
+        promise = @q.all([@.refreshTagsColors(), @rs.userstories.listUnassigned(@scope.projectId, @scope.httpParams)])
+
+        return promise.then (data) =>
+            userstories = data[1]
             # NOTE: Fix order of USs because the filter orderBy does not work propertly in the partials files
             @scope.userstories = _.sortBy(userstories, "backlog_order")


Add keybindings for windows/linux

Add keybindings for windows/linux

--- keymaps/coffeescript-check.cson
+++ keymaps/coffeescript-check.cson
@@ -1,2 +1,5 @@
-".editor":
+".platform-linux .editor, .platform-win32 .editor":
+  "ctrl-k ctrl-c": "coffeescript-check:check"
+
+".platform-darwin .editor":
   "cmd-k cmd-c": "coffeescript-check:check"


Fixed some issues with the context menu which appeared from nowhere

--- src/main/client/grid/grid-directives-tables.coffee
+++ src/main/client/grid/grid-directives-tables.coffee
@@ -263,12 +263,15 @@
                     entityIdentifier = handsonTable.getSourceDataAtRow(start).id
                     deleteCase scope, handsonTable, entityIdentifier
               }
+            contextMenu = false
+            if editing and Object.keys(commands).length > 0
+              contextMenu = {
+                callback: (key, options) ->
+                items: commands
+              }
             handsonTable.updateSettings {
               readOnly: ! editing,
-              contextMenu:
-                callback: (key, options) ->
-
-                items: if editing and Object.keys(commands).length > 0 then commands else false
+              contextMenu: contextMenu
             }


FIX: cached bitmaps were getting invalidaed while they were being used - oops FIX: filter-children weren't drawn correctly with dirty areas - answer for now: do full draws when there are filter-children. IMPROVED: for cached children who's size is changing a lot, attempt to avoid creating too many bitmaps

--- source/Art/Engine/Core/Element.coffee
+++ source/Art/Engine/Core/Element.coffee
@@ -129,6 +129,7 @@

   _initComputedFields: ->
     @_elementSpaceDrawArea = null
+    @_filterChildren = []
     # @_filterDescendants = []
     # @_filterSource = null

@@ -189,7 +190,7 @@
       opacity

     isChanging: -> @__stateChangeQueued
-    # isFilter:      -> false
+    isFilter:      -> false
     # filterSource:  -> @_filterSource || @_parent
     rootElement:   -> @_rootElement ||= if @_parent then @_parent.getRootElement() else @
     bitmapFactory: -> @_bitmapFactory || @getCanvasElement()?.bitmapFactory || Canvas.Bitmap
@@ -456,11 +457,13 @@
         p

     children:
-      default: initialChildren = []
+      default: noChildren = []
       setter: (newChildren, oldChildren) ->
         @__drawPropertiesChanged = true # TODO - this is a hack-fix; is this the right way to do this?
         newChildren = compactFlatten newChildren, keepIfRubyTrue
-        firstTimeSettingChildren = oldChildren == initialChildren
+        firstTimeSettingChildren = oldChildren == noChildren
+
+        @_filterChildren = null

         # detect childrenHaveRemovedAnimations
         for child in oldChildren when child.getPendingHasToVoidAnimators()
@@ -489,9 +492,14 @@
         # update children which were added:
         #   remove from old parent
         #   update parent
-        for child in newChildren when (oldParent = child.getPendingParent()) != @
-          oldParent?._setChildrenOnly oldParent.pendingChildrenWithout child
-          child._setParentOnly @
+        for child in newChildren
+          if child.getIsFilter()
+            (@_filterChildren ||= []).push child
+          if (oldParent = child.getPendingParent()) != @
+            oldParent?._setChildrenOnly oldParent.pendingChildrenWithout child
+            child._setParentOnly @
+
+        @_filterChildren ||= noChildren

         newChildren

@@ -1043,7 +1051,6 @@

     @_generateDrawCache targetSpaceDrawArea, elementToTargetMatrix

-    drawCacheManager.useDrawCache @
     if !!@_drawCacheBitmap != !!@_drawCacheToElementMatrix
       throw new Error "expected both or neither: @_drawCacheToElementMatrix, @_drawCacheBitmap"

@@ -1079,6 +1086,7 @@
       pixelsPerPoint *
         if @getCacheDraw() then 1 else elementToTargetMatrix.getExactScaler()

+    cacheSpaceDrawArea = cacheSpaceDrawArea.roundOut()
     # don't cache if too big
     # TODO: this doesn't work; it causes errors to abort caching at this point
     # return if cacheSpaceDrawArea.size.area >= 2048 * 1536 && !@getNeedsStagingBitmap()
@@ -1086,10 +1094,14 @@
     # re-use existing bitmap, if possible
     d2eMatrix = Matrix.translateXY(-elementSpaceDrawArea.x, -elementSpaceDrawArea.y).scale(cacheScale).inv
     if d2eMatrix.eq(@_drawCacheToElementMatrix) && cacheSpaceDrawArea.size.eq @_drawCacheBitmap?.size
+      drawCacheManager.useDrawCache @
       return unless @_dirtyDrawAreas || @_redrawAll
     else
+      {size} = cacheSpaceDrawArea.size
+      if (unioned = @_drawCacheBitmap?.size.max size) && unioned.area < size.area * 2
+        size = unioned
       @_clearDrawCache()
-      @_drawCacheBitmap = drawCacheManager.allocateCacheBitmap @, cacheSpaceDrawArea.size
+      @_drawCacheBitmap = drawCacheManager.allocateCacheBitmap @, size
       @_dirtyDrawAreas = null
       @_redrawAll = true

@@ -1118,28 +1130,27 @@
       # disable draw-caching for children
       Element._cachingDraws++

-      if dirtyAreasToDraw
+      if config.partialRedrawEnabled && dirtyAreasToDraw && @_filterChildren.length == 0
         for dirtyDrawArea in dirtyAreasToDraw
           drawCacheSpaceDrawArea = @_elementToDrawCacheMatrix.transformBoundingRect dirtyDrawArea, true
           lastClippingInfo = @_drawCacheBitmap.openClipping drawCacheSpaceDrawArea
-          @_cachedDrawInternal()
+          @_drawCachedBitmapInternal()
           @_drawCacheBitmap.closeClipping lastClippingInfo

       else
-        @_cachedDrawInternal()
+        @_drawCachedBitmapInternal()

     finally
       @_redrawAll = false
       @_dirtyDrawAreas = remainingDirtyAreas
       Element._cachingDraws--

-  _cachedDrawInternal: ->
+  _drawCachedBitmapInternal: ->
     @_drawCacheBitmap.clear() # TODO - if we know we will REPLACE 100% of the pixels, we don't need to do this
     if @_clip && @getHasCustomClipping()
       @_drawWithClipping null, @_drawCacheBitmap, @_elementToDrawCacheMatrix
     else
       @_drawChildren @_drawCacheBitmap, @_elementToDrawCacheMatrix, true
-

   #################
   # ToBitmap


Address code review comments

Address code review comments

--- app/models/experimental_subject.coffee
+++ app/models/experimental_subject.coffee
@@ -152,41 +152,36 @@

   # for interesting cohort users, we will show a selection of known interesting and random unknown subjects.
   @next: (callback) =>
-    if Experiments.ACTIVE_EXPERIMENT?
-      if Experiments.ACTIVE_EXPERIMENT == "SerengetiInterestingAnimalsExperiment1"
-        @current.destroy() if @current?
-        count = @count()
+    if Experiments.ACTIVE_EXPERIMENT == "SerengetiInterestingAnimalsExperiment1"
+      @current.destroy() if @current?
+      count = @count()

-        # If empty, try to prepare one random "current" quickly
-        if count == 0
-          fetcher = @fetch 1
+      # If empty, try to prepare one random "current" quickly
+      if count == 0
+        fetcher = @fetch 1

-        # for experimental cohort, load up the right subjects
-        Experiments.getParticipant()
-        .then (participant) =>
-          if participant?
-            Experiments.currentParticipant = participant
-            Experiments.currentCohort = participant.cohort
-            toFetch = (@queueLength - @count()) + 1
-            if Experiments.currentParticipant.active && Experiments.currentCohort == Experiments.COHORT_INSERTION
-              # Fill the rest of the queue in the background according to the experiment server's instructions
-              @loadMoreExperimentalSubjects toFetch
-            else
-              @loadMoreRandomSubjects toFetch
+      # for experimental cohort, load up the right subjects
+      Experiments.getParticipant()
+      .then (participant) =>
+        if participant?
+          Experiments.currentParticipant = participant
+          Experiments.currentCohort = participant.cohort
+          toFetch = (@queueLength - @count()) + 1
+          if Experiments.currentParticipant.active && Experiments.currentCohort == Experiments.COHORT_INSERTION
+            # Fill the rest of the queue in the background according to the experiment server's instructions
+            @loadMoreExperimentalSubjects toFetch
+          else
+            @loadMoreRandomSubjects toFetch

-        # background work kicked off if needed, now we can advance
-        if fetcher is null
-          fakeFetcher = new $.Deferred
-          fetcher = fakeFetcher
-        @current.destroy() if @current?
-        @advance fetcher, callback
-        if fakeFetcher?
-          fakeFetcher.resolve()
-      else
-        # wrong experiment running - revert to parent
-        super.next callback
+      # background work kicked off if needed, now we can advance
+      if fetcher is null
+        fakeFetcher = new $.Deferred
+        fetcher = fakeFetcher
+      @advance fetcher, callback
+      if fakeFetcher?
+        fakeFetcher.resolve()
     else
-      # no experiment running - revert to parent
+      # InterestingAnimals experiment not running - revert to parent
       super.next callback

 module.exports = ExperimentalSubject


utils: eliminate unnecessary variable declaration

--- core/utils.coffee
+++ core/utils.coffee
@@ -81,8 +81,7 @@
     selection.addRange range

   selectText:(element, start, end = start)->
-    doc   = document
-    if doc.body.createTextRange
+    if document.body.createTextRange
       range = document.body.createTextRange()
       range.moveToElementText element
       range.select()


Avoid converting changes to Points

Avoid converting changes to Points

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -459,7 +459,6 @@
   bufferChangedText: (changes) =>
     lastCursorPosition = @editor.getLastCursor().getBufferPosition()
     changeOccurredNearLastCursor = changes.some ({start, newExtent}) ->
-      start = Point.fromObject(start)
       newRange = new Range(start, start.traverse(newExtent))
       newRange.containsPoint(lastCursorPosition)


обновлена ссылка на сайт

обновлена ссылка на сайт

--- app/assets/javascripts/social.coffee
+++ app/assets/javascripts/social.coffee
@@ -18,7 +18,7 @@
         shortened_reason = reason.substr(0, 80)
         FB.ui({
           method: 'feed'
-          link: 'brainlook.org'
+          link: 'valiizrashki.ru'
           caption: app.social_title
           description: "#{app.social_text} #{shortened_reason}"
         }, (response)->);


set trigger point for optimizaion back to 5MB

--- app/assets/javascripts/upload.coffee
+++ app/assets/javascripts/upload.coffee
@@ -330,7 +330,7 @@
     id: fileInput.id
     autoProceed: true
     restrictions:
-      maxFileSize: 15728640)
+      maxFileSize: 30*1024*1024)
     .use(Uppy.FileInput,
       target: uploadButton
       locale: strings: chooseFiles: uploadButton.dataset.choosefiles)
@@ -488,7 +488,7 @@
     $('#file-optimize').hide()
     $('#userManuscript-upload-notice').hide()
     $('#userManuscript-uploadButton-call').prop('disabled',true)
-    if file.size < 10000000
+    if file.size < 5000000
       $('#userManuscript-uploadButton-call').prop('disabled',false)
       $('#file-size-correct').show()
       $('#userManuscript-uploadCenter').show()


fixed sending the correct peer_id

fixed sending the correct peer_id

--- app/assets/javascripts/webrtc.coffee
+++ app/assets/javascripts/webrtc.coffee
@@ -76,7 +76,7 @@

   send: (op, data) ->
     #actors = Object.keys( @peers )
-    data.peer_id = @worker.uuid
+    data.peer_id = @worker.username
     data.room = @room
     msg = {slot:'webrtc',op:op,data:data}
     console.log('sending',msg)
@@ -91,7 +91,7 @@
     @send('relay', {
       'type':'hangup'
       'actors': peerIds,
-      'uuid': @worker.uuid
+      'uuid': @worker.username
     })
     @peers = {}
     if( @localStream? )


Better solution to last: empty and re-use row[i] on re-generation

Sorry, just spastic today

--- app/assets/volume/spreadsheet.coffee
+++ app/assets/volume/spreadsheet.coffee
@@ -377,9 +377,11 @@
         generateRow = (i) ->
           slot = slots[i]
           stop = slot.id == volume.top.id
-          row = document.createElement('tr')
-          rows[i]?.parentNode?.replaceChild(row, rows[i])
-          rows[i] = row
+          row = if rows[i]
+              $(rows[i]).empty()
+              rows[i]
+            else
+              rows[i] = document.createElement('tr')
           row.id = id + '_' + i
           row.data = i
           if editing && stop


refactor key building

refactor key building

--- libraries/metrics/metrics.coffee
+++ libraries/metrics/metrics.coffee
@@ -11,6 +11,8 @@
 buildKey = (key)-> "#{name}.#{hostname}.#{key}"
 buildGlobalKey = (key)-> "#{name}.global.#{key}"

+
+
 promMetrics = {}

 destructors = []
@@ -20,7 +22,7 @@
 module.exports = Metrics =
 	initialize: (_name) ->
 		name = _name
-		collectDefaultMetrics({ timeout: 5000, prefix: name+"_"})
+		collectDefaultMetrics({ timeout: 5000, prefix: Metrics.buildPromKey()})

 	registerDestructor: (func) ->
 		destructors.push func
@@ -30,6 +32,9 @@
 			res.set('Content-Type', Register.contentType)
 			res.end(Register.metrics())
 		)
+
+	buildPromKey: (key = "")->
+		Metrics.sanitizeKey "#{name}_#{key}"

 	sanitizeKey: (key) ->
 		key.replace /[^a-zA-Z0-9]/g, "_"
@@ -42,10 +47,10 @@

 	inc : (key, sampleRate = 1)->
 		statsd.increment buildKey(key), sampleRate
-		key = this.sanitizeKey(key)
+		key = Metrics.buildPromKey(key)
 		if !promMetrics[key]?
 			promMetrics[key] = new prom.Counter({
-				name: "#{name}_#{key}",
+				name: key,
 				help: key,
 				labelNames: ['name','host']
 			})
@@ -56,11 +61,10 @@

 	timing: (key, timeSpan, sampleRate)->
 		statsd.timing(buildKey(key), timeSpan, sampleRate)
+		key = Metrics.buildPromKey("timer_#{key}")
 		if !promMetrics[key]
-			k = "#{name}_timer_#{key}".replace(/\./g,"_").replace(/-/g,"_")
-			console.log("sending timing", k)
 			promMetrics[key] = new prom.Summary({
-				name: k,
+				name: key,
 				help: key,
 				maxAgeSeconds: 600,
 				ageBuckets: 10
@@ -70,6 +74,7 @@
 	Timer : class
 		constructor :(key, sampleRate = 1)->
 			this.start = new Date()
+			key = Metrics.sanitizeKey(key)
 			this.key = key
 			this.sampleRate = sampleRate

@@ -80,10 +85,10 @@

 	gauge : (key, value, sampleRate = 1)->
 		statsd.gauge buildKey(key), value, sampleRate
-		key = this.sanitizeKey(key)
+		key = Metrics.buildPromKey(key)
 		if !promMetrics[key]
 			promMetrics[key] = new prom.Gauge({
-				name: "#{name}_#{key}",
+				name: key,
 				help: key,
 				labelNames: ['name','host']
 			})
@@ -91,10 +96,10 @@

 	globalGauge: (key, value, sampleRate = 1)->
 		statsd.gauge buildGlobalKey(key), value, sampleRate
-		key = this.sanitizeKey(key)
+		key = Metrics.buildPromKey(key)
 		if !promMetrics[key]
 			promMetrics[key] = new prom.Gauge({
-				name: "#{name}_#{key}",
+				name: key,
 				help: key,
 				labelNames: ['name','host']
 			})


Animated end scores

Animated end scores

--- app/scripts/components/StageEnd.coffee
+++ app/scripts/components/StageEnd.coffee
@@ -1,34 +1,49 @@
 Crafty.c 'StageEnd',
   init: ->
-    @requires '2D, DOM, Text, HUD, Delay'
+    @requires '2D, Canvas, HUD, Delay, Color'

   stageEnd: (level) ->
     @lineNr = 0
-    this.attr w: 640, z: 1
-      .css 'textAlign', 'center'
+
+    this.attr(
+      w: 580
+      h: 200
+      alpha: 0.2
+    )
+    .positionHud(
+      x: 40
+      y: 130
+      z: 90
+    )
+    .color('#000000')
+
+    c = Crafty.e('Text, DOM').attr(
+      x: @x
+      y: @y + 10
+      w: @w
+      z: 91
+    ).css 'textAlign', 'center'
       .text "Stage #{level.data.stage} Cleared: #{level.data.title}"
-      .positionHud(
-        x: @x,
-        y: 60,
-        z: -1
-      )
       .textColor('#FFFFFF')
       .textFont({
         size: '12px',
         weight: 'bold',
         family: 'Courier new'
-      }).delay((i) ->
-        switch @lineNr
-          when 0
-            @showPlayerHeaders()
-            @showAccuracy level
-          when 2 then @showShotsFired level
-          when 3 then @showEnemiesKilled level
-          when 4 then @showLives level
-          when 5 then @showWeaponXP level
-          when 6 then @showTotals level
-          when 7 then level.data.stageFinished = yes
-      , 1000, 6)
+      })
+    @attach c
+
+    @delay((i) ->
+      switch @lineNr
+        when 0
+          @showPlayerHeaders()
+          @showAccuracy level
+          level.data.stageFinished = yes
+        when 2 then @showShotsFired level
+        when 3 then @showEnemiesKilled level
+        when 4 then @showLives level
+        when 5 then @showWeaponXP level
+        when 6 then @showTotals level
+    , 2000, 6)

   showPlayerHeaders: ->
     cells = ['']
@@ -50,7 +65,7 @@
           value = 0.0
         cells.push "#{value}%"
         score = Math.round(value * 100)
-        cells.push "#{score}"
+        cells.push score
         @stats.bonus += score
       else
         cells.push ''
@@ -70,7 +85,7 @@
           accuracy = 0.0

         score = Math.round(value * (accuracy + accuracy))
-        cells.push "#{score}"
+        cells.push score
         @stats.bonus += score
       else
         cells.push ''
@@ -89,7 +104,7 @@
         cells.push "#{value}%"

         score = Math.round(value * 100)
-        cells.push "#{score}"
+        cells.push score
         @stats.bonus += score
       else
         cells.push ''
@@ -103,7 +118,7 @@
         value = @lives
         cells.push "#{value}"
         score = Math.round(value * 1000)
-        cells.push "#{score}"
+        cells.push score
         @stats.bonus += score
       else
         cells.push ''
@@ -117,7 +132,7 @@
         value = @ship?.primaryWeapon?.xp ? 0
         cells.push "#{value}"
         score = Math.round(value * 10)
-        cells.push "#{score}"
+        cells.push score
         @stats.bonus += score
       else
         cells.push ''
@@ -140,45 +155,44 @@
   addCelledLine: (cells) ->
     @lineNr += 1

-    y = 80 + (@lineNr * 20)
+    y = 150 + (@lineNr * 20)

-    xOffset = 70
+    xOffset = 0
     cellWidth = 500.0 / (cells.length + .5)
     for cell, i in cells
-      cw = cellWidth
-      cw += (cellWidth / 2.0) if i is 0
-      if i is 0
-        x = @x + xOffset
-      else
-        x = @x + xOffset + (cellWidth * (i + .5))
+      do (cell, i) =>
+        cw = cellWidth
+        cw += (cellWidth / 2.0) if i is 0
+        if i is 0
+          x = @x + xOffset
+        else
+          x = @x + xOffset + (cellWidth * (i + .5))

-      c = Crafty.e('Text, DOM').attr(
-        x: x
-        y: y
-        w: cw
-        z: 3
-      ).css 'textAlign', 'right'
-        .text cell
-        .textColor('#FFFFFF')
-        .textFont({
-          size: '12px',
-          weight: 'normal',
-          family: 'Courier new'
-        })
-      @attach c
-      c2 = Crafty.e('Text, DOM').attr(
-        x: x
-        y: y + 2
-        w: cw
-        z: 2
-      ).css 'textAlign', 'right'
-        .text cell
-        .textColor('#000000')
-        .textFont({
-          size: '12px',
-          weight: 'bold',
-          family: 'Courier new'
-        })
-      @attach c2
+        c = Crafty.e('Text, DOM').attr(
+          x: x
+          y: y
+          w: cw
+          z: 3
+        ).css 'textAlign', 'right'
+          .text cell
+          .textColor('#FFFFFF')
+          .textFont({
+            size: '12px',
+            weight: 'normal',
+            family: 'Courier new'
+          })
+
+        if i in [2, 4] and typeof cell is 'number'
+          c.attr endScore: cell, v: 0
+          c.text c.v
+          c.bind 'EnterFrame', (fd) ->
+            @v += (fd.dt * 3) + 3
+            if @v > @endScore
+              @v = @endScore
+              @unbind 'EnterFrame'
+            @text @v

+        @attach c
+
+


Restructure content in skyline section

Restructure content in skyline section

--- app/scripts/lazerscripts/stage1.coffee
+++ app/scripts/lazerscripts/stage1.coffee
@@ -69,15 +69,33 @@
       @checkpoint @checkpointMidStage('Skyline', 450000)

       @setPowerupPool 'damageb', 'damage', 'aimb', 'rapidb', 'damage', 'damageb'
-      @placeSquad Game.Scripts.ScraperFlyer,
-        amount: 8
-        delay: 500
-        drop: 'pool'
-      @parallel(
-        @placeSquad Game.Scripts.ScraperFlyer,
-          amount: 8
-          delay: 500
+      @attackWaves(
+        @parallel(
+          @placeSquad Game.Scripts.ScraperFlyer,
+            amount: 8
+            delay: 500
+          @placeSquad Game.Scripts.Shooter,
+            amount: 8
+            delay: 500
+            options:
+              shootOnSight: yes
+        )
+        drop: 'pool'
+      )
+      @parallel(
+        @attackWaves(
+          @parallel(
+            @placeSquad Game.Scripts.ScraperFlyer,
+              amount: 8
+              delay: 500
+            @placeSquad Game.Scripts.Shooter,
+              amount: 8
+              delay: 500
+              options:
+                shootOnSight: yes
+          )
           drop: 'pool'
+        )
         @cloneEncounter()
       )
       @cityFighting()
@@ -373,30 +391,32 @@
         )
         @sequence(
           @wait 3000
-          @placeSquad Game.Scripts.Swirler,
+          @placeSquad Game.Scripts.Shooter,
             amount: 4
             delay: 750
             drop: 'pool'
             options:
               shootOnSight: yes
-          @placeSquad Game.Scripts.HeliAttack
+          @placeSquad Game.Scripts.HeliAttack,
+            drop: 'pool'
         )
       )
     )

   cloneEncounter: ->
-    @parallel(
-      @sequence(
-        @wait 4000
+    @attackWaves(
+      @parallel(
+        @sequence(
+          @wait 4000
+          @placeSquad Game.Scripts.PlayerClone,
+            options:
+              from: 'top'
+        )
         @placeSquad Game.Scripts.PlayerClone,
-          drop: 'pool'
           options:
-            from: 'top'
-      )
-      @placeSquad Game.Scripts.PlayerClone,
-        drop: 'pool'
-        options:
-          from: 'bottom'
+            from: 'bottom'
+      )
+      drop: 'pool'
     )


Restore LineWrapper.clipScreenPosition version that passes existing tests.

It's still wrong, but I need a test to demonstrate how.

--- src/atom/line-wrapper.coffee
+++ src/atom/line-wrapper.coffee
@@ -94,13 +94,10 @@
       @lineMap.bufferRangeForScreenRange(screenRange))

   clipScreenPosition: (screenPosition) ->
-    # console.log @lineMap.bufferPositionForScreenPosition(@lineMap.clipScreenPosition(screenPosition)).inspect()
-    @lineFolder.clipScreenPosition(@lineMap.clipScreenPosition(screenPosition))
-    # @lineMap.clipScreenPosition(screenPosition)
-    # @lineMap.screenPositionForBufferPosition(
-    #   @lineFolder.clipScreenPosition(
-    #     @lineMap.bufferPositionForScreenPosition(
-    #       @lineMap.clipScreenPosition(screenPosition))))
+    @lineMap.screenPositionForBufferPosition(
+      @lineFolder.clipScreenPosition(
+        @lineMap.bufferPositionForScreenPosition(
+          @lineMap.clipScreenPosition(screenPosition))))

   lineForScreenRow: (screenRow) ->
     @linesForScreenRows(screenRow, screenRow)[0]


delete obsolete file

delete obsolete file


#928 - improving question selecting, reverted sorting

--- jsapp/xlform_model_view/view.surveyApp.coffee
+++ jsapp/xlform_model_view/view.surveyApp.coffee
@@ -336,14 +336,6 @@
       sortable_stop = (evt, ui)=>
         $(ui.item).trigger('survey__row-sortablestop')

-      sortable_start = (event, ui) =>
-        if ui.item.hasClass('survey__row--selected')
-          $selected_rows = @formEditorEl.find('survey__row--selected')
-          if $selected_rows.length > 1
-            $wrapper = $('div')
-            $selected_rows.remove().appendTo($wrapper)
-            ui.item = $wrapper
-
       @formEditorEl.sortable({
           # PM: commented out axis, because it's better if cards move horizontally and vertically
           # axis: "y"
@@ -355,7 +347,6 @@
           connectWith: ".group__rows"
           opacity: 0.9
           scroll: true
-          start: sortable_start
           stop: sortable_stop
           activate: sortable_activate_deactivate
           deactivate: sortable_activate_deactivate


:lipstick:

:lipstick:

--- src/packages/symbols-view/lib/tag-generator.coffee
+++ src/packages/symbols-view/lib/tag-generator.coffee
@@ -18,7 +18,7 @@
   generate: ->
     deferred = $.Deferred()
     tags = []
-    command = "#{require.resolve('ctags')}"
+    command = require.resolve('ctags')
     args = ['--fields=+KS', '-nf', '-', @path]
     stdout = (lines) =>
       for line in lines.split('\n')


[HEXDEV-456] Add a new parser type.

[HEXDEV-456] Add a new parser type.

Avro parser type appended into a list of availabel parsers.

--- src/ext/components/parse-input.coffee
+++ src/ext/components/parse-input.coffee
@@ -1,6 +1,6 @@
-MaxItemsPerPage = 15
+MaxItemsPerPage = 15

-parseTypes = map [ 'AUTO', 'ARFF', 'XLS', 'XLSX', 'CSV', 'SVMLight', 'ORC' ], (type) -> type: type, caption: type
+parseTypes = map [ 'AUTO', 'ARFF', 'XLS', 'XLSX', 'CSV', 'SVMLight', 'ORC', 'AVRO' ], (type) -> type: type, caption: type

 parseDelimiters = do ->
   whitespaceSeparators = [
@@ -68,7 +68,7 @@
   _sourceKeys = map _result.source_frames, (src) -> src.name
   _parseType =  signal find parseTypes, (parseType) -> parseType.type is _result.parse_type
   _canReconfigure = lift _parseType, (parseType) -> parseType.type isnt 'SVMLight'
-  _delimiter = signal find parseDelimiters, (delimiter) -> delimiter.charCode is _result.separator
+  _delimiter = signal find parseDelimiters, (delimiter) -> delimiter.charCode is _result.separator
   _useSingleQuotes = signal _result.single_quotes
   _destinationKey = signal _result.destination_frame
   _headerOptions = auto: 0, header: 1, data: -1