simplify tests

simplify tests

--- test/job-assembler-spec.coffee
+++ test/job-assembler-spec.coffee
@@ -17,6 +17,8 @@
       client: @localClient
       namespace: 'test:internal'
       timeoutSeconds: 1
+      responseQueue: 'authenticate'
+      requestQueue: 'authenticate'

     @remoteJobManager = new JobManager
       client: @remoteClient
@@ -111,42 +113,52 @@

         context 'when authenticate responds', ->
           beforeEach (done) ->
+            metadata =
+              responseId: 'r-id'
+            data =
+              authenticated: true
+
+            options =
+              responseId: 'r-id'
+              metadata: metadata
+              data: data
+
+            @localJobManager.createResponse options, done
+
             metadataStr = '{"responseId": "r-id"}'
             dataStr     = '{"authenticated": true}'
-
-            async.series [
-              async.apply @localClient.hset,  'test:internal:r-id', 'response:metadata', metadataStr
-              async.apply @localClient.hset,  'test:internal:r-id', 'response:data',     dataStr
-              async.apply @localClient.lpush, 'test:internal:authenticate:r-id', 'test:internal:r-id'
-            ], done

           it 'should call the callback with the response', (done) ->
             setTimeout =>
               expectedResponse =
                 metadata:
                   responseId: 'r-id'
-                rawData: '{"authenticated": true}'
+                rawData: '{"authenticated":true}'
               expect(@callback).to.have.been.calledWith null, expectedResponse
               done()
             , 1000

         context 'when authenticate responds differently', ->
           beforeEach (done) ->
-            metadataStr = '{"responseId": "r-id"}'
-            dataStr     = '{"authenticated": false}'
+            metadata =
+              responseId: 'r-id'
+            data =
+              authenticated: false

-            async.series [
-              async.apply @localClient.hset,  'test:internal:r-id', 'response:metadata', metadataStr
-              async.apply @localClient.hset,  'test:internal:r-id', 'response:data',     dataStr
-              async.apply @localClient.lpush, 'test:internal:authenticate:r-id', 'test:internal:r-id'
-            ], done
+            options =
+              responseId: 'r-id'
+              metadata: metadata
+              data: data
+
+            @localJobManager.createResponse options, done

           it 'should call the callback with the response', (done) ->
             setTimeout =>
               expectedResponse =
                 metadata:
                   responseId: 'r-id'
-                rawData: '{"authenticated": false}'
+                rawData: '{"authenticated":false}'
+
               expect(@callback).to.have.been.calledWith null, expectedResponse
               done()
             , 1000


Bugfix.

Bugfix.

--- server.coffee
+++ server.coffee
@@ -415,7 +415,7 @@
     if field instanceof globals.Document._ObservingField
       if field.ancestorArray and name is field.ancestorArray
         unless _.isArray value
-          Log.error "Document '#{ @sourceDocument.Meta._name }' '#{ id }' field '#{ name }' was updated with a non-array value: #{ util.inspect value }"
+          Log.error "Document '#{ @Meta._name }' '#{ id }' field '#{ name }' was updated with a non-array value: #{ util.inspect value }"
           return
       else
         value = [value]


small problem with nested sections

small problem with nested sections

--- server.coffee
+++ server.coffee
@@ -42,7 +42,7 @@
 	if level == 2
 		head = "
#{head}"
 		head = "
#{head}" if rdr.block_state > 0
-		rdr.block_state++
+		rdr.block_state = 1
 	else if level > 2
 		rdr.block_state++
 	else
@@ -56,6 +56,8 @@

 	if rdr.block_state > 0 then rdr.block_state--
 	list = "#{list}
