add tests for 2 update scenarii

add tests for 2 update scenarii

--- test/data_tests.coffee
+++ test/data_tests.coffee
@@ -157,7 +157,7 @@
                 @body = parseBody response, body
                 done()

-        it "Then the id of the new document should be returned", ->
+        it "Then the id of the new Document should be returned", ->
             @body.should.have.property '_id'
             @_id = @body._id

@@ -172,3 +172,47 @@
                 @body = parseBody response, body
                 @body.should.have.property 'value', @randomValue
                 done()
+
+describe "Update", ->
+    describe "Try to Update a Document that doesn't exist", ->
+        before cleanRequest
+
+        it "When I send a request to update a Document with id 123", (done) ->
+            client.put 'data/123/', {"value":"created_value"}, \
+                        (error, response, body) =>
+                @response = response
+                done()
+
+        it "Then error 404 should be returned", ->
+            @response.statusCode.should.equal 404
+
+    describe "Update a modified Document in DB (concurrent access)", ->
+        before cleanRequest
+
+    describe "Update a Document (no concurrent access)", ->
+        before cleanRequest
+        after ->
+            delete @randomValue
+
+        it "When I send a request to update Document with id 987", (done) ->
+            @randomValue = randomString()
+            client.put 'data/987/', {"new_value":@randomValue}, \
+                        (error, response, body) =>
+                @response = response
+                done()
+
+        it "Then HTTP status 200 should be returned", ->
+            @response.statusCode.should.equal 200
+
+        it "Then the Document should exist in DataBase", (done) ->
+            client.get "data/exist/987/", (error, response, body) =>
+                @body = parseBody response, body
+                @body.exist.should.be.true
+                done()
+
+        it "Then the old Document must have been replaced", (done) ->
+            client.get "data/987/", (error, response, body) =>
+                @body = parseBody response, body
+                @body.should.not.have.property 'value'
+                @body.should.have.property 'new_value', @randomValue
+                done()


refactored test_operations.coffee

refactored test_operations.coffee

--- test/test_operations.coffee
+++ test/test_operations.coffee
@@ -1,184 +1,262 @@
-# CoffeeScript's operations should be chainable, like Python's.
-ok 500 > 50 > 5 > -5
-
-# Some chainable operators can be negated by `unless`
-ok (true unless 0==10!=100)
-
-ok true is not false is true is not false
-
-ok 0 is 0 isnt 50 is 50
-
-ok 10 < 20 > 10
-
-ok 50 > 10 > 5 is parseInt('5', 10)
-
-eq 1, 1 | 2 < 3 < 4
-
-ok 1 == 1 <= 1, '`x == y <= z` should become `x === y && y <= z`'
-
-i = 0
-ok 1 > i++ < 1, 'chained operations should evaluate each value only once'
-
-
-# `==` and `is` should be interchangeable.
-a = b = 1
-
-ok a is 1 and b is 1
-ok a == b
-ok a is b
-
-
-# Allow "if x not in y"
-obj = {a: true}
-ok 'a' of obj
-ok 'b' not of obj
-
-# And for "a in b" with array presence.
-ok 200 in [100, 200, 300]
-array = [100, 200, 300]
-ok 200 in array
-ok 1 not in array
-ok array[0]++ in [99, 100], 'should cache testee'
-
-# And with array presence on an instance variable.
-obj = {
-  list: [1, 2, 3, 4, 5]
-  in_list: (value) -> value in @list
-}
-ok obj.in_list 4
-ok not obj.in_list 0
-
-# Non-spaced values still work.
-x = 10
-y = -5
-
-ok x*-y is 50
-ok x*+y is -50
-
-
-# Compound operators.
-one  = 1
-two  = 0
-one or= 2
-two or= 2
-
-eq one, 1
-eq two, 2
-
-zero = 0
-
-zero and= 'one'
-one  and= 'one'
-
-eq zero, 0
-eq one , 'one'
-
-
-# Compound assignment should be careful about caching variables.
-count = 0
-list = []
-
-list[++count] or= 1
-eq list[1], 1
-eq count, 1
-
-list[++count] ?= 2
-eq list[2], 2
-eq count, 2
-
-list[count++] and= 'two'
-eq list[2], 'two'
-eq count, 3
-
-base = -> ++count; base
-
-base().four or= 4
-eq base.four, 4
-eq count, 4
-
-base().five ?= 5
-eq base.five, 5
-eq count, 5
-
-
-# Ensure that RHS is treated as a group.
-a = b = false
-a and= b or true
-ok a is false
-
-
-# Bitwise operators:
-ok (10 &   3) is 2
-ok (10 |   3) is 11
-ok (10 ^   3) is 9
-ok (10 <<  3) is 80
-ok (10 >>  3) is 1
-ok (10 >>> 3) is 1
-
-num = 10; ok (num <<=  3) is 80
-num = 10; ok (num >>=  3) is 1
-num = 10; ok (num >>>= 3) is 1
-num = 10; ok (num &=   3) is 2
-num = 10; ok (num ^=   3) is 9
-num = 10; ok (num |=   3) is 11
-
-
-# Compound assignment with implicit objects.
-obj = undefined
-obj ?=
-  one: 1
-
-ok obj.one is 1
-
-obj and=
-  two: 2
-
-ok not obj.one
-ok obj.two is 2
-
-
-# Compound assignment as a sub expression.
-[a, b, c] = [1, 2, 3]
-ok (a + b += c) is 6
-ok a is 1
-ok b is 5
-ok c is 3
-
-
-# Instanceof.
-ok new String instanceof String
-ok new Number not instanceof String
-
-
-#737: `in` should have higher precedence than logical operators.
-eq 1, 1 in [1] and 1
-
-#768: `in` should preserve evaluation order.
-share = 0
-a = -> share++ if share is 0
-b = -> share++ if share is 1
-c = -> share++ if share is 2
-ok a() not in [b(),c()] and share is 3
-
-# `in` with cache and `__indexOf` should work in commaed lists.
-eq [Object() in Array()].length, 1
-
-
-# Operators should respect new lines as spaced.
-a = (123) +
-456
-ok a is 579
-
-a = "1#{2}3" +
-"456"
-ok a is '123456'
-
-
-# Multiple operators should space themselves.
+# binary (2-ary) math operators do not require spaces
+(->
+  a = 1
+  b = -1
+  ok a*-b is 1
+  ok a*+b is -1
+  ok a/-b is 1
+  ok a/+b is -1
+)()
+
+# operators should respect new lines as spaced
+(->
+  a = 123 +
+  456
+  ok a is 579
+
+  b = "1#{2}3" +
+  "456"
+  ok b is '123456'
+)()
+
+# multiple operators should space themselves
 ok + +1 is - -1

-#891: incorrect inversion of chained comparisons
-(->
-  ok (true unless 0 > 1 > 2)
-  ok (true unless (NaN = 0/0) < 0/0 < NaN)
-)()
+# bitwise operators
+(->
+  ok (10 &   3) is 2
+  ok (10 |   3) is 11
+  ok (10 ^   3) is 9
+  ok (10 <<  3) is 80
+  ok (10 >>  3) is 1
+  ok (10 >>> 3) is 1
+
+  num = 10; ok (num <<=  3) is 80
+  num = 10; ok (num >>=  3) is 1
+  num = 10; ok (num >>>= 3) is 1
+  num = 10; ok (num &=   3) is 2
+  num = 10; ok (num ^=   3) is 9
+  num = 10; ok (num |=   3) is 11
+)()
+
+# `instanceof`
+(->
+  ok new String instanceof String
+  ok new Boolean instanceof Boolean
+  # `instanceof` supports negation by prefixing the operator with `not`
+  ok new Number not instanceof String
+  ok new Array not instanceof Boolean
+)()
+
+
+## compound assignment operators
+(->
+
+  # boolean operators
+  (->
+    a  = 0
+    a or= 2
+    eq a, 2
+
+    b  = 1
+    b or= 2
+    eq b, 1
+
+    c = 0
+    c and= 2
+    eq c, 0
+
+    d = 1
+    d and= 2
+    eq d, 2
+
+    # ensure that RHS is treated as a group
+    e = f = false
+    e and= f or true
+    ok e is false
+  )()
+
+  # compound assignment as a sub expression
+  (->
+    [a, b, c] = [1, 2, 3]
+    ok (a + b += c) is 6
+    ok a is 1
+    ok b is 5
+    ok c is 3
+  )()
+
+  # compound assignment should be careful about caching variables
+  (->
+    count = 0
+    list = []
+
+    list[++count] or= 1
+    eq list[1], 1
+    eq count, 1
+
+    list[++count] ?= 2
+    eq list[2], 2
+    eq count, 2
+
+    list[count++] and= 'two'
+    eq list[2], 'two'
+    eq count, 3
+
+    base = ->
+      ++count
+      base
+
+    base().four or= 4
+    eq base.four, 4
+    eq count, 4
+
+    base().five ?= 5
+    eq base.five, 5
+    eq count, 5
+  )()
+
+  # compound assignment with implicit objects
+  (->
+    obj = undefined
+    obj ?=
+      one: 1
+
+    ok obj.one is 1
+
+    obj and=
+      two: 2
+
+    ok not obj.one
+    ok obj.two is 2
+  )()
+
+)()
+
+
+## `is`,`isnt`,`==`,`!=`
+(->
+
+  # `==` and `is` should be interchangeable.
+  (->
+    a = b = 1
+    ok a is 1 and b == 1
+    ok a == b
+    ok a is b
+  )()
+
+  # `!=` and `isnt` should be interchangeable.
+  (->
+    a = 0
+    b = 1
+    ok a isnt 1 and b != 0
+    ok a != b
+    ok a isnt b
+  )()
+
+)()
+
+
+## `in`, `of`
+(->
+
+  # `in` should check if an array contains a value using `indexOf`
+  # `of` should check if a property is defined on an object using `in`
+  (->
+    arr = [1]
+    ok 0 of arr
+    ok 1 in arr
+    # prefixing `not` to `in and `of` should negate them
+    ok 1 not of arr
+    ok 0 not in arr
+  )()
+
+  # `in` should be able to operate on an array literal
+  (->
+    ok 2 in [0, 1, 2, 3]
+    ok 4 not in [0, 1, 2, 3]
+    arr = [0, 1, 2, 3]
+    ok 2 in arr
+    ok 4 not in arr
+    # should cache the value used to test the array
+    arr = [0]
+    val = 0
+    ok val++ in arr
+    ok val++ not in arr
+    val = 0
+    ok val++ of arr
+    ok val++ not of arr
+  )()
+
+  # `of` and `in` should be able to operate on instance variables
+  (->
+    obj = {
+      list: [2,3]
+      in_list: (value) -> value in @list
+      not_in_list: (value) -> value not in @list
+      of_list: (value) -> value of @list
+      not_of_list: (value) -> value not of @list
+    }
+    ok obj.in_list 3
+    ok obj.not_in_list 1
+    ok obj.of_list 0
+    ok obj.not_of_list 2
+  )()
+
+  #???: `in` with cache and `__indexOf` should work in argument lists
+  eq [Object() in Array()].length, 1
+
+  #737: `in` should have higher precedence than logical operators.
+  eq 1, 1 in [1] and 1
+
+  #768: `in` should preserve evaluation order.
+  (->
+    share = 0
+    a = -> share++ if share is 0
+    b = -> share++ if share is 1
+    c = -> share++ if share is 2
+    ok a() not in [b(),c()] and share is 3
+  )()
+
+)()
+
+
+## CoffeeScript supports chainable operators like Python
+(->
+
+  ok 100 > 10 > 1 > 0 > -1
+  ok -1 < 0 < 1 < 10 < 100
+
+  # `is` and `isnt` may be chained
+  ok true is not false is true is not false
+  ok 0 is 0 isnt 1 is 1
+
+  # different comparison operators (`>`,`<`,`is`,etc.) may be combined
+  ok 1 < 2 > 1
+  ok 10 < 20 > 2+3 is 5
+
+  # some chainable operators can be negated by `unless`
+  ok (true unless 0==10!=100)
+
+  # operator precedence: `|` lower than `<`
+  eq 1, 1 | 2 < 3 < 4
+
+  # preserve references
+  (->
+    a = b = c = 1
+    # `a == b <= c` should become `a === b && b <= c`
+    ok a == b <= c
+  )()
+
+  # chained operations should evaluate each value only once
+  (->
+    a = 0
+    ok 1 > a++ < 1
+  )()
+
+  #891: incorrect inversion of chained comparisons
+  (->
+    ok (true unless 0 > 1 > 2)
+    ok (true unless (NaN = 0/0) < 0/0 < NaN)
+  )()
+
+)()


Remove unused import

Remove unused import

