one more

one more

--- salvus/compute.coffee
+++ salvus/compute.coffee
@@ -1854,9 +1854,9 @@
                 opts.cb(err)
             else
                 x =
-                    state : @_state
-                    time  : @_state_time
-                    error : @_state_error
+                    state       : @_state
+                    time        : @_state_time
+                    state_error : @_state_error
                 opts.cb(undefined, x)

     set_mintime: (opts) =>


fixes for rethinkdb 2.1

fixes for rethinkdb 2.1

--- salvus/rethink.coffee
+++ salvus/rethink.coffee
@@ -655,10 +655,14 @@
                 account =
                     first_name    : opts.first_name
                     last_name     : opts.last_name
-                    email_address : opts.email_address
-                    password_hash : opts.password_hash
                     created       : new Date()
                     created_by    : opts.created_by
+
+                if opts.password_hash?
+                    account.password_hash = opts.password_hash
+                if opts.email_address?
+                    account.email_address = opts.email_address
+
                 @table('accounts').insert(account).run (err, x) =>
                     if err
                         cb(err)
@@ -1600,9 +1604,9 @@
         if not @_validate_opts(opts) then return
         @table('projects').get(opts.project_id)('users')(opts.account_id)('group').run (err, group) =>
             if err?
-                if err.name == "ReqlRuntimeError"
+                if err.name == "RqlRuntimeError"
                     # indicates that there's no opts.account_id key in the table (or users key) -- error is different
-                    # (i.e., ReqlDriverError) when caused by connection being down.
+                    # (i.e., RqlDriverError) when caused by connection being down.
                     # one more chance -- admin?
                     @is_admin(opts.account_id, opts.cb)
                 else
@@ -1906,8 +1910,8 @@
             cb         : required  # cb(err, ttl actually used in seconds); ttl=0 for infinite ttl
         @table('blobs').get(opts.uuid).pluck('expire').run (err, x) =>
             if err
-                if err.name == 'ReqlRuntimeError'
-                    # get ReqlRuntimeError if the blob not already saved, due to trying to pluck from nothing
+                if err.name == 'RqlRuntimeError'
+                    # get RqlRuntimeError if the blob not already saved, due to trying to pluck from nothing
                     x =
                         id         : opts.uuid
                         blob       : opts.blob


Adding / and - to branch chars in deploy

--- scripts/deploy.coffee
+++ scripts/deploy.coffee
@@ -15,10 +15,8 @@
     msg.reply failMsg

 module.exports = (robot) ->
-  robot.respond /deploy ?([a-zA-Z-_\&\^\!\#]+)?/i, (msg) ->
+  robot.respond /deploy ?([a-zA-Z-_\&\^\/\-\!\#]+)?/i, (msg) ->
     deploy msg

   robot.respond /sudo deploy/i, (msg) ->
     deploy msg, "Nope.  Now make me a sandwich."
-
-


adding more lines for matt

adding more lines for matt

--- scripts/dr-mat.coffee
+++ scripts/dr-mat.coffee
@@ -19,11 +19,11 @@
 MATT_QUOTE = [
   "Burritos are just tacoids in the category of enchiladafunctors",
   "Monads are just monoids in the category of endofunctors",
-  "The review is pretty 3rd quadrant",
-  "Let's do multivariate test",
   "We have asymptotic release cycle",
   "To win greatest victories one must take the greatest risk",
-  "Formitable display of coding power"
+  "Formitable display of coding power",
+  "You can not follow the path, before you will become the path yourself",
+  "Expect nothing, seek nothing, grasp nothing"
 ]

 module.exports = (robot) ->


Check for username in recall.

Check for username in recall.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -124,3 +124,5 @@
             # Outputs an inspection of recall which comes out as:
             # { masonest: 3670, awebot: 3860, knexem: 3455 }
             # msg.send "#{Util.inspect(recall)}"
+
+            msg.send "#{username}" for username in recall"


Fix for #2461 JS driver lost variable digits

--- drivers/javascript/ast.coffee
+++ drivers/javascript/ast.coffee
@@ -456,7 +456,7 @@

 class Var extends RDBOp
     tt: protoTermType.VAR
-    compose: (args) -> ['var_'+args[0]]
+    compose: (args) -> ['var_'+args]

 class JavaScript extends RDBOp
     tt: protoTermType.JAVASCRIPT


Check if hasImplicit returns true and not if it coerce to true (style)

--- drivers/javascript/ast.coffee
+++ drivers/javascript/ast.coffee
@@ -788,7 +788,7 @@
         return super(optargs, argsArr, body)

     compose: (args) ->
-        if hasImplicit(args[1])
+        if hasImplicit(args[1]) is true
             [args[1]]
         else
             ['function(', (Var::compose(arg) for arg in args[0][1...-1]), ') { return ', args[1], '; }']


Update the specs for .cursor/selection-line decorations

--- spec/editor-component-spec.coffee
+++ spec/editor-component-spec.coffee
@@ -315,9 +315,6 @@
     describe "when line decorations are attached to markers", ->
       {marker, decoration} = {}

-      lineHasClass = (screenRow, klass) ->
-        component.lineNodeForScreenRow(screenRow).classList.contains(klass)
-
       beforeEach ->
         marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], invalidate: 'inside')
         decoration = {type: 'line', class: 'someclass'}
@@ -424,13 +421,6 @@

   describe "gutter rendering", ->
     [gutter] = []
-
-    lineNumberHasClass = (screenRow, klass) ->
-      component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
-
-    lineNumberForBufferRowHasClass = (bufferRow, klass) ->
-      screenRow = editor.displayBuffer.screenRowForBufferRow(bufferRow)
-      component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)

     beforeEach ->
       {gutter} = component.refs
@@ -607,62 +597,6 @@
           runSetImmediateCallbacks()
           expect(lineNumberHasClass(1, 'folded')).toBe false

-    describe "cursor-line decorations", ->
-      cursor = null
-      beforeEach ->
-        cursor = editor.getCursor()
-
-      it "modifies the cursor-line decoration when the cursor moves", ->
-        cursor.setScreenPosition([0, 0])
-        runSetImmediateCallbacks()
-        expect(lineNumberHasClass(0, 'cursor-line')).toBe true
-
-        cursor.setScreenPosition([1, 0])
-        runSetImmediateCallbacks()
-        expect(lineNumberHasClass(0, 'cursor-line')).toBe false
-        expect(lineNumberHasClass(1, 'cursor-line')).toBe true
-
-      it "updates cursor-line decorations for multiple cursors", ->
-        cursor.setScreenPosition([2, 0])
-        cursor2 = editor.addCursorAtScreenPosition([8, 0])
-        cursor3 = editor.addCursorAtScreenPosition([10, 0])
-        runSetImmediateCallbacks()
-
-        expect(lineNumberHasClass(2, 'cursor-line')).toBe true
-        expect(lineNumberHasClass(8, 'cursor-line')).toBe true
-        expect(lineNumberHasClass(10, 'cursor-line')).toBe true
-
-        cursor2.destroy()
-        runSetImmediateCallbacks()
-        expect(lineNumberHasClass(2, 'cursor-line')).toBe true
-        expect(lineNumberHasClass(8, 'cursor-line')).toBe false
-        expect(lineNumberHasClass(10, 'cursor-line')).toBe true
-
-        cursor3.destroy()
-        runSetImmediateCallbacks()
-        expect(lineNumberHasClass(2, 'cursor-line')).toBe true
-        expect(lineNumberHasClass(8, 'cursor-line')).toBe false
-        expect(lineNumberHasClass(10, 'cursor-line')).toBe false
-
-      it "adds cursor-line decorations to multiple lines when a selection is performed", ->
-        cursor.setScreenPosition([1, 5])
-        editor.selectDown(2)
-        runSetImmediateCallbacks()
-        expect(lineNumberHasClass(0, 'cursor-line')).toBe false
-        expect(lineNumberHasClass(1, 'cursor-line')).toBe true
-        expect(lineNumberHasClass(2, 'cursor-line')).toBe true
-        expect(lineNumberHasClass(3, 'cursor-line')).toBe true
-        expect(lineNumberHasClass(4, 'cursor-line')).toBe false
-
-      it "does not render a cursor-line decoration for the last line of a multi-line selection of the selection ends at column 0", ->
-        cursor.setScreenPosition([1, 0])
-        editor.selectDown(2)
-        runSetImmediateCallbacks()
-        expect(lineNumberHasClass(0, 'cursor-line')).toBe false
-        expect(lineNumberHasClass(1, 'cursor-line')).toBe true
-        expect(lineNumberHasClass(2, 'cursor-line')).toBe true
-        expect(lineNumberHasClass(3, 'cursor-line')).toBe false
-
     describe "when gutter decorations are attached to markers", ->
       {marker, decoration} = {}
       beforeEach ->
@@ -1905,6 +1839,50 @@
       runSetImmediateCallbacks()
       expect(node.querySelector('.line').textContent).toBe "var quicksort "

+  describe "default decorations", ->
+    it "applies .cursor-line decorations to lines and line numbers for rows containing the cursor", ->
+      editor.setCursorScreenPosition([4, 4])
+      runSetImmediateCallbacks()
+      expect(lineNumberHasClass(3, 'cursor-line')).toBe false
+      expect(lineNumberHasClass(4, 'cursor-line')).toBe true
+      expect(lineNumberHasClass(5, 'cursor-line')).toBe false
+      expect(lineHasClass(3, 'cursor-line')).toBe false
+      expect(lineHasClass(4, 'cursor-line')).toBe true
+      expect(lineHasClass(5, 'cursor-line')).toBe false
+
+      editor.setSelectedScreenRange([[3, 4], [4, 4]])
+      runSetImmediateCallbacks()
+      expect(lineNumberHasClass(4, 'cursor-line')).toBe true
+      expect(lineHasClass(4, 'cursor-line')).toBe true
+
+    it "applies .selection-line decorations to lines and line numbers for rows spanned by non-empty selections", ->
+      editor.setCursorScreenPosition([4, 4])
+      runSetImmediateCallbacks()
+      expect(lineNumberHasClass(4, 'selection-line')).toBe false
+      expect(lineHasClass(4, 'selection-line')).toBe false
+
+      editor.setSelectedScreenRange([[4, 4], [6, 4]])
+      runSetImmediateCallbacks()
+      expect(lineNumberHasClass(3, 'selection-line')).toBe false
+      expect(lineNumberHasClass(4, 'selection-line')).toBe true
+      expect(lineNumberHasClass(5, 'selection-line')).toBe true
+      expect(lineNumberHasClass(6, 'selection-line')).toBe true
+      expect(lineNumberHasClass(7, 'selection-line')).toBe false
+      expect(lineHasClass(3, 'selection-line')).toBe false
+      expect(lineHasClass(4, 'selection-line')).toBe true
+      expect(lineHasClass(5, 'selection-line')).toBe true
+      expect(lineHasClass(6, 'selection-line')).toBe true
+      expect(lineHasClass(7, 'selection-line')).toBe false
+
+    it "applies .cursor-line-no-selection to line numbers for rows containing the cursor when the selection is empty", ->
+      editor.setCursorScreenPosition([4, 4])
+      runSetImmediateCallbacks()
+      expect(lineNumberHasClass(4, 'cursor-line-no-selection')).toBe true
+
+      editor.setSelectedScreenRange([[3, 4], [4, 4]])
+      runSetImmediateCallbacks()
+      expect(lineNumberHasClass(4, 'cursor-line-no-selection')).toBe false
+
   describe "legacy editor compatibility", ->
     it "triggers the screen-lines-changed event before the editor:display-update event", ->
       editor.setSoftWrap(true)
@@ -1939,3 +1917,13 @@
     clientX = gutterClientRect.left + positionOffset.left - editor.getScrollLeft()
     clientY = gutterClientRect.top + positionOffset.top - editor.getScrollTop()
     {clientX, clientY}
+
+  lineNumberHasClass = (screenRow, klass) ->
+    component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
+
+  lineNumberForBufferRowHasClass = (bufferRow, klass) ->
+    screenRow = editor.displayBuffer.screenRowForBufferRow(bufferRow)
+    component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
+
+  lineHasClass = (screenRow, klass) ->
+    component.lineNodeForScreenRow(screenRow).classList.contains(klass)


Convert lat, lng to distance

Convert lat, lng to distance

--- public/javascripts/game.photography.coffee
+++ public/javascripts/game.photography.coffee
@@ -1,4 +1,21 @@
 jQuery(document).ready ->
+
+  deg2rad = (deg) ->
+    return deg * (Math.PI/180)
+
+  distanceTravelled = (from, to) ->
+    lat1 = from.lat
+    lng1 = from.lng
+    lat2 = to.lat
+    lng2 = to.lng
+    R = 6371
+    dLat = deg2rad(lat2-lat1)
+    dLng = deg2rad(lng2-lng1);
+    a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLng/2) * Math.sin(dLng/2)
+    c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+    dist = R * c
+    return dist;
+
   class photographyGame
     constructor: (@debug, @map) ->

