Add record comment spec

Add record comment spec

--- spec/haskell-record-spec.coffee
+++ spec/haskell-record-spec.coffee
@@ -1,18 +1,18 @@
 describe 'Record', ->
   grammar = null

-  zip = () ->
+  zip = ->
     lengthArray = (arr.length for arr in arguments)
     length = Math.max(lengthArray...)
     for i in [0...length]
       arr[i] for arr in arguments

   check = (line, exp) ->
-    for t,i in zip(line,exp)
+    for t, i in zip(line, exp)
       t[0] ?= {}
       t[1] ?= {}
-      t[0].index=i
-      t[1].index=i
+      t[0].index = i
+      t[1].index = i
       expect(t[0]).toEqual(t[1])

   beforeEach ->
@@ -375,3 +375,367 @@
     ]
     for l in zip(lines, exp)
       check l[0], l[1]
+
+  it "understands comments in records", ->
+    string = """
+      data Car = Car {
+          company :: String, -- comment
+          -- model :: String, -- commented field
+          year :: Int -- another comment
+        }
+      """
+    lines = grammar.tokenizeLines(string)
+    # console.log JSON.stringify(lines, undefined, 2)
+    expect(lines).toEqual [
+            [
+              {
+                "value": "data",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "storage.type.data.haskell"
+                ]
+              },
+              {
+                "value": " ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell"
+                ]
+              },
+              {
+                "value": "Car",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.type-signature.haskell",
+                  "entity.name.type.haskell"
+                ]
+              },
+              {
+                "value": " ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.type-signature.haskell"
+                ]
+              },
+              {
+                "value": "=",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "keyword.operator.assignment.haskell"
+                ]
+              },
+              {
+                "value": " ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell"
+                ]
+              },
+              {
+                "value": "Car",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "entity.name.tag.haskell"
+                ]
+              },
+              {
+                "value": " ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell"
+                ]
+              },
+              {
+                "value": "{",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "keyword.operator.record.begin.haskell"
+                ]
+              }
+            ],
+            [
+              {
+                "value": "    ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell"
+                ]
+              },
+              {
+                "value": "company",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "entity.other.attribute-name.haskell"
+                ]
+              },
+              {
+                "value": " ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell"
+                ]
+              },
+              {
+                "value": "::",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "keyword.other.double-colon.haskell"
+                ]
+              },
+              {
+                "value": " ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell"
+                ]
+              },
+              {
+                "value": "String",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "support.class.prelude.haskell"
+                ]
+              },
+              {
+                "value": ", ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell"
+                ]
+              },
+              {
+                "value": "--",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "comment.line.double-dash.haskell",
+                  "punctuation.definition.comment.haskell"
+                ]
+              },
+              {
+                "value": " comment",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "comment.line.double-dash.haskell"
+                ]
+              },
+              {
+                "value": "",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "comment.line.double-dash.haskell"
+                ]
+              }
+            ],
+            [
+              {
+                "value": "    ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "punctuation.whitespace.comment.leading.haskell"
+                ]
+              },
+              {
+                "value": "--",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "comment.line.double-dash.haskell",
+                  "punctuation.definition.comment.haskell"
+                ]
+              },
+              {
+                "value": " model :: String, -- commented field",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "comment.line.double-dash.haskell"
+                ]
+              },
+              {
+                "value": "",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "comment.line.double-dash.haskell"
+                ]
+              }
+            ],
+            [
+              {
+                "value": "    ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell"
+                ]
+              },
+              {
+                "value": "year",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "entity.other.attribute-name.haskell"
+                ]
+              },
+              {
+                "value": " ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell"
+                ]
+              },
+              {
+                "value": "::",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "keyword.other.double-colon.haskell"
+                ]
+              },
+              {
+                "value": " ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell"
+                ]
+              },
+              {
+                "value": "Int",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "support.class.prelude.haskell"
+                ]
+              },
+              {
+                "value": " ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell"
+                ]
+              },
+              {
+                "value": "--",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "comment.line.double-dash.haskell",
+                  "punctuation.definition.comment.haskell"
+                ]
+              },
+              {
+                "value": " another comment",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "comment.line.double-dash.haskell"
+                ]
+              },
+              {
+                "value": "",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell",
+                  "comment.line.double-dash.haskell"
+                ]
+              }
+            ],
+            [
+              {
+                "value": "  ",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "meta.record-field.type-declaration.haskell",
+                  "meta.type-signature.haskell"
+                ]
+              },
+              {
+                "value": "}",
+                "scopes": [
+                  "source.haskell",
+                  "meta.declaration.type.data.haskell",
+                  "meta.declaration.type.data.record.block.haskell",
+                  "keyword.operator.record.end.haskell"
+                ]
+              }
+            ]
+          ]


Better PreviousMap#withSources test

Better PreviousMap#withSources test

--- test/previous-map.coffee
+++ test/previous-map.coffee
@@ -35,10 +35,11 @@
     root.prevMap.annotation.should.eql('# sourceMappingURL=a.css.map')

   it 'checks previous sources', ->
-    root = parse('a{}', map: @map)
+    map  = { version: 3, file: 'a.css', sources: [], names: [], mappings: ''}
+    root = parse('a{}', map: map)
     root.prevMap.withSources.should.be.false

-    map  = '{"version":3,"file":"a","sources":["a{}"],"names":[],"mappings":""}'
+    map.sources.push('a{}')
     root = parse('a{}', map: map)
     root.prevMap.withSources.should.be.true


Fixed import errors with missing body

Fixed import errors with missing body

--- PostmanImporter.coffee
+++ PostmanImporter.coffee
@@ -19,7 +19,6 @@
         for headerLine in postmanHeaders
             match = headerLine.match /^([^\s\:]*)\s*\:\s*(.*)$/
             if match
-
                 pawRequest.setHeader @expandEnvironmentVariables(context, match[1]), @expandEnvironmentVariables(context, match[2])

         # Set raw body
@@ -41,10 +40,11 @@
                     foundBody = true

             if not foundBody
-                pawRequest.body = @expandEnvironmentVariables context, rawRequestBody
+                if typeof rawRequestBody isnt 'undefined'
+                    pawRequest.body = @expandEnvironmentVariables context, rawRequestBody

         # Set Form URL-Encoded body
-        else if postmanRequest["dataMode"] == "urlencoded"
+        else if postmanRequest["dataMode"] == "urlencoded" and postmanRequest["data"] != null
             postmanBodyData = postmanRequest["data"]
             bodyObject = new Object()
             for bodyItem in postmanBodyData
@@ -56,7 +56,7 @@
             pawRequest.urlEncodedBody = bodyObject;

         # Set Multipart body
-        else if postmanRequest["dataMode"] == "params"
+        else if postmanRequest["dataMode"] == "params" and postmanRequest["data"] != null
             postmanBodyData = postmanRequest["data"]
             bodyObject = new Object()
             for bodyItem in postmanBodyData


Update DiplomatView.coffee

Update DiplomatView.coffee

Humble request to become a member of the diplomat corps.

--- app/views/contribute/DiplomatView.coffee
+++ app/views/contribute/DiplomatView.coffee
@@ -96,7 +96,7 @@
     'en-US': []        # English (US), English (US)
     'en-GB': []        # English (UK), English (UK)
     ru: ['EagleTA', 'ImmortalJoker', 'Mr A', 'Shpionus', 'a1ip', 'fess89', 'iulianR', 'kerradus', 'kisik21', 'nixel', 'ser-storchak', 'CatSkald']             # русский язык, Russian
-    'de-DE': ['Anon', 'Dirk', 'HiroP0', 'bahuma20', 'bkimminich', 'djsmith85', 'dkundel', 'domenukk', 'faabsen', 'Zeldaretter']        # Deutsch (Deutschland), German (Germany)
+    'de-DE': ['Anon', 'Dirk', 'HiroP0', 'bahuma20', 'bkimminich', 'djsmith85', 'dkundel', 'domenukk', 'faabsen', 'Zeldaretter','joca16']        # Deutsch (Deutschland), German (Germany)
     'de-AT': ['djsmith85']        # Deutsch (Österreich), German (Austria)
     'de-CH': ['greyhusky']        # Deutsch (Schweiz), German (Switzerland)
     'es-419': ['2xG', 'Federico Tomas', 'Jesús Ruppel', 'Mariano Luzza', 'Matthew Burt']       # español (América Latina), Spanish (Latin America)


Mind as well throw a comment here too

Mind as well throw a comment here too

You put them every fucking where anyway

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -22,6 +22,7 @@
 ## S3 Storage
 s3 = S3("s3://#{AYP_AWS_KEY}:#{AYP_AWS_SECRET}@#{AYP_AWS_BUCKET}.s3.amazonaws.com/")

+## Robot event bindings
 module.exports = (robot) ->
   buffer = new PantsBuffer()


Internet exploder alert warning

Internet exploder alert warning

--- script.coffee
+++ script.coffee
@@ -20,4 +20,8 @@

     # if the browser doesn't support svg, use a png
     if not document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1")
-        jQuery("#robot").attr("src", "decisiverobot.png")+        jQuery("#robot").attr("src", "decisiverobot.png")
+
+	#if the browser is IE, tell them it is shit
+	if jQuery.browser.msie
+		alert("Decisive Robot works best on decent browsers like Firefox and Chrome. Don't use Internet Explorer, meatbag.")


MachineGeneralSettingsView: Put always on toggle.

--- client/app/lib/providers/machinegeneralsettingsview.coffee
+++ client/app/lib/providers/machinegeneralsettingsview.coffee
@@ -33,6 +33,26 @@
     kd.singletons.computeController[method] @machine

     @emit 'ModalDestroyRequested'
+
+
+  handleAlwaysOnStateChanged: (state) ->
+
+    { alwaysOn } = @form.inputs
+    { computeController } = kd.singletons
+
+    computeController.fetchUserPlan (plan) =>
+
+      computeController.setAlwaysOn @machine, state, (err) =>
+
+        return  unless err
+
+        if err.name is 'UsageLimitReached' and plan isnt 'hobbyist'
+          @emit 'ModalDestroyRequested'
+          kd.utils.defer => new ComputeErrorUsageModal { plan }
+        else
+          showError err
+
+        alwaysOn.setOff no

   handleNicknameUpdate: ->
@@ -147,10 +167,16 @@
           cssClass      : if running then 'custom-link-view' else 'hidden'
           itemClass     : KDView
           partial       : @machine.ipAddress or 'N/A'
+        alwaysOn        :
+          label         : 'Keep VM always on'
+          defaultValue  : @machine.alwaysOn
+          itemClass     : KodingSwitch
+          disabled      : @machine.isPermanent()
+          callback      : @bound 'handleAlwaysOnStateChanged'
         accessUri       :
           label         : 'Assigned URL'
           cssClass      : 'assigned-url'
           itemClass     : CustomLinkView
           title         : @machine.domain
           href          : accessUri
-          target        : '_blank'
+          target        : '_blank'


Removed duplicate tests

Removed duplicate tests

--- test/tb/views/base/index_test.coffee
+++ test/tb/views/base/index_test.coffee
@@ -184,43 +184,3 @@
       view.$el.trigger 'click'

       assert.isFalse view.clicked
-
-  describe 'child views', ->
-
-    describe 'adding a View object', ->
-
-      it 'should save the view to the element\'s "children" array', ->
-
-        class ViewWithChildren extends View
-
-          render: ->
-
-            @$el.append (new View).el
-
-            @
-
-        view = new ViewWithChildren
-
-        view.render()
-
-        assert.equal view.el.children.length, 1
-
-    describe 'clearing child views', ->
-
-      it 'should be able to empty the child views', ->
-
-        class ViewWithChildren extends View
-
-          render: ->
-
-            @$el.append (new View).el
-
-            @
-
-        view = new ViewWithChildren
-
-        view.render()
-
-        view.$el.empty()
-
-        assert.equal view.el.children.length, 0