--- test/unit/alias.spec.coffee
+++ test/unit/alias.spec.coffee
@@ -5,7 +5,6 @@
   decodeURIComponent
   Math
   createElement
-  createTextNode
 } from "@/alias"

 describe "Alias", ->


added test for #20

added test for #20

--- test/index_test.coffee
+++ test/index_test.coffee
@@ -682,10 +682,26 @@
       .pipe(amdOptimize("index"))
       .on("data", (file) ->
         assert(file.sourceMap?)
-        assert(_.contains(file.sourceMap.sources.toString(), file.relative))
-        assert(_.contains(file.sourceMap.sources.toString(), file.relative.replace(".js", ".coffee")))
+        assert(_.contains(file.sourceMap.sources, file.relative))
+        assert(_.contains(file.sourceMap.sources, file.relative.replace(".js", ".coffee")))
       )
       # .pipe(sourcemaps.write("."))
       # .pipe(vinylfs.dest("#{dir}/.tmp"))
       .on("end", done)

+
+  it "should keep the relative paths", (done) ->
+
+    checkExpectedFiles(
+      ["fuz/ahah.js", "duu.js"]
+      vinylfs.src("#{dir}/fixtures/core/**/*.js")
+        .pipe(amdOptimize("duu"))
+        .pipe(sourcemaps.init())
+        .on("data", (file) ->
+          assert(file.sourceMap?)
+          assert.equal(file.sourceMap.file, file.relative)
+          assert(_.contains(file.sourceMap.sources, file.relative))
+        )
+      done
+    )
+


Fixed error messages

Fixed error messages

--- client.coffee
+++ client.coffee
@@ -525,9 +525,9 @@
 		circle = L.circle(event.latlng, beaconRadius)
 		outsideGame = !(boundaryRectangle.getBounds().contains(circle.getBounds()))
 		if outsideGame
-			result 'Beacon is outside the game border'
+			result = 'Beacon is outside the game border'

-	if tooClose
+	if tooClose or outsideGame
 		Modal.show(result)

 	else


Test tunings

Test tunings

--- test/js/totodoo.coffee
+++ test/js/totodoo.coffee
@@ -14,13 +14,11 @@

 describe 'Totodoo App', ->

-	describe 'Init', ->
+	@timeout 30000

-		@timeout 12000
+	beforeEach ->
+		@driver.get 'http://127.0.0.1/'

-		beforeEach ->
-			@driver.get 'http://127.0.0.1/'
-
-		it 'has the title of the application in the window\'s title', (done) ->
-			expect(@driver.getTitle()).to.eventually.contain 'Totodoo - Sample todo application with StormPath user management service'
-			@timeout 12000, done()
+	it 'has the title of the application in the window\'s title', (done) ->
+		expect(@driver.getTitle()).to.eventually.contain 'Totodoo - Sample todo application with StormPath user management service'
+		@timeout 10000, done()


:lipstick:

:lipstick:

--- src/app/atom-package.coffee
+++ src/app/atom-package.coffee
@@ -29,17 +29,13 @@
     if _.isArray(activationEvents)
       activateHandler = =>
         @activatePackageMain()
-        for event in activationEvents
-          rootView.off event, activateHandler
-      for event in activationEvents
-        rootView.command event, activateHandler
+        rootView.off(event, activateHandler) for event in activationEvents
+      rootView.command(event, activateHandler) for event in activationEvents
     else
       activateHandler = =>
         @activatePackageMain()
-        for event, selector of activationEvents
-          rootView.off event, selector, activateHandler
-      for event, selector of activationEvents
-        rootView.command event, selector, activateHandler
+        rootView.off(event, selector, activateHandler) for event, selector of activationEvents
+      rootView.command(event, selector, activateHandler) for event, selector of activationEvents

   activatePackageMain: ->
     if packageMain = @getPackageMain()


Bind add selection commands to alt-shift-up/down

--- src/app/keymaps/editor.cson
+++ src/app/keymaps/editor.cson
@@ -9,8 +9,8 @@
   'ctrl-]': 'editor:unfold-current-row'
   'ctrl-{': 'editor:fold-all'
   'ctrl-}': 'editor:unfold-all'
-  'ctrl-shift-down': 'editor:add-selection-below'
-  'ctrl-shift-up': 'editor:add-selection-above'
+  'alt-shift-down': 'editor:add-selection-below'
+  'alt-shift-up': 'editor:add-selection-above'
   'alt-meta-ctrl-f': 'editor:fold-selection'
   'shift-tab': 'editor:outdent-selected-rows'
   'meta-[': 'editor:outdent-selected-rows'


more tests

more tests

--- test/phabs_test.coffee
+++ test/phabs_test.coffee
@@ -143,6 +143,17 @@
         expect(hubotResponse()).to.eql "Okay, I'll remember your email is momo@example.com"
         expect(room.robot.brain.userForId('momo').email_address).to.eql 'momo@example.com'

+  context 'user declares email for somebody else', ->
+    context 'phab toto = toto@example.com', ->
+      hubot 'phab toto = toto@example.com'
+      it 'complains if the user is unknown', ->
+        expect(hubotResponse()).to.eql "Sorry I have no idea who toto is. Did you mistype it?"
+    context 'phab user = user@example.com', ->
+      hubot 'phab user = user@example.com'
+      it 'sets the email for the user', ->
+        expect(hubotResponse()).to.eql "Okay, I'll remember user email as user@example.com"
+        expect(room.robot.brain.userForId('user').email_address).to.eql 'user@example.com'
+

   context 'user creates a new task', ->
     beforeEach ->


docpad.coffee: remove reference to non-existant categoryDirectory

--- docpad.coffee
+++ docpad.coffee
@@ -110,7 +110,7 @@
         write: true
         relativeOutDirPath: $endsWith: dir
         body: $ne: ""
-      sorting = [categoryDirectory:1, filename:1]
+      sorting = [filename:1]

       database.findAllLive(query, sorting).on 'add', (document) ->
         a = document.attributes


forcing build

forcing build

--- docpad.coffee
+++ docpad.coffee
@@ -117,6 +117,7 @@

 	environments:
 		development:
+
 			ignoreCustomPatterns: /2005|2006|2007|2009|2010|2011|2012/

 			collections:


Add additional entries to the Idris main menu

In particular: Apropos, Open REPL, Show Holes and Stop Compiler
All these are commands which are not specific to a particular location
in the idris file being edited.

--- menus/language-idris.cson
+++ menus/language-idris.cson
@@ -10,6 +10,22 @@
               {
                 'label': 'Typecheck'
                 'command': 'language-idris:typecheck'
+              }
+              {
+                'label': 'Open REPL'
+                'command': 'language-idris:open-repl'
+              }
+              {
+                'label': 'Apropos'
+                'command': 'language-idris:apropos'
+              }
+              {
+                'label': 'Holes'
+                'command': 'language-idris:holes'
+              }
+              {
+                'label': 'Stop Compiler'
+                'command': 'language-idris:stop-compiler'
               }
             ]
         ]


adjust palette block expansion test

adjust palette block expansion test

--- test/src/uitest.coffee
+++ test/src/uitest.coffee
@@ -117,7 +117,7 @@
     { location: '.droplet-main-scroller', dx: 40, dy: 50 })
   simulate('mouseup', '.droplet-drag-cover',
     { location: '.droplet-main-scroller', dx: 40, dy: 50 })
-  equal(editor.getValue().trim(), 'pen red\na1 = b')
+  equal(editor.getValue().trim(), 'pen red\na3 = b')
   simulate('mousedown', '[title=ftest]')
   simulate('mousemove', '.droplet-drag-cover',
     { location: '[title=ftest]', dx: 5 })
@@ -125,7 +125,7 @@
     { location: '.droplet-main-scroller', dx: 40, dy: 80 })
   simulate('mouseup', '.droplet-drag-cover',
     { location: '.droplet-main-scroller', dx: 40, dy: 80 })
-  equal(editor.getValue().trim(), 'pen red\na1 = b\na2 = b')
+  equal(editor.getValue().trim(), 'pen red\na3 = b\na6 = b')
   start()

 asyncTest 'Controller: reparse and undo reparse', ->


Layer

Layer

- remove duplicate cui class

--- src/base/Layer/Layer.coffee
+++ src/base/Layer/Layer.coffee
@@ -24,7 +24,7 @@
 		# not affect the layer's opacity as it would to if the layer would be
 		# a child of backdrop
 		@__layer_root = new Template
-			class: "cui-layer-root-"+("cui-"+toDash(@__cls)+" "+@_class).trim().split(/\s+/).join(" cui-layer-root-")
+			class: "cui-layer-root-"+(toDash(@__cls)+" "+@_class).trim().split(/\s+/).join(" cui-layer-root-")
 			name: "layer-root"

 		@__backdropClickDisabled = false


renamed pubdate test to more generic date test

--- testRequirement.coffee
+++ testRequirement.coffee
@@ -23,9 +23,9 @@
     # testValues is an object like {"empty": true}
     return matchesEmptyTest objectKey, testValues, object

-  else if isPubdateTest testValues
+  else if isDateTest testValues
     # testValues is an object like {"year": "now"}
-    return matchesPubdate objectKey, testValues, object
+    return dateTest objectKey, testValues, object

   else
     # testValues is a single value; a string, boolean, or number
@@ -110,14 +110,14 @@
 isEmpty = (objectValue) ->
   return (objectValue is null) or (objectValue is 0) or (objectValue.length is 0) or (Object.keys(objectValue).length is 0)

-# Check if the test is a pubdate test
-isPubdateTest = (testValues) ->
+# Check if the test is a date test
+isDateTest = (testValues) ->
   if (_.isPlainObject testValues) and 'year' of testValues then true else false

-# Extract specific time from the document pubdate field and compare it with the test
-matchesPubdate = (objectKey, pubdateTest, object) ->
+# Extract specific time from a date field and compare it with test
+dateTest = (objectKey, pubdateTest, object) ->
   objectValue = object[objectKey]
-  year = new Date(objectValue).getFullYear() # convert to milliseconds
+  year = new Date(objectValue).getFullYear()

   if 'year' of pubdateTest # compare years
     testValue = pubdateTest['year']


同じ文字を重ねるルールをコメントアウト

同じ文字を重ねるルールをコメントアウト

--- src/coffee/kotoawase.coffee
+++ src/coffee/kotoawase.coffee
@@ -31,11 +31,12 @@
 	ruleList = new KanaComparationRuleList()
 	ruleList.addCombineRule('に', 'じ', '虹')
 	ruleList.addCombineRule('し', 'ま', '島')
-	ruleList.addUnionRule('に')
-	ruleList.addUnionRule('じ')
-	ruleList.addUnionRule('か')
-	ruleList.addUnionRule('し')
-	ruleList.addUnionRule('ま')
+	# 同じ文字をひとつにするルール
+	#ruleList.addUnionRule('に')
+	#ruleList.addUnionRule('じ')
+	#ruleList.addUnionRule('か')
+	#ruleList.addUnionRule('し')
+	#ruleList.addUnionRule('ま')

 	content = new Vue
 		template: template


Changed my mind about 'force: true' in development

--- bin/www.coffee
+++ bin/www.coffee
@@ -43,6 +43,5 @@
 # Listen on provided port, on all network interfaces.

 # TODO: replace sync with migration files
-force = config.nodeEnv == 'development'
-models.sequelize.sync(force: force).then ->
+models.sequelize.sync().then ->
   server.listen config.port


Further simplify. Gotta find a better name for the wrapper.

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -92,10 +92,8 @@

 primitiveOrObservableParametersWrapper = (wrapped) ->
   return (f, args...) ->
-    for arg, i in args
-      args[i] = Bacon.constant(arg) unless arg instanceof Observable
-    stream = partiallyApplied(wrapped, [(observableValues, callback) ->
-      f(observableValues..., callback)])
+    stream = partiallyApplied(wrapped, [(values, callback) ->
+      f(values..., callback)])
     Bacon.combineAsArray(args).flatMap(stream)

 Bacon.fromCallback = primitiveOrObservableParametersWrapper(Bacon._fromCallback)


Place parentheses around splatted arguments for consistency.

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -1443,7 +1443,7 @@
     makeFunctionArgs args
 makeFunctionArgs = (args) ->
   args = Array.prototype.slice.call(args)
-  makeFunction_ args...
+  makeFunction_(args...)
 makeFunction_ = withMethodCallSupport (f, args...) ->
   if isFunction f
     if args.length then partiallyApplied(f, args) else f


simplify

simplify

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -951,10 +951,10 @@
     pats = []
     i = 0
     while (i < len)
-       patterns[i] = [patterns[i]] unless patterns[i] instanceof Array
+       patSources = _.toArray patterns[i]
        f = patterns[i+1]
        pat = {f: (if isFunction(f) then f else (-> f)), ixs: []}