@@ -92,3 +109,5 @@
   retrieveResources(100).then (res) ->
     generateMarkers(processData(res))
     return
+
+  return


Fix forum tooltip hiding

Fix forum tooltip hiding

Don't explode if doesn't exist

--- resources/assets/coffee/_classes/forum-posts-seek.coffee
+++ resources/assets/coffee/_classes/forum-posts-seek.coffee
@@ -32,6 +32,8 @@

   hideTooltip: =>
+    return if @tooltip.length == 0
+
     Fade.out @tooltip[0]


myteam: text formating

myteam: text formating

--- client/home/lib/myteam/components/hometeamteammates/member.coffee
+++ client/home/lib/myteam/components/hometeamteammates/member.coffee
@@ -25,15 +25,15 @@
     items = []

     if role is 'owner'
-      items.push { title: 'MAKE MEMBER', key: 'makemember', onClick: @props.handleRoleChange.bind(this, @props.member, 'member') }
+      items.push { title: 'Make member', key: 'makemember', onClick: @props.handleRoleChange.bind(this, @props.member, 'member') }
     else if role is 'admin'
-      items.push { title: 'MAKE MEMBER', key: 'makemember', onClick: @props.handleRoleChange.bind(this, @props.member, 'member') }
-      items.push { title: 'MAKE OWNER', key: 'mameowner', onClick: @props.handleRoleChange.bind(this, @props.member, 'owner') }
-      items.push { title: 'DISABLE USER', key: 'disableuser', onClick: @props.handleRoleChange.bind(this, @props.member, 'kick') }
+      items.push { title: 'Make member', key: 'makemember', onClick: @props.handleRoleChange.bind(this, @props.member, 'member') }
+      items.push { title: 'Make owner', key: 'mameowner', onClick: @props.handleRoleChange.bind(this, @props.member, 'owner') }
+      items.push { title: 'Disable user', key: 'disableuser', onClick: @props.handleRoleChange.bind(this, @props.member, 'kick') }
     else
-      items.push { title: 'MAKE OWNER', key: 'makemember', onClick: @props.handleRoleChange.bind(this, @props.member, 'owner') }
-      items.push { title: 'MAKE ADMIN', key: 'makeadmin', onClick: @props.handleRoleChange.bind(this, @props.member, 'admin') }
-      items.push { title: 'DISABLE USER', key: 'disableuser', onClick: @props.handleRoleChange.bind(this, @props.member, 'kick') }
+      items.push { title: 'Make owner', key: 'makemember', onClick: @props.handleRoleChange.bind(this, @props.member, 'owner') }
+      items.push { title: 'Make admin', key: 'makeadmin', onClick: @props.handleRoleChange.bind(this, @props.member, 'admin') }
+      items.push { title: 'Disable user', key: 'disableuser', onClick: @props.handleRoleChange.bind(this, @props.member, 'kick') }

     return items


fix existance checks for first_name and last_name

--- services/real-time/app/coffee/WebsocketController.coffee
+++ services/real-time/app/coffee/WebsocketController.coffee
@@ -145,7 +145,7 @@
 				cursorData.id      = client.id
 				cursorData.user_id = user_id if user_id?
 				cursorData.email   = email   if email?
-				if first_name? or last_name?
+				if first_name or last_name
 					cursorData.name = if first_name && last_name
 						"#{first_name} #{last_name}"
 					else if first_name


:lipstick: simplify assignment using coffeescript magic

--- spec/context-menu-manager-spec.coffee
+++ spec/context-menu-manager-spec.coffee
@@ -6,7 +6,7 @@
   [contextMenu] = []

   beforeEach ->
-    resourcePath = atom.getLoadSettings().resourcePath
+    {resourcePath} = atom.getLoadSettings()
     contextMenu = new ContextMenuManager({resourcePath})

   describe "adding definitions", ->


Bug fixes in ACS

Bug fixes in ACS

--- acs.coffee
+++ acs.coffee
@@ -77,8 +77,7 @@

       if count == 0
         util.log("#{deviceId}: new device detected")