Improve mappers coverage

Improve mappers coverage

--- test/units/utils/mappers.spec.coffee
+++ test/units/utils/mappers.spec.coffee
@@ -36,6 +36,15 @@

     expect(res).toEqual([3,3,3])

+  it 'should return undefined for null entries', ->
+    res = [
+      {foo: 10},
+      null
+    ].map _.property 'foo'
+
+    expect(res).toEqual([10, undefined])
+
+
 describe 'first', ->
   it 'should map the given value at first index in the element', ->

@@ -56,6 +65,15 @@
     ].map _.first _.property 'foo'

     expect(res).toEqual([10, 'foo', true])
+
+  it 'should return undefined for null entries', ->
+    res = [
+      [10, 20, 30],
+      null
+    ].map _.first()
+
+    expect(res).toEqual([10, undefined])
+

 describe 'last', ->
   it 'should map the given value at last index in the element', ->
@@ -78,6 +96,15 @@

     expect(res).toEqual([10, 'foo', true])

+  it 'should return undefined for null entries', ->
+    res = [
+      [10, 20, 30],
+      null
+    ].map _.last()
+
+    expect(res).toEqual([30, undefined])
+
+
 describe 'at', ->
   it 'should map the given value at index in the element', ->

@@ -99,6 +126,23 @@

     expect(res).toEqual([10, 'foo', true])

+  it 'should default to 0 without an index', ->
+    res = [
+      [10, 20, 30],
+      ['foo', 'bar', 'baz'],
+      [true, false, undefined]
+    ].map _.at()
+
+    expect(res).toEqual([10, 'foo', true])
+
+  it 'should return undefined for null entries', ->
+    res = [
+      [10, 20, 30],
+      null
+    ].map _.at 0
+
+    expect(res).toEqual([10, undefined])
+
 describe 'length', ->
   it 'should map the length of the element', ->

@@ -109,3 +153,12 @@
     ].map _.length()

     expect(res).toEqual([3,2,1])
+
+  it 'should return undefined for null entries', ->
+    res = [
+      [10, 20, 30],
+      null
+    ].map _.length()
+
+    expect(res).toEqual([3, undefined])
+


oauth: session schema cleanup, add fields

--- workers/social/lib/social/models/session.coffee
+++ workers/social/lib/social/models/session.coffee
@@ -24,8 +24,6 @@
       username      : String
       guestId       : Number
       terminalId    : String
-      oAuthCodes    :
-        github      : String
       sessionBegan  :
         type        : Date
         default     : -> new Date
@@ -40,7 +38,12 @@
           firstName : String
           lastName  : String
           email     : String
-        odesk       : Object
+        odesk                :
+          accessToken        : String
+          accessTokenSecret  : String
+          foreignId          : String
+          requestToken       : String
+          requestTokenSecret : String
     sharedEvents    :
       instance      : [
         { name: 'updateInstance' }


SocialWorker: add guestSessionBegan field to guest sessions

--- workers/social/lib/social/models/session.coffee
+++ workers/social/lib/social/models/session.coffee
@@ -5,38 +5,39 @@
   { v4: createId } = require 'node-uuid'

   @set
-    indexes         :
-      clientId      : 'unique'
-      otaToken      : 'sparse' # unique is also required
-      username      : 'descending'
-      clientIP      : 'sparse'
-    schema          :
-      clientId      : String
-      clientIP      : String
-      username      : String
-      otaToken      : String
-      groupName     :
-        type        : String
-        default     : -> 'koding'
-      guestId       : Number
-      terminalId    : String
-      sessionBegan  :
-        type        : Date
-        default     : -> new Date
-      lastAccess    :
-        type        : Date
-        get         : -> new Date
-      foreignAuth   :
-        github      : Object
-        odesk       : Object
-        facebook    : Object
-        linkedin    : Object
-      returnUrl     : String
-      foreignAuthType : String
-      impersonating : Boolean
-    sharedEvents    :
-      instance      : []
-      static        : []
+    indexes             :
+      clientId          : 'unique'
+      otaToken          : 'sparse' # unique is also required
+      username          : 'descending'
+      clientIP          : 'sparse'
+    schema              :
+      clientId          : String
+      clientIP          : String
+      username          : String
+      otaToken          : String
+      groupName         :
+        type            : String
+        default         : -> 'koding'
+      guestId           : Number
+      terminalId        : String
+      sessionBegan      :
+        type            : Date
+        default         : -> new Date
+      guestSessionBegan : Date
+      lastAccess        :
+        type            : Date
+        get             : -> new Date
+      foreignAuth       :
+        github          : Object
+        odesk           : Object
+        facebook        : Object
+        linkedin        : Object
+      returnUrl         : String
+      foreignAuthType   : String
+      impersonating     : Boolean
+    sharedEvents        :
+      instance          : []
+      static            : []

   do ->
     JAccount  = require './account'
@@ -65,8 +66,13 @@
         return @emit 'error', { message }

       { account } = resp
-      username  = JUser.createGuestUsername()
-      session   = new JSession { clientId, username }
+
+      sessionOptions =
+        clientId          : clientId
+        username          : JUser.createGuestUsername()
+        guestSessionBegan : new Date()
+
+      session = new JSession sessionOptions

       session.save (err) ->
         if err then callback err


remove autoplay fn from wallop slider

remove autoplay fn from wallop slider

it was just for demo and is not good for production (very poor performance

--- assets/coffeescript/wallop_config.coffee
+++ assets/coffeescript/wallop_config.coffee
@@ -40,24 +40,21 @@
     $('.Wallop-txt').removeClass('Wallop-txt--current').addClass('Wallop-txt--hidden')
     $(text_points[event.detail.currentItemIndex]).removeClass('Wallop-txt--hidden').addClass('Wallop-txt--current')

-  autoplay = (interval)=>
-    last_time = 0
+  # autoplay = (interval)=>
+  #   last_time = 0

-    frame = (timestamp)=>
-      console.log "called frame"
-      update = timestamp - last_time >= interval
-      if update
-        console.log update
-        slider.next()
-        last_time = timestamp
+  #   frame = (timestamp)=>
+  #     update = timestamp - last_time >= interval
+  #     if update
+  #       slider.next()
+  #       last_time = timestamp

-      requestAnimationFrame(frame)
-    requestAnimationFrame(frame)
+  #     requestAnimationFrame(frame)
+  #   requestAnimationFrame(frame)

-  if wallop_element.dataset.autoplay
-    console.log "autoplay"
-    # something
-    autoplay(500)
+  # if wallop_element.dataset.autoplay
+  #   # something
+  #   autoplay(500)

 $(document).ready ->
   if $("#Pupil").length > 0


added better error handling

added better error handling

as suggested by @EamonNerbonne

--- core/app/backbone/models/wheel.coffee
+++ core/app/backbone/models/wheel.coffee
@@ -79,6 +79,7 @@
     @updateTo @get("authority"), new_opinion_types

   setActiveOpinionType: (opinion_type, options={}) ->
+    old_opinion_type = @userOpinion()
     fact_id = @get('fact_id')
     @turnOnActiveOpinionType opinion_type
     $.ajax
@@ -93,7 +94,10 @@
       error: =>
         # TODO: This is not a proper undo. Should be restored to the current
         #       state when the request fails.
-        @turnOffActiveOpinionType()
+        if old_opinion_type
+          @turnOnActiveOpinionType old_opinion_type
+        else
+          @turnOffActiveOpinionType()
         options.error?()

   unsetActiveOpinionType: (opinion_type, options={}) ->


Improved naming

Improved naming

--- core/app/backbone/models/wheel.coffee
+++ core/app/backbone/models/wheel.coffee
@@ -21,13 +21,13 @@
     @set 'opinion_types', @mergedOpinionTypes()

   setRecursive: (attributes) ->
-    @_updateAttributes @attributes, attributes
+    @_recursivelyUpdateAttributes @attributes, attributes

-  _updateAttributes: (oldAttributes, newAttributes) ->
+  _recursivelyUpdateAttributes: (oldAttributes, newAttributes) ->
     for key, value of newAttributes
       if typeof value is 'object'
         oldAttributes[key] ?= {}
-        @_updateAttributes oldAttributes[key], value
+        @_recursivelyUpdateAttributes oldAttributes[key], value
       else
         oldAttributes[key] = value


Search completion; generalize Google regexp.

--- background_scripts/search_engines.coffee
+++ background_scripts/search_engines.coffee
@@ -7,11 +7,17 @@
 #
 class Google
   constructor: ->
+    @regexps = [
+      # We include matches for the major English-speaking TLDs.
+      new RegExp "^https?://[a-z]+\.google\.(com|ie|co.uk|ca|com.au)/"
+      # NOTE(smblott). A  temporary hack, just for me, and just for development. Will be removed.
+      new RegExp "localhost/.*/booky"
+      ]
+
   name: "Google"
   match: (searchUrl) ->
-    return true if /^https?:\/\/[a-z]+.google.com\//.test searchUrl
-    # NOTE(smblott). A  temporary hack, just for me, and just for development. Will be removed.
-    return true if /localhost\/.*\/booky/.test searchUrl
+    for re in @regexps
+      return true if re.test searchUrl
     false

   getUrl: (queryTerms) ->


LazyRender replace _.debounce

LazyRender replace _.debounce

--- coffee/backbone/views/clone-control-view.coffee
+++ coffee/backbone/views/clone-control-view.coffee
@@ -26,7 +26,6 @@
             })

         @mode = ""
-        @lazy_render_set_timeout = null

     getControl:() => @itemControl
     setCanvas:(canvas) =>
@@ -100,13 +99,6 @@
         )
         @line_list_view.render()

-    lazyRender:()=>
-        clearTimeout(@lazy_render_set_timeout) if @lazy_render_set_timeout
-        @lazy_render_set_timeout = setTimeout((() =>
-            @render()
-            @lazy_render_set_timeout = null
-        ), 50)
-
     initControls:(models) =>
         @clear()
         for model in models
@@ -160,6 +152,8 @@
         @itemControl.show()
         @itemControl.render()
         @line_list_view.render()