-       for s in patterns[i]
+       for s in patSources
          assert (s instanceof Observable), usage
          index = sources.indexOf s
          if index < 0


Improve support for getting doc changes

Improve support for getting doc changes

- Refactored logic for getting doc changes into a recursive method accepting two objects.
- Added support for getting formatted values from AutoForm instead of parsing them ourselves if possible.
- Refactored logic for sanitizing compare values to use JSON instead of toString() to accept objects.

--- src/Forms.coffee
+++ src/Forms.coffee
@@ -560,14 +560,9 @@
       # Form doc is the full set of fields which the doc supports. We must avoid creating modifiers
       # containing any other fields, since this form does not affect them.
       keys = _.keys(formDoc)
-      changes = {}
-      _.each keys, (key) ->
-        formValue = Forms._sanitizeCompareValue(formDoc[key])
-        prevValue = Forms._sanitizeCompareValue(prevFormDoc[key])
-        if formValue != prevValue
-          # Ensure empty values are null.
-          changes[key] = formValue || null
-      changes
+      changes = Forms._getObjectChanges(prevFormDoc, formDoc)
+      # Ensure object values are flattened.
+      Objects.flattenProperties(changes)

     # Merges the latest document into the form, giving precedence to the changed values in the form.
     # @returns {Object} The flattened properties of the latest document which were merged into the
@@ -608,9 +603,11 @@
       $inputs.each ->
         $input = $(@)
         id = Forms.getInputId($input)
-        value = Forms.getInputValue($input)
+        value = Form.getInputValue($input)
         values[id] = value
       values
+
+    Form.getInputValue = ($input) -> Forms.getInputValue($input, Form.getSchema())

     Form.setSubmitButtonDisabled = (disabled, template) ->
       Form.getSubmitButton(template).toggleClass('disabled', !!disabled)
@@ -669,8 +666,18 @@
       $input.val(value)
     return true

-  getInputValue: ($input) ->
-    if @isSelectInput($input)
+  getInputValue: ($input, schema) ->
+    $input = $($input)
+    element = $input[0]
+    return unless element?
+
+    # Attempt to obtain the value from AutoForm which will also parse it based on the schema and
+    # support custom AutoForm fields.
+    templateName = AutoForm.getInputTypeTemplateNameForElement(element)
+    if templateName
+      AutoForm.getInputValue(element, schema)
+    # Otherwise try to parse it ourselves.
+    else if @isSelectInput($input)
       @getSelectValue($input)
     else if @isDropdown($input)
       Template.dropdown.getValue($input)
@@ -687,7 +694,29 @@
     else
       $input.prop('readonly', disabled)

-  _sanitizeCompareValue: (value) -> value?.toString().trim() ? ''
+  _sanitizeCompareValue: (value) ->
+    # Ensure whitespace is ignored.
+    if Types.isString(value) then value = value.trim()
+    # Ensure empty values are null.
+    if value? then JSON.stringify(value) else null
+
+  _getObjectChanges: (oldObj, newObj) ->
+    changes = {}
+    # Assumes that the set of keys in newObj is a superset of those in oldObj.
+    _.each newObj, (newValue, key) =>
+      oldValue = oldObj[key]
+      # Recursively find object diffs.
+      if Types.isObject(newValue) and Types.isObject(oldValue)
+        valueChanges = @_getObjectChanges(oldValue, newValue)
+        unless _.isEmpty(valueChanges) then changes[key] = valueChanges
+      else
+        sanitizedNewValue = @_sanitizeCompareValue(newValue)
+        santizedOldValue = @_sanitizeCompareValue(oldValue)
+        # NOTE: Not using _.isEqual() to allow trimming strings and other logic.
+        if sanitizedNewValue != santizedOldValue
+          # Ensure empty values are null. Return the unsanitized value from getInputValue().
+          changes[key] = newValue ? null
+    changes

   getInputId: ($input) -> $input.attr('data-schema-key')


fix move event not trigger (to much clean on previous commit)

--- lib/BlockView.coffee
+++ lib/BlockView.coffee
@@ -178,6 +178,7 @@
       top: vector.y + @_state.elPosition.top + "px"

     @move(pos)
+    @trigger 'change:move', pos
     @

   # @todo events are on document


added no color menu option

added no color menu option

Former-commit-id: 64bdc70bbdb7ec9217dabf3358cefba536e4c112 [formerly 6b5c91b851ea693ffc48a3913473edb44391f14f]
Former-commit-id: 7c9f96a05aff55b264223b709c6f5e679ad84faf

--- src/menu/defaultmenu.coffee
+++ src/menu/defaultmenu.coffee
@@ -169,6 +169,9 @@
       menuColor.addNode "Hydrophobicity", =>
         @msa.g.colorscheme.set "scheme","hydrophobicity"

+      menuColor.addNode "No color", =>
+        @msa.g.colorscheme.set "scheme","foo"
+
       menuColor.addNode "Toggle background", =>
         @msa.g.colorscheme.set "colorBackground", !@msa.g.colorscheme.get("colorBackground")


Update IrcClient.coffee to throw Error objects instead of strings

--- lib/IrcClient.coffee
+++ lib/IrcClient.coffee
@@ -6,9 +6,9 @@
 class IrcClient extends events.EventEmitter
   constructor: (@options={}) ->
     if !@options.server
-      throw "Server not specified"
+      throw new Error "Server not specified"
     if !@options.nick
-      throw "Nick not specified"
+      throw new Error "Nick not specified"
     @options.port = @options.port | 6667
     @options.username = @options.username | "UberIRC"
     @options.realname = @options.realname | "A UberIRC Bot"


fix for undefined progress on concept coach dashboard

--- src/components/cc-dashboard/index.cjsx
+++ src/components/cc-dashboard/index.cjsx
@@ -50,7 +50,7 @@
     percents.incorrect = 100 - percents.correct

     if percents.correct
-      correctLabel = if percents.incorrect > percents.correct then "#{percents.correct}%"
+      correctLabel = "#{percents.correct}%"
       correctLabel = if percents.correct is 100 then "#{correctLabel} correct" else correctLabel
       correctBar = <BS.ProgressBar
         className="reading-progress-bar progress-bar-correct"
@@ -60,7 +60,7 @@
         key={1} />

     if percents.incorrect
-      incorrectLabel = if percents.incorrect > percents.correct then "#{percents.incorrect}%"
+      incorrectLabel = "#{percents.incorrect}%"
       incorrectLabel = if percents.incorrect is 100 then "#{incorrectLabel} incorrect" else incorrectLabel
       incorrectBar = <BS.ProgressBar
         className="reading-progress-bar progress-bar-incorrect"


disable breadcumb initialization

disable breadcumb initialization

--- vendor/assets/javascripts/lib/melbourne/lp_melbourne_base.coffee
+++ vendor/assets/javascripts/lib/melbourne/lp_melbourne_base.coffee
@@ -29,7 +29,7 @@

     header: ->
       AdManager.init(@adConf,'ad_masthead')
-      Breadcrumbs.init("/assets/breadcrumbs.html?destId=357884")
+      # Breadcrumbs.init("/assets/breadcrumbs.html?destId=357884")
       shopCart = new ShoppingCart()
       AssetFetch.get "http://www.lonelyplanet.com/global-navigation", () ->
         dest = new DestinationNav(jsonNavItems.nav, "nav.primary ul li.destinations")


Problems with using event constructors

Problems with using event constructors

--- vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
+++ vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
@@ -77,8 +77,8 @@
         if element.get(0).click
           element.get(0).click()