-        updates['_id'] = deviceId
-        devicesCollection.save(updates, {safe: true}, (err) ->
+        devicesCollection.update({'_id' : deviceId}, {'$set' : updates}, {upsert: true, safe: true}, (err) ->
           if err?
             callback(err) if callback?
             return
@@ -103,7 +102,7 @@

   if Object.keys(resParams).length > 0
     memcached.set(String(task._id), task, config.CACHE_DURATION, (err, result) ->
-      res = tr069.response(reqParams.sessionId, resParams, {task : String(task._id)})
+      res = tr069.response(sessionId, resParams, {task : String(task._id)})
       writeResponse response, res
     )
     return
@@ -210,7 +209,7 @@
           util.log("#{deviceId}: inform (#{reqParams.eventCodes}); retry count #{reqParams.retryCount}")

         updateDevice(deviceId, {'inform' : true, 'parameterValues' : reqParams.informParameterValues}, (err) ->
-          res = tr069.response(reqParams.sessionId, resParams, cookies)
+          res = tr069.response(sessionId, resParams, cookies)
           writeResponse response, res
         )
         return
@@ -221,7 +220,6 @@

       if not taskId
         nextTask(sessionId, deviceId, response, cookies)
-        return
       else
         memcached.get(taskId, (err, task) ->
           runTask(sessionId, deviceId, task, reqParams, response)


Optimizing overall performance under heavy load by temporarily rejecting new sessions that will likely not finish successfully anyway.

--- acs.coffee
+++ acs.coffee
@@ -16,6 +16,10 @@
 INSTANCE_REGEX = /\.[\d]+\.$/

+# Used to reject new TR-069 sessions when under load
+holdUntil = Date.now()
+
+
 applyConfigurations = (currentRequest, taskList) ->
   if taskList.length
     util.log("#{currentRequest.deviceId}: Presets discrepancy found")
@@ -129,36 +133,44 @@

 runTask = (currentRequest, task, methodResponse) ->
+  timeDiff = process.hrtime()
   tasks.task(task, methodResponse, (err, status, cwmpResponse, deviceUpdates) ->
     # TODO handle error
-    updateDevice(currentRequest, deviceUpdates)
-
-    switch status
-      when tasks.STATUS_FINISHED
-        db.memcached.del(String(task._id))
-        db.tasksCollection.remove({'_id' : mongodb.ObjectID(String(task._id))}, {safe: true}, (err, removed) ->
-          util.log("#{currentRequest.deviceId}: Completed task #{task.name}(#{task._id})")
-          nextTask(currentRequest)
-        )
-      when tasks.STATUS_FAULT
-        util.log("#{currentRequest.deviceId}: Fault response for task #{task._id}")
-        db.saveTask(task, (err) ->
-          # Faulty task. No more work to do until task is deleted.
-          res = tr069.response(null, cwmpResponse)
-          writeResponse(currentRequest, res)
-        )
-      when tasks.STATUS_PENDING
-        db.saveTask(task, (err) ->
-          # task expects CPE confirmation later
-          nextTask(currentRequest)
-        )
-      when tasks.STATUS_STARTED
-        db.updateTask(task, (err) ->
-          res = tr069.response(task._id, cwmpResponse)
-          writeResponse(currentRequest, res)
-        )
-      else
-        throw Error('Unknown task status')
+    updateDevice(currentRequest, deviceUpdates, (err) ->
+      util.error(err) if err?
+
+      timeDiff = process.hrtime(timeDiff)[0] + 1
+      if timeDiff > 3 # in seconds
+        # Server under load. Hold new sessions temporarily.
+        holdUntil = Math.max(Date.now() + timeDiff * 2000, holdUntil)
+
+      switch status
+        when tasks.STATUS_FINISHED
+          db.memcached.del(String(task._id))
+          db.tasksCollection.remove({'_id' : mongodb.ObjectID(String(task._id))}, {safe: true}, (err, removed) ->
+            util.log("#{currentRequest.deviceId}: Completed task #{task.name}(#{task._id})")
+            nextTask(currentRequest)
+          )
+        when tasks.STATUS_FAULT
+          util.log("#{currentRequest.deviceId}: Fault response for task #{task._id}")
+          db.saveTask(task, (err) ->
+            # Faulty task. No more work to do until task is deleted.
+            res = tr069.response(null, cwmpResponse)
+            writeResponse(currentRequest, res)
+          )
+        when tasks.STATUS_PENDING
+          db.saveTask(task, (err) ->
+            # task expects CPE confirmation later
+            nextTask(currentRequest)
+          )
+        when tasks.STATUS_STARTED
+          db.updateTask(task, (err) ->
+            res = tr069.response(task._id, cwmpResponse)
+            writeResponse(currentRequest, res)
+          )
+        else
+          throw Error('Unknown task status')
+    )
   )

@@ -263,6 +275,12 @@

     if cwmpRequest.methodRequest?
       if cwmpRequest.methodRequest.type is 'Inform'
+        if Date.now() < holdUntil
+          # Ask CPE to retry in 5 mins
+          res = {code : 503, headers : {'Content-Length' : 0, 'Retry-After' : 300}, data : ''}
+          writeResponse(currentRequest, res)
+          return
+
         cwmpResponse.methodResponse = {type : 'InformResponse'}

         if config.LOG_INFORMS


Improved: error message when DS failed to respond with doctype list

--- main.coffee
+++ main.coffee
@@ -333,7 +333,7 @@

             if err? or not body? or (body? and body.error?)
                 msg = "An error occurred while retrieving the doctypes list"
-                errorInfo = require('util').inspect body
+                errorInfo = err or require('util').inspect body
                 msgError = "#{msg} -- #{msgError}"
                 @log msgError.red
                 callback new Error msgError


log error

log error

--- server/cluster.coffee
+++ server/cluster.coffee
@@ -39,6 +39,7 @@

     HTTP.post "#{agentUrl}/app/install-and-run", callOpts, (err, result) ->
       console.log "Sent request to start instance. Response from the agent is" #, "#{result.content}"
+      console.log err if err
       s = JSONStream.parse()
       s.on 'data', (data) -> console.log 'data', data
       s.on 'error', (err) -> console.log 'err', err


latency compensation for copying/deleting a bucket spinner

--- server/methods.coffee
+++ server/methods.coffee
@@ -36,9 +36,13 @@
   clearInstance: Cluster.clearInstance
   saveApp: Cluster.saveApp
   deleteApp: Cluster.deleteApp
-  'storage/buckets/delete': (id) -> Agent.deleteStorageBucket StorageBuckets.findOne(id)?.name
+  'storage/buckets/delete': (id) ->
+    StorageBuckets.update id, $set: isLocked: true
+    Agent.deleteStorageBucket StorageBuckets.findOne(id)?.name
   'storage/buckets/create': (name) -> Agent.createStorageBucket name
-  'storage/buckets/copy': (source, destination) -> Agent.copyStorageBucket source, destination
+  'storage/buckets/copy': (source, destination) ->
+    StorageBuckets.update {name: source}, $set: isLocked: true
+    Agent.copyStorageBucket source, destination

   restartTag: (tag) ->
     for instance in Instances.find('parameters.tags': tag).fetch()


started a few methods for one off charges

--- server/methods.coffee
+++ server/methods.coffee
@@ -66,7 +66,36 @@
       user.update('billing.cardId': null)
     catch e
       console.error e
-      throw new Meteor.Error 500, e.message
+      throw new Meteor.Error 500, e.message
+
+  #
+  # Create a single one-time charge
+  #
+  createCharge: (params) ->
+    console.log "Creating charge"
+
+    Stripe = StripeAPI(Billing.settings.secretKey)
+    createCharge = Async.wrap Stripe.charges, 'create'
+    try
+      createCharge params
+    catch e
+      console.error e
+      throw new Meteor.Error 500, e.message
+
+  #
+  # List charges with any filters applied
+  #
+  listCharges: (params) ->
+    console.log "Getting past charges"
+
+    Stripe = StripeAPI(Billing.settings.secretKey)
+    listCharges = Async.wrap Stripe.charges, 'list'
+    try
+      listCharges params
+    catch e
+      console.error e
+      throw new Meteor.Error 500, e.message
+

   #
   # Update stripe subscription for user with provided plan and quantitiy


Updated old urls

Updated old urls

--- docpad.coffee
+++ docpad.coffee
@@ -70,7 +70,7 @@

       oldUrls: [
         'www.softwareniagara.com',
-        'softwareniagara.herokuapp.com'
+        'softwareniagara-com.herokuapp.com'
       ]

       title: "Software Niagara"


Adding/removing objects from API

Adding/removing objects from API

--- api.coffee
+++ api.coffee
@@ -14,6 +14,7 @@
 TASKS_REGEX = /^\/tasks\/([a-zA-Z0-9\-\_\%]+)(\/[a-zA-Z_]*)?$/
 TAGS_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/tags\/([a-zA-Z0-9\-\_\%]+)\/?$/
 PRESETS_REGEX = /^\/presets\/([a-zA-Z0-9\-\_\%]+)\/?$/
+OBJECTS_REGEX = /^\/objects\/([a-zA-Z0-9\-\_\%]+)\/?$/
 FILES_REGEX = /^\/files\/([a-zA-Z0-9\-\_\%\ \.\/\(\)]+)\/?$/
 PING_REGEX = /^\/ping\/([a-zA-Z0-9\-\_\.]+)\/?$/
 QUERY_REGEX = /^\/([a-zA-Z0-9_]+s)\/?$/
@@ -81,6 +82,40 @@
         else if request.method == 'DELETE'
           db.presetsCollection.remove({'_id' : presetName}, (err, removedCount) ->
             db.memcached.del('presets', (err, res) ->
+            )
+            db.memcached.del('presets_hash', (err, res) ->
+            )
+            if err
+              response.writeHead(500)
+              response.end(err)
+              return
+            response.writeHead(200)
+            response.end()
+          )
+        else
+          response.writeHead 405, {'Allow': 'PUT, DELETE'}
+          response.end('405 Method Not Allowed')
+      else if OBJECTS_REGEX.test(urlParts.pathname)
+        objectName = querystring.unescape(OBJECTS_REGEX.exec(urlParts.pathname)[1])
+        if request.method == 'PUT'
+          object = JSON.parse(body)
+          object._id = objectName
+
+          db.objectsCollection.save(object, (err) ->
+            db.memcached.del('objects', (err, res) ->
+            )
+            db.memcached.del('presets_hash', (err, res) ->
+            )
+            if err
+              response.writeHead(500)
+              response.end(err)
+              return
+            response.writeHead(200)
+            response.end()
+          )
+        else if request.method == 'DELETE'
+          db.objectsCollection.remove({'_id' : objectName}, (err, removedCount) ->
+            db.memcached.del('objects', (err, res) ->
             )
             db.memcached.del('presets_hash', (err, res) ->
             )


Remove unused next argument in error handler

--- app.coffee
+++ app.coffee
@@ -67,7 +67,7 @@
 else
   # Production error handler
   # No stacktraces leaked to user
-  app.use (err, req, res, next) ->
+  app.use (err, req, res) ->
     err.status = err.status or 500
     res.status err.status
     res.render "error",


More logging

More logging

--- app.coffee
+++ app.coffee
@@ -13,13 +13,14 @@
 tasks = [
 	(callback) ->
 		if state.get('virgin')
-			console.log('Device is virgin. Boostrapping')
+			console.log('Device is virgin. Bootstrapping')
 			handler = (error) ->
 				if error
 					console.log('Bootstrapping failed with error', error)
 					console.log('Trying again in 10s')
 					setTimeout((-> bootstrap(handler)), 10000)
 				else
+					console.log('Bootstrapping successful')
 					state.set('virgin', false)
 					callback()
 			bootstrap(handler)


Implemented #38

Implemented #38

--- app.coffee
+++ app.coffee
@@ -43,6 +43,7 @@
         } else {
             $('.game-module').hide();
             active_element.show();
+            $("html, body").animate({ scrollTop: 0 }, "fast");
         }
     }
     $scope.$on('switchToModule', vm.switchToModule);  // module switching via events


Do not auto delete the cloudsdale push exchange.

--- app.coffee
+++ app.coffee
@@ -98,7 +98,7 @@

   rabbit = connectRabbit(process.env.AMQP_URL || "amqp://guest:guest@localhost")
   rabbit.on "ready", ->
-    rabbit.exchange "cloudsdale.push", { type: "direct", auto_delete: false }, (exchange) ->
+    rabbit.exchange "cloudsdale.push", { type: "direct", autoDelete: false }, (exchange) ->
       rabbit.queue "cloudsdale.push", { passive: false, durable: true, noDeclare: false, autoDelete: false }, (queue) ->
         queue.bind(exchange, "#")
         queue.subscribe { ack: false }, (message, headers, deliveryInfo) ->


Make use of CoffeeScript features

Make use of CoffeeScript features

--- file/js/jCounter.jQuery.coffee
+++ file/js/jCounter.jQuery.coffee
@@ -2,7 +2,7 @@
 # jCounter - a simple character counter
 #
 # @author	Maximilian Mader
-# @copyright	2011 Maximilian Mader
+# @copyright	2011 - 2013 Maximilian Mader
 # @license	Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>
 # @package	jQuery.jCounter
 ###
@@ -28,22 +28,23 @@
 		@.on 'keypress keyup', $.proxy () ->
 			length = if options.countUp then @.val().length else max - @.val().length

-			if options.countUp && max > 0
-				if length < max / 2
-					color = 1
-				else if max / 2 < length <= max / 1.2
-					color = 2
+			color = (if options.countUp
+					if max > 0
+						if length < max / 2
+							1
+						else if max / 2 < length <= max / 1.2
+							2
+						else
+							3
+					else
+						1
 				else
-					color = 3
-			else if options.countUp
-				color = 1
-			else
-				if max / 2 < length
-					color = 1
-				else if max / 6 <= length <= max / 2
-					color = 2
-				else
-					color = 3
+					if max / 2 < length
+						1
+					else if max / 6 <= length <= max / 2
+						2
+					else
+						3)

 			jCounterContainer.text(length).removeClass().addClass "#{options.counterClass} color-#{color}"
 		, @


:memo: Doc TextBuffer::isEmpty

:memo: Doc TextBuffer::isEmpty

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -91,7 +91,8 @@
   getLines: ->
     @lines.slice()

-  # Public:
+  # Public: Returns a {Boolean}, `true` if this buffer has no text, `false`
+  # otherwise.
   isEmpty: ->
     @getLastRow() is 0 and @lineLengthForRow(0) is 0


Fix colors for HSL API.

Fix colors for HSL API.

--- src/transit-map.coffee
+++ src/transit-map.coffee
@@ -29,12 +29,17 @@
         CAR: hslColors.walk
         BICYCLE: hslColors.walk
         WAIT: hslColors.wait
-        0: hslColors[2]
-        1: hslColors[6]
-        2: hslColors[12]
-        3: hslColors[5]
-        4: hslColors[7]
-        109: hslColors[12]
+        BUS: hslColors[1]
+        FERRY: hslColors[7]
+        TRAM: hslColors[2]
+        SUBWAY: hslColors[6]
+        RAIL: hslColors[12]
+        # 0: hslColors[2] # tram
+        # 1: hslColors[6] # metro
+        # 2: hslColors[12] # commuter trains
+        # 3: hslColors[5] # regional bus lines
+        # 4: hslColors[7] # ferry
+        # 109: hslColors[12] # commuter trains

     class TransitMapMixin
         initializeTransitMap: (opts) ->


track.set('trackGap', 3) now updates the whole track correctly instantly.  A few lingering issues though, especially with remove.

--- src/woodentrack.coffee
+++ src/woodentrack.coffee
@@ -31,10 +31,12 @@
 			@_listeners[event.type].forEach (listener) -> listener.call(this, event)

 	# update property and fire "change" event
-	# todo: use 'dirty' state to recalculate connection transforms when gridSize or trackGap are changed
 	set: (property, value) ->
 		if value!=@[property]
 			@[property]=value
+			switch property
+				when 'trackGap'
+					@_gapTransform = new Transform(value, 0, 0)
 			@_fire { type: 'change', target: @ }

 	# available connection codes


formatting

formatting

--- client/app/MainApp/ApplicationManager.coffee
+++ client/app/MainApp/ApplicationManager.coffee
@@ -39,8 +39,7 @@
     else
       @forceQuit path

-  setFrontApp:(app)->
-    @frontApp = app
+  setFrontApp:(app)-> @frontApp = app

   getFrontApp:-> @frontApp


Client: Activity: Updated formatBlockquotes util method to check block quote has lang

--- client/app/lib/util/formatBlockquotes.coffee
+++ client/app/lib/util/formatBlockquotes.coffee
@@ -7,6 +7,12 @@
   for part, index in parts
     blockquote = index %% 2 is 1

-    parts[index] = "\n```#{Encoder.htmlDecode part}\n```\n"  if blockquote
+    if blockquote
+      if match = part.match /^\w+/
+        [lang] = match
+        if not hljs.getLanguage(lang) or not part.match /^\w+\s*\n/
+          part = "\n#{part}"
+
+      parts[index] = "\n```#{Encoder.htmlDecode part}\n```\n"

   parts.join ''


Fix #16

Fix #16

--- client/app/scripts/services/tetromino.coffee
+++ client/app/scripts/services/tetromino.coffee
@@ -19,6 +19,7 @@
           rows: 7
           score_value: 1000
           checking: false
+          removing: false
           in_progress: true
           plummetting_block: false
           sliding_block: false
@@ -127,7 +128,7 @@

       check_for_tetrominos: ->
         return unless @info.in_progress
-        return if @info.checking
+        return if @info.checking || @info.removing
         @info.checking = true
         for block in @blocks
           continue unless block && block.locked && !block.active
@@ -167,6 +168,7 @@

       # Animate dropping the given block to the given x coordinate.
       plummet_block: (block, x, on_land_callback) ->
+        return if block.x == x
         drop_single_block_interval = undefined
         drop_single_block = =>
           block.plummetting = true
@@ -203,18 +205,20 @@

       drop_blocks: ->
         return unless @info.in_progress
+        return if @info.removing
+        last_row_x = @info.rows - 1
         for block in @blocks
           continue unless block
           continue if block.sliding
-          active_or_not_locked = block.active || !block.locked
-          if block.x == @info.rows - 1 && active_or_not_locked
-            block.locked = true
-            block.active = false
-            @on_block_land block
-          if @is_block_directly_below(block.x, block.y) && active_or_not_locked
-            block.locked = true
-            block.active = false
-            @on_block_land block
+          if block.active || !block.locked
+            if block.x == last_row_x
+              block.locked = true
+              block.active = false
+              @on_block_land block
+            if @is_block_directly_below(block.x, block.y)
+              block.locked = true
+              block.active = false
+              @on_block_land block
           continue if block.locked
           block.x++

@@ -236,12 +240,15 @@

       remove_blocks: (to_remove) ->
         return unless @info.in_progress
+        return if @info.removing
+        @info.removing = true
         ids_to_remove = to_remove.map((b) -> b.id)
         idx = @blocks.length - 1
         while idx >= 0
           if ids_to_remove.indexOf(@blocks[idx].id) > -1
             @blocks.splice(idx, 1)
           idx--
+        @info.removing = false
         @info.current_score += @info.score_value
         @increment_level_if_necessary()
         on_top = @blocks_on_top(to_remove)


client: home: remove virtualmachines tab

--- client/home/lib/virtualmachines/index.coffee
+++ client/home/lib/virtualmachines/index.coffee
@@ -1,45 +0,0 @@
-kd                                   = require 'kd'
-Promise                              = require 'bluebird'
-HomeVirtualMachinesVirtualMachines   = require './homevirtualmachinesvirtualmachines'
-HomeVirtualMachinesConnectedMachines = require './homevirtualmachinesconnectedmachines'
-HomeVirtualMachinesSharedMachines    = require './homevirtualmachinessharedmachines'
-
-
-SECTIONS =
-  'Virtual Machines'   : HomeVirtualMachinesVirtualMachines
-  'Connected Machines' : HomeVirtualMachinesConnectedMachines
-  'Shared Machines'    : HomeVirtualMachinesSharedMachines
-
-header = (title) ->
-  new kd.CustomHTMLView
-    tagName  : 'header'
-    cssClass : 'HomeAppView--sectionHeader'
-    partial  : title
-
-
-section = (name) ->
-  new (SECTIONS[name] or kd.View)
-    tagName  : 'section'
-    cssClass : "HomeAppView--section #{kd.utils.slugify name}"
-
-
-module.exports = class HomeVirtualMachines extends kd.CustomScrollView
-
-  constructor: (options = {}, data) ->
-
-    options.cssClass = kd.utils.curry 'HomeAppView--scroller', options.cssClass
-
-    super options, data
-
-    kd.singletons.mainController.ready =>
-
-      @wrapper.addSubView header 'Virtual Machines'
-      @wrapper.addSubView section 'Virtual Machines'
-
-      @wrapper.addSubView header 'Connected Machines'
-      @wrapper.addSubView section 'Connected Machines'
-
-      @wrapper.addSubView header 'Shared Machines'
-      @wrapper.addSubView section 'Shared Machines'
-
-


Set props on self rather than prototype

Set props on self rather than prototype

Ampersand-state ver 5 uses `Object.keys` when an object is passed to
`set`.  In order to allow a Grid.Selections instance to be be passed to
Query.Fields the properties need to be set directly

--- client/lanes/components/grid/Selections.cjsx
+++ client/lanes/components/grid/Selections.cjsx
@@ -12,7 +12,8 @@
         <input type="checkbox" checked={selected} onChange={@onChange} />

-class Lanes.Components.Grid.Selections
+
+DEFAULTS =
     id: 'selected'
     query: false
     textAlign: 'center'
@@ -21,7 +22,9 @@
     sortBy: (row, indx, all) ->
         false == this.xtraData(indx)?.selected

+class Lanes.Components.Grid.Selections
     constructor: (options) ->
+        _.extend(@, DEFAULTS)
         @onChange = options.onChange
         @choices = {}
         _.bindAll(@, 'onColumnClick')


Only create the class once

Only create the class once

--- client/modules/packages/limiter/index.coffee
+++ client/modules/packages/limiter/index.coffee
@@ -1,7 +1,7 @@

 errors = require 'errors'

-exports.$errorType = -> class LimiterThresholdError extends errors.BaseError
+class LimiterThresholdError extends errors.BaseError

 	constructor: (message, @time) -> super

@@ -9,3 +9,5 @@
 	template: ":message You may try again :time."

 	toJSON: -> [@key, @message, @time]
+
+exports.$errorType = -> LimiterThresholdError


Invalidate all visualizations when filter is changed. PP-141

--- client/src/scripts/scoring-sheet-view.coffee
+++ client/src/scripts/scoring-sheet-view.coffee
@@ -596,7 +596,6 @@

   _visualizationTypes = [ _metricsVisualizationType, _thresholdVisualizationType ]

-
   initialize = (scorings) ->
     _metricFrame = createMetricFrameFromScorings scorings
     scoringVariable.domain = map _metricFrame.metrics, (metric) ->
@@ -608,7 +607,7 @@
     _allFilters = [ _scoringFilter, _metricTypeFilter, _metricCriteriaFilter ]
     updateFiltering()
     _metricTable createMetricTable _metricFrame
-    _visualizations.push addThresholdVisualization thresholdVariablesIndex.fpr, thresholdVariablesIndex.tpr, yes
+    _visualizations.push createThresholdVisualizationPane thresholdVariablesIndex.fpr, thresholdVariablesIndex.tpr, yes

   updateFiltering = ->
     _filteredMetricVariables = filter _metricFrame.metricVariables, (variable) ->
@@ -624,6 +623,12 @@

   invalidate = ->
     _metricTable createMetricTable()
+    newVisualizations = []
+    forEach _visualizations(), (oldVisualization) ->
+      { type, variableX, variableY } = oldVisualization.data
+      createVisualizationPane type, variableX, variableY, (visualization) ->
+        newVisualizations.push visualization
+    _visualizations newVisualizations

   createMetricTable = ->
     [ table, thead, tbody, tr, th, thAsc, thDesc, td ] = geyser.generate words 'table.table.table-condensed thead tbody tr th th.y-sorted-asc th.y-sorted-desc td'
@@ -694,8 +699,8 @@
       _visualizations.splice index, 1, newVisualization
     return

-  addMetricsVisualization = (variableX, variableY) ->
-    rendering =  createMetricsVisualization _metricFrame.metrics, variableX, variableY, (metric) ->
+  createMetricsVisualizationPane = (variableX, variableY) ->
+    rendering =  createMetricsVisualization _filteredMetrics, variableX, variableY, (metric) ->
       _.inspect
         content: createMetricInspection _metricFrame.metricVariables, metric
         template: 'geyser'
@@ -718,8 +723,8 @@

     self

-  addThresholdVisualization = (variableX, variableY, showReferenceLine) ->
-    rendering =  createThresholdVisualization _metricFrame.metrics, variableX, variableY, showReferenceLine, (metric, index) ->
+  createThresholdVisualizationPane = (variableX, variableY, showReferenceLine) ->
+    rendering =  createThresholdVisualization _filteredMetrics, variableX, variableY, showReferenceLine, (metric, index) ->
       _.inspect
         content: createThresholdInspection _metricFrame.thresholdVariables, metric, index
         template: 'geyser'
@@ -741,6 +746,13 @@

     self

+  createVisualizationPane = (visualizationType, variableX, variableY, go) ->
+    switch visualizationType
+      when _metricsVisualizationType
+        go createMetricsVisualizationPane variableX, variableY
+      when _thresholdVisualizationType
+        go createThresholdVisualizationPane variableX, variableY, no
+
   configureVisualization = (caption, type, variableX, variableY, go) ->
     parameters =
       visualizationTypes: _visualizationTypes
@@ -751,11 +763,7 @@
     _.configureScoringVisualization caption, parameters, (action, response) ->
       switch action
         when 'confirm'
-          switch response.visualizationType
-            when _metricsVisualizationType
-              go addMetricsVisualization response.variableX, response.variableY
-            when _thresholdVisualizationType
-              go addThresholdVisualization response.variableX, response.variableY, no
+          createVisualizationPane response.visualizationType, response.variableX, response.variableY, go

   addVisualization = ->
     configureVisualization 'Add Visualization', _metricsVisualizationType, _metricsVisualizationType.variables[0], _metricsVisualizationType.variables[1], (visualization) -> _visualizations.push visualization


removed some duplicate data

removed some duplicate data

--- app/client/bootstrap/alert.coffee
+++ app/client/bootstrap/alert.coffee
@@ -1,9 +0,0 @@
-Meteor.startup ->
-  sAlert.config
-    effect: 'genie'
-    position: 'top-right'
-    timeout: 2000
-    html: false
-    onRouteClose: true
-    stack: true
-    offset: 60


Move i18next defaults out of class.

Move i18next defaults out of class.

--- app/coffee/plugins/i18next.coffee
+++ app/coffee/plugins/i18next.coffee
@@ -12,24 +12,24 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.

+defaultOptions = {
+    postProcess: "lodashTemplate"
+    fallbackLng: "en"
+    useLocalStorage: false
+    localStorageExpirationTime: 60*60*24*1000 # 1 day
+    ns: 'app'
+    resGetPath: 'locales/__lng__/__ns__.json'
+    getAsync: false
+}

 class I18NextService extends TaigaBaseService
     @.$inject = ["$rootScope", "$q"]
+
     constructor: (@rootScope, @q) ->
         i18n.addPostProcessor "lodashTemplate", (value, key, options) ->
             template = _.template(value)
             return template(options.scope)
         @t = @translate
-
-    _defaultOptions: {
-        postProcess: "lodashTemplate"
-        fallbackLng: "en"
-        useLocalStorage: false
-        localStorageExpirationTime: 60*60*24*1000 # 1 day
-        ns: 'app'
-        resGetPath: 'locales/__lng__/__ns__.json'
-        getAsync: false
-    }

     setLang: (lang) ->
         $rootScope.currentLang = lang
@@ -45,7 +45,7 @@

     initialize: (async=false, defaultLang="en") ->
         # Put to rootScope a initial values
-        options = _.clone(@._defaultOptions, true)
+        options = _.clone(defaultOptions, true)
         options.lng = @rootScope.currentLang = defaultLang

         if async


fix: notify report errors

fix: notify report errors

--- app/components/file-header.coffee
+++ app/components/file-header.coffee
@@ -22,9 +22,9 @@
       .then (result) ->
         window.open result.url
       .catch (error) ->
+        that.get("notify").error "Please wait while the report is getting generated"
         for error in error.errors
           that.get("notify").error error.detail?.message
-        that.get("notify").error "Please wait while the report is getting generated"

     requestManual: ->
       that = @


Increase hit radius of sims

Increase hit radius of sims

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -683,8 +683,7 @@
         for index, annotation of @annotations
           if trainingType is 'lensed_galaxy'
             r2 = (annotation.x - 220)*(annotation.x - 220) + (annotation.y - 220)*(annotation.y - 220)
-            @isLensMarked = if r2 < 400 then true else false
-            console.log r2, @isLensMarked, annotation.x, annotation.y
+            @isLensMarked = if r2 < 900 then true else false
           else
             @isLensMarked = @checkImageMask(annotation.x, annotation.y)


aria close is not considered a real aria prop

--- shared/specs/components/buttons/close-button.spec.coffee
+++ shared/specs/components/buttons/close-button.spec.coffee
@@ -9,6 +9,5 @@

   it 'has proper classes', ->
     Testing.renderComponent( Button, props: @props ).then ({dom}) ->
-      expect(dom.getAttribute('aria-role')).equal('close')
       expect(dom.tagName).equal('BUTTON')
       expect(dom.classList.contains('openstax-close-x')).to.be.true


Tests for mixed-case pragmas (#109)

Tests for mixed-case pragmas (#109)

--- spec/language-haskell-spec.coffee
+++ spec/language-haskell-spec.coffee
@@ -420,6 +420,28 @@
                             [2, ['punctuation.definition.comment.block.start.haskell']]
                             [4, ['punctuation.definition.comment.block.end.haskell']]
                             [6, ['punctuation.definition.comment.block.end.haskell']]]]
+
+  describe "pragmas", ->
+    it "parses pragmas", ->
+      g = grammarExpect grammar, '{-# LANGUAGE OverloadedStrings #-}'
+      g.toHaveTokens [['{-#', ' ', 'LANGUAGE', ' OverloadedStrings ', '#-}']]
+      g.toHaveScopes [['source.haskell', 'meta.preprocessor.haskell']]
+      g.tokenToHaveScopes [[[2, ['keyword.other.preprocessor.haskell']]]]
+
+  describe "pragmas", ->
+    it "parses lowercase pragmas", ->
+      g = grammarExpect grammar, '{-# language OverloadedStrings #-}'
+      g.toHaveTokens [['{-#', ' ', 'language', ' OverloadedStrings ', '#-}']]
+      g.toHaveScopes [['source.haskell', 'meta.preprocessor.haskell']]
+      g.tokenToHaveScopes [[[2, ['keyword.other.preprocessor.haskell']]]]
+
+  describe "pragmas", ->
+    it "parses mixed case pragmas", ->
+      g = grammarExpect grammar, '{-# lanGuaGE OverloadedStrings #-}'
+      g.toHaveTokens [['{-#', ' ', 'lanGuaGE', ' OverloadedStrings ', '#-}']]
+      g.toHaveScopes [['source.haskell', 'meta.preprocessor.haskell']]
+      g.tokenToHaveScopes [[[2, ['keyword.other.preprocessor.haskell']]]]
+
   describe "instance", ->
     it "recognizes instances", ->
       g = grammarExpect grammar, 'instance Class where'
@@ -432,6 +454,18 @@
                             ]]
     it "recognizes instance pragmas", ->
       for p in [ 'OVERLAPS', 'OVERLAPPING', 'OVERLAPPABLE', 'INCOHERENT' ]
+        g = grammarExpect grammar, "instance {-# #{p} #-} Class where"
+        g.toHaveTokens [['instance', ' ', '{-#', ' ', p, ' ', '#-}', ' ', 'Class', ' ', 'where']]
+        g.toHaveScopes [['source.haskell', 'meta.declaration.instance.haskell']]
+        g.tokenToHaveScopes [[[2, ['meta.preprocessor.haskell']]
+                              [3, ['meta.preprocessor.haskell']]
+                              [4, ['meta.preprocessor.haskell', 'keyword.other.preprocessor.haskell']]
+                              [5, ['meta.preprocessor.haskell']]
+                              [6, ['meta.preprocessor.haskell']]
+                              ]]
+
+    it "recognizes lowercase instance pragmas", ->
+      for p in [ 'overlaps', 'overlapping', 'overlappable', 'incoherent' ]
         g = grammarExpect grammar, "instance {-# #{p} #-} Class where"
         g.toHaveTokens [['instance', ' ', '{-#', ' ', p, ' ', '#-}', ' ', 'Class', ' ', 'where']]
         g.toHaveScopes [['source.haskell', 'meta.declaration.instance.haskell']]


:white_check_mark: Add specs

:white_check_mark: Add specs

--- spec/markdown-preview-spec.coffee
+++ spec/markdown-preview-spec.coffee
@@ -348,3 +348,37 @@
       expectPreviewInSplitPane()

       runs -> expect(preview.find('atom-text-editor')).toExist()
+
+  describe "GitHub style markdown preview", ->
+    beforeEach ->
+      atom.config.set 'markdown-preview.useGitHubStyle', false
+
+    it "renders markdown using the default style when GitHub styling is disabled", ->
+      waitsForPromise -> atom.workspace.open("subdir/simple.md")
+      runs -> atom.commands.dispatch workspaceElement, 'markdown-preview:toggle'
+      expectPreviewInSplitPane()
+
+      runs -> expect(preview.element.getAttribute('data-use-github-style')).toBeNull()
+
+    it "renders markdown using the GitHub styling when enabled", ->
+      atom.config.set 'markdown-preview.useGitHubStyle', true
+
+      waitsForPromise -> atom.workspace.open("subdir/simple.md")
+      runs -> atom.commands.dispatch workspaceElement, 'markdown-preview:toggle'
+      expectPreviewInSplitPane()
+
+      runs -> expect(preview.element.getAttribute('data-use-github-style')).toBe ''
+
+    it "updates the rendering style immediately when the configuration is changed", ->
+      waitsForPromise -> atom.workspace.open("subdir/simple.md")
+      runs -> atom.commands.dispatch workspaceElement, 'markdown-preview:toggle'
+      expectPreviewInSplitPane()
+
+      runs ->
+        expect(preview.element.getAttribute('data-use-github-style')).toBeNull()
+
+        atom.config.set 'markdown-preview.useGitHubStyle', true
+        expect(preview.element.getAttribute('data-use-github-style')).toBe ''
+
+        atom.config.set 'markdown-preview.useGitHubStyle', false
+        expect(preview.element.getAttribute('data-use-github-style')).toBeNull()


Adapt specs to use the new API

Adapt specs to use the new API

--- spec/tokenized-buffer-spec.coffee
+++ spec/tokenized-buffer-spec.coffee
@@ -903,7 +903,7 @@
         expect(tokenizedBuffer.tokenizedLineForRow(9).indentLevel).toBe 2
         expect(tokenizedBuffer.tokenizedLineForRow(10).indentLevel).toBe 2 # }

-  describe ".foldable on tokenized lines", ->
+  describe "::foldableRowsForRowRange(startRow, endRow)", ->
     changes = null

     beforeEach ->
@@ -915,74 +915,74 @@
         buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
       })
       fullyTokenize(tokenizedBuffer)