\n" if rdr.block_state == 0
+
+	list

 mkd_opt = {
 	breaks: false


get secret from env

get secret from env

--- server.coffee
+++ server.coffee
@@ -15,7 +15,7 @@
 app.use validator

 app.use express.session
-    secret: "YOURMOSTDARKSECRET"
+    secret: process.env.SECRET || "YOURMOSTDARKSECRET"

 require './app/routes'
 require './app/params'


Don't update all documents when initializing.

Added Document.updateAll to manually trigger rerun on all documents.

--- server.coffee
+++ server.coffee
@@ -19,17 +19,25 @@
       Log.error "PeerDB exception: #{ e }: #{ util.inspect args, depth: 10 }"

 class globals.Document._TargetedFieldsObservingField extends globals.Document._TargetedFieldsObservingField
-  setupTargetObservers: =>
+  setupTargetObservers: (updateAll) =>
+    initializing = true
+
     referenceFields = fieldsToProjection @fields
-    @targetCollection.find({}, fields: referenceFields).observeChanges
+    handle = @targetCollection.find({}, fields: referenceFields).observeChanges
       added: catchErrors (id, fields) =>
-        @updateSource id, fields
+        @updateSource id, fields if updateAll or not initializing

       changed: catchErrors (id, fields) =>
         @updateSource id, fields

       removed: catchErrors (id) =>
         @removeSource id
+
+    if updateAll
+      handle.stop()
+      return
+
+    initializing = false

 # Have to refresh with new methods from TargetedFieldsObservingField
 _.extend globals.Document._ReferenceField.prototype,
@@ -257,6 +265,9 @@
   updatedWithValue: (id, value) =>
     # Do nothing. Code should not be updating generated field by itself anyway.

+  @updateAll: ->
+    setupObservers true
+
 class globals.Document extends globals.Document
   @_sourceFieldUpdated: (id, name, value, field) ->
     # TODO: Should we check if field still exists but just value is undefined, so that it is the same as null? Or can this happen only when removing the field?
@@ -292,7 +303,7 @@
     for name, value of fields
       @_sourceFieldUpdated id, name, value

-  @setupSourceObservers: ->
+  @setupSourceObservers: (updateAll) ->
     return if _.isEmpty @Meta.fields

     sourceFields =
@@ -307,12 +318,20 @@

     sourceFieldsWalker @Meta.fields

-    @Meta.collection.find({}, fields: sourceFields).observeChanges
+    initializing = true
+
+    handle = @Meta.collection.find({}, fields: sourceFields).observeChanges
       added: catchErrors (id, fields) =>
-        @_sourceUpdated id, fields
+        @_sourceUpdated id, fields if updateAll or not initializing

       changed: catchErrors (id, fields) =>
         @_sourceUpdated id, fields
+
+    if updateAll
+      handle.stop()
+      return
+
+    initializing = false

   @setupMigrations: ->
     @Meta.collection.find(
@@ -329,18 +348,18 @@
           $set:
             _schema: '1.0.0'

-setupObservers = ->
+setupObservers = (updateAll) ->
   setupTargetObservers = (fields) ->
     for name, field of fields
       # There are no arrays anymore here, just objects (for subdocuments) or fields
       if field instanceof globals.Document._ObservingField
-        field.setupTargetObservers()
+        field.setupTargetObservers updateAll
       else if field not instanceof globals.Document._Field
         setupTargetObservers field

   for document in globals.Document.list
     setupTargetObservers document.Meta.fields
-    document.setupSourceObservers()
+    document.setupSourceObservers updateAll

 setupMigrations = ->
   for document in globals.Document.list
@@ -363,17 +382,10 @@
 Meteor.startup ->
   # To try delayed references one last time, this time we throw any exceptions
   # (Otherwise setupObservers would trigger strange exceptions anyway)
-  globals.Document._retryDelayed true
+  globals.Document.defineAll()

   migrations()

-  # TODO: Use official API when it will be available: https://github.com/meteor/meteor/issues/180
-  if process.env.NODE_ENV is 'production' or Meteor.settings?.production or Meteor.settings?.public?.production
-    # Setup observers and run all initial updates in blocking mode on production
-    setupObservers()
-  else
-    # Otherwise do it in the background
-    Meteor.defer ->
-      setupObservers()
+  setupObservers()

 Document = globals.Document


Handle same percentage as correct rank

Handle same percentage as correct rank

--- server.coffee
+++ server.coffee
@@ -197,13 +197,17 @@
             score = 2
             rank = 0
             for i in [1..5]
-                if i>3
-                    rank = i if +userId in resultsObj[i]
-                else
-                    rank = i if +userId is resultsObj[i]
+                rank = i if +userId is resultsObj[i]

             if rank
-                diff = Math.min(3, Math.abs(rank - self))
+                if self in [1..3]
+                    for i in [1..3]
+                        if results[self][1] is results[i][1]
+                            diff = 0
+
+                if not diff?
+                    diff = Math.min(3, Math.abs(rank - self))
+
                 scoring = Util.scoring()
                 score = scoring[diff]


test(view): getDirection should return null if segements do not match

Before:
/home/second -> /home/foo/bar should return 'normal' direction
/home/second/third -> /crazy/whatever should return 'reverse'

After:
/home/second -> /home/foo/bar should return null
/home/second -> /home/second/third should return 'normal'
/home/second/third -> /crazy/whatever should return null

--- test/spec/services/view.coffee
+++ test/spec/services/view.coffee
@@ -25,13 +25,15 @@
         url: '/home/baz/fifth'
         data:
           transition: 'slide'
+      .state 'alien',
+        url: '/crazy/route'
     return

   viewService = null
   state = null
   scope = null

-  home = second = third = fourth = fifth = null
+  home = second = third = fourth = fifth = alien = null

   beforeEach inject ($rootScope, bpView, $state) ->
@@ -43,6 +45,7 @@
     third = state.get 'third'
     fourth = state.get 'fourth'
     fifth = state.get 'fifth'
+    alien = state.get 'alien'

   describe 'listen', ->
     it 'should listen to events', ->
@@ -104,14 +107,19 @@
   describe 'getDirection', ->
     it 'should detect "normal"', ->
       expect(viewService.getDirection home, second).toBe 'normal'
-      expect(viewService.getDirection fourth, fifth).toBe 'normal'
+      expect(viewService.getDirection second, third).toBe 'normal'

     it 'should detect "reverse"', ->
       expect(viewService.getDirection second, home).toBe 'reverse'

     it 'should detect no direction', ->
+      expect(viewService.getDirection fourth, fifth).toBe null
       expect(viewService.getDirection third, fifth).toBe null
       expect(viewService.getDirection {url: '^'}).toBe null
+      expect(viewService.getDirection alien, second).toBe null
+      expect(viewService.getDirection alien, fifth).toBe null
+      expect(viewService.getDirection third, fifth).toBe null
+      expect(viewService.getDirection home, alien).toBe null

   describe 'getType', ->
     it 'should detect "normal" type', ->


added echo command

added echo command

--- scripts/teamcity.coffee
+++ scripts/teamcity.coffee
@@ -160,6 +160,9 @@
                 else
                   msg.send "The requested builds have been killed"

+  robot.respond /tc host, (msg) ->
+    msg.send "Current configured to talk to #{base_url}"
+
   robot.respond /show me builds/i, (msg) ->
     getCurrentBuilds msg, (err, builds, msg) ->
       if typeof(builds)=='string'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -63,7 +63,7 @@
   @param {Number} [duration=2] How long the effect lasts in seconds
   @param {Number} [frequency=0.1] Number of seconds in between opacity changes
   @param {Number} [alpha=0.5] Alpha value to toggle between
-  @returns
+  @returns {GameObject}
   ###
   flicker: (options={}) ->
     Object.reverseMerge options,


Fix PartnerCellCarouselView test

Fix PartnerCellCarouselView test

--- apps/galleries_institutions/components/partner_cell_carousel/test/view.coffee
+++ apps/galleries_institutions/components/partner_cell_carousel/test/view.coffee
@@ -5,7 +5,6 @@
 Partners = require '../../../../../collections/partners'
 PartnerCellView = benv.requireWithJadeify require.resolve('../../partner_cell/view'), ['template']
 PartnerCellCarouselView = benv.requireWithJadeify require.resolve('../view'), ['template']
-PartnerCellCarouselView.__set__ 'PartnerCellView', PartnerCellView

 describe 'PartnerCellCarouselView', ->

@@ -13,6 +12,8 @@
     benv.setup ->
       benv.expose $: benv.require 'jquery'
       Backbone.$ = $
+      PartnerCellCarouselView.__set__ 'PartnerCellView', PartnerCellView
+      sinon.stub PartnerCellCarouselView::, 'setupFlickity'
       done()

   after ->


Fix specs

Fix specs

--- spec/Parser-spec.coffee
+++ spec/Parser-spec.coffee
@@ -1,13 +1,23 @@
 Parser = require '../lib/Parser'
 { Range } = require 'atom'
+{ inspect } = require 'util'

-expectEqualRefs = (parser, searchedRange, expectedRanges...) ->
-  ranges = parser.find searchedRange
-  expect ranges
-  .toHaveLength expectedRanges.length
-  for range, i in ranges
-    expect range
-    .toEqual expectedRanges[i]
+expectNoRefs = (parser, range) ->
+  resultRanges = parser.find range
+  expect resultRanges
+  .toHaveLength 0
+
+expectEqualRefs = (parser, ranges...) ->
+  resultRanges = parser.find ranges[0]
+  ranges.sort (a, b) ->
+    return delta if (delta = a.start.row - b.start.row) isnt 0
+    a.start.column - b.start.column
+
+  expect resultRanges
+  .toHaveLength ranges.length
+  for resultRange, i in resultRanges
+    expect resultRange
+    .toEqual ranges[i]

 describe 'Parser', ->
@@ -23,7 +33,7 @@
         """
       .not.toThrow()
       expect(parser.nodes).toBeNull()
-      expectEqualRefs parser, new Range([0, 0], [0, 1])
+      expectNoRefs parser, new Range([0, 0], [0, 1])

   describe 'find', ->

@@ -31,9 +41,9 @@
       parser.parse """
       b = a * b / 10
       """
-      expectEqualRefs parser, new Range([0, 1], [0, 2])
-      expectEqualRefs parser, new Range([0, 2], [0, 3])
-      expectEqualRefs parser, new Range([1, 6], [1, 7])
+      expectNoRefs parser, new Range([0, 1], [0, 2])
+      expectNoRefs parser, new Range([0, 2], [0, 3])
+      expectNoRefs parser, new Range([1, 6], [1, 7])

     it 'should find references in FUNCTION', ->
       parser.parse """


add "nick: !trigger" listener

add "nick: !trigger" listener

--- scripts/triggers.coffee
+++ scripts/triggers.coffee
@@ -47,3 +47,11 @@
         msg.send "#{user}: #{triggerPhrase}"
       else
         msg.reply triggerPhrase
+
+  robot.hear /(\w+)(?:\:?)?(?:\s+?)(!.+)/, (msg) ->
+    user          = msg.match[1]
+    trigger       = msg.match[2]
+    triggerPhrase = triggers[trigger]
+
+    if triggerPhrase
+      msg.send "#{user}: #{triggerPhrase}"


bugfix: fix regression with member counter

--- client/landing/app/Applications/Home.kdapplication/Views/homeintroview.coffee
+++ client/landing/app/Applications/Home.kdapplication/Views/homeintroview.coffee
@@ -85,11 +85,11 @@
     vmController = KD.getSingleton("vmController")
     {JAccount, JTag, JGroup, CActivity} = KD.remote.api

-    members.ready    => JAccount.count                 (err, count)=> members.update count    or 0
-    vms.ready        => vmController.fetchTotalVMCount (err, count)=> vms.update count        or 0
-    groups.ready     => JGroup.count                   (err, count)=> groups.update count     or 0
-    topics.ready     => JTag.fetchCount                (err, count)=> topics.update count     or 0
-    activities.ready => CActivity.fetchCount           (err, count)=> activities.update count or 0
+    members.ready    => JAccount.count type: 'registered', (err, count)=> members.update count    or 0
+    vms.ready        => vmController.fetchTotalVMCount (err, count)=> vms.update count            or 0
+    groups.ready     => JGroup.count                   (err, count)=> groups.update count         or 0
+    topics.ready     => JTag.fetchCount                (err, count)=> topics.update count         or 0
+    activities.ready => CActivity.fetchCount           (err, count)=> activities.update count     or 0
     # loc.ready        => vmController.fetchTotalLoC     (err, count)=> loc.update count        or 0

     KD.getSingleton("activityController").on "ActivitiesArrived", (newActivities=[])->


Testing scope count/distinct

Testing scope count/distinct

--- spec/finder-spec.coffee
+++ spec/finder-spec.coffee
@@ -13,9 +13,9 @@
     topic: ->
       fixtures =
         posts: [
-          { title: "Test 1", author_id: 1, category: "low", created_at: new Date },
-          { title: "Test 2", author_id: 1, category: "high", created_at: new Date },
-          { title: "Test 3", author_id: 2, category: "low", created_at: new Date }
+          { title: "Post 1", author_id: 1, category: "low", created_at: new Date },
+          { title: "Post 2", author_id: 1, category: "high", created_at: new Date },
+          { title: "Post 3", author_id: 2, category: "low", created_at: new Date }
         ]
       setup fixtures, @callback

@@ -35,7 +35,7 @@
               assert.equal post.author_id, 1
           "should return all posts in order": (posts)->
             title = (post.title for post in posts)
-            assert.deepEqual title, ["Test 2", "Test 1"]
+            assert.deepEqual title, ["Post 2", "Post 1"]
         "query and callback":
           topic: ->
             connect().find "posts", author_id: 1, @callback
@@ -59,7 +59,7 @@
               assert.equal post.author_id, 1
           "should return all posts in order": (posts)->
             title = (post.title for post in posts)
-            assert.deepEqual title, ["Test 2", "Test 1"]
+            assert.deepEqual title, ["Post 2", "Post 1"]
         "query only":
           topic: ->
             scope = connect().find("posts", author_id: 1)
@@ -83,7 +83,7 @@
             assert.equal posts.length, 3
           "should return all posts in order": (posts)->
             title = (post.title for post in posts)
-            assert.deepEqual title, ["Test 3", "Test 2", "Test 1"]
+            assert.deepEqual title, ["Post 3", "Post 2", "Post 1"]
         "IDs and callback":
           topic: ->
             connect().distinct "posts", "_id", (err, ids, db)=>
@@ -104,7 +104,7 @@
               id = (post._id for post in posts)[0]
               connect().find "posts", id, fields: ["title"], @callback
           "should return a single post": (post)->
-            assert.equal post.title, "Test 1"
+            assert.equal post.title, "Post 1"
           "should return specified fields": (post)->
             assert.isUndefined post.author_id
         "ID and callback":
@@ -113,7 +113,7 @@
               id = (post._id for post in posts)[0]
               connect().find "posts", id, @callback
           "should return a single post": (post)->
-            assert.equal post.title, "Test 1"
+            assert.equal post.title, "Post 1"
         "ID and options":
           topic: ->
             connect().find "posts", (err, posts, db)=>
@@ -121,7 +121,7 @@
               scope = connect().find("posts", id, fields: ["title"])
               scope.one @callback
           "should return a single post": (post)->
-            assert.equal post.title, "Test 1"
+            assert.equal post.title, "Post 1"
           "should return specified fields": (post)->
             assert.isUndefined post.author_id
         "ID only":
@@ -131,7 +131,7 @@
               scope = connect().find("posts", id)
               scope.one @callback
           "should return a single post": (post)->
-            assert.equal post.title, "Test 1"
+            assert.equal post.title, "Post 1"

       # -- Test connect().find() --
@@ -166,13 +166,13 @@
             connect().distinct "posts", "title", author_id: 1, @callback
           "should return distinct values": (values)->
             assert.equal values.length, 2
-            assert.deepEqual values, ["Test 1", "Test 2"]
+            assert.deepEqual values, ["Post 1", "Post 2"]
         "callback only":
           topic: ->
             connect().distinct "posts", "title", @callback
           "should return distinct values": (values)->
             assert.equal values.length, 3
-            assert.deepEqual values, ["Test 1", "Test 2", "Test 3"]
+            assert.deepEqual values, ["Post 1", "Post 2", "Post 3"]
         "no callback":
           topic: ->
             try
@@ -241,7 +241,7 @@
               id = (post._id for post in posts)[1]
               collection.find id, fields: ["title"], @callback
           "should return a single post": (post)->
-            assert.equal post.title, "Test 2"
+            assert.equal post.title, "Post 2"
           "should return specified fields": (post)->
             assert.isUndefined post.author_id
         "ID and callback":
@@ -250,7 +250,7 @@
               id = (post._id for post in posts)[1]
               collection.find id, @callback
           "should return a single post": (post)->
-            assert.equal post.title, "Test 2"
+            assert.equal post.title, "Post 2"
           "should return all fields": (post)->
             assert post.author_id
             assert post.title
@@ -265,7 +265,7 @@
             topic: (scope)->
               scope.one @callback
             "should find specific object": (post)->
-              assert.equal post.title, "Test 2"
+              assert.equal post.title, "Post 2"

       # -- Test collection().one() --
@@ -302,7 +302,7 @@
               id = (post._id for post in posts)[1]
               collection.one id, fields: ["title"], @callback
           "should return matching post": (post)->
-            assert.equal post.title, "Test 2"
+            assert.equal post.title, "Post 2"
           "should return specified fields": (post)->
             assert post.title
             assert.isUndefined post.author_id
@@ -312,7 +312,7 @@
               id = (post._id for post in posts)[1]
               collection.one id, @callback
           "should return a single post": (post)->
-            assert.equal post.title, "Test 2"
+            assert.equal post.title, "Post 2"
           "should return all fields": (post)->
             assert post.author_id
             assert post.title
@@ -447,13 +447,13 @@
             collection.distinct "title", author_id: 1, @callback
           "should return distinct values": (values)->
             assert.equal values.length, 2
-            assert.deepEqual values, ["Test 1", "Test 2"]
+            assert.deepEqual values, ["Post 1", "Post 2"]
         "callback only":
           topic: (collection)->
             collection.distinct "title", @callback
           "should return distinct values": (values)->
             assert.equal values.length, 3
-            assert.deepEqual values, ["Test 1", "Test 2", "Test 3"]
+            assert.deepEqual values, ["Post 1", "Post 2", "Post 3"]
         "no callback":
           topic: (collection)->
             try
@@ -480,7 +480,7 @@
               scope.all @callback
             "should find all objects in the collection": (posts)->
               assert.equal posts.length, 3
-              assert.include (post.title for post in posts), "Test 2"
+              assert.include (post.title for post in posts), "Post 2"
         "with criteria":
           topic: (collection)->
             scope = collection.where(author_id: 1)
@@ -492,11 +492,11 @@
         "nested":
           topic: (collection)->
             scope = collection.where(author_id: 1)
-            scope = scope.where(title: "Test 2")
+            scope = scope.where(title: "Post 2")
             scope.all @callback
           "should use combined scope": (posts)->
             assert.equal posts.length, 1
-            assert.equal posts[0].title, "Test 2"
+            assert.equal posts[0].title, "Post 2"

       # -- Test Scope fields, asc, desc, limit and skip --
@@ -544,25 +544,25 @@
               scope.asc().all @callback
             "should return in natural order": (posts)->
               titles = (post.title for post in posts)
-              assert.deepEqual titles, ["Test 1", "Test 2", "Test 3"]
+              assert.deepEqual titles, ["Post 1", "Post 2", "Post 3"]
           "single argument":
             topic: (scope)->
               scope.asc("category").all @callback
             "should return in specified order": (posts)->
               titles = (post.title for post in posts)
-              assert.deepEqual titles, ["Test 2", "Test 1", "Test 3"]
+              assert.deepEqual titles, ["Post 2", "Post 1", "Post 3"]
           "multiple arguments":
             topic: (scope)->
               scope.asc("category", "title").all @callback
             "should return in specified order": (posts)->
               titles = (post.title for post in posts)
-              assert.deepEqual titles, ["Test 2", "Test 1", "Test 3"]
+              assert.deepEqual titles, ["Post 2", "Post 1", "Post 3"]
           "array arguments":
             topic: (scope)->
               scope.asc(["category", "title"]).all @callback
             "should return in specified order": (posts)->
               titles = (post.title for post in posts)
-              assert.deepEqual titles, ["Test 2", "Test 1", "Test 3"]
+              assert.deepEqual titles, ["Post 2", "Post 1", "Post 3"]

         "desc":
           topic: ->
@@ -572,25 +572,25 @@
               scope.desc().all @callback
             "should return in natural order": (posts)->
               titles = (post.title for post in posts)
-              assert.deepEqual titles, ["Test 1", "Test 2", "Test 3"]
+              assert.deepEqual titles, ["Post 1", "Post 2", "Post 3"]
           "single argument":
             topic: (scope)->
               scope.desc("title").all @callback
             "should return in specified order": (posts)->
               titles = (post.title for post in posts)
-              assert.deepEqual titles, ["Test 3", "Test 2", "Test 1"]
+              assert.deepEqual titles, ["Post 3", "Post 2", "Post 1"]
           "multiple arguments":
             topic: (scope)->
               scope.desc("title", "category").all @callback
             "should return in specified order": (posts)->
               titles = (post.title for post in posts)
-              assert.deepEqual titles, ["Test 3", "Test 2", "Test 1"]
+              assert.deepEqual titles, ["Post 3", "Post 2", "Post 1"]
           "array arguments":
             topic: (scope)->
               scope.desc(["title", "category"]).all @callback
             "should return in specified order": (posts)->
               titles = (post.title for post in posts)
-              assert.deepEqual titles, ["Test 3", "Test 2", "Test 1"]
+              assert.deepEqual titles, ["Post 3", "Post 2", "Post 1"]

         "combined asc, desc":
           topic: ->
@@ -600,13 +600,13 @@
               scope.asc("category").desc("title").all @callback
             "should return in specified order": (posts)->
               titles = (post.title for post in posts)
-              assert.deepEqual titles, ["Test 2", "Test 3", "Test 1"]
+              assert.deepEqual titles, ["Post 2", "Post 3", "Post 1"]
           "mixed order":
             topic: (scope)->
               scope.asc("category").asc("title").all @callback
             "should return in specified order": (posts)->
               titles = (post.title for post in posts)
-              assert.deepEqual titles, ["Test 2", "Test 1", "Test 3"]
+              assert.deepEqual titles, ["Post 2", "Post 1", "Post 3"]

         "limit":
           topic: ->
@@ -616,7 +616,7 @@
             assert.equal posts.length, 1
           "should return first results": (posts)->
             titles = (post.title for post in posts)
-            assert.deepEqual titles, ["Test 1"]
+            assert.deepEqual titles, ["Post 1"]

         "skip":
           topic: ->
@@ -626,7 +626,7 @@
             assert.equal posts.length, 2
           "should skip first result": (posts)->
             titles = (post.title for post in posts)
-            assert.deepEqual titles, ["Test 2", "Test 3"]
+            assert.deepEqual titles, ["Post 2", "Post 3"]

         "limit, skip":
           topic: ->
@@ -636,17 +636,17 @@
             assert.equal posts.length, 1
           "should skip first result, return only one": (posts)->
             titles = (post.title for post in posts)
-            assert.deepEqual titles, ["Test 2"]
+            assert.deepEqual titles, ["Post 2"]

         # -- Test Scope one, each and all --

         "one":
           topic: ->
-            connect().find("posts").where(title: "Test 2").one @callback
+            connect().find("posts").where(title: "Post 2").one @callback
           "should return one object": (post)->
             assert post
-            assert.equal post.title, "Test 2"
+            assert.equal post.title, "Post 2"

         "each":
           topic: ->
@@ -658,7 +658,7 @@
                 @callback null, titles
           "should pass each object, then null": (titles)->
             assert.equal titles.length, 2
-            assert.deepEqual titles, ["Test 1", "Test 2"]
+            assert.deepEqual titles, ["Post 1", "Post 2"]

         "all":
           topic: ->
@@ -668,6 +668,19 @@
             for post in posts
               assert.equal post.author_id, 1

+        # -- Test Scope count, distinct --
+
+        "count":
+          topic: ->
+            connect().find("posts").where(author_id: 1).count @callback
+          "should return number of matching objects": (count)->
+            assert.equal count, 2
+
+        "distinct":
+          topic: ->
+            connect().find("posts").where(author_id: 1).distinct "title", @callback
+          "should return distinct values": (titles)->
+            assert.deepEqual titles, ["Post 1", "Post 2"]
     ###
     "Simple queries":
       "count":
@@ -678,7 +691,7 @@
             assert.equal count, 3
         "query":
           topic: (connect)->
-            connect.find("posts", text: "Test 1").count @callback
+            connect.find("posts", text: "Post 1").count @callback
           "should return number of records in collection": (count)->
             assert.equal count, 1

@@ -688,33 +701,33 @@
             connect.find("posts").distinct "text", @callback
           "should return all distinct values": (values)->
             assert.equal values.length, 3
-            assert.include values, "Test 1"
-            assert.include values, "Test 2"
-            assert.include values, "Test 3"
+            assert.include values, "Post 1"
+            assert.include values, "Post 2"
+            assert.include values, "Post 3"
         "query":
           topic: (connect)->
-            connect.find("posts", text: "Test 2").distinct "text", @callback
+            connect.find("posts", text: "Post 2").distinct "text", @callback
           "should return only matching records": (values)->
             assert.equal values.length, 1
-            assert.include values, "Test 2"
+            assert.include values, "Post 2"

       "find one":
         "no query":
           topic: (connect)->
             connect.find("posts").one @callback
           "should return a single post": (post)->
-            assert.equal "Test 1", post.text
+            assert.equal "Post 1", post.text
             assert post._id
         "with query":
           topic: (connect)->
-            connect.find("posts", text: "Test 2").one @callback
-          "should return a single post": (post)->
-            assert.equal "Test 2", post.text
+            connect.find("posts", text: "Post 2").one @callback
+          "should return a single post": (post)->
+            assert.equal "Post 2", post.text
         "with where query":
           topic: (connect)->
-            connect.find("posts").where(text: "Test 3").one @callback
-          "should return a single post": (post)->
-            assert.equal "Test 3", post.text
+            connect.find("posts").where(text: "Post 3").one @callback
+          "should return a single post": (post)->
+            assert.equal "Post 3", post.text
         "with no fields":
           topic: (connect)->
             connect.find("posts").fields().one @callback
@@ -750,9 +763,9 @@
               @callback null, objects
           finder.next takeNext
         "should call once for each post": (objects)->
-          assert.equal "Test 1", objects[0].text
-          assert.equal "Test 2", objects[1].text
-          assert.equal "Test 3", objects[2].text
+          assert.equal "Post 1", objects[0].text
+          assert.equal "Post 2", objects[1].text
+          assert.equal "Post 3", objects[2].text
         "should call last with null": (objects)->
           assert.equal 4, objects.length
           assert.isNull objects[3]
@@ -761,7 +774,7 @@
       # Rewind is broken in connect 0.9.6-23
       "rewind":
         topic: (connect)->
-          finder = connect.find("posts").where(text: "Test 2")
+          finder = connect.find("posts").where(text: "Post 2")
           objects = []
           takeNext = (error, object)=>
             objects.push object
@@ -774,9 +787,9 @@
               @callback null, objects
           finder.next takeNext
         "should rewind on query results": (objects)->
-          assert.equal "Test 2", objects[0].text
+          assert.equal "Post 2", objects[0].text
           assert.isNull objects[1]
-          assert.equal "Test 2", objects[2].text
+          assert.equal "Post 2", objects[2].text
           assert.isNull objects[3]
       ##


AH, accidentally sending turn message multiple times

--- lib/arena.coffee
+++ lib/arena.coffee
@@ -52,10 +52,10 @@

         #TODO: clearly, this needs a cleanup
         # Tic tac toe
-        agent.on "move", (args...) ->
-          game.handle ["move", position].join("."), args...
-        game.on [position, "turn"].join("."), (data) ->
-          agent.send("turn", data)
+        # agent.on "move", (args...) ->
+        #   game.handle ["move", position].join("."), args...
+        # game.on [position, "turn"].join("."), (data) ->
+        #   agent.send("turn", data)

         # Hearts
         agentEvents = ["timeout"]


Refactor code more

Refactor code more

--- lib/broadcast-target.coffee
+++ lib/broadcast-target.coffee
@@ -2,9 +2,12 @@
 class BroadcastTarget
   editor: null
   listener: null
+  isMarkdownPreview: false

   constructor: ->
     @editor = atom.workspace.activePaneItem
+
+    @isMarkdownPreview = @editor[0]?

     @editor.on 'markdown-preview:markdown-changed', =>
       @listener?()
@@ -13,13 +16,15 @@
     @listener = listener

   getContent: ->
-    if @editor[0]?
-      content = @editor[0].outerHTML
-      if atom.config.get 'broadcast.getEmojisFromCheatSheetSite'
-        content = content.replace /[\w-\.\/]+pngs/g, @urlToCheatSheetSite
-      else
-        content = content.replace /[\w-\.\/]+node_modules\/roaster\/node_modules/g, ''
+    return if @isMarkdownPreview then @getMarkdownPreviewContent() else @getOtherContent()
+
+  getMarkdownPreviewContent: ->
+    content = @editor[0].outerHTML
+    if atom.config.get 'broadcast.getEmojisFromCheatSheetSite'
+      content = content.replace /[\w-\.\/]+pngs/g, @urlToCheatSheetSite
     else
-      content = '
' + @editor.getText?() + '
'
+      content = content.replace /[\w-\.\/]+node_modules\/roaster\/node_modules/g, ''

-    return content
+  getOtherContent: ->
+    content = @editor.getText?()
+    return '
' + content + '
'


Rename 6to5 to Babel.

Rename 6to5 to Babel.

Updated to reflect the changes introduced in
https://github.com/atom/atom/commit/caff9e08663e669759a64cb725d9571e894558f9

--- lib/cache-panel-view.coffee
+++ lib/cache-panel-view.coffee
@@ -13,8 +13,8 @@
             @span class: 'inline-block highlight-info', outlet: 'coffeeCompileCount'

           @div class: 'timing', =>
-            @span class: 'inline-block', '6to5 files compiled'
-            @span class: 'inline-block highlight-info', outlet: 'to5CompileCount'
+            @span class: 'inline-block', 'Babel files compiled'
+            @span class: 'inline-block highlight-info', outlet: 'babelCompileCount'

           @div class: 'timing', =>
             @span class: 'inline-block', 'CSON files compiled'
@@ -29,7 +29,7 @@

   populate: ->
     @coffeeCompileCount.text(@getCoffeeCompiles())
-    @to5CompileCount.text(@get6to5Compiles())
+    @babelCompileCount.text(@getBabelCompiles())
     @csonCompileCount.text(@getCsonCompiles())
     @lessCompileCount.text(@getLessCompiles())

@@ -40,11 +40,11 @@
       cacheMisses = CoffeeCache.getCacheMisses?() ? 0
     cacheMisses

-  get6to5Compiles: ->
+  getBabelCompiles: ->
     cacheMisses = 0
     try
-      To5Cache = require(path.join(atom.getLoadSettings().resourcePath, 'src', '6to5'))
-      cacheMisses = To5Cache.getCacheMisses?() ? 0
+      babelCache = require(path.join(atom.getLoadSettings().resourcePath, 'src', 'babel'))
+      cacheMisses = babelCache.getCacheMisses?() ? 0
     cacheMisses

   getCsonCompiles: ->


Increased buffer size for output:

Increased buffer size for output:

--- lib/build.coffee
+++ lib/build.coffee
@@ -37,13 +37,21 @@
       cmd = @buildCommand(buildTarget)
     return if !cmd

-    if(atom.config.get('build.arguments'))
-      args = (atom.config.get('build.arguments').split(' ')).filter((e) -> '' != e)
+    args = {
+      encoding: 'utf8',
+      timeout: 0,
+      maxBuffer: 2000*1024, #increased buffer size
+      killSignal: 'SIGTERM',
+      cwd: null,
+      env: null
+    }

     @child = child_process.exec(cmd,args)
+
     @child.stdout.on 'data', @buildView.append
     @child.stderr.on 'data', @buildView.append
     @child.on 'close', (exitCode) =>
+      console.log('exitCode is : '+exitCode)
       @buildView.buildFinished(0 == exitCode)
       @child = null


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -10,5 +10,4 @@

   equals engine.find('.findable').length, 1

-
 module()


Debounce test: ensure that requests work after debounce period

--- test/integration/api.coffee
+++ test/integration/api.coffee
@@ -469,30 +469,38 @@

           @redisClient.psubscribe("*.cobalt.dataset.#{process.env.USER}.update")

+        doRequest = (_, cb) ->
+          request.post
+            uri: "#{serverURL}/api/status"
+            form:
+              type: "ok"
+              message: "just testing"
+          , (err, res, body) ->
+            # This relies on the fact that there is a box with
+            # the same name as your userid. Add one to
+            # fixtures.js if there isn't one already.
+            res.should.have.status 200
+            obj = JSON.parse body
+            cb err
+
         it 'lets me POST to the status API endpoint (and is debounced)', (done) ->
           # Debounce meaning rate limit requests
           if @skip
             return done new Error "Skipped because no local identd"
           # Fire off 10 post requests (where only the last causes
           # redis activity)
-          async.each [1..10], (_, cb) =>
-            request.post
-              uri: "#{serverURL}/api/status"
-              form:
-                type: "ok"
-                message: "just testing"
-            , (err, res, body) =>
-              # This relies on the fact that there is a box with
-              # the same name as your userid. Add one to
-              # fixtures.js if there isn't one already.
-              res.should.have.status 200
-              obj = JSON.parse body
-              cb err
-          , (err) =>
+          async.each [1..10], doRequest, (err) =>
             # Wait long enough for debounce and message to propagate
             setTimeout =>
               @messagesReceived.should.equal 1
-              done()
+
+              # Make another request and ensure that this one wasn't
+              # culled by the debouncer
+              doRequest "THIS PARAMETER NOT USED", =>
+                setTimeout =>
+                  @messagesReceived.should.equal 2
+                  done()
+                , DEBOUNCE_PERIOD + EPSILON
             , DEBOUNCE_PERIOD + EPSILON

     describe 'Billing', ->


class for file sharing

class for file sharing

--- kites/sharedHosting/fileSharing.coffee
+++ kites/sharedHosting/fileSharing.coffee
@@ -10,78 +10,84 @@
   baseDir : '/Users'
   setfacl : '/usr/bin/setfacl'

+class fileSharing

-execFacl = (username, sharedDir) ->
-  setfacl   = "#{config.setfacl} -m d:u:#{username}:rwx #{sharedDir} && #{config.setfacl} -m u:#{username}:rwx #{sharedDir}"
-  exec setfacl, (err,stdout,stderr) ->
-    if err?
-      log.error err = "[ERROR] Can't execute #{setfacl}: #{stderr}"
-      return false
-    else
-      log.info "[OK] acl on #{sharedDir} changed with #{setfacl}"
-      return true
+  execFacl : (username, sharedDir) ->
+    setfacl   = "#{config.setfacl} -m d:u:#{username}:rwx #{sharedDir} && #{config.setfacl} -m u:#{username}:rwx #{sharedDir}"
+    exec setfacl, (err,stdout,stderr) ->
+      if err?
+        log.error err = "[ERROR] Can't execute #{setfacl}: #{stderr}"
+        return false
+      else
+        log.info "[OK] acl on #{sharedDir} changed with #{setfacl}"
+        return true

-linkToHome = (owner, username, callback) ->
-  sharedDir = path.join config.baseSharedDir, owner
-  symlink   = path.join config.baseDir, username, "Shared", owner
-  link  = "/bin/ln -s #{sharedDir} #{symlink}"
-  exec link, (err, stdout, stderr) ->
-    if err?
-      log.error err = "[ERROR] Can't link #{sharedDir} to #{symlink}"
+  linkToHome : (owner, username, callback) ->
+    sharedDir = path.join config.baseSharedDir, owner
+    symlink   = path.join config.baseDir, username, "Shared", owner
+    link  = "/bin/ln -s #{sharedDir} #{symlink}"
+    exec link, (err, stdout, stderr) ->
+      if err?
+        log.error err = "[ERROR] Can't link #{sharedDir} to #{symlink}"
+        callback err
+      else
+        log.info "[OK] shared dir #{sharedDir} linked to #{symlink}"
+        callback null
+
+
+  setOwnerAcl : (owner, callback) ->
+    if not owner?
+      log.error err = "[ERROR] username should be defined"
       callback err
     else
-      log.info "[OK] shared dir #{sharedDir} linked to #{symlink}"
-      callback null
+      sharedDir = path.join config.baseSharedDir, owner
+      if not @execFacl(owner, sharedDir)
+        callback "[ERROR] can't share dir #{sharedDir}"
+      else
+        callback null
+
+  createSharedDir : (owner, callback) ->
+    sharedDir = path.join config.baseSharedDir, owner
+    console.log owner
+    fs.mkdir sharedDir, 0720, (err, stat) =>
+      if err?
+        log.error err
+        callback err
+      else
+        @setOwnerAcl owner,(err)=>
+          if err?
+            callback err
+          else
+            @linkToHome owner, owner, (err) ->
+              if err?
+                log.error err = "[ERROR] Couldnt create shared dir for #{owner}"
+                callback err
+              else
+                log.info "[OK] shared dir #{sharedDir} for user #{owner} successfully created"
+                callback null
+
+  shareWithUser : (owner, username, callback)->
+
+    sharedDir = path.join config.baseSharedDir, owner
+    if not @execFacl(username, sharedDir)
+      callback "[ERROR] can't share dir #{sharedDir} for user #{username}"
+    else
+       linkToHome owner, username, (err) ->
+         if err?
+           log.error err = "[ERROR] Couldnt share  dir #{sharedDir} with #{username}"
+           callback err
+         else
+           log.info "[OK] dir #{sharedDir} successfully shared with #{username}"
+           callback null

-setOwnerAcl = (owner, callback) ->
-  if not owner?
-    log.error err = "[ERROR] username should be defined"
-    callback err
-  else
-    sharedDir = path.join config.baseSharedDir, owner
-    if not execFacl(owner, sharedDir)
-      callback "[ERROR] can't share dir #{sharedDir}"
-    else
-      callback null
+share = new fileSharing
+module.exports = share

-createSharedDir = (owner, callback) ->
-  sharedDir = path.join config.baseSharedDir, owner
-  console.log owner
-  fs.mkdir sharedDir, 0720, (err, stat) ->
-    if err?
-      log.error err
-      callback err
-    else
-      setOwnerAcl owner,(err)->
-        if err?
-          callback err
-        else
-          linkToHome owner, owner, (err) ->
-            if err?
-              log.error err = "[ERROR] Couldnt create shared dir for #{owner}"
-              callback err
-            else
-              log.info "[OK] shared dir #{sharedDir} for user #{owner} successfully created"
-              callback null
-
-shareWithUser = (owner, username, callback)->
-
-  sharedDir = path.join config.baseSharedDir, owner
-  if not execFacl(username, sharedDir)
-    callback "[ERROR] can't share dir #{sharedDir} for user #{username}"
-  else
-     linkToHome owner, username, (err) ->
-       if err?
-         log.error err = "[ERROR] Couldnt share  dir #{sharedDir} with #{username}"
-         callback err
-       else
-         log.info "[OK] dir #{sharedDir} successfully shared with #{username}"
-         callback null
-
-
-
+#share.createSharedDir 'alekseymykhailov',(err)->
+#  if err?
+#    log.error err

 #createSharedDir "alekseymykhailov", (err)->
 #  if err?


Remove bootstrap from prebuildLessConfig

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -92,7 +92,6 @@
   prebuildLessConfig =
     src: [
       'static/**/*.less'
-      'node_modules/bootstrap/less/bootstrap.less'
     ]

   csonConfig =


Add test

Add test

--- test/tests/LayerTest.coffee
+++ test/tests/LayerTest.coffee
@@ -838,6 +838,13 @@
 			layer._elementHTML.innerHTML.should.equal "Hello"
 			layer.ignoreEvents.should.equal true

+		it "should not effect subLayers", ->
+
+			layer = new Layer
+			layer.html = "Hello"
+			subLayer = new Layer superLayer: layer
+
+			subLayer._element.offsetTop.should.equal 0

 		it "should set interactive html and allow pointer events", ->


ci(mac): Fix zip creation and upload for updater

--- build/tasks/publish-nylas-build-task.coffee
+++ build/tasks/publish-nylas-build-task.coffee
@@ -98,29 +98,29 @@
       postToSlack(msg).then ->
         Promise.resolve(data)

-  uploadZipToS3 = (filename, key) ->
+  uploadZipToS3 = (filenameToZip, key) ->
     buildDir = grunt.config.get('atom.buildDir')
-    filepath = path.join(buildDir, filename)
+    buildZipFilename = "#{filenameToZip}.zip"
+    buildZipPath = path.join(buildDir, buildZipFilename)

     grunt.log.writeln ">> Creating zip file…"
+
     new Promise (resolve, reject) ->
-      zipFilepath = filepath + ".zip"
-      rm(zipFilepath)
+      rm(buildZipPath)
       orig = process.cwd()
       process.chdir(buildDir)

       spawn
         cmd: "zip"
-        args: ["-9", "-y", "-r", zipFilepath, filename]
+        args: ["-9", "-y", "-r", buildZipPath, filenameToZip]
       , (error) ->
         process.chdir(orig)
         if error
           reject(error)
           return

-        grunt.log.writeln ">> Created #{zipFilepath}"
-        grunt.log.writeln ">> Uploading…"
-        uploadToS3(zipFilepath, key).then(resolve).catch(reject)
+        grunt.log.writeln ">> Created #{buildZipPath}"
+        uploadToS3(buildZipFilename, key).then(resolve).catch(reject)

   grunt.registerTask "publish-nylas-build", "Publish Nylas build", ->
     awsKey = process.env.AWS_ACCESS_KEY_ID ? ""


Supports basic auth with a cli param

Supports basic auth with a cli param

--- src/dredd.coffee
+++ src/dredd.coffee
@@ -14,6 +14,7 @@
   'output': {'alias': 'o', 'description': 'Specifies output file when using additional file-based reporter. This option can be used multiple times if multiple file-based reporters are used.', default: []}
   'header': {'alias': 'h', 'description': 'Extra header to include in every request. This option can be used multiple times to add multiple headers.', default:[]}
   'verbose': { 'description': 'Display debug information', 'default': false}
+  'user': {'alias': 'u', 'description': 'Basic Auth credentials in the form username:password.', 'default': null}

 ###
   Events:
@@ -37,12 +38,11 @@

 class Dredd
   constructor: (config) ->
-    emitter = new EventEmitter
     @callback = null
     @configuration =
       blueprintPath: null
       server: null
-      emitter: emitter
+      emitter: new EventEmitter
       options:
         'dry-run': false
         silent: false
@@ -50,6 +50,7 @@
         output: null
         debug: false
         header: null
+        user: null
     @testData =
       tests: []
       stats:
@@ -66,6 +67,10 @@
     #normalize options and config
     for own key, value of config
       @configuration[key] = value
+
+    if @configuration.options.user?
+      authHeader = 'Authorization:Basic ' + new Buffer(@configuration.options.user).toString('base64')
+      @configuration.options.header.push authHeader

     #coerce single/multiple options into an array
     @configuration.options.reporter = coerceToArray(@configuration.options.reporter)
@@ -99,7 +104,6 @@

           # need to wait for files to finish writing, otherwise we can exit
           if stats.fileBasedReporters is 0
-            console.log "no file reporters"
             callback(null, stats)

   # called when a file-based reporter saves


DRY assert stream

DRY assert stream

--- lib/index.coffee
+++ lib/index.coffee
@@ -57,12 +57,8 @@
       .each (obj, cb) -> out.write obj, cb

     streams.push _(have_join_key).stream()
-      .map ([key, obj], cb) ->
-        # This function is synchronous, but the only way that map exposes to return an error is via
-        # an asynchronous callback
-        return setImmediate cb, new Error "join key '#{field}' was not a primitive" if _(key).isObject()
-        hash = JSON.stringify key
-        setImmediate cb, null, hash + options.delim + obj + '\n'
+      .assert((([key, obj]) -> not _(key).isObject()), "join key '#{field}' was not a primitive")
+      .map(([key, obj]) -> JSON.stringify(key) + options.delim + obj + '\n')
       .writeFile file_name

     file_names.push file_name


adding demos to uses section

adding demos to uses section

Former-commit-id: 186c2987db541b694a6fddbeee54d3e73bb78c89 [formerly 41dffe72f11615b80a1aff17856ed62a3ffc7fd2] [formerly b0af2af085bedb10992d0b974ea457ece948f6ca [formerly 9982bc41506c45521118158bd0d63118122867d7]] [formerly 906af0e3fc16662162b43393aa11791d8faa5cf3 [formerly 5c5a69207e2e2702c108b82f3a573147567e9e81] [formerly e6adf05cf9169257f18a31a89149cfd6f2e57ae8 [formerly e6adf05cf9169257f18a31a89149cfd6f2e57ae8 [formerly 0a8441de3ae1690f1a596b86b3fe3df23b3ee229]]]]
Former-commit-id: 09fb8d4a89a738150c06610091e99c487dbecf61 [formerly 06b53089e3164f30037e083575e48d85917cfdea] [formerly 7984ed927652850ce6c62cb05cf37ac1ac783488 [formerly 1937022819db65745ac76d813ccb01c42749367f]]
Former-commit-id: fbcbec283ca7990f73241fb41d1282e0977c6d94 [formerly 3183d4b5b22feb19b6e93e7f8433de7323fb7c82]
Former-commit-id: e696ce76d8f1bc884252678732b7e0de9fe29c1e
Former-commit-id: 9d694c0d9f2ad4e8c0267bf93a5681a306cb9a14

--- @client/saas_landing_page/uses.coffee
+++ @client/saas_landing_page/uses.coffee
@@ -1,57 +1,100 @@
+p =
+  marginTop: 15

+demosList = (demos) ->
+  P style: p,
+    "Demos: "
+    for demo, idx in demos
+
+      [A
+        href: demo[1]
+        target: '_blank'
+        style:
+          textDecoration: 'underline'
+
+        demo[0]
+      if idx < demos.length - 1
+        ', '
+      ]

 uses = [
   {
     icon: 'doc'
     strong: 'Collect feedback'
-    body: """
-          You have a draft proposal. Maybe it’s a plan, a policy, or an idea for
-          a new product direction. Whatever it may be, Consider.it can help you
-          gather organized feedback from anyone you invite to participate.
-          Improve your proposal with the insights of others, without having to
-          sort through a long email chain.
-          """
+    body: ->
+      """
+      You have a draft proposal. Maybe it’s a plan, a policy, or an idea for
+      a new product direction. Whatever it may be, Consider.it can help you
+      gather organized feedback from anyone you invite to participate.
+      Improve your proposal with the insights of others, without having to
+      sort through a long email chain.
+      """
   }, {
     icon: 'crossroads'
     strong: "Lead change"
-    body: """
-          Engage employees, membership, and stakeholders about the future.
-          Strong leaders create change and build buy-in by explaining and
-          evolving plans, not imposing them.
-          """
+    body: ->
+      """
+      Engage employees, membership, and stakeholders about the future.
+      Strong leaders create change and build buy-in by explaining and
+      evolving plans, not imposing them.
+      """
   }, {
     icon: 'teaching'
     strong: "Teach critical thinking"
-    body: """
-          Students learn how to develop and express a considered opinion while listening
-          to and engaging with others' ideas. Supports Common Core aligned exercises
-          in English and Social Studies.
-          """
+    body: ->
+        demos = [
+          ["literature discussion", 'https://schools.consider.it/Gatsby_Wedding?results=true'],
+          ["historical debate", 'https://schools.consider.it/Atomic_Bombs?results=true'],
+          ["civics / bioethics", 'https://schools.consider.it/Genetic_Testing?results=true']
+        ]
+        DIV null,
+          P style: p,
+            """
+            Students learn how to develop and express a considered opinion while listening
+            to and engaging with others' ideas. Supports Common Core aligned exercises
+            in English and Social Studies.
+            """
+
+          demosList(demos)
+
+
   }, {
     icon: 'network'
     strong: "Decentralize decision making"
-    body: """
-          Make decisions as a whole, without resorting to hierarchy. The will of a community,
-          and the thoughts behind that will, become visible and actionable.
-          """
+    body: ->
+      """
+      Make decisions as a whole, without resorting to hierarchy. The will of a community,
+      and the thoughts behind that will, become visible and actionable with Consider.it.
+      """

-  },{
+  }, {
     icon: 'public'
     strong: "Engage the public"
-    body: """
+    body: ->
+      demos = [
+        ["Voters Guide", 'https://livingvotersguide.org'],
+        ["NASA Asteroid Initiative", 'https://ecastonline.consider.it']
+      ]
+
+      DIV null,
+        P style: p,
+
+          """
           Enable constituents to provide input on an upcoming decision. Consider.it
           organizes this feedback into a guide to public thought that can be used
           to refine the proposal or target common misconceptions during outreach.
           """
+        demosList(demos)

   }, {
     icon: 'meeting'
     strong: 'Conduct meetings'
-    body: """
-          Plan more effective meetings by creating agendas on Consider.it. Thresh
-          through ideas before a meeting. After a meeting, use Consider.it to
-          close the loose ends!
-          """
+    body: ->
+      """
+      Plan more effective meetings by creating agendas on Consider.it. Thresh
+      through ideas before a meeting. After a meeting, use Consider.it to
+      close the loose ends!
+      """
   }
 ]

@@ -127,7 +170,7 @@
       DIV
         style: light_base_text

-        props.body
+        props.body()

     if !props.even


Fix the CI build

Fix the CI build

--- tests/e2e/table_spec.coffee
+++ tests/e2e/table_spec.coffee
@@ -5,7 +5,7 @@
 module.exports =
 	"The owl-table is on the page with data": (browser) ->
 		browser
-			.url("http://localhost:9000")
+			.url("http://onijim.github.io/owl-table")
 			.waitForElementVisible('owl-table', 10000)
 			.waitForElementPresent('.owl-row', 10000)


Revert time-without-period-marker test changes

--- tests/models/test_punch.coffee
+++ tests/models/test_punch.coffee
@@ -1,5 +1,5 @@

-moment = require 'moment-timezone'
+moment = require 'moment'
 expect = require('chai').expect

 Organization = require('../../src/models/organization').get('test')
@@ -87,10 +87,9 @@
       punch = Punch.parse @user, 'in 9:15', 'in'
       expect(punch).to.have.property 'mode', 'in'
       expect(punch).to.have.deep.property 'times[0]'
-      tz = @user.timetable.timezone.name
-      now = moment.tz(tz)
+      now = moment()
       amPm = now.format('A')
-      expectedTime = moment.tz("9:15 #{amPm}", 'h:mm A', tz)
+      expectedTime = moment("9:15 #{amPm}", 'h:mm A')
       if expectedTime.isAfter now
         if expectedTime.diff(now, 'hours', true) > 6
           expectedTime.subtract(12, 'hours')


Added some super rough code to keep server from crashing

--- gameserver/gameserver.coffee
+++ gameserver/gameserver.coffee
@@ -120,12 +120,13 @@
         @pending_lobbies = new_pending

     sendChat: (id, text) =>
+        return unless id of @lobbies_by_id
         @lobbies_by_id[id].sendChat(id, text)

     getMatchingLobbies: (player) =>
         fake_lobby = new Lobby(player)
         return (l.players[0] for l in @pending_lobbies when l.matches(fake_lobby))
-
+
     joinLobby: (player, cb) =>
         new_lobby = new Lobby(player)
         lobby_to_join = @lobbies_by_id[player.params.lobby_id]
@@ -138,7 +139,12 @@
             cb('Lobby unavailable, please try another.')

 LobbyManager = new LobbyManagerGlobal()
-setInterval(LobbyManager.matchmake, 5000)
+setInterval(->
+    try
+        LobbyManager.matchmake()
+    catch err
+        console.log('Exception: ' + err)
+, 5000)

 class UserProfilesGlobal
     LEAGUES:
@@ -228,37 +234,55 @@
     )
     socket.on('createLobby', (req, cb) ->
         UserProfiles.get(req.profile_url, (err, profile) ->
-            return if err
-            req.league = profile.league
-            player = new Player(socket, req)
-            LobbyManager.addPlayer(player)
-            cb()
+            try
+                return if err
+                req.league = profile.league
+                player = new Player(socket, req)
+                LobbyManager.addPlayer(player)
+                cb()
+            catch err
+                console.log('Exception: ' + err)
         )
     )
     socket.on('listLobbies', (req, cb) ->
         UserProfiles.get(req.profile_url, (err, profile) ->
-            return if err
-            req.league = profile.league
-            player = new Player(socket, req)
-            lobbies = LobbyManager.getMatchingLobbies(player)
-            cb(lobbies)
+            try
+                return if err
+                req.league = profile.league
+                player = new Player(socket, req)
+                lobbies = LobbyManager.getMatchingLobbies(player)
+                cb(lobbies)
+            catch err
+                console.log('Exception: ' + err)
         )
     )
     socket.on('joinLobby', (req, cb) ->
         UserProfiles.get(req.profile_url, (err, profile) ->
-            return if err
-            req.league = profile.league
-            player = new Player(socket, req)
-            LobbyManager.joinLobby(player, cb)
+            try
+                return if err
+                req.league = profile.league
+                player = new Player(socket, req)
+                LobbyManager.joinLobby(player, cb)
+            catch err
+                console.log('Exception: ' + err)
         )
     )
     socket.on('sendChat', (text) ->
-        LobbyManager.sendChat(socket.id, text)
+        try
+            LobbyManager.sendChat(socket.id, text)
+        catch err
+            console.log('Exception: ' + err)
     )
     socket.on('exitLobby', ->
-        LobbyManager.removePlayer(socket.id)
+        try
+            LobbyManager.removePlayer(socket.id)
+        catch err
+            console.log('Exception: ' + err)
     )
     socket.on('disconnect', ->
-        LobbyManager.removePlayer(socket.id)
+        try
+            LobbyManager.removePlayer(socket.id)
+        catch err
+            console.log('Exception: ' + err)
     )
 )


don't use parenthesis when requireing

don't use parenthesis when requireing

--- lib/idris-controller.coffee
+++ lib/idris-controller.coffee
@@ -1,6 +1,6 @@
 {MessagePanelView, PlainMessageView, LineMessageView} =
-  require('atom-message-panel')
-ProofObligationView = require('./views/proof-obligation-view')
+  require 'atom-message-panel'
+ProofObligationView = require './views/proof-obligation-view'
 HolesView = require './views/holes-view'
 StatusIndicator = require './views/status-indicator-view'
 Logger = require './Logger'


fix warning positions

fix warning positions

--- lib/idris-controller.coffee
+++ lib/idris-controller.coffee
@@ -103,14 +103,11 @@
         @messages.show()
         @messages.clear()
         @messages.setTitle '<i class="icon-bug"></i> Idris Errors', true
-        i = 0
-        while i < err.warnings.length
-          warning = err.warnings[i]
-          @messages.add new LineMessageView(
-            line: warning[1]
-            character: warning[2]
-            message: warning[3])
-          i++
+        for warning in err.warnings
+          @messages.add new LineMessageView
+            line: warning[1][0]
+            character: warning[1][1]
+            message: warning[3]
       else if progress
         console.log '... ' + progress
         @statusbar.setStatus 'Idris: ' + progress


Make interactiveActionTimeout actually work

--- lib/ghc-mod/interactive-process.coffee
+++ lib/ghc-mod/interactive-process.coffee
@@ -60,7 +60,7 @@
           cleanup = =>
             @proc.stdout.removeListener 'data', parseData
             @proc.removeListener 'exit', exitCallback
-            clearTimeout timer
+            clearTimeout timer if timer?
           parseData = (data) ->
             debug "Got response from ghc-modi:#{EOL}#{data}"
             lines = data.split(EOL)
@@ -77,11 +77,12 @@
             reject mkError "ghc-modi crashed", "#{savedLines}"
           @proc.stdout.on 'data', parseData
           @proc.on 'exit', exitCallback
-          timer = setTimeout (->
-            cleanup()
-            console.error "#{savedLines}"
-            reject mkError "Timeout", "#{savedLines}"
-            ), 60000
+          if tml = atom.config.get('haskell-ghc-mod.interactiveActionTimeout')
+            timer = setTimeout (->
+              cleanup()
+              console.error "#{savedLines}"
+              reject mkError "Timeout", "#{savedLines}"
+              ), tml * 1000
       args_ =
         if @caps.quoteArgs
           ['ascii-escape', command].concat args.map (x) -> "\x02#{x}\x03"


Made the disabled option work with kodingswitch

--- client/Main/CommonViews/kodingswitch.coffee
+++ client/Main/CommonViews/kodingswitch.coffee
@@ -13,7 +13,9 @@

     @domElement = $ "

"

-  mouseDown:-> @setValue if @getValue() is on then off else on
+  mouseDown:->
+    unless @getOption 'disabled'
+      @setValue if @getValue() is on then off else on

   setOff:(wCallback = yes)->


enable multi cursor again

enable multi cursor again

--- demo/scripts/advanced.coffee
+++ demo/scripts/advanced.coffee
@@ -25,7 +25,7 @@
     console.assert(sourceDelta.isEqual(targetDelta), "Editor diversion!", source, target, sourceDelta, targetDelta)
   ).on(Quill.events.SELECTION_CHANGE, (range) ->
     return unless range?
-    # target.getModule('multi-cursor').moveCursor(source.id, range.end)
+    target.getModule('multi-cursor').moveCursor(source.id, range.end)
   )

@@ -35,7 +35,7 @@
   $container = $('.editor-container', $wrapper)
   editor = new Quill($container.get(0), {
     modules:
-      # 'multi-cursor': true
+      'multi-cursor': true
       'toolbar': { container: $('.toolbar-container', $wrapper).get(0) }
       'link-tooltip': true
       'image-tooltip': true
@@ -52,5 +52,5 @@
 listenEditor(editors[1], editors[0])
 editors[0].getModule('authorship').addAuthor(editors[1].id, getColor(editors[1].id, true))
 editors[1].getModule('authorship').addAuthor(editors[0].id, getColor(editors[0].id, true))
-# editors[0].getModule('multi-cursor').setCursor(editors[1].id, 0, editors[1].id, getColor(editors[1].id))
-# editors[1].getModule('multi-cursor').setCursor(editors[0].id, 0, editors[0].id, getColor(editors[0].id))
+editors[0].getModule('multi-cursor').setCursor(editors[1].id, 0, editors[1].id, getColor(editors[1].id))
+editors[1].getModule('multi-cursor').setCursor(editors[0].id, 0, editors[0].id, getColor(editors[0].id))


Activity: further unused stuff removed from AppControlle

--- client/Social/Activity/AppController.coffee
+++ client/Social/Activity/AppController.coffee
@@ -41,9 +41,6 @@
     @lastQuery             = null

     KD.singletons.dock.getView().show()
-
-    # if @mainController.appIsReady then @putListeners()
-    # else @mainController.on 'AppIsReady', => @putListeners()

     @status = KD.getSingleton "status"
     @status.on "reconnected", (conn)=>
@@ -328,11 +325,6 @@
     to   = new Date(messages.first.meta.createdAt).getTime()
     @setLastTimestamps to, from #from, to

-  # Store first & last activity timestamp.
-  extractTeasersTimeStamps:(teasers)->
-    return unless teasers.first
-    @setLastTimestamps new Date(teasers.last.meta.createdAt).getTime(), new Date(teasers.first.meta.createdAt).getTime()
-
   createContentDisplay:(activity, callback=->)->
     contentDisplay = @createStatusUpdateContentDisplay activity
     @utils.defer -> callback contentDisplay
@@ -347,17 +339,6 @@
       title : "Status Update"
       type  : "status"
     ,activity
-
-  streamByIds:(ids, callback)->
-
-    selector = _id : $in : ids
-    KD.remote.api.CActivity.streamModels selector, {}, (err, model) =>
-      if err then callback err
-      else
-        unless model is null
-          callback null, model[0]
-        else
-          callback null, null

   fetchActivitiesProfilePage:(options,callback)->
     {originId} = options


ide: file permissions method name changed

--- client/ide/lib/views/tabview/ideview.coffee
+++ client/ide/lib/views/tabview/ideview.coffee
@@ -115,7 +115,7 @@
     if file.isDummyFile() or not notifyIfNoPermissions
       return @createEditorAfterFileCheck file, content, callback, emitChange, no

-    file.isReadableWritable (err, result) =>
+    file.permissions (err, result) =>

       return showErrorNotification err  if err


[client][app][mainapp] changed notify message

--- client/landing/app/MainApp/KD.extend.coffee
+++ client/landing/app/MainApp/KD.extend.coffee
@@ -35,6 +35,7 @@
             if groupName and KD.isLoggedIn()
               @joinGroup_ groupName, (res)=>
                 unless res then return @notify_ "Joining to #{groupName} group failed", "error"
+                @notify_ "You have joined to #{groupName} group!", "success"
                 KD.lastFuncCall?()
                 KD.lastFuncCall = null
         KD.lastFuncCall = callback
@@ -59,7 +60,7 @@
             return callback true
         currentGroup.join (err)=>
           if err then return callback false
-          @notify_ "Joined to #{groupName} group!", "success"
+          @notify_ "You have joined to #{groupName} group!", "success"
           return callback true

   jsonhTest:->


Assume uris are always Strings

Assume uris are always Strings

--- lib/markdown-preview.coffee
+++ lib/markdown-preview.coffee
@@ -6,7 +6,7 @@
   activate: ->
     atom.workspaceView.command 'markdown-preview:show', => @show()

-    atom.project.registerOpener (uriToOpen='') ->
+    atom.project.registerOpener (uriToOpen) ->
       {protocol, pathname} = url.parse(uriToOpen)
       if protocol is 'markdown-preview:' and fs.isFileSync(pathname)
         new MarkdownPreviewView(pathname)


Ignoring dragged blocks for resolving, hitbox

--- src/js/Renderer.coffee
+++ src/js/Renderer.coffee
@@ -137,7 +137,8 @@
       for blockX in [0...@map.size]
         for blockY in [@map.size - 1..0]
           for blockZ in [@map.size - 1..0]
-            continue unless currentBlock = @map.getBlock blockX, blockY, blockZ
+            currentBlock = @map.getBlock(blockX, blockY, blockZ)
+            continue if not currentBlock or currentBlock.dragged
             [screenX, screenY] = @renderingCoordinatesForBlock blockX, blockY, blockZ

             pixel = @getTexture('basic','solid').getContext('2d').getImageData x - screenX, y - screenY, 1, 1
@@ -148,7 +149,7 @@
                 side: side
               }
     else
-      return null
+      return {}

   getTexture: (group, type, rotation) ->
     unless rotation
@@ -168,7 +169,7 @@

     @map.visibleBlocksEach (block, x, y, z) =>
       @drawBlock  block, x, y, z
-      @drawHitbox block, x, y, z
+      @drawHitbox block, x, y, z unless block.dragged

     @map.setNeedsRedraw no
     @isDrawing = no


Make sure to format the whole path

Make sure to format the whole path

--- lib/models/synced-fs.coffee
+++ lib/models/synced-fs.coffee
@@ -38,7 +38,7 @@
     @ws.send JSON.stringify({
       action: "local_save",
       project: {
-        path: project.getPaths()[0]
+        path: formatFilePath(project.getPaths()[0])
       },
       file: {
         path: this.formatFilePath(file.path)


Tests: Fixed postMessageAndSeeIfItsPostedOnlyOnce test.

--- tests/src/activity/post.coffee
+++ tests/src/activity/post.coffee
@@ -27,12 +27,14 @@

     helpers.postActivity(browser)

-    browser.waitForElementVisible '[testpath=activity-list] > section:first-child', 25000
+    # browser.waitForElementVisible '[testpath=activity-list] > section:first-child', 25000

     secondPostSelector = '[testpath=activity-list] section:nth-of-type(2) [testpath=ActivityListItemView]:nth-of-type(1) article'

-    browser.getText secondPostSelector, (result) ->
-      assert.notEqual(post, result.value)
+    browser
+      .waitForElementVisible secondPostSelector, 25000
+      .getText secondPostSelector, (result) ->
+        assert.notEqual(post, result.value)

     browser.end()


Log less

Log less

--- rtmp.coffee
+++ rtmp.coffee
@@ -520,6 +520,7 @@
       continue

     #console.log "lets stream"
+    #fixme -> remove the ! and test again, this was a hack for stream switching
     if !session.isPlaying
       if session.stream.isVideoStarted  # has video stream
         for rtmpMessage, i in msgs
@@ -548,7 +549,7 @@
         buf = createRTMPAggregateMessage msgs, session.chunkSize
       else
         buf = createRTMPMessage msgs[0], session.chunkSize
-      console.log "Sending data to", session.streamName
+      #console.log "Sending data to", session.streamName
       session.sendData buf

   return


tests for bundle loading

tests for bundle loading

--- tests/test_loader_wd.coffee
+++ tests/test_loader_wd.coffee
@@ -260,6 +260,125 @@
                     ])
                 expect(urls[6]).to.equal("/app/")
                 expect(urls[7]).to.equal("/app/manifest.json")
+            .safeExecute("localforage.keys( function(err, keys) { window.forage_keys = keys; } )")
+            .sleep(DELAY)
+            .safeEval("window.forage_keys")
+            .then (keys) =>
+                expect(keys.map((key) => key.split(":")[2])).to.consist([
+                    "/app/a.js",
+                    "/app/b.js",
+                    "/app/c.js",
+                    "/app/d.js",
+                    ])
+            .then ->
+                content = """
+                    var d = require("./d.js");
+                    module.exports = function()
+                    {
+                        return "B" + d();
+                    };
+                    """
+                fs.writeFileSync("/app/b.js", content)
+                fs.unlinkSync("/app/c.js")
+                spa.Builder.from_config("/app/spa.yaml").build()
+            .get('http://127.0.0.1:3332/')
+            .sleep(DELAY)
+            .then => @urls_log.clear()
+            .get('http://127.0.0.1:3332/app/')
+            .sleep(3 * DELAY)
+            .title().should.eventually.become("aBd")
+            .then =>
+                urls = @urls_log.get()
+                expect(urls[0]).to.equal("/app/")
+                expect(urls[1]).to.equal("/app/manifest.json")
+                expect(urls[2]).to.equal("/app/b.js")
+                expect(urls[3]).to.equal("/app/")
+                expect(urls[4]).to.equal("/app/manifest.json")
+            .safeExecute("localforage.keys( function(err, keys) { window.forage_keys = keys; } )")
+            .sleep(DELAY)
+            .safeEval("window.forage_keys")
+            .then (keys) =>
+                expect(keys.map((key) => key.split(":")[2])).to.consist([
+                    "/app/a.js",
+                    "/app/b.js",
+                    "/app/d.js",
+                    ])
+            .safeExecute("localforage.clear()")
+            .nodeify(done)
+
+    it.only 'multiple files loading and updating with bundle', (done) ->
+        return @browser
+            .then ->
+                system = yaml.safeLoad("""
+                    index.html: |
+
+
+                                <title></title>
+
+
+
Testing

+
+
+                    app:
+                        d.js: |
+                            module.exports = function()
+                            {
+                                return "d";
+                            };
+                        c.js: |
+                            var d = require("./d.js");
+                            module.exports = function()
+                            {
+                                return "c" + d();
+                            };
+                        b.js: |
+                            var c = require("./c.js");
+                            module.exports = function()
+                            {
+                                return "b" + c();
+                            };
+                        a.js: |
+                            var loader = require("loader");
+                            var b = require("./b.js");
+                            loader.onApplicationReady = function()
+                            {
+                                document.title = "a" + b();
+                                loader.checkUpdate();
+                            };
+                            loader.onUpdateCompleted = function(event) {
+                                setTimeout(location.reload.bind(location), 0)
+                                return true
+                            };
+                        spa.yaml: |
+                            root: "./"
+                            manifest: "./manifest.json"
+                            index: "./index.html"
+                            bundle: "./bundle.js"
+                            randomize_urls: false
+                            excludes:
+                                - "./bundle.js"
+                            hosting:
+                                "./(*.js)": "/app/$1"
+                    """)
+                utils.mount(system, path.resolve(__dirname, "../lib/assets"))
+                utils.mount(system, path.resolve(__dirname, "../tests"))
+                mock(system)
+                spa.Builder.from_config("/app/spa.yaml").build()
+            .get('http://127.0.0.1:3332/')
+            .sleep(DELAY)
+            .clearLocalStorage()
+            .then => @urls_log.clear()
+            .get('http://127.0.0.1:3332/app/')
+            .sleep(3 * DELAY)
+            .title().should.eventually.become("abcd")
+            .then =>
+                urls = @urls_log.get()
+                expect(urls).to.be.an("Array").with.length(5)
+                expect(urls[0]).to.equal("/app/")
+                expect(urls[1]).to.equal("/app/manifest.json")
+                expect(urls[2]).to.equal("/app/bundle.js")
+                expect(urls[3]).to.equal("/app/")
+                expect(urls[4]).to.equal("/app/manifest.json")
             .safeExecute("localforage.keys( function(err, keys) { window.forage_keys = keys; } )")
             .sleep(DELAY)
             .safeEval("window.forage_keys")


original oauth2 is correct

original oauth2 is correct

--- src/api-strategy.coffee
+++ src/api-strategy.coffee
@@ -1,5 +1,5 @@
 _ = require 'lodash'
-PassportGoogleCloudPlatform = require 'passport-google-oauth20'
+PassportGoogleCloudPlatform = require 'passport-google-oauth2'

 class GoogleCloudPlatformStrategy extends PassportGoogleCloudPlatform
   constructor: (env) ->


Uncaught exceptions need to be left uncaught when running tests.

--- lib/unhandled_exception_handler.coffee
+++ lib/unhandled_exception_handler.coffee
@@ -18,8 +18,8 @@

   enable: ()->
     enabled= true
-    process.on('uncaughtException', uncaught)
+    process.on('uncaughtException', uncaught) unless process.env.NODE_ENV == 'test'

   diable: ()->
-    process.removeListener('uncaughtException', uncaught)
+    process.removeListener('uncaughtException', uncaught) unless process.env.NODE_ENV == 'test'
     enabled= false


Adding robustness assuming API may send back arbitrary data

--- src/base.iced
+++ src/base.iced
@@ -126,6 +126,16 @@

   #-----------------------------------

+  aget : (o, k, cb) ->
+    if k of o
+      return cb null, o[k]
+    cb new Error('''Key #{k} not found in object.'''), null
+
+  aassert : (x, cb) ->
+    if x == true then cb null else cb new Error('Assertion failed.')
+
+  #-----------------------------------
+
   # Estimates fee per byte for a specific network type needed to achieve
   # verification before maxClearanceMins minutes For bitcoin, returns in
   # satoshis and uses the 21.co API with no fallback.
@@ -135,13 +145,17 @@
       apiUrl = 'https://bitcoinfees.21.co/api/v1/fees/list'
       await request apiUrl, esc defer resp, body
       if resp.statusCode == 200
-        fees = JSON.parse(body)['fees']
+        await a_json_parse body, esc defer body_json
+        await @aget body_json, 'fees', esc defer fees
         currentClearanceMins = 10000
         idx = 0
+        await @aassert Array.isArray(fees), esc defer()
         while idx < fees.length and currentClearanceMins >= maxClearanceMins
           fee = fees[idx]
-          currentClearanceMins = fee['maxMinutes']
-          currentFee = fee['maxFee']
+          await @aget fee, 'maxMinutes', esc defer currentClearanceMins
+          await @aget fee, 'maxFee', esc defer currentFee
+          await @aassert Number.isFinite(currentClearanceMins), esc defer()
+          await @aassert Number.isFinite(currentFee), esc defer()
           idx++
         cb null, currentFee
       else if resp.statusCode == 429


bug fix

bug fix

--- src/plugins/request-portals.coffee
+++ src/plugins/request-portals.coffee
@@ -143,7 +143,7 @@
         for i in [0 ... tiles.length] by bucketSize
             tileIdsBucket.push tiles[i ... i + bucketSize]

-        async.eachLimit tiles, Config.Database.MaxParallel, (tileIds, callback) ->
+        async.eachLimit tileIdsBucket, Config.Database.MaxParallel, (tileIds, callback) ->
             # find this tile in the database
             Database.db.collection('Tiles').find
                 _id:


Add Docco-style comments

Add Docco-style comments

--- src/kissmetrics.coffee
+++ src/kissmetrics.coffee
@@ -1,8 +1,23 @@
+# # Kissmetrics JS
+
+# ## Bootstrap
+# -------------
+
+# Keep track of what environment we're running in, Node.js or a browser.
+# If we're in Node, require the `https` module for our network requests.
+# If we're in a browser we'll just use the implicit requests generated by
+# `Image`'s `src` property. Kissmetrics responds to API requests with an
+# `image/gif` content type, so the browser won't throw up any warnings.
 ENV = if typeof exports isnt 'undefined' then 'node' else 'browser'

 https = require 'https' if ENV is 'node'

+# ## Requests
+# ------------
+
+# Generic wrapper for HTTP requests that abstracts the differences between
+# requests made in Node and in a browser.
 httpRequest = (url) ->
 	if ENV is 'node'
 		https.get url
@@ -10,6 +25,22 @@
 		(new Image()).src = url

+# ## KissmetricsClient
+# --------------------
+
+# Wrapper for interacting with the Kissmetrics API.
+#
+# Instantiates a new client with your Kissmetrics API key and an identifier
+# for the person you're recording data about. A new client must be created
+# for each person you record data about.
+#
+# `key` (String): Your API key from Kissmetrics
+#
+#	`person` (String): An identifier for the person you'll record data about
+#
+# ```
+# km = new KissmetricsClient(API_KEY, 'evan@example.com')
+# ```
 class KissmetricsClient
 	constructor: (@key, @person) ->
 		@host        = 'trk.kissmetrics.com'
@@ -19,20 +50,74 @@
 			set    : 's'
 			alias  : 'a'

+	# ### record
+	# ----------
+
+	# Record an "event" in Kissmetrics.
+	# http://support.kissmetrics.com/apis/common-methods#record
+	#
+	# `action` (String): Name of the event you're recording. This is
+	#   usually something a person did or something that affects them.
+	#
+	# `properties` (Object) *Optional*: Properties to associate with
+	#   the person's event. Keys will be used as property names and values
+	#   as property values.
+	#
+	# ```
+	# km.record('Signed up', {page: 'home'})
+	# ```
 	record: (action, properties = {}) ->
 		properties._n = action
 		@generateQuery 'record', properties

+	# ### set
+	# -------
+
+	# Set a "property" in Kissmetrics.
+	# http://support.kissmetrics.com/apis/common-methods#set
+	#
+	# `properties` (Object): Properties to associate with
+	#   the person's event. Keys will be used as property names and values
+	#   as property values.
+	#
+	# This behaves exactly like the `properties` argument in `record` except
+	# that it is required and that each property has to be sent in its own query.
+	#
+	# ```
+	# km.set({location: 'San Francisco', gender: 'male'})
+	# ```
 	set: (properties) ->
-		# Each property has to be sent in its own query
 		for name, value of properties
 			data       = {}
 			data[name] = value
 			@generateQuery 'set', data

+	# ### alias
+	# ---------
+
+	# Alias a person to another "identity" in Kissmetrics.
+	# http://support.kissmetrics.com/apis/common-methods#alias
+	#
+	# `to` (String): A new identifier to map to the `@person` set on
+	# the current instance.
+	#
+	# ```
+	# km.alias('evan+newemail@example.com')
+	# ```
 	alias: (to) ->
 		@generateQuery 'alias', _n: to

+
+	# ### generateQuery
+	# ------------------
+
+	# Prepare data to be sent to Kissmetrics by turning it into a URL path
+	# and query string. Once the query is formed, call `record()` to send
+	# it to Kissmetrics.
+	#
+	# * `type` (String): Type of data being sent (`record`, `set` or `alias`).
+	#
+	# * `data` (Object): Specific data being recorded about this person.
 	generateQuery: (type, data) ->
 		data._k = @key
 		data._p = @person
@@ -45,10 +130,22 @@

 		@request "#{@query_types[type]}?#{queryString}"

+	# ### record
+	# ----------
+
+	# Query the Kissmetrics API
+	#
+	# `endpoint` (String): URL path (without a leading slash) that will be used
+	#   as a Kissmetrics API endpoint.
 	request: (endpoint) ->
 		httpRequest "https://#{@host}:#{@port}/#{endpoint}"

+# ### Exports
+# -----------
+
+# Make `KissmetricsClient` available either as a Node module or a property
+# on the current context in the browser.
 if ENV is 'node'
 	exports.KissmetricsClient = KissmetricsClient
 else


in mongo, store data under _data if not an object or at the document level if an object always

--- lib/mongo.coffee
+++ lib/mongo.coffee
@@ -51,10 +51,10 @@
   return query

 castToDoc = (docName, data) ->
-  doc = if data.type is 'http://sharejs.org/types/JSONv0'
-    data.data || {}
+  doc = if data.data && typeof data.data is 'object' && !Array.isArray(data.data)
+    data.data
   else
-    data: if data.data? then data.data else null
+    _data: data.data || null
   doc._type = data.type || null
   doc._v = data.v
   doc._id = docName
@@ -65,17 +65,17 @@
   type = doc._type
   v = doc._v
   docName = doc._id
-  delete doc._type
-  delete doc._v
-  delete doc._id
-  if type is 'http://sharejs.org/types/JSONv0'
+  data = doc._data
+  if data is undefined
+    delete doc._type
+    delete doc._v
+    delete doc._id
     data =
       data: doc
       type: type
       v: v
       docName: docName
   else
-    data = doc
     data.type = type
     data.v = v
     data.docName = docName


Updating test

Updating test

--- tests/unit/utils/config.coffee
+++ tests/unit/utils/config.coffee
@@ -155,7 +155,7 @@

     it 'error should be shown when mapped folder doesn\'t exist', (done)->
       out = 0
-      err_msg = 'error Mapping dir for \'mapped\' does not exist ~> '
+      err_msg = 'error Virtual map for \'mapped\' does not exist ~> '
       err_msg += 'non/existent/folder'

       global.__stdout = (data)-> out++


browser adds itself to DOM in add, not show

--- src/atom/browser.coffee
+++ src/atom/browser.coffee
@@ -15,7 +15,7 @@
     true

   # innerHTML - Optional String to set as iframe's content.
-  show: (innerHTML=null) ->
+  add: (innerHTML=null) ->
     style = "width:100%;height:100%;background-color:#fff;border:none"
     @html = "<iframe src='#{@url}' style='#{style}'></iframe>"


Ensure atomic sends when using `GroupComponent` trait with async handlers.

--- src/lib/Helpers.coffee
+++ src/lib/Helpers.coffee
@@ -24,6 +24,23 @@
       when 'disconnect'
         groups = []
         outPort.disconnect()
+
+# outPort wrapper for atomic sends w/ groups
+class AtomicSender
+  constructor: (@port, @groups) ->
+  beginGroup: (group) ->
+    @port.beginGroup group
+  endGroup: ->
+    @port.endGroup()
+  connect: ->
+    @port.connect()
+  disconnect: ->
+    @port.disconnect()
+  send: (packet) ->
+    @port.beginGroup group for group in @groups
+    @port.send packet
+    @port.endGroup() for group in @groups
+

 exports.GroupComponent = (component, func, inPorts='in', outPort='out', config={}) ->
   unless Object.prototype.toString.call(inPorts) is '[object Array]'
@@ -58,21 +75,18 @@
             # Flush the data if the tuple is complete
             if Object.keys(groupedData[key]).length is inPorts.length
               groups = inPort.groups
-              out.beginGroup group for group in groups
-              if config.async
-                grps = groups.slice(0)
-                func groupedData[key], groups, out, (err) ->
-                  if err
-                    component.error err, groups
-                    # For use with MultiError trait
-                    component.fail if typeof component.fail is 'function'
-                  out.endGroup() for group in grps
-                  out.disconnect()
-                  delete groupedData[key]
-              else
-                func groupedData[key], inPort.groups, out
-                out.endGroup() for group in inPort.groups
+              atomicOut = new AtomicSender out, groups
+              callback = (err) ->
+                if err
+                  component.error err, groups
+                   # For use with MultiError trait
+                  component.fail if typeof component.fail is 'function'
                 out.disconnect()
                 delete groupedData[key]
+              if config.async
+                func groupedData[key], groups, atomicOut, callback
+              else
+                func groupedData[key], inPort.groups, atomicOut
+                callback()
           when 'endgroup'
             inPort.groups.pop()


Detect widths

Detect widths

--- slider.coffee
+++ slider.coffee
@@ -19,4 +19,17 @@
 			console?.log msg if settings.debug

 		return @each () ->
-
+			slides = 0
+
+			$this = $(@)
+
+			$this.find('li').each ->
+				slides = slides + 1
+
+			$this.find('li').css(
+				"width": "#{100/slides}%"
+			)
+
+			$this.find('ul').css(
+				"width": "#{100*slides}%"
+			)


Correctly parses (almost) all of Bootstrap's variables + defaults

--- lib/tap-theme-server.coffee
+++ lib/tap-theme-server.coffee
@@ -73,9 +73,9 @@
   # update collection with rendered css
   ThemeCollection.update 'main', {$set: update}

-# bootstrap coillection on first load
+# 'bootstrap' the collection on first load
 if ThemeCollection.find().count() is 0
-  # move me to be handled by TAPi18n
+  # to be handled by TAPi18n...
   descriptions =
     "@gray-dark" : "Main Body Text"
     "@body-bg" : "Background Colour"
@@ -85,16 +85,16 @@
     parser.parse originalVariables, (err,cssTree) ->
       lessVars = []
       for rule in cssTree.rules
-        unless rule.silent
+        if rule.variable
+          lessVar =
+            name: rule.name
           try
-            lessVar =
-              name: rule.name
-              val: rule.value.toCSS()
+            lessVar.val = rule.value.toCSS()
+          catch
+            lessVar.val = rule.value.value[0].value[0].name

-            lessVar.desc = descriptions[lessVar.name] # USE TAPi18n
-
-            lessVars.push lessVar
-
+          lessVar.desc = descriptions[lessVar.name] # USE TAPi18n on client instead
+          lessVars.push lessVar
       done null, lessVars

   ThemeCollection.insert


XHR

XHR

- new parameter "withCredentials", pass thru to XMLHttpRequest.

--- src/base/XHR/XHR.coffee
+++ src/base/XHR/XHR.coffee
@@ -45,6 +45,10 @@
 				mandatory: true
 				default: {}
 				check: "PlainObject"
+			withCredentials:
+				mandatory: true
+				default: false
+				check: Boolean
 		@

@@ -63,6 +67,8 @@
 	readOpts: ->
 		super()
 		@__xhr = new XMLHttpRequest()
+		@__xhr.withCredentials = @_withCredentials
+
 		# CUI.debug "XHR.readOpts", @
 		@__readyStatesSeen = [@readyState()]
 		@__registerEvents("download")