+
+    @::lazyRender = _.debounce(@::render, 50)

     _setChildControlEvent:(view) =>
         view.bind("onClick", (obj, e) =>


Allow formatting hover details via options

--- app/assets/javascripts/datagraph.js.coffee
+++ app/assets/javascripts/datagraph.js.coffee
@@ -36,6 +36,9 @@
     detailSmoothing: 10
     overviewSmoothing: 10
     yAxisTickFormat: (y) -> y
+    hoverDetailYFormat: (y) -> y.toFixed(2)
+    hoverDetailLabelFormat: (series, x, y, formattedX, formattedY, d) ->
+      "#{series.name}: #{formattedY}"

   constructor: (element, @url, options) ->
     @element = $(element)
@@ -133,6 +136,8 @@

     detail = new Rickshaw.Graph.HoverDetail
       graph: @detailGraph
+      yFormatter: @options.hoverDetailYFormat
+      formatter: @options.hoverDetailLabelFormat

     smoother = new Rickshaw.Graph.Smoother
       graph: @detailGraph


Use namespaced events in IframeSaver

Use namespaced events in IframeSaver

[#89024392]

--- app/assets/javascripts/iframe-saver.coffee
+++ app/assets/javascripts/iframe-saver.coffee
@@ -143,17 +143,20 @@
         @error("couldn't load interactive")

   set_autosave_enabled: (v) ->
-    # Focus event is attached to the window, so it has to have unique namespace.
-    focus_namespace = "focus.iframe_saver_#{@$iframe.data('id')}"
-    #Save interactive every 5 seconds, on window focus and iframe mouseout just to be safe.
+    # Save interactive every 5 seconds, on window focus and iframe mouseout just to be safe.
+    # Focus event is attached to the window, so it has to have unique namespace. Mouseout is attached to the iframe
+    # itself, but other code can use that event too (e.g. logging).
+    namespace = "focus.iframe_saver_#{@$iframe.data('id')}"
+    focus_namespace = "focus.#{namespace}"
+    mouseout_namespace = "mouseout.#{namespace}"
     if v
       @autosave_interval_id = setInterval (=> @save()), 5 * 1000
       $(window).on focus_namespace, => @save()
-      @$iframe.on 'mouseout', => @save()
+      @$iframe.on mouseout_namespace, => @save()
     else
       clearInterval(@autosave_interval_id) if @autosave_interval_id
       $(window).off focus_namespace
-      @$iframe.off 'mouseout'
+      @$iframe.off mouseout_namespace

 # Export constructor.


Only set credentials object in prod.

Only set credentials object in prod.

--- server.coffee
+++ server.coffee
@@ -113,12 +113,11 @@
     logger.error err
     res.sendStatus 500

-credentials =
-  key: fs.readFileSync path.join certsPath, 'privkey.pem'
-  cert: fs.readFileSync path.join certsPath, 'fullchain.pem'
-  ca: fs.readFileSync path.join certsPath, 'chain.pem'
-
 if PROD
+  credentials =
+    key: fs.readFileSync path.join certsPath, 'privkey.pem'
+    cert: fs.readFileSync path.join certsPath, 'fullchain.pem'
+    ca: fs.readFileSync path.join certsPath, 'chain.pem'
   https.createServer(credentials, server).listen 8443, ->
     logger.info 'HTTPS server listening on port 8443...'


Add number of players to data

Add number of players to data

--- server.coffee
+++ server.coffee
@@ -730,8 +730,10 @@
 moveData = ->
 	if not (Db.backend.peek('history', 'groupCode')?)
 		Db.backend.set 'history', 'groupCode', Plugin.groupCode()
+	if not (Db.backend.peek('history', 'players')?)
+		Db.backend.set 'history', 'players', Plugin.userIds().length
 	current = Db.shared.peek('gameNumber')
-	if current?
+	if current? and parseInt(Db.shared.peek('game', 'gameState')) != 0
 		Db.backend.set 'history', current,'game', Db.shared.peek('game')
 		Db.backend.set 'history', current, 'gameState', Db.shared.peek('gameState')


ETIMEDOUT treated as connection error as well...

--- server.coffee
+++ server.coffee
@@ -19,7 +19,7 @@
 	'BADLOGIN': [401, 'BADLOGIN', 'Bad username/password!']
 	'INVALID_TOKEN': [401, 'INVALID_TOKEN', 'Invalid session!\nPlease login again.']
 }
-mappedErrors.ECONNRESET = mappedErrors.ENOTFOUND = [502, 'CONNERR', 'Connection Error!\nSVU servers are probably having some problem, Please try again in a few minutes...']
+mappedErrors.ETIMEDOUT = mappedErrors.ECONNRESET = mappedErrors.ENOTFOUND = [502, 'CONNERR', 'Connection Error!\nSVU servers are probably having some problem, Please try again in a few minutes...']
 app.use (err, req, res, next)->
 	console.log err, err.code, req.url, req.body
 	console.log err.stack


Server cleanup

Server cleanup

--- server.coffee
+++ server.coffee
@@ -15,7 +15,7 @@
 # 		newRound()

 exports.client_answer = (id, a) !->
-	log "", Plugin.userId(), "answered:", a
+	log Plugin.userId(), "answered:", a
 	Db.shared.merge 'rounds', id, 'answers', Plugin.userId(), a

 	# Count number of people who answered. If it is everyone, we can resolve the question. (Handy for local party mode)
@@ -93,17 +93,12 @@
 		log "Question already resolved"
 		# return
 	answers = question.get('answers')||[]
-
-	getVotes = (uId) !->
-		votes = 0
-		for k,v of answers
-			if +v is +uId then ++votes
-		return votes
+	solution = Util.getSolution(roundId)

 	for user in Plugin.userIds()
-		input = (answers[user]||[]).slice(0)
+		input = (answers[user]||[]).slice(0) # clone array
 		continue unless input.length
-		target = Util.getSolution(roundId)
+		target = solution.slice(0) # clone array
 		# calc score using the V⁴ Method (van Viegen, van Vliet)
 		# there are a maximum of 'steps' needed to bring any answer to the correct order. So the point range from 0 to 6
 		errors = 0
@@ -112,7 +107,7 @@
 			input.splice(0,1)
 			target.splice(index,1)
 		score = 6-errors
-		log "user #{user} answer:", answers[user], "solution:", Util.getSolution(roundId), "errors:", errors, "score:", score
+		log "user #{user} answer:", answers[user], "solution:", solution, "errors:", errors, "score:", score

 		# for each other user
 		for user2 in Plugin.userIds()
@@ -132,7 +127,6 @@
 		for k,v of votes
 				if typeof(v) is 'number'
 					result+=v
-					log "incr", user, k, v
 			if result then question.set 'results', user, result
 			# safe to db
 			Db.shared.incr 'scores', user, result # global


Fixed an issue with non-existant round lengths in new plugins

--- server.coffee
+++ server.coffee
@@ -21,8 +21,8 @@
 	if config?
 		# Do things
 		if not Db.shared.get 'rounds'
+			Db.shared.set 'roundlength', 6
 			exports.startgame()
-			Db.shared.set 'roundlength', 6

 		if config.roundlength and !isNaN +config.roundlength
 			Db.shared.set 'roundlength', +config.roundlength
@@ -39,10 +39,6 @@
 	request.respond 200, JSON.stringify db

 exports.onUpgrade = !->
-	log 'Upgraded the Plugin'
-	log 'This plugin"s public URL:', Plugin.inboundUrl()
-	log 'Plugin Userids:', Plugin.userIds()
-
 	if not Db.shared.get 'roundlength'
 		Db.shared.set 'roundlength', 6

@@ -414,7 +410,7 @@
 	Timer.cancel()
 	Db.shared.set 'questiondeck', [0..Black.numcards() - 1]
 	Db.shared.set 'answerdeck', [0..White.numcards() - 1]
-	Db.shared.set 'round', 0
+	Db.shared.set 'lastround', 0
 	Db.shared.set 'score', {}
 	Db.shared.set 'rounds', {}
 	Db.shared.set 'paused', false
@@ -488,7 +484,9 @@
 	Timer.cancel()

 	round = Db.shared.ref 'rounds', roundId
-	if not round then return
+	if not round
+		log 'Invalid round:', roundId
+		return

 	if round.get('phase') isnt 'draw' then return


Check if model exists

Check if model exists

--- scripts/views/workspace/menu/auth.coffee
+++ scripts/views/workspace/menu/auth.coffee
@@ -35,7 +35,7 @@
       @listenTo(content, 'add remove', @changed)
       @listenTo content, 'change', (model, options) =>
         # A change event can occur (ie setting a title during parsing but the changed set is still empty)
-        @changed() if model.hasChanged()
+        @changed() if model?.hasChanged()

       # Bind a function to the window if the user tries to navigate away from this page
       $(window).on 'beforeunload', () ->


- currently refactoring projectBrowser: still work in progress  * renames : projectBrowser2 -> projectBrowser, new project browser -> projectBrowser2  * deleted unused projectBrowser and projectBrowser3  * thumbnails are now fetched and displayed for browser store as well , thus fixes #93  * zip export of browser store is functionnal in the new project browser : closes #121 (not visible for now, but can be set by "requiring" projectBrowser2 in the projectManager.coffee file)  * various (in progress) UI improvements for the project browser (see above to enable)

- updated backbone modelbinder and jquery slimscroll libs
- google plus community icon now loaded locally

--- dropBoxStore.coffee
+++ dropBoxStore.coffee
@@ -167,42 +167,17 @@
       deferred = @store._readFile( "/#{projectName}/.thumbnail.png",{arrayBuffer:true})

       parseBase64Png=( rawData)->
-        #console.log rawData
-
-        ab2str=(buf)->
-          return String.fromCharCode.apply(null, new Uint16Array(buf))
-        str2ab=(str)->
-          buf = new ArrayBuffer(str.length*2); # 2 bytes for each char
-          bufView = new Uint16Array(buf)
-          for i in [0...str.length]
-            bufView[i] = str.charCodeAt(i)
-          return buf
-
-
-        data = btoa(String.fromCharCode.apply(null, new Uint8Array(rawData)))
+        #convert binary png to base64
+        bytes = new Uint8Array(rawData)
+        data = ''
+        for i in [0...bytes.length]
+          data += String.fromCharCode(bytes[i])
+        data =   btoa(data)
+        #crashes
+        #data = btoa(String.fromCharCode.apply(null, ))
         base64src='data:image/png;base64,'+data
         myDeferred.resolve(base64src)
-
-        ###
-        data = ''
-        for i in [0...rawData.length]
-          data += String.fromCharCode( ( rawData[ i ].charCodeAt(0) & 0xff ) )
-
-        # Convert raw data to base64
-        data = btoa( data )
-        base64src='data:image/png;base64,'+data  # change the MIME type according to content
-        myDeferred.resolve(base64src)
-        ###
-
-
-        #blob = new Blob([rawData], {type: 'image/png'})
-        #console.log blob
-        #bytes = new Uint8Array( blob )
-        #console.log bytes
-        #data = btoa( bytes )
-        #base64src='data:image/png;base64,'+data
-        #myDeferred.resolve(base64src)
-
+
       deferred.done(parseBase64Png)
       return myDeferred


Add first line of drones (2nd stage), speed up boat and easInOut boat.

--- src/lazerscripts/stage1/ship_boss.coffee
+++ src/lazerscripts/stage1/ship_boss.coffee
@@ -1,7 +1,7 @@
 { EntityScript } = require('src/lib/LazerScript')
 MineCannon = require('./mine_cannon').default
 { TurretInActive, TurretActive } = require('./turret')
-{ Swirler, Shooter, CrewShooters, Stalker, ScraperFlyer } = require('../stage1/army_drone')
+{ Swirler, Shooter, CrewShooters, Stalker, ScraperFlyer, DroneFlyer } = require('../stage1/army_drone')

 class Cabin1Inactive extends EntityScript
   spawn: (options) ->
@@ -67,10 +67,28 @@
     Crafty.e('BattleShip').attr(
       x: Crafty.viewport.width + 180
       y: 400
-      defaultSpeed: options.speed ? 45
+      defaultSpeed: options.speed ? 85
     )
     # .setSealevel(@level.visibleHeight - 10)

+  getPath: (pattern) ->
+    console.log('pattern', pattern)
+    return [
+      [.156, .5]
+      [.5, .833]
+      [.86, .52]
+
+      [.5, .21]
+      [.156, .5]
+      [.5, .833]
+      [.86, .52]
+
+      [-20, .21]
+    ]
+
+  randomAmountSpawnedDrones: ->
+    console.log(Math.round(Math.random() * 5))
+    return Math.round(Math.random() * 5)

   placeEnemiesOnShip: ->
     @sequence(
@@ -91,28 +109,27 @@

   executeStageOne: ->
     @parallel(
-      @moveTo(x: 0.8)
+      @moveTo(x: 0.8, easing: "easeInOutQuad")
       @placeSquad MineCannon,
         options:
           attach: 'MineCannonPlace'
     )

-  releaseDronesFromHatchOne: ->
+  releaseDronesFromHatchOne: (dronePattern) ->
     @sequence(
       @action 'open1'
       @wait(500)
-
       @parallel(
-        @placeSquad Shooter,
+        @placeSquad DroneFlyer,
           amount: 5,
-          delay: 200
-          options: {
+          delay: 500
+          options:
             startAt: 'ShipHatch1'
             hatchReveal: 'ShipHatch1'
             dx: 25
             dy: 20
-          }
-
+            debug: true,
+            path: @getPath(dronePattern)
         @sequence(
           @wait(1200)
           @action 'close1'
@@ -120,17 +137,50 @@
       )
     )

+  releaseDronesFromHatchTwo: (dronePattern) ->
+    @sequence(
+      @action 'open2'
+      @wait(500)
+      @parallel(
+        @placeSquad DroneFlyer,
+          amount: 8,
+          delay: 500
+          options:
+            startAt: 'ShipHatch1'
+            hatchReveal: 'ShipHatch1'
+            dx: 25
+            dy: 20
+            debug: true,
+            path: @getPath(dronePattern)
+        @sequence(
+          @wait(1200)
+          @action 'close2'
+        )
+      )
+    )
+
+
   executeStageTwo: ->
     @sequence(
-      @moveTo(x: -0.1)
+      @moveTo(x: -0.1, easing: "easeInOutQuad")
       @while(
         @placeSquad TurretActive,
           amount: 1
           delay: 0
           options:
             attach: 'TurretPlace'
-        @releaseDronesFromHatchOne()
+        @releaseDronesFromHatchOne(Math.round(Math.random() * 3 + 1))
       )
+    )
+
+  executeStageThree: =>
+    @sequence(
+      @placeSquad TurretActive,
+        amount: 1
+        delay: 0
+        options:
+          attach: 'TurretPlace'
+      @releaseDronesFromHatchTwo()
     )

   execute: ->
@@ -139,6 +189,7 @@
       @placeEnemiesOnShip()
       @executeStageOne()
       @executeStageTwo()
+      @executeStageThree()
     )

         # @sequence(


Added more snark to the pjcbot help response.

--- scripts/pjc.coffee
+++ scripts/pjc.coffee
@@ -1,8 +1,5 @@
 module.exports = (robot) ->
-  lulz = ['lol', 'rofl', 'lmao']
-
-  robot.respond /lulz/i, (res) ->
-    res.send res.random lulz
+  lulz = ['lol', 'rofl', 'lmao', 'haha nice try', '**snort**', 'you wish']

   robot.hear /^hubot:? (.+)/i, (res) ->
     response = "Sorry, I'm a diva and only respond to #{robot.name}"


Don't show the tagger suggestion box for nonprintable keycodes.

It used to be the case that pressing e.g. modifier keys would cause the
suggestion box to show up.  That gets confusing.

--- app/assets/javascripts/views/tagger.coffee
+++ app/assets/javascripts/views/tagger.coffee
@@ -55,7 +55,8 @@
           pv.willRemoveLastSelection()
           e.preventDefault()
       else
-        @set 'wantsCandidates', true
+        if @get('hasText')
+          @set 'wantsCandidates', true

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


Remove unused variable.

Remove unused variable.

--- test/unit/tnmSpec.coffee
+++ test/unit/tnmSpec.coffee
@@ -1,4 +1,4 @@
-define ['ngmocks', 'tnm', 'helpers'], (tnm) ->
+define ['ngmocks', 'tnm', 'helpers'], ->
   describe 'Unit Testing the TNM Service', ->
     # The qiprofile TNM factory.
     TNM = null


wait execute: rename label

wait execute: rename label

--- test/wait/execute.coffee
+++ test/wait/execute.coffee
@@ -4,7 +4,7 @@
 they = require 'ssh2-they'
 fs = require 'ssh2-fs'

-describe 'wait_execute', ->
+describe 'wait execute', ->

   scratch = test.scratch @


Initial work on grid-lists

Initial work on grid-lists

--- frontend/coffeescript/app/webform/views.coffee
+++ frontend/coffeescript/app/webform/views.coffee
@@ -169,14 +169,33 @@
             # Field-list controls
             if @input_fields[question].control.appearance is "field-list"
               current_tree = @input_fields[question].tree
-              console.log(@input_fields[question])
               $('#' + @input_fields[question].name + '_field').addClass('active')
-              console.log("Here")
               question++
               question_info = @input_fields[question]

               while question_info.tree is current_tree
                 switch_question = $('#' + $($('.control-group').eq(question)[0]).data('key') + "_field")
+                switch_question.fadeIn(1).addClass('active')
+                $('.active input').focus()
+                question++
+                question_info = @input_fields[question]
+
+            # Grid-list controls
+            else if @input_fields[question].control.appearance is "grid-list"
+              current_tree = @input_fields[question].tree
+              $('#' + @input_fields[question].name + '_field').addClass('active')
+              question++
+              question_info = @input_fields[question]
+
+              while question_info.tree is current_tree
+                switch_question = $('#' + $($('.control-group').eq(question)[0]).data('key') + "_field")
+
+                attrs = { }
+                $.each($('#' + switch_question[0].id + ' select')[0].attributes, (idx, attr) ->
+                  attrs[attr.nodeName] = attr.nodeValue
+                )
+                $('#' + switch_question[0].id + ' select').replaceWith( () ->
+                  return $("<input type='radio' />", attrs).append($(@).contents()) )
                 switch_question.fadeIn(1).addClass('active')
                 $('.active input').focus()
                 question++


Fix required, constraint checks

Fix required, constraint checks

--- frontend/coffeescript/app/webform/views.coffee
+++ frontend/coffeescript/app/webform/views.coffee
@@ -135,6 +135,10 @@

         submit: ->
             $( ".form" ).submit()
+
+
+        # Returns a dictionary of all the form values
+        get_form_values: -> @queryStringToJSON $(".form").serialize()

         # _geopointDisplay = ->
         #   onMapClick = (e) ->
@@ -381,6 +385,20 @@

         passes_question_constraints: ->
           question = document.flat_fields[@currentQuestionIndex]
+          answers = @get_form_values()
+
+          # If question type is group, ensure all the children pass constraints.
+          if question.type is 'group'
+            for __, child of question.children
+              if not XFormConstraintChecker.passesConstraint child, answers
+                msg = "Answer doesn't pass constraint: #{child.bind['jr:constraintMsg'] or child.bind.constraint}"
+                alert msg
+                return false
+
+            if child.bind?.required is "yes"
+              if @get_question_value(child).length is 0
+                alert "Answer is required."
+                return false

           # Check if the question is required and whether or not a value is
           # present. If not, raise an alert.
@@ -391,8 +409,8 @@

           # Ensure that the question passes any constraints, if given. If
           # not, raise an alert.
-          if not XFormConstraintChecker.passesConstraint( question, @get_question_value(question) )
-            alert "Answer doesn't pass constraint: #{question.info.bind.constraint}"
+          if not XFormConstraintChecker.passesConstraint question, answers
+            alert "Answer doesn't pass constraint: #{child.bind['jr:constraintMsg'] or child.bind.constraint}"
             return false

           return true
@@ -500,7 +518,7 @@
           # it's relevant; it could be irrelevant based on preceding values, or
           # a calculation.
           if (next_question.bind?.calculate?) or not
-            XFormConstraintChecker.isRelevant( next_question, @queryStringToJSON( $( ".form" ).serialize()) )
+            XFormConstraintChecker.isRelevant next_question, @get_form_values()

               # If its a calculation, calculate it!
               $("#" + next_question.name).val _performCalcluate(next_question.bind.calculate)  if next_question.bind?.calculate?


Re-added layout=client for correct sign in page

--- core/app/assets/javascripts/intermediate.coffee
+++ core/app/assets/javascripts/intermediate.coffee
@@ -20,7 +20,8 @@
               "?fact=" + encodeURIComponent(text) +
               "&url=" + encodeURIComponent(siteUrl) +
               "&title=" + encodeURIComponent(siteTitle) +
-              "&guided=" + encodeURIComponent(guided)
+              "&guided=" + encodeURIComponent(guided) +
+              "&layout=client" # layout=client is still necessary to get the client sign in page
       showUrl url, successFn

       onFactlinkCreated = (e, id) ->


:art:

:art:

--- src/main-process/atom-application.coffee
+++ src/main-process/atom-application.coffee
@@ -83,7 +83,7 @@
   initialize: (options) ->
     global.atomApplication = this

-    @config.onDidChange 'core.useCustomTitleBar', @promptForRelaunch
+    @config.onDidChange 'core.useCustomTitleBar', @promptForRestart

     @autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath, @config)
     @applicationMenu = new ApplicationMenu(@version, @autoUpdateManager)
@@ -734,7 +734,7 @@

     dialog.showOpenDialog(parentWindow, openOptions, callback)

-  promptForRelaunch: ->
+  promptForRestart: ->
     chosen = dialog.showMessageBox BrowserWindow.getFocusedWindow(),
       type: 'warning'
       title: 'Restart required'


Fix replacing to compile so autocompletion works

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1983,6 +1983,7 @@
         nameCompile ctx, atom, symbol
   atom.tea = type if type
   atom.id = id if id?
+  atom.scope = ctx.currentScopeIndex()
   if ctx.isOperator()
     # TODO: maybe don't use label here, it's getting confusing what is its purpose
     (labelOperator atom)


Add support for anchors in the browser impl

--- impls/browser/index.coffee
+++ impls/browser/index.coffee
@@ -7,15 +7,38 @@

 exports.init = ->

-	uri = location.pathname
+	# Send internal request to change the page based on the URI
+	changePage = (uri) =>

-	uid = utils.uid()
+		# change browser URI in the history
+		history.pushState null, '', uri

-	res = @onRequest
-		uid: uid
-		method: @constructor.GET
-		uri: uri.slice 1
-		data: null
+		# send internal request
+		uid = utils.uid()
+
+		res = @onRequest
+			uid: uid
+			method: @constructor.GET
+			uri: uri.slice 1
+			data: null
+
+	# don't refresh page on click anchor
+	document.addEventListener 'click', (e) ->
+
+		{target} = e
+
+		# consider only anchors
+		# omit anchors with the `target` attribute
+		return if target.nodeName isnt 'A' or target.getAttribute('target')
+
+		# avoid browser to refresh page
+		e.preventDefault()
+
+		# change page to the anchor pathname
+		changePage target.pathname
+
+	# change page to the current one
+	changePage location.pathname

 exports.sendRequest = (opts, callback) ->


Moving by lines and fixed moving holes

Moving by lines and fixed moving holes

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -7359,6 +7359,7 @@
 exports.filter = filter
 exports.partition = partition
 exports._notEmpty = _notEmpty
+exports._empty = _empty
 exports._is = _is
 exports.__ = __


Fix backtraces containing r.row in JavaScript

Closes #2096
Review 1331 by @neumino

--- drivers/javascript/errors.coffee
+++ drivers/javascript/errors.coffee
@@ -57,13 +57,18 @@
         else
             carrotify(term.compose(args, optargs))

-    carrotify = (tree) -> (joinTree tree).replace(/./g, '^')
+    carrotMarker = {}
+
+    carrotify = (tree) -> [carrotMarker, tree]

     joinTree = (tree) ->
         str = ''
         for term in tree
             if Array.isArray term
-                str += joinTree term
+                if term.length == 2 and term[0] is carrotMarker
+                        str += (joinTree term[1]).replace(/./g, '^')
+                else
+                        str += joinTree term
             else
                 str += term
         return str


Use capitalized nicknames for guests as firstName and lastName

--- client/landing/app/Applications/Members.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Members.kdapplication/AppView.coffee
@@ -114,8 +114,13 @@
     super options, data

   createCommons:(account)->
+
+    if account.type is 'unregistered'
+    then name = account.profile.nickname.capitalize()
+    else name = "#{account.profile.firstName} #{account.profile.lastName}"
+
     contentDisplayController = KD.getSingleton "contentDisplayController"
-    headerTitle              = "Activities which #{account.profile.firstName} #{account.profile.lastName} liked"
+    headerTitle              = "Activities which #{name} liked"

     @addSubView header = new HeaderViewSection
       type  : "big"
@@ -144,10 +149,16 @@
     super options, data

   createCommons:(account, filter)->
-    headerTitle = if filter is "following" then "Members who #{account.profile.firstName} #{account.profile.lastName} follows" else "Members who follow #{account.profile.firstName} #{account.profile.lastName}"
-    @addSubView header = new HeaderViewSection
-      type  : "big"
-      title : headerTitle
+
+    if account.type is 'unregistered'
+    then name = account.profile.nickname.capitalize()
+    else name = "#{account.profile.firstName} #{account.profile.lastName}"
+
+    if filter is "following"
+    then title = "Members who #{name} follows"
+    else title = "Members who follow #{name}"
+
+    @addSubView header = new HeaderViewSection {type : "big", title}

     @addSubView subHeader = new KDCustomHTMLView
       tagName  : "h2"


fix url decoding

fix url decoding

--- app/app.coffee
+++ app/app.coffee
@@ -203,7 +203,7 @@
 Base64EncodedNotebookCellComponent = React.createClass
   displayName: 'Base64EncodedNotebookCellComponent'
   render: ->
-    value = unescape(decodeURIComponent(atob(@props.params.splat)))
+    value = decodeURIComponent(escape(atob(@props.params.splat)))
     SingleCoffeeScriptCellNotebookComponent {value}

 SingleCoffeeScriptCellNotebookComponent = React.createClass
@@ -317,7 +317,7 @@

 encodeNotebookValue = (value) ->
-  btoa(encodeURIComponent(escape(value)))
+  btoa(unescape(encodeURIComponent(value)))

 exports.raw_cell_url = (ctx, value) ->
   # TODO don't require appComponent


Add the correct Content-Type header to our JSON responses.

--- server.coffee
+++ server.coffee
@@ -5,6 +5,7 @@
 routeMatcher = new vertx.RouteMatcher

 routeMatcher.get '/routes', (req) ->
+  req.response.putHeader 'Content-Type', 'application/json; charset=utf8'
   req.response.setChunked true
   eb.send 'reitti.geocode', query: req.params().from, (from) ->
     eb.send 'reitti.geocode', query: req.params().to, (to) ->
@@ -23,6 +24,7 @@
         req.response.end JSON.stringify(from: !!from, to: !!to)

 routeMatcher.get '/address', (req) ->
+  req.response.putHeader 'Content-Type', 'application/json; charset=utf8'
   eb.send 'reitti.hsl.reverseGeocode', query: req.params().coords, (address) ->
     if address
       req.response.end JSON.stringify(address)
@@ -31,6 +33,7 @@
       req.response.end()

 routeMatcher.get '/autocomplete', (req) ->
+  req.response.putHeader 'Content-Type', 'application/json; charset=utf8'
   eb.send 'reitti.searchIndex.find', query: req.params().query, (data) ->
     req.response.end JSON.stringify(itm.name for itm in data.results)


don't log size of already minified files: *.min.js

--- server.coffee
+++ server.coffee
@@ -86,7 +86,7 @@
         sizes.push ob.length
         try fs.writeFileSync f1, ob catch # ignore errors
         s = s1
-      log "  #{f} #{sizes.join '→'} bytes"
+      if sizes.length > 1 then log "  #{f} #{sizes.join '→'} bytes"
     else
       s = fs.readFileSync f1, 'utf8'
     js += s + '\n'


remove jobs temporary

remove jobs temporary

--- server.coffee
+++ server.coffee
@@ -38,12 +38,12 @@

     # set-up CRON
-    createCheckJobs()
+    #createCheckJobs()

-    jobs.promote()
+    #jobs.promote()

-    # KUE jobs
-    jobs.process "check mailbox", 1, (job, done) ->
-      console.log job.data.title + " #" + job.id + " job started"
-      (Mailbox job.data.mailbox).getNewMail job.data.num, done
+    ## KUE jobs
+    #jobs.process "check mailbox", 1, (job, done) ->
+      #console.log job.data.title + " #" + job.id + " job started"
+      #(Mailbox job.data.mailbox).getNewMail job.data.num, done


Fix "Missing or invalid User Agent string." error.

--- tasks/update-atom-shell-task.coffee
+++ tasks/update-atom-shell-task.coffee
@@ -30,6 +30,7 @@
         headers:
           authorization: "token #{accessToken}"
           accept: 'application/vnd.github.manifold-preview'
+          'user-agent': 'Atom'

       request options, (error, response, body) ->
         if not error?
@@ -87,6 +88,7 @@
         options.headers =
           authorization: "token #{accessToken}"
           accept: 'application/octet-stream'
+          'user-agent': 'Atom'

         callback(error, options)
     else


bottompanel controller preps

bottompanel controller preps

--- client/app/MainApp/bottompanel/bottompanelcontroller.coffee
+++ client/app/MainApp/bottompanel/bottompanelcontroller.coffee
@@ -1,8 +1,7 @@
-class BottomPanelController extends KDViewController
+class BottomPanelController extends KDController

   loadView:(bottomPanel)->

-    @isVisible   = no
     @bottomPanel = bottomPanel
     @wc          = @getSingleton("windowController")

@@ -24,7 +23,16 @@
     @split.on "panelSplitted", (panel)->
       panel.addSubView new KDView

-  toggle:-> if @isVisible then @hide() else @show()
+  togglePanel:(name)->
+
+  createPanel:(name)->
+
+  destroyPanel:(name)->
+
+  showPanel:(name)->
+
+  hidePanel:(name)->
+

 class BottomChatSideBar extends JView


Prevent default and stop propagation on block integration view

--- components/connect/integration/block_collection/view.coffee
+++ components/connect/integration/block_collection/view.coffee
@@ -7,8 +7,13 @@
   className: 'BlockCollectionConnectIntegration'

   events:
-    'click': (e) -> e.stopPropagation() # Prevent clicks from propagating down to the Block link
+    'click': 'preventDefault'
     'click .js-close': 'remove'
+
+  preventDefault: (e) ->
+    # Prevent clicks from propagating down to the Block link
+    e.stopPropagation()
+    e.preventDefault()

   render: ->
     invoke @subViews, 'remove'


getting rid of "lookupAndSendFoundMessage" because it was doing nothing

--- fizzylogo_Objects.coffee
+++ fizzylogo_Objects.coffee
@@ -166,7 +166,9 @@
             theContext.unparsedMessage = null
             console.log "theContext method invocation after: " + methodInvocation.print()

-            return [(@lookupAndSendFoundMessage newContext, eachSignatureIndex),methodInvocation]
+            contextToBeReturned = @methodCall (@flClass.methodBodies[eachSignatureIndex]), newContext
+
+            return [contextToBeReturned,methodInvocation]

         # we are still here trying to match but
@@ -174,18 +176,6 @@
         console.log "evaluation " + indentation() + "  matching - no match found"
         return [null, methodInvocationToBeChecked]

-  lookupAndSendFoundMessage: (theContext, countSignaturePosition) ->
-    console.log "evaluation " + indentation() + "  matching - found a matching signature: " + @flClass.msgPatterns[countSignaturePosition].print()
-    # we have a matching signature!
-    methodBody = @flClass.methodBodies[countSignaturePosition]
-    console.log "evaluation " + indentation() + "  matching - method body: " + methodBody
-
-    returnedContext = @methodCall methodBody, theContext
-    console.log "evaluation " + indentation() + "  returned from message send: " + theContext
-    flContexts.pop() # pops the theContext
-    #console.dir theContext
-
-    return returnedContext

   # this could be native, in which case it's a JS call,
   # or non-native, in which case it will result into


Log URL the service is hitting

Log URL the service is hitting

--- indicatorators/esri.coffee
+++ indicatorators/esri.coffee
@@ -126,6 +126,8 @@
   }

 exports.fetchDataFromService = (serviceName, featureServer, callback) ->
