remove pkg_path log

remove pkg_path log

--- lib/server.coffee
+++ lib/server.coffee
@@ -37,7 +37,6 @@
   # watch for file changes
   if not prod
     pkg_path = path.resolve "#{__dirname}/../../../pkg"
-    console.log pkg_path
     watcher = chokidar.watch pkg_path, {
       ignored: /(^\.|\.swp$|\.tmp$|~$)/
     }


CUI.DateTime

CUI.DateTime

- Fixed initializing date time with null value.

--- src/elements/DateTime/DateTime.coffee
+++ src/elements/DateTime/DateTime.coffee
@@ -342,7 +342,11 @@
 		@

 	getValueForDisplay: ->
-		value = @getValue().trim()
+		value = @getValue()?.trim?()
+
+		if isEmpty(value)
+			return ""
+
 		mom = @parse(value)

 		if mom.isValid()


ListView: automatically set last column to 'maximize', if not set.

--- src/elements/ListView/ListView.coffee
+++ src/elements/ListView/ListView.coffee
@@ -53,11 +53,16 @@
 			if col == "maximize"
 				# assert(@_maximize, "new ListView", "maximized columns can only exist inside an maximized ListView", opts: @opts)
 				assert(col_i >= @fixedColsCount, "new ListView", "maximized columns can only be in the non-fixed side of the ListView.", opts: @opts)
-
 				@__maxCols.push(col_i)

+		if @__maximize_horizontal and @__maxCols.length == 0
+			# auto-max the last column
+			len = @__cols.length - 1
+			if len >= @fixedColsCount
+				@__maxCols.push(len)
+				@__cols[len] = 'maximize'
+
 		# CUI.debug @fixedColsCount, @fixedRowsCount, @__maxCols, @__cols, @tools
-
 		@rowsCount = 0
 		@colsCount = @__cols.length


Clean up and add comments

Clean up and add comments

--- src/exprestive.coffee
+++ src/exprestive.coffee
@@ -4,20 +4,30 @@
 path = require 'path'

 class Exprestive
+
   constructor: (baseDir, @options = {}) ->
-    @appDir             = path.resolve baseDir, @options.appDir ? ''
-    @routesFilePath     = path.resolve @appDir, @options.routesFilePath ? 'routes'
+
+    # The directory where routes and controllers can be found
+    # Used only as a relative directory for @routesFilePath and @controllersDirPath
+    @appDir = path.resolve baseDir, @options.appDir ? ''
+
+    # Path to the routes file
+    @routesFilePath = path.resolve @appDir, @options.routesFilePath ? 'routes'
+
+    # Path to the directory to look for controllers
     @controllersDirPath = path.resolve @appDir, @options.controllersDirPath ? 'controllers'
+
+    # Router to register routes and pass to express as middleware
     @middlewareRouter = express.Router()

-  # Registers a route on the middlewareRouter
+  # Registers a route on @middlewareRouter
   addRoute: ({ httpMethod, url, controllerName, controllerAction }) ->
-    httpMethod = httpMethod.toLowerCase()
-    @middlewareRouter[httpMethod] url, => @controllers[camelCase controllerName][controllerAction] arguments...
+    @middlewareRouter[httpMethod.toLowerCase()] url, =>
+      @controllers[camelCase controllerName][controllerAction] arguments...

-  # Returns a hash of methods (GET, POST, PUT, DELETE) to be called in the routes file
+  # Returns the hash of methods passed to the routes file function
   getRoutesHelperMethods: ->
     helperMethods = {}
     for httpMethod in ['GET', 'POST', 'PUT', 'DELETE']
@@ -34,6 +44,7 @@
     @middlewareRouter

+  # Populates @controllers by instantiating controllers found in @controllerDirPath
   initializeControllers: ->
     @controllers = @options.controllers ? {}
     return if @options.controllers?
@@ -44,6 +55,7 @@
       @controllers[controllerName] = new Controller

+  # Sets the @routesMethod from the function exported from @routesFilePath
   initializeRoutes: ->
     return if @routesInitialized
     @routesInitialized = yes


Add tests for legacy API detection

Add tests for legacy API detection

--- spec/NetworkLifecycle.coffee
+++ spec/NetworkLifecycle.coffee
@@ -197,6 +197,32 @@
       loader.registerComponent 'process', 'Generator', processGenerator
       loader.registerComponent 'legacy', 'Sync', legacyBasic
       done()
+
+  describe 'recognizing API level', ->
+    it 'should recognize legacy component as such', (done) ->
+      loader.load 'legacy/Sync', (err, inst) ->
+        return done err if err
+        chai.expect(inst.isLegacy()).to.equal true
+        done()
+      return
+    it 'should recognize WirePattern component as non-legacy', (done) ->
+      loader.load 'wirepattern/Async', (err, inst) ->
+        return done err if err
+        chai.expect(inst.isLegacy()).to.equal false
+        done()
+      return
+    it 'should recognize Process API component as non-legacy', (done) ->
+      loader.load 'process/Async', (err, inst) ->
+        return done err if err
+        chai.expect(inst.isLegacy()).to.equal false
+        done()
+      return
+    it 'should recognize Graph component as non-legacy', (done) ->
+      loader.load 'Graph', (err, inst) ->
+        return done err if err
+        chai.expect(inst.isLegacy()).to.equal false
+        done()
+      return

   describe 'with single Process API component receiving IIP', ->
     c = null


spec: Cookie test should not rely on cookie's count

Other apps can also write cookie to `file://` protocol.

--- spec/api-session-spec.coffee
+++ spec/api-session-spec.coffee
@@ -5,6 +5,7 @@
 BrowserWindow = remote.require 'browser-window'

 describe 'session module', ->
+  @timeout 10000
   fixtures = path.resolve __dirname, 'fixtures'
   w = null
   url = "http://127.0.0.1"
@@ -22,29 +23,27 @@
     server.listen port, '127.0.0.1', ->
       {port} = server.address()
       w.loadUrl "#{url}:#{port}"
-      w.webContents.on 'did-finish-load', ()->
+      w.webContents.on 'did-finish-load', ->
         w.webContents.session.cookies.get {url: url}, (error, cookies) ->
-          throw error if error
-          assert.equal 1, cookies.length
+          return done(error) if error
           assert.equal 'type', cookies[0].name
           assert.equal 'dummy', cookies[0].value
           done()

-  it 'should overwrite the existent cookie', (done) ->
+  it 'should over-write the existent cookie', (done) ->
     w.loadUrl 'file://' + path.join(fixtures, 'page', 'a.html')
-    w.webContents.on 'did-finish-load', ()->
+    w.webContents.on 'did-finish-load', ->
       w.webContents.session.cookies.set {url: url, name: 'type', value: 'dummy2'}, (error) ->
-        throw error if error
+        return done(error) if error
         w.webContents.session.cookies.get {url: url}, (error, cookies_list) ->
-          throw error if error
-          assert.equal 1, cookies_list.length
+          return done(error) if error
           assert.equal 'type', cookies_list[0].name
           assert.equal 'dummy2', cookies_list[0].value
           done()

   it 'should set new cookie', (done) ->
     w.loadUrl 'file://' + path.join(fixtures, 'page', 'a.html')
-    w.webContents.on 'did-finish-load', ()->
+    w.webContents.on 'did-finish-load', ->
       w.webContents.session.cookies.set {url: url, name: 'key', value: 'dummy2'}, (error) ->
         return done(error) if error
         w.webContents.session.cookies.get {url: url}, (error, cookies_list) ->
@@ -52,24 +51,21 @@
           for cookie in cookies_list
             if cookie.name is 'key'
                assert.equal 'dummy2', cookie.value
-               done()
-               return
+               return done()
           done('Can not find cookie')

   it 'should remove cookies', (done) ->
     w.loadUrl 'file://' + path.join(fixtures, 'page', 'a.html')
-    w.webContents.on 'did-finish-load', ()->
-      w.webContents.session.cookies.get {url: url}, (error, cookies_list) ->
-        count = 0
-        for cookie in cookies_list
-          w.webContents.session.cookies.remove {url: url, name: cookie.name}, (error) ->
-            throw error if error
-            ++count
-            if count == cookies_list.length
-              w.webContents.session.cookies.get {url: url}, (error, cookies_list) ->
-                throw error if error
-                assert.equal 0, cookies_list.length
-                done()
+    w.webContents.on 'did-finish-load', ->
+      w.webContents.session.cookies.set {url: url, name: 'key', value: 'dummy2'}, (error) ->
+        return done(error) if error
+        w.webContents.session.cookies.remove {url: url, name: 'key'}, (error) ->
+          return done(error) if error
+          w.webContents.session.cookies.get {url: url}, (error, list) ->
+            return done(error) if error
+            for cookie in list when cookie.name is 'key'
+               return done('Cookie not deleted')
+            done()

   describe 'session.clearStorageData(options)', ->
     fixtures = path.resolve __dirname, 'fixtures'


Fix specs on OS X.

Fix specs on OS X.

--- spec/pathwatcher-spec.coffee
+++ spec/pathwatcher-spec.coffee
@@ -117,7 +117,7 @@
           throw new Error('test')
         catch e
           done()
-      fs.writeFileSync(tempFile, '')
+      fs.writeFileSync(tempFile, 'changed')

   describe 'when watching multiple files under the same directory', ->
     it 'fires the callbacks when both of the files are modifiled', ->
@@ -128,8 +128,8 @@
         called |= 1
       pathWatcher.watch tempFile2, (type, path) ->
         called |= 2
-      fs.writeFileSync(tempFile, '')
-      fs.writeFileSync(tempFile2, '')
+      fs.writeFileSync(tempFile, 'changed')
+      fs.writeFileSync(tempFile2, 'changed')
       waitsFor -> called == 3

     it 'shares the same handle watcher between the two files on Windows', ->


feature: disable catcher on nonstable rules

--- scripts-src/application.coffee
+++ scripts-src/application.coffee
@@ -259,8 +259,12 @@
         @ruleset_phase = phase

       onStep: (rulesetPhase)->
-        if (rulesetPhase is 0) and (gc = @spaceship_catcher)
-          gc.scan @gol
+        if (gc = @spaceship_catcher)
+          if (@ruleset.length > 1) and not @ruleset_enabled
+            alert "Rule is unstable, disabling catcher. Enable stabilization to make it available"
+            @disable_spaceship_catcher()
+          else
+            gc.scan @gol

       doStep: ->
           if @ruleset_enabled
@@ -589,11 +593,14 @@
           alert "Incorrect value of the analysis depth, will use #{maxSteps}"
         maxSteps
       enable_spaceship_catcher: ->
-        gol_application = this
+        if (@ruleset.length > 1) and not @ruleset_enabled
+          alert "Enable rule stabilization to run catcher"
+          return
         if @spaceship_catcher is null
           maxSteps = @_getAnalyzerMaxSteps()
           on_spaceship = (pattern)  =>
-            rule = gol_application.rule
+            rule = @rule
+            pattern = @_promoteToZeroPhase pattern
             if result=Cells.analyze(pattern, rule, {max_iters:maxSteps})
               if result.period?
                 if result.dx isnt 0 or result.dy isnt 0


Hide when no deprecations

Hide when no deprecations

--- spec/atom-reporter.coffee
+++ spec/atom-reporter.coffee
@@ -53,7 +53,7 @@
         @div outlet: "message", class: 'message'
       @div outlet: "results", class: 'results'

-      @div outlet: "deprecations", class: 'status alert alert-warning', =>
+      @div outlet: "deprecations", class: 'status alert alert-warning', style: 'display: none', =>
         @span outlet: 'deprecationStatus', '0 deprecations'
         @div class: 'deprecation-toggle'
       @div outlet: 'deprecationList', class: 'deprecation-list'
@@ -98,6 +98,7 @@
   addDeprecations: (spec) ->
     deprecations = grim.getDeprecations()
     @deprecationCount += deprecations.length
+    @deprecations.show() if @deprecationCount > 0
     if @deprecationCount is 1
       @deprecationStatus.text("1 deprecation")
     else


Display time in seconds instead of milliseconds

--- spec/time-reporter.coffee
+++ spec/time-reporter.coffee
@@ -17,14 +17,16 @@
     log "Longest running suites:"
     suites = _.map(window.timedSuites, (key, value) -> [value, key])
     for suite in _.sortBy(suites, (suite) => -suite[1])[0...number]