-      tokenizedBuffer.onDidChange (change) ->
-        delete change.bufferChange
-        changes.push(change)
-
-    it "sets .foldable to true on the first line of multi-line comments", ->
-      expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true
-      expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent
-      expect(tokenizedBuffer.tokenizedLineForRow(13).foldable).toBe true
-      expect(tokenizedBuffer.tokenizedLineForRow(14).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(15).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(16).foldable).toBe false
+
+    it "includes the first line of multi-line comments", ->
+      foldableRows = tokenizedBuffer.foldableRowsForRowRange(0, 16)
+      expect(foldableRows.has(0)).toBe true
+      expect(foldableRows.has(1)).toBe false
+      expect(foldableRows.has(2)).toBe false
+      expect(foldableRows.has(3)).toBe true # because of indent
+      expect(foldableRows.has(13)).toBe true
+      expect(foldableRows.has(14)).toBe false
+      expect(foldableRows.has(15)).toBe false
+      expect(foldableRows.has(16)).toBe false

       buffer.insert([0, Infinity], '\n')
-      expect(changes).toEqual [{start: 0, end: 1, delta: 1}]
-
-      expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe true
-      expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe false
-
-      changes = []
+
+      foldableRows = tokenizedBuffer.foldableRowsForRowRange(0, 3)
+      expect(foldableRows.has(0)).toBe false
+      expect(foldableRows.has(1)).toBe false
+      expect(foldableRows.has(2)).toBe true
+      expect(foldableRows.has(3)).toBe false
+
       buffer.undo()