+  console.log exports.makeGetUrl(serviceName, featureServer)
+
   request.get(
     url: exports.makeGetUrl(serviceName, featureServer)
     qs: ESRI_QUERY_SUFFIX


Video directive: minor code cleanup

Video directive: minor code cleanup

--- app/scripts/common/directives/video.coffee
+++ app/scripts/common/directives/video.coffee
@@ -1,4 +1,5 @@
-angular.module('directives.youtubeEmbed', ['app.enums']).directive "youtubeEmbed", ["$log", "$timeout", "$rootScope", "enums", ($log, $timeout, $rootScope, enums) ->
+angular.module('directives.youtubeEmbed', ['app.enums'])
+.directive "youtubeEmbed", ["$log", "$timeout", "$rootScope", "enums", ($log, $timeout, $rootScope, enums) ->
   priority: 0
   template: ""
   replace: false
@@ -7,45 +8,28 @@
   scope: {
     videoData: '=youtubeEmbed'
   }
-
-  compile: compile = (tElement, tAttrs, transclude) ->
-    #$log.info "yt compile"
-
-
-    pre: preLink = (scope, iElement, iAttrs, controller) ->
-      #console.log 'yt prelink ' + scope.videoData.id
+  compile: ->
+    pre: (scope) ->
       scope.hasStarted = false
       scope.loadAPI = ->
-
-        # console.log "yt loadAPI"
-        # 2. This code loads the IFrame Player API code asynchronously.
         tag = document.createElement("script")
         tag.src = "//www.youtube.com/iframe_api"
         firstScriptTag = document.getElementsByTagName("script")[0]
         firstScriptTag.parentNode.insertBefore tag, firstScriptTag
       scope.onAPIReady = ->
-        #console.log "yt onAPIReady scope.videoData.id:" + scope.videoData.id
         scope.player = new YT.Player("youtube-player",
-          #height: '390', width: '640',
           width: "1920", height: "1080"
-          videoId: scope.videoData.id #"MpqY4gOdQBU" #'M7lc1UVf-VE',
+          videoId: scope.videoData.id
           playerVars:
-            #autohide:1
             enablejsapi: 1
             modestbranding: 1
             origin: document.domain
             showinfo: 0
             theme: "light"
             rel: 0
-            ,vq: 'hd720',#'hd1080'
-          # events:
-          onReady: () ->
-
+            vq: 'hd720'
         )
-        #TODO youtube start event
         scope.player.addEventListener 'onStateChange', (newState) ->
-
-          $log.info "onPlayerStateChange:" + newState.data
           if !scope.hasStarted
             if newState.data == YT.PlayerState.PLAYING
               $log.info 'yt started'
@@ -54,10 +38,7 @@

           if scope.hasStarted && newState.data == YT.PlayerState.ENDED
             $rootScope.$broadcast enums.EventType.VideoEnd, scope.videoData
-
-
-    post: postLink = (scope, iElement, iAttrs, controller) ->
-
+    post: (scope) ->
       ytLoaded = window.YT != undefined && window.YT.Player != undefined
       if !ytLoaded
         _onYouTubeIframeAPIReady = window.onYouTubeIframeAPIReady
@@ -71,24 +52,23 @@
         scope.onAPIReady()
 ]

-angular.module('directives.videoJsEmbed', ['app.enums']).directive "videoJsEmbed", ["$log", "$timeout", "$rootScope", "enums", ($log, $timeout, $rootScope, enums) ->
+angular.module('directives.videoJsEmbed', ['app.enums'])
+.directive "videoJsEmbed", ["$log", "$timeout", "$rootScope", "enums", ($log, $timeout, $rootScope, enums) ->
   priority: 0
   template: ""
   replace: false
   transclude: false
   restrict: "A"
   scope: {videoData: '=videoJsEmbed', videoId: '=VideoJsId'}
-  compile: compile = (tElement, tAttrs, transclude) ->
-    pre: preLink = (scope, iElement, iAttrs, controller) ->
-      #$log.info "preLink compile"
-    post: postLink = (scope, iElement, iAttrs, controller) ->
+  compile: ->
+    post: (scope, iElement) ->
       if scope && scope.videoData
         if scope.videoData.mp4
           mp4src = scope.videoData.mp4.toString()
           if Modernizr.video.h264 and scope.videoData.mp4_low
             mp4src = scope.videoData.mp4_low
         player = videojs iElement.attr("id"), {}, ->
-          player.poster scope.videoData.poster #.toString()
+          player.poster scope.videoData.poster
           player.src(
             {type: "video/mp4", src: mp4src},
             {type: "video/webm", src: scope.videoData.webm}


Map spec to properties.

Map spec to properties.

--- app.coffee
+++ app.coffee
@@ -1,26 +1,26 @@

 class Sheet

-    constructor: (@spec) ->
-        @spec.data ?= [[0]]
-        @spec.colHeaders ?= ("c#{k}" for k in [0...@spec.data[0].length])
-        @spec.rowHeaders ?= ("r#{k}" for k in [0...@spec.data.length])
+    constructor: (spec) ->
+        spec.data ?= [[0]]
+        @id = spec.id
+        @data = spec.data
+        @colHeaders ?= ("c#{k}" for k in [0...spec.data[0].length])
+        @rowHeaders ?= ("r#{k}" for k in [0...spec.data.length])

     rowJson: ->
         x = {}
-        x[rh] = @spec.data[k] for rh, k in @spec.rowHeaders
+        x[rh] = @data[k] for rh, k in @rowHeaders
         return x

     labelRows: ->
-        ([@spec.rowHeaders[m]].concat row for row, m in @spec.data)
+        ([@rowHeaders[m]].concat row for row, m in @data)

     toLocal: ->
-        #console.log "app.sheet", app.sheet
-        eval("#{@spec.id} = app.sheet['#{@spec.id}'].spec.data")
+        eval("#{@id} = app.sheet['#{@id}'].data")

     fromLocal: (u)->
-        app.sheet[@spec.id].spec.data = u
-
+        app.sheet[@id].data = u

 $blab.compute = ()->

@@ -29,12 +29,12 @@
     for type of app.component
         for i of app.component[type]
             item = app.component[type][i]
-            if item.spec.isSource is "true"
+            if item.isSource is "true"
                 item.update()

     for sym of app.sheet
         app.sheet[sym].toLocal()
-
+
     console.log "######## user-code ########"

     # ??? eval app.file["user.coffee"] ???
@@ -56,7 +56,7 @@
     for c of app.component
         for i of app.component[c]
             item = app.component[c][i]
-            item.update() if item.spec.isSink is "true"
+            item.update() # if item.isSink is "true"

 class App

@@ -100,7 +100,8 @@

         @sheet = {}
         specs = JSON.parse(@file["sheet.json"])
-        @sheet[spec.id] = new Sheet spec for spec in specs
+        for spec in specs
+            @sheet[spec.id] = new Sheet spec

         # components

@@ -108,6 +109,7 @@
         make = (type) =>
             @component[type] = {}
             for spec in JSON.parse(@file["#{type}.json"])
+                spec.containerId ?= "app"
                 @component[type][spec.id] = new @toolbox[type](spec, @sheet, @file)
         make(type) for type of @toolbox


close HTTP keep-alive connections when shutting down

--- app.coffee
+++ app.coffee
@@ -62,6 +62,12 @@
 app.get  "/project/:project_id/file/:file_id", keyBuilder.userFileKey, fileController.getFile
 app.post "/project/:project_id/file/:file_id", keyBuilder.userFileKey, fileController.insertFile

+app.use (req, res, next) ->
+	if not appIsOk
+		# when shutting down, close any HTTP keep-alive connections
+		res.set 'Connection', 'close'
+	next()
+
 app.put "/project/:project_id/file/:file_id", keyBuilder.userFileKey, fileController.copyFile
 app.del "/project/:project_id/file/:file_id", keyBuilder.userFileKey, fileController.deleteFile


use prod files

use prod files

--- app.coffee
+++ app.coffee
@@ -29,12 +29,12 @@
   #parsers: [fileParser]

   # Uncomment for production files
-  #directoryFilters: ['!node_modules', '!Release Notes']
-  #fileFilters: '*.md'
+  directoryFilters: ['!node_modules', '!Release Notes']
+  fileFilters: '*.md'

   # Uncomment for test files
-  directoryFilters: ['Accounts & Users']
-  fileFilters: 'creating-users.md'
+  #directoryFilters: ['Accounts & Users']
+  #fileFilters: 'creating-users.md'

   readOpts: ->
     root: path.join(__dirname, '..')


Changed extend/defaults order of routes. This should allow project specific routes to be gone through first, followed by personal routes, and finally default routes. The order used to be reversed, so if you had a default catch-all route, you would never get to the project specific routes. This makes use of `_.defaults` which will only set up the route if it is not already defined (so catch-alls for projects won't be overwritten by defaults).

--- app.coffee
+++ app.coffee
@@ -75,7 +75,9 @@
   if options.httpsPort and not _.isArray options.httpsPort
     options.httpsPort = [options.httpsPort]

-  options.routes = _.extend {}, defaults.routes, project.routes, personal.routes
+  options.routes = _.extend {}, project.routes
+  _.defaults options.routes, personal.routes
+  _.defaults options.routes, defaults.routes

   proxy.start options


tweak(category-picker): Be slightly more robust against missing inbox

--- internal_packages/category-picker/lib/category-picker.cjsx
+++ internal_packages/category-picker/lib/category-picker.cjsx
@@ -265,6 +265,7 @@

   _allInInbox: (usageCount, numThreads) ->
     inbox = CategoryStore.getStandardCategory("inbox")
+    return false unless inbox
     return usageCount[inbox.id] is numThreads

   _itemForCategory: ({usageCount, numThreads}, category) ->


Compute property names immediately

Compute property names immediately

--- source/builders/abstract-property.coffee
+++ source/builders/abstract-property.coffee
@@ -1,76 +1,75 @@
 class AbstractProperty

-  {defineProperty} = Object
-  {hasOwnProperty} = Object.prototype
-
+  nativeDefineProperty = Object.defineProperty
+  {hasOwnProperty}     = Object.prototype
+  {isString}           = _
+
   define: ->
-    defineProperty @target, @property,
+    nativeDefineProperty @target, @property,
       get:          @publicGetter()
       set:          @publicSetter()
       enumerable:   yes
       configurable: yes

     if @options.silent
-      defineProperty @target, "_#{@property}",
+      nativeDefineProperty @target, "_#{@property}",
         writable:     yes
         enumerable:   no
         configurable: yes
     else
-      defineProperty @target, "_#{@property}",
+      nativeDefineProperty @target, "_#{@property}",
         get:          @shadowGetter()
         set:          @shadowSetter()
         enumerable:   no
         configurable: yes

       unless hasOwnProperty.call(@target, "__#{@property}")
-        defineProperty @target, "__#{@property}",
+        nativeDefineProperty @target, "__#{@property}",
           enumerable:   no
           writable:     yes
           configurable: no
-    @configureDependencies?()
     this

   publicGetter: ->
-    if @getter
+    if @getter?
       if @options.memo
-        if typeof @getter is 'string'
-          do (computer = @getter, property = @property) ->
+        if isString(@getter)
+          do (computer = @getter, _property = "_#{@property}") ->
             ->
-              this["_#{property}"] ?= this[computer]()
-              this["_#{property}"]
+              this[_property] ?= this[computer]()
+              this[_property]
         else
-          do (computer = @getter, property = @property) ->
+          do (computer = @getter, _property = "_#{@property}") ->
             ->
-              this["_#{property}"] ?= computer.call(this)
-              this["_#{property}"]
+              this[_property] ?= computer.call(this)
+              this[_property]
       else
-        if typeof @getter is 'string'
+        if isString(@getter)
           do (computer = @getter) -> -> this[computer]()
         else
           @getter
     else
-      do (property = @property) -> -> this["_#{property}"]
+      do (_property = "_#{@property}") -> -> this[_property]

   publicSetter: ->
     if @options.readonly
       do (property = @property, Error = ReadonlyPropertyError) ->
         -> throw new Error(this, property)
-    else if @setter
-      if typeof @setter is 'string'
+    else if @setter?
+      if isString(@getter)
         do (setter = @setter) -> (value) -> this[setter](value); return
       else
         @setter
     else
-      do (property = @property) -> (value) -> this["_#{property}"] = value; return
+      do (_property = "_#{@property}") -> (value) -> this[_property] = value; return

   shadowGetter: ->
-    do (property = @property) -> -> this["__#{property}"]
+    do (property = "__#{@property}") -> -> this[property]

   shadowSetter: ->
-    do (equal = comparator, property = @property) ->
+    do (equal = comparator, property = @property, __property = "__#{@property}") ->
       (x1) ->
-        x0 = this["__#{property}"]
+        x0 = this[__property]
         if not equal(x1, x0)
-          this["__#{property}"] = x1
-          @notify("change:#{property}", this, x1, x0)
-        return+          this[__property] = x1
+        return


speed adjustment

speed adjustment

--- app/vis.coffee
+++ app/vis.coffee
@@ -8,7 +8,7 @@
 module.exports = class Main

   scaleText: 0.005
-  speedMultiplier: 0.6
+  speedMultiplier: 0.9

   CAMERA_Z = -9


Given when then, with creation method.

Given when then, with creation method.

--- cakes.coffee
+++ cakes.coffee
@@ -96,10 +96,6 @@
 			fn()

-###
-exports.Background = (action, callback)->
-	depict action.magenta, arguments
-###

 createScenario = (options)->

@@ -142,6 +138,11 @@
 	dic 'describe', "\n    Scenario: %s".green, arguments
 ###

+###
+exports.Background = (action, callback)->
+	depict action.magenta, arguments
+###
+
 gwt = (label, args, options)->
 	[title, cb] = args_wash args

@@ -162,6 +163,44 @@

 	dic 'it', label, [title,cb], options

+createGiven = (options)->
+	return (message, callback)->
+		if 'label' in options
+			label = 'Given: '
+			if 'labelcolor' in options
+				label = label.yellow
+			message = label+message
+
+		mocha.it message, callback
+
+exports.Given = createGiven(['label', 'labelcolor'])
+
+createWhen = (options)->
+	return (message, callback)->
+		if 'label' in options
+			label = ' When: '
+			if 'labelcolor' in options
+				label = label.yellow
+			message = label+message
+
+		mocha.it message, callback
+
+exports.When = createWhen(['label', 'labelcolor'])
+
+createThen = (options)->
+	return (message, callback)->
+		if 'label' in options
+			label = ' Then: '
+			if 'labelcolor' in options
+				label = label.yellow
+			message = label+message
+
+		mocha.it message, callback
+
+exports.Then = createThen(['label', 'labelcolor'])
+
+
+###
 exports.Given = ->
 	gwt "Given:".yellow+" %s", arguments

@@ -171,6 +210,8 @@
 exports.Then = ->
 	gwt " Then:".yellow+" %s", arguments

+###
+
 exports.And = ->
 	gwt "  And".grey+"  %s", arguments, padding_color:'black'


Make dialog asynchronous when a renderer process crashes

…so that other event handlers have the chance to execute even if the
user doesn't choose an option in the message box. This will allow us to
recover files when a window crashes.

--- src/browser/atom-window.coffee
+++ src/browser/atom-window.coffee
@@ -141,14 +141,15 @@
     @browserWindow.webContents.on 'crashed', =>
       global.atomApplication.exit(100) if @headless

-      chosen = dialog.showMessageBox @browserWindow,
+      dialog.showMessageBox @browserWindow,
         type: 'warning'
         buttons: ['Close Window', 'Reload', 'Keep It Open']
         message: 'The editor has crashed'
-        detail: 'Please report this issue to https://github.com/atom/atom'
-      switch chosen
-        when 0 then @browserWindow.destroy()
-        when 1 then @browserWindow.reload()
+        detail: 'Please report this issue to https://github.com/atom/atom',
+        (chosen) =>
+          switch chosen
+            when 0 then @browserWindow.destroy()
+            when 1 then @browserWindow.reload()

     @browserWindow.webContents.on 'will-navigate', (event, url) =>
       unless url is @browserWindow.webContents.getURL()


Implemented Explorer, Tribute, Venture, and Walled Village

--- cards.coffee
+++ cards.coffee
@@ -536,6 +536,24 @@
     vp
 }

+makeCard 'Explorer', action, {
+  cost: 5
+
+  playEffect: (state) ->
+    cardToGain = c.Silver
+
+    if c.Province in state.current.hand
+      state.log("…revealing a Province.")
+      cardToGain = c.Gold
+
+    if state.countInSupply(cardToGain) > 0
+      state.gainCard(state.current, cardToGain, true)
+      transferCard(cardToGain, state.current.discard, state.current.hand)
+      state.log("…and gaining a #{cardToGain}, putting it in the hand.")
+    else
+      state.log("…but there are no #{cardToGain}s available to gain.")
+}
+
 makeCard 'Farming Village', action, {
   cost: 4
   actions: 2
@@ -893,6 +911,26 @@
       state.log("…gaining #{numGolds} Golds, putting them on top of the deck.")
 }

+makeCard 'Tribute', action, {
+  cost: 5
+
+  playEffect: (state) ->
+    revealedCards = state.players[1].discardFromDeck(2)
+
+    unique = []
+    for card in revealedCards
+      if card not in unique
+        unique.push(card)
+
+    for card in unique
+      if card.isAction
+        state.current.actions += 2
+      if card.isTreasure
+        state.current.coins += 2
+      if card.isVictory
+        state.current.drawCards(2)
+}
+
 makeCard "Trusty Steed", c["Bag of Gold"], {
   actions: 0
   playEffect: (state) ->
@@ -921,10 +959,43 @@
     state.gainOneOf(state.current, choices)
 }

+makeCard 'Venture', c.Silver, {
+  cost: 5
+  coins: 1
+
+  playEffect: (state) ->
+    treasuresDrawn = 0
+    foundTreasure = null
+
+    while treasuresDrawn < 1
+      drawn = state.current.getCardsFromDeck(1)
+      if drawn.length == 0
+        break
+      card = drawn[0]
+      if card.isTreasure
+        treasuresDrawn += 1
+        foundTreasure = card
+        state.log("…drawing and playing a #{card}.")
+      else
+        state.current.setAside.push(card)
+    state.current.discard = state.current.discard.concat(state.current.setAside)
+    state.current.setAside = []
+
+    if treasuresDrawn == 1
+      state.current.inPlay.push(foundTreasure)
+      foundTreasure.onPlay(state)
+}
+
 makeCard 'Vineyard', c.Estate, {
   cost: 0
   costPotion: 1
   getVP: (state) -> Math.floor(state.current.numActionCardsInDeck().length / 3)
+}
+
+makeCard 'Walled VIllage', c.Village, {
+  cost: 4
+
+  #Clean up effect defined in `State.doCleanupPhase`
 }

 makeCard 'Warehouse', action, {


added Haggler

added Haggler

--- cards.coffee
+++ cards.coffee
@@ -1473,6 +1473,22 @@
   # Grand Market is the only card with a non-constant mayBeBought value.
   mayBeBought: (state) ->
     not(c.Copper in state.current.inPlay)
+}
+
+makeCard 'Haggler', action, {
+  cost: 5
+  coins: +2
+  buyInPlayEffect: (state, card1) ->
+    [coins1, potions1] = card1.getCost(state)
+    choices = []
+    for cardName of state.supply
+      card2 = c[cardName]
+      [coins2, potions2] = card2.getCost(state)
+      if (potions2 <= potions1) and (coins2 < coins1) and not card2.isVictory
+        choices.push(card2)
+      else if (potions2 < potions1) and (coins2 == coins1) and not card2.isVictory
+        choices.push(card2)
+    state.gainOneOf(state.current, choices)
 }

 makeCard "Harvest", action, {


start of tooltips for schools

start of tooltips for schools

--- source/javascripts/county-map.coffee
+++ source/javascripts/county-map.coffee
@@ -1,15 +1,28 @@
 render = (event, env) ->
   selectedSchool = null
+
+  tip = d3.tip().attr('class', 'd3-tip')
+      .html (d) ->
+        summary = summaryForSchool d.team
+        console.log summary
+        "
#{d.team}
"
+        "
#{d.city}
"

   map = d3.select('#county-map').append('svg')
     .attr('width', env.width)
     .attr('height', env.height)
+    .call(tip)

   zoomGroup = map.append 'g'

   recruit.coordinates = env.projection [recruit.lat, recruit.lon] for recruit in env.recruits
   school.coordinates  = env.projection [school.lat, school.lon] for school in env.schools

+  summaryForSchool = (school) ->
+    d3.nest()
+      .key((d) -> d.institution)
+      .map(env.recruits, d3.map)
+      .get(school)
   # Generates a LineString GeoJSON object from a player to a school
   #
   # player - Object
@@ -130,6 +143,7 @@
   .enter().append('polygon')
     .attr('class', 'school')
     .attr('points', (d) -> drawStar(d.coordinates[0], d.coordinates[1], 5, 6, 3))
+    .on('mouseover', tip.show)
     .on('click', drawRecruitPathsToSchool)

   # Shade the county by the selected year


[i18n] fix for browsers that need module.extension.init()

--- src/coffee/content/main.coffee
+++ src/coffee/content/main.coffee
@@ -65,8 +65,9 @@
         selfDestructObserver.observe window.document.body,
             attributes: true

+        module.extension.init() if module.extension.init?
+
         module.i18n.init ->
-            module.extension.init() if module.extension.init?
             module.comm.setOnUpdate forceUpdate

             forceUpdate()


[content] added self-destruct Consequences: - only one instance of the extension can be running at a given moment Why? - the extension is updated in firefox and safari without restarting the browser,   this could lead to two running instances of the content script. - once in a while I forget to disable the Webstore version or the release version   when I load the debug version Why is having multiple instances running bad? - because both will listen to DOM changes, which will be triggered by the other   content script. One mention where the script adds a DOM node is enough to create   a perpetual loop of modifications (script A then B then A then B then A ...)

--- src/coffee/content/main.coffee
+++ src/coffee/content/main.coffee
@@ -20,6 +20,8 @@
 # })();

 ((module, $, window) ->
+    selfTimestamp = '' + +new Date
+
     observer = new window.MutationObserver (mutations) ->
         module.checkProfile()
         module.listSources()
@@ -27,6 +29,25 @@
         mutations.each (mutation) ->
             Array.prototype.each.call mutation.addedNodes, (node) ->
                 module.checkElement node
+
+    selfDestructObserver = new window.MutationObserver (mutations) ->
+        mutations.each (mutation) ->
+            return unless mutation.type is 'attributes' and mutation.attributeName is 'data-iidentity-timestamp'
+
+            newTimestamp = $ mutation.target
+                .attr 'data-iidentity-timestamp'
+
+            return if newTimestamp is selfTimestamp
+
+            module.log.info 'Self-destructing', selfTimestamp, 'due to loading of second extension instance', newTimestamp
+
+            # stop this extension
+            observer.disconnect()
+            module.comm.setOnUpdate ->
+
+            selfDestructObserver.disconnect()
+
+            false

     forceUpdate = ->
         module.doOnce.update()
@@ -37,6 +58,11 @@
         module.checkElement window.document

     $ ->
+        $ window.document.body
+            .attr 'data-iidentity-timestamp', selfTimestamp
+        selfDestructObserver.observe window.document.body,
+            attributes: true
+
         module.extension.init() if module.extension.init?
         module.comm.setOnUpdate forceUpdate


布局大小改变后resize window

布局大小改变后resize window

--- src/coffee/smart-layout.coffee
+++ src/coffee/smart-layout.coffee
@@ -1,5 +1,5 @@
 angular.module 'smart.layout', []
-  .directive 'smartLayout', ['$parse', '$window', 'parseSize', ($parse, $window, parseSize) ->
+  .directive 'smartLayout', ['$parse', '$window', '$timeout', 'parseSize', '_debounceResize', ($parse, $window, $timeout, parseSize, _debounceResize) ->
     restrict: 'A'
     scope: true
     link: (scope, elem, attrs) ->
@@ -11,7 +11,7 @@
             right: 0
             top: 0
             bottom: 0
-            overflow: 'hidden'
+            overflow: 'auto'
           }
           layoutOption = $parse(attrs.smartLayout) scope
           @layoutDirection = if layoutOption.direction is 'column' then 'column' else 'row'
@@ -22,6 +22,7 @@
           @fixedItemIdxs = []
           @itemMinSizeTotal = 0
           @activeSpliterIdx = 0
+          @spliterIsActive = false
           @itemAll = []
           @sizeType =
             size: if @layoutDirection is 'column' then 'width' else 'height'
@@ -39,6 +40,7 @@
             child.css 'position', "absolute"
             child.css @sizeType.begin, 0
             child.css @sizeType.end, 0
+            child.css 'overflow', 'auto'

             if angular.isUndefined child.attr('layout-spliter')
               item = {}
@@ -68,13 +70,13 @@
               spliterOption = $parse(spliter.elem.attr('layout-spliter'))(scope) ? {}
               collapseOption = $parse(spliter.elem.attr('collapsable'))(scope) ? {}
               spliter.size = parseSize.spliter(spliterOption.size)
-              spliter.color = spliterOption.color ? '#222'
+              spliter.color = spliterOption.color ? '#333'
               spliter.collapsable = angular.isDefined spliter.elem.attr('collapsable')
               if spliter.collapsable
                 spliter.collapsed = false
                 spliter.collapseDirection = collapseOption.direction ? 'pre'
                 spliter.collapseColor = collapseOption.color ? '#ddd'
-                spliter.elem.html '
<svg><path /></svg>
'
+                spliter.elem.html '
<svg><path /></svg>
'
                 spliter.collapse = spliter.elem.children()
                 spliter.collapse.css @sizeType.begin, '50%'
                 spliter.collapse.find('svg').css @sizeType.osize, "#{spliter.size * 4}px"
@@ -92,7 +94,7 @@
               spliter.elem.remove()
             else
               if spliter.collapsable
-                spliter.preDivBoth = Math.max(preItem.size, 20) / (Math.max(nextItem.size, 20) + Math.max(nextItem.size, 20))
+                spliter.preDivBoth = Math.max(preItem.size, 20) / (Math.max(preItem.size, 20) + Math.max(nextItem.size, 20))
               splitersTmp.push spliter
               @spliterSizeTotal += spliter.size
           @spliters = splitersTmp
@@ -117,13 +119,11 @@
           collapse.find('path').attr "d", path
           return
         toggleCollapse: (spliter) ->
-          console.log spliter
           pre = @items[spliter.preItemIdx]
           next = @items[spliter.nextItemIdx]
           rawSizeBoth = pre.size + next.size
           if spliter.collapsed
             pre.size = rawSizeBoth * spliter.preDivBoth
-            console.log pre.size
           else
             if spliter.collapseDirection is 'pre'
               pre.size = pre.minSize
@@ -142,9 +142,10 @@
             @setCollapseStyle(spliter.collapse, spliter.size, @layoutDirection, if spliter.collapsed then 'next' else 'pre')
           else
             @setCollapseStyle(spliter.collapse, spliter.size, @layoutDirection, if spliter.collapsed then 'pre' else 'next')
-          angular.element($window).triggerHandler('resize')
+          _debounceResize()
           return
         resize: () ->
+          layoutSizeLast = @layoutSize
           @layoutSize = elem[0][@sizeType.offset]
           rest = Math.max(@layoutSize - @spliterSizeTotal - @fixedSizeTotal, 0)
           readyItemidxs = angular.copy @fixedItemIdxs
@@ -165,6 +166,8 @@
                   readyItemidxs.push index
                   rest = Math.max(rest - item.size, 0)
           @raw()
+          if Math.abs(layoutSizeLast - @layoutSize) > 5
+            _debounceResize()
           return
         raw: () ->
           cSize = 0
@@ -179,22 +182,26 @@
           return
         registerObservers: () ->
           @resize()
-          setTimeout () =>
-            angular.element($window).triggerHandler('resize')
-          , 200
-          angular.element($window).on 'resize', () => @resize(); return
-          for spliter in @spliters
+          _debounceResize()
+          angular.element($window).on 'resize', () =>
+            @resize()
+            return
+          for spliter, spliter_idx in @spliters
             if spliter.collapsable
               spliter.collapse.on 'click', ((spliter) =>
                 (evt) =>
                   @toggleCollapse(spliter)
               )(spliter)
-            spliter.elem.on 'mousedown', ((spliter) =>
+            spliter.elem.on 'mousedown', ((spliter, spliter_idx) =>
               (evt) =>
                 spliter.from = evt[@sizeType.coord]
+                spliter.delta = 0
                 evt.preventDefault()
                 evt.stopPropagation()
+                @spliterIsActive = true
                 angular.element($window).on 'mousemove', (evt) =>
+                  @activeSpliterIdx = spliter_idx
+                  spliter.elem.css 'z-index', 99999
                   spliter.to = evt[@sizeType.coord]
                   pre = @items[spliter.preItemIdx]
                   next = @items[spliter.nextItemIdx]
@@ -209,15 +216,19 @@
                     if not (next.minSize <= next.size <= next.maxSize)
                       next.size = Math.max next.minSize, Math.min(next.size, next.maxSize)
                       pre.size = rawSizeBoth - next.size
-                    @raw()
-                    angular.element($window).triggerHandler('resize')
+                    spliter.elem.css @sizeType.from, parseSize.toPixel(parseInt(spliter.elem.css(@sizeType.from), 10) + spliter.to - spliter.from)
                   spliter.from = spliter.to
                   return
                 return
-            )(spliter)
+            )(spliter, spliter_idx)
           angular.element($window).on 'mouseup', (evt) =>
-            angular.element($window).off 'mousemove'
-            return
+            if @spliterIsActive
+              @raw()
+              _debounceResize()
+              @spliters[@activeSpliterIdx].elem.css 'z-index', 'inherit'
+              angular.element($window).off 'mousemove'
+              return
+            @spliterIsActive = false
           return
       new Layout().registerObservers()
       return
@@ -237,4 +248,20 @@
     toPixel: (size) ->
       size + 'px'
   ]
-
+  .factory '_debounceResize', ['$timeout', '$window', ($timeout, $window) ->
+    _debounce = (func, wait) ->
+      timeout = null
+      () ->
+        context = this
+        args = arguments
+        later = () ->
+          timeout = null
+          func.apply(context, args)
+        $timeout.cancel timeout if timeout
+        timeout = $timeout later, wait
+        return
+    _debounce () ->
+      angular.element($window).triggerHandler('resize')
+      return
+    , 300
+  ]


Tests: Added checkMachine test.

Tests: Added checkMachine test.

--- tests/src/ide/ide.coffee
+++ tests/src/ide/ide.coffee
@@ -80,6 +80,45 @@
       .end()

+  checkMachine: (browser) ->
+
+    user = helpers.beginTest(browser)
+
+    ideModalSelector   = '.ide-modal.ide-machine-state'
+    turnOnButton       = '.turn-on.state-button'
+    progressBar        = '.progressbar-container'
+    stateLabelSelector = ideModalSelector + ' .state-label'
+
+    browser
+      .pause 3000 # wait to see ide modal
+      .element 'css selector', ideModalSelector, (result) =>
+
+        if result.status is 0 # ide modal found
+          browser
+            .waitForElementNotPresent ideModalSelector + '.checking', 30000
+            .getText stateLabelSelector, (result) =>
+              label = result.value
+
+              if label.indexOf('turned off') > -1
+                console.log 'machine turned off, turning on'
+
+                browser
+                  .waitForElementVisible      turnOnButton, 30000
+                  .click                      turnOnButton
+                  .waitForElementVisible      progressBar, 20000
+                  .waitForElementNotVisible   ideModalSelector, 120000 # Assertion
+                  .end()
+
+              else if label.indexOf('building') is -1 or label.indexOf('starting') is -1
+                console.log 'machine is building/starting, waiting up to 2.5 min for assertion'
+
+                browser.waitForElementNotVisible ideModalSelector, 120000 # Assertion
+                browser.end()
+
+        else # machine is already turned on
+          console.log 'machine is already running'
+
+
   duplicate: (browser) ->

     user          = helpers.beginTest(browser)


Actually - sort by average, per speaker, it makes more sense...

--- client/controller/vote-controller.coffee
+++ client/controller/vote-controller.coffee
@@ -29,4 +29,4 @@
     - sum(speaker.proposals().map((p) -> p.voteCount()))
   )

-sum = (arr) -> _.reduce(arr, ((sum, num) -> sum + num), 0)+sum = (arr) -> if arr.length then _.reduce(arr, ((sum, num) -> sum + num), 0) / arr.length else 0


chat: add minimize functionality

chat: add minimize functionality

--- client/ide/views/chat/messagepane.coffee
+++ client/ide/views/chat/messagepane.coffee
@@ -71,7 +71,7 @@

     'Search'   : { callback: noop }
     'Settings' : { callback: @getDelegate().bound 'showSettingsPane' }
-    'Minimize' : { callback: noop }
+    'Minimize' : { callback: @getDelegate().bound 'hide' }

   createInputWidget: ->


Fixing so sensors selection works as expected.

--- src/main/webapp/lib/views/MapSensorView.coffee
+++ src/main/webapp/lib/views/MapSensorView.coffee
@@ -5,12 +5,6 @@
   @ICON: 'camera-orig'
   @SELECTED_ICON: 'camera-selected'
   $a = window.beats
-
-  broker_events : {
-    'map:open_view_mode' : 'viewMode'
-    'map:open_network_mode' : 'networkMode'
-    'map:open_scenario_mode' : 'scenarioMode'
-  }

   initialize: (model) ->
     super model


Send csrf token on ajax requests

Send csrf token on ajax requests

--- app/assets/javascripts/backend/animals.js.coffee
+++ app/assets/javascripts/backend/animals.js.coffee
@@ -1,26 +1,11 @@
-### OLD INTERFACE ((E, $) ->
-  'use strict'
-  # Filters supports with given production
-  # Hides supports line if needed
-  $(document).behave "load selector:set", "#production_id", (event) ->
-    production = $(this)
-    id = production.selector('value')
-    form = production.closest('form')
-    url = "/backend/production_supports/unroll?scope[of_currents_campaigns]=true"
-    support = form.find("#production_support_id").first()
-    if /^\d+$/.test(id)
-      url += "&scope[of_productions]=#{id}"
-      form.addClass("with-supports")
-    else
-      form.removeClass("with-supports")
-    support.attr("data-selector", url)
-    support.data("selector", url)
-) ekylibre, jQuery###
-
-
 #= require bootstrap/modal

 $ ->
+
+  $(document).ajaxSend (e, xhr, options) ->
+    token = $('meta[name=\'csrf-token\']').attr('content')
+    xhr.setRequestHeader 'X-CSRF-Token', token
+    return

   ko.bindingHandlers.checkbox = init: (element, valueAccessor, allBindings, data, context) ->
     $element = undefined


Removed justify button from ckeditor toolbar.

--- app/assets/javascripts/ckeditor/config.js.coffee
+++ app/assets/javascripts/ckeditor/config.js.coffee
@@ -8,7 +8,7 @@
   config.toolbar_Pure = [
     { name: 'styles',      items: [ 'Format' ] },
     { name: 'basicstyles', items: [ 'Bold','Italic','Strike','-','RemoveFormat' ] },
-    { name: 'paragraph',   items: [ 'BulletedList','NumberedList','-','Outdent','Indent','-','Blockquote','-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock' ] },
+    { name: 'paragraph',   items: [ 'BulletedList','NumberedList','-','Outdent','Indent','-','Blockquote','-','JustifyLeft','JustifyCenter','JustifyRight' ] },
     '/',
     { name: 'clipboard',   items: [ 'Undo','Redo' ] },
     { name: 'links',       items: [ 'Link','Unlink' ] },


Add path when setting cookies.

Add path when setting cookies.

--- app/assets/javascripts/controllers/map.js.coffee
+++ app/assets/javascripts/controllers/map.js.coffee
@@ -25,9 +25,9 @@
   mapChanged: (()->
     center = @get('center')

-    $.cookie('last_lat', center.lat)
-    $.cookie('last_lng', center.lng)
-    $.cookie('last_zoom', @get('zoom'))
+    $.cookie('last_lat', center.lat, { path: '/' } )
+    $.cookie('last_lng', center.lng, { path: '/' } )
+    $.cookie('last_zoom', @get('zoom'), { path: '/' })
   ).observes('center', 'zoom')

   poppingNode: ((key, nodeId)->


Add missing require for path

Add missing require for path

--- src/app/project.coffee
+++ src/app/project.coffee
@@ -1,4 +1,5 @@
 fsUtils = require 'fs-utils'
+path = require 'path'
 _ = require 'underscore'
 $ = require 'jquery'
 Range = require 'range'


ctrl-alt-b is now 'build', so beautifier is ctl-alt-u

--- keymap.cson
+++ keymap.cson
@@ -41,6 +41,8 @@
   'ctrl-p': 'core:move-up'
   #TODO: set new key for find-and-replace:use-selection-as-find-pattern
   'ctrl-e': 'editor:move-to-end-of-screen-line'
+  # ctrl-alt-b got taken by atom-ide-ui build system
+  'ctrl-alt-u':	'atom-beautify:beautify-editor'

 'atom-text-editor:not([mini])':
   'ctrl-alt-w': 'emmet:wrap-with-abbreviation'