-      log "  #{suite[0]} (#{suite[1]}ms)"
+      time = Math.round(suite[1] / 100) / 10
+      log "  #{suite[0]} (#{time}s)"
     undefined

   logLongestSpecs: (number=10, log) ->
     log ?= (line) -> console.log(line)
     log "Longest running specs:"
     for spec in _.sortBy(window.timedSpecs, (spec) -> -spec.time)[0...number]
-      log "#{spec.description} (#{spec.time}ms)"
+      time = Math.round(spec.time / 100) / 10
+      log "#{spec.description} (#{time}s)"
     undefined

   reportSpecStarting: (spec) ->


write_properties: fix description in tests

--- test/core/write_properties.coffee
+++ test/core/write_properties.coffee
@@ -5,10 +5,10 @@
 they = require 'ssh2-they'
 fs = require 'ssh2-fs'

-describe 'write', ->
+describe 'write_properties', ->

   scratch = test.scratch @
-
+
   they 'overwrite by default', (ssh, next) ->
     # Write the content
     mecano


Rename local storage delete method to clear

This breaks back compat for custom storage engines, but not for the cookie or local storage defaults.

--- src/kissmetrics-anon.coffee
+++ src/kissmetrics-anon.coffee
@@ -37,12 +37,12 @@
     window.localStorage.setItem @key, value

-  # #### Delete
-  # -----------
+  # #### Clear
+  # ----------

-  # Delete data from localStorage.
+  # Clear data from localStorage.

-  delete: ->
+  clear: ->
     window.localStorage.removeItem @key

@@ -87,12 +87,12 @@
     document.cookie = "#{@key}=#{value}; #{options.expires}; path=/"

-  # #### Delete
-  # -----------
+  # #### Clear
+  # ----------

-  # Delete a cookie.
+  # Clear a cookie.

-  delete: ->
+  clear: ->
     Cookie.set @key, '', {expires: -1}

@@ -113,7 +113,7 @@
 #
 # If you provide your own storage engine, it **must** match the
 # API's provided by `Cookie` and `LocalStorage` with `get()`, `set()` and
-# `delete()` methods. All methods should use `this.storageKey` to reference
+# `clear()` methods. All methods should use `this.storageKey` to reference
 # the key to retrieve data by, and `set()` must accept a key and value. The
 # methods will always be called in the correct context by the API, such
 # that `this.storageKey` will be available.
@@ -179,7 +179,7 @@
   # ```

   alias: (newIdentity, deleteStoredID = true) ->
-    @_storage.delete() unless deleteStoredID is off
+    @_storage.clear() unless deleteStoredID is off
     super newIdentity


Better constantize

Better constantize

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -60,6 +60,7 @@

   equals o.test2, "coolio"

-test "#include"
+test "#include string", ->
+

 module undefined


Better constantize

Better constantize

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -61,6 +61,6 @@
   equals o.test2, "coolio"

 test "#include string", ->
-  window.TestM =
+  window.TestM =

 module undefined


Added fix for template_dir

Added fix for template_dir

--- src/jolokiasrv.coffee
+++ src/jolokiasrv.coffee
@@ -60,33 +60,37 @@
   ###
   load_all_templates: (fn) =>
     @stop_gmond()
-    fs.readdir path.resolve(config.get('template_dir')), (err, files) =>
-      if files == undefined
-        json_files = []
-      else
-        json_files = files.filter (x) -> x.match /\.json/
-      async.each json_files, @load_template, (err) =>
-        @start_gmond()
-        unless fn == undefined then fn(err)
+    fs.exists config.get('template_dir'), (exists) =>
+      unless exists then return fn("Template dir does not exist.")
+      fs.readdir path.resolve(config.get('template_dir')), (err, files) =>
+        if files == undefined
+          json_files = []
+        else
+          json_files = files.filter (x) -> x.match /\.json/
+        async.each json_files, @load_template, (err) =>
+          @start_gmond()
+          unless fn == undefined then fn(err)

   ###*
    * Removes the given template from the available templates.
    * @param {String}   (template) The template to remove
   ###
   load_template: (template, fn) =>
-    fs.readFile path.resolve(config.get('template_dir'), template)
-      , 'utf8', (err, data) =>
-        if (err)
-          logger.error "Error reading file: #{template}: #{err}"
-        else
-          try
-            json_data = JSON.parse(data)
-            @templates[json_data.name] =
-              inherits: json_data.inherits
-              mappings: json_data.mappings
-          catch error
-            logger.error "Error parsing `#{template}`: #{error}"
-        unless fn == undefined then fn(err)
+    fs.exists config.get('template_dir'), (exists) =>
+      unless exists then return fn("Template dir does not exist.")
+      fs.readFile path.resolve(config.get('template_dir'), template)
+        , 'utf8', (err, data) =>
+          if (err)
+            logger.error "Error reading file: #{template}: #{err}"
+          else
+            try
+              json_data = JSON.parse(data)
+              @templates[json_data.name] =
+                inherits: json_data.inherits
+                mappings: json_data.mappings
+            catch error
+              logger.error "Error parsing `#{template}`: #{error}"
+          unless fn == undefined then fn(err)

   ###*
    * Removes the given template from the available templates.


change history to show edits for comment actions

--- vue/src/shared/services/comment_service.coffee
+++ vue/src/shared/services/comment_service.coffee
@@ -64,7 +64,7 @@
         Session.user() && comment.translate(Session.user().locale)

     show_history:
-      name: 'action_dock.history'
+      name: 'action_dock.show_edits'
       icon: 'mdi-history'
       menu: true
       canPerform: ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -14,4 +14,5 @@
 test 'it should change the alpha value when the flicker duration has elapsed', ->

+
 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -45,6 +45,7 @@
   equals boss.I.flickerFrequency, 0.1
   equals boss.I.flickerDuration, 3

+#
 test 'it should change the alpha value when the flicker duration has elapsed', ->
   player = GameObject
     alpha: 0.9


fix stupid test

fix stupid test

--- test/integration.coffee
+++ test/integration.coffee
@@ -139,19 +139,23 @@
     c =
       factories:
         a: (b, c) ->
-          test.equal callsTo.a, 1
+          callsTo.a++
           Promise.delay(b + c, 40)
         b: (d) ->
-          test.equal callsTo.b, 1
+          callsTo.b++
           Promise.delay(d + 1, 20)
         c: (d) ->
-          test.equal callsTo.c, 1
+          callsTo.c++
           Promise.delay(d + 2, 30)
         d: ->
-          test.equal callsTo.d, 1
+          callsTo.d++
           Promise.delay(10, 10)

     hinoki.get(c, 'a').then (a) ->
+      test.equal callsTo.a, 1
+      test.equal callsTo.b, 1
+      test.equal callsTo.c, 1
+      test.equal callsTo.d, 1
       test.equal a, 23
       test.done()


Test km instances inherit from KissmetricsClient

--- test/kissmetrics.coffee
+++ test/kissmetrics.coffee
@@ -10,6 +10,10 @@
 		km = new KM.KissmetricsClient 'apiKey', 'evan@example.com'
 		km.key.should.equal 'apiKey'
 		km.person.should.equal 'evan@example.com'
+
+	it 'should be instance of KissmetricsClient', ->
+		km = new KM.KissmetricsClient 'apiKey', 'evan@example.com'
+		km.should.be.an.instanceOf KM.KissmetricsClient

 	it 'should have API methods', ->
 		km = new KM.KissmetricsClient 'apiKey', 'evan@example.com'


fix test title for bufferWhileBy test

fix test title for bufferWhileBy test

--- test/specs/buffer-while-by.coffee
+++ test/specs/buffer-while-by.coffee
@@ -32,7 +32,7 @@
       b = stream()
       expect(a.bufferWhileBy(b, {emitEmpty: true})).toEmit [[], '<end>'], -> send(a, ['<end>'])

-    it 'should flush empty buffer and then end when secondary ends (w/ {flushOnChange: true, emitEmpty: true})', ->
+    it 'should flush empty buffer when secondary emits false (w/ {flushOnChange: true, emitEmpty: true})', ->
       expect(stream().bufferWhileBy(send(prop(), [true, false]), {flushOnChange: true, emitEmpty: true})).toEmit [{current: []}]
       a = stream()
       b = stream()


Fix indentation

Fix indentation

--- workers/social/lib/social/models/domain.coffee
+++ workers/social/lib/social/models/domain.coffee
@@ -119,38 +119,38 @@
           callback null, !/shared[\-]?([0-9]+)?$/.test prefix

   @createDomain: secure (client, options={}, callback)->
-      {delegate} = client.connection
-
-      JGroup.one {slug:'koding'}, (err, group)->
+    {delegate} = client.connection
+
+    JGroup.one {slug:'koding'}, (err, group)->
+      return callback err  if err
+
+      delegate.checkPermission group, 'create domains', (err, hasPermission)->
         return callback err  if err
-
-        delegate.checkPermission group, 'create domains', (err, hasPermission)->
+        return callback new KodingError "Access denied"  unless hasPermission
+
+        JDomain.isDomainEligible
+          delegate : delegate
+          domain   : options.domain
+        , (err, isEligible)->
           return callback err  if err
-          return callback new KodingError "Access denied"  unless hasPermission
-
-          JDomain.isDomainEligible
-            delegate : delegate
-            domain   : options.domain
-          , (err, isEligible)->
-            return callback err  if err
-            return callback new KodingError "You can't create this domain."  unless isEligible
-
-            model = new JDomain options
-            model.save (err) ->
+          return callback new KodingError "You can't create this domain."  unless isEligible
+
+          model = new JDomain options
+          model.save (err) ->
+            return callback err if err
+
+            account = client.connection.delegate
+            rel = new Relationship
+              targetId: model.getId()
+              targetName: 'JDomain'
+              sourceId: account.getId()
+              sourceName: 'JAccount'
+              as: 'owner'
+
+            rel.save (err)->
               return callback err if err

-              account = client.connection.delegate
-              rel = new Relationship
-                targetId: model.getId()
-                targetName: 'JDomain'
-                sourceId: account.getId()
-                sourceName: 'JAccount'
-                as: 'owner'
-
-              rel.save (err)->
-                return callback err if err
-
-              callback err, model
+            callback err, model

   @isDomainAvailable = (domainName, tld, callback)->


Github: expose complete github api to userspace

--- workers/social/lib/social/models/github.coffee
+++ workers/social/lib/social/models/github.coffee
@@ -20,9 +20,13 @@

   @set
     permissions           :
+      'api access'        : ['member','moderator']

     sharedMethods         :
       static              :
+        api               :
+          (signature Object, Function)
+

   initGithubFor = (client) ->

@@ -38,3 +42,24 @@
     gh.authenticate { type: 'oauth', token }

     return gh
+
+
+  @api = permit 'api access', success: revive
+
+    shouldReviveProvider : no
+    shouldHaveOauth      : OAUTH_PROVIDER
+
+  , (client, _options, callback) ->
+
+    { method, options } = _options
+    [ base, method ]    = method.split '.'
+
+    # Forcing `per_page` option to max 10 because of max_call_stack_size issue ~ GG
+    (options ?= {}).per_page = 10
+
+    gh = initGithubFor client
+
+    try
+      gh[base][method] options, callback
+    catch err
+      callback err


newkite: fix reconnecting logic to kontrol

--- client/app/MainApp/kite/newkite.coffee
+++ client/app/MainApp/kite/newkite.coffee
@@ -45,17 +45,18 @@
     xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
     xhr.send JSON.stringify requestData
     xhr.onload = =>
-       if xhr.status is 200
-          data = JSON.parse xhr.responseText
-          @token = data[0].token
-          @addr = data[0].addr
+      if xhr.status is 200
+        data = JSON.parse xhr.responseText
+        @token = data[0].token
+        @addr = data[0].addr

-          # this should be optional
-          @connectDirectly() if connect
-        else
-          log "kontrol request error", xhr.responseText
-          # Make a request again if we could not get the addres, use backoff for that
-          KD.utils.defer => @setBackoffTimeout @bound "getKiteAddr"
+        # this should be optional
+        @connectDirectly() if connect
+      else
+        log "kontrol request error", xhr.responseText
+        # Make a request again if we could not get the addres, use backoff for that
+        KD.utils.defer => @setBackoffTimeout =>
+          @getKiteAddr true

   disconnect:(reconnect=true)->
     @autoReconnect = !!reconnect  if reconnect?


