chore(improv): Remove coffee tests file

chore(improv): Remove coffee tests file

--- test/unit/08-improv_test._coffee
+++ test/unit/08-improv_test._coffee
@@ -1,298 +0,0 @@
-sinon = require 'sinon'
-chai = require 'chai'
-should = chai.should()
-chai.use require 'sinon-chai'
-chai.use require 'chai-subset'
-co = require 'co'
-_ = require 'lodash'
-pretend = require 'hubot-pretend'
-improv = require '../../lib/modules/improv'
-Transcript = require '../../lib/modules/transcript'
-Director = require '../../lib/modules/director'
-Dialogue = require '../../lib/modules/dialogue'
-
-describe 'Improv', ->
-  context 'singleton', ->
-    before -> pretend.start()
-    after -> pretend.shutdown()
-
-    it 'sequential use returns existing instance', ->
-      improvA = improv.use pretend.robot
-      improvB = improv.use pretend.robot
-      improvA.should.eql improvB
-
-    it 'instance persists after test robot shutdown', ->
-      improv.instance.should.exist
-
-    it 'use after clear returns new instance', ->
-      improvA = improv.use pretend.robot
-      improv.reset()
-      improvB = improv.use pretend.robot
-      improvA.should.not.eql improvB
-
-    it 'overwrite robot when reused', ->
-      improv.use pretend.robot
-      .robot.should.eql pretend.robot
-
-    it 'configuration merges existing config', ->
-      improv.configure foo: 'bar'
-      improv.configure baz: 'qux'
-      improv.instance.config.should.include foo: 'bar', baz: 'qux'
-
-  context 'instance', ->
-
-    beforeEach ->
-      pretend.start()
-      improv.use pretend.robot
-      improv.configure save: false
-
-      # generate first listen response
-      pretend.robot.hear /test/, -> # listen for tests
-      pretend.user('tester', { room: 'testing' }).send('test')
-
-    afterEach ->
-      pretend.shutdown()
-      improv.reset()
-
-    describe '.use', ->
-
-      it 'attaches response middleware to robot', ->
-        pretend.robot.responseMiddleware.should.have.calledOnce
-
-    describe '.extend', ->
-
-      context 'with function only', ->
-
-        it 'stores function in extensions array with undefined path', ->
-          func = sinon.spy()
-          improv.extend func
-          improv.extensions.should.eql [{ function: func, path: undefined }]
-
-      context 'with function and path', ->
-
-        it 'stores function in extensions array with undefined path', ->
-          func = sinon.spy()
-          improv.extend func, 'a.path'
-          improv.extensions.should.eql [{ function: func, path: 'a.path' }]
-
-    describe '.remember', ->
-
-      it 'stores data at key in data', ->
-        improv.data.site = name: 'Hub'
-        improv.remember 'site', lang: 'en'
-        improv.data.should.eql site:
-          lang: 'en'
-
-      it 'stores data at path in data', ->
-        improv.data.site = name: 'Hub'
-        improv.remember 'site.lang', 'en'
-        improv.data.should.eql site:
-          name: 'Hub'
-          lang: 'en'
-
-    describe '.rememberForUser', ->
-
-      it 'stores data at path with user ID', ->
-        uid = pretend.lastListen().message.user.id
-        improv.data.site = name: 'Hub'
-        improv.rememberForUser uid, 'site.nickname', 'Hubby'
-        improv.userData.should.eql `${uid}`: site: nickname: 'Hubby'
-
-    describe '.forget', ->
-
-      it 'removes data at path', ->
-        improv.data.site = name: 'Hub', lang: 'en'
-        improv.remember 'site.lang', 'en'
-        improv.data.should.eql site:
-          name: 'Hub'
-          lang: 'en'
-
-    describe '.forgetForUser', ->
-
-      it 'forgets user data at path for user ID', ->
-        uid = pretend.lastListen().message.user.id
-        improv.userData.uid = site: nickname: 'Hubby'
-        improv.forgetForUser uid, 'site.nickname'
-        improv.userData.should.eql {}
-
-    describe '.mergeData', ->
-
-      context 'with data passed as option', ->
-
-        it 'merges data with context', ->
-          improv.data.site = name: 'Hub'
-          improv.mergeData user: pretend.lastListen().message.user
-          .should.eql
-            user: pretend.lastListen().message.user
-            site: name: 'Hub'
-
-        it 'merges data with remembered user data in context', ->
-          user = pretend.lastListen().message.user
-          improv.data.site = name: 'Hub'
-          improv.userData = `${user.id}`: site: nickname: 'Hubby'
-          improv.mergeData user: user
-          .should.eql
-            user: user
-            site: name: 'Hub', nickname: 'Hubby'
-
-        it 'does not merge remembered user data without context', ->
-          user = pretend.lastListen().message.user
-          improv.data.site = name: 'Hub'
-          improv.userData = `${user.id}`: site: nickname: 'Hubby'
-          improv.mergeData()
-          .should.eql
-            site: name: 'Hub'
-
-      context 'with data loaded from brain', ->
-
-        it 'merges data with user data', ->
-          improv.configure save: true
-          pretend.robot.brain.set 'improv', site: owner: 'Hubot'
-          improv.data.site = name: 'Hub'
-          improv.mergeData user: pretend.lastListen().message.user
-          .should.eql
-            user: pretend.lastListen().message.user
-            site: owner: 'Hubot' name: 'Hub'
-
-      context 'with extension functions added', ->
-
-        it 'merges data with results of functions', ->
-          improv
-          .extend -> custom1: 'foo'
-          .extend -> custom2: 'bar'
-          .mergeData user: pretend.lastListen().message.user
-          .should.eql
-            user: pretend.lastListen().message.user
-            custom1: 'foo'
-            custom2: 'bar'
-
-        it 'deep merges existing data with extensions', ->
-          improv
-          .extend -> user: type: 'human'
-          .mergeData user: name: 'frendo'
-          .should.eql user: name: 'frendo', type: 'human'
-
-        context 'with paths argument matching extension', ->
-
-          it 'merges extension with existing data', ->
-            func = -> return { test: { foo: 'bar' } }
-            improv
-            .extend(func, 'test.foo')
-            .mergeData({ test: { baz: 'qux' } }, ['test.foo'])
-            .should.eql({ test: { foo: 'bar', baz: 'qux' } })
-
-        context 'with paths partially matching extension', ->
-
-          it 'merges extension with existing data', ->
-            func = -> return { test: { foo: 'bar' } }
-            improv
-            .extend(func, 'test.foo')
-            .mergeData({ test: { baz: 'qux' } }, ['test'])
-            .should.eql({ test: { foo: 'bar', baz: 'qux' } })
-
-        context 'with paths that don\'t match extension', ->
-
-          it 'returns extension data only', ->
-            func = -> return { test: { foo: 'bar' } }
-            improv
-            .extend(func, 'test.foo')
-            .mergeData({ test: { baz: 'qux' } }, ['something.else'])
-            .should.eql({ test: { baz: 'qux' } })
-
-    describe '.parse', ->
-
-      context 'with data', ->
-
-        it 'populates message template with data at path', ->
-          improv.data = site: 'The Hub'
-          improv.parse strings: ['welcome to ${ this.site }']
-          .should.eql ['welcome to The Hub']
-
-      context 'without data', ->
-
-        it 'uses fallback value', ->
-          string = 'hey ${ this.user.name }, pay ${ this.product.price }'
-          improv.parse strings: [string]
-          .should.eql ['hey unknown, pay unknown']
-
-      context 'with partial data', ->
-
-        it 'uses fallback for unknowns', ->
-          improv.data = product: price: '$55'
-          string = 'hey ${ this.user.name }, pay ${ this.product.price }'
-          improv.parse strings: [string]
-          .should.eql ['hey unknown, pay $55']
-
-        it 'replaces entire string as configured', ->
-          improv.configure replacement: '¯\\_(ツ)_/¯'
-          improv.data = product: price: '$55'
-          string = 'hey ${ this.user.name }, pay ${ this.product.price }'
-          improv.parse strings: [string]
-          .should.eql ['¯\\_(ツ)_/¯']
-
-    describe '.middleware', ->
-
-      context 'with series of hubot sends', ->
-
-        it 'rendered messages with data', -> co ->
-          improv.data.site = { name: 'The Hub' }
-          yield pretend.lastListen().send 'hello you'
-          yield pretend.lastListen().send 'hi ${ this.user.name }'
-          pretend.messages.should.eql [
-            [ 'testing', 'tester', 'test' ]
-            [ 'testing', 'hubot', 'hello you' ]
-            [ 'testing', 'hubot', 'hi tester' ]
-          ]
-
-      context 'with multiple strings', ->
-
-        it 'renders each message with data', -> co ->
-          improv.data.site = name: 'The Hub'
-          yield pretend.lastListen().send 'testing'
-          , 'hi ${ this.user.name }'
-          , 'welcome to ${ this.site.name }'
-          pretend.messages.should.eql [
-            [ 'testing', 'tester', 'test' ]
-            [ 'testing', 'hubot', 'testing' ]
-            [ 'testing', 'hubot', 'hi tester' ]
-            [ 'testing', 'hubot', 'welcome to The Hub' ]
-          ]
-
-        it 'renders messages with remembered user data', -> co ->
-          pretend.robot.hear /remember i like (.*)/, res ->
-            improv.rememberForUser res.message.user.id, 'fav', res.match[1]
-          yield pretend.user('blueboi').send('remember i like blue')
-          yield pretend.lastListen().send 'i know you like ${this.fav}'
-          pretend.messages.should.eql [
-            [ 'blueboi', 'remember i like blue' ]
-            [ 'hubot', 'i know you like blue' ]
-          ]
-
-        it 'does not confuse user memory in succession', -> co ->
-          pretend.robot.hear /remember i like (.*)/, res ->
-            improv.rememberForUser res.message.user.id, 'fav', res.match[1]
-          yield pretend.user('blueboi').send('remember i like blue')
-          blueres = pretend.lastListen()
-          yield pretend.user('redkid').send('remember i like red')
-          redres = pretend.lastListen()
-          blueres.reply 'i know you like ${this.fav}'
-          redres.reply 'i know you like ${this.fav}'
-          pretend.messages.should.eql [
-            [ 'blueboi', 'remember i like blue' ]
-            [ 'redkid', 'remember i like red' ]
-            [ 'hubot', '@blueboi i know you like blue' ]
-            [ 'hubot', '@redkid i know you like red' ]
-          ]
-
-      # beforeEach ->
-      #   @middleware = sinon.spy improv.middleware
-      #   @parse = sinon.spy improv.parse
-
-      # TODO: Tests below fail because spied methods are just exported clones
-      # of the improv functions called by middleware (i think)
-
-      # it 'gets called whenever robot sends', ->
-      #   @middleware.should.have.calledTwice
-
-      # it 'only parses strings with expressions', ->
-      #   @parse.should.have.calledOnce


only invoke msg as a function if it's a function, not in any case it's not a string

--- lib/debug.coffee
+++ lib/debug.coffee
@@ -5,7 +5,7 @@
 exports.log = (msg, level = 1) ->
         if debug_level < level
                 return
-        if typeof msg isnt "string"
+        if typeof msg is "function"
                 msg = msg()
         console.warn "#{(' ' for i in [0..indent]).join('')}#{msg}"


Restrict extra option tags to be string

Restrict extra option tags to be string

--- lib/debug.coffee
+++ lib/debug.coffee
@@ -30,9 +30,10 @@
     enabled

   enableExtra: (tag) ->
-    extraOpts.add tag
+    console.assert tag, 'Are you kidding me? What do you want to enable?'
+    extraOpts.add tag.toString()
   disableExtra: (tag) ->
-    extraOpts.delete tag
+    extraOpts.delete tag.toString()
   isExtraEnabled: (tag) ->
     extraOpts.has tag
   getAllExtraOptionsAsArray: ->


[TEST] test that incorrect routes return 404

--- test/src/routes.coffee
+++ test/src/routes.coffee
@@ -41,9 +41,11 @@
         return context

 ((Vows.describe "Voicious' Routes").addBatch
-    'GET /'     : api.respondsWith 200
+    'GET /aRouteThatShouldNotExist' : api.respondsWith 404
+    'GET /'                         : api.respondsWith 200
 ).export module

 ((Vows.describe "Voicious' Static Server").addBatch
+    'GET /public/someIncorrectFile.jpg' : api.respondsWith 404
     'GET /public/core/css/style.css'    : api.respondsWith 200
 ).export module


Minor formatting change

Minor formatting change

--- tapestry-core/src/main/coffeescript/org/apache/tapestry5/t5-core-dom-prototype.coffee
+++ tapestry-core/src/main/coffeescript/org/apache/tapestry5/t5-core-dom-prototype.coffee
@@ -547,4 +547,4 @@
     # Returns a wrapped version of the document.body element. Because all Tapestry JavaScript occurs
     # inside a block at the end of the document, inside the `<body`> element, it is assumed that
     # it is always safe to get the body.
-    body: -> (wrapElement document.body)+    body: -> wrapElement document.body