-      expect(changes).toEqual [{start: 0, end: 2, delta: -1}]
-      expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true
-      expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent
-
-    it "sets .foldable to true on non-comment lines that precede an increase in indentation", ->
+
+      foldableRows = tokenizedBuffer.foldableRowsForRowRange(0, 3)
+      expect(foldableRows.has(0)).toBe true
+      expect(foldableRows.has(1)).toBe false
+      expect(foldableRows.has(2)).toBe false
+      expect(foldableRows.has(3)).toBe true # because of indent
+
+    it "includes non-comment lines that precede an increase in indentation", ->
       buffer.insert([2, 0], '  ') # commented lines preceding an indent aren't foldable
-      expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true
-      expect(tokenizedBuffer.tokenizedLineForRow(4).foldable).toBe true
-      expect(tokenizedBuffer.tokenizedLineForRow(5).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true
-      expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
-
-      changes = []
+
+      foldableRows = tokenizedBuffer.foldableRowsForRowRange(0, 8)
+      expect(foldableRows.has(1)).toBe false
+      expect(foldableRows.has(2)).toBe false
+      expect(foldableRows.has(3)).toBe true
+      expect(foldableRows.has(4)).toBe true
+      expect(foldableRows.has(5)).toBe false
+      expect(foldableRows.has(6)).toBe false
+      expect(foldableRows.has(7)).toBe true
+      expect(foldableRows.has(8)).toBe false
+
       buffer.insert([7, 0], '  ')
-      expect(changes).toEqual [{start: 6, end: 7, delta: 0}]
-      expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
-      expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
-
-      changes = []
+
+      foldableRows = tokenizedBuffer.foldableRowsForRowRange(6, 8)
+      expect(foldableRows.has(6)).toBe true
+      expect(foldableRows.has(7)).toBe false
+      expect(foldableRows.has(8)).toBe false
+
       buffer.undo()
-      expect(changes).toEqual [{start: 6, end: 7, delta: 0}]
-      expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true
-      expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
-
-      changes = []
+
+      foldableRows = tokenizedBuffer.foldableRowsForRowRange(6, 8)
+      expect(foldableRows.has(6)).toBe false
+      expect(foldableRows.has(7)).toBe true
+      expect(foldableRows.has(8)).toBe false
+
       buffer.insert([7, 0], "    \n      x\n")
-      expect(changes).toEqual [{start: 6, end: 7, delta: 2}]
-      expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
-      expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
-
-      changes = []
+
+      foldableRows = tokenizedBuffer.foldableRowsForRowRange(6, 8)
+      expect(foldableRows.has(6)).toBe true
+      expect(foldableRows.has(7)).toBe false
+      expect(foldableRows.has(8)).toBe false
+
       buffer.insert([9, 0], "  ")
-      expect(changes).toEqual [{start: 9, end: 9, delta: 0}]
-      expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
-      expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
-      expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
+
+      foldableRows = tokenizedBuffer.foldableRowsForRowRange(6, 8)
+      expect(foldableRows.has(6)).toBe true
+      expect(foldableRows.has(7)).toBe false
+      expect(foldableRows.has(8)).toBe false

   describe "when the buffer is configured with the null grammar", ->
     it "uses the placeholder tokens and does not actually tokenize using the grammar", ->


Fixed <code> links to page anchors in the docs.

--- website/index.coffee
+++ website/index.coffee
@@ -8,7 +8,7 @@
   renderID location.hash.replace("#", "") if location.hash

 $(document).on "click", "a[href^='#']", (event) ->
-  renderID event.target.href.toString().split("#")[1]
+  renderID event.currentTarget.href.toString().split("#")[1]

 renderID = (id) ->
   el = document.getElementById(id)


Modeify database schema

Modeify database schema


Add support for brackets preceded by special characters


triggering events

triggering events

--- app/assets/javascripts/evaluaciones_pregunta_kind_helper.js.coffee
+++ app/assets/javascripts/evaluaciones_pregunta_kind_helper.js.coffee
@@ -41,5 +41,5 @@
     $(".question_kinds_fields select").trigger "change"

 window.Helpers.EvaluacionesPreguntaKindHelper.init()
-$(document).on "nested:fieldAdded", (e) ->
-  window.Helpers.EvaluacionesPreguntaKindHelper.trigger()
+$(document).on "nested:fieldAdded", window.Helpers.EvaluacionesPreguntaKindHelper.trigger
+$(document).on "page:load", window.Helpers.EvaluacionesPreguntaKindHelper.trigger


fix data to _data

fix data to _data

--- src/intermediate/IntermediateModel.coffee
+++ src/intermediate/IntermediateModel.coffee
@@ -1,14 +1,14 @@
 class @IntermediateModel
   constructor: ->
     @_events = {}
-    @data = []
+    @_data = []
     @fetch()

   getData: ->
-    @data
+    @_data

   setData: (value) ->
-    @data = value
+    @_data = value

   emit: (eventName) ->
     for callback in @_events[eventName]


Retrieve the current location if the geolocation API is present and center the map on it.

--- src/main/assets/javascripts/simple.coffee
+++ src/main/assets/javascripts/simple.coffee
@@ -1,35 +1,3 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
-
-latitude = null
-longitude = null
-
-#if navigator && navigator.geolocation
-# navigator.geolocation.getCurrentPosition(geo_success, geo_error)
-#else
-# alert 'Geolocation is not supported.'
-#
-#initGeo = ->
-#  if Modernizr.geolocation
-#    navigator.geolocation.getCurrentPosition showMap
-#
-#
-#showMap = (position) ->
-#  latitude = position.coords.latitude
-#  longitude = position.coords.longitude
-#  mapOptions = {
-#    zoom: 15,
-#    mapTypeId: google.maps.MapTypeId.ROADMAP
-#  }
-#  map = new google.maps.Map(document.getElementById("map"), mapOptions)
-#  latlng = new google.maps.LatLng(latitude, longitude)
-#  map.setCenter(latlng)
-#
-#  geocoder = new google.maps.Geocoder()
-#  geocoder.geocode({'latLng': latlng}, addAddressToMap)
-#
-
   # locations = []
   # polyline = null
 #
@@ -275,7 +243,7 @@

   configureFeedBehaviour()

-initMap (lat, long) = ->
+initMap = (lat, long) ->
   map = L.map('map').setView([lat,long], 14)
   L.tileLayer('http://{s}.tile.cloudmade.com/aeb94991e883413e8262bd55def34111/997/256/{z}/{x}/{y}.png',{
     attribution: 'Made with love at RHoK Melbourne, Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade',
@@ -315,5 +283,13 @@

     $('div.messages').append($(html).data('geo', v.geo))

-$ ->
-  initMap (-37.793566209439,144.94111608134)
+showMapAtDefault = ->
+  initMap(-37.793566209439, 144.94111608134)
+
+showMapAtGeoPosition = (position) ->
+  initMap( position.coords.latitude, position.coords.longitude)
+
+if navigator && navigator.geolocation
+  navigator.geolocation.getCurrentPosition(showMapAtGeoPosition, showMapAtDefault)
+else
+  showMapAtDefault


Switched working capital to cash at bank.

--- public/javascripts/game.photography.coffee
+++ public/javascripts/game.photography.coffee
@@ -84,15 +84,20 @@
       updateMarkers()
       $('#takePic').show()
       newStats = @stats
-      newStats.workingCapital -= mark.playerAt.travelExpense
+      newStats.CAB -= mark.playerAt.travelExpense
       @updateStats(newStats)

     updateStats: (stats) ->
       @stats = stats
-      $('#infoOverlay #stats #workingCapital').text 'Working Capital $' + parseInt(@stats.workingCapital)
-      $('#infoOverlay #stats #capital').text 'Capital $' + parseInt((@stats.assets - @stats.liabilities))
-      $('#infoOverlay #stats #assets').text 'Current Assets $' + parseInt(@stats.assets)
+      assets = parseInt(@stats.assets + @stats.CAB)
+      workingCapital = parseInt(assets - @stats.liabilities)
+      $('#infoOverlay #stats #CAB').text 'Cash at Bank $' + parseInt(@stats.CAB)
       $('#infoOverlay #stats #liabilities').text 'Current Liabilities $' + parseInt(@stats.liabilities)
+      $('#infoOverlay #stats #assets').text 'Current Assets $' + assets
+      $('#infoOverlay #stats #workingCapital').text 'Working Capital $' + workingCapital
+      if workingCapital <= -1000
+        $('#gameEnd p').text 'You survived for ' + monthPassed + ' Months.';
+        $('#gameEnd').show();

   retrieveResources = (amount) ->
     reqParam = {
@@ -141,7 +146,7 @@

   mark = new player {lat: -25.363, lng: 151.044}, 'Mark', {'type':'self'} ,'https://developers.google.com/maps/documentation/javascript/images/custom-marker.png'
   mark.initTo(googleMap)
-  mark.updateStats({'workingCapital':1000, 'assets': 0, 'liabilities': 300 })
+  mark.updateStats({'CAB':1000, 'workingCapital': 0, 'assets': 0, 'liabilities': 300 })

   retrieveResources(parseInt(Math.random() * (100 - 20) + 20)).then (res) ->
     generateMarkers(processData(res))
@@ -156,7 +161,7 @@
   interest = 1.5
   endTurn = ->
     newStats = mark.stats
-    newStats.workingCapital -= mark.stats.liabilities
+    newStats.CAB -= mark.stats.liabilities
     mark.updateStats(newStats)
     for location in locations
       location.marker.setVisible(true)
@@ -205,11 +210,9 @@
     $('#loanOverlay').show()

   $('#confirmLoan').click ->
-    console.log $('#loanInput').val(), parseInt($('#loanInput').val())*(interest/10)
     newStats = mark.stats
     newStats.liabilities += parseInt($('#loanInput').val())+parseInt($('#loanInput').val())*(interest/10)
-    newStats.workingCapital += parseInt($('#loanInput').val())
-    console.log typeof(newStats.workingCapital)
+    newStats.CAB += parseInt($('#loanInput').val())
     mark.updateStats(newStats)

   return


Use nupkg version-formatting code from grunt-electron-installer

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -6,6 +6,7 @@
 fs = require 'fs-plus'
 GitHub = require 'github-releases'
 request = require 'request'
+{convertVersion} = require 'grunt-electron-installer'

 grunt = null

@@ -76,12 +77,8 @@
         {assetName: 'atom-api.json', sourcePath: 'atom-api.json'}
       ]
     when 'win32'
+      nupkgVersion = convertVersion(version)
       assets = [{assetName: 'atom-windows.zip', sourcePath: 'Atom'}]
-
-      # NuGet packages can't have dots in their pre-release name, so we remove
-      # those dots in `grunt-electron-installer` when generating the package.
-      nupkgVersion = version.replace(/\.(\d+)$/, '$1')
-
       for squirrelAsset in ['AtomSetup.exe', 'RELEASES', "atom-#{nupkgVersion}-full.nupkg", "atom-#{nupkgVersion}-delta.nupkg"]
         cp path.join(buildDir, 'installer', squirrelAsset), path.join(buildDir, squirrelAsset)
         assets.push({assetName: squirrelAsset, sourcePath: assetName})


Clarify logging when upload-assets is skipped on non-release branches

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -32,10 +32,14 @@
     cp path.join(docsOutputDir, 'api.json'), path.join(buildDir, 'atom-api.json')

   grunt.registerTask 'upload-assets', 'Upload the assets to a GitHub release', ->
-    grunt.log.ok("Starting upload-assets to #{repo} repo")
     releaseBranch = grunt.config.get('atom.releaseBranch')
     isPrerelease = grunt.config.get('atom.channel') is 'beta'
-    return unless releaseBranch?
+
+    unless releaseBranch?
+      grunt.log.ok("Skipping upload-assets to #{repo} repo because this is not a release branch")
+      return
+
+    grunt.log.ok("Starting upload-assets to #{repo} repo")

     doneCallback = @async()
     startTime = Date.now()


Added scoreboard navbar link

Added scoreboard navbar link

--- picoCTF-web/web/coffee/navbar.coffee
+++ picoCTF-web/web/coffee/navbar.coffee
@@ -61,6 +61,7 @@

 userNotLoggedIn =
   About: "/about"
+  Scoreboard: "/scoreboard"
   FAQ: "/faq"
   Learn: "/learn"
   Teachers: "/teachers"


Fixed the next reference in the series object.

--- .metalsmith/plugins/series.coffee
+++ .metalsmith/plugins/series.coffee
@@ -50,7 +50,7 @@
           pageNumber:  index
           prev:        prev
           next:        null
-        if prev? then prev.next = page
+        if prev? then prev.series.next = page
         prev = page

     done()


Server crash due to mimetype with newline

The mimetype string that is saved to the DB has a trailing newline-char.
This leads to the server crashing, when trying to download a file.

--- main.coffee
+++ main.coffee
@@ -308,6 +308,8 @@
     mimetype = null
     process.execFile '/usr/bin/file', ['-bi', req.files.files.path], (err, stdout) ->
       mimetype = unless err then stdout.toString()
+	  if mimetype.indexOf "\n"
+	    mimetype = mimetype.substr 0, mimetype.indexOf "\n"

       file = {
         name: req.files.files.name,


Fallback to un-nested if there were no matching parents.

--- src/batman.coffee
+++ src/batman.coffee
@@ -2591,6 +2591,7 @@
         if parentID
           delete options.data[key]
           return "#{plural}/#{parentID}/#{children}"
+      return children

     @::url = ->
       for key, plural of parents
@@ -2599,9 +2600,11 @@
           parentID = @get(key)
         if parentID
           url = "#{plural}/#{parentID}/#{children}"
-          if id = @get('id')
-            url += '/' + id
-          return url
+          break
+      url ||= children
+      if id = @get('id')
+        url += '/' + id
+      url

   # ### Query methods
   @classAccessor 'all',


different font stack for czech

different font stack for czech

--- @client/shared.coffee
+++ @client/shared.coffee
@@ -747,6 +747,9 @@
 body, h1, h2, h3, h4, h5, h6 {
   color: black; }

+html[lang='cs'] body, html[lang='cs'] input, html[lang='cs'] button, html[lang='cs'] textarea {
+  font-family: Helvetica, Verdana, Arial, 'Lucida Grande', 'Lucida Sans Unicode', sans-serif; }
+
 body, input, button, textarea {
   font-family: 'Avenir Next W01', 'Avenir Next', 'Lucida Grande', 'Lucida Sans Unicode', 'Helvetica Neue', Helvetica, Verdana, sans-serif; }


Updated shipping-rate-selection to use Checkout.setDelivery

--- app/assets/javascripts/sprangular/directives/shippingRatesSelection.coffee
+++ app/assets/javascripts/sprangular/directives/shippingRatesSelection.coffee
@@ -36,7 +36,7 @@

       $scope.loading = true

-      Checkout.update('payment').then ->
+      Checkout.setDelivery().then ->
         $scope.loading = false

     validateAddress($scope.address)


Lefay, pls stop showing this gif avery time we talk about asset magick

--- scripts/magic.coffee
+++ scripts/magic.coffee
@@ -1,5 +1,5 @@
 # DID SOMEBODY SAY MAGIC?

 module.exports = (robot) ->
-  robot.hear /.*magic[^k]*/i, (msg) ->
+  robot.hear /.*magic\s|$/i, (msg) ->
     msg.send "http://i.imgur.com/Mk76H.gif"


update fileTypes

update fileTypes

--- grammars/bridlensis.cson
+++ grammars/bridlensis.cson
@@ -1,4 +1,6 @@
 fileTypes: [
+  "bnsi"
+  "bnsh"
   "bridle-nsi"
   "bridle-nsis"
   "bridle-nsh"


Path finder for status updates.

Path finder for status updates.

--- client/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
+++ client/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
@@ -10,6 +10,7 @@
     decodedName = Encoder.htmlDecode name

     @smallInput = new KDInputView
+      testPath      : "status-update-input"
       cssClass      : "status-update-input warn-on-unsaved-data"
       placeholder   : "What's new #{decodedName}?"
       name          : 'dummy'


services endpoint: fix a typo, remove logs

--- server/lib/server/services-presence.coffee
+++ server/lib/server/services-presence.coffee
@@ -26,10 +26,6 @@
     else if genericServices[serviceUniqueName] < 0
       console.error 'Negative service count!'

-setInterval ->
-  console.log allServices
-, 3000
-
 koding.connect ->
   koding.monitorPresence
     join: (serviceKey) ->
@@ -53,7 +49,7 @@
 module.exports = (req, res) ->
   {params:{service}, query} = req

-  protocol = KONFIG.mq.webProtocol ? 'https:'
+  protocol = KONFIG.broker.webProtocol ? 'https:'

   genericServices = allServices[service]


Testing time of day in greeting

Testing time of day in greeting

--- scripts/hello.coffee
+++ scripts/hello.coffee
@@ -64,7 +64,7 @@

     # Weather
     getWeather msg, (currentWeather) ->
-    	msg.send greeting + '. ' + currentWeather + [msg.random pleasantry] + chatEnding
+    	msg.send currentHour + greeting + '. ' + currentWeather + [msg.random pleasantry] + chatEnding


Add another magic image, for variation

Add another magic image, for variation

--- scripts/magic.coffee
+++ scripts/magic.coffee
@@ -1,5 +1,9 @@
 # DID SOMEBODY SAY MAGIC?
+images = [
+  "http://i.imgur.com/Mk76H.gif",
+  "http://weknowmemes.com/wp-content/uploads/2013/07/hey-kids-wanna-buy-some-magic.jpg"
+]

 module.exports = (robot) ->
   robot.hear /.*magic(\s|$)/i, (msg) ->
-    msg.send "http://i.imgur.com/Mk76H.gif"
+    msg.send msg.random images


Fix "hubot you suck"

Fix "hubot you suck"

--- scripts/kicks.coffee
+++ scripts/kicks.coffee
@@ -8,6 +8,6 @@
   robot.hear /kicks hubot/i, (msg) ->
     msg.send "*oof*"

-  robot.hear /hubot you suck/i, (msg) ->
+  robot.hear /(hubot you suck|you suck hubot)/i, (msg) ->
     msg.send gifs.come_at_me_bro


Show spinner when changing cover

Show spinner when changing cover

--- resources/assets/coffee/react/profile-page/cover-selection.coffee
+++ resources/assets/coffee/react/profile-page/cover-selection.coffee
@@ -38,11 +38,15 @@
   onClick: (e) =>
     return if !@props.url?

+    $.publish 'user:cover:upload:state', [true]
+
     $.ajax laroute.route('account.cover'),
       method: 'post'
       data:
         cover_id: @props.name
       dataType: 'json'
+    .always ->
+      $.publish 'user:cover:upload:state', [false]
     .done (userData) ->
       $.publish 'user:update', userData
     .error osu.emitAjaxError(e.target)


gulp: stats.json only contain hash

gulp: stats.json only contain hash

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -153,6 +153,6 @@
     if err
       console.trace err
       return
-    statsJson = JSON.stringify stats.toJson()
+    statsJson = JSON.stringify {hash: stats.toJson().hash}
     fs.writeFileSync "#{__dirname}/#{cfg.paths.dist}/stats.json", statsJson
   .pipe gulp.dest cfg.paths.dist


Support WT status from informatics

Support WT status from informatics

--- server/testSystems/informatics/InformaticsSubmitDownloader.coffee
+++ server/testSystems/informatics/InformaticsSubmitDownloader.coffee
@@ -39,6 +39,7 @@

 TEST_STATUS_TO_OUTCOME =
     RT: "RE"
+    WT: "TL"

 export default class InformaticsSubmitDownloader extends TestSystemSubmitDownloader


Unblocking manage functions. Fixes #14

Unblocking manage functions. Fixes #14

--- server/manage.coffee
+++ server/manage.coffee
@@ -37,6 +37,8 @@

   Meteor.methods
     'sync-arxhiv-pdf-cache': ->
+      @unblock()
+
       if not Meteor.settings.AWS
         console.error "AWS settings missing"
         throw new Meteor.Error 500, "AWS settings missing"
@@ -141,6 +143,8 @@
       console.log "Done"

     'sync-arxhiv-metadata': ->
+      @unblock()
+
       console.log "Syncing arXiv metadata"

       # TODO: URL hardcoded - not good
@@ -261,6 +265,8 @@
       Meteor.call 'sync-local-pdf-cache' if count > 0

     'sync-local-pdf-cache': ->
+      @unblock()
+
       console.log "Syncing local PDF cache"

       count = 0
@@ -277,6 +283,8 @@
       Meteor.call 'process-pdfs' if count > 0

     'process-pdfs': ->
+      @unblock()
+
       console.log "Processing pending PDFs"

       Publications.find(cached: true, processed: {$ne: true}).forEach (publication) ->
@@ -288,6 +296,8 @@
       console.log "Done"

     'dummy-comments': ->
+      @unblock()
+
       console.log "Generating dummy comments"

       Publications.find(cached: true, processed: true, paragraphs: null).forEach (publication) ->
@@ -312,6 +322,8 @@
       console.log "Done"

     'dummy-summaries': ->
+      @unblock()
+
       console.log "Generating dummy summaries"

       Publications.find(cached: true, processed: true, paragraphs: $ne: null).forEach (publication) ->


realtime: update realtime controller for tab spaces

--- client/Main/realtimecontroller.coffee
+++ client/Main/realtimecontroller.coffee
@@ -1,57 +1,57 @@
 class RealtimeController extends KDController

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

-		super options, data
+    super options, data

-		{subscribekey} = KD.config.pubnub
+    {subscribekey} = KD.config.pubnub

-		@pubnub = PUBNUB.init subscribe_key : subscribekey
+    @pubnub = PUBNUB.init subscribe_key : subscribekey

-	# subscriptionData =
-	# 	serviceType: 'socialapi'
-	# 	group      : group.slug
-	# 	channelType: socialApiChannel.typeConstant
-	# 	channelName: socialApiChannel.name
-	authenticate: (options = {}, callback = noop) ->
+  # subscriptionData =
+  #   serviceType: 'socialapi'
+  #   group      : group.slug
+  #   channelType: socialApiChannel.typeConstant
+  #   channelName: socialApiChannel.name
+  authenticate: (options = {}, callback = noop) ->

-		{ channelName, typeConstant, group } = options
-		return callback { message: "channel name is not defined" }  unless channelName
+    { channelName, typeConstant, group } = options
+    return callback { message: "channel name is not defined" }  unless channelName

-		xhr = new XMLHttpRequest()
-		endPoint = "/api/gatekeeper/channel/authenticate"
-		xhr.open 'POST', endPoint, true
-		xhr.setRequestHeader "Content-Type", "application/json;"
-		xhr.onreadystatechange = (result) =>
-			response = JSON.parse xhr.responseText
-			# 0     - connection failed
-			# >=400 - http errors
-			if xhr.status is 0 or xhr.status >= 400
-	    	return callback { message: response.description}
+    xhr = new XMLHttpRequest()
+    endPoint = "/api/gatekeeper/channel/authenticate"
+    xhr.open 'POST', endPoint, true
+    xhr.setRequestHeader "Content-Type", "application/json;"
+    xhr.onreadystatechange = (result) =>
+      response = JSON.parse xhr.responseText
+      # 0     - connection failed
+      # >=400 - http errors
+      if xhr.status is 0 or xhr.status >= 400
+        return callback { message: response.description}

-			return if xhr.readyState isnt 4
+      return if xhr.readyState isnt 4

-			if xhr.status not in [200, 304]
-				return callback { message: response.description}
+      if xhr.status not in [200, 304]
+        return callback { message: response.description}

-			return callback null, response
+      return callback null, response

-		xhr.send JSON.stringify {name: channelName, typeConstant, group}
+    xhr.send JSON.stringify {name: channelName, typeConstant, group}

-	subscribe: (options = {}) ->
-		{ channelName, typeConstant, group } = options
-		return KD.showError { message: "channel name is not set" }  unless channelName
+  subscribe: (options = {}) ->
+    { channelName, typeConstant, group } = options
+    return KD.showError { message: "channel name is not set" }  unless channelName

-		that = this
-		@pubnub.subscribe
-			channel : prepareChannelName options
-			message : (message, env, channel) =>
-				if message
-					{eventName, body} = message
-					that.emit eventName, body
+    that = this
+    @pubnub.subscribe
+      channel : prepareChannelName options
+      message : (message, env, channel) =>
+        if message
+          {eventName, body} = message
+          that.emit eventName, body

-	prepareChannelName = (options) ->
-   	{ channelName, typeConstant, group } = options
+  prepareChannelName = (options) ->
+    { channelName, typeConstant, group } = options

-   	return "#{group}-#{typeConstant}-#{channelName}"
+    return "#{group}-#{typeConstant}-#{channelName}"


Activity: new message default route handling on activity appview

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -33,13 +33,19 @@

-  lazyLoadThresholdReached: -> @tabs.getActivePane().emit 'LazyLoadThresholdReached'
+  lazyLoadThresholdReached: -> @tabs.getActivePane()?.emit 'LazyLoadThresholdReached'

   viewAppended: ->

     @addSubView @sidebar
     @addSubView @tabs
+
+
+  showNewMessageModal: ->
+
+    unless @tabs.getActivePane()
+      @open 'public'

   open: (type, slug) ->


shortcuts: remove dangling console.log statements :horse:

--- client/app/lib/shortcuts/index.coffee
+++ client/app/lib/shortcuts/index.coffee
@@ -171,9 +171,6 @@
         matchesEnabled = override.enabled is enabled
         matchesBinding = _.isEqual klass._getPlatformBinding(model), override.binding

-        console.log model, override
-        console.log matchesEnabled, matchesBinding
-
         unless matchesEnabled and matchesBinding
           @emit 'change', collection,
             @shortcuts.update collection.name, model.name, {


Router: Try-catch around building routes

--- server/router.coffee
+++ server/router.coffee
@@ -110,7 +110,10 @@
   # Build route definitions based on the routes file.
   buildRoutes: ->
     routeBuilder = require(@config.paths.routes)
-    routeBuilder(@route)
+    try
+      routeBuilder(@route)
+    catch e
+      throw new Error("Error building routes: #{e.message}")
     @routes()

   # Returns current route definitions.


Add more comments to make stuff clearer

Add more comments to make stuff clearer

--- server/routes.coffee
+++ server/routes.coffee
@@ -26,7 +26,7 @@
             # First user!
             newUid = 1

-          # Add user data
+          # Add user data from twitter API response
           req.user.twitterId = req.user.id
           req.user.twitterUsername = req.user.username
           photoURL = req.user.photos[0].value
@@ -46,7 +46,7 @@
           newUser.save (err, newUserSave) ->
             res.send '<header><meta http-equiv="refresh" content="0; url=/" /></header>'
       else
-        # User has data, add it
+        # User has data, add it from DB response
         req.user.twitterId = data.twitterId
         req.user.twitterUsername = data.twitterUsername
         req.user.twitterProfilePicture = data.twitterProfilePicture


amqp -> mq

amqp -> mq

--- server/server.coffee
+++ server/server.coffee
@@ -1,6 +1,6 @@
 {argv} = require 'optimist'

-{webPort, mongo, amqp} = require argv.c
+{webPort, mongo, mq} = require argv.c
 webPort = argv.p if argv.p?

 projectRoot = require('path').join(__dirname, '/..')


Set .psqlrc file type to "source.sql" in .atom/config.cson.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -46,6 +46,7 @@
     "^.gitignore": "source.git-config"
     "^.gvimrc.after": "source.viml"
     "^.profile$": "source.shell"
+    "^.psqlrc": "source.sql"
     "^.shared_env.*": "source.shell"
     "^.spacemacs$": "source.lisp"
     ".*.sshconfig": "source.ssh-config"


Main: lazy top threshold to fire when hit scrolltop 0

--- client/Core/mainviewcontroller.coffee
+++ client/Core/mainviewcontroller.coffee
@@ -62,7 +62,7 @@
       {innerHeight}             = window

       # return when it pulls the page on top
-      return lastPos = innerHeight  if scrollTop <= 0
+      return lastPos = innerHeight  if scrollTop < 0

       # return when it pulls the page at the bottom
       return  if scrollHeight - scrollTop < innerHeight


Webterm: Keep plus button visible all the time

--- client/Terminal/webtermappview.coffee
+++ client/Terminal/webtermappview.coffee
@@ -6,7 +6,7 @@

     @tabHandleContainer = new ApplicationTabHandleHolder
       delegate          : this
-      addPlusHandle     : no
+      addPlusHandle     : yes

     @tabView = new ApplicationTabView
       delegate                  : this
@@ -210,9 +210,6 @@

     @messagePane.hide()

-    if not @tabHandleContainer.plusHandle
-      @tabHandleContainer.addPlusHandle()
-
     if @_secondTab
       KD.mixpanel "Open new Webterm tab, success"


correct copyFrom and add clone()

correct copyFrom and add clone()

--- client/lanes/models/Collection.coffee
+++ client/lanes/models/Collection.coffee
@@ -33,8 +33,10 @@

     # replace these models from other collection
     copyFrom: (collection) ->
-        this.set(collection)
+        this.set(collection.models)

+    clone: ->
+        new @constructor( @invoke( 'clone' ), @options )

 }


Bugfix: pawn canvas had default size.

Bugfix: pawn canvas had default size.

--- maze.coffee
+++ maze.coffee
@@ -179,7 +179,7 @@
     container.append(grid)

     # make pawn
-    @pawn = $('<canvas></canvas>').css({
+    @pawn = $('<canvas width=70 height=70></canvas>').css({
        position: 'relative'
        top: @y * 71
        left: @x * 71


Get benchmark working with tags instead of tokens

--- benchmark/benchmark.coffee
+++ benchmark/benchmark.coffee
@@ -10,9 +10,10 @@

 tokenize = (grammar, content, lineCount) ->
   start = Date.now()
-  tokens = grammar.tokenizeLines(content)
+  {tags} = grammar.tokenizeLines(content)
   duration = Date.now() - start
-  tokenCount = tokens.reduce ((count, line) -> count + line.length), 0
+  tokenCount = 0
+  tokenCount++ for tag in tags when tag >= 0
   tokensPerMillisecond = Math.round(tokenCount / duration)
   console.log "Generated #{tokenCount} tokens for #{lineCount} lines in #{duration}ms (#{tokensPerMillisecond} tokens/ms)"


Taking padding into account when calculating size

--- framer/VekterTextLayer.coffee
+++ framer/VekterTextLayer.coffee
@@ -323,6 +323,7 @@
 				fontSize: 40
 				fontWeight: 400
 				lineHeight: 1.25
+				padding: 0
 			if not options.font? and not options.fontFamily?
 				options.fontFamily = @defaultFont()
 			@_styledText = new StyledText()
@@ -469,15 +470,23 @@
 		@_styledText.render()
 		@_updateHTMLScale()
 		parentWidth = if @parent? then @parent.width else Screen.width
-		calculatedSize = @_styledText.measure
-			width: if @autoWidth then parentWidth else @size.width
-			height: if @autoHeight then null else @size.height
+		constrainedWidth = if @autoWidth then parentWidth else @size.width
+		constrainedWidth -= (@_padding.left + @_padding.right)
+		if @autoHeight
+			constrainedHeight = null
+		else
+			constrainedHeight = @size.height - (@_padding.top + @_padding.bottom)
+		constraints =
+			width: constrainedWidth
+			height: constrainedHeight
 			multiplier: @context.pixelMultiplier
+
+		calculatedSize = @_styledText.measure constraints
 		@disableAutosizeUpdating = true
 		if calculatedSize.width?
-			@width = calculatedSize.width
+			@width = calculatedSize.width + @_padding.left + @_padding.right
 		if calculatedSize.height?
-			@height = calculatedSize.height
+			@height = calculatedSize.height + @_padding.top + @_padding.bottom
 		@disableAutosizeUpdating = false

 	defaultFont: ->


Use `atom.activatePackage` directly in specs

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -11,7 +11,7 @@
   beforeEach ->
     window.rootView = new RootView
     rootView.open('sample.js')
-    window.loadPackage("tabs")
+    atom.activatePackage("tabs")

   describe ".activate()", ->
     it "appends a tab bar all existing and new panes", ->


Fixed specs

Fixed specs

--- spec/util-spec.coffee
+++ spec/util-spec.coffee
@@ -1,7 +1,7 @@
 uri = require('../lib/util/uri-helpers')
 clone = require('../lib/util/clone-obj')

-xdescribe 'resolveURL()', ->
+describe 'resolveURL()', ->
   it 'should replace the last segment', ->
     expect(uri.resolveURL('a', 'b')).toBe 'b'
     expect(uri.resolveURL('x/y', 'z')).toBe 'x/z'
@@ -30,7 +30,7 @@
       prop:
         name: 'foo'
     circular.prop.otherProp = circular.prop
-    expect(-> clone(circular)).toThrow('unable dereference circular structures');
+    expect(-> clone(circular)).toThrow()

   it 'should permit shared subobjects', ->
     subobject =


Correcting error #130 - changing @stop to @pause()

This appears to fix issue #130:
https://github.com/Differential/accounts-entry/issues/130

@stop is depreciated in iron-router 0.7.0 and going forward we should use @pause().  When @stop is used in the entrySignOut route, BlazeLayout throws the following error in the console (client side):

Exception from Deps recompute function: Error: BlazeLayout: Sorry, couldn't find a template named entrySignOut. Are you sure you defined it?

Replaces @stop with @pause seems to fix the issue.

--- shared/router.coffee
+++ shared/router.coffee
@@ -24,7 +24,7 @@
       if AccountsEntry.settings.homeRoute
         Meteor.logout () ->
           Router.go AccountsEntry.settings.homeRoute
-      @stop()
+      @pause()

   @route 'entryResetPassword',
     path: 'reset-password/:resetToken'


lazy-requiring server/server module in syncer for easier testing

--- shared/syncer.coffee
+++ shared/syncer.coffee
@@ -1,4 +1,3 @@
-server = require('../server/server') if global.isServer
 qs = require('qs') if global.isServer

 methodMap =
@@ -29,6 +28,9 @@
     req.json = options.data
   else
     _.extend req.query, options.data
+
+  if !server?
+    server = require('../server/server') if global.isServer

   server.dataAdapter.makeRequest req, (err, response, body) ->
     err ||= getErrForResponse(response)


Add api `chat.messageExample`

Add api `chat.messageExample`

--- packages/rocketchat-api/server/routes.coffee
+++ packages/rocketchat-api/server/routes.coffee
@@ -15,6 +15,40 @@
 			'active'
 			'language'
 		]
+
+
+# Send Channel Message
+RocketChat.API.v1.addRoute 'chat.messageExamples', authRequired: true,
+	get: ->
+		return RocketChat.API.v1.success
+			body: [
+				token: Random.id(24)
+				channel_id: Random.id()
+				channel_name: 'general'
+				timestamp: new Date
+				user_id: Random.id()
+				user_name: 'rocket.cat'
+				text: 'Sample text 1'
+				trigger_word: 'Sample'
+			,
+				token: Random.id(24)
+				channel_id: Random.id()
+				channel_name: 'general'
+				timestamp: new Date
+				user_id: Random.id()
+				user_name: 'rocket.cat'
+				text: 'Sample text 2'
+				trigger_word: 'Sample'
+			,
+				token: Random.id(24)
+				channel_id: Random.id()
+				channel_name: 'general'
+				timestamp: new Date
+				user_id: Random.id()
+				user_name: 'rocket.cat'
+				text: 'Sample text 3'
+				trigger_word: 'Sample'
+			]

 # Send Channel Message


Add missing doc

Add missing doc

DESC: A

--- src/tau-checkbox-model/services/checkbox_model.js.coffee
+++ src/tau-checkbox-model/services/checkbox_model.js.coffee
@@ -25,6 +25,8 @@
         @_cm_data.model[cand] = if selected then true else false

     # List the selected candidates
+    #
+    # @return [Array<String>] The selected candidates.
     #
     list: =>
       results = []


add unbinding of resize utility callbacks

--- src/coffee/breadcrumbsEatr.coffee
+++ src/coffee/breadcrumbsEatr.coffee
@@ -505,7 +505,9 @@
             o.documentBody.off ".#{ pluginName }"

             # remove any events attached to window object
-            browserWindow.off o.resizeKey
+            browserWindow
+                .off(o.resizeKey)
+                .off o.resizeUtilKey

             # Remove Plugin Data
             o.el.removeData pluginName


Don't save noflo-browser runtime if it already exists

--- components/EnsureLocalRuntimes.coffee
+++ components/EnsureLocalRuntimes.coffee
@@ -7,7 +7,7 @@
   for runtime in runtimes
     # Check that we don't have the iframe runtime already
     if runtime.protocol is 'iframe' and runtime.address is iframeAddress
-      return runtime
+      return null
   iframeRuntime =
     label: 'NoFlo HTML5 environment'
     id: uuid()


fix mistake from rebasing to fix coach exercise spec

--- coach/test/exercise/index.spec.coffee
+++ coach/test/exercise/index.spec.coffee
@@ -27,9 +27,7 @@

   beforeEach ->
     updatedStep = _.clone(props.item)
-    @props =
-      id: '4573'
-      item: _.clone(step)
+    Collection.quickLoad(props.id, updatedStep)

   afterEach ->
     updatedStep = null


use array to build UPDATE statement in sql-formatter

--- src/dialects/sql-formatter.coffee
+++ src/dialects/sql-formatter.coffee
@@ -488,32 +488,22 @@
         ret += "VALUES (#{values.join(', ')})"

     update: (stmt) ->
-        #names = []
-        #values = []
-        #@fillNamesAndValues(stmt.values, names, values)
-
-        #ret = [
-            #"UPDATE #{@_doTargetTable(stmt.targetTable)} SET "
-            #("#{names[i]} = #{values[i]}" for i in names).join(", ")
-            #@where(stmt)
-        #]
-
-        #return ret.join('')
-
-        ret = "UPDATE #{@_doTargetTable(stmt.targetTable)} SET "
-
+        targetTable = @_doTargetTable(stmt.targetTable)
+
+        names = []
         values = []
-        for k, v of stmt.values
-            c = @_doInsertUpdateColumn(k)
-            continue unless c
-            values.push("#{c} = #{@f(v)}")
-
-        ret += values.join(', ')
-
-        ret += @where(stmt)
-        return ret
+        @fillNamesAndValues(stmt.values, names, values)
+
+        ret = [
+            "UPDATE #{targetTable} SET "
+            ("#{name} = #{values[i]}" for name, i in names).join(", ")
+            @where(stmt)
+        ]
+
+        return ret.join('')

     upsert: (stmt) ->
+        targetTable = @_doTargetTable(stmt.targetTable)
         names = []
         values = []
         @fillNamesAndValues(stmt.values, names, values)
@@ -524,7 +514,7 @@
         updates = (eq(c) for c in names when !_.contains(onColumns, c)).join(", ")

         ret = [
-            "MERGE #{@_doTargetTable(stmt.targetTable)} AS target"
+            "MERGE #{targetTable} AS target"
             "USING (SELECT " + values.join(', ') + ")"
             "AS source (" + names.join(', ') + ")"
             "ON (#{onClauses})"


Add option for creating the representative node if lookup fails

Supports the common 'get or create' use case if `options.create` is true

--- coffee/cilantro/models/context.coffee
+++ coffee/cilantro/models/context.coffee
@@ -51,9 +51,11 @@
         isTyped: ->
             @attributes.field? or @attributes.concept?

-        fetch: (query, options) ->
+        fetch: (query, options={}) ->
             if queryAttrs(@, query, options)
                 return @
+            if options.create isnt false
+                return new @constructor query

     # Branch-type node that acts as a container for other nodes. The `type`
@@ -89,18 +91,27 @@
                         return message
             return

-        fetch: (query, options) ->
-            if (node = super) then return node
+        fetch: (query, options={}) ->
+            # Set false to prevent shadowing the children below
+            create = options.create
+            options.create = false
+
+            if (node = super(query, options))
+                return node

             children = @get('children')
+
+            # Recurse on each child node for a match, converting raw
+            # attributes into nodes as needed
             for child, i in children
-                if queryAttrs(child, query, options)
-                    # If this a match, convert the child into an instance
-                    # for downstream use.
-                    if not (child instanceof ContextNodeModel)
-                        child = children[i] = getContextNodeModel(child, options)
-                    return child
-            return
+                if not (child instanceof ContextNodeModel)
+                    child = children[i] = getContextNodeModel(child, options)
+                if (node = child.fetch(query, options))
+                    return node
+
+            if create isnt false
+                @add(node = new @constructor query)
+                return node

         # If this is a deep save, recursively save children prior to
         # creating a copy to publicAttributes.


PUBDEV-978 Always exclude response column from ignored columns.

--- src/ext/components/model-input.coffee
+++ src/ext/components/model-input.coffee
@@ -128,7 +128,7 @@
   react _values, -> _selectedValues []

   _value = lift _selectedValues, (views) ->
-    for view in views
+    for view in views when not view.isUnavailable()
       view.value

   _availableValuesCaption = signal "0 items hidden"


Simplify serving index.html

Simplify serving index.html

--- server/server.coffee
+++ server/server.coffee
@@ -57,23 +57,12 @@
   app.use(authentication.initialize())
   app.use(authentication.session())

-
   #- setup routes
   routes(app)

-
   #- Serve index.html
-  try
+  app.all '*', (req, res) ->
     mainHTML = fs.readFileSync(path.join(__dirname, '../public', 'index.html'), 'utf8')
-  catch e
-    winston.error "Error modifying index.html: #{e}"
-
-  app.all '*', (req, res) ->
-    # insert the user object directly into the html so the application can have it immediately. Sanitize </script>
-#      data = mainHTML.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)).replace(/\//g, '\\/'))
-    res.header 'Cache-Control', 'no-cache, no-store, must-revalidate'
-    res.header 'Pragma', 'no-cache'
-    res.header 'Expires', 0
     res.status(200).send(mainHTML)

   @server = http.createServer(app).listen app.get('port'), ->


better behavior when no AWS settings available

--- server/server.coffee
+++ server/server.coffee
@@ -19,4 +19,7 @@
     _id = BinaryData.insert(doc)
     "images/" + _id + "/" + doc.name

-Slingshot.createDirective "myFileUploads", Slingshot.S3Storage, options
+if Meteor.settings.AWSAccessKeyId
+  Slingshot.createDirective "myFileUploads", Slingshot.S3Storage, options
+else
+  console.warn "No AWSAccessKeyId in Meteor settings. No uploads will be possible."


Fix typo in COCO_BITLY_ACCESS_TOKEN

Fix typo in COCO_BITLY_ACCESS_TOKEN

--- server_config.coffee
+++ server_config.coffee
@@ -148,7 +148,7 @@
   accessToken: process.env.COCO_INTERCOM_ACCESS_TOKEN or 'dGVzdA==' #base64 "test"

 config.bitly =
-  accessToken: process.env.COCO_BITLY_ACESS_TOKEN or ''
+  accessToken: process.env.COCO_BITLY_ACCESS_TOKEN or ''

 config.apcspFileUrl = process.env.COCO_APCSP_FILE_URL or "http://localhost:#{config.port}/apcsp-local/"


Don't load if URL is in process of loading

--- src/api/loader.coffee
+++ src/api/loader.coffee
@@ -3,7 +3,7 @@
 interpolate = require 'interpolate'

 METHODS_WITH_DATA = ['PUT', 'PATCH', 'POST']
-
+LOADING = {}
 API_ACCESS_TOKEN = false

 defaultFail = (response) ->
@@ -41,25 +41,28 @@

 handleAPIEvent = (apiEventChannel, baseUrl, setting, eventData = {}) ->
+
   isLocal = setting.loadLocally
   # simulate server delay
   delay = if isLocal then 200 else 0

   apiSetting = getAjaxSettingsByEnv(isLocal, baseUrl, setting, eventData)
+  if apiSetting.method is 'GET'
+    return if LOADING[apiSetting.url]
+    LOADING[apiSetting.url] = true

   _.delay ->
     axios(apiSetting)
       .then((response) ->
-
+        delete LOADING[apiSetting.url]
         completedEvent = interpolate(setting.completedEvent, eventData.data)
         completedData = getResponseDataByEnv(isLocal, eventData, response)

         apiEventChannel.emit(completedEvent, completedData)

       ).catch((response) ->
-
+        delete LOADING[apiSetting.url]
         failedData = getResponseDataByEnv(isLocal, eventData, response)
-
         if _.isString(setting.failedEvent)
           failedEvent = interpolate(setting.failedEvent, eventData.data)
           apiEventChannel.emit(failedEvent, failedData)


fix prod config

fix prod config

--- config/environments/production.coffee
+++ config/environments/production.coffee
@@ -16,7 +16,7 @@
     minify: true

   assets:
-    publicDir: e.publicDir
+    publicDir: publicDir
     cdn:
       protocol: 'https'
       cnames: [0,1,2,3].map((i) -> return "a" + i + ".muscache.com")


Improved tests

Improved tests

--- test/alarm_test.coffee
+++ test/alarm_test.coffee
@@ -35,49 +35,46 @@
                 should.exist response
                 should.exist body

+                response.should.have.property 'statusCode'
                 response.statusCode.should.equal 200
                 body.length.should.equal 2
                 done()

     describe "POST alarms/", ->

-        before (done) ->
+        before (done) =>
             helpers.cleanDb done
             @alarm =
                 action: 'DISPLAY'
                 trigg: '20130410T1500Z'
                 description: 'Something to remind'

-        it "should return the alarm json object", (done) ->
+        it "should return the alarm json object", (done) =>

             client.post "alarms/", @alarm, (error, response, body) =>

                 should.not.exist error
                 should.exist response
-                response.statusCode.should.equal 201
+                response.should.have.status 201
                 should.exist body
-                should.exist body.action
-                should.exist body.trigg
-                should.exist body.description
-
-                body.action.should.equal @alarm.action
-                body.trigg.should.equal @alarm.trigg
-                body.description.should.equal @alarm.description
+                body.should.have.property 'action', @alarm.action
+                body.should.have.property 'trigg', @alarm.trigg
+                body.should.have.property 'description', @alarm.description

                 done()

-        it "should have persisted the alarm into database", (done) ->
+        it "should have persisted the alarm into database", (done) =>
             helpers.getAllAlarms (err, alarms) =>

                 should.not.exist err
                 should.exist alarms
                 alarms.length.should.equal 1
-                should.exist alarms[0].action
-                should.exist alarms[0].trigg
-                should.exist alarms[0].description
-                alarms[0].action.should.equal @alarm.action
-                alarms[0].trigg.should.equal @alarm.trigg
-                alarms[0].description.should.equal @alarm.description
+                should.exist alarms[0]
+
+                alarms[0].should.have.property 'action', @alarm.action
+                alarms[0].should.have.property 'trigg', @alarm.trigg
+                alarms[0].should.have.property 'description', @alarm.description
+
                 done()

     describe "PUT alarms/:id", ->