Remove deprecated InPort handle callback

--- src/lib/InPort.coffee
+++ src/lib/InPort.coffee
@@ -22,9 +22,6 @@

     if options.handle
       platform.deprecated 'InPort handle callback is deprecated. Please use Process API'
-      unless typeof options.handle is 'function'
-        throw new Error 'handle must be a function'
-      @handle = options.handle
       delete options.handle

     super options
@@ -35,10 +32,7 @@
   attachSocket: (socket, localId = null) ->
     # have a default value.
     if @hasDefault()
-      if @handle
-        socket.setDataDelegate => new IP 'data', @options.default
-      else
-        socket.setDataDelegate => @options.default
+      socket.setDataDelegate => @options.default

     socket.on 'connect', =>
       @handleSocketEvent 'connect', socket, localId
@@ -68,9 +62,6 @@
     buf = @prepareBufferForIP ip
     buf.push ip
     buf.shift() if @options.control and buf.length > 1
-
-    if @handle
-      @handle ip, @nodeInstance

     @emit 'ip', ip, id


Fixed getBlocks method returning DocPad instead of blocks

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -282,9 +282,7 @@
 		@

 	#  Get blocks
-	getBlocks: (blocks) ->
-		@blocks
-		@
+	getBlocks: -> @blocks

 	#  Set blocks
 	setBlocks: (blocks) ->
@@ -425,7 +423,7 @@
 		next(null, file)

 		# Chain
-		return @
+		@

 	# Get a file by its selector
 	getFileBySelector: (selector,opts={}) ->
@@ -1425,7 +1423,7 @@
 		extendr.safeDeepExtendPlainObjects(configsToMerge...)

 		# Chain
-		return @
+		@

 	# Set Instance Configuration
 	setInstanceConfig: (instanceConfig) ->
@@ -3391,7 +3389,7 @@
 	# Destroy Regenerate Timer
 	destroyRegenerateTimer: ->
 		# Prepare
-		docpad =@
+		docpad = @

 		# Clear Regenerate Timer
 		if docpad.regenerateTimer
@@ -4306,7 +4304,7 @@
 		next()

 		# Chain
-		return @
+		@

 	# Server Middleware: Router
 	serverMiddlewareRouter: (req,res,next) =>
@@ -4329,7 +4327,7 @@
 			docpad.serveDocument({document:file, req, res, next})

 		# Chain
-		return @
+		@

 	# Server Middleware: 404
 	serverMiddleware404: (req,res,next) =>
@@ -4345,7 +4343,7 @@
 		docpad.serveDocument({document, req, res, next, statusCode:404})

 		# Chain
-		return @
+		@

 	# Server Middleware: 500
 	serverMiddleware500: (err,req,res,next) =>
@@ -4361,7 +4359,7 @@
 		docpad.serveDocument({document,err,req,res,next,statusCode:500})

 		# Chain
-		return @
+		@

 	# Server
 	server: (opts,next) =>


Update range tracker.

Update range tracker.

--- app/coffee/RangesTracker.coffee
+++ app/coffee/RangesTracker.coffee
@@ -108,13 +108,16 @@
 			for change_id in change_to_remove_ids
 				remove_change_id[change_id] = true

-			while (i--)
-				if remove_change_id[@changes[i].id]
-					delete remove_change_id[@changes[i].id]
-					removed_change = @changes.splice(i, 1)[0]
-					@_markAsDirty removed_change, "change", "removed"
-					if Object.keys(remove_change_id).length == 0
-						break
+			remaining_changes = []
+
+			for change in @changes
+				if remove_change_id[change.id]
+					delete remove_change_id[change.id]
+					@_markAsDirty change, "change", "removed"
+				else
+					remaining_changes.push change
+
+			@changes = remaining_changes

 		validate: (text) ->
 			for change in @changes


restrict syncType values to full/incremental

--- app/coffee/RequestParser.coffee
+++ app/coffee/RequestParser.coffee
@@ -33,6 +33,7 @@
 				type: "string"
 			response.syncType = @_parseAttribute "syncType",
 				compile.options.syncType,
+				validValues: ["full", "incremental"]
 				type: "string"
 			response.syncState = @_parseAttribute "syncState",
 				compile.options.syncState,


Fix discussion error popup on http request abort

--- common/static/coffee/src/discussion/views/discussion_thread_view.coffee
+++ common/static/coffee/src/discussion/views/discussion_thread_view.coffee
@@ -120,7 +120,9 @@
           )
           @trigger "thread:responses:rendered"
           @loadedResponses = true
-        error: (xhr) =>
+        error: (xhr, textStatus) =>
+          return if textStatus == 'abort'
+
           if xhr.status == 404
             DiscussionUtil.discussionAlert(
               gettext("Sorry"),


See #282, #284. Move language options out of option of type `object`

See Atom Settings-View bug:
https://github.com/atom/settings-view/issues/386#issuecomment-98379708

--- src/beautifiers/index.coffee
+++ src/beautifiers/index.coffee
@@ -185,32 +185,27 @@
         ), {})

         # Generate Language configurations
-        langConfigs = {}
-        # Process all languages
+        # console.log('languages', languages)
         for langName, lang of languages
+            # console.log(langName, lang)
             name = lang.name
             beautifiers = lang.beautifiers
-            langConfigs[name] = {
-                type: 'object'
-                properties:
-                    disabled:
-                        title: "Language Config - #{name} - Disable Beautifying Language"
-                        type: 'boolean'
-                        default: false
-                        description: "Disable #{name} Beautification"
-                    default_beautifier:
-                        title: "Language Config - #{name} - Default Beautifier"
-                        type: 'string'
-                        default: beautifiers[0]
-                        description: "Default Beautifier to be used for #{name}"
-                        enum: _.uniq(beautifiers)
+            optionName = "language_#{lang.namespace}"
+            # Add Language configurations
+            flatOptions["#{optionName}_disabled"] = {
+                title: "Language Config - #{name} - Disable Beautifying Language"
+                type: 'boolean'
+                default: false
+                description: "Disable #{name} Beautification"
             }
-        # Add Language configurations
-        flatOptions.languages = {
-              type: 'object'
-              properties: langConfigs
-        }
-
+            flatOptions["#{optionName}_default_beautifier"] = {
+                title: "Language Config - #{name} - Default Beautifier"
+                type: 'string'
+                default: beautifiers[0]
+                description: "Default Beautifier to be used for #{name}"
+                enum: _.uniq(beautifiers)
+            }
+        # console.log('flatOptions', flatOptions)
         return flatOptions

     ###
@@ -238,12 +233,13 @@
                 language = languages[0]

                 # Get language config
-                langConfig = atom.config.get("atom-beautify.languages")?[language.name]
+                langDisabled = atom.config.get("atom-beautify.language_#{language.namespace}_disabled")
+                preferredBeautifierName = atom.config.get("atom-beautify.language_#{language.namespace}_default_beautifier")

                 # Beautify!
                 unsupportedGrammar = false
                 # Check if Language is disabled
-                if langConfig?.disabled
+                if langDisabled
                   return resolve(null)

                 # Options for Language
@@ -264,7 +260,6 @@
                     unsupportedGrammar = true
                 else
                     # Select beautifier from language config preferences
-                    preferredBeautifierName = langConfig.default_beautifier
                     beautifier = _.find(beautifiers, (beautifier) ->
                         beautifier.name is preferredBeautifierName
                     ) or beautifiers[0]


Callback when mongo is connected

Callback when mongo is connected

--- server/common/dal/mongo.coffee
+++ server/common/dal/mongo.coffee
@@ -18,7 +18,7 @@

 module.exports =

-  connect: (conf) ->
+  connect: (conf, cb) ->
     port = parseInt conf.port

     opts =
@@ -26,7 +26,10 @@
       pass: conf.password

     mongoose.connect conf.host, conf.name, port, opts
-    mongoose.connection
+
+    mongoose.connection.on 'connected',  () ->
+      cb() if cb
+

   sessionStore: (express) ->
     MongoStore = connectMongo(express)


API: jobs response changed to json

API: jobs response changed to json

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -259,7 +259,7 @@

   request options, (err, r, postings) ->
     res.send 404 if err
-    res.jsonp postings
+    res.json postings

 app.get "/sitemap:sitemapName", (req, res)->
   {JSitemap}       = koding.models


Set up prompt to sign in after N classifications

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -1,3 +1,4 @@
+auth = require '../../api/auth'
 React = require 'react'
 TitleMixin = require '../../lib/title-mixin'
 HandlePropChanges = require '../../lib/handle-prop-changes'
@@ -6,11 +7,19 @@
 animatedScrollTo = require 'animated-scrollto'
 counterpart = require 'counterpart'
 Classifier = require '../../classifier'
+alert = require '../../lib/alert'
+
+PROMPT_TO_SIGN_IN_AFTER = [5, 10, 25, 50, 100, 250, 500]

 SKIP_CELLECT = location.search.match(/\Wcellect=0(?:\W|$)/)?

 if SKIP_CELLECT
   console?.warn 'Intelligent subject selection disabled'
+
+classificationsThisSession = 0
+
+auth.listen ->
+  classificationsThisSession = 0

 # Map each project ID to a promise of its last randomly-selected workflow ID.
 # This is to maintain the same random workflow for each project when none is specified by the user.
@@ -170,6 +179,17 @@
       console?.log 'Saved classification', classification.id
       # After saving, remove the classification resource from the local cache.
       classification.destroy()
+    classificationsThisSession += 1
+    @maybePromptToSignIn()
+
+  maybePromptToSignIn: ->
+    auth.checkCurrent().then (user) ->
+      console.log classificationsThisSession, classificationsThisSession in PROMPT_TO_SIGN_IN_AFTER, not user?
+      if classificationsThisSession in PROMPT_TO_SIGN_IN_AFTER and not user?
+        alert

+
Hey, you’ve done {classificationsThisSession} classifications, but you’re not signed in!

+
Signing in allows us to give you credit for your work, and helps us make better sense of all the data.

+

   loadAnotherSubject: ->
     @getCurrentWorkflowID(@props).then (workflowID) =>


Tweaked the goal id field schema to be a bit more stringent. Forces the id to be a slug.

--- app/schemas/models/level.coffee
+++ app/schemas/models/level.coffee
@@ -27,7 +27,7 @@
 GoalSchema = c.object {title: 'Goal', description: 'A goal that the player can accomplish.', required: ['name', 'id']},
   name: c.shortString(title: 'Name', description: 'Name of the goal that the player will see, like \"Defeat eighteen dragons\".')
   i18n: {type: 'object', format: 'i18n', props: ['name'], description: 'Help translate this goal'}
-  id: c.shortString(title: 'ID', description: 'Unique identifier for this goal, like \"defeat-dragons\".')  # unique somehow?
+  id: c.shortString(title: 'ID', description: 'Unique identifier for this goal, like \"defeat-dragons\".', pattern: '^[a-z0-9-]+$')  # unique somehow?
   worldEndsAfter: {title: 'World Ends After', description: 'When included, ends the world this many seconds after this goal succeeds or fails.', type: 'number', minimum: 0, exclusiveMinimum: true, maximum: 300, default: 3}
   howMany: {title: 'How Many', description: 'When included, require only this many of the listed goal targets instead of all of them.', type: 'integer', minimum: 1}
   hiddenGoal: {title: 'Hidden', description: 'Hidden goals don\'t show up in the goals area for the player until they\'re failed. (Usually they\'re obvious, like "don\'t die".)', 'type': 'boolean', default: false}


Remove 'max_line_length' coffeelint option for Gruntfile.coffee. Closes #45

--- app/templates/_Gruntfile.coffee
+++ app/templates/_Gruntfile.coffee
@@ -59,6 +59,8 @@
             options:
                 indentation:
                     value: 4
+                max_line_length:
+                    level: 'ignore'

             all: ['Gruntfile.coffee']


adds for sale back to test

adds for sale back to test

--- test/collections/filter_artworks.coffee
+++ test/collections/filter_artworks.coffee
@@ -20,7 +20,7 @@
       _.keys(@artworks.counts.medium)[3].should.equal "installation"

     it 'should sort the price ranges by value', ->
-      _.keys(@artworks.counts.price_range)[0].should.equal "*-1000"
+      _.keys(@artworks.counts.price_range)[0].should.equal "*-*"
       _.keys(@artworks.counts.price_range)[1].should.equal "1000-5000"
       _.keys(@artworks.counts.price_range)[2].should.equal "5000-10000"


監視タスク最適化

監視タスク最適化

--- root/Gruntfile.coffee
+++ root/Gruntfile.coffee
@@ -295,7 +295,7 @@
           '<%= path.source %>/img/sprites/*.png'
         ]
         destImg: '<%= path.source %>/img/sprite.png'