Fix backwards caching logic Thought there was something fishy about this.

--- api/routes/gallery.coffee
+++ api/routes/gallery.coffee
@@ -24,7 +24,7 @@
     app.options '/gallery/:year/:month/:day', options ['GET']

     app.get '/gallery/stats', (req, res, next) ->
-        if statsCache.updated == 0 || moment().subtract(1, 'minutes').valueOf() < statsCache.updated
+        if moment().subtract(1, 'minutes').valueOf() > statsCache.updated
             stats = {
                 count: 0
                 years: {}


remove X-Powered-By header

remove X-Powered-By header

--- lib/index.coffee
+++ lib/index.coffee
@@ -12,6 +12,10 @@
 app.use express.query()
 app.use express.methodOverride()

+app.use (req, res, next) ->
+  res.removeHeader("X-Powered-By")
+  next()
+
 config.set('app', app)
 module.exports._app = app
 module.exports._express = express


Refactored out the redis listening code

Refactored out the redis listening code

--- lib/index.coffee
+++ lib/index.coffee
@@ -120,8 +120,7 @@
             if Math.random() < 0.4
               snapshotDb.setSnapshot cName, docName, {v:opData.v, data:doc} unless snapshotDb.closed

-    # Callback called with (err, op stream)
-    observe: (cName, docName, v, callback) ->
+    _subscribe: (channel, callback) -> # Subscribe to a redis pubsub channel and get a nodejs stream out
       stream = new Readable objectMode:yes

       # This function is for notifying us that the stream is empty and needs data.
@@ -130,7 +129,6 @@
       # that is any better than the buffer implementation in nodejs streams themselves.
       stream._read = ->

-      opChannel = getDocOpChannel cName, docName
       redisObserver = redisLib.createClient redis.port, redis.host, redis.options

       open = true
@@ -138,36 +136,44 @@
         throw new Error 'Stream already closed' unless open
         stream.push null
         open = false
-        redisObserver.unsubscribe opChannel
+        redisObserver.unsubscribe channel
         redisObserver.quit()
+
+      redisObserver.on 'message', (_channel, msg) ->
+        assert open
+        return unless _channel is channel
+        data = JSON.parse msg
+        stream.push data
+
+      redisObserver.subscribe channel, (err) ->
+        if err
+          stream.destroy()
+          callback err, null
+        else
+          callback null, stream
+
+    # Callback called with (err, op stream). v must be in the past or present. Behaviour
+    # with a future v is undefined (because I don't think thats an interesting case).
+    observe: (cName, docName, v, callback) ->
+      opChannel = getDocOpChannel cName, docName

       # Subscribe redis to the stream first so we don't miss out on any operations
       # while we're getting the history
-      queue = []
-      redisObserver.on 'message', (channel, msg) ->
-        assert open
-        return unless channel is opChannel
-        opData = JSON.parse msg
-
-        if queue
-          queue.push opData
-        else
-          assert opData.v is v
-          v++
-          stream.push opData
-
-      redisObserver.subscribe opChannel, (err) ->
-        if err
-          stream.destroy()
-          return callback? err
-
-        # Get all ops from v to current
+      @_subscribe opChannel, (err, stream) ->
+        callback err if err
+
+        # From here on, we need to call stream.destroy() if there are errors.
         getOps cName, docName, v, (err, data) ->
           if err
             stream.destroy()
-            return callback? err
-
-          callback? null, stream
+            return callback err
+
+          # Ok, so if there's anything in the stream right now, it might overlap with the
+          # historical operations. We'll pump the reader and (probably!) prefix it with the
+          # getOps result.
+          queue = (d while d = stream.read())
+
+          callback null, stream

           # First send all the operations between v and when we called getOps
           for d in data
@@ -179,8 +185,6 @@
             assert d.v is v
             v++
             stream.push d
-          # Mark all future ops on the stream to go straight to the stream.
-          queue = null

     # Callback called with (err, {v, data})
     fetch: (cName, docName, callback) ->


remove unnecessary async module

remove unnecessary async module

--- lib/index.coffee
+++ lib/index.coffee
@@ -1,4 +1,3 @@
-async    = require 'async'
 buildSearch = require 'stream-search-helper'
 buildChain  = require 'chain-builder'


Update app.main comments.

Update app.main comments.

--- app/client/js/main.coffee
+++ app/client/js/main.coffee
@@ -7,15 +7,15 @@
 ###
 app.main = (data) ->

-  # DI container is magic tool, check: github.com/steida/closure-dicontainer.
+  # Learn something about: github.com/steida/closure-dicontainer
   container = new app.DiContainer

-  # Inject document.body to App constructor.
+  # Configure runtime values.
   container.configure
     resolve: App
     with: element: document.body

-  # Instantiate App with all their dependencies resolved.
+  # Run app with resolved dependencies.
   container.resolveApp()

 goog.exportSymbol 'app.main', app.main


further isolate the Firefox longpolling hack

--- lib/assets/javascripts/firehose/long_poll.js.coffee
+++ lib/assets/javascripts/firehose/long_poll.js.coffee
@@ -50,33 +50,14 @@
       timeout: @_timeout
       success: @_success
       error: @_error
-      xhr: ->
-        # TODO - This while `xrh` attr is a stupid hack to deal with CORS short-comings in jQuery in Firefox.
-        # This ticket can be viewed at http://bugs.jquery.com/ticket/10338. Once jQuery is
-        # upgraded to this version, we can probably remove this, but be sure you test the
-        # crap out of Firefox!
-        #
-        # Its also worth noting that I had to localize this monkey-patch to the Firehose.LongPoll
-        # consumer because a previous global patch on jQuery.ajaxSettings.xhr was breaking regular IE7
-        # loading. I figured its better to localize this anyway to solve that problem and loading order issues.
-        xhr = jQuery.ajaxSettings.xhr()
-        getAllResponseHeaders = xhr.getAllResponseHeaders
-        xhr.getAllResponseHeaders = ->
-          allHeaders = getAllResponseHeaders.call(xhr)
-          return allHeaders if allHeaders
-          allHeaders = ""
-          for headerName in [ "Cache-Control", "Content-Language", "Content-Type", "Expires", "Last-Modified", "Pragma" ]
-            do (headerName) ->
-              allHeaders += headerName + ": " + xhr.getResponseHeader(headerName) + "\n"  if xhr.getResponseHeader(headerName)
-          allHeaders
-        xhr
+      xhr: hackedXHR

       complete: (jqXhr) =>
         # Get the last sequence from the server if specified.
         if jqXhr.status == 200
           @_lastMessageSequence = jqXhr.getResponseHeader(@messageSequenceHeader)
           if @_lastMessageSequence == null
-            console.log 'ERROR: Unable to get last message sequnce from header'
+            console?.log 'ERROR: Unable to get last message sequnce from header'

   stop: =>
     @_stopRequestLoop = true
@@ -120,3 +101,26 @@

     # Reconnect with delay
     setTimeout @_request, @_retryDelay
+
+# NB: This is a stupid hack to deal with CORS short-comings in jQuery in
+# Firefox. There is a ticket for this: http://bugs.jquery.com/ticket/10338
+# Once jQuery is upgraded to this version we can probably remove this, but be
+# sure you test the crap out of Firefox!
+#
+# Its also worth noting that I had to localize this monkey-patch to the
+# Firehose.LongPoll consumer because a previous global patch on
+# jQuery.ajaxSettings.xhr was breaking regular IE7 loading. Better to localize
+# this anyway to solve that problem and loading order issues.
+hackedXHR = ->
+  xhr = jQuery.ajaxSettings.xhr()
+  originalFun = xhr.getAllResponseHeaders
+  xhr.getAllResponseHeaders = ->
+    XHR_HEADERS = [
+      "Cache-Control", "Content-Language", "Content-Type"
+      "Expires", "Last-Modified", "Pragma"
+    ]
+    return allHeaders if (allHeaders = originalFun.call xhr)?
+    lines = for name in XHR_HEADERS when xhr.getResponseHeader(name)?
+      "#{name}: #{xhr.getResponseHeader name}"
+    lines.join '\n'
+  xhr


Remove disabled Talk link.

Remove disabled Talk link.

--- app/collections/show.cjsx
+++ app/collections/show.cjsx
@@ -16,7 +16,6 @@
   collectionPage:
     settings: 'Settings'
     collaborators: 'Collaborators'
-    talk: 'Talk'
   collectionsPageWrapper:
     error: 'There was an error retrieving this collection.'

@@ -55,10 +54,6 @@
             <Link to="collection-collaborators" params={params} className="tabbed-content-tab">
               <Translate content="collectionPage.collaborators" />
             </Link>}
-
-          <Link to="collection-talk" params={params} className="tabbed-content-tab" style={pointerEvents: 'none', opacity: 0.7}>
-            <Translate content="collectionPage.talk" />
-          </Link>
         </nav>
         <div className="collection-container">
           <RouteHandler user={@props.user} collection={@props.collection} roles={@props.roles} />


Start using callbacks with errors

Start using callbacks with errors

--- src/pagerduty.coffee
+++ src/pagerduty.coffee
@@ -13,6 +13,9 @@
 pagerNoop               = false if pagerNoop is "false" or pagerNoop  is "off"

 module.exports = (robot) ->
+
+  class PagerDutyError extends Error
+
   http = (path) ->
     auth =
     HttpClient.create("#{pagerDutyBaseUrl}#{path}")
@@ -26,15 +29,27 @@
       .query(query)
       .get() (err, res, body) ->
         if err?
-          return robot.emit 'error', err, msg
+          if cb.length is 1
+            robot.emit 'error', err, msg
+          else
+            cb(err)
+          return
         json_body = null
         switch res.statusCode
           when 200 then json_body = JSON.parse(body)
           else
-            console.log res.statusCode
-            console.log body
-            json_body = null
-        cb json_body
+            if cb.length is 1
+              console.log res.statusCode
+              console.log body
+              json_body = null
+            else
+              cb(new PagerDutyError("#{res.statusCode} back from #{url}"))
+              return
+
+        if cb.length is 1
+          cb json_body
+        else
+          cb null, json_body

   missingEnvironmentForApi = (msg) ->
     missingAnything = false
@@ -117,7 +132,9 @@
     query =
       status:  status
       sort_by: "incident_number:asc"
-    pagerduty.get msg, "/incidents", query, (json) ->
+    pagerduty.get msg, "/incidents", query, (err, json) ->
+      if err?
+        return
       cb(json.incidents)

   pagerduty =


Add auto detect to expected item count

Add auto detect to expected item count

--- spec/encoding-selector-spec.coffee
+++ spec/encoding-selector-spec.coffee
@@ -34,7 +34,8 @@
       encodingView = atom.workspaceView.find('.encoding-selector').view()
       expect(encodingView).toExist()
       encodings = require('../lib/encodings')
-      expect(encodingView.list.children('li').length).toBe Object.keys(encodings).length
+      expectedItemCount = Object.keys(encodings).length + 1 # Include Auto Detect
+      expect(encodingView.list.children('li').length).toBe expectedItemCount

   describe "when an encoding is selected", ->
     it "sets the new encoding on the editor", ->


add todo

add todo

--- src/jquery.coffee
+++ src/jquery.coffee
@@ -1,6 +1,8 @@

 # TODO i think this should work with asyncxml as well
 # FIXME  this should be in an own package
+# TODO listen on data and use innerHTML to create all dom elems at once
+#       http://blog.stevenlevithan.com/archives/faster-than-innerhtml

 jquerify = (tpl) ->
     tpl.xml._children = []