-        else if MouseEvent?
-          evt = new MouseEvent("click", {
+        else if Event?
+          evt = new Event("click", {
             bubbles: true,
             cancelable: true,
             view: window,


tweak randomization assigner for weird cases

--- lib/assigners.coffee
+++ lib/assigners.coffee
@@ -175,14 +175,24 @@
       ]
     }).fetch()

-    if existing.length > 0
-      console.log "Not creating new instances as recent ones already exist"
+    # Reuse buffer instance if it already exists
+    if existing.length > 0 and _.any(existing, (exp) -> exp.startTime? )
+      console.log "Not creating new instances as recently started ones already exist"
       return

     @groupConfig = TutorialRandomizedGroupAssigner.generateConfig(@groupArray, @groupTreatments)
+
+    if existing.length is @groupConfig.length
+      console.log "Not creating new instances as we already have the expected number"
+      return

     # create and setup instances
     for config in @groupConfig
+      # TODO hack if the buffer instance already exists, don't create it
+      if _.isEqual(config.treatments, @groupTreatments) and _.any(existing, (exp) => _.isEqual(exp.treatments, @groupTreatments))
+        console.log "Skipping creating buffer as it already exists"
+        continue
+
       instance = @batch.createInstance(config.treatments)
       instance.setup()

@@ -300,8 +310,13 @@
     nextInstId = @instanceSlots[@instanceSlotIndex]
     @instanceSlotIndex++

-    # TODO check for ended-ness here.
     instance = TurkServer.Instance.getInstance(nextInstId)
+
+    if instance.isEnded()
+      console.log "Skipping assignment to slot for ended instance #{instance.groupId}"
+      # Recursively try to assign to the next slot
+      @assignNext(asst)
+      return

     @lobby.pluckUsers( [asst.userId] )
     instance.addAssignment(asst)


An active editor is ensured, even if on the settings page - response to #34

--- lib/atom_pair.coffee
+++ lib/atom_pair.coffee
@@ -106,15 +106,20 @@
       atom.workspace.open().then => @pairingSetup() #starts a new tab to join pairing session

   startSession: ->
-    @getKeysFromConfig()
-
-    if @missingPusherKeys()
-      new AlertView "Please set your Pusher keys."
+
+    @editor = atom.workspace.getActiveTextEditor()
+    if !@editor
+      atom.workspace.open().then => @startSession()
     else
-      @generateSessionId()
-      new StartView(@sessionId)
-      @markerColour = @colours[0]
-      @pairingSetup()
+      @getKeysFromConfig()
+
+      if @missingPusherKeys()
+        new AlertView "Please set your Pusher keys."
+      else
+        @generateSessionId()
+        new StartView(@sessionId)
+        @markerColour = @colours[0]
+        @pairingSetup()

   generateSessionId: ->
     @sessionId = "#{@app_key}-#{@app_secret}-#{randomstring.generate(11)}"


Ensure there are no references to a destroyed editor. Resolves #1

--- lib/atom_pair.coffee
+++ lib/atom_pair.coffee
@@ -84,6 +84,7 @@
     @editorListeners.dispose()
     _.each @friendColours, (colour) => @clearMarkers(colour)
     atom.views.getView(@editor).removeAttribute('id')
+    @editor = @buffer = null
     @markerColour = null

   copyId: -> atom.clipboard.write(@sessionId)


BUGFIX: Added Mac OS X Dashboard web clip support to the browser-check HTTP middleware.

--- lib/http_middleware/browser_check.coffee
+++ lib/http_middleware/browser_check.coffee
@@ -37,7 +37,7 @@

     # Capture headless browsers (e.g. spiders)
     return true unless ua?
-
+
     # Allow Chrome version 4 and above
     if ua.match(/Chrom(e|ium)/)
       re = new RegExp("Chrom(e|ium)/([0-9]{1,})")
@@ -52,6 +52,11 @@
         version = parseFloat( RegExp.$1 )
         return false if version >= 5

+    # Mac OS X Dashboard Web Clip
+    return false if ua.match(/WebClip/)
+    # TODO - find out what version of AppleWebKit (~ 533) implemented websockets,
+    # will allow for greater refactoring of code above.
+
     # Else show the Browser Incompatible page
     true


logoupload ; group image file name changed.

--- client/Main/CommonViews/uploadimagemodalview.coffee
+++ client/Main/CommonViews/uploadimagemodalview.coffee
@@ -32,7 +32,6 @@
         @updateLogoImage()

     @addSubView @uploaderView
-    # @addSubView uploadButton
     @addSubView @loaderView

   updateLogoImage : =>
@@ -47,24 +46,31 @@

   uploadToS3: (avatarData, callback)->
     #TODO : change the address and name of the logo
-    FSHelper.s3.upload "grouplogo-test1.png", avatarData, (err, url)=>
-      if err
-        new KDNotificationView title : "Error while uploading photo."
-        @loaderView.hide()
-        return
+    {groupsController} = KD.singletons
+    groupsController.ready =>

-      proxifyOptions =
-        crop         : yes
-        width        : @getOptions().image.size.width
-        height       : @getOptions().image.size.height
+      group     = groupsController.getCurrentGroup()
+      imageType = @getOptions().image.type
+      imageName = "#{group.slug}-#{imageType}"

-      resized = KD.utils.proxifyUrl url, proxifyOptions
-      group   = KD.singletons.groupsController.getCurrentGroup()
+      FSHelper.s3.upload imageName, avatarData, (err, url)=>
+        if err
+          new KDNotificationView title : "Error while uploading photo."
+          @loaderView.hide()
+          return

-      if @getOptions().image.type is "coverPhoto"
-        group.modify "customize.coverPhoto" : "#{url}?#{Date.now()}", callback
-      else
-        group.modify "customize.logo" : "#{url}?#{Date.now()}", callback
+        proxifyOptions =
+          crop         : yes
+          width        : @getOptions().image.size.width
+          height       : @getOptions().image.size.height
+
+        resized = KD.utils.proxifyUrl url, proxifyOptions
+        group   = KD.singletons.groupsController.getCurrentGroup()
+
+        if @getOptions().image.type is "coverPhoto"
+          group.modify "customize.coverPhoto" : "#{url}?#{Date.now()}", callback
+        else
+          group.modify "customize.logo" : "#{url}?#{Date.now()}", callback

   upload:(callback)->


remve sendEvent to Analytics

remve sendEvent to Analytics

--- coffee/services/redmine.coffee
+++ coffee/services/redmine.coffee
@@ -81,13 +81,11 @@
   _bindDefer: (success, error, mothodName) ->
     deferred = @$q.defer()
     onSuccess = (args...) =>
-      @Analytics.sendEvent 'internal', mothodName, 'total_count', args[0].total_count
       args[0].account = @auth
       deferred.resolve(args...)
       success?(args...)

     onError = (args...) =>
-      @Analytics.sendException("Error: " + mothodName + "\tstatus: " + args[1])
       @Log.warn(mothodName + " failed:\taccount: " + @auth.name + "\tstatus: " + args[1])
       if not args[0] or args[0].isBlank?()
         args[0] = { error: true }


fixed remove of option if not custom option closes #56

--- _src/js/views/facets/subarray.coffee
+++ _src/js/views/facets/subarray.coffee
@@ -52,8 +52,10 @@

 	rmRes: ( evnt )=>
 		_id = $( evnt.target )?.data( "id" )
+		_mdl = @result.get( _id )
 		@result.remove( _id )
-		@searchcoll.remove( _id )
+		if _mdl?.get( "custom" )
+			@searchcoll.remove( _id )
 		return

 	editRes: ( evnt )=>
@@ -62,7 +64,7 @@
 		@result.remove( _id )
 		@searchcoll.remove( _id )
 		@sub.reopen()
-		console.log @searchcoll
+
 		@search(_v)
 		return


Bump semaphore

Bump semaphore

--- components/article/view.coffee
+++ components/article/view.coffee
@@ -103,8 +103,8 @@

   renderEmbedSections: =>
     sections = []
-    Q.all( for section in @article.get('sections') when section.type is 'embed'
-      $.get oembed(section.url, { maxwidth: @getWidth(section) })
+    Q.all( for section in @article.get 'sections' when section.type is 'embed'
+      $.get oembed section.url, { maxwidth: @getWidth(section) }
     ).done (responses) =>
       if responses.length
         for section in @article.get('sections') when section.type is 'embed'


Machine: Send notification when user denied the machine.

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -771,8 +771,13 @@
       permanent : yes

     @shareWith options, (err) =>
-      options = { action: 'deny', @uid }
+      options               = { action: 'deny', @uid }
+      [ owner ]             = @users.filter (user) -> return user.owner
+      { notifyByUsernames } = require '../notify'
+
       client.connection.delegate.sendNotification 'MachineShareActionTaken', options
+      notifyByUsernames [ owner.username ], 'MachineShareListUpdated', options
+
       callback err


Fix edge swipes

Fix edge swipes

--- framer/GestureInputRecognizer.coffee
+++ framer/GestureInputRecognizer.coffee
@@ -164,10 +164,10 @@
 	# Direction swipe

 	swipedirectionstart: (event) =>
-		return unless event.direction
+		return unless event.offsetDirection
 		return if @session.started.swipedirection
 		@session.started.swipedirection = event
-		direction = @session.started.swipedirection.direction
+		direction = @session.started.swipedirection.offsetDirection
 		@_dispatchEvent("swipe#{direction}start", event)

 		swipeEdge = @_edgeForSwipeDirection(direction)
@@ -185,31 +185,31 @@

 	swipedirection: (event) =>
 		return unless @session.started.swipedirection
-		direction = @session.started.swipedirection.direction
+		direction = @session.started.swipedirection.offsetDirection
 		@_dispatchEvent("swipe#{direction}", event)
 		@edgeswipedirection(event) if @session.started.edgeswipedirection

 	swipedirectionend: (event) =>
 		return unless @session.started.swipedirection
-		direction = @session.started.swipedirection.direction
+		direction = @session.started.swipedirection.offsetDirection
 		@_dispatchEvent("swipe#{direction}end", event)

 	# Edge swipe

 	edgeswipedirection: (event) =>
-		swipeEdge = @_edgeForSwipeDirection(@session.started.edgeswipedirection.direction)
+		swipeEdge = @_edgeForSwipeDirection(@session.started.edgeswipedirection.offsetDirection)
 		Screen.emit("edgeswipe", @_createEvent("edgeswipe", event))
 		Screen.emit("edgeswipe#{swipeEdge}", @_createEvent("edgeswipe#{swipeEdge}", event))

 	edgeswipedirectionstart: (event) =>
 		return if @session.started.edgeswipedirection
 		@session.started.edgeswipedirection = event
-		swipeEdge = @_edgeForSwipeDirection(@session.started.edgeswipedirection.direction)
+		swipeEdge = @_edgeForSwipeDirection(@session.started.edgeswipedirection.offsetDirection)
 		Screen.emit("edgeswipestart", @_createEvent("edgeswipestart", event))
 		Screen.emit("edgeswipe#{swipeEdge}start", @_createEvent("edgeswipe#{swipeEdge}start", event))

 	edgeswipedirectionend: (event) =>
-		swipeEdge = @_edgeForSwipeDirection(@session.started.edgeswipedirection.direction)
+		swipeEdge = @_edgeForSwipeDirection(@session.started.edgeswipedirection.offsetDirection)
 		Screen.emit("edgeswipeend", @_createEvent("edgeswipeend", event))
 		Screen.emit("edgeswipe#{swipeEdge}end", @_createEvent("edgeswipe#{swipeEdge}end", event))


alteration to collection each to make sure it does not loop too far

--- server/api/lib/collection.coffee
+++ server/api/lib/collection.coffee
@@ -146,26 +146,18 @@
     opts = undefined
   # TODO could use es.scroll here...
   opts ?= {}
-  opts = {size:opts} if typeof opts is 'number'
-  opts = {newest: true} if opts is true
-  if opts.newest is true
-    delete opts.newest
-    opts = {sort: {createdAt:{order:'desc'}}}
-  opts = {random:true} if opts is 'random'
-  opts.from ?= 0
-  res = this.search q, opts
+  qy = API.collection._translate q, opts
+  qy.from ?= 0
+  res = this.search qy
   return 0 if res is undefined
-  count = res.hits.total
-  counter = 0
-  while (counter < count)
+  while (qy.from < res.hits.total)
     for h in res.hits.hits
       fn = fn.bind this
       fn h._source ? h.fields
-    counter += res.hits.hits.length
-    if counter < count
-      opts.from += res.hits.hits.length
-      res = this.search(q, opts)
-  return counter
+    if qy.from < res.hits.total
+      qy.from += res.hits.hits.length
+      res = this.search qy
+  return res.hits.total

 API.collection.prototype.count = (q) ->
   return this.search(q).hits.total


Kill the server after the deploy script finishes

--- server/controllers/deploy.coffee
+++ server/controllers/deploy.coffee
@@ -1,5 +1,4 @@
-CommandRunner = require('./command-runner')
-cp = require('child_process')
+CommandRunner = require('../bin/command-runner')

 getBranchFromRef = (ref) ->
   refParts = ref.split("/")
@@ -15,8 +14,13 @@

   console.log "Forking #{process.cwd()}/bin/deploy.coffee"

-  deployProcess = CommandRunner "coffee #{process.cwd()}/bin/deploy.coffee"
+  deployProcess = CommandRunner.spawn "coffee #{process.cwd()}/bin/deploy.coffee"

   console.log "Forked!"

+  deployProcess.on('close', ->
+    console.log "deploy finished, restarting server"
+    process.exit()
+  )
+
   res.send 200


Remove users from admin pub

Remove users from admin pub

--- server/publications/admin.coffee
+++ server/publications/admin.coffee
@@ -7,5 +7,4 @@
   res.push servers.findFaster()
   res.push lobbies.findFaster()
   res.push ServerAddons.findFaster()
-  res.push users.findFaster({}, {fields: {authItems: 1, profile: 1, services: 1}})
   res


Namespace handler class

Namespace handler class

--- components/notice/index.coffee
+++ components/notice/index.coffee
@@ -4,11 +4,11 @@
   className: 'notice-message'

   events:
-    'click .notice-message-close': 'close'
+    'click .js-notice-message-close': 'close'

   template: -> "
     #{@message}
-
+
   "

   initialize: ({ @message }) -> #


Removed extra newline

Removed extra newline

--- server/ssr/renderOnServer.coffee
+++ server/ssr/renderOnServer.coffee
@@ -85,7 +85,6 @@
     store = createStore()

     try
-
         Routes.some((route) ->
             match = matchPath(req.url, route)
             if (match)


Show default first pane when "Save & Test" button is clicked.

--- client/admin/lib/views/stacks/definestackview.coffee
+++ client/admin/lib/views/stacks/definestackview.coffee
@@ -124,6 +124,8 @@

   handleSave: ->

+    # Show default first pane.
+    @tabView.showPaneByIndex 0
     @stackTemplateView.outputView.clear().raise()

     @cancelButton.setTitle 'Cancel'


groups: fix for menu parsing an empty read me, button rerendering

--- client/app/MainApp/lazy/staticgroupcontroller.coffee
+++ client/app/MainApp/lazy/staticgroupcontroller.coffee
@@ -44,8 +44,9 @@
   parseMenuItems :(callback)->
     KD.remote.cacheable @groupEntryPoint, (err, groups, name)=>
       if groups.first
-        groups.first.fetchReadme (err,{content})=>
-          unless err
+        groups.first.fetchReadme (err,readme={})=>
+          {content} = readme
+          unless err or not content
             menuItems = []
             lines = content.split("\n");
             for line,index in lines
@@ -193,6 +194,7 @@
         event.preventDefault()
         @lazyDomController.openPath "/#{@groupEntryPoint}/Activity"

+    @requestButton?.hide()
     @buttonWrapper.addSubView open

     if isAdmin
@@ -240,6 +242,8 @@

   decorateGuestStatus:->

+    @requestButton?.hide()
+
     @requestButton = new CustomLinkView
       title    : "Request Access"
       cssClass : "request"


terminal: a case when user saves not changed terminal name

--- client/ide/lib/views/tabview/idetabhandleview.coffee
+++ client/ide/lib/views/tabview/idetabhandleview.coffee
@@ -40,6 +40,7 @@
             newTitle  = @titleInput.getValue()

             return  unless newTitle.length
+            return @setTitleEditMode no  if newTitle is title

             @emit 'RenamingRequested', newTitle, title

@@ -65,7 +66,7 @@

   dblClick: ->

-    return  unless @isEditable and @getWidth() > MIN_EDIT_WIDTH
+    return  unless @isEditable and @getWidth() >= MIN_EDIT_WIDTH
     return  unless @titleInput.hasClass 'hidden'

     @setTitleEditMode yes


fix bug on user form

fix bug on user form

--- BackofficeBundle/Resources/public/coffee/FullPagePanelView.coffee
+++ BackofficeBundle/Resources/public/coffee/FullPagePanelView.coffee
@@ -17,65 +17,42 @@
     links = @options.element.get('links')
     panels = @getPanels links
     for panel in panels
-      $("#superboxTab").append('

')
-      @callPanel panel["link"], panel["isActive"], panel["id"], panel["title"]
-
+      $("#superboxTab").append('

')
+      @callPanel panel

   getPanels: (links) ->
     panels = []
+    if links._self_form
+      panels[0] = {link:links._self_form, isActive:true, id:'form', title:'form'}
     for key in Object.keys(links)
-      if /^_self_form$/.test(key) or /^_self_panel_/.test(key)
-        if /^_self_form$/.test(key)
-          id = "form"
-          active = true
-          position = 0
-        else
-          id = key.replace(/^_self_panel_[0-9]+_/,"")
-          active = false
-          position = key.replace(/^_self_panel_([0-9]+)_.+$/,'$1')
-        panels[position] = []
-        panels[position]["link"] = links[key]
-        panels[position]["isActive"] = active
-        panels[position]["id"] = id
-        panels[position]["title"] = id
+      if infos = key.match(/^_self_panel_([0-9]+)_(.*)/)
+        panels[infos[1]] = {link:links[key], isActive:false, id:infos[2], title:infos[2]}
     return panels

-  addEventOnForm: (id) ->
-    options = @options
-    viewContext = @
-    $("form", @$("#tab-" + id)).on "submit", (e) ->
-      e.preventDefault()
-      $(this).ajaxSubmit
-        context:
-          button: $(".submit_form",e.target).parent()
-        success: (response) ->
-          container = $(".tab-pane.active")
-          container.html(response)
-          container.attr("id").replace("tab-","")
-          viewContext.addEventOnForm  container.attr("id").replace("tab-","")
-        error: (response) ->
-          container = $(".tab-pane.active")
-          container.html(response.responseText)
-          viewContext.addEventOnForm  container.attr("id").replace("tab-","")
-      return
+  addEventOnForm: (event)->
+    event.preventDefault()
+    target = $(event.target)
+    target.ajaxSubmit
+      context:
+        button: $(".submit_form", target).parent()
+      success: (response) ->
+        target.parent().html response
+      error: (response) ->
+        target.parent().html response.responseText
+    return

-  callPanel: (url, isActive, id, title) ->
+  callPanel: (panel) ->
     viewContext = @
     $.ajax
-      url: url
+      url: panel["link"]
       method: "GET"
       success: (response) ->
-        element = []
-        element["isActive"] = isActive
-        element["id"] = id
-        element["title"] = title
-        viewContext.addPanelTitle(element);
-        element["response"] = response
-        viewContext.addResponseInTab(element)
-        viewContext.addEventOnForm id
+        viewContext.addPanelTitle panel
+        panel.response = response
+        viewContext.addResponseInTab panel

   addResponseInTab: (element) ->
-    $(".tab-content").prepend(
+    $(".tab-content", @$el).prepend(
       @renderTemplate('OpenOrchestraBackofficeBundle:BackOffice:Underscore/elementPanelTab', element))

   addPanelTitle: (element) ->


Enable CMs for multinomial GLM models.

Enable CMs for multinomial GLM models.

--- src/ext/components/model-output.coffee
+++ src/ext/components/model-output.coffee
@@ -286,6 +286,15 @@
               g.from table
             )

+      if output = _model.output
+        if output.model_category is 'Multinomial'
+          if confusionMatrix = output.training_metrics?.cm?.table
+            renderMultinomialConfusionMatrix 'Training Metrics - Confusion Matrix', confusionMatrix
+          if confusionMatrix = output.validation_metrics?.cm?.table
+            renderMultinomialConfusionMatrix 'Validation Metrics - Confusion Matrix', confusionMatrix
+          if confusionMatrix = output.cross_validation_metrics?.cm?.table
+            renderMultinomialConfusionMatrix 'Cross Validation Metrics - Confusion Matrix', confusionMatrix
+
     when 'deeplearning'
       if table = _.inspect 'output - training_metrics - Metrics for Thresholds', _model
         plotter = _.plot (g) ->


Social: fix code style

Social: fix code style

--- servers/lib/server/client.coffee
+++ servers/lib/server/client.coffee
@@ -82,9 +82,7 @@
     account.profile = nickname: username
     account.type    = 'unregistered'

-  fakeClient.sessionToken = session.clientId
-
-  fakeClient.sessionToken = sessionToken if sessionToken
+  fakeClient.sessionToken = sessionToken ? session.clientId

   # set username into context
   fakeClient.context or= {}


dont need that file

dont need that file

--- tst.coffee
+++ tst.coffee
@@ -1,14 +0,0 @@
-
-
-
-class tst
-  fn: (val) ->
-    max = 16
-    newVal = val % max + 1
-
-    return newVal
-
-  f2: (arr) ->
-    arr.reduce (p, n) =>
-      return @fn p + n
-module.exports = new tst()


Additional randsisms

Additional randsisms

--- src/scripts/rands-when-you-say.coffee
+++ src/scripts/rands-when-you-say.coffee
@@ -91,7 +91,16 @@
     "When you say “Well, you‘re lucky that..”, I hear “Well, I have absolutely no control or influence over this situation”. http://twitter.com/rands/statuses/52036340375764992",
     "When you say “No Meeting Wednesday,” I hear, “We’ve let meetings take over.” https://twitter.com/rands/status/294480995644743680",
     "When you say “Sinus infection,” I hear, “I’m fully in denial that I am sick.” https://twitter.com/rands/status/306933143703781377",
-    "@IAmRoot When you say “management consultants,” I die a little inside. https://twitter.com/rands/status/300290819913555972"
+    "@IAmRoot When you say “management consultants,” I die a little inside. https://twitter.com/rands/status/300290819913555972",
+    "When you say “We’re firefighting,” I hear “We lack a credible plan.” https://twitter.com/rands/status/317647183547621376",
+    "When you say “It’s was an iterative process,” I hear “It blows.” https://twitter.com/rands/status/326919083364847616",
+    "When you say “It is what what it is,” I hear “It is not what I want.” https://twitter.com/rands/status/330570233339334656",
+    "When you say “Can you remind me?” I hear “I’ve already forgotten this thing I don’t actually care about.” https://twitter.com/rands/status/358258824701947904",
+    "When you say, “It’s not a big deal,” I hear, “I’d prefer if you thought this wasn’t a big deal.” https://twitter.com/rands/status/364934241697542144",
+    "When you say “You get the idea,” I hear “Because I’m bored talking about it.” https://twitter.com/rands/status/400741704442990592",
+    "When you say, “Everything happens for a reason,” I hear “I wish I knew the reason.” https://twitter.com/rands/status/422980222099529728",
+    "When you say “Let’s keep an open mind,” I hear “Because you’re really not going to like what is about to occur.” https://twitter.com/rands/status/434793516573532160",
+    "When you say, “I’ll know it when I see it,” I hear “Your time is less important in figuring out this thing I care more about than you.” https://twitter.com/rands/status/437682992018776064"
   ]
   robot.respond /rands$/i, (msg) ->
     msg.send msg.random wisdom


Basic URL expansion works

Basic URL expansion works

--- src/scripts/short-url-expander.coffee
+++ src/scripts/short-url-expander.coffee
@@ -1,5 +1,5 @@
 # Description:
-#   Expands short URLs
+#   Expands short URLs based on http://longurl.org/api
 #
 # Dependencies:
 #   None
@@ -13,6 +13,26 @@
 # Author:
 #   Scott J Roberts - @sroberts

+long_url = "http://api.longurl.org/v2/expand?format=json&all-redirects=1&title=1&url="
+
 module.exports = (robot) ->
   robot.respond /expand url (.*)/i, (msg) ->
-    short_url = msg.match[1].toLowerCase()
+    short_url = msg.match[1]
+    short_url_encoded = "#{encodeURIComponent short_url}"
+
+    request_url = long_url + short_url_encoded
+    msg.send request_url
+
+    robot.http(request_url)
+      .get() (err, res, body) ->
+        longurl_json = JSON.parse body
+        msg.send body
+
+        long_url = longurl_json.longurl
+
+        longurl_profile = """
+        Title: #{longurl_json.title}
+        URL: #{longurl_json["long-url"]}
+        """
+
+        msg.send longurl_profile


Clean up formatting, consistent parentheses

--- src/allen.coffee
+++ src/allen.coffee
@@ -10,10 +10,10 @@
       null

   setAudioContext: ( context ) ->
-    if @isAudioContext( context )
+    if @isAudioContext context
       @context = context
     else
-      throw new Error('setAudioContext only accepts an AudioContext object')
+      throw new Error 'setAudioContext only accepts an AudioContext object'

   isAudioContext: ( node ) ->
     checkCurrentType node, 'AudioContext'
@@ -45,16 +45,16 @@

 checkCurrentType = ( node, goalName ) ->
   return false if typeof node isnt 'object' or not node
-  node?.constructor?.name is goalName or toStringMatch( node, goalName )
+  node?.constructor?.name is goalName or toStringMatch node, goalName

 checkProtoChainFor = ( node, goalName ) ->
   return false if typeof node isnt 'object' or not node
-  pType = Object.getPrototypeOf( node )
+  pType = Object.getPrototypeOf node

   until pType is Object.getPrototypeOf {}
-    if pType?.constructor?.name is goalName or toStringMatch( pType, "#{goalName}Prototype" )
+    if pType?.constructor?.name is goalName or toStringMatch pType, "#{goalName}Prototype"
       return true
-    pType = Object.getPrototypeOf( pType )
+    pType = Object.getPrototypeOf pType
   return false

 toStringMatch = ( object, name ) ->
@@ -64,6 +64,6 @@
 if typeof exports is 'object'
   module.exports = allen
 else if typeof define is 'function' and define.amd
-  define( 'allen', () -> allen )
+  define 'allen', () -> allen
 else
   root.allen = allen


added little explanation about missing unbinding

--- core/app/backbone/views/feed_activities_view.coffee
+++ core/app/backbone/views/feed_activities_view.coffee
@@ -13,6 +13,7 @@
     $(window).on "scroll", @checkScrolledPosition

   componentWillUnmount: ->
+    # createBackboneClass unbinds model
     $(window).off "scroll", @checkScrolledPosition

   render: ->


fix exception on empty config

fix exception on empty config

--- src/public/operation.coffee
+++ src/public/operation.coffee
@@ -171,7 +171,7 @@
           "js" : "js_eval"
           "separator" : "split"

-        if config.type != undefined
+        if config && config.type != undefined
         then type = config.type
         else for own attr, typeName of type_mapping
           if typeof config[attr] != "undefined"


Fix editing a filled-in CVC when text is selected

--- src/jquery.payment.coffee
+++ src/jquery.payment.coffee
@@ -293,6 +293,8 @@
   $target = $(e.currentTarget)
   digit   = String.fromCharCode(e.which)
   return unless /^\d+$/.test(digit)
+
+  return if hasTextSelected($target)

   val     = $target.val() + digit
   val.length <= 4


added some and corrected join to binary fn

--- src/array.coffee
+++ src/array.coffee
@@ -29,6 +29,9 @@
 findIndexOf = curry (a, ax) -> if (i = ax.indexOf a) != -1 then i
 findLastIndexOf = curry (a, ax) -> if (i = ax.lastIndexOf a) != -1 then i

+# reactive `any` is preferred but `some` is faster
+some = curry binary detach Array::some
+
 # Array mutators
 push = curry (ax, a...) ->
   ax.push a...
@@ -57,7 +60,7 @@
 slice = curry (i, j, ax) -> ax[i...j]

 sort = curry binary detach Array::sort
-join = curry ternary detach Array::join
+join = curry binary detach Array::join
 fill = curry (ax, a) -> ax.fill a

 # Set operations...


Emit events when directories are toggled

--- lib/directory.coffee
+++ lib/directory.coffee
@@ -53,6 +53,12 @@

   onDidRemoveEntries: (callback) ->
     @emitter.on('did-remove-entries', callback)
+
+  onDidCollapse: (callback) ->
+    @emitter.on('did-collapse', callback)
+
+  onDidExpand: (callback) ->
+    @emitter.on('did-expand', callback)

   loadRealPath: ->
     if @useSyncFS
@@ -251,6 +257,7 @@
     @expansionState.isExpanded = false
     @expansionState = @serializeExpansionState()
     @unwatch()
+    @emitter.emit('did-collapse')

   # Public: Expand this directory, load its children, and start watching it for
   # changes.
@@ -258,6 +265,7 @@
     @expansionState.isExpanded = true
     @reload()
     @watch()
+    @emitter.emit('did-expand')

   serializeExpansionState: ->
     expansionState = {}


fix module_names

fix module_names

--- app/app.coffee
+++ app/app.coffee
@@ -21,8 +21,9 @@
   'compat'
 ]

-imports.push.apply imports, settings.get('app', 'imports') or []
-module_names.push.apply imports, settings.get('app', 'module_names') or []
+imports.push (settings.get('app', 'imports') or [])...
+module_names.push imports...
+module_names.push (settings.get('app', 'module_names') or [])...

 settings.default 'app', 'intro_command', "help 'introduction'"


fix: minor fixes

fix: minor fixes

--- app/app.coffee
+++ app/app.coffee
@@ -7,11 +7,11 @@
 config.isDevknox = 'secure.devknox.io' is location.hostname
 config.isAppknox = !config.isDevknox

-config.Platform =
+config.platform =
   if config.isAppknox
     " | Appknox"
   else if config.isDevknox
-    "| Devknox"
+    " | Devknox"

 installIntercom()


Remove outdated blacklist in DomUtils.isEditable, use .isSelectable

This fixes #1964.

--- lib/dom_utils.coffee
+++ lib/dom_utils.coffee
@@ -179,12 +179,7 @@
   # enter insert mode if focused. Also note that the "contentEditable" attribute can be set on any element
   # which makes it a rich text editor, like the notes on jjot.com.
   isEditable: (element) ->
-    return true if element.isContentEditable
-    nodeName = element.nodeName?.toLowerCase()
-    # Use a blacklist instead of a whitelist because new form controls are still being implemented for html5.
-    if nodeName == "input" and element.type not in ["radio", "checkbox"]
-      return true
-    nodeName in ["textarea", "select"]
+    (@isSelectable element) or element.nodeName?.toLowerCase() == "select"

   # Embedded elements like Flash and quicktime players can obtain focus.
   isEmbed: (element) ->


Remove unused function

Remove unused function

--- src/grammar-registry.coffee
+++ src/grammar-registry.coffee
@@ -202,11 +202,6 @@
   selectGrammar: (filePath, fileContents) ->
     _.max @grammars, (grammar) -> grammar.getScore(filePath, fileContents)

-  # Test-Only: Clear all observers registered with ::on* methods.
-  clearObservers: ->
-    @off()
-    @emitter = new Emitter
-
   createToken: (value, scopes) -> {value, scopes}

   grammarUpdated: (scopeName) ->


Speed up text-decoration updater

Speed up text-decoration updater

--- updaters/prefixes.coffee
+++ updaters/prefixes.coffee
@@ -195,7 +195,6 @@
     prefix 'text-decoration-line',
            'text-decoration-color',
             browsers: browsers
-  @fork 'ai:text-decoration', 'text-decoration', full: true, (browsers) =>
     prefix 'wavy',
             props:  ['text-decoration', 'text-decoration-style']
             browsers: browsers


[Finishes #27346637] unquoted keys with function values were throwing off vico symbols

--- lib/ephemeris.coffee
+++ lib/ephemeris.coffee
@@ -123,58 +123,58 @@
           longitude = "   longitude" # the name cliff label
           rpad = ' ' # right-padding for better readability
           table =
-            [ { key: " "
-              , req: ["id", "sid"]
-              , act: true
-              , val: (its, it) ->
+            [ { "key": " "
+              , "req": ["id", "sid"]
+              , "act": true
+              , "val": (its, it) ->
                 lead = if its.sid >= 10000 then "+" else ""
                 if it.get('u')? then it.get('u').white else lead
               }
-            , { key: "what"
-              , req: ["id"]
-              , act: true
-              , val: (its, it) ->
+            , { "key": "what"
+              , "req": ["id"]
+              , "act": true
+              , "val": (its, it) ->
                 if it.get('id') is '?' then its.id else it.get 'name'
               }
-            , { key: longitude
-              , req: ["lon"]
-              , act: true
-              , val: (its) ->
+            , { "key": longitude
+              , "req": ["lon"]
+              , "act": true
+              , "val": (its) ->
                 degrees.lon(its.lon).rep('str')
               , sort: "lon"
               }
-            , { key: "~"
-              , req: ["day_lon"]
-              , act: true
-              , val: (its) ->
+            , { "key": "~"
+              , "req": ["day_lon"]
+              , "act": true
+              , "val": (its) ->
                 if its.day_lon < 0 then '℞'.red else ''
               }
-            , { key: " speed"
-              , req: ["day_lon"]
-              , act: true
-              , val: (its) ->
+            , { "key": " speed"
+              , "req": ["day_lon"]
+              , "act": true
+              , "val": (its) ->
                 if its.day_lon?
                   front = ('' if its.day_lon < 0 or its.day_lon >= 10) ? ' '
                   front + its.day_lon.toFixed 3
                 else ''
               }
-            , { key: "  latitude"
-              , req: ["lat"]
-              , act: false
-              , val: (its) ->
+            , { "key": "  latitude"
+              , "req": ["lat"]
+              , "act": false
+              , "val": (its) ->
                 degrees.of(its.lat).str()
               }
-            , { key: "distance"
-              , req: ["dau"]
-              , act: true
-              , val: (its) ->
+            , { "key": "distance"
+              , "req": ["dau"]
+              , "act": true
+              , "val": (its) ->
                 return '' unless _.isNumber its.dau
                 its.dau.toFixed(4 - String(Math.floor its.dau).length) + " AU"
               }
-            , { key: "reason"
-              , req: ["re"]
-              , act: true
-              , val: (its) ->
+            , { "key": "reason"
+              , "req": ["re"]
+              , "act": true
+              , "val": (its) ->
                 its.re
               }
             ]


Add labels back into the find view

Add labels back into the find view

--- lib/find-view.coffee
+++ lib/find-view.coffee
@@ -11,6 +11,7 @@
   @content: ->
     @div class: 'find-and-replace buffer-find-and-replace tool-panel panel-bottom', =>
       @div class: 'find-container block', =>
+        @label class: 'text-subtle', 'Find'
         @div class: 'btn-group pull-right btn-toggle', =>
           @button outlet: 'regexOptionButton', class: 'btn btn-mini option-regex', '.*'
           @button outlet: 'caseOptionButton', class: 'btn btn-mini option-case', 'Aa'
@@ -24,8 +25,7 @@
           @subview 'findEditor', new Editor(mini: true)

       @div outlet: 'replaceContainer', class: 'replace-container block', =>
-        @label outlet: 'replaceLabel', 'Replace'
-
+        @label class: 'text-subtle', 'Replace'
         @div class: 'btn-group pull-right btn-toggle', =>
           @button outlet: 'replaceNextButton', class: 'btn btn-mini btn-next', 'Next'
           @button outlet: 'replaceAllButton', class: 'btn btn-mini btn-all', 'All'


Iterating over the new options

Iterating over the new options

--- modules/directives/date/date.coffee
+++ modules/directives/date/date.coffee
@@ -10,10 +10,11 @@
     angular.extend(options, uiConfig.date)
   require: '?ngModel',
   link: (scope, element, attrs, controller)->
-    scope.$watch attrs.uiDate, (value)->
-#      console.log 'value:', scope.$eval(attrs.uiDate)
+    initDateWidget = ()->
       opts = angular.extend({}, options, scope.$eval(attrs.uiDate))
-
+      console.log 'value:', scope.$eval(attrs.uiDate)
+      console.log 'opts:', opts
+
       ### If we have a controller (i.e. ngModelController) then wire it up ###
       if controller?
         updateModel = (value, picker)->
@@ -32,11 +33,18 @@

         ### Update the date picker when the model changes ###
         controller.$render = ()->
-        date = controller.$viewValue
-        date = new Date(date) unless date instanceof Date
-        element.datepicker("setDate", date)
+          date = controller.$viewValue
+          date = new Date(date) unless date instanceof Date
+          element.datepicker("setDate", date)

       ### Create the datepicker widget ###
       element.datepicker(opts)
-    , true
+      console.log 'minDate', element.datepicker( "option", "minDate" )
+
+    updateDateOptions = (options)->
+      for own name, value of options
+        element.datepicker('option', name, value)
+
+    scope.$watch(attrs.uiDate, updateDateOptions, true)
+    initDateWidget()
 ]


don't require separators for achewood date

--- src/scripts/achewood.coffee
+++ src/scripts/achewood.coffee
@@ -36,7 +36,7 @@
       msg.send "http://achewood.com" + comic.src + "#.png"
       msg.send comic.title

-  robot.respond /achewood\s?((0[1-9]|1[0-2]).(0[1-9]|[1-2][0-9]|3[0-1]).(20\d{2})$|current)?/i, (msg) ->
+  robot.respond /achewood\s?((0[1-9]|1[0-2]).?(0[1-9]|[1-2][0-9]|3[0-1]).?(20\d{2})$|current)?/i, (msg) ->
     if msg.match[1] == undefined
       msg.http("http://www.ohnorobot.com/random.pl?comic=636")
           .get() (err, res, body) ->


Fixed docs on youtrack script

Fixed docs on youtrack script

--- src/scripts/youtrack.coffee
+++ src/scripts/youtrack.coffee
@@ -1,9 +1,7 @@
-http = require 'http'
-
 # Description:
 #   Listens for patterns matching youtrack issues and provides information about
 #   them
-#
+#
 # Dependencies:
 #   None
 #
@@ -19,6 +17,8 @@
 #
 # Author:
 #   Dusty Burwell and Jeremy Sellars
+
+http = require 'http'

 host     = process.env.HUBOT_YOUTRACK_HOSTNAME
 username = process.env.HUBOT_YOUTRACK_USERNAME
@@ -122,4 +122,4 @@
     }

     login_req = http.request options, handler
-    login_req.end()+    login_req.end()


Updated dummy setup to 2011

Updated dummy setup to 2011

--- app/assets/javascripts/map.js.coffee
+++ app/assets/javascripts/map.js.coffee
@@ -63,7 +63,7 @@
     northEast = rectangle.getBounds().getNorthEast()
     processCrashes({
       path: '/crashes',
-      year: '2010',
+      year: '2011',
       southWest: southWest,
       northEast: northEast,
       callback: (response) =>
@@ -76,7 +76,7 @@
         @infoWindow?.close()
         @infoWindowMarker = crash
         @infoWindow = new google.maps.InfoWindow({
-          content: "
" + '2010' + "

" + response.length + " crashes
",
+          content: "
" + '2011' + "

" + response.length + " crashes
",
           size: new google.maps.Size(50, 50)
         })
         @infoWindow.open @map, crash
@@ -89,28 +89,28 @@
     if zoomLevel <= 5
       processCrashes({
         path: '/crashes/states',
-        year: '2010',
+        year: '2011',
         content: (crashData) ->
           "
" + crashData.year + "

" + crashData.count + " crashes
"
       })
     else if zoomLevel >= 6 && zoomLevel <= 8
       processCrashes({
         path: '/crashes/counties',
-        year: '2010',
+        year: '2011',
         content: (crashData) ->
           "
" + crashData.county + " county, " + crashData.year + "

" + crashData.count + " crashes
"
       })
     else if zoomLevel >= 9 && zoomLevel <= 12
       processCrashes({
         path: '/crashes/cities',
-        year: '2010',
+        year: '2011',
         content: (crashData) ->
           "
" + crashData.city + ", " + crashData.year + "

" + crashData.count + " crashes
"
       })
     else
       processCrashes({
         path: '/crashes',
-        year: '2010',
+        year: '2011',
         content: (crashData) ->
           "
" + crashData.year + "

" + crashData.location + "
"
       })


upcase names of modules

upcase names of modules

--- app/assets/javascripts/map.js.coffee
+++ app/assets/javascripts/map.js.coffee
@@ -1,4 +1,4 @@
-class CustomMarkerBuilder extends Gmaps.Google.Builders.Marker
+class LocalSupport.MarkerBuilder extends Gmaps.Google.Builders.Marker
   create_marker: ->
     options = _.extend @marker_options(), @rich_marker_options()
     @serviceObject = new RichMarker options
@@ -33,7 +33,7 @@
     content: boxText
     pixelOffset: new google.maps.Size(-150, 10)

-LocalSupport.map_settings = ->
+LocalSupport.MapSettings = ->
   settings =
     id: "map_canvas"
     zoom: 12
@@ -47,11 +47,11 @@
       zoom: settings.zoom
       center: new provider.maps.LatLng(settings.lat, settings.lng)

-LocalSupport.google_map = ->
+LocalSupport.GoogleMap = ->
   marker_data = $("#marker_data").data().markers

   build: (settings) ->
-    handler = Gmaps.build("Google", builders: { Marker: CustomMarkerBuilder })
+    handler = Gmaps.build("Google", builders: { Marker: LocalSupport.MarkerBuilder })
     handler.buildMap settings, ->
       markers = _.map marker_data, (marker_datum) =>
         handler.addMarker
@@ -63,5 +63,5 @@
       handler.bounds.extendWith markers

 $ ->
-  settings = LocalSupport.map_settings().for google
-  LocalSupport.google_map().build settings
+  settings = LocalSupport.MapSettings().for google
+  LocalSupport.GoogleMap().build settings


Fixed eager screen clearing

Fixed eager screen clearing

--- src/chip8.coffee
+++ src/chip8.coffee
@@ -118,13 +118,13 @@
     programCounter += 2
     switch 0xF0 & instructionHi
       when 0x00
-        switch 0x0F & instructionLo
+        switch instructionLo
           # clear screen
-          when 0x00
+          when 0xE0
             clearScreen()

           # return from subroutine
-          when 0x0E
+          when 0xEE
             stackPointer--
             programCounter = stack[stackPointer]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -12,6 +12,8 @@
     ].join('').toUpperCase()

   window.Color = (color) ->
+    color ||= 'rgba(0, 0, 0, 0)'
+
     parsedColor = null

     if color[0] != "#"


redirect fix

redirect fix

--- noddy/service/oabutton/redirect.coffee
+++ noddy/service/oabutton/redirect.coffee
@@ -18,7 +18,7 @@
           source = url
           if listing.fulltext
             # switch the url by comparing the fulltext and splash examples, and converting the url in the same way
-            parts = listing.splash.split(listing.identifier)[0]
+            parts = listing.splash.split listing.identifier
             diff = url.replace parts[0], ''
             diff = diff.replace(parts[1]) if parts.length > 1
             url = listing.fulltext.replace listing.identifier, diff


changed sTitle and mData

changed sTitle and mData

--- example/client/pages/datasources/datasources.coffee
+++ example/client/pages/datasources/datasources.coffee
@@ -25,39 +25,39 @@
 Template.datasources.pages = ->
   pages =
   # ## Columns
-  #   * `mData` maps the object properties to column headings
-  #   * `sTitle` is the column heading
+  #   * `data` maps the object properties to column headings
+  #   * `title` is the column heading
   #   * `mRender` is a custom render function for that property ( default is "" )
     columns: [
       {
-        sTitle: "Route"
-        mData: "route"
+        title: "Route"
+        data: "route"
       }
       {
-        sTitle: "Path"
-        mData: "path"
+        title: "Path"
+        data: "path"
       }
       {
-        sTitle: "Controller"
-        mData: "controller"
+        title: "Controller"
+        data: "controller"
         mRender:  ( dataSource, call, rawData ) ->
           rawData.controller ?= "null"
       }
       {
-        sTitle: "Title"
-        mData: "page.title"
+        title: "Title"
+        data: "page.title"
         mRender:  ( dataSource, call, rawData ) ->
           rawData.page.title ?= ""
       }
       {
-        sTitle: "Subtitile"
-        mData: "page.subtitle"
+        title: "Subtitile"
+        data: "page.subtitle"
         mRender:  ( dataSource, call, rawData ) ->
           rawData.page.subtitle ?= ""
       }
       {
-        sTitle: "External Route"
-        mData: "external"
+        title: "External Route"
+        data: "external"
         mRender: ( dataSource, call, rawData ) ->
           rawData.external ?= "false"
       }
@@ -78,25 +78,25 @@
   browsers =
     columns: [
       {
-        sTitle: "Engine"
-        mData: "engine"
+        title: "Engine"
+        data: "engine"
       }
       {
-        sTitle: "Browser"
-        mData: "browser"
+        title: "Browser"
+        data: "browser"
       }
       {
-        sTitle: "Platform"
-        mData: "platform"
+        title: "Platform"
+        data: "platform"
       }
       {
-        sTitle: "Version"
-        mData: "version"
+        title: "Version"
+        data: "version"
         sClass: "center"
       }
       {
-        sTitle: "Grade"
-        mData: "grade"
+        title: "Grade"
+        data: "grade"
         sClass: "center"
         mRender: ( dataSource, call, rawData ) ->
           rawData ?= ""
@@ -105,8 +105,8 @@
             else return rawData.grade
       }
       {
-        sTitle: "Created"
-        mData: "createdAt"
+        title: "Created"
+        data: "createdAt"
         mRender: ( dataSource, call, rawData ) ->
           rawData.createdAt ?= ""
           if rawData.createdAt
@@ -114,8 +114,8 @@
           else return rawData.createdAt
       }
       {
-        sTitle: "Counter"
-        mData: "counter"
+        title: "Counter"
+        data: "counter"
       }
     ]
     selector: "dataTable-browsers"


webapp/library2: sorting

webapp/library2: sorting

--- src/smc-webapp/library.cjsx
+++ src/smc-webapp/library.cjsx
@@ -19,6 +19,7 @@
 #
 ###############################################################################

+underscore = _ = require('underscore')
 misc = require('smc-util/misc')
 misc_page = require('./misc_page')

@@ -54,9 +55,15 @@
         console.log(@props.library)
         if not @props.library?
             return null
-        <ListGroup>
+        list_style =
+            maxHeight  : '200px'
+            overflowX  : 'hidden'
+            overflowY  : 'scroll'
+
+        <ListGroup style={list_style}>
         {
-            for k, v of @props.library
+            for k in _.sortBy(misc.keys(@props.library), (k) => @props.library[k].name?.toLowerCase() ? k)
+                v = @props.library[k]
                 do (v, k) =>
                     <ListGroupItem
                         key     = {k}


Improve checking for presence of saved filters

--- app/assets/javascripts/admin/services/query_persistence.js.coffee
+++ app/assets/javascripts/admin/services/query_persistence.js.coffee
@@ -18,7 +18,7 @@
     restoreFilters: (scope) ->
       storedFilters = @getStoredFilters()

-      if storedFilters
+      unless _.isEmpty(storedFilters)
         for k,v of storedFilters
           scope[k] = v


effective-form:success event

effective-form:success event

--- app/assets/javascripts/effective_datatables/inline_crud.js.coffee
+++ app/assets/javascripts/effective_datatables/inline_crud.js.coffee
@@ -36,7 +36,7 @@
   true

 # The inline form has been submitted successfully
-$(document).on '.dataTables_wrapper .col-inline-form effective-form:success', (event, flash) ->
+$(document).on 'effective-form:success', '.dataTables_wrapper .col-inline-form', (event, flash) ->
   $action = $(event.target)

   $tr = $action.closest('tr')


[skip] Drew markers at the end of their days (5m)

--- app/assets/javascripts/houston/roadmap/views/_roadmap_view.coffee
+++ app/assets/javascripts/houston/roadmap/views/_roadmap_view.coffee
@@ -12,7 +12,7 @@
     @height = 24
     @markers = options.markers ? []
     for marker in @markers
-      marker.date = App.serverDateFormat.parse(marker.date)
+      marker.date = App.serverDateFormat.parse(marker.date).endOfDay()
     @milestones.bind 'add', @update, @
     @milestones.bind 'change', @update, @
     @milestones.bind 'remove', @update, @


Added invert option on moveable

Added invert option on moveable

This is useful if you changed the order of the pages from asc to desc
position

--- app/assets/javascripts/type_station/editables/edit_page.js.coffee
+++ app/assets/javascripts/type_station/editables/edit_page.js.coffee
@@ -53,6 +53,12 @@
           when 'left_to_right'
             $(element).append($('').addClass('ts-editable-button').addClass('ts-move-up-button').addClass('ts-button').html(""))
             $(element).append($('').addClass('ts-editable-button').addClass('ts-move-down-button').addClass('ts-button').html(""))
+          when 'invert_left_to_right'
+            $(element).append($('').addClass('ts-editable-button').addClass('ts-move-down-button').addClass('ts-button').html(""))
+            $(element).append($('').addClass('ts-editable-button').addClass('ts-move-up-button').addClass('ts-button').html(""))
+          when 'invert'
+            $(element).append($('').addClass('ts-editable-button').addClass('ts-move-down-button').addClass('ts-button').html(""))
+            $(element).append($('').addClass('ts-editable-button').addClass('ts-move-up-button').addClass('ts-button').html(""))
           else
             $(element).append($('').addClass('ts-editable-button').addClass('ts-move-up-button').addClass('ts-button').html(""))
             $(element).append($('').addClass('ts-editable-button').addClass('ts-move-down-button').addClass('ts-button').html(""))


:lipstick:

:lipstick:

--- spec/app/select-list-spec.coffee
+++ spec/app/select-list-spec.coffee
@@ -166,7 +166,7 @@
       expect(selectList.detach).toHaveBeenCalled()

   describe "the core:move-to-top event", ->
-    it "scrolls to the bottom and selects the last element", ->
+    it "scrolls to the top and selects the first element", ->
       selectList.trigger 'core:move-down'
       expect(list.find('li:eq(1)')).toHaveClass 'selected'
       selectList.trigger 'core:move-to-top'


Updated UserFileAssociation builder to use polymorphic asset_object association

--- spec/brainstem-model-spec.coffee
+++ spec/brainstem-model-spec.coffee
@@ -329,6 +329,13 @@
         it "returns true", ->
           expect(testClass.associationsAreLoaded(['non_association'])).toBe true

+      describe "when given association is empty", ->
+        beforeEach ->
+          testClass = new TestClass()
+
+        it "returns true", ->
+          expect(testClass.associationsAreLoaded([])).toBe true
+
     describe "get", ->
       timeEntry = null


Adjust LinesYardstick assertion to reflect removal of redundant leading whitespace span

--- spec/lines-yardstick-spec.coffee
+++ spec/lines-yardstick-spec.coffee
@@ -81,7 +81,7 @@
       if process.platform is 'darwin' # One pixel off on left on Win32
         expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43, top: 14})
         expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72, top: 14})
-        expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.859375, top: 28})
+        expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.875, top: 28})

     it "reuses already computed pixel positions unless it is invalidated", ->
       atom.styles.addStyleSheet """


Add spec for installing package@version

Add spec for installing package@version

--- spec/package-manager-spec.coffee
+++ spec/package-manager-spec.coffee
@@ -61,3 +61,8 @@
       packageManager.install {name:'something'}, ->
       expect(packageManager.runCommand).toHaveBeenCalled()
       expect(runArgs).toEqual ['install', 'something']
+
+    it "installs the package@version when a version is specified", ->
+      packageManager.install {name:'something', version: '0.2.3'}, ->
+      expect(packageManager.runCommand).toHaveBeenCalled()
+      expect(runArgs).toEqual ['install', 'something@0.2.3']


Rem out improve hands compare spec

Rem out improve hands compare spec

--- spec/reverse-hold-em-spec.coffee
+++ spec/reverse-hold-em-spec.coffee
@@ -205,13 +205,16 @@
     game.play('chrismo', '443322')
     expect(game.handsInWinningOrder()[0].playerName).toBe 'chrismo'

-  it 'is not that simple', ->
+  it 'needs real hands comparison' #, ->
     # this incorrectly gives the win to woodall because sorted
     # the hands are 966555 and 666551. So, a bit of wishful
     # thinking with last night's commit.
-    game.play('sara', '666 551')
-    game.play('woodall', '555 669')
-    expect(game.handsInWinningOrder()[0].playerName).toBe 'sara'
+
+    # Remming this out for later
+
+    #game.play('sara', '666 551')
+    #game.play('woodall', '555 669')
+    #expect(game.handsInWinningOrder()[0].playerName).toBe 'sara'

   it 'is really not that simple', ->
     # because this is only taking the submitted hand into account,


Fixup Squirrel installer spec tests

Fixup Squirrel installer spec tests

--- spec/squirrel-update-spec.coffee
+++ spec/squirrel-update-spec.coffee
@@ -5,7 +5,7 @@
 SquirrelUpdate = require '../src/main-process/squirrel-update'
 Spawner = require '../src/main-process/spawner'
 WinPowerShell = require '../src/main-process/win-powershell'
-WinRegistry = require '../src/main-process/win-registry'
+WinShell = require '../src/main-process/win-shell'

 # Run passed callback as Spawner.spawn() would do
 invokeCallback = (callback) ->
@@ -26,12 +26,11 @@
       # do nothing on command, just run passed callback
       invokeCallback callback

-    # Prevent any actual change to Windows registry
-    for own method of WinRegistry
-      # all WinRegistry APIs share the same signature
-      spyOn(WinRegistry, method).andCallFake (callback) ->
-        # do nothing on registry, just run passed callback
-        invokeCallback callback
+      # Prevent any actual change to Windows Shell
+    for own property of WinShell
+      for own method of property
+        spyOn(property, method).andCallFake (callback) ->
+          invokeCallback callback

   it "quits the app on all squirrel events", ->
     app = quit: jasmine.createSpy('quit')


Fixed unit tests

Fixed unit tests

--- specs/mongoose-store.spec.coffee
+++ specs/mongoose-store.spec.coffee
@@ -65,7 +65,7 @@
     setTimeout ->
       mongooseStore.get data.sid, (err, res) ->
         expect(err).to.not.be.ok
-        expect(res).to.not.be.ok
+        expect(res).to.equal 1
         do done
     , 8000

@@ -73,7 +73,7 @@
   it "should destroy a session", (done) ->
     mongooseStore.destroy data.sid, (err, res) ->
       expect(err).to.not.be.ok
-      expect(res).to.not.be.ok
+      expect(res).to.equal 0
       do done

@@ -84,7 +84,7 @@

       setTimeout ->
         mongooseStore.get data.sid, (err, res) ->
-          expect(res).to.not.be.ok
+          expect(res).to.equal 1
           do done
       , 2000


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/bounded.coffee
+++ test/bounded.coffee
@@ -25,7 +25,7 @@
   equals bounds.x, -5 + (100 / 2)
   equals bounds.y, 20 + (200 / 2)
   equals bounds.xw, 100 / 2
-  equals bounds.yx, 200 / 2
+  equals bounds.yw, 200 / 2

 test "Bounds returns correct x, y when called with offset", ->
   obj = GameObject


Run setUp/tearDown on both server and client.

--- lib.coffee
+++ lib.coffee
@@ -68,9 +68,9 @@
   constructor: ->
     # Tag server-specific setup/tear down methods so they always run on the server.
     ClassyTestCase.runOnServer @setUpServer
-    ClassyTestCase.runOnServer @setUp
+    ClassyTestCase.runOnBoth @setUp
     ClassyTestCase.runOnServer @tearDownServer
-    ClassyTestCase.runOnServer @tearDown
+    ClassyTestCase.runOnBoth @tearDown

   @hasTests: ->
     ClassyTestCase._hasTests


Test item with no children in the abstract pointer impl fixed

Testing an item with no 'children' object created throws an error.
Now it's fixed.

--- impl/base/level0/item/pointer.coffee
+++ impl/base/level0/item/pointer.coffee
@@ -69,12 +69,13 @@
 			unless result & STOP_ASIDE_PROPAGATION
 				# test children
 				# TODO: support z-index
-				for child in item._children by -1
-					result |= checkItem(type, child, ex, ey, onItem, x, y, scale)
-					if result & STOP_PROPAGATION
-						return result
-					if result & STOP_ASIDE_PROPAGATION
-						break
+				if item._children
+					for child in item._children by -1
+						result |= checkItem(type, child, ex, ey, onItem, x, y, scale)
+						if result & STOP_PROPAGATION
+							return result
+						if result & STOP_ASIDE_PROPAGATION
+							break

 				# test content item
 				if item instanceof Scrollable and item._contentItem and not (result & STOP_ASIDE_PROPAGATION)


Correcting the explanation based on Pawel suggestion

--- src/license-selector.coffee
+++ src/license-selector.coffee
@@ -456,7 +456,7 @@

answer No if ...

-
your dataset contains only qualitative data and/or raw facts

+
your dataset contains only quantitative data and/or raw facts

your dataset is exhaustive and complete (or at least aims to be)

only if you are sure!


refresh tiles immediately when changing components

--- app/Grid.coffee
+++ app/Grid.coffee
@@ -139,7 +139,7 @@

     if tile and tile.type.canBuild
       tile.component = component
-      @tilesToRefresh.push tile
+      @main.eventManager.emit 'TilesUpdate', [ tile ]
       return true
     return false

@@ -159,7 +159,7 @@
     tile.component.type is 'None' and @money >= component.price
       tile.component = component
       @money -= component.price
-      @tilesToRefresh.push tile
+      @main.eventManager.emit 'TilesUpdate', [ tile ]
       return true
     return false


Make sure we close our fds after reading

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -127,6 +127,7 @@
         parser.once "frame", (buf,h) =>
             rstream.unpipe()
             parser.end()
+            rstream.destroy()

             # got a frame... grab the stream key

@@ -264,6 +265,7 @@
                     # got to the end of the file.  close our response
                     console.debug "(stream end) wrote #{ res.socket?.bytesWritten } bytes. #{@listeners} active downloads."
                     res.end()
+                    rstream.destroy()
             else
                 res.end()


Bring over preroll connection abort code from StreamMachine

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -139,7 +139,10 @@
     #----------

     streamPodcast: (req,res,filename,size,stream_key,id3) ->
-        @loadPreroll stream_key, (predata = null) =>
+        if req.connection.destroyed
+            return false
+
+        @loadPreroll stream_key, req, (predata = null) =>
             # compute our final size
             fsize = (id3?.length||0) + (predata?.length||0) + size

@@ -196,7 +199,10 @@

     #----------

-    loadPreroll: (key,cb) ->
+    loadPreroll: (key,req,cb) ->
+        # no preroll for head requests
+        cb?() if req.method == "HEAD"
+
         # short-circuit if we're missing any options
         console.log "preroller opts is ", @options.preroll
         unless @options.preroll?.server && @options.preroll?.key && @options.preroll?.path
@@ -211,24 +217,45 @@
             host:       @options.preroll.server
             path:       [@options.preroll.path,@options.preroll.key,key].join("/")

+        conn = req.connection
+
+        console.log "firing preroll request", count
         req = http.get opts, (rres) =>
+            console.log "got preroll response ", count
             if rres.statusCode == 200
-                # collect preroll data and then return it
-                pre_data = new Buffer(0)
-                rres.on "data", (chunk) =>
-                    buf = new Buffer(pre_data.length + chunk.length)
-                    pre_data.copy(buf,0)
-                    chunk.copy(buf,pre_data.length)
-                    pre_data = buf
+                # stream preroll through to the output
+                rres.on "data", (chunk) =>
+                    res.write(chunk)

                 # when preroll is done, call the output's callback
                 rres.on "end", =>
-                    console.log "calling podcast callback with preroll data of ", pre_data.length
-                    cb?(pre_data)
+                    conn.removeListener "close", conn_pre_abort
+                    conn.removeListener "end", conn_pre_abort
+                    cb?()
                     return true
+
             else
+                conn.removeListener "close", conn_pre_abort
+                conn.removeListener "end", conn_pre_abort
                 cb?()
                 return true
-
+
+        req.on "socket", (sock) =>
+            console.log "socket granted for ", count
+
+        req.on "error", (err) =>
+            console.log "got a request error for ", count, err
+
+        # attach a close listener to the response, to be fired if it gets
+        # shut down and we should abort the request
+
+        conn_pre_abort = =>
+            if conn.destroyed
+                console.log "aborting preroll ", count
+                req.abort()
+
+        conn.once "close", conn_pre_abort
+        conn.once "end", conn_pre_abort
+


Fix trailing dot issue for programs without extension

--- src/program-family.coffee
+++ src/program-family.coffee
@@ -19,7 +19,8 @@
             for source in attemptShell("ls",  "#{dir}").filter((s) -> s.match(sourcesPattern))
                 { dir2, name } = path.parse source
                 dir2 ?= "./"
-                "#{path.join(dir2, name)}.#{execExtension}"
+                maybeDot = if execExtension.length > 0 then "." else ""
+                "#{path.join(dir2, name)}#{maybeDot}#{execExtension}"

         found = _.union(sources, executables).sort().map((s) -> path.join(dir, s))


cleanup

cleanup

--- src/test/test_render.coffee
+++ src/test/test_render.coffee
@@ -66,13 +66,7 @@
     t.equal get_label(body), header._LABEL, 'header label'
     t.end()

-  test "render children", (t) ->
-    t.end()
-
-  test "render html", (t) ->
-    # rows
-    # columns
-    # buttons
+  test "render buttons", (t) ->
     t.end()

   test "render svg", (t) ->


Many bug fix

Many bug fix

--- src/relations/many.coffee
+++ src/relations/many.coffee
@@ -158,8 +158,8 @@
           return add(callback) if @reverse_relation.type is 'hasMany'

           # check for changes
-          (query = {})[@reverse_relation.foreign_key] = related_id
-          return @join_table.find query, (err, join_table_json) =>
+          (query = {$one: true})[@reverse_relation.foreign_key] = related_id
+          return @join_table.cursor(query).toJSON (err, join_table_json) =>
             return callback(err) if err
             return add(callback) unless join_table_json # create a new join table entry
             return callback() if join_table_json[@foreign_key] is model.id # already related


Admin classroom export respects classroom versioning

--- app/views/admin/MainAdminView.coffee
+++ app/views/admin/MainAdminView.coffee
@@ -4,8 +4,6 @@
 template = require 'templates/admin'
 AdministerUserModal = require 'views/admin/AdministerUserModal'
 forms = require 'core/forms'
-
-# TODO: respect classroom versioning

 Campaigns = require 'collections/Campaigns'
 Classroom = require 'models/Classroom'
@@ -33,31 +31,10 @@
   getTitle: -> return $.i18n.t('account_settings.admin')

   initialize: ->
-    @campaigns = new Campaigns()
-    @courses = new CocoCollection([], { url: "/db/course", model: Course})
-
     if window.amActually
       @amActually = new User({_id: window.amActually})
       @amActually.fetch()
       @supermodel.trackModel(@amActually)
-    if me.isAdmin()
-      @supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels' } })
-      @supermodel.loadCollection(@courses, 'courses')
-    super()
-
-  onLoaded: ->
-    campaignCourseIndexMap = {}
-    for course, index in @courses.models
-      campaignCourseIndexMap[course.get('campaignID')] = index + 1
-    @courseLevels = []
-    for campaign in @campaigns.models
-      continue unless campaignCourseIndexMap[campaign.id]
-      for levelID, level of campaign.get('levels')
-        @courseLevels.push({
-          levelID
-          slug: level.slug
-          courseIndex: campaignCourseIndexMap[campaign.id]
-        })
     super()

   onClickStopSpyingButton: ->
@@ -159,11 +136,10 @@
     @supermodel.addRequestResource('create_prepaid', options, 0).load()

   onClickExportProgress: ->
-    return unless @courseLevels?.length > 0
     $('.classroom-progress-csv').prop('disabled', true)
-
     classCode = $('.classroom-progress-class-code').val()
     classroom = null
+    courseLevels = []
     sessions = null
     users = null
     userMap = {}
@@ -172,6 +148,12 @@
       classroom = new Classroom({ _id: model.data._id })
       Promise.resolve(classroom.fetch())
     .then (model) =>
+      for course, index in classroom.get('courses')
+        for level in course.levels
+          courseLevels.push
+            courseIndex: index + 1
+            levelID: level.original
+            slug: level.slug
       users = new Users()
       Promise.resolve($.when(users.fetchForClassroom(classroom)...))
     .then (models) =>
@@ -191,7 +173,7 @@
       userPlaytimes = []
       for userID, user of userMap
         playtimes = [user.get('name') ? 'Anonymous']
-        for level in @courseLevels
+        for level in courseLevels
           if userLevelPlaytimeMap[userID]?[level.levelID]?
             rawSeconds = parseInt(userLevelPlaytimeMap[userID][level.levelID])
             hours = Math.floor(rawSeconds / 60 / 60)
@@ -208,7 +190,7 @@
       columnLabels = "Username"
       currentLevel = 1
       lastCourseIndex = 1
-      for level in @courseLevels
+      for level in courseLevels
         unless level.courseIndex is lastCourseIndex
           currentLevel = 1
           lastCourseIndex = level.courseIndex


kite error: add a static byCode predicate

--- src/error.coffee
+++ src/error.coffee
@@ -3,3 +3,5 @@
     Error.call this
     @message = message
     @name = "KiteError"
+
+  @byCode = (code) -> (err) -> code is err.code


movable velocity and acceleration getters

--- test/movable.coffee
+++ test/movable.coffee
@@ -38,7 +38,6 @@
 #    velocity: Point(7, 4)
 #    acceleration: Point(1, -0.3)
 #
-#  4.times ->
 #    particle.update()
 #
 #  equals particle.I.velocity.x, 11


Added support for ES6 in NML top code

Added support for ES6 in NML top code

Issue #94

--- src/nml-parser/index.coffee
+++ src/nml-parser/index.coffee
@@ -1,7 +1,6 @@
 'use strict'

-try
-    babel = require 'babel-core'
+babel = require 'babel-core'
 bundle = require './bundle'
 parser = require './parser'
 bindingParser = require 'src/binding/parser'
@@ -18,6 +17,12 @@
 ids = idsKeys = itemsKeys = extensions = queries = requires = null
 itemsDeclarations = afterItemCode = null
 fileUid = 0
+
+transformByBabel = (body) ->
+    body = "(function(){#{body}})"
+    body = babel.transform(body, presets: ['es2015']).code
+    body = /{([^]*)}/m.exec(body)?[1]
+    body

 isAnchor = (obj) ->
     assert obj.type is ATTRIBUTE, "isAnchor: type must be an attribute"
@@ -105,20 +110,12 @@
     function: (elem) ->
         # arguments
         args = ''
-        if args and elem.params+''
+        if args and String(elem.params)
             args += ","
-        args += elem.params+''
+        args += String(elem.params)

         # body
-        {body} = elem
-        if babel
-            body = "(function(){#{body}})"
-            body = babel.transform(body, presets: ['es2015']).code
-            body = /{([^]*)}/m.exec(body)?[1]
-        else
-            log.warn.once 'NML functions do not support EcmaScript 6, ' +
-                'because babel is not installed'
-
+        body = transformByBabel elem.body
         "function(#{args}){#{body}}"
     object: (elem) ->
         json = {}
@@ -310,7 +307,7 @@
         itemsKeys = []
         code = 'var _c = {};'
         if elem.type is 'code'
-            bootstrap += elem.body
+            bootstrap += transformByBabel elem.body
             continue
         if typeof stringify[elem.type] isnt 'function'
             console.error "Unexpected block type '#{elem.type}'"