-        destCSS: '<%= path.source %>/css/_sprites.styl'
+        destCSS: '<%= path.source %>/css/sprites.css'
         algorithm: 'binary-tree'
         padding: 1

@@ -342,10 +342,55 @@
     watch:
       options:
         livereload: true
-      source:
-        files: '<%= path.source %>/**/*'
+      css:
+        files: [
+          '<%= path.source %>/**/*.css'
+          '<%= path.source %>/**/*.less'
+          '<%= path.source %>/**/*.s(a|c)ss'
+          '<%= path.source %>/**/*.styl'
+        ]
         tasks: [
-          'default'
+          'css'
+          'copy'
+          'notify:build'
+        ]
+      html:
+        files: [
+          '<%= path.source %>/**/*.html'
+          '<%= path.source %>/**/*.jade'
+        ]
+        tasks: [
+          'html'
+          'copy'
+          'notify:build'
+        ]
+      image:
+        files: [
+          '<%= path.source %>/**/img/**/*'
+        ]
+        tasks: [
+          'image'
+          'copy'
+          'notify:build'
+        ]
+      js:
+        files: [
+          '<%= path.source %>/**/*.coffee'
+          '<%= path.source %>/**/*.js'
+          '<%= path.source %>/**/*.litcoffee'
+        ]
+        tasks: [
+          'js'
+          'copy'
+          'notify:build'
+        ]
+      json:
+        files: '<%= path.source %>/**/*.json'
+        tasks: [
+          'json'
+          'html'
+          'copy'
+          'notify:build'
         ]

@@ -386,8 +431,7 @@
       'image'
       'js'
       'json'
-      'copy:source'
-      'copy:intermediate'
+      'copy'
       'notify:build'
     ]


operations: subscribe defaultAffiliation, fixes

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -312,9 +312,9 @@
                                     cb4 null, null
                                 else
                                     cb4 err, item
-                    , (oldItem, cb3) =>
+                    , (oldItem, cb4) =>
                         normalizeItem @req, oldItem, item, cb4
-                    , (newItem, cb3) =>
+                    , (newItem, cb4) =>
                         t.writeItem @req.node, newItem.id, @req.actor, newItem.el, (err) ->
                             cb4 err, newItem.id
                     ], cb3
@@ -332,8 +332,9 @@
     requiredAffiliation: 'member'

     privilegedTransaction: (t, cb) ->
+        @affiliation = @req.config?.defaultAffiliation or 'member'
         t.setSubscription @req.node, @req.actor, @req.sender, 'subscribed', (err) =>
-            t.setAffiliation @req.node, @req.actor, 'member', (err) =>
+            t.setAffiliation @req.node, @req.actor, @affiliation, (err) =>
                 cb err,
                     user: @req.actor
                     subscription: 'subscribed'
@@ -344,6 +345,11 @@
             node: @req.node
             user: @req.actor
             subscription: 'subscribed'
+        }, {
+            type: 'affiliation'
+            node: @req.node
+            user: @req.actor
+            affiliation: @affiliation
         }]

 ##


Use list comprehension instead of for loop

--- src/coffee/mandelbrot.coffee
+++ src/coffee/mandelbrot.coffee
@@ -10,8 +10,7 @@
     @colorCache = {}

     # Set the alpha channel for all pixels in the image
-    for i in [0...@numPixels]
-      @image.data[i * 4 + 3] = 255
+    @image.data[i * 4 + 3] = 255 for i in [0...@numPixels]

   mandelbrot: (region, px, py) ->
     x0 = px / @width * region.width + region.left


Use small tab size in atom-material-ui theme.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -89,5 +89,6 @@
     compactTreeView: true
     rippleAccentColor: true
     slimScrollbar: true
+    tabSize: "Small"
   jumpy:
     highContrast: true


fix another concurrent log message in rethink

--- salvus/rethink.coffee
+++ salvus/rethink.coffee
@@ -301,7 +301,7 @@
                                 @_stats ?= {sum:0, n:0}
                                 @_stats.sum += tm
                                 @_stats.n += 1
-                                winston.debug("rethink: query time using (#{id}) took #{tm}ms; average=#{Math.round(@_stats.sum/@_stats.n)}ms; #{that._concurrent_queries} concurrent -- '#{query_string}'")
+                                winston.debug("[#{that._concurrent_queries} concurrent]   rethink: query time using (#{id}) took #{tm}ms; average=#{Math.round(@_stats.sum/@_stats.n)}ms;  -- '#{query_string}'")
                             if err
                                 report_time()
                                 if err.message.indexOf('is closed') != -1


[atom] Turn on linter package and allow detect .remarkignore files for linter-markdown

--- src/atom/atom/config.cson
+++ src/atom/atom/config.cson
@@ -5,7 +5,6 @@
   core:
     autoHideMenuBar: true
     disabledPackages: [
-      "linter"
       "vim-mode"
     ]
     telemetryConsent: "limited"
@@ -20,7 +19,10 @@
     general:
       _analyticsUserId: "b87a31f7-4c7c-4a18-af7f-2ad25c52a013"
   linter: {}
-  "linter-ui-default": {}
+  "linter-markdown":
+    detectIgnore: false
+  "linter-ui-default":
+    showPanel: true
   "markdown-preview":
     useGitHubStyle: true
   minimap: {}


Implement custom tool tip for lodheatmap

--- src/lodheatmap.coffee
+++ src/lodheatmap.coffee
@@ -143,22 +143,6 @@
                         cells.push({lod: lod, chr:chr, pos:pos, poslabel: data.poslabelByChr[chr][i], posindex:+i, lodindex:+j})
         d3panels.calc_chrcell_rect(cells, xmid_scaled, ymid_scaled)

-        # tool tips
-        celltip = d3.tip()
-                   .attr('class', "d3-tip #{tipclass}")
-                   .html((d) ->
-                             z = d3.format(".2f")(Math.abs(d.lod))
-                             lodlabel = if data.ycat? then data.ycat[d.lodindex] else d3panels.formatAxis(data.y)(data.y[d.lodindex])
-                             return "#{lodlabel}, #{d.poslabel} → #{z}" if horizontal
-                             "#{d.poslabel}, #{lodlabel} → #{z}")
-                   .direction(() ->
-                       return 'n' if horizontal
-                       'e')
-                   .offset(() ->
-                       return [-10, 0] if horizontal
-                       [0,10])
-        svg.call(celltip)
-
         cellg = svg.append("g").attr("id", "cells")
         cellSelect =
             cellg.selectAll("empty")
@@ -182,14 +166,12 @@
                  .attr("stroke", "none")
                  .attr("stroke-width", "1")
                  .attr("shape-rendering", "crispEdges")