Update uk.coffee (#5574)

Update uk.coffee (#5574)


Update specs

Update specs

--- spec/tree-view-package-spec.coffee
+++ spec/tree-view-package-spec.coffee
@@ -3566,75 +3566,61 @@

   describe "showSelectedEntryInFileManager()", ->
     beforeEach ->
-      atom.notifications.clear()
-
-    it "displays the standard error output when the process fails", ->
-      {BufferedProcess} = require 'atom'
-      spyOn(BufferedProcess.prototype, 'spawn').andCallFake ->
-        EventEmitter = require 'events'
-        fakeProcess = new EventEmitter()
-        fakeProcess.send = ->
-        fakeProcess.kill = ->
-        fakeProcess.stdout = new EventEmitter()
-        fakeProcess.stdout.setEncoding = ->
-        fakeProcess.stderr = new EventEmitter()
-        fakeProcess.stderr.setEncoding = ->
-        @process = fakeProcess
-        process.nextTick ->
-          fakeProcess.stderr.emit('data', 'bad process')
-          fakeProcess.stderr.emit('close')
-          fakeProcess.stdout.emit('close')
-          fakeProcess.emit('exit')
-
+      spyOn(shell, 'showItemInFolder').andReturn(false)
+
+    it "does nothing if no entry is selected", ->
+      treeView.deselect()
       treeView.showSelectedEntryInFileManager()
-
-      waitsFor ->
-        atom.notifications.getNotifications().length is 1
-
-      runs ->
-        expect(atom.notifications.getNotifications()[0].getMessage()).toContain 'Opening folder'
-        expect(atom.notifications.getNotifications()[0].getMessage()).toContain 'failed'
-        expect(atom.notifications.getNotifications()[0].getDetail()).toContain 'bad process'
-
-    it "handle errors thrown when spawning the OS file manager", ->
-      spyOn(treeView, 'fileManagerCommandForPath').andReturn
-        command: path.normalize('/this/command/does/not/exist')
-        label: 'OS file manager'
-        args: ['foo']
-
+      expect(shell.showItemInFolder).not.toHaveBeenCalled()
+
+    it "shows the selected entry in the OS's file manager", ->
       treeView.showSelectedEntryInFileManager()
-
-      waitsFor ->
-        atom.notifications.getNotifications().length is 1
-
-      runs ->
-        expect(atom.notifications.getNotifications()[0].getMessage()).toContain 'Opening folder in OS file manager failed'
-        expect(atom.notifications.getNotifications()[0].getDetail()).toContain if process.platform is 'win32' then 'cannot find the path' else 'ENOENT'
+      expect(shell.showItemInFolder).toHaveBeenCalled()
+
+    it "displays a notification if showing the file fails", ->
+      treeView.showSelectedEntryInFileManager()
+      expect(atom.notifications.getNotifications().length).toBe(1)
+      expect(atom.notifications.getNotifications()[0].getMessage()).toContain('Unable to show')

   describe "showCurrentFileInFileManager()", ->
+    beforeEach ->
+      spyOn(shell, 'showItemInFolder').andReturn(false)
+
     it "does nothing when no file is opened", ->
       expect(atom.workspace.getCenter().getPaneItems().length).toBe(0)
-      expect(treeView.showCurrentFileInFileManager()).toBeUndefined()
+
+      treeView.showCurrentFileInFileManager()
+      expect(shell.showItemInFolder).not.toHaveBeenCalled()

     it "does nothing when only an untitled tab is opened", ->
       waitsForPromise ->
         atom.workspace.open()
+
       runs ->
         workspaceElement.focus()
-        expect(treeView.showCurrentFileInFileManager()).toBeUndefined()
-
-    it "shows file in file manager when some file is opened", ->
+        treeView.showCurrentFileInFileManager()
+        expect(shell.showItemInFolder).not.toHaveBeenCalled()
+
+    it "shows the current file in the OS's file manager", ->
       filePath = path.join(os.tmpdir(), 'non-project-file.txt')
       fs.writeFileSync(filePath, 'test')
       waitsForPromise ->
         atom.workspace.open(filePath)

       runs ->
-        {BufferedProcess} = require 'atom'
-        spyOn(BufferedProcess.prototype, 'spawn').andCallFake ->
-        fileManagerProcess = treeView.showCurrentFileInFileManager()
-        expect(fileManagerProcess instanceof BufferedProcess).toBeTruthy()
-        fileManagerProcess.kill()
+        treeView.showCurrentFileInFileManager()
+        expect(shell.showItemInFolder).toHaveBeenCalled()
+
+    it "shows a notification if showing the file fails", ->
+      filePath = path.join(os.tmpdir(), 'non-project-file.txt')
+      fs.writeFileSync(filePath, 'test')
+      waitsForPromise ->
+        atom.workspace.open(filePath)
+
+      runs ->
+        treeView.showCurrentFileInFileManager()
+        expect(atom.notifications.getNotifications().length).toBe(1)
+        expect(atom.notifications.getNotifications()[0].getMessage()).toContain('Unable to show')

   describe "when reloading a directory with deletions and additions", ->
     it "does not throw an error (regression)", ->


Provide implementation of Object.fromEntries (#998)

Refactors away from using Object.fromEntries since it's not supported in older browsers.
Resolves https://app.honeybadger.io/projects/61305/faults/51925696

--- app/assets/javascripts/init.coffee
+++ app/assets/javascripts/init.coffee
@@ -167,7 +167,11 @@
   $bike_search_link = $("#search_bikes_header_link")
   return true unless $bike_search_link.length
   bike_search_query = $bike_search_link[0].search
-  params = Object.fromEntries(bike_search_query.replace("?", "").split("&").map((e) => e.split("=")))
+
+  tuples_to_object = (tuples_list) =>
+     tuples_list.reduce ((obj, tuple) -> (obj[tuple[0]] = tuple[1]) && obj), {}
+  params = tuples_to_object(bike_search_query.replace("?", "").split("&").map((e) => e.split("=")))
+
   params["stolenness"] = "proximity"
   params["location"] = location
   params["distance"] = distance


Make navigation pills function

Make navigation pills function

--- app/assets/javascripts/site.coffee
+++ app/assets/javascripts/site.coffee
@@ -1,3 +1,4 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/
+$(document).ready ->
+  $('.nav-pills li').click ->
+    $(@).siblings().removeClass('active');
+    $(@).addClass('active');


[fix] syntax errors

[fix] syntax errors

--- app.coffee
+++ app.coffee
@@ -31,6 +31,7 @@

 app.configure('development', ()->
   app.use(express.errorHandler());
+)

 #url definitions
 app.get('/', routes.index)
@@ -42,6 +43,8 @@
   res.send 'abc'
   res.send 'def'

-http.createServer(app).listen(app.get('port'),() ->
-  console.log('Express server listening on port ' + app.get('port'));
+http.createServer(app).listen(
+  app.get('port')
+  () ->
+    console.log('Express server listening on port ' + app.get('port'));
 )


Fixed the boot error

Fixed the boot error

--- app.coffee
+++ app.coffee
@@ -16,7 +16,6 @@
 app.set('view engine', 'handlebars')

 if process.env.NO_LOGS? and process.env.NO_LOGS
-  console = {}
   console.log = () ->

 server.listen port, ()->


Allow disabling of HTTP frontend

Allow disabling of HTTP frontend

--- app.coffee
+++ app.coffee
@@ -41,8 +41,8 @@
 config.securityToken = ''
 config.userID = null
 config.upSince = new Date()
+config.enableFrontend = yes

-frontend = require './frontend'
 api = require './api'
 handlers = require './handlers'

@@ -53,7 +53,8 @@
 	# new session after login, refetch token
 	api.fetchSecurityToken -> api.getRoomList (roomList) ->
 		common.fatal 'No available rooms' if roomList.length is 0
-		do frontend.listen
+		do (require './frontend').listen if config.enableFrontend
+
 		api.joinRoom roomList[0].roomID, ->
 			do handlers.loadHandlers
 			api.sendMessage "I'm here!", yes, ->


rename fn

rename fn

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -36,7 +36,7 @@
 		@displayTitlePrompt() if @status.titleEmpty
 		@displayPrompt() if @status.empty
 		@bindEditorTitleEvents()
-		@bindEditorEvents()
+		@bindAllEvents()
 	setConnecting: (bool)->
 		@status.connecting = bool
 	setChanged: (bool)->
@@ -342,7 +342,6 @@
 	handleKeyUp: (e)->
 		# debug
 		$('#debug-keyup').text e.keyCode
-
 		switch e.keyCode
 			when 13
 				@divsToPs()
@@ -386,7 +385,7 @@
 				@updateTitlePrompt(true)
 			, 0

-	bindEditorEvents: ->
+	bindAllEvents: ->
 		$('#editor').on 'keydown', (e)=>
 			@hideTooltip()
 			@hideToolpad()


keydown fades tooltips

keydown fades tooltips

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -305,7 +305,7 @@

 	bindEditorEvents: ->
 		$('#editor').on 'keydown', (e)=>
-			@hideInsertion()
+			@hideTooltip()
 			@checkNew() if @status.new
 			@handleKeyDown(e)
 			@update()


remove the link even select the part of it

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -177,7 +177,6 @@
 	cancelLinkInput: ->
 		$('#toolpad li:first-child').removeClass 'toggled'
 	applyOrCancelUrl: (range, url)->
-		console.log 'applyOrCancelUrl'
 		@selection().removeAllRanges()
 		@selection().addRange range
 		if url
@@ -239,6 +238,12 @@
 		$('#toolpad').delegate '.cancel', 'click', (e)->
 			self.cancelLinkInput()
 			if self.linkRange
+				node = self.linkRange.commonAncestorContainer
+				if node.nodeType == 3
+					node = $(node).parent('a')
+					range = document.createRange()
+					range.selectNode node[0]
+					self.linkRange = range
 				self.applyOrCancelUrl self.linkRange, $(@).val()

 	update: ->


setup geometry

setup geometry

--- src/layout.coffee
+++ src/layout.coffee
@@ -26,9 +26,20 @@
 #     inspector
 #       active_sprite.state...

+setup_geometry = (dict) ->
+  my.extend dict, {
+    padding: my.padding
+    margin: my.margin
+    outer_width: (world) -> world.get('width') + 2 * world.get('padding')
+    inner_width: (world) -> world.get('width') - 2 * world.get('margin')
+    available_width: (world) -> world.up.get('inner_width') - 2 * world.get('padding')
+  }
+
+
 set_font_size = (dict, size) -> dict.name_style = {font_size: size}

 exports.layout = (game_dict) ->
+  setup_geometry(game_dict)
   level_dict = game_dict[my.key.children][0]
   console.log "!! layout level_dict", level_dict
   set_font_size(level_dict, 24)


In ProxyTwitterSearch, keep only returned messages

Since it is intended to be displayed on a wall, we want to be able to control the loop

--- frontend/src/js/datasources.coffee
+++ frontend/src/js/datasources.coffee
@@ -187,7 +187,7 @@
             $.getJSON(@url, @params)
                 .success((data)=>
                     if data.statuses.length
-                        @messages = data.statuses.concat(@messages)
+                        @messages = data.statuses
                         @params['since_id'] = @messages[0].id_str
                     if callback
                         if @messages.length


patch refactor issue

patch refactor issue

--- src/editor.coffee
+++ src/editor.coffee
@@ -171,7 +171,7 @@
     if delta
       oldDelta = @delta
       @delta = oldDelta.compose(delta)
-      @.emit(@.constructor.events.TEXT_CHANGE, delta, 'user')
+      @scribe.emit(@scribe.constructor.events.TEXT_CHANGE, delta, 'user')
     @selection.update(delta != false)

   doSilently: (fn) ->


export 'open' explicitly pass in the file path to open.

--- src/editor.coffee
+++ src/editor.coffee
@@ -32,7 +32,9 @@
   File.write filename, editor.getSession().getValue()
   setMode()
   Chrome.setDirty false
-open = ->
+exports.open = (path) ->
+  filename = path
+
   if Dir.isDir(filename)
     Process.cwd(filename)
     Chrome.title _.last filename.split('/')
@@ -71,8 +73,7 @@

 bindKey 'open', 'Command-O', (env, args, request) ->
   if file = Chrome.openPanel()
-    filename = file
-    open()
+    open(file)

 bindKey 'openURL', 'Command-Shift-O', (env, args, request) ->
   if url = prompt "Enter URL:"


Chat: AppController: Disable chat requests

--- client/landing/app/Applications/Chat.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Chat.kdapplication/AppController.coffee
@@ -9,8 +9,8 @@
     super options, data

     notificationController = KD.getSingleton 'notificationController'
-    notificationController.on 'chatRequest', @bound 'handleChatRequest'
-    notificationController.on 'chatOpen', @bound 'handleChatOpen'
+    # notificationController.on 'chatRequest', @bound 'handleChatRequest'
+    # notificationController.on 'chatOpen', @bound 'handleChatOpen'

     @channels = {}


Allow string or array for excluded modules

--- src/engine.coffee
+++ src/engine.coffee
@@ -254,7 +254,7 @@

     defaultModules = ["Shadows", "HUD", "Developer", "SaveState", "Selector", "Collision", "Tilemap", "FPSCounter"]
     modules = defaultModules.concat(I.includedModules)
-    modules = modules.without(I.excludedModules)
+    modules = modules.without([].concat(I.excludedModules))

     modules.each (moduleName) ->
       throw "#Engine.#{moduleName} is not a valid engine module" unless Engine[moduleName]


Update fr.coffee (#5606)

Update fr.coffee (#5606)

--- app/locale/fr.coco.coffee
+++ app/locale/fr.coco.coffee
@@ -1,29 +1,29 @@
 module.exports = nativeDescription: "français", englishDescription: "French", translation:

   new_home:
-#    title: "CodeCombat - Coding games to learn Python and JavaScript"
+    title: "CodeCombat - Jeux de codage pour apprendre Python et JavaScript"
 #    meta_description: "Learn typed code through a programming game. Learn Python, JavaScript, and HTML as you solve puzzles and learn to make your own coding games and websites."
 #    built_for_teachers_title: "A Coding Game Built with Teachers in Mind"
 #    built_for_teachers_blurb: "Teaching kids to code can often feel overwhelming. CodeCombat helps all educators teach students how to code in either JavaScript or Python, two of the most popular programming languages. With a comprehensive curriculum that includes six computer science units and reinforces learning through project-based game development and web development units, kids will progress on a journey from basic syntax to recursion!"
-#    built_for_teachers_subtitle1: "Computer Science"
+    built_for_teachers_subtitle1: "Informatique"
 #    built_for_teachers_subblurb1: "Starting with our free Introduction to Computer Science course, students master core coding concepts such as while/for loops, functions, and algorithms."
-#    built_for_teachers_subtitle2: "Game Development"
+    built_for_teachers_subtitle2: "Développement de jeux"
 #    built_for_teachers_subblurb2: "Learners construct mazes and use basic input handling to code their own games that can be shared with friends and family."
-#    built_for_teachers_subtitle3: "Web Development"
+    built_for_teachers_subtitle3: "Développement Web"
 #    built_for_teachers_subblurb3: "Using HTML, CSS, and jQuery, learners flex their creative muscles to program their own webpages with a custom URL to share with their classmates."
-#    century_skills_title: "21st Century Skills"
+    century_skills_title: "Compétences du 21e siècle"
 #    century_skills_blurb1: "Students Don't Just Level Up Their Hero, They Level Up Themselves"
 #    century_skills_quote1: "You mess up…so then you think about all of the possible ways to fix it, and then try again. I wouldn't be able to get here without trying hard."
-#    century_skills_subtitle1: "Critical Thinking"
+    century_skills_subtitle1: "Pensée critique"
 #    century_skills_subblurb1: "With coding puzzles that are naturally scaffolded into increasingly challenging levels, CodeCombat's programming game ensures kids are always practicing critical thinking."
 #    century_skills_quote2: "Everyone else was making mazes, so I thought, ‘capture the flag’ and that’s what I did."
-#    century_skills_subtitle2: "Creativity"
+    century_skills_subtitle2: "Créativité"
 #    century_skills_subblurb2: "CodeCombat encourages students to showcase their creativity by building and sharing their own games and webpages."
 #    century_skills_quote3: "If I got stuck on a level. I would work with people around me until we were all able to figure it out."
-#    century_skills_subtitle3: "Collaboration"
+    century_skills_subtitle3: "Collaboration"
 #    century_skills_subblurb3: "Throughout the game, there are opportunities for students to collaborate when they get stuck and to work together using our pair programming guide."
 #    century_skills_quote4: "I’ve always had aspirations of designing video games and learning how to code ... this is giving me a great starting point."
-#    century_skills_subtitle4: "Communication"
+    century_skills_subtitle4: "Communication"
 #    century_skills_subblurb4: "Coding requires kids to practice new forms of communication, including communicating with the computer itself and conveying their ideas using the most efficient code."
 #    classroom_in_box_title: "We Strive To:"
 #    classroom_in_box_blurb1: "Engage every student so that they believe coding is for them."
@@ -37,16 +37,16 @@
 #    creativity_rigor_subtitle3: "Built for all teachers, regardless of experience"
 #    creativity_rigor_blurb3: "CodeCombat’s self-paced, standards-aligned curriculum makes teaching computer science possible for everyone. CodeCombat equips teachers with the training, instructional resources, and dedicated support to feel confident and successful in the classroom."
 #    featured_partners_title1: "Featured In"
-#    featured_partners_title2: "Awards & Partners"
-#    featured_partners_blurb1: "CollegeBoard Endorsed Provider"
-#    featured_partners_blurb2: "Best Creativity Tool for Students"
-#    featured_partners_blurb3: "Top Pick for Learning"
+    featured_partners_title2: "Prix et partenaires"
+    featured_partners_blurb1: "Fournisseur approuvé par le CollegeBoard"
+    featured_partners_blurb2: "Meilleur outil de créativité pour les étudiants"
+#    featured_partners_blurb3: "Le meilleur choix pour l'apprentissage"
 #    featured_partners_blurb4: "Code.org Official Partner"
 #    featured_partners_blurb5: "CSforAll Official Member"
-#    featured_partners_blurb6: "Hour of Code Activity Partner"
-#    for_leaders_title: "For School Leaders"
+    featured_partners_blurb6: "Heure de l'activité du code Partenaire"
+    for_leaders_title: "Pour les chefs d'établissement"
 #    for_leaders_blurb: "A Comprehensive, Standards-Aligned Computer Science Program"
-#    for_leaders_subtitle1: "Easy Implementation"
+    for_leaders_subtitle1: "Mise en œuvre facile"
 #    for_leaders_subblurb1: "A web-based program that requires no IT support. Get started in under 5 minutes using Google or Clever Single Sign-On (SSO)."
 #    for_leaders_subtitle2: "Full Coding Curriculum"
 #    for_leaders_subblurb2: "A standards-aligned curriculum with instructional resources and professional development to enable any teacher to teach computer science."
@@ -58,21 +58,21 @@
 #    for_teachers_blurb: "Tools to Unlock Student Potential"
 #    for_teachers_subtitle1: "Project-Based Learning"
 #    for_teachers_subblurb1: "Promote creativity, problem-solving, and confidence in project-based courses where students develop their own games and webpages."
-#    for_teachers_subtitle2: "Teacher Dashboard"
+    for_teachers_subtitle2: "Tableau de bord de l'enseignant"
 #    for_teachers_subblurb2: "View data on student progress, discover curriculum resources, and access real-time support to empower student learning."
 #    for_teachers_subtitle3: "Built-in Assessments"
 #    for_teachers_subblurb3: "Personalize instruction and ensure students understand core concepts with formative and summative assessments."
 #    for_teachers_subtitle4: "Automatic Differentiation"
 #    for_teachers_subblurb4: "Engage all learners in a diverse classroom with practice levels that adapt to each student's learning needs."
 #    game_based_blurb: "CodeCombat is a game-based computer science program where students type real code and see their characters react in real time."
-#    get_started: "Get started"
+    get_started: "Commencez à travailler"
 #    global_title: "Join Our Global Community of Learners and Educators"
-#    global_subtitle1: "Learners"
-#    global_subtitle2: "Lines of Code"
-#    global_subtitle3: "Teachers"
-#    global_subtitle4: "Countries"
-#    go_to_my_classes: "Go to my classes"
-#    go_to_my_courses: "Go to my courses"
+    global_subtitle1: "Apprenants"
+    global_subtitle2: "Lignes de code"
+    global_subtitle3: "Enseignants"
+    global_subtitle4: "Pays"
+    go_to_my_classes: "Aller à mes cours"
+    go_to_my_courses: "Aller à mes cours"
 #    quotes_quote1: "Name any program online, I’ve tried it. None of them match up to CodeCombat. Any teacher who wants their students to learn how to code... start here!"
 #    quotes_quote2: " I was surprised about how easy and intuitive CodeCombat makes learning computer science. The scores on the AP exam were much higher than I expected and I believe CodeCombat is the reason why this was the case."
 #    quotes_quote3: "CodeCombat has been the most beneficial for teaching my students real-life coding capabilities. My husband is a software engineer and he has tested out all of my programs. He put this as his top choice."
@@ -85,11 +85,11 @@
 #    teachers_love_codecombat_blurb2: "Would recommend CodeCombat to other computer science teachers"
 #    teachers_love_codecombat_blurb3: "Say that CodeCombat helps them support students’ problem solving abilities"
 #    teachers_love_codecombat_subblurb: "In partnership with McREL International, a leader in research-based guidance and evaluations of educational technology."
-#    try_the_game: "Try the game"
+    try_the_game: "Essayez le jeu"
     classroom_edition: "Édition Salle de Classe:"
     learn_to_code: "Apprendre à programmer:"
     play_now: "Allons-y !"
-#    im_an_educator: "I'm an Educator"
+    im_an_educator: "Je suis un éducateur"
     im_a_teacher: "Je suis un enseignant"
     im_a_student: "Je suis un étudiant"
     learn_more: "Apprenez-en plus"


Adding engine modules

Adding engine modules

--- src/engine.coffee
+++ src/engine.coffee
@@ -75,37 +75,6 @@
       eachObject: (iterator) ->
         I.objects.each iterator

-      find: (selector) ->
-        results = []
-
-        matcher = EngineSelector.generate(selector)
-
-        I.objects.each (object) ->
-          results.push object if matcher.match object
-
-        $.extend results, EngineSelector.instanceMethods
-
-      collides: (bounds, sourceObject) ->
-        I.objects.inject false, (collided, object) ->
-          collided || (object.solid() && (object != sourceObject) && object.collides(bounds))
-
-      rayCollides: (source, direction, sourceObject) ->
-        hits = I.objects.map (object) ->
-          hit = object.solid() && (object != sourceObject) && Collision.rayRectangle(source, direction, object.centeredBounds())
-          hit.object = object if hit
-
-          hit
-
-        nearestDistance = Infinity
-        nearestHit = null
-
-        hits.each (hit) ->
-          if hit && (d = hit.distance(source)) < nearestDistance
-            nearestDistance = d
-            nearestHit = hit
-
-        nearestHit
-
       start: () ->
         unless intervalId
           intervalId = setInterval(() ->
@@ -135,7 +104,7 @@
     self.attrAccessor "cameraTransform"
     self.include Bindable

-    defaultModules = ["Shadows", "HUD", "Developer", "SaveState"]
+    defaultModules = ["Shadows", "HUD", "Developer", "SaveState", "Selector", "Collision"]
     modules = defaultModules.concat(I.includedModules)
     modules = modules.without(I.excludedModules)


set routes

set routes

--- app/scripts/routes.coffee
+++ app/scripts/routes.coffee
@@ -0,0 +1,3 @@
+@Yabyap.Router.map (->
+
+)


Update zh-HANS.coffee

Update zh-HANS.coffee

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -848,15 +848,15 @@
 #    track_concepts1: "Track concepts"
     track_concepts2a: "由每位学生学习"
     track_concepts2b: "由每位成员学习"
-#    track_concepts3a: "Track levels completed for each student"
-#    track_concepts3b: "Track levels completed for each member"
-#    track_concepts4a: "See your students'"
-#    track_concepts4b: "See your members'"
-#    track_concepts5: "solutions"
-#    track_concepts6a: "Sort students by name or progress"
-#    track_concepts6b: "Sort members by name or progress"
-#    track_concepts7: "Requires invitation"
-#    track_concepts8: "to join"
+    track_concepts3a: "查看每位同学达到的等级"
+    track_concepts3b: "查看每位成员达到的等级"
+    track_concepts4a: "查看你的学生"
+    track_concepts4b: "查看你的成员"
+    track_concepts5: "解决方案"
+    track_concepts6a: "按姓名或进度排序学生"
+    track_concepts6b: "按姓名或进度排序成员"
+    track_concepts7: "需要邀请"
+    track_concepts8: "来加入"
 #    private_require_sub: "Private clans require a subscription to create or join."

   courses:
@@ -1258,27 +1258,27 @@
 #    purchase_code1: "Subscription Codes can be redeemed to add premium subscription time to one or more CodeCombat accounts."
 #    purchase_code2: "Each CodeCombat account can only redeem a particular Subscription Code once."
 #    purchase_code3: "Subscription Code months will be added to the end of any existing subscription on the account."
-#    users: "Users"
+    users: "玩家"
 #    months: "Months"
     purchase_total: "总共"
     purchase_button: "提交购买"
     your_codes: "你的订阅码：" # {change}
     redeem_codes: "兑换订阅码"
-#    prepaid_code: "Prepaid Code"
-#    lookup_code: "Lookup prepaid code"
-#    apply_account: "Apply to your account"
-#    copy_link: "You can copy the code's link and send it to someone."
-#    quantity: "Quantity"
-#    redeemed: "Redeemed"
-#    no_codes: "No codes yet!"
-#    you_can1: "You can"
-#    you_can2: "purchase a prepaid code"
-#    you_can3: "that can be applied to your own account or given to others."
+    prepaid_code: "预购码"
+    lookup_code: "查看预购码"
+    apply_account: "使用到您的账户"
+    copy_link: "您可以复制此代码链接发送给别人。"
+    quantity: "数量"
+    redeemed: "兑换"
+    no_codes: "还没有兑换码！"
+    you_can1: "您可以"
+    you_can2: "购买一个预购码"
+    you_can3: "使用在您的账户或者给其他人。"

   loading_error:
     could_not_load: "载入失败"
     connection_failure: "连接失败。"
-    unauthorized: "您需要登录才行。 您是不是把 cookies 禁用了?"
+    unauthorized: "您需要登录才行。您是不是禁用了cookie？"
     forbidden: "您没有权限。"
     not_found: "没找到。"
     not_allowed: "方法不允许。"


Revert "Removed FactlinkConfig"

Revert "Removed FactlinkConfig"

This reverts commit 688d95fdc9f301edf9707b00d89e092c514878a0.

--- app/js/publisher_poc/nu.nl.coffee
+++ app/js/publisher_poc/nu.nl.coffee
@@ -6,9 +6,23 @@
   */
 (function(e,t,n){typeof module!="undefined"&&module.exports?module.exports=n():typeof define=="function"&&define.amd?define(n):t[e]=n()})("$script",this,function(){function v(e,t){for(var n=0,r=e.length;n<r;++n)if(!t(e[n]))return f;return 1}function m(e,t){v(e,function(e){return!t(e)})}function g(e,t,a){function d(e){return e.call?e():r[e]}function b(){if(!--p){r[h]=1,c&&c();for(var e in s)v(e.split("|"),d)&&!m(s[e],d)&&(s[e]=[])}}e=e[l]?e:[e];var f=t&&t.call,c=f?t:a,h=f?e.join(""):t,p=e.length;return setTimeout(function(){m(e,function(e){if(e===null)return b();if(u[e])return h&&(i[h]=1),u[e]==2&&b();u[e]=1,h&&(i[h]=1),y(!n.test(e)&&o?o+e+".js":e,b)})},0),g}function y(n,r){var i=e.createElement("script"),s=f;i.onload=i.onerror=i[d]=function(){if(i[h]&&!/^c|loade/.test(i[h])||s)return;i.onload=i[d]=null,s=1,u[n]=2,r()},i.async=1,i.src=n,t.insertBefore(i,t.firstChild)}var e=document,t=e.getElementsByTagName("head")[0],n=/^https?:\/\//,r={},i={},s={},o,u={},a="string",f=!1,l="push",c="DOMContentLoaded",h="readyState",p="addEventListener",d="onreadystatechange";return!e[h]&&e[p]&&(e[p](c,function b(){e.removeEventListener(c,b,f),e[h]="complete"},f),e[h]="loading"),g.get=y,g.order=function(e,t,n){(function r(i){i=e.shift(),e.length?g(i,r):g(i,t,n)})()},g.path=function(e){o=e},g.ready=function(e,t,n){e=e[l]?e:[e];var i=[];return!m(e,function(e){r[e]||i[l](e)})&&v(e,function(e){return r[e]})?t():!function(e){s[e]=s[e]||[],s[e][l](t),n&&n(i)}(e.join("|")),g},g.done=function(e){g([null],e)},g})
 `
+localConfig =
+  api: "http://localhost:3000"
+  lib: "http://localhost:8000/lib/dist"
+  srcPath: "/factlink.core.js"
+  url: window.location.href
+
+remoteConfig =
+  api: "https://factlink-testserver.inverselink.com:443"
+  lib: "https://factlink-static-testserver.inverselink.com:443/lib/dist"
+  srcPath: "/factlink.core.min.js"
+  url: window.location.href
+
+window.FactlinkConfig = localConfig
+
 $script(['//code.jquery.com/jquery-1.10.2.min.js',
          '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js',
-         "#{window.FactlinkConfig.lib}/factlink_loader_basic.#{window.FactlinkConfig.minified}js"
+         "#{window.FactlinkConfig.lib}/factlink_loader_basic.js"
          ], 'myBundle')

 $script.ready 'myBundle', ->


How to test custom types?

How to test custom types?

--- test/acceptance.coffee
+++ test/acceptance.coffee
@@ -26,6 +26,14 @@
       Given -> @wrapped = @v Array, Function, @cb
       When -> @wrapped @fn
       Then -> expect(@cb).to.have.been.calledWith ['foo', 'bar'], @fn
+
+    # TODO: How to test? Coffeescript wipes out stringify by virtue of closure
+    #context 'with a custom type', ->
+      #Given -> @Foo = class Foo
+      #Given -> @wrapped = @v Array, @Foo, @cb
+      #Given -> @foo = new @Foo()
+      #When -> @wrapped [], @foo
+      #Then -> expect(@cb).to.have.been.calledWith [], @foo

   describe 'array syntax', ->
     context 'all types match', ->
@@ -160,3 +168,5 @@
       Given -> @wrapped = @v '-+oo[a]+1_s*a|[b]', @cb
       When -> @wrapped {foo: 'bar'}, [1], '', true, 2
       Then -> expect(@cb).to.have.been.calledWith {}, {foo: 'bar'}, [[1]], 0, 'foo bar', [true], 2
+
+    context 'custom', ->


Comment on why the xdg paths were set up this way

--- lib/paths.coffee
+++ lib/paths.coffee
@@ -73,6 +73,7 @@
     # Linux, non-OS X Unix, AIX, etc.
     xdg = process.env['XDG_RUNTIME_DIR']
     if xdg
+      # Ref https://github.com/jupyter/jupyter_core/commit/176b7a9067bfc20bfa97be5dae93912e2930a427#commitcomment-11331375
       return path.join(xdg, 'jupyter')
     return path.join(jupyterDataDir, 'runtime')


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -1,4 +1,4 @@
-Follow = (I={}) ->
+Follow = (I={}, self) ->
   Object.reverseMerge I,
     color: "blue"
     height: 32


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -6,6 +6,7 @@
 # Make an enemy follow the player
 player = GameObject
   health: 50
+  x:

 player.cooldown "health",
   target: 100


set all java executions to headless

set all java executions to headless

--- app/helper/executableHelper.coffee
+++ app/helper/executableHelper.coffee
@@ -62,7 +62,7 @@
   getExecutionType = (programType) ->
     switch programType
       when 'java'
-        return 'java -jar'
+        return 'java -Djava.awt.headless=true -jar'
       when 'coffeescript'
         return 'coffee'
       else


Fix IE bug whereby FrameCss wasn't accessible in jail_iframe

--- app/js/loader/loader_common.coffee
+++ app/js/loader/loader_common.coffee
@@ -110,7 +110,6 @@
   load_time_before_jail = new Date().getTime()

   jail_window = jslib_jail_iframe.contentWindow
-  jail_window.FrameCss = frame_style_code

   # Load iframe with script tag
   jslib_jail_doc = jail_window.document
@@ -118,6 +117,10 @@
   jslib_jail_doc.open()
   jslib_jail_doc.write '<!DOCTYPE html><title></title>'
   jslib_jail_doc.close()
+
+  jail_window.FrameCss = frame_style_code #warning: this MUST be placed after the doc write
+  #IE11 apparently "reinitializes" the window on doc write (at least, FrameCss isn't in scope otherwise)
+

   script_tag = jslib_jail_doc.createElement('script')
   script_tag.appendChild(jslib_jail_doc.createTextNode(jail_code))


Add Spec T004-001-003

Add Spec T004-001-003

--- spec/src/coffee/label_category/models/t004_label_category_spec.coffee
+++ spec/src/coffee/label_category/models/t004_label_category_spec.coffee
@@ -47,6 +47,21 @@
         should.strictEqual @root.get("childrens")["test1"].get("childrens")["test201"], undefined
         _(@root.get("childrens")["test1"].get("childrens")).keys().length.should.equal 200 - 2

+    context "003: multi a/a/a a/a/b a/b/a a/b/c b/a/a", ->
+      beforeEach ->
+        @root.parse_labels_recursive_func "a/a/a", @fake_label
+        @root.parse_labels_recursive_func "a/a/b", @fake_label
+        @root.parse_labels_recursive_func "a/b/a", @fake_label
+        @root.parse_labels_recursive_func "a/b/c", @fake_label
+        @root.parse_labels_recursive_func "b/a/a", @fake_label
+
+      it "001: check name", ->
+        @root.get("childrens")["a"].get("childrens")["a"].get("childrens")["a"].get("name").should.equal "a"
+        @root.get("childrens")["a"].get("childrens")["a"].get("childrens")["b"].get("name").should.equal "b"
+        @root.get("childrens")["a"].get("childrens")["b"].get("childrens")["a"].get("name").should.equal "a"
+        @root.get("childrens")["a"].get("childrens")["b"].get("childrens")["c"].get("name").should.equal "c"
+        @root.get("childrens")["b"].get("childrens")["a"].get("childrens")["a"].get("name").should.equal "a"
+
   context "002: properties", ->
     context "001: root", ->
       beforeEach ->


Optimize code.

Optimize code.

--- src/fs_utils/generated_file.coffee
+++ src/fs_utils/generated_file.coffee
@@ -14,10 +14,10 @@
     .filter (key) ->
       key in types
     .map (key) ->
-      config.files[key].order
+      config.files[key].order ? {}

-  before = helpers.flatten orders.map (type) -> (type?.before ? [])
-  after = helpers.flatten orders.map (type) -> (type?.after ? [])
+  before = helpers.flatten orders.map (type) -> (type.before ? [])
+  after = helpers.flatten orders.map (type) -> (type.after ? [])
   vendorConvention = config._normalized.conventions.vendor
   {before, after, vendorConvention}


file: explicitly set properties for test readability

--- test/file/index.coffee
+++ test/file/index.coffee
@@ -909,6 +909,7 @@
       .file
         target: "#{scratch}/new_file_perm"
         content: 'Hello'
+        mode: 0o0644
         backup: '.bck1'
       , (err, status) ->
         status.should.be.true() unless err
@@ -920,6 +921,7 @@
         target: "#{scratch}/new_file_perm"
         content: 'Hello World'
         backup: '.bck2'
+        mode: 0o0644
         backup_mode: 0o0640
       , (err, status) ->
         status.should.be.true() unless err


guv-heroku-workerstats: Remove hardcoding, use --started option instead

Now minimally useful, can be used to analyze and compare
times spent in process startup/shutdown that is controlled
by application author.
Fixes #58

--- src/heroku.coffee
+++ src/heroku.coffee
@@ -187,29 +187,26 @@
       shutdown_length: stops.length
     return callback null, results

-# TODO: add a guv-update-jobstats tool
+# TODO: calculate whole delay from scaling to up by default, and scaling down to down
 # TODO: allow to separate between (module) loading time, and startup time
-# TODO: calculate whole delay from scaling to up by default, and scaling down to down
-# TODO: add tool for calculating 'waste' percentage. Ratio of time spent processing versus startup+shutdown
+# TODO: add a guv-update-jobstats tool, would modify 'boot' and 'shutdown' values in config
+# TODO: add tool for calculating scaling 'waste'. Ratio of time spent processing vs startup+shutdown
+# MAYBE: allow specifying subsets in time?
+# MAYBE: allow ignoring certain dynos?
 exports.startuptime_main = () ->
   program = require 'commander'

   filename = null
-
-# TODO: allow specifying subsets in time?
   program
     .arguments('<heroku.log>')
-#    .option('-f --file <FILE.guv>', 'Configuration file', String, '')
+    .option('--started <regexp>', 'Regular expression matching output sent by process when started', String, 'noflo-runtime-msgflo started')
     .action (f, env) ->
       filename = f
     .parse(process.argv)
-
-  # FIXME: make configurable
+  program.started = new RegExp program.started
+
   started = (info) ->
-    return true if startsWith info, 'noflo-runtime-msgflo started'
-    return true if info.indexOf('started using broker') != -1
-    return false
-
+    return program.started.test info
   analyzeStartups filename, started, (err, res) ->
     throw err if err
     console.log res


improve comments

improve comments

--- src/hinoki.coffee
+++ src/hinoki.coffee
@@ -112,6 +112,9 @@
     dependencyPaths = dependencyNames.map (x) ->
       hinoki.coerceToArray(x).concat newPath

+    # this code is reached synchronously from the start of the function call
+    # without interleaving.
+
     dependenciesPromise = hinoki.get remainingContainers, dependencyPaths, debug

     factoryCallResultPromise = dependenciesPromise.then (dependencyValues) ->
@@ -121,9 +124,9 @@

       hinoki.callFactory resolution.container, newPath, resolution.factory, dependencyValues, debug

-    # we are caching the promise
-    # still in the same sync run (no interleaving) from the initial call of the
-    # function
+    # cache the promise.
+    # this code is reached synchronously from the start of the function call
+    # without interleaving.

     unless nocache
       resolution.container.promisesAwaitingResolution ?= {}


added a lot of ES6 generator tests

added a lot of ES6 generator tests

--- test/generators.coffee
+++ test/generators.coffee
@@ -3,22 +3,40 @@
 #
 # * Generator Definition

-test "generator as argument", ->
+test "most basic generator support", ->
   ok -> yield 0

-test "generator definition", ->
+test "empty generator", ->
+  x = do -> yield return
+
+  y = x.next()
+  ok y.value is undefined and y.done is true
+
+test "generator iteration", ->
   x = do ->
     yield 0
     yield 1
     yield 2
   y = x.next()
   ok y.value is 0 and y.done is false
+
   y = x.next()
   ok y.value is 1 and y.done is false
+
   y = x.next()
   ok y.value is 2 and y.done is false
+
   y = x.next()
   ok y.value is undefined and y.done is true
+
+test "last line yields are returned", ->
+  x = do ->
+    yield 3
+  y = x.next()
+  ok y.value is 3 and y.done is false
+
+  y = x.next 42
+  ok y.value is 42 and y.done is true

 test "bound generator", ->
   obj =
@@ -41,8 +59,8 @@
 test "error if `yield` occurs outside of a function", ->
   throws -> CoffeeScript.compile 'yield 1'

-test "error if `yield from` occurs outside of a function", ->
-  throws -> CoffeeScript.compile 'yield from 1'
+test "`yield` by itself not at the end of a function errors", ->
+  throws -> CoffeeScript.compile 'x = -> yield; return'

 test "`yield from` support", ->
   x = do ->
@@ -59,14 +77,8 @@
   arrayEq y.value, [1, 2]
   ok y.done is true

-test "empty generator", ->
-  x = do -> yield return
-
-  y = x.next()
-  ok y.value is undefined and y.done is true
-
-test "`yield` by itself not at the end of a function errors", ->
-  throws -> CoffeeScript.compile 'x = -> yield; return'
+test "error if `yield from` occurs outside of a function", ->
+  throws -> CoffeeScript.compile 'yield from 1'

 test "`yield from` at the end of a function errors", ->
   throws -> CoffeeScript.compile 'x = -> x = 1; yield from'
@@ -79,6 +91,103 @@

   y = x.next 1
   ok y.value is 3 and y.done is true
+
+test "yielding if statements", ->
+  x = do -> yield if true then 3 else 4
+
+  y = x.next()
+  ok y.value is 3 and y.done is false
+
+  y = x.next 42
+  ok y.value is 42 and y.done is true
+
+test "yield in for loop expressions", ->
+  x = do ->
+    y = for i in [1..3]
+      yield i * 2
+
+  z = x.next()
+  ok z.value is 2 and z.done is false
+
+  z = x.next 10
+  ok z.value is 4 and z.done is false
+
+  z = x.next 20
+  ok z.value is 6 and z.done is false
+
+  z = x.next 30
+  arrayEq z.value, [10, 20, 30]
+  ok z.done is true
+
+test "yielding for loop expressions", ->
+  x = do ->
+    yield for i in [1..3]
+      i * 2
+
+  y = x.next()
+  arrayEq y.value, [2, 4, 6]
+  ok y.done is false
+
+  y = x.next 42
+  ok y.value is 42 and y.done is true
+
+test "yield in switch expressions", ->
+  x = do ->
+    y = switch yield 1
+      when 2 then yield 1337
+      else 1336
+
+  z = x.next()
+  ok z.value is 1 and z.done is false
+
+  z = x.next 2
+  ok z.value is 1337 and z.done is false
+
+  z = x.next 3
+  ok z.value is 3 and z.done is true
+
+test "yielding switch expressions", ->
+  x = do ->
+    yield switch 1337
+      when 1337 then 1338
+      else 1336
+
+  y = x.next()
+  ok y.value is 1338 and y.done is false
+
+  y = x.next 42
+  ok y.value is 42 and y.done is true
+
+test "yield in try expressions", ->
+  x = do ->
+    try yield 1 catch
+
+  y = x.next()
+  ok y.value is 1 and y.done is false
+
+  y = x.next 42
+  ok y.value is 42 and y.done is true
+
+test "yielding try expressions", ->
+  x = do ->
+    yield try 1
+
+  y = x.next()
+  ok y.value is 1 and y.done is false
+
+  y = x.next 42
+  ok y.value is 42 and y.done is true
+
+test "`yield` can be thrown", ->
+  x = do ->
+    throw yield null
+  x.next()
+  throws -> x.next new Error "boom"
+
+test "`throw` can be yielded", ->
+  x = do ->
+    yield throw new Error "boom"
+  throws -> x.next()

 test "symbolic operators has precedence over the `yield`", ->
   symbolic   = '+ - * / << >> & | || && ** ^ // or and'.split ' '
@@ -97,41 +206,3 @@
     mapped  = CoffeeScript.eval "(arr) ->       (#{expression} for i in arr)"

     arrayEq mapped(values), collect yielded values
-
-test "for loop expressions", ->
-  x = do ->
-    y = for i in [1..3]
-      yield i * 2
-
-  z = x.next()
-  ok z.value is 2 and z.done is false
-  z = x.next 10
-  ok z.value is 4 and z.done is false
-  z = x.next 20
-  ok z.value is 6 and z.done is false
-  z = x.next 30
-  arrayEq z.value, [10, 20, 30]
-  ok z.done is true
-
-test "switch expressions", ->
-  x = do ->
-    y = switch yield 1
-      when 2 then yield 1337
-
-  z = x.next()
-  ok z.value is 1 and z.done is false
-  z = x.next 2
-  ok z.value is 1337 and z.done is false
-  z = x.next 3
-  ok z.value is 3 and z.done is true
-
-test "`yield` can be thrown", ->
-  x = do ->
-    throw yield null
-  x.next()
-  throws -> x.next new Error "boom"
-
-test "`throw` can be yielded", ->
-  x = do ->
-    yield throw new Error "boom"
-  throws -> x.next()


Small tweak.

Small tweak.

--- test/jnoid_spec.coffee
+++ test/jnoid_spec.coffee
@@ -80,16 +80,11 @@
       Jnoid.unit(),
       done

-describe 'sequentially', ->
-  it 'sends all events', (done)->
-    h.expectValues [1, 2, 3],
-      Jnoid.sequentially(10, [1, 2, 3]),
-      done
-
-describe 'later', ->
-  it 'sends one event', (done)->
-    h.expectValues [1],
-      Jnoid.later(10, 1),
+describe 'map', ->
+  it 'transforms values', (done)->
+    stream = Jnoid.fromList([1, 2, 3])
+    h.expectValues [2, 4, 6],
+      stream.map((x)-> x * 2),
       done

 describe 'flatMap', ->
@@ -97,19 +92,6 @@
     stream = Jnoid.fromList([1, 2, 3])
     h.expectValues [1, 2, 3],
       stream.flatMap((x)-> Jnoid.unit(x)),
-      done
-
-  it 'can be aliased as bind', (done)->
-    stream = Jnoid.fromList([1, 2, 3])
-    h.expectValues [1, 2, 3],
-      stream.bind((x)-> Jnoid.unit(x)),
-      done
-
-describe 'map', ->
-  it 'transforms values', (done)->
-    stream = Jnoid.fromList([1, 2, 3])
-    h.expectValues [2, 4, 6],
-      stream.map((x)-> x * 2),
       done

 describe 'merge', ->
@@ -203,3 +185,15 @@
     h.expectValues [10, 20, 30],
       stream.takeUntil(stopper),
       done
+
+describe 'sequentially', ->
+  it 'sends all events', (done)->
+    h.expectValues [1, 2, 3],
+      Jnoid.sequentially(10, [1, 2, 3]),
+      done
+
+describe 'later', ->
+  it 'sends one event', (done)->
+    h.expectValues [1],
+      Jnoid.later(10, 1),
+      done


White space cleanup in simple test.

White space cleanup in simple test.

--- test/mocha-test.coffee
+++ test/mocha-test.coffee
@@ -2,13 +2,13 @@
 # and chai are working with requirejs
 # and that we can make expectations correctly.

-describe 'Testing', () ->
-  describe 'With mocha and Chai', () ->
-    describe "should", () ->
+describe 'Testing',  ->
+  describe 'With mocha and Chai', ->
+    describe "should", ->
       should = require('chai').should()
-      it 'should knows about equal', () ->
+      it 'should knows about equal', ->
         5.should.equal(5)
-    describe "expect", () ->
+    describe "expect", ->
       expect = require('chai').expect
-      it "knows about to.equal too", () ->
+      it "knows about to.equal too", ->
         expect(1).to.equal(1)


Toggle storage backend tests with an env var

--- test/model.test.coffee
+++ test/model.test.coffee
@@ -6,31 +6,46 @@
 processors = require '../src/processors'

 SENTINEL = MarkovModel.sentinel
+CHOSEN_CLASSES = ['memory', 'redis', 'postgres']
+if process.env.MEMORYTEST_USE?
+  CHOSEN_CLASSES = process.env.MEMORYTEST_USE.split(/\s+/).map (klass) -> klass.toLowerCase()

 describe 'MarkovModel', ->

   storageClasses = []

-  storageClasses.push
-    name: 'Memory'
-    constructor: storage.memory
-    connStr: ''
-
-  if process.env.REDIS_URL?
+  if 'memory' in CHOSEN_CLASSES
     storageClasses.push
-      name: 'Redis'
-      constructor: storage.redis
-      connStr: process.env.REDIS_URL
+      name: 'Memory'
+      constructor: storage.memory
+      connStr: ''
+
+  if 'redis' in CHOSEN_CLASSES
+    if process.env.REDIS_URL?
+      storageClasses.push
+        name: 'Redis'
+        constructor: storage.redis
+        connStr: process.env.REDIS_URL
+    else
+      it 'is missing ${REDIS_URL} in the test environment', ->
+        expect.fail 'missing environment variable'
   else
     it 'should be tested with a redis URL as ${REDIS_URL}'

-  if process.env.DATABASE_URL?
-    storageClasses.push
-      name: 'Postgres'
-      constructor: storage.postgres
-      connStr: process.env.DATABASE_URL
+  if 'postgres' in CHOSEN_CLASSES
+    if process.env.DATABASE_URL?
+      storageClasses.push
+        name: 'Postgres'
+        constructor: storage.postgres
+        connStr: process.env.DATABASE_URL
+    else
+      it 'is missing ${DATABASE_URL} in the test environment', ->
+        expect.fail 'missing environment variable'
   else
     it 'should be tested with a Postgres database at ${DATABASE_URL}'
+
+  it 'should exercise at least one storage implementation', ->
+    expect(storageClasses).to.not.be.empty

   generate = (storageClass) ->
     describe "with #{storageClass.name} storage", ->
@@ -180,7 +195,7 @@
               'a b c2 d2': 0

             ITERATION_COUNT = 10000
-            TOLERANCE = ITERATION_COUNT / 100
+            TOLERANCE = ITERATION_COUNT / 10

             generate = (n, cb) ->
               model.generate [], 10, (err, states) ->


reverse order for fair organizer and fair apps

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -232,8 +232,8 @@
   app.use require "../apps/profile"
   app.use require "../apps/user_profile"
   app.use require "../apps/partner"
+  app.use require "../apps/fair"
   app.use require "../apps/fair_organizer"
-  app.use require "../apps/fair"
   app.use require "../apps/user"
   app.use require "../apps/style_guide"
   app.use require "../apps/auth"


tests: Replace eventually.match checks to work in newer chai version

Change-type: patch
Signed-off-by: Thodoris Greasidis <5791bdc4541e79bd43d586e3c3eff1e39c16665f@balena.io>

--- tests/integration/models/device.spec.coffee
+++ tests/integration/models/device.spec.coffee
@@ -630,12 +630,14 @@
 					resin.models.device.enableDeviceUrl(@device.id)

 				it 'should eventually return the correct device url given a shorter uuid', ->
-					promise = resin.models.device.getDeviceUrl(@device.uuid.slice(0, 7))
-					m.chai.expect(promise).to.eventually.match(/[a-z0-9]{62}/)
+					resin.models.device.getDeviceUrl(@device.uuid.slice(0, 7))
+					.then (deviceUrl) ->
+						m.chai.expect(deviceUrl).to.match(/[a-z0-9]{62}/)

 				it 'should eventually return the correct device url given an id', ->
-					promise = resin.models.device.getDeviceUrl(@device.id)
-					m.chai.expect(promise).to.eventually.match(/[a-z0-9]{62}/)
+					resin.models.device.getDeviceUrl(@device.id)
+					.then (deviceUrl) ->
+						m.chai.expect(deviceUrl).to.match(/[a-z0-9]{62}/)

 				it 'should eventually be an absolute url given a uuid', ->
 					resin.models.device.getDeviceUrl(@device.uuid)


Fixed IE compatibility issue with Javascript .indexOf() function—replaced with jQuery.inArray().

--- src/ajax-chosen.coffee
+++ src/ajax-chosen.coffee
@@ -89,7 +89,7 @@
             # Iterate through the given data and inject the <option> elements into
             # the DOM if it doesn't exist in the selector already
             $.each items, (value, text) ->
-              if selected_values.indexOf(value + "-" + text) == -1
+              if $.inArray(value + "-" + text, selected_values) == -1
                 $("<option />")
                   .attr('value', value)
                   .html(text)


Only show installed version when different

--- src/app/package-config-view.coffee
+++ src/app/package-config-view.coffee
@@ -34,7 +34,7 @@
     @name.text(@pack.name)

     installedVersion = atom.getLoadedPackage(@pack.name)?.getVersion()
-    if installedVersion
+    if installedVersion and @pack.version isnt installedVersion
       @versions.text("Version: #{@pack.version} (#{installedVersion} installed)")
     else
       @versions.text("Version: #{@pack.version}")


Guard against possibly null repo

Guard against possibly null repo

--- src/app/project.coffee
+++ src/app/project.coffee
@@ -83,7 +83,7 @@
     @ignoreRepositoryPath(path)

   ignoreRepositoryPath: (path) ->
-    config.get("core.hideGitIgnoredFiles") and @repo.isPathIgnored(fs.join(@getPath(), path))
+    config.get("core.hideGitIgnoredFiles") and @repo?.isPathIgnored(fs.join(@getPath(), path))

   resolve: (filePath) ->
     filePath = fs.join(@getPath(), filePath) unless filePath[0] == '/'


Use single quotes when shelling out to ag.

This prevents `sh` from interpreting $ and \ as special characters.

--- src/app/project.coffee
+++ src/app/project.coffee
@@ -162,8 +162,7 @@
     _.remove(@buffers, buffer)

   scan: (regex, iterator) ->
-    escapedRegex = regex.source.replace("\\", "\\\\")
-    command = "#{require.resolve('ag')} --ackmate \"#{escapedRegex}\" \"#{@getPath()}\""
+    command = "#{require.resolve('ag')} --ackmate '#{regex.source}' '#{@getPath()}'"
     bufferedData = ""

     state = 'readingPath'


enh: apply normalization to imported data to support user-generated fields

--- src/application.coffee
+++ src/application.coffee
@@ -1,6 +1,6 @@
 "use strict"
 {Tessellation} = require "./hyperbolic_tessellation.coffee"
-{unity, inverseChain, appendChain, appendInverseChain, NodeHashMap, showNode, parseNode} = require "./vondyck_chain.coffee"
+{unity, inverseChain, appendChain, appendInverseChain, NodeHashMap, showNode, parseNode, node2array} = require "./vondyck_chain.coffee"
 {makeAppendRewrite, vdRule, eliminateFinalA} = require "./vondyck_rewriter.coffee"
 {RewriteRuleset, knuthBendix} = require "./knuth_bendix.coffee"

@@ -190,7 +190,13 @@
       if n isnt @getGroup().n or m isnt @getGroup().m
         console.log "Need to change grid"
         @setGridImpl n, m
-      @cells = importField parseFieldData match[3]
+
+      #normzlize chain coordinates, so that importing of user-generated data could be possible
+      normalizeChain = (chain) =>
+        rewritten = @appendRewrite unity, node2array(chain)
+        eliminateFinalA rewritten, @appendRewrite, @getGroup().n
+
+      @cells = importField parseFieldData(match[3]), null, normalizeChain
       console.log "Imported #{@cells.count} cells"
     catch e
       alert "Faield to import data: #{e}"


Typo fixes

Typo fixes

--- src/application.coffee
+++ src/application.coffee
@@ -14,9 +14,9 @@
             when "user"
                 return engine.User
             when "menu_ui"
-                return engine.ui.components.Menu()
+                return engine.ui.components.Menu
             when "menu_data_source"
-                return engine.ui.components.data_sources.MenuDataSource()
+                return engine.ui.components.data_sources.MenuDataSource

     start: ->
         User = @constructor_for_name "user"


sockjs/sockjs-client#52 - capture also 'close' not only 'end'

--- src/transport.coffee
+++ src/transport.coffee
@@ -214,9 +214,11 @@

     setUp: ->
         @thingy_end_cb = () => @didAbort(1006, "Connection closed")
+        @thingy.addListener('close', @thingy_end_cb)
         @thingy.addListener('end', @thingy_end_cb)

     tearDown: ->
+        @thingy.removeListener('close', @thingy_end_cb)
         @thingy.removeListener('end', @thingy_end_cb)
         @thingy_end_cb = null


1.0 release

1.0 release

--- src/js/app.coffee
+++ src/js/app.coffee
@@ -27,7 +27,7 @@

 traktvRequest = (opt, success, failure) ->
-  # console.log "traktvRequest: opt: #{JSON.stringify opt}"
+  console.log "traktvRequest: opt: #{JSON.stringify opt}"
   # console.log success
   if typeof opt == 'string'
     opt = if opt.indexOf('http') == 0
@@ -69,12 +69,13 @@
   traktvRequest "/shows/#{showID}/progress/watched",
     (response, status, req) ->
       console.log "Reloading show #{showID}"
+      # console.log "response: #{JSON.stringify response}"
       item = i for i in shows when i.show.ids.trakt == showID

       item.next_episode = response.next_episode
       item.seasons = response.seasons
       success(item) if success?
-    failure if failure?
+    if failure? then failure else ->

 getToWatchList = (showList, callback) ->
   showListUpdated = showList[..]
@@ -155,13 +156,14 @@
     method: 'POST'
     data: request
     (response, status, req) ->
-      console.log "Check succeeded: #{JSON.stringify request}"
+      console.log "Check succeeded: req: #{JSON.stringify request}"
+      console.log "response: #{JSON.stringify response}"
       # console.log "#{index}: #{key}: #{value}" for key, value of index for index in shows
       for item in shows when item.show.ids.trakt == opt.showID
         for season in item.seasons when not opt.seasonNumber? or season.number == opt.seasonNumber
           for episode in season.episodes when not opt.episodeNumber? or episode.number == opt.episodeNumber
-            console.log "Marking as seen #{item.show.title} S#{season.number}E#{episode.number}"
             episode.completed = opt.completed
+            console.log "Marking as seen #{item.show.title} S#{season.number}E#{episode.number}, #{episode.completed}"
       success()
     (response, status, req) ->
       console.log "Check FAILURE"
@@ -336,10 +338,10 @@
       showID: data.showID
       episodeNumber: data.episodeNumber
       seasonNumber: data.seasonNumber
-      completed: not e.item.completed
+      completed: not data.completed
       () ->
-        element = e.item
-        isNowCompleted = not element.data.completed
+        isNowCompleted = not data.completed
+        console.log "episode is now #{JSON.stringify e.item}"

         if isNowCompleted
           element.data.completed = true
@@ -357,6 +359,7 @@
           # TODO: clean this mess using getEpisodeData
           reloadShow data.showID, (reloadedShow) ->
             console.log "RELOADED ShowID: #{reloadedShow.show.ids.trakt}, title: #{reloadedShow.show.title}"
+            # console.log "item: #{JSON.stringify reloadedShow}"
             if isNextEpisodeForItemAired(reloadedShow) and not element.isNextEpisodeListed
               element.isNextEpisodeListed = true

@@ -370,6 +373,11 @@
               console.log "toWatchMenu.item(#{e.sectionIndex}, #{e.section.items.length}, #{JSON.stringify newItem}"

               toWatchMenu.item(e.sectionIndex, e.section.items.length, newItem)
+      () ->
+        element.subtitle = element.data.previousSubtitle
+        delete element.data.previousSubtitle
+
+        toWatchMenu.item(e.sectionIndex, e.itemIndex, element)

   toWatchMenu.on 'select', (e) ->
     element = e.item


Fix I18n object marshaling

Fix I18n object marshaling

--- src/util/i18n.coffee
+++ src/util/i18n.coffee
@@ -22,15 +22,15 @@
   @method constructor
     Constructor of multi-language text
   ###
-  constructor: (obj) ->
-    @_text = obj
+  constructor: (@_obj) ->
+    @_text = @_obj
     return if typeof(@_text) == "string"
-    @_text = obj[lang]
+    @_text = @_obj[lang]
     return if typeof(@_text) == "string"
-    @_text = obj["en"]
+    @_text = @_obj["en"]
     return if typeof(@_text) == "string"
     @_text = "<translate error>"
-    console.warn("No translated text for object (#{obj})")
+    console.warn("No translated text for object (#{@_obj})")
     Object.freeze(this)
     return


Ensure use of "new" when instantiating a control.

--- src/layout.coffee
+++ src/layout.coffee
@@ -40,7 +40,7 @@
     layout = jQuery.event.special.layout
     layout._trackedElements = layout._trackedElements.add @
     # Send an initial layout event when the element is in the document.
-    Control( this ).inDocument ->
+    ( new Control( this )).inDocument ->
       ###
       Directly invoke the handler instead of triggering the event.
       If add() is invoked on an element that's already in the document,


Conditionally exclude deprecations in Marker

--- src/marker.coffee
+++ src/marker.coffee
@@ -1,5 +1,4 @@
 {extend, isEqual, omit, pick, size} = require 'underscore-plus'
-EmitterMixin = require('emissary').Emitter
 {Emitter} = require 'event-kit'
 Grim = require 'grim'
 Delegator = require 'delegato'
@@ -30,14 +29,13 @@
 # deleted. See {TextBuffer::markRange} for invalidation strategies.
 module.exports =
 class Marker
-  EmitterMixin.includeInto(this)
   Delegator.includeInto(this)
   Serializable.includeInto(this)

   @extractParams: (inputParams) ->
     outputParams = {}
     if inputParams?
-      @handleDeprecatedParams(inputParams)
+      @handleDeprecatedParams(inputParams) if Grim.includeDeprecatedAPIs
       extend(outputParams, pick(inputParams, OptionKeys))
       properties = omit(inputParams, OptionKeys)
       outputParams.properties = properties if size(properties) > 0
@@ -136,17 +134,6 @@
   onDidChange: (callback) ->
     @emitter.on 'did-change', callback

-  on: (eventName) ->
-    switch eventName
-      when 'changed'
-        Grim.deprecate("Use Marker::onDidChange instead")
-      when 'destroyed'
-        Grim.deprecate("Use Marker::onDidDestroy instead")
-      else
-        Grim.deprecate("Marker::on is deprecated. Use event subscription methods instead.")
-
-    EmitterMixin::on.apply(this, arguments)
-
   # Public: Returns the current {Range} of the marker. The range is immutable.
   getRange: ->
     @range
@@ -310,16 +297,6 @@
   getInvalidationStrategy: ->
     @invalidate

-  # Deprecated: Use ::getProperties instead
-  getAttributes: ->
-    Grim.deprecate("Use Marker::getProperties instead.")
-    @getProperties()
-
-  # Deprecated: Use ::setProperties instead
-  setAttributes: (args...) ->
-    Grim.deprecate("Use Marker::setProperties instead.")
-    @setProperties(args...)
-
   # Public: Returns an {Object} containing any custom properties associated with
   # the marker.
   getProperties: ->
@@ -346,7 +323,7 @@
     @manager.removeMarker(@id)
     @manager.intervals.remove(@id)
     @emitter.emit 'did-destroy'
-    @emit 'destroyed'
+    @emit 'destroyed' if Grim.includeDeprecatedAPIs

   extractParams: (params) ->
     params = @constructor.extractParams(params)
@@ -358,10 +335,6 @@
   # * `other` {Marker}
   compare: (other) ->
     @range.compare(other.range)
-
-  # Deprecated: Use ::matchesParams instead
-  matchesAttributes: (args...) ->
-    @matchesParams(args...)

   # Returns whether this marker matches the given parameters. The parameters
   # are the same as {MarkerManager::findMarkers}.
@@ -514,7 +487,7 @@
       @deferredChangeEvents.push(event)
     else
       @emitter.emit 'did-change', event
-      @emit 'changed', event
+      @emit 'changed', event if Grim.includeDeprecatedAPIs
     true

   # Updates the interval index on the marker manager with the marker's current
@@ -531,4 +504,30 @@

       for event in deferredChangeEvents
         @emitter.emit 'did-change', event
-        @emit 'changed', event
+        @emit 'changed', event if Grim.includeDeprecatedAPIs
+
+if Grim.includeDeprecatedAPIs
+  EmitterMixin = require('emissary').Emitter
+  EmitterMixin.includeInto(Marker)
+
+  Marker::on = (eventName) ->
+    switch eventName
+      when 'changed'
+        Grim.deprecate("Use Marker::onDidChange instead")
+      when 'destroyed'
+        Grim.deprecate("Use Marker::onDidDestroy instead")
+      else
+        Grim.deprecate("Marker::on is deprecated. Use event subscription methods instead.")
+
+    EmitterMixin::on.apply(this, arguments)
+
+  Marker::matchesAttributes = (args...) ->
+    @matchesParams(args...)
+
+  Marker::getAttributes = ->
+    Grim.deprecate("Use Marker::getProperties instead.")
+    @getProperties()
+
+  Marker::setAttributes = (args...) ->
+    Grim.deprecate("Use Marker::setProperties instead.")
+    @setProperties(args...)


KiteCache: expose generateQueryString method

--- client/Main/kite/kitecache.coffee
+++ client/Main/kite/kitecache.coffee
@@ -6,7 +6,7 @@
     console.log "[KC] ", rest...

-  generateQueryString = (options)->
+  @generateQueryString = (options)->

     keys = [ "username", "environment", "name",
              "version", "region", "hostname", "id" ]
@@ -40,7 +40,7 @@
   @unset = (query)->

     if typeof query is 'object'
-      query = generateQueryString query
+      query = @generateQueryString query

     delete storage[signed query]

@@ -49,7 +49,7 @@

   @cache = (query, kite)->

-    queryString = generateQueryString query
+    queryString = @generateQueryString query
     kite = proxifyTransport kite
     try storage[signed queryString] = JSON.stringify kite
     log "Kite cached with '#{queryString}' queryString."
@@ -57,7 +57,7 @@

   @get = (query)->

-    queryString = generateQueryString query
+    queryString = @generateQueryString query

     kite = storage[signed queryString]


* serializer fixed

* serializer fixed

--- lib/generators/barbecue/gui/templates/serializer.js.coffee
+++ lib/generators/barbecue/gui/templates/serializer.js.coffee
@@ -1,8 +1,8 @@
 <%= application_name.camelize %>.<%= class_name.camelize %>Serializer = DS.ActiveModelSerializer.extend DS.EmbeddedRecordsMixin,
-  images: [ <%= image_attributes.map {|a| "'#{a.name}'" }.join(',') %> ]
+  images: [ <%= image_attributes.map {|a| "'#{a.ember_name}'" }.join(',') %> ]
   attrs:
     <%- image_attributes.each do |attr| -%>
-    <%= attr.name %>:  { serialize: 'records', deserialize: 'records' }
+    <%= attr.ember_name %>:  { serialize: 'records', deserialize: 'records' }
     <%- end -%>

   typeForRoot: (key) ->


Fixed display of series title.

Fixed display of series title.

--- client/room_list/room_list.coffee
+++ client/room_list/room_list.coffee
@@ -20,8 +20,6 @@
     Session.get 'zoom'
   metadata: (metadata) ->
     if metadata
-      if metadata.series_title is 'Choose a Module...'
-        metadata.series_title = ''
       created = moment.unix(metadata.created)
       metadata.createdDisplay = created.format "HH:mm"
       serverNow = Session.get 'serverTime'


merging in the fixed branch for gfxmonk/break_issue

--- test/test_break.coffee
+++ test/test_break.coffee
@@ -1,3 +1,4 @@
+# Test with break at the top level.
 a: [1,2,3]
 call_with_lambda: (l) -> null
 for i in a
@@ -9,3 +10,19 @@

 ok a is null

+
+# Test with break *not* at the top level.
+some_func: (input) ->
+  takes_lambda: (l) -> null
+  for i in [1,2]
+    arbitraty_var: takes_lambda(->)
+    if input == 1
+      return 1
+    else
+      break
+
+  return 2
+
+ok some_func(1) is 1
+ok some_func(2) is 2
+


transition from current color instead of current state

--- framer/LayerStates.coffee
+++ framer/LayerStates.coffee
@@ -118,7 +118,7 @@

 				# Set all the values for keys that we couldn't animate
 				for k, v of properties
-					@layer[k] = v if not _.isNumber(v)
+					@layer[k] = v unless _.isNumber(v) or v instanceof Color

 				@emit Events.StateDidSwitch, _.last(@_previousStates), stateName, @


Common

Common

"toDash" fixed, so that it dashes "TypeGN250" to "type-gn250" instead
 of "type-g-n250"

--- src/base/Common.coffee
+++ src/base/Common.coffee
@@ -319,7 +319,7 @@
 # convert camel case to dash
 toDash = (s) ->
 	s = s + "U"
-	s1 = (s.substring(0,1) + s.substring(1).replace(/([A-Z](?![A-Z]))/g, ($1)->"-#{$1.toLowerCase()}"))
+	s1 = (s.substring(0,1) + s.substring(1).replace(/([A-Z](?![A-Z0-9]))/g, ($1)->"-#{$1.toLowerCase()}"))
 	s1 = s1.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
 	s1 = s1.substring(0,s1.length-2)
 	s1.replace(/\./g, "-")


Switch to simply using build

Switch to simply using build

--- bokehjs/gulp/tasks/default.coffee
+++ bokehjs/gulp/tasks/default.coffee
@@ -5,4 +5,4 @@

 gulp.task "default", (cb) ->
   console.log('Building BokehJS for developer mode ...')
-  runSequence(["scripts", "styles"], "install", "watch", cb)
+  runSequence("build", "install", "watch", cb)


update path to widgets/main.js

update path to widgets/main.js

--- bokehjs/gulp/tasks/scripts.coffee
+++ bokehjs/gulp/tasks/scripts.coffee
@@ -159,7 +159,7 @@
   preludeText = fs.readFileSync(preludePath, { encoding: 'utf8' })

   widgetsOpts = {
-    entries: [path.resolve(path.join(paths.buildDir.jsTree, 'widget/main.js'))]
+    entries: [path.resolve(path.join(paths.buildDir.jsTree, 'models/widgets/main.js'))]
     extensions: [".js"]
     debug: true
     preludePath: preludePath


Deprecate `changeFocus` option to Workspace::open

Its documentation was already removed, and it was already
deprecated in ::openSync.

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -377,11 +377,15 @@
   #   * `activatePane` A {Boolean} indicating whether to call {Pane::activate} on
   #     the containing pane. Defaults to `true`.
   openSync: (uri='', options={}) ->
-    deprecate("Don't use the `changeFocus` option") if options.changeFocus?
+    # TODO: Remove deprecated changeFocus option
+    if options.changeFocus?
+      deprecate("The `changeFocus` option has been renamed to `activatePane`")
+      options.activatePane = options.changeFocus
+      delete options.changeFocus

     {initialLine, initialColumn} = options
-    # TODO: Remove deprecated changeFocus option
-    activatePane = options.activatePane ? options.changeFocus ? true
+    activatePane = options.activatePane ? true
+
     uri = atom.project.resolve(uri)

     item = @activePane.itemForUri(uri)
@@ -395,7 +399,13 @@
     item

   openUriInPane: (uri, pane, options={}) ->
-    changeFocus = options.changeFocus ? true
+    # TODO: Remove deprecated changeFocus option
+    if options.changeFocus?
+      deprecate("The `changeFocus` option has been renamed to `activatePane`")
+      options.activatePane = options.changeFocus
+      delete options.changeFocus
+
+    activatePane = options.activatePane ? true

     if uri?
       item = pane.itemForUri(uri)
@@ -409,7 +419,7 @@
           @paneContainer.root = pane
         @itemOpened(item)
         pane.activateItem(item)
-        pane.activate() if changeFocus
+        pane.activate() if activatePane
         index = pane.getActiveItemIndex()
         @emit "uri-opened"
         @emitter.emit 'did-open', {uri, pane, item, index}


Actually working cache

Actually working cache

--- resources/assets/coffee/react/beatmap-discussions/new-discussion.coffee
+++ resources/assets/coffee/react/beatmap-discussions/new-discussion.coffee
@@ -31,7 +31,6 @@

     @throttledPost = _.throttle @post, 1000
     @handleKeyDown = InputHandler.textarea @handleKeyDownCallback
-    @cache = {}

     # FIXME: should save state on navigation?
     @state =
@@ -45,10 +44,6 @@
   componentDidMount: =>
     $(window).on 'throttled-resize.new-discussion', @setTop
     @inputBox?.focus() if @props.autoFocus
-
-
-  componentWillUpdate: =>
-    @cache = {}

   componentWillUnmount: =>
@@ -243,7 +238,10 @@
   nearbyDiscussions: =>
     return [] if !@state.timestamp? || @props.mode != 'timeline'

-    if !@cache.nearbyDiscussions? || @cache.nearbyDiscussions.timestamp != @state.timestamp
+    if @nearbyDiscussionsCache? && (@nearbyDiscussionsCache.beatmap != @props.currentBeatmap || @nearbyDiscussionsCache.timestamp != @state.timestamp)
+      @nearbyDiscussionsCache = null
+
+    if !@nearbyDiscussionsCache?
       discussions = {}

       for discussion in @props.currentDiscussions.timelineAllUsers
@@ -267,11 +265,12 @@

       shownDiscussions = discussions.d0 ? discussions.d100 ? discussions.d1000 ? discussions.other ? []

-      @cache.nearbyDiscussions =
+      @nearbyDiscussionsCache =
+        beatmap: @props.currentBeatmap
         timestamp: @state.timestamp
         discussions: _.sortBy shownDiscussions, 'timestamp'

-    @cache.nearbyDiscussions.discussions
+    @nearbyDiscussionsCache.discussions

   onFocus: =>


:memo: Add more comments

:memo: Add more comments

--- src/browser/squirrel-update.coffee
+++ src/browser/squirrel-update.coffee
@@ -37,6 +37,7 @@
 spawnUpdate = (args, callback) ->
   spawn(updateDotExe, args, callback)

+# Install the Open with Atom explorer context menu items via the registry.
 installContextMenu = (callback) ->
   addToRegistry = (args, callback) ->
     args.unshift('add')
@@ -55,6 +56,7 @@
     installMenu directoryKeyPath, ->
       installMenu(backgroundKeyPath, callback)

+# Uninstall the Open with Atom explorer context menu items via the registry.
 uninstallContextMenu = (callback) ->
   deleteFromRegistry = (keyPath, callback) ->
     spawnReg(['delete', keyPath, '/f'], callback)
@@ -63,6 +65,11 @@
     deleteFromRegistry directoryKeyPath, ->
       deleteFromRegistry(backgroundKeyPath, callback)

+# Add apm and atom.exe to the PATH
+#
+# This is done by adding .cmd shims to the root bin folder in the Atom
+# install directory that point to the newly installed versions inside
+# the versioned app directories.
 updatePath = (callback) ->
   installCommands = (callback) ->
     atomCommandPath = path.join(binFolder, 'atom.cmd')


Spawn setx directly

Spawn setx directly

--- src/browser/squirrel-update.coffee
+++ src/browser/squirrel-update.coffee
@@ -90,25 +90,25 @@
       lines = stdout.split(/[\r\n]+/).filter (line) -> line
       segments = lines[lines.length - 1]?.split('    ')
       if segments[1] is 'Path' and segments.length >= 3
-        envPath = segments?[3..].join('    ')
-        callback(null, envPath)
+        pathEnv = segments?[3..].join('    ')
+        callback(null, pathEnv)
       else
         callback(new Error('Registry query for PATH failed'))

-  addBinToPath = (envSegments, callback) ->
-    envSegments.push(binFolder)
-    args = ['setx', 'Path', envSegments.join(';')]
-    spawnReg(args, callback)
+  addBinToPath = (pathSegments, callback) ->
+    pathSegments.push(binFolder)
+    newPathEnv = pathSegments.join(';')
+    spawn('setx', ['Path', newPathEnv], callback)

   installCommands (error) ->
     return callback(error) if error?

-    getPath (error, envPath) ->
+    getPath (error, pathEnv) ->
       return callback(error) if error?

-      envSegments = envPath.split(';')
-      if envSegments.indexOf(binFolder) is -1
-        addBinToPath(envSegments, callback)
+      pathSegments = pathEnv.split(';')
+      if pathSegments.indexOf(binFolder) is -1
+        addBinToPath(pathSegments, callback)
       else
         callback()


--- atom/browser/api/lib/dialog.coffee
+++ atom/browser/api/lib/dialog.coffee
@@ -93,12 +93,19 @@
     options.detail ?= ''
     options.icon ?= null

+    # Choose a default button to get selected when dialog is cancelled.
     unless options.cancelId?
       options.cancelId = 0
       for text, i in options.buttons
         if text.toLowerCase() in ['cancel', 'no']
           options.cancelId = i
           break
+
+    # On OS X the "Cancel" is always get selected when dialog is cancelled.
+    if process.platform is 'darwin'
+      for text, i in options.buttons when text is 'Cancel'
+        options.cancelId = i
+        break

     binding.showMessageBox messageBoxType,
                            options.buttons,


Fix "name" of Error object not serialized

Close #3364.

--- atom/browser/lib/rpc-server.coffee
+++ atom/browser/lib/rpc-server.coffee
@@ -43,6 +43,8 @@
     meta.then = valueToMeta sender, value.then.bind(value)
   else if meta.type is 'error'
     meta.members = plainObjectToMeta value
+    # Error.name is not part of own properties.
+    meta.members.push {name: 'name', value: value.name}
   else if meta.type is 'date'
     meta.value = value.getTime()
   else


change thresholds

change thresholds

--- src/player.coffee
+++ src/player.coffee
@@ -42,7 +42,7 @@

     if value >= 0.5
       bet = if gameState.players[0]?.status == 'active'
-        500
+        600
       else
         300
       if value < 1


fixed #1102; lexer: cleaned up `unfinished`

--- lexer.coffee
+++ lexer.coffee
@@ -489,9 +489,8 @@
   # Are we in the midst of an unfinished expression?
   unfinished: ->
     LINE_CONTINUER.test(@chunk) or
-    (prev = last @tokens, 1) and prev[0] isnt '.' and
-      (value = @value()) and not value.reserved and
-      NO_NEWLINE.test(value) and not CODE.test(value) and not ASSIGNED.test(@chunk)
+    @tag() in ['\\', '.', '?.', 'UNARY', 'MATH', '+', '-', 'SHIFT', 'RELATION'
+               'COMPARE', 'LOGIC', 'COMPOUND_ASSIGN', 'THROW', 'EXTENDS']

   # Converts newlines for string literals.
   escapeLines: (str, heredoc) ->
@@ -610,17 +609,9 @@

 HEREDOC_ILLEGAL = /\*\//

-ASSIGNED        = /^\s*@?([$A-Za-z_][$\w\x7f-\uffff]*|['"].*['"])[^\n\S]*?[:=][^:=>]/
-
 LINE_CONTINUER  = /// ^ \s* (?: , | \??\.(?![.\d]) | :: ) ///

 TRAILING_SPACES = /\s+$/
-
-NO_NEWLINE      = /// ^ (?:            # non-capturing group
-  [-+*&|/%=<>!.\\][<>=&|]* |           # symbol operators
-  and | or | is(?:nt)? | n(?:ot|ew) |  # word operators
-  delete | typeof | instanceof
-) $ ///

 # Compound assignment tokens.
 COMPOUND_ASSIGN = [