-                 .on("mouseover.paneltip", (d) ->
+                 .on("mouseover", (d) ->
                                                d3.select(this).attr("stroke", hilitcolor).raise()
-                                               celltip.show(d)
                                                if data.ycat?
                                                    svg.select("text#ylab#{d.lodindex}").attr("opacity",1))
-                 .on("mouseout.paneltip", (d) ->
+                 .on("mouseout", (d) ->
                                                d3.select(this).attr("stroke", "none")
-                                               celltip.hide()
                                                if data.ycat?
                                                    svg.select("text#ylab#{d.lodindex}").attr("opacity",0))

@@ -198,6 +180,15 @@
             svg.selectAll("g#ylines").remove()
             ylabels.attr("opacity", 0)
                    .attr("id", (d,i) -> "ylab#{i}")
+
+        # tool tips
+        celltipfunc = (d) ->
+                             z = d3.format(".2f")(Math.abs(d.lod))
+                             lodlabel = if data.ycat? then data.ycat[d.lodindex] else d3panels.formatAxis(data.y)(data.y[d.lodindex])
+                             return "#{lodlabel}, #{d.poslabel} → #{z}" if horizontal
+                             "#{d.poslabel}, #{lodlabel} → #{z}"
+        direction = if horizontal then "north" else "east"
+        celltip = d3panels.tooltip_create(d3.select("body"), cellg.selectAll("rect"), {direction:direction, tipclass:tipclass}, celltipfunc)

         myframe.box().raise()

@@ -213,7 +204,7 @@
     # function to remove chart
     chart.remove = () ->
                       svg.remove()
-                      celltip.destroy()
+                      d3panels.tooltip_destroy(celltip)
                       return null

     # return the chart function


nav is independent from default layout

nav is independent from default layout

--- src/components/layout.coffee
+++ src/components/layout.coffee
@@ -4,37 +4,18 @@
 {DOM} = React

 module.exports = React.createFactory React.createClass
-  getInitialState: ->
-    navActive: false
-
-  componentWillReceiveProps: (newProps) ->
-    if newProps.currentUrl != @props.currentUrl
-      @setState
-        navActive: false
-
-  toggleNav: (e) ->
-    e.preventDefault()
-    @setState
-      navActive: !@state.navActive
-
   render: ->
-    url = @props.currentUrl #window.location.pathname
-    wrapperClasses = [
-      'wrapper'
-      'relative'
-    ]
-    if @state.navActive
-      wrapperClasses.push 'active'
+    ContentComponent = @props.getComponent @props.contentComponent

     DOM.div
       className: 'fixed'
     ,
       DOM.div
-        className: wrapperClasses.join ' '
+        className: 'wrapper relative'
       ,
         DOM.section
           className: 'content'
           style:
             height: '100%'
         ,
-          @props.getComponent(@props.contentComponent) _.extend {key: @props.currentUrl}, @props
+          ContentComponent _.extend {}, @props


Tweak

Tweak

--- test/spec/lib/schema_types.tests.coffee
+++ test/spec/lib/schema_types.tests.coffee
@@ -60,6 +60,7 @@
       assert.equal(Reverse.schema().idType('owner.reverse_id'), 'Integer')
     it 'should parse a related hasMany id as an Integer', ->
       assert.equal(Owner.schema().idType('reverse.another_owner_id'), 'Integer')
+
     describe 'JSONUtils', ->
       it 'should parse a belongsTo id as an Integer', ->
         assert.strictEqual(JSONUtils.parse({'owner_id': '1'}, Reverse)['owner_id'], 1)


test the `retain` option to `revokeAllUserSessions`.

--- services/web/test/UnitTests/coffee/User/UserSessionsManagerTests.coffee
+++ services/web/test/UnitTests/coffee/User/UserSessionsManagerTests.coffee
@@ -250,11 +250,12 @@
 	describe 'revokeAllUserSessions', ->

 		beforeEach ->
-			@sessionKeys = ['one', 'two']
+			@sessionKeys = ['sess:one', 'sess:two']
+			@retain = []
 			@rclient.smembers.callsArgWith(1, null, @sessionKeys)
 			@rclient.exec.callsArgWith(0, null)
 			@call = (callback) =>
-				@UserSessionsManager.revokeAllUserSessions @user, [], callback
+				@UserSessionsManager.revokeAllUserSessions @user, @retain, callback

 		it 'should not produce an error', (done) ->
 			@call (err) =>
@@ -276,6 +277,37 @@
 				@rclient.exec.callCount.should.equal 1
 				done()

+		describe 'when a session is retained', ->
+
+			beforeEach ->
+				@sessionKeys = ['sess:one', 'sess:two', 'sess:three', 'sess:four']
+				@retain = ['two']
+				@rclient.smembers.callsArgWith(1, null, @sessionKeys)
+				@rclient.exec.callsArgWith(0, null)
+				@call = (callback) =>
+					@UserSessionsManager.revokeAllUserSessions @user, @retain, callback
+
+			it 'should not produce an error', (done) ->
+				@call (err) =>
+					expect(err).to.not.be.instanceof Error
+					expect(err).to.equal null
+					done()
+
+			it 'should call the appropriate redis methods', (done) ->
+				@call (err) =>
+					@rclient.smembers.callCount.should.equal 1
+					@rclient.multi.callCount.should.equal 1
+					@rclient.del.callCount.should.equal 1
+					@rclient.srem.callCount.should.equal 1
+					@rclient.exec.callCount.should.equal 1
+					done()
+
+			it 'should remove all sessions except for the retained one', (done) ->
+				@call (err) =>
+					expect(@rclient.del.firstCall.args[0]).to.deep.equal(['sess:one', 'sess:three', 'sess:four'])
+					expect(@rclient.srem.firstCall.args[1]).to.deep.equal(['sess:one', 'sess:three', 'sess:four'])
+					done()
+
 		describe 'when rclient produces an error', ->

 			beforeEach ->
@@ -290,7 +322,7 @@

 			beforeEach ->
 				@call = (callback) =>
-					@UserSessionsManager.revokeAllUserSessions null, [], callback
+					@UserSessionsManager.revokeAllUserSessions null, @retain, callback

 			it 'should not produce an error', (done) ->
 				@call (err) =>


Escape < and > to avoid turning them into html tags and prevent xss

Escape < and > to avoid turning them into html tags.
This prevents XSS when you are reconstructing data from the server.

--- src/bootstrap-tags.coffee
+++ src/bootstrap-tags.coffee
@@ -333,17 +333,18 @@

     # formatTag spits out the html for a tag (with or without it's popovers)
     @formatTag = (i, tag) =>
+      tag_data = tag.replace("<",'<').replace(">",'>')
       if @displayPopovers == true # then attach popover data
-        "
"+tag+"
"
-      else
-        "
"+tag+"
"
+        "
"+tag_data+"
"
+      else
+        "
"+tag_data+"
"

     @formatTagReadOnly = (i, tag) =>
+      tag_data = tag.replace("<",'<').replace(">",'>')
       if @displayPopovers == true # then attach popover data
-        "
 "+tag+"
"
-      else
-        "
 "+tag+"
"
-
+        "
 "+tag_data+"
"
+      else
+        "
 "+tag_data+"
"

     @addDocumentListeners = =>
       $(document).mouseup (e) =>
@@ -390,4 +391,4 @@
         $el.data 'tags', new $.tags(this, options)
       if stopOn == i or i == 0 # return first or specified by index tags object
         tagsObject = $el.data 'tags'
-    tagsObject+    tagsObject


fix for typo. refactoring.

fix for typo. refactoring.

--- scripts/bungie.coffee
+++ scripts/bungie.coffee
@@ -37,22 +37,17 @@
     # if 3 elements, assume: gamertag, network, weaponSlot
     if input.length is 3
       el = input[1].toLowerCase()
-      membershipType = checkNetwork(el)
-      if membershipType is '1'
-        # replaces underscores with spaces for xbox users
-        data['displayName'] = input[0].splt('_').join(' ')
-      else
-        data['displayName'] = input[0]
+      data['membershipType'] = checkNetwork(el)
+      data['displayName'] = input[0]
     else if input.length is 2
       el = input[0].toLowerCase()
-      membershipType = checkNetwork(el)
-      if membershipType is null
+      data['membershipType'] = checkNetwork(el)
+      if data['membershipType'] is null
         # assume first input was gamertag
         data['displayName'] = input[0]
       else
         # assume gamertag not provided, use slack first name
         data['displayName'] = res.message.user.slack.profile.first_name
-      data['membershipType'] = membershipType
     else if input.length is 1
       # assume only weaponSlot was provided
       data['membershipType'] = null
@@ -103,6 +98,9 @@

 tryPlayerId = (res, membershipType, displayName, robot) ->
   deferred = new Deferred()
+  # replaces underscores with spaces (for xbox)
+  # safe to call on PSN IDs because underscores are not allowed
+  displayName = displayName.split('_').join(' ')

   if membershipType
     networkName = if membershipType is '1' then 'xbox' else 'playstation'


Unnecessary with new fork.

Unnecessary with new fork.

--- src/client/browser.coffee
+++ src/client/browser.coffee
@@ -3,8 +3,6 @@

 import Client     from './client'
 import {newError} from '../utils'
-
-Xhr.Promise = Promise

 class BrowserClient extends Client


Remote retry loading if timeout

Remote retry loading if timeout

--- src/client/channel.coffee
+++ src/client/channel.coffee
@@ -42,7 +42,9 @@
     overflow: 'hidden'

 class Channel
+
   constructor : (currentUser, currentConfig)->
+    @retryCount = 0
     @currentConfig = currentConfig
     @currentUser = currentUser
     @timeout  = null
@@ -51,10 +53,11 @@
     @promise = new Promise (resolve, reject)=>
       @_ready.resolve = resolve
       @_ready.reject = reject
-    domready(@onDomReady)
+    domready(@startRpc)

-  onDomReady : ()=>
-    @timeout = setTimeout(@loadingFailed, 60000);
+  startRpc: =>
+    @retryCount += 1
+    @timeout = setTimeout(@loadingFailed, @retryCount * 5000)
     @rpc = new xdm.Rpc
       remote    : @currentConfig.getRemoteUrl()
       container : document.body
@@ -77,12 +80,16 @@
         getClientConfig : @getClientConfig
     @rpc

-  loadError       : (err)=>
+  loadError: (err)=>
     window.clearTimeout @timeout
     @_ready.reject err

-  loadingFailed   : (err) =>
-    @_ready.reject('Remote loading has failed. Please check "orgUrl" and "appId" in your configuration. This may also be about connectivity.')
+  loadingFailed: (err) =>
+    @rpc.destroy()
+    if @retryCount < 10
+      @startRpc()
+    else
+      @_ready.reject(new Error('Remote loading has failed. Please check "orgUrl" and "appId" in your configuration. This may also be about connectivity.'))

   onMessage       : (e)->
     if e.error then @_ready.reject(e.error) else logger.log("RPC Message", arguments)


Fix send message each emoji on emoji action

--- scripts/github.coffee
+++ scripts/github.coffee
@@ -23,19 +23,18 @@
   if fs.existsSync(cachefile)
     body = fs.readFile cachefile, (err, data) ->
       emojis = JSON.parse(data)
-      emoji_url = []
       for emoji in emoji_arr
-        emoji_url.push emojis[emoji]
-      cb emoji_url.join("\n") || "ないよ"
+        emoji_url = emojis[emoji]
+        cb emoji_url.join("\n") || "ないよ"
+
   else
     msg.http('https://api.github.com/emojis')
       .get() (err, res, body) ->
         fs.writeFile cachefile, body
         emojis = JSON.parse(body)
-        emoji_url = []
         for emoji in emoji_arr
-          emoji_url.push emojis[emoji]
-        cb emoji_url.join("\n") || "ないよ"
+          emoji_url = emojis[emoji]
+          cb emoji_url.join("\n") || "ないよ"

 clearEmojiCache = (msg) ->


Lookup credentials also on project path

Lookup credentials also on project path

--- src/coffee/helpers.coffee
+++ src/coffee/helpers.coffee
@@ -28,7 +28,12 @@
       secret: secret
       bucket: bucket
     else
-      existingPath = _.find [argPath, "#{ROOT}/.s3-credentials.json", '/etc/.s3-credentials.json'], (path) ->
+      existingPath = _.find [
+        argPath,
+        "./.s3-credentials.json",
+        "#{ROOT}/.s3-credentials.json",
+        '/etc/.s3-credentials.json'
+      ], (path) ->
         fs.existsSync path
       if existingPath
         @parseJsonFromFile existingPath


Manual point rate for testing.

Manual point rate for testing.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -120,6 +120,7 @@
                     else
                         msg.send "The stream is online and point rate is " + pointrate + " points per hour."

+    # Setting increment to pointrate, only myself was getting points.
     setInterval (->
         robot.http("https://tmi.twitch.tv/group/user/masonest/chatters").get() (err, res, body) ->
             chat = JSON.parse(body)
@@ -129,7 +130,7 @@

             for username in people
                 points[username] ?= 0
-                points[username] += pointrate
+                points[username] += 1
                 save(robot)

             # Winners (and then recall) is equal to all users


added an optimization

added an optimization

--- src/compiler/nodes.coffee
+++ src/compiler/nodes.coffee
@@ -284,11 +284,11 @@
 # `Script` is the main script class that outputs the target code.
 exports.Script = class Script
   @prototype: []
-  toString: -> """
+  toString: -> if @length then """
     !function(){
     #{@join('\n')}
     }();
-  """
+  """ else ""

 # Insert a stylesheet to the `head`
 #


step properly

step properly

--- src/config/sprites.coffee
+++ src/config/sprites.coffee
@@ -17,12 +17,8 @@
     assert dir, "expects dir"
     p = world.get('p')
     v = world.get('v')
-
-    #world.update 'i', dir * world.get('v_i'), split
-    #world.update 'j', dir * world.get('v_j'), split
-    next_i = (p.at(0) + dir * v.at(0)) % split
-    next_j = (p.at(1) + dir * v.at(1)) % split
-    world.put 'p', [next_i, next_j]
+    next_p = [0,1].map (i) -> (p.at(i) + dir*v.at(i)) % split
+    world.put 'p', next_p

   turn: (world, args) ->
     {dir} = args
@@ -33,6 +29,7 @@
     world.put 'v', [next_v_i, next_v_j]

   step: (world, args) ->
+    #console.log "+step", world.get('p').all(), world.get('v').all(),
     signal = world.get('next_signal')
     world.call(signal['do'], signal)
   reset: (world, args) ->


'application' class is not needed on views with simple layout

--- assets/scripts/app/views.coffee
+++ assets/scripts/app/views.coffee
@@ -19,11 +19,9 @@

 Travis.InsufficientOauthPermissionsView = Travis.View.extend
   layoutName: 'layouts/simple'
-  classNames: ['application']

 Travis.FirstSyncView = Travis.View.extend
   layoutName: 'layouts/simple'
-  classNames: ['application']

 Travis.SidebarView = Travis.View.extend
   classQueues: (->


Add layout for notFound view

Add layout for notFound view

--- assets/scripts/app/views.coffee
+++ assets/scripts/app/views.coffee
@@ -5,6 +5,9 @@
     this.container ||= Travis.__container__

     @_super.apply(this, arguments)
+
+Travis.NotFoundView = Ember.View.extend
+  layoutName: 'layouts/simple'

 @Travis.reopen
   View: Em.View.extend


Sim: Add Team#tell

Sim: Add Team#tell

--- server/bw/team.coffee
+++ server/bw/team.coffee
@@ -44,15 +44,17 @@
   attach: (attachment, options={}) ->
     options = _.clone(options)
     attachment = @attachments.push(attachment, options, battle: @battle, team: this)
-    playerIndex = @battle.getPlayerIndex(@playerId)
-    if attachment then @battle?.tell(Protocol.TEAM_ATTACH, playerIndex, attachment.name)
+    if attachment then @tell(Protocol.TEAM_ATTACH, attachment.name)
     attachment

   unattach: (klass) ->
     attachment = @attachments.unattach(klass)
+    if attachment then @tell(Protocol.TEAM_UNATTACH, attachment.name)
+    attachment
+
+  tell: (protocol, args...) ->
     playerIndex = @battle.getPlayerIndex(@playerId)
-    if attachment then @battle?.tell(Protocol.TEAM_UNATTACH, playerIndex, attachment.name)
-    attachment
+    @battle?.tell(protocol, playerIndex, args...)

   switch: (pokemon, toPosition) ->
     newPokemon = @at(toPosition)


remove instance from DB (no more etcd)

remove instance from DB (no more etcd)

--- server/cluster.coffee
+++ server/cluster.coffee
@@ -78,12 +78,12 @@

   clearInstance: (project, instance) ->
     console.log "Cluster.clearInstance #{project}, #{instance}"
-    inst = Instances.findOne project: project, name: instance
-    console.log "#{Settings.findOne().etcd}instances/#{project}/#{inst.application}/#{instance}?recursive=true"
-    HTTP.del "#{Settings.findOne().etcd}instances/#{project}/#{inst.application}/#{instance}?recursive=true", {}, (error, result) ->
-      console.log(error) if error
-      console.log "Cluster.clearInstance completed -> #{project}, #{instance}"
-      sync()
+    Instances.remove project: project, name: instance
+    # console.log "#{Settings.findOne().etcd}instances/#{project}/#{inst.application}/#{instance}?recursive=true"
+    # HTTP.del "#{Settings.findOne().etcd}instances/#{project}/#{inst.application}/#{instance}?recursive=true", {}, (error, result) ->
+    #   console.log(error) if error
+    #   console.log "Cluster.clearInstance completed -> #{project}, #{instance}"
+    #   sync()
     ""

   saveApp: (name, version, definition) ->


Prevent tooltip from moving when hovering over labels. Fixes #53

--- src/d3-flame-graph.coffee
+++ src/d3-flame-graph.coffee
@@ -280,11 +280,11 @@
       @container.call(@tip)
       @container
         .selectAll('.node')
-          .on 'mouseover', @tip.show
+          .on 'mouseover', (d) => @tip.show(d, d3.event.currentTarget)
           .on 'mouseout', @tip.hide
         .selectAll('.label')
-          .on 'mouseover', () -> console.log('x'); d3.event.preventDefault()
-          .on 'mouseout', () -> console.log('y'); d3.event.preventDefault()
+          .on 'mouseover', (d) => @tip.show(d, d3.event.currentTarget.parentNode)
+          .on 'mouseout', @tip.hide
       @

     _renderAncestors: () ->


engine#add

engine#add

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -35,8 +35,8 @@
   # We must always return self as the last line
   return {
     # Just pass through to the current state
-    add: (clasentityData) ->
-
+    add: (className, entityData) ->
+      if en
       self.trigger "beforeAdd", entityData
       object = I.currentState.add(entityData)
       self.trigger "afterAdd", object


Thrown items stop before hitting walls now

--- src/entities/creature.coffee
+++ src/entities/creature.coffee
@@ -191,7 +191,11 @@

 		found = @raytraceUntilBlocked endPos, range: 15

-		endPos = found if found.type is 'creature'
+		if found.type is 'creature'
+			endPos = found
+
+		else if found.type is 'wall'
+			endPos = found.checked[1]

 		game.renderer.effectLine @, endPos,
 			delay: 50
@@ -299,11 +303,14 @@
 		if _.isFunction opts then [opts, cb] = [{}, opts]
 		opts.range ?= Infinity

+		checked = []
+
 		found = { type: 'none' }

 		bresenhamLine @, to, (x, y) =>
 			return no if (@distanceSqTo {x, y}) > (opts.range * opts.range)

+			checked.unshift {x, y}
 			return if x is @x and y is @y

 			if @map.collidable x, y
@@ -322,6 +329,8 @@
 				}
 				return no

+		found.checked = checked
+
 		found

 	collidable: (x, y) ->


Enabled initialisation of api when user logs in for the first time

--- server/methods.coffee
+++ server/methods.coffee
@@ -99,5 +99,5 @@
     newKey = Random.hexString 32
     APIKeys.insert owner: userId, key: newKey

-# Meteor.users.after.insert (userId, doc) ->
-#   if doc.username in Meteor.settings?.ldap?.admins then Meteor.call "initApiKey", @_id
+Meteor.users.after.insert (userId, doc) ->
+  if doc.username in Meteor.settings?.ldap?.admins then Meteor.call "initApiKey", @_id


Construct optimizer chain only once

Construct optimizer chain only once

--- src/fs_utils/generate.coffee
+++ src/fs_utils/generate.coffee
@@ -135,7 +135,7 @@
   initial = {data, code: data, path, map: prevMap}
   return callback null, initial unless isEnabled

-  chained = optimizers.map (optimizer) ->
+  optimize.chained ?= optimizers.map (optimizer) ->
     (params, next) ->
       debug "Optimizing '#{path}' with '#{optimizer.constructor.name}'"

@@ -172,7 +172,7 @@

       optimizeFn.apply optimizer, optimizerArgs
   first = (next) -> next null, initial
-  waterfall [first].concat(chained), callback
+  waterfall [first].concat(optimize.chained), callback

 generate = (path, sourceFiles, config, optimizers, callback) ->
   type = if sourceFiles.some((file) -> file.type in ['javascript', 'template'])


Use more concise coffee syntax

Use more concise coffee syntax

--- src/smc-webapp/billing.cjsx
+++ src/smc-webapp/billing.cjsx
@@ -1048,10 +1048,7 @@
         applied_coupons : rtypes.immutable.Map

     render: ->
-        v = []
-        @props.applied_coupons.forEach (coupon, id) =>
-            v.push(<CouponInfo key={id} coupon={coupon}/>)
-        return v
+        [<CouponInfo key={coupon.id} coupon={coupon}/> for coupon from @props.applied_coupons.values()]

 CouponInfo = rclass
     displayName : 'CouponInfo'


Add unrefined() method to refinement library; gets unrefined object

--- javascripts/lib.coffee
+++ javascripts/lib.coffee
@@ -67,7 +67,7 @@
       refinement_class = ->
       refinement_class.prototype = subject
       ret = new refinement_class
-      _.extend(ret, @methods)
+      _.extend(ret, {unrefined: -> subject }, @methods)
       ret
   class_mixer RefinementDeclaration

@@ -76,8 +76,7 @@

     isApplicable: (subject)->
       subject instanceof @type
+  class_mixer RefinementByType

-  class_mixer RefinementByType
-
   return Refinement


Switch HathiTrust Bib API calls to use HTTPS by default

--- js/book-aligner.coffee
+++ js/book-aligner.coffee
@@ -44,7 +44,7 @@

 ht_biblio_query = (ht_id, score = 0) ->
   if $("##{html_id(ht_id)}").length == 0
-    $.ajax "http://catalog.hathitrust.org/api/volumes/brief/htid/#{ht_id}.json",
+    $.ajax "https://catalog.hathitrust.org/api/volumes/brief/htid/#{ht_id}.json",
       type: 'GET'
       cache: true
       dataType: 'json'


Simplify industry identifier lookup for HT (see #3)

--- js/book-aligner.coffee
+++ js/book-aligner.coffee
@@ -77,15 +77,10 @@
           ]).draw(false)
           $('#table').DataTable().columns.adjust().draw()
           # (Original from #{ht_object.orig})
-          oclcs = _.uniq(_.values(data.records)[0].oclcs)
-          if oclcs? and (oclcs.length > 0)
-            industry_identifier_query('oclc', oclc_id) for oclc_id in oclcs
-          lccns = _.uniq(_.values(data.records)[0].lccns)
-          if lccns? and (lccns.length > 0)
-            industry_identifier_query('lccn', lccn_id) for lccn_id in lccns
-          isbns = _.uniq(_.values(data.records)[0].isbns)
-          if isbns? and (isbns.length > 0)
-            industry_identifier_query('isbn', isbn_id) for isbn_id in isbns
+          for identifier_type in ['oclc','lccn','isbn']
+            identifiers = _.uniq(_.values(data.records)[0]["#{identifier_type}s"])
+            if identifiers? and (identifiers.length > 0)
+              industry_identifier_query(identifier_type, identifier) for identifier in identifiers

 process_ht_catalog = (identifier_string) ->
   console.log 'process_ht_catalog'


fixes for photo urls

fixes for photo urls

--- src/ui/views/convadd.coffee
+++ src/ui/views/convadd.coffee
@@ -4,14 +4,7 @@

 unique = (obj) -> obj.id.chat_id or obj.id.gaia_id

-photoUrlProtocolFix = (url) -> return "http:" + url if url?.indexOf('//') == 0
-
-inputSetValue = (sel, val) ->
-    setTimeout ->
-        el = document.querySelector sel
-        el.value = val if el != null
-    , 1
-    null
+photoUrlProtocolFix = (url) -> if url?.indexOf('//') == 0 then "https:#{url}" else url

 module.exports = view (models) ->
     {convsettings, entity} = models
@@ -27,7 +20,6 @@
                   placeholder: 'Conversation name'
                   onkeyup: (e) ->
                       action 'conversationname', e.currentTarget.value
-              inputSetValue '.name-input', convsettings.name

       div class: 'input', ->
           div ->
@@ -37,13 +29,12 @@
                   onkeyup: (e) ->
                       chilledaction 'searchentities', e.currentTarget.value, 7
                       action 'conversationquery', e.currentTarget.value, 7
-              inputSetValue '.search-input', convsettings.searchQuery

       ul ->
           convsettings.selectedEntities.forEach (r) ->
               cid = r?.id?.chat_id
               li class: 'selected', ->
-                  if purl = r.properties.photo_url ? entity[cid].photo_url
+                  if purl = r.properties?.photo_url ? entity[cid]?.photo_url
                       img src:photoUrlProtocolFix purl
                   else
                       img src:"images/photo.jpg"
@@ -57,7 +48,7 @@
               cid = r?.id?.chat_id
               if unique(r) in selected_ids then return
               li ->
-                  if purl = r.properties.photo_url ? entity[cid].photo_url
+                  if purl = r.properties?.photo_url ? entity[cid]?.photo_url
                       img src:photoUrlProtocolFix purl
                   else
                       img src:"images/photo.jpg"


Turn meme log message to debug.

Turn meme log message to debug.

--- src/ui/views/convadd.coffee
+++ src/ui/views/convadd.coffee
@@ -84,7 +84,7 @@
                 cid = r?.id?.chat_id
                 ctLocal = ''
                 ctInfo = ''
-                console.log('meme', r.properties, entity[cid])
+                console.debug('meme', r.properties, entity[cid])
                 if entity[cid]?.type == 0
                     ctLocal = 'ct-local'
                     ctInfo = 'Internal Contact' + "\r\n"


Add scrollTop when add new widget

Add scrollTop when add new widget

--- app/coffeescripts/bundles/account_homepage.coffee
+++ app/coffeescripts/bundles/account_homepage.coffee
@@ -139,6 +139,10 @@
         $data = $(data).addClass("new_widget_ajax")
         $container.append($data)
         makeWidgetsDeletable()
+        $('body').animate({
+          scrollTop: $data.offset().top
+          backgroundColor:'yellow'
+        }, 500)
         $(".add_widget_loading_icon").hide()
         $("#add_widget").show()


using inline-block for the header (fix for FF)

Former-commit-id: 70c14fe19b5f4d9b2c8f5a44c29f70f61da95084 [formerly 56eb8b8291829906612092d4ba1c65d2da54652c]
Former-commit-id: a3de76269e29764a2c1b475915c29f853b27057b

--- src/views/HeaderView.coffee
+++ src/views/HeaderView.coffee
@@ -20,7 +20,7 @@
       # padding el
       spacer = document.createElement "span"
       spacer.innerHTML = " "
-      spacer.style.display = "block"
+      spacer.style.display = "inline-block"
       spacer.style.float = "left"
       spacerWidth = 0
       spacerWidth += @g.zoomer.get "labelWidth" if @g.vis.get "labels"


ComputeController: checkGroupStacks: re-fetch current group and compare stacks

After we got the StackTemplateChanged notification we need to re-fetch latest JGroup from remote.api since we've disabled auto update instance for it, it's not getting updated when a change happens cc/ @cihangir

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -46,7 +46,7 @@
       @on "MachineBuilt",     => do @reset
       @on "MachineDestroyed", => do @reset

-      groupsController.on 'StackTemplateChanged', @bound 'checkStackRevisions'
+      groupsController.on 'StackTemplateChanged', @bound 'checkGroupStacks'

       @fetchStacks =>

@@ -387,6 +387,8 @@
       else
         @emit 'StacksNotConfigured'

+      @checkGroupStackRevisions()
+

   # remote.ComputeProvider and Kloud kite public methods
   #
@@ -879,6 +881,22 @@
           @emit 'StacksInconsistent', stack

+  checkGroupStacks: ->
+
+    @checkStackRevisions()
+
+    { groupsController } = kd.singletons
+    { slug } = currentGroup = groupsController.getCurrentGroup()
+
+    remote.api.JGroup.one { slug }, (err, _currentGroup) =>
+      return kd.warn err  if err
+      return kd.warn 'No such Group!'  unless _currentGroup
+
+      currentGroup.stackTemplates = _currentGroup.stackTemplates
+
+      @checkGroupStackRevisions()
+
+
   checkGroupStackRevisions: ->

     return  if isKoding()


Toggle state of "show my events" button

Toggle state of "show my events" button

--- client/controllers/smartEvents/smartEvents.coffee
+++ client/controllers/smartEvents/smartEvents.coffee
@@ -79,10 +79,10 @@

   'click .show-current-user-events': (event, instance) ->
     filterSelector = ''
-    createFilter = instance.creatorFilter
+    creatorFilter = instance.creatorFilter
     showCurrentUserEvents = instance.showCurrentUserEvents
-    if not createFilter.get()
+    if not creatorFilter.get()
       filterSelector = $eq: Meteor.userId()
-      showCurrentUserEvents.set(not showCurrentUserEvents.get())
-    createFilter.set(filterSelector)
+    showCurrentUserEvents.set(not showCurrentUserEvents.get())
+    creatorFilter.set(filterSelector)
     $(event.currentTarget).blur()


Use -> arrows on methods moved to the model

--- src/pane-model.coffee
+++ src/pane-model.coffee
@@ -72,7 +72,7 @@
     @items[index]

   # Public: Switches to the next contained item.
-  showNextItem: =>
+  showNextItem: ->
     index = @getActiveItemIndex()
     if index < @items.length - 1
       @showItemAtIndex(index + 1)
@@ -80,7 +80,7 @@
       @showItemAtIndex(0)

   # Public: Switches to the previous contained item.
-  showPreviousItem: =>
+  showPreviousItem: ->
     index = @getActiveItemIndex()
     if index > 0
       @showItemAtIndex(index - 1)
@@ -182,11 +182,11 @@
       when 2 then true

   # Public: Saves the currently focused item.
-  saveActiveItem: =>
+  saveActiveItem: ->
     @saveItem(@activeItem)

   # Public: Save and prompt for path for the currently focused item.
-  saveActiveItemAs: =>
+  saveActiveItemAs: ->
     @saveItemAs(@activeItem)

   # Public: Saves the specified item and call the next action when complete.
@@ -210,7 +210,7 @@
       nextAction?()

   # Public: Saves all items in this pane.
-  saveItems: =>
+  saveItems: ->
     @saveItem(item) for item in @getItems()

   # Public: Finds the first item that matches the given uri.


add b2 in this module

add b2 in this module

--- coffeescript/004-webworkers/multiplication.coffee
+++ coffeescript/004-webworkers/multiplication.coffee
@@ -6,8 +6,9 @@
 define [
   'underscore',
   'cs!/wahlque/geometry/vector',
+  'cs!/wahlque/nbody/bodies2',
   'cs!/wahlque/units/au'
-], (_, v, au) ->
+], (_, v, b2, au) ->
     self.postMessage('before binding')
     self.onmessage = (e) ->
         self.postMessage('hello from worker!')
@@ -15,6 +16,7 @@
         self.postMessage(result)
         self.postMessage(v.norm([1,1,1,1]))
         self.postMessage(au.G)
+        self.postMessage(v.norm(b2.derivative([1,1])([1,0,1,0,-1,0,-1,0])))
         true
     self.postMessage('after binding')
     true


允许用户获取公开的项目

允许用户获取公开的项目

--- src/entity/project.coffee
+++ src/entity/project.coffee
@@ -109,6 +109,9 @@
     rightTable += " AND M.member_id = #{cond.member_id}" if cond.member_id
     rightTable += " AND M.role = '#{cond.role}'" if cond.role
     rightTable += " OR P.flag = 1" if cond.special
+    #允许获取公开的项目
+    rightTable += " OR visibility_level = #{_common.enumerate.visibilityLevel.public}"
+    #团队内的项目还没有
     rightTable += ') AS tmp'

     #最新版本的id


fire: Remove some flaky config

fire: Remove some flaky config

--- spec/editor-linter-spec.coffee
+++ spec/editor-linter-spec.coffee
@@ -37,35 +37,6 @@
       textEditor.save()
       textEditor.save()
       expect(timesTriggered).toBe(5)
-    it 'respects lintOnFlyInterval config', ->
-      timeCalled = null
-      flyStatus = null
-      atom.config.set('linter.lintOnFlyInterval', 300)
-      editorLinter.onShouldLint (fly) ->
-        flyStatus = fly
-        timeCalled = new Date()
-      timeDid = new Date()
-      editorLinter.editor.insertText("Hey\n")
-      waitsForPromise ->
-        wait(300).then ->
-          expect(timeCalled isnt null).toBe(true)
-          expect(flyStatus isnt null).toBe(true)
-          expect(flyStatus).toBe(true)
-          expect(timeCalled - timeDid).toBeLessThan(400)
-
-          atom.config.set('linter.lintOnFlyInterval', 600)
-          timeCalled = null
-          flyStatus = null
-          timeDid = new Date()
-          editorLinter.editor.insertText("Hey\n")
-
-          wait(600)
-        .then ->
-          expect(timeCalled isnt null).toBe(true)
-          expect(flyStatus isnt null).toBe(true)
-          expect(flyStatus).toBe(true)
-          expect(timeCalled - timeDid).toBeGreaterThan(599)
-          expect(timeCalled - timeDid).toBeLessThan(700)

   describe '::onDidDestroy', ->
     it 'is called when TextEditor is destroyed', ->


:art: Rename variable to increase clarity

--- spec/notifications-spec.coffee
+++ spec/notifications-spec.coffee
@@ -680,7 +680,7 @@

         describe "when the message is longer than 100 characters", ->
           message = "Uncaught Error: Cannot find module 'dialog'Error: Cannot find module 'dialog' at Function.Module._resolveFilename (module.js:351:15) at Function.Module._load (module.js:293:25) at Module.require (module.js:380:17) at EventEmitter.<anonymous> (/Applications/Atom.app/Contents/Resources/atom/browser/lib/rpc-server.js:128:79) at EventEmitter.emit (events.js:119:17) at EventEmitter.<anonymous> (/Applications/Atom.app/Contents/Resources/atom/browser/api/lib/web-contents.js:99:23) at EventEmitter.emit (events.js:119:17)"
-          truncatedMessage = "Uncaught Error: Cannot find module 'dialog'Error: Cannot find module 'dialog' at Function.Module...."
+          expectedIssueTitle = "Uncaught Error: Cannot find module 'dialog'Error: Cannot find module 'dialog' at Function.Module...."

           beforeEach ->
             generateFakeFetchResponses()
@@ -700,7 +700,7 @@
             runs ->
               button = fatalError.querySelector('.btn')
               expect(button.textContent).toContain 'Create issue'
-              expect(fatalError.issue.getIssueTitle()).toBe(truncatedMessage)
+              expect(fatalError.issue.getIssueTitle()).toBe(expectedIssueTitle)

       describe "when the package is out of date", ->
         beforeEach ->


do not read non-js files in master dir

do not read non-js files in master dir

--- src/fixture-loader.coffee
+++ src/fixture-loader.coffee
@@ -23,6 +23,8 @@

         for file in fs.readdirSync @fixtureDir + '/data'
             [ modelName, ext ] = file.split('.')
+
+            continue if ext not in ['coffee', 'js']

             tables[modelName] = @loadFile(file)


Remove merge marker that was left in after rebase

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -225,7 +225,6 @@
     {page_ids} = @_getClonedSettings(id, 'page_ids')
     @_changeSettings(id, page_ids: sortTopics(page_ids))

-<<<<<<< dcee8c903f73b70baba4c9a7ad123e1f69dc8627
   addTopic: (id, topicId) ->
     {page_ids} = @_getClonedSettings(id, 'page_ids')
     page_ids.push(topicId) unless page_ids.indexOf(topicId) >= 0


Drop atom- prefix from temp files

Drop atom- prefix from temp files

--- spec/file-spec.coffee
+++ spec/file-spec.coffee
@@ -8,7 +8,7 @@
   [filePath, file] = []

   beforeEach ->
-    filePath = path.join(__dirname, 'fixtures', 'atom-file-test.txt') # Don't put in /tmp because /tmp symlinks to /private/tmp and screws up the rename test
+    filePath = path.join(__dirname, 'fixtures', 'file-test.txt') # Don't put in /tmp because /tmp symlinks to /private/tmp and screws up the rename test
     fs.removeSync(filePath)
     fs.writeFileSync(filePath, "this is old!")
     file = new File(filePath)
@@ -60,7 +60,7 @@
       newPath = null

       beforeEach ->
-        newPath = path.join(path.dirname(filePath), "atom-file-was-moved-test.txt")
+        newPath = path.join(path.dirname(filePath), "file-was-moved-test.txt")

       afterEach ->
         if fs.existsSync(newPath)


Some more specs for declareElement

Some more specs for declareElement

--- spec/genx_spec.coffee
+++ spec/genx_spec.coffee
@@ -12,3 +12,14 @@
       it 'raises an exception if there is the wrong number of arguments', ->
         expect(-> w.declareElement()).toThrow('Wrong number of arguments to declareElement')
         expect(-> w.declareElement(true, true, true)).toThrow('Wrong number of arguments to declareElement')
+
+      describe 'with one argument', ->
+
+        it 'raises an exception if it isn\'t a string', ->
+          expect(-> w.declareElement(1)).toThrow('First argument must be a string')
+
+        it 'returns an element', ->
+          expect(w.declareElement 'test').toBeDefined()
+
+      describe 'with two arguments', ->
+


react: fix search to work with multiple terms; also focus on clear

--- salvus/page/project_log.cjsx
+++ salvus/page/project_log.cjsx
@@ -45,9 +45,13 @@
         search    : ''   # search that user has typed in so far

     clear_and_focus_input : ->
-        @setState(search : '', -> React.findDOMNode(@refs.search).focus())
-
-    render_search_button: ->
+        @setState(search : '')
+        #React.findDOMNode(@refs.search).focus()
+        # TODO: Using jquery -- this hack actually works, unlike the above -- maybe react-bootstrap needs a focus method...
+        $(React.findDOMNode(@refs.search)).find("input").focus()
+        @props.do_search('')
+
+    render_clear_button: ->
         <Button onClick={@clear_and_focus_input}>
             <Icon name="times-circle" />
         </Button>
@@ -69,7 +73,7 @@
                 ref         = "search"
                 placeholder = "Search log..."
                 onChange    = {(e) => e.preventDefault(); x=@refs.search.getValue(); @setState(search:x); @props.do_search(x)}
-                buttonAfter = {@render_search_button()}
+                buttonAfter = {@render_clear_button()}
                 />
         </form>

@@ -143,6 +147,12 @@
         </div>

 PAGE_SIZE = 10  # number of entries to show per page (TODO: move to account settings)
+
+matches = (s, words) ->
+    for word in words
+        if s.indexOf(word) == -1
+            return false
+    return true

 ProjectLog = rclass
     propTypes: ->
@@ -202,8 +212,9 @@
                     x.search = (users.get_name(x.account_id) + " " + (x.event?.filename ? "")).toLowerCase()
                     v.push(x)
             log = v
+        words = misc.split(@state.search)
         if @state.search
-            log = (x for x in log when x.search.indexOf(@state.search) != -1)
+            log = (x for x in log when matches(x.search, words))
         page = @state.page
         num_pages = Math.ceil(log.length / PAGE_SIZE)
         log = log.slice(PAGE_SIZE*page, PAGE_SIZE*(page+1))


Clarify PulsarApi construction. Remove the default config extending redundancy.

--- src/pulsar-api.coffee
+++ src/pulsar-api.coffee
@@ -9,12 +9,8 @@

   constructor: ()->
     pulsarApiConfig = config.pulsarApi
-
-    clientDefaultConfig = _.pick(pulsarApiConfig, 'url', 'token')
-    @_clientDefault = new PulsarApiClient(clientDefaultConfig.url, clientDefaultConfig.authToken)
-
+    @_clientDefault = new PulsarApiClient(pulsarApiConfig.url, pulsarApiConfig.authToken)
     _.each pulsarApiConfig.auxiliary, (clientConfig, key) ->
-      clientConfig = _.defaults({}, clientConfig, clientDefaultConfig)
       _clientMap[key] = new PulsarApiClient(clientConfig.url, clientConfig.authToken)

   getClientDefault: () ->


disable bigger radio buttons for mobile

disable bigger radio buttons for mobile


Adjust contact models so that poorly populated data doesn't crash the edit interface

--- ui/coffee/models.coffee
+++ ui/coffee/models.coffee
@@ -114,6 +114,10 @@

 class root.ContactInformation extends Backbone.Model
   initialize: (options) ->
+    # Generate a blank contact from the schema if nothing was passed in
+    if ( prop for prop, value of options ).length is 0
+      getEmptyInstance @, 'contactInformation'
+
     # Push Address information to a sub-model
     address = @get 'Address'
     if address?
@@ -122,7 +126,10 @@

 class root.Address extends Backbone.Model
   initialize: (options) ->
-
+    # Generate a blank contact from the schema if nothing was passed in
+    if ( prop for prop, value of options ).length is 0
+      getEmptyInstance @, 'address'
+
 class root.Contacts extends Backbone.Collection
   model: root.Contact


AccountModal: Splice needed danger items.

--- client/account/lib/index.coffee
+++ client/account/lib/index.coffee
@@ -134,8 +134,10 @@
     groupSlug  = kd.singletons.groupsController.getGroupSlug()
     items = []

+    dangerItems = NAV_ITEMS.danger.items
+
     index = if isKoding() then 1 else 0
-    NAV_ITEMS.danger.items.splice index, 1 # remove leave or danger
+    dangerItems.slice index, 1 # remove leave or danger

     for own sectionKey, section of NAV_ITEMS
       mergeables = []


client: builder: expose kd no matter what

--- client/builder/lib/index.coffee
+++ client/builder/lib/index.coffee
@@ -290,10 +290,7 @@
     else
       b = bant opts_

-    if not opts.watchJs
-      # xxx: breaks watchify in watch mode, dedupe fuckup
-      b.require require.resolve('kd.js'), { expose: 'kd' }
-
+    b.require require.resolve('kd.js'), { expose: 'kd' }
     b.use manifests

     if opts.collapseJs then b.plugin collapse


AppController: Change param name.

AppController: Change param name.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1176,9 +1176,9 @@
     return targetPane

-  createPaneFromChange: (change, isOffline) ->
-
-    return  if not @rtm and not isOffline
+  createPaneFromChange: (change = {}, isFromLocalStorage) ->
+
+    return  if not @rtm and not isFromLocalStorage

     {context} = change
     return  unless context


Added mousewheel plugin support so shifting with horizontal mousewheel movement is now possible. Control left/right are now hidden if action is not possible. Improved some comments. Some refactorings.

--- src/jquery.rondell.coffee
+++ src/jquery.rondell.coffee
@@ -62,6 +62,11 @@
       strings: # String for the controls
         prev: 'prev'
         next: 'next'
+      mousewheel:
+        enabled: true
+        threshold: 5
+        minTimeBetweenShifts: 300
+        _lastShift: 0
       touch:
         enabled: true
         preventDefaults: true   # Will call event.preventDefault() on touch events
@@ -193,8 +198,8 @@
       scaling = @scaling

       # create size vars for the small and focused size
-      foWidth = smWidth = copy?.width() || copy?[0].width || icon[0].width || icon.width()
-      foHeight = smHeight = copy?.height() || copy?[0].height || icon[0].height || icon.height()
+      foWidth = smWidth = copy?.width() or copy?[0].width or icon[0].width or icon.width()
+      foHeight = smHeight = copy?.height() or copy?[0].height or icon[0].height or icon.height()

       # Delete copy, not needed anymore
       copy?.remove()
@@ -262,39 +267,75 @@
       # Create controls
       controls = @controls
       if controls.enabled
-        shiftLeft = $('').text(@strings.prev).click(@shiftLeft)
+        @controls._shiftLeft = $('').text(@strings.prev).click(@shiftLeft)
         .css(
           left: controls.margin.x
           top: controls.margin.y
           "z-index": @zIndex + @maxItems + 2
         )

-        shiftRight = $('').text(@strings.next).click(@shiftRight)
+        @controls._shiftRight = $('').text(@strings.next).click(@shiftRight)
         .css(
           right: controls.margin.x
           top: controls.margin.y
           "z-index": @zIndex + @maxItems + 2
         )

-        @container.append(shiftLeft, shiftRight)
-
+        @container.append(@controls._shiftLeft, @controls._shiftRight)

       # Attach keydown event to document for each rondell instance
       $(document).keydown(@keyDown)
-
-      # Add hover and touch functions to container
-      @container.removeClass('initializing').bind('mouseover mouseout', @_hover).bind('touchstart touchmove touchend', @_touch)
-
-      # Show items parent container
+
+      # Enable rondell traveling with mousewheel if plugin is available
+      @container.bind('mousewheel', @_onMousewheel) if @mousewheel.enabled and $.fn.mousewheel?
+
+      # Use modernizr feature detection to enable touch device support
+      if @_onMobile()
+        # Enable swiping
+        @container.bind('touchstart touchmove touchend', @_onTouch) if @touch.enabled
+      else
+        # Add hover and touch functions to container when we don't have touch support
+        @container.bind('mouseover mouseout', @_hover)
+
+      @container.removeClass('initializing')
+
+      # Show items hidden parent container to prevent graphical glitch
       @container.parent().show() if @showContainer

-      # Fire callback with rondell instance if callback was provided
+      # Fire callback after initialization with rondell instance if callback was provided
       @initCallback?(@)

       # Move items to starting positions
       @shiftTo(@currentLayer)

-    _touch: (e) =>
+    _onMobile: ->
+      ###
+      Mobile device detection.
+      Check for touch functionality is currently enough.
+      ###
+      return Modernizr?.touch
+
+    _onMousewheel: (e, d, dx, dy) =>
+      ###
+      Allows rondell traveling with mousewheel.
+      Requires mousewheel plugin for jQuery.
+      ###
+      return unless (@mousewheel.enabled and @isFocused())
+
+      now = (new Date()).getTime()
+      return if now - @mousewheel._lastShift < @mousewheel.minTimeBetweenShifts
+
+      viewport = $(window)
+      viewportTop = viewport.scrollTop()
+      viewportBottom = viewportTop + viewport.height()
+
+      selfYCenter = @container.offset().top + @container.outerHeight() / 2
+
+      if selfYCenter > viewportTop and selfYCenter < viewportBottom and Math.abs(dx) >= @mousewheel.threshold
+        if dx < 0 then @shiftLeft() else @shiftRight()
+        @mousewheel._lastShift = now
+
+    _onTouch: (e) =>
       return unless @touch.enabled

       touch = e.originalEvent.touches[0] or e.originalEvent.changedTouches[0]
@@ -311,6 +352,7 @@
             y: touch.pageY
         when 'touchend'
           if @touch._start and @touch._end
+            # Check if delta x is greater than our threshold for swipe detection
             changeX = @touch._end.x - @touch._start.x
             if Math.abs(changeX) > @touch.threshold
               if changeX > 0
@@ -323,14 +365,19 @@

       true

-    _hover: (e) =>
-      # Show or hide controls if they exist
-      $('.rondellControl', @container).stop().fadeTo(@controls.fadeTime, if e.type is 'mouseover' then 1 else 0)
+    _hover: (e) =>
+      ###
+      Shows/hides rondell controls.
+      Starts/pauses autorotation.
+      Updates active rondell id.
+      ###

       # Start or stop auto rotation if enabled
       paused = @autoRotation.paused
       if e.type is 'mouseover'
-        Rondell.activeRondell = @.id
+        # Set active rondell id if hovered
+        Rondell.activeRondell = @id
+
         @hovering = true
         unless paused
           @autoRotation.paused = true
@@ -341,6 +388,9 @@
           @autoRotation.paused = false
           @_autoShift()
         @hideCaption(@currentLayer) unless @alwaysShowCaption
+
+      # Show or hide controls if they exist
+      @_refreshControls() if @controls.enabled

     layerFadeIn: (layerNum) =>
       item = @_getItem(layerNum)
@@ -398,7 +448,7 @@
       fadeTime = @fadeTime + @itemProperties.delay * layerDist
       isNew = item.object.hasClass('rondellItemNew')

-      # Is item visible
+      # Smooth animation when item is visible
       if isNew or layerDist <= @visibleItems
         @hideCaption(layerNum)

@@ -428,7 +478,7 @@
               , fadeTime
             )
       else if item.hidden
-        ### Update position even if out of view to fix animation when reappearing ###
+        # Update position even if out of view to fix animation when reappearing
         item.object.css(
           left: newX
           top: newY
@@ -459,14 +509,22 @@
         # Hide all layers except the current layer
         @layerFadeOut(i) for i in [1..@maxItems] when i isnt @currentLayer
         @layerFadeIn(@currentLayer)
-
+
+      @_refreshControls()
+
+    _refreshControls: =>
+      return unless @controls.enabled
+
+      @controls._shiftLeft.stop().fadeTo(@controls.fadeTime, if @currentLayer > 1 or @repeating and @hovering then 1 else 0)
+      @controls._shiftRight.stop().fadeTo(@controls.fadeTime, if @currentLayer < @maxItems or @repeating and @hovering then 1 else 0)
+
     shiftLeft: (e) =>
       e?.preventDefault()
       @shiftTo(@currentLayer - 1)

     shiftRight: (e) =>
       e?.preventDefault()
-      @shiftTo(@currentLayer + 1)
+      @shiftTo(@currentLayer + 1)

     _autoShift: =>
       autoRotation = @autoRotation
@@ -481,9 +539,12 @@

     isActive: ->
       true
+
+    isFocused: =>
+      Rondell.activeRondell is @id

     keyDown: (e) =>
-      if @isActive() and Rondell.activeRondell is @.id
+      if @isActive() and @isFocused()
         # Clear current rotation timer on user interaction
         if @autoRotation._timer >= 0
           window.clearTimeout(@autoRotation._timer)


Stop using asar archives

Stop using asar archives

--- src/apm.coffee
+++ src/apm.coffee
@@ -26,28 +26,24 @@

     apmFolder = path.resolve(__dirname, '..')
     appFolder = path.dirname(apmFolder)
-    if path.basename(apmFolder) is 'apm' and path.basename(appFolder) is 'app'
-      asarPath = "#{appFolder}.asar"
-      if fs.existsSync(asarPath)
-        return process.nextTick -> callback(asarPath)
+    if path.basename(apmFolder) is 'apm' and path.basename(appFolder) is 'app' and fs.existsSync(appFolder)
+      return process.nextTick -> callback(appFolder)

     apmFolder = path.resolve(__dirname, '..', '..', '..')
     appFolder = path.dirname(apmFolder)
-    if path.basename(apmFolder) is 'apm' and path.basename(appFolder) is 'app'
-      asarPath = "#{appFolder}.asar"
-      if fs.existsSync(asarPath)
-        return process.nextTick -> callback(asarPath)
+    if path.basename(apmFolder) is 'apm' and path.basename(appFolder) is 'app' and fs.existsSync(appFolder)
+      return process.nextTick -> callback(appFolder)

     switch process.platform
       when 'darwin'
         child_process.exec 'mdfind "kMDItemCFBundleIdentifier == \'com.github.atom\'"', (error, stdout='', stderr) ->
           [appLocation] = stdout.split('\n') unless error
           appLocation = '/Applications/Atom.app' unless appLocation
-          callback("#{appLocation}/Contents/Resources/app.asar")
+          callback("#{appLocation}/Contents/Resources/app")
       when 'linux'
-        appLocation = '/usr/local/share/atom/resources/app.asar'
+        appLocation = '/usr/local/share/atom/resources/app'
         unless fs.existsSync(appLocation)
-          appLocation = '/usr/share/atom/resources/app.asar'
+          appLocation = '/usr/share/atom/resources/app'
         process.nextTick -> callback(appLocation)

   getReposDirectory: ->


whitespace

whitespace

--- src/app.coffee
+++ src/app.coffee
@@ -15,6 +15,7 @@
         chrome.runtime.onStartup.addListener =>
             console.log 'Starting up; calling update!'
             @update()
+
         chrome.idle.onStateChanged.addListener (newstate) =>
             if newstate is 'active'
                 console.log 'Changed state to \'active\'; calling update!'


Warmed up cache offset

Warmed up cache offset

--- src/app.coffee
+++ src/app.coffee
@@ -191,8 +191,8 @@
             console.log "Connected."
             callback null
       (callback) =>
-        console.log "Warming up recent posts cache..."
         warmUpCache = (callback) ->
+          console.log "Warming up recent posts cache..."
           async.waterfall [
             (callback) ->
               ClientOnlyToken.ensure config.clientID, config.clientSecret, callback
@@ -205,8 +205,11 @@
               console.error err
             else
               console.dir {posts: posts.length, days: days, message: "Warmed up cache"}
-        # Warm up every 15 minutes
-        @warmupTimeout = setTimeout periodic, 15 * 60 * 1000
+        # Warm up every 30 minutes
+        startPeriodic = () ->
+          @warmupInterval = setInterval periodic, 30 * 60 * 1000
+        # Use a random offset so load-balanced servers warm up at different times
+        setTimeout startPeriodic, Math.floor(Math.random() * 30 * 60 * 1000)
         # Warm up now
         warmUpCache (err) ->
           callback err