improve on the error routing

improve on the error routing

--- library/membrane/duplex.coffee
+++ library/membrane/duplex.coffee
@@ -285,9 +285,13 @@
         reconnect = "attempting to reconnect at #{@location}"
         connecting = "attempting connection at #{@location}"
         disconnect = "lost socket connection at #{@location}"
-        @socket.on "disconnect", => this.emit "disconnect"
-        @socket.on "connect_failed", => @emit "disconnect"
-        @socket.on "exception", => $root?.emit? "exception"
+        r = (e, s) => this.emit(e, s...); $root.emit(e, s...)
+        if _.isEmpty @STOP_ROOT_PROPAGATION or undefined
+            @socket.on "connect", => r "connect", arguments
+            @socket.on "exception", => r "exception", arguments
+            @socket.on "disconnect", => r "disconnect", arguments
+            @socket.on "connect_failed", => r "disconnect", arguments
+            @socket.on "reconnecting", => r "reconnecting", arguments
         @socket.on "exception", (e) -> logger.error p, e.message
         @socket.on "error", (e) -> logger.error c, e.message
         @socket.on "reconnecting", -> logger.info reconnect


add generic message

add generic message

--- library/membrane/health.coffee
+++ library/membrane/health.coffee
@@ -61,10 +61,12 @@
     @heartrate: (bound) -> (callback) ->
         internalError = "heartbeat internal error"
         wrongWiring = "received no valid callback"
+        generic = "the health status is rejected"
         assert _.isFunction(bound), internalError
         assert _.isFunction(callback), wrongWiring
         accept = -> callback undefined, yes
         return bound accept, (message) ->
+            message = generic unless message?
             noMessage = "got no rejection message"
             assert _.isString(message), noMessage
             return callback undefined, message


one more minor change in here

one more minor change in here

--- library/membrane/remote.coffee
+++ library/membrane/remote.coffee
@@ -76,7 +76,7 @@
     assert compiled.remote = Object.create {}
     assert compiled.remote.compiled = compiled
     assert compiled.remote.compile = compiler
-    assert compiled.remote.btable = b = Object()
+    assert compiled.remote.bonding = b = Object()
     assert compiled.remote.source = tabled(pv) b
     assert compiled.remote.tabled = tabled(pv)
     assert compiled.remote.tleaky = tabled(pn)


Avoid exceptions on invalid nick/name

Avoid exceptions on invalid nick/name

--- library/nucleus/routing.coffee
+++ library/nucleus/routing.coffee
@@ -74,7 +74,7 @@
     registerRoutable: (routable) ->
         nick = routable?.constructor?.nick
         name = routable?.constructor?.name
-        inspected = nick or name or undefined
+        inspected = nick or name or typeof routable
         identify = inspected.toString().underline
         duplicate = routable in (@registry or [])
         [matches, process] = [routable.matches, routable.process]
@@ -97,7 +97,7 @@
     installFallback: (routable) ->
         nick = routable?.constructor?.nick
         name = routable?.constructor?.name
-        inspected = nick or name or undefined
+        inspected = nick or name or typeof routable
         identify = inspected.toString().underline
         [matches, process] = [routable.matches, routable.process]
         goneMatches = "The #{inspected} has no valid matches method"


Cleanup test-layout

Cleanup test-layout

--- src/layout/test_layout.coffee
+++ src/layout/test_layout.coffee
@@ -11,6 +11,7 @@
 exports.test_layout = (test, rx) ->
   game_dict = game({name: 'example', level: 1})
   config = layout(game_dict)
+  world = god(rx, config)

   test "layout config", (t) ->
     t.ok game_dict, 'game_dict'
@@ -18,11 +19,11 @@
     t.ok controls, 'controls'
     t.ok config, 'config'
     t.ok grid, 'grid'
-    console.log 'game config', config
+    t.ok world, 'world'
+    console.log 'game config', config, world
     t.end()

-  test "layout as world", (t) ->
-    t.ok world = god(rx, config), 'world'
+  test "layout properties", (t) ->
     t.ok shapes = world.get('shapes'), 'shapes'
     t.ok levels = world.get('levels'), 'levels'
     t.ok world.is_array(levels), 'levels rx_array'
@@ -30,7 +31,13 @@
     t.ok level = world._child_array()[0], 'active level'
     t.ok sprites = level.get('sprites'), 'sprites'
     t.ok world.is_array(sprites), 'sprites rx_array'
-    t.ok sprite = sprites.at(0), 'sprite'
-    t.ok sprite.get('paths'), 'paths'
+    t.ok sprite_dict = sprites.at(0), 'sprite'
     t.end()

+  test "layout children", (t) ->
+    t.ok level = world.find_child(), 'active level'
+    t.ok layout = level.find_child(), 'layout'
+    t.ok grid = level.find_child('grid'), 'grid'
+    t.ok sprite = level.find_child('sprite'), 'grid'
+    t.end()
+


Only write predata if we're not in a Range Request, or if the range request start is 0

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -160,6 +160,11 @@
             rangeVals    = req.headers.range.match(/bytes ?= ?(\d+)-(\d+)?/)
             rangeRequest = true

+            # Get the requested start and end
+            # Force into integers
+            requestStart    = rangeVals[1] - 0
+            requestEnd      = rangeVals[2] - 0 or undefined
+

         @loadPreroll stream_key, req, (predata = null) =>
             # compute our final size
@@ -177,14 +182,9 @@
             @listeners++

             if rangeRequest
-                # Get the requested start and end
-                # Force into integers
-                requestStart    = rangeVals[1] - 0
-                requestEnd      = rangeVals[2] - 0 or undefined
-
                 rangeStart  = if (requestStart  <= fend) then requestStart else 0
                 rangeEnd    = if (requestEnd    <= fend) then requestEnd   else fend
-
+
                 length = (rangeEnd - rangeStart) + 1

             # What is the actual length of content being sent back?
@@ -215,20 +215,29 @@
                 return true

-            # if we have an id3 or preroll, write them
-            res.write id3 if id3
-            res.write predata if predata
-
-
             # now set up our file read as a stream
             console.debug "creating read stream. #{@listeners} active downloads."
             readStreamOpts = bufferSize: 256*1024
+

             # If this is a range request, only deliver that range of bytes
             if rangeRequest
                 console.debug "Sending byte range #{rangeStart}-#{rangeEnd}"
                 readStreamOpts['start'] = rangeStart
                 readStreamOpts['end']   = rangeEnd
+
+            if !rangeRequest || rangeStart == 0
+                # if we have an id3 or preroll, write them
+                # We only want to do this if we're not doing a range request
+                # (i.e. requesting the whole file), OR if the rangeStart is 0
+                # (i.e. listener is at the beginning of the stream).
+                # Otherwise, every time someone seeks or lose connection or
+                # otherwise need to request another chunk of data, they'll
+                # get the preroll again.
+                res.write predata if predata
+
+            res.write id3 if id3
+

             console.debug "read stream opts are", readStreamOpts
             rstream = fs.createReadStream filename, readStreamOpts


Fixed requires

Fixed requires

--- globe-geometry/mercator/mercator.coffee
+++ globe-geometry/mercator/mercator.coffee
@@ -4,6 +4,8 @@

 goog.provide 'globeGeometry.mercator'

+goog.require 'globeGeometry.LatLng'
+goog.require 'globeGeometry.LatLngBounds'
 goog.require 'globeGeometry.Point'

 class globeGeometry.mercator


:lipstick:

:lipstick:

--- src/list.coffee
+++ src/list.coffee
@@ -99,7 +99,7 @@
       _atomPackages ?= {}

       if options.argv.json
-        packageMetadata = (v['metadata'] for k, v of _atomPackages)
+        packageMetadata = (v.metadata for k, v of _atomPackages)
         packages = packageMetadata.filter ({name}) ->
           packageDependencies.hasOwnProperty(name)
       else
@@ -115,25 +115,24 @@
       callback?(null, packages)

   listInstalledPackages: (options) ->
-    @listDevPackages options, (err, packages) =>
+    @listDevPackages options, (error, packages) =>
       @logPackages(packages, options)
-    @listUserPackages options, (err, packages) =>
+    @listUserPackages options, (error, packages) =>
       @logPackages(packages, options)

   listPackagesAsJson: (options) ->
-    out =
+    output =
       core: []
       dev: []
       user: []

-    @listBundledPackages options, (err, packages) =>
-      out.core = packages
-      @listDevPackages options, (err, packages) =>
-        out.dev = packages
-        @listUserPackages options, (err, packages) =>
-          out.user = packages
-          console.log JSON.stringify(out)
-
+    @listBundledPackages options, (error, packages) =>
+      output.core = packages
+      @listDevPackages options, (error, packages) =>
+        output.dev = packages
+        @listUserPackages options, (error, packages) =>
+          output.user = packages
+          console.log JSON.stringify(output)

   run: (options) ->
     {callback} = options
@@ -145,7 +144,7 @@
       @listInstalledPackages(options)
       callback()
     else
-      @listBundledPackages options, (err, packages) =>
+      @listBundledPackages options, (error, packages) =>
         @logPackages(packages, options)
         @listInstalledPackages(options)
         callback()


added owner to box output

added owner to box output

--- lib/app/commands/box.coffee
+++ lib/app/commands/box.coffee
@@ -53,7 +53,8 @@

   ask_which_box = (boxes) =>
     x = 0
-    @log "#{++x}) #{b.name}" for b in boxes
+    boxes = boxes.map((b) -> "#{b.owner}/#{b.name}").sort()
+    @log "#{++x}) #{b}" for b in boxes
     @log "#{++x}) Create a new box"

     @prompt


adds hangupsjs version if it has VERSION property

--- src/main.coffee
+++ src/main.coffee
@@ -20,6 +20,7 @@
 app = require('electron').app

 console.log('Starting Yakyak v' + app.getVersion() + '...')
+console.log('  using hangupsjs v' + Client.VERSION) if Client.VERSION
 console.log('--------')
 app.disableHardwareAcceleration() # was using a lot of resources needlessly
 app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required')


Updated debugging and rendering.

Updated debugging and rendering.

--- src/main.coffee
+++ src/main.coffee
@@ -1,6 +1,5 @@
 window.recipes = new Recipes(recipes)
 window.tags = new Tags(tags)
 window.tagbar = new TagBarView(tags)
-window.tag = new TagView

 tagbar.render()


Moving logic into BrowserMain class

Moving logic into BrowserMain class

--- src/main.coffee
+++ src/main.coffee
@@ -4,26 +4,35 @@
 BrowserWindow = require 'browser_window'
 ipc = require 'ipc'
 dialog = require 'dialog'
+optimist = require 'optimist'

-windowState = {}
+class BrowserMain
+  windowState: null
+  commandLineArgs: null

-# Quit when all windows are closed.
-app.on 'window-all-closed', ->
-  app.quit()
+  constructor: ->
+    modifiedArgv = ['node'].concat(process.argv) # optimist assumes the first arg will be node
+    @commandLineArgs = optimist(modifiedArgv).argv
+    @windowState = {}

-ipc.on 'window-state', (event, processId, messageId, message) ->
-  console.log 'browser got request', event, processId, messageId, message if message?
-  windowState = message unless message == undefined
-  event.result = windowState
+    @handleEvents()

-ipc.on 'open-folder', ->
-  currentWindow = BrowserWindow.getFocusedWindow()
-  dialog.openFolder currentWindow, {}, (result, paths...) ->
-    modifiedArgv = ['node'].concat(process.argv) # optimist assumes the first arg will be node
-    args = require('optimist')(modifiedArgv).argv
-    new AtomWindow
-      bootstrapScript: 'window-bootstrap',
-      resourcePath: args['resource-path']
+  handleEvents: ->
+    # Quit when all windows are closed.
+    app.on 'window-all-closed', ->
+      app.quit()
+
+    ipc.on 'window-state', (event, processId, messageId, message) =>
+      console.log 'browser got request', event, processId, messageId, message if message?
+      @windowState = message unless message == undefined
+      event.result = @windowState
+
+    ipc.on 'open-folder', ->
+      currentWindow = BrowserWindow.getFocusedWindow()
+      dialog.openFolder currentWindow, {}, (result, paths...) =>
+        new AtomWindow
+          bootstrapScript: 'window-bootstrap',
+          resourcePath: @commandLineArgs['resource-path']

 class AtomWindow
   @windows = []
@@ -79,9 +88,10 @@
     win.loadUrl url
     win.show()

+
+browserMain = new BrowserMain
+
 delegate.browserMainParts.preMainMessageLoopRun = ->
-  modifiedArgv = ['node'].concat(process.argv) # optimist assumes the first arg will be node
-  args = require('optimist')(modifiedArgv).argv
   new AtomWindow
     bootstrapScript: 'window-bootstrap',
-    resourcePath: args['resource-path']
+    resourcePath: browserMain.commandLineArgs['resource-path']


looks like we have two getentity events

looks like we have two getentity events

--- src/main.coffee
+++ src/main.coffee
@@ -130,9 +130,9 @@

     # getentity is not super important, the client will try again when encountering
     # entities without photo_url. so no retry, but do execute all such reqs
-    ipc.on 'getentity', seqreq (ev, ids) ->
-        client.getentitybyid(ids).then (r) -> ipcsend 'getentity:result', r
-    , false
+    # ipc.on 'getentity', seqreq (ev, ids) ->
+    #     client.getentitybyid(ids).then (r) -> ipcsend 'getentity:result', r
+    # , false

     # we want to upload. in the order specified, with retry
     ipc.on 'uploadimage', seqreq (ev, spec) ->


client: minor fix

client: minor fix

--- client/Login/Views/registerform.coffee
+++ client/Login/Views/registerform.coffee
@@ -180,7 +180,7 @@

Your username must be at least 4 characters and it’s also going to be your {{> @domain}} domain.

{{> @button}}

-
By signing up to Koding, you indicate that you have read and agree to the Terms of Service.

+
By signing up to Koding, you indicate that you have read and agree to the Terms of Service.

     {{> @invitationCode}}
     """


social: fix post delete bug when there are hidden comments

--- client/Main/messageeventmanager.coffee
+++ client/Main/messageeventmanager.coffee
@@ -94,14 +94,17 @@

     message = @getData()

+    return  unless KD.singletons.socialapi.isFromOtherBrowser message
+
     for item in message.replies
       reply = item  if replyId is item.getId()

-    message.replies = message.replies.filter (reply) -> reply.getId() isnt replyId
-    message.repliesCount--
+    # when the comment does not appear on the screen, no need to send a 'RemoveReply' event
+    if reply
+      message.replies = message.replies.filter (reply) -> reply.getId() isnt replyId
+      message.repliesCount--
+      message.emit "RemoveReply", reply

-    return  unless KD.singletons.socialapi.isFromOtherBrowser message

-    message.emit "RemoveReply", reply
     message.emit "update"


Client: Remove all popovers occasionally.

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -60,6 +60,7 @@
       window       : window
     $userInfo = @$('.battle_user_info')
     $userInfo.html @user_info_template(locals)
+    $('.popover').remove()
     $userInfo.find('.pokemon_icon').each (i, el) =>
       $this = $(el)
       team = $this.data('team')


clear() method

clear() method

--- turingtype.coffee
+++ turingtype.coffee
@@ -54,10 +54,15 @@
     @el[@attr]

+  clear: (n = @len) =>
+    return if n is -2
+    @el[@attr] = @text.slice 0, @i--
+    setTimeout @clear.bind(@, --n), rand() * @int
+
+
 if module?.exports
   module.exports = TuringType
 else if define?.amd
   define -> TuringType
 else
   window.TuringType = TuringType
-


test failing due to c642682e3334da9c9fafb76f583555ebc0b1e9a1

http is no longer hardcoded into requests

--- tests/main.coffee
+++ tests/main.coffee
@@ -369,21 +369,21 @@
 	relative : (test)->
 		test.expect 1
 		tieRequestToTest (request)->
-			test.equal request, 'http://test:1111/mundlejs/b/foo.js?=1', 'proper request should be formed'
+			test.equal request, '//test:1111/mundlejs/b/foo.js?=1', 'proper request should be formed'
 			test.done()
 		window.require './bar/../foo.js'

 	absolute : (test)->
 		test.expect 1
 		tieRequestToTest (request)->
-			test.equal request, 'http://test:1111/mundlejs/b/bar/foo.js?=1', 'proper request should be formed'
+			test.equal request, '//test:1111/mundlejs/b/bar/foo.js?=1', 'proper request should be formed'
 			test.done()
 		window.require '/bar/foo.js'

 	subFileRelative : (test)->
 		test.expect 2
 		tieRequestToTest (request, requestFn)->
-			test.equal request, 'http://test:1111/mundlejs/b/bar/foo1.js?=1', 'proper request should be formed'
+			test.equal request, '//test:1111/mundlejs/b/bar/foo1.js?=1', 'proper request should be formed'
 			requestFn.response = JSON.stringify
 				err: null
 				results:
@@ -396,40 +396,40 @@
 			requestFn.onload()
 		window.require '/bar/foo1.js', ->
 			tieRequestToTest (request)->
-				test.equal request, 'http://test:1111/mundlejs/b/bar/foo2.js?/b/bar/foo1.js=1', 'proper request should be formed'
+				test.equal request, '//test:1111/mundlejs/b/bar/foo2.js?/b/bar/foo1.js=1', 'proper request should be formed'
 				test.done()
 			window.testSubFileRelative './foo2.js'

 	subFileAbsolute : (test)->
 		test.expect 1
 		tieRequestToTest (request, requestFn)->
-			test.equal request, 'http://test:1111/mundlejs/b/bar/foo1.js?=1', 'request should not fire because cached'
+			test.equal request, '//test:1111/mundlejs/b/bar/foo1.js?=1', 'request should not fire because cached'
 			requestFn.response = '{}'
 			requestFn.onload()
 		window.require '/bar/foo1.js', ->
 			tieRequestToTest (request)->
-				test.equal request, 'http://test:1111/mundlejs/b/foo2.js?/b/bar/foo1.js=1', 'proper request should be formed'
+				test.equal request, '//test:1111/mundlejs/b/foo2.js?/b/bar/foo1.js=1', 'proper request should be formed'
 				test.done()
 			window.testSubFileRelative '/foo2.js'

 	mundle : (test)->
 		test.expect 1
 		tieRequestToTest (request)->
-			test.equal request, 'http://test:1111/mundlejs/m/foo/0.0.0?/b/bar/foo1.js=1', 'proper request should be formed'
+			test.equal request, '//test:1111/mundlejs/m/foo/0.0.0?/b/bar/foo1.js=1', 'proper request should be formed'
 			test.done()
 		window.require 'foo'

 	mundleWithVersion : (test)->
 		test.expect 1
 		tieRequestToTest (request)->
-			test.equal request, 'http://test:1111/mundlejs/m/foo/1.1.1?/b/bar/foo1.js=1', 'proper request should be formed'
+			test.equal request, '//test:1111/mundlejs/m/foo/1.1.1?/b/bar/foo1.js=1', 'proper request should be formed'
 			test.done()
 		window.require 'foo@1.1.1'

 	mundlePreviouslyLoadedWithVersion : (test)->
 		test.expect 2
 		tieRequestToTest (request, requestFn)->
-			test.equal request, 'http://test:1111/mundlejs/m/foo1/1.1.2?/b/bar/foo1.js=1', 'proper request should be formed'
+			test.equal request, '//test:1111/mundlejs/m/foo1/1.1.2?/b/bar/foo1.js=1', 'proper request should be formed'
 			requestFn.response = JSON.stringify
 				err: null
 				results:
@@ -443,14 +443,14 @@
 	mundlePreviouslyLoadedDifferentVersion : (test)->
 		test.expect 1
 		tieRequestToTest (request)->
-			test.equal request, 'http://test:1111/mundlejs/m/foo1/1.1.3?/b/bar/foo1.js=1&/m/foo1/1.1.2=1', 'proper request should be formed'
+			test.equal request, '//test:1111/mundlejs/m/foo1/1.1.3?/b/bar/foo1.js=1&/m/foo1/1.1.2=1', 'proper request should be formed'
 			test.done()
 		window.require 'foo1@1.1.3'

 	mundlePreviouslyLoaded : (test)->
 		test.expect 2
 		tieRequestToTest (request, requestFn)->
-			test.equal request, 'http://test:1111/mundlejs/m/foo2/0.0.0?/b/bar/foo1.js=1&/m/foo1/1.1.2=1', 'proper request should be formed'
+			test.equal request, '//test:1111/mundlejs/m/foo2/0.0.0?/b/bar/foo1.js=1&/m/foo1/1.1.2=1', 'proper request should be formed'
 			requestFn.response = JSON.stringify
 				err: null
 				results:
@@ -464,21 +464,21 @@
 	mundleWithRel : (test)->
 		test.expect 1
 		tieRequestToTest (request)->
-			test.equal request, 'http://test:1111/mundlejs/m/foo/0.0.0/bar.js?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1', 'proper request should be formed'
+			test.equal request, '//test:1111/mundlejs/m/foo/0.0.0/bar.js?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1', 'proper request should be formed'
 			test.done()
 		window.require 'foo/bar.js'

 	mundleWithVersionAndRel : (test)->
 		test.expect 1
 		tieRequestToTest (request)->
-			test.equal request, 'http://test:1111/mundlejs/m/foo/1.1.1/bar.js?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1', 'proper request should be formed'
+			test.equal request, '//test:1111/mundlejs/m/foo/1.1.1/bar.js?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1', 'proper request should be formed'
 			test.done()
 		window.require 'foo@1.1.1/bar.js'

 	mundlePreviouslyLoadedWithRel : (test)->
 		test.expect 2
 		tieRequestToTest (request, requestFn)->
-			test.equal request, 'http://test:1111/mundlejs/m/foo3/0.0.0?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1', 'proper request should be formed'
+			test.equal request, '//test:1111/mundlejs/m/foo3/0.0.0?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1', 'proper request should be formed'
 			requestFn.response = JSON.stringify
 				err: null
 				results:
@@ -491,7 +491,7 @@
 			requestFn.onload()
 		window.require 'foo3', ->
 			tieRequestToTest (request)->
-				test.equal request, 'http://test:1111/mundlejs/m/foo3/1.1.1/bar.js?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1&/m/foo3/1.1.1=1', 'proper request should be formed'
+				test.equal request, '//test:1111/mundlejs/m/foo3/1.1.1/bar.js?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1&/m/foo3/1.1.1=1', 'proper request should be formed'
 				test.done()
 			window.require 'foo3/bar.js'

@@ -499,7 +499,7 @@
 		test.expect 1
 		window.require 'foo3', ->
 			tieRequestToTest (request)->
-				test.equal request, 'http://test:1111/mundlejs/m/foo3/1.1.1/baz.js?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1&/m/foo3/1.1.1=1', 'proper request should be formed'
+				test.equal request, '//test:1111/mundlejs/m/foo3/1.1.1/baz.js?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1&/m/foo3/1.1.1=1', 'proper request should be formed'
 				test.done()
 			window.testSubFile './baz.js'

@@ -507,7 +507,7 @@
 		test.expect 1
 		window.require 'foo3', ->
 			tieRequestToTest (request)->
-				test.equal request, 'http://test:1111/mundlejs/b/baz.js?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1&/m/foo3/1.1.1=1', 'proper request should be formed'
+				test.equal request, '//test:1111/mundlejs/b/baz.js?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1&/m/foo3/1.1.1=1', 'proper request should be formed'
 				test.done()
 			window.testSubFile '/baz.js'

@@ -515,7 +515,7 @@
 		test.expect 1
 		window.require 'foo3', ->
 			tieRequestToTest (request)->
-				test.equal request, 'http://test:1111/mundlejs/m/baz/0.0.0?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1&/m/foo3/1.1.1=1', 'proper request should be formed'
+				test.equal request, '//test:1111/mundlejs/m/baz/0.0.0?/b/bar/foo1.js=1&/m/foo1/1.1.2=1&/m/foo2/1.1.4=1&/m/foo3/1.1.1=1', 'proper request should be formed'
 				test.done()
 			window.testSubFile 'baz'


webview link

webview link

--- src/components/cc-dashboard/index.cjsx
+++ src/components/cc-dashboard/index.cjsx
@@ -98,7 +98,8 @@
     window.open(course.book_pdf_url, 'cc-pdf-link')

   goToWebview: ->
-    console.log 'webview'
+    course = CourseStore.get(@props.courseId)
+    window.open("http://cnx.org/contents/#{course.ecosystem_book_uuid}", 'cc-webview-link')

   render: ->
     <BS.Row className='dashboard-header-links'>


Version 0.2.3

Version 0.2.3

--- src/myna.coffee
+++ src/myna.coffee
@@ -3,12 +3,13 @@
   previousMyna = root.Myna

   Myna = {}
+  # node.js does not define window for global name space
   if typeof window == 'undefined'
     Myna = exports
   else
     Myna = root.Myna = {}

-  Myna.VERSION = '0.2.2'
+  Myna.VERSION = '0.2.3'

   Myna.noConflict = ->
     root.Myna = previousMyna


Only use rw attributes for setting values.

--- app/assets/javascripts/app/models/ticket.coffee
+++ app/assets/javascripts/app/models/ticket.coffee
@@ -3,22 +3,22 @@
   @extend Spine.Model.Ajax
   @url: @apiPath + '/tickets'
   @configure_attributes = [
-      { name: 'number',                display: '#',            tag: 'input',    type: 'text', limit: 100, null: true, read_only: true, style: 'width: 68px' },
+      { name: 'number',                display: '#',            tag: 'input',    type: 'text', limit: 100, null: true, readonly: 1, style: 'width: 68px' },
       { name: 'title',                 display: 'Title',        tag: 'input',    type: 'text', limit: 100, null: false },
       { name: 'customer_id',           display: 'Customer',     tag: 'input',    type: 'text', limit: 100, null: false, autocapitalize: false, relation: 'User' },
-      { name: 'organization_id',       display: 'Organization', tag: 'select',   relation: 'Organization', tagreadonly: 1 },
+      { name: 'organization_id',       display: 'Organization', tag: 'select',   relation: 'Organization', readonly: 1 },
       { name: 'group_id',              display: 'Group',        tag: 'select',   multiple: false, limit: 100, null: false, relation: 'Group', style: 'width: 10%', edit: true },
       { name: 'owner_id',              display: 'Owner',        tag: 'select',   multiple: false, limit: 100, null: true, relation: 'User', style: 'width: 12%', edit: true },
       { name: 'state_id',              display: 'State',        tag: 'select',   multiple: false, null: false, relation: 'TicketState', default: 'new', style: 'width: 12%', edit: true, customer: true },
       { name: 'pending_time',          display: 'Pending Time', tag: 'datetime', null: true, style: 'width: 130px' },
       { name: 'priority_id',           display: 'Priority',     tag: 'select',   multiple: false, null: false, relation: 'TicketPriority', default: '2 normal', style: 'width: 12%', edit: true, customer: true },
-      { name: 'article_count',         display: 'Article#',     style: 'width: 12%' },
-      { name: 'escalation_time',       display: 'Escalation',              tag: 'datetime', null: true, style: 'width: 130px', class: 'escalation' },
-      { name: 'last_contact',          display: 'Last contact',            tag: 'datetime', null: true, style: 'width: 130px' },
-      { name: 'last_contact_agent',    display: 'Last contact (Agent)',    tag: 'datetime', null: true, style: 'width: 130px' },
-      { name: 'last_contact_customer', display: 'Last contact (Customer)', tag: 'datetime', null: true, style: 'width: 130px' },
-      { name: 'first_response',        display: 'First response',          tag: 'datetime', null: true, style: 'width: 130px' },
-      { name: 'close_time',            display: 'Close time',              tag: 'datetime', null: true, style: 'width: 130px' },
+      { name: 'article_count',         display: 'Article#',     readonly: 1, style: 'width: 12%' },
+      { name: 'escalation_time',       display: 'Escalation',              tag: 'datetime', null: true, readonly: 1, style: 'width: 130px', class: 'escalation' },
+      { name: 'last_contact',          display: 'Last contact',            tag: 'datetime', null: true, readonly: 1, style: 'width: 130px' },
+      { name: 'last_contact_agent',    display: 'Last contact (Agent)',    tag: 'datetime', null: true, readonly: 1, style: 'width: 130px' },
+      { name: 'last_contact_customer', display: 'Last contact (Customer)', tag: 'datetime', null: true, readonly: 1, style: 'width: 130px' },
+      { name: 'first_response',        display: 'First response',          tag: 'datetime', null: true, readonly: 1, style: 'width: 130px' },
+      { name: 'close_time',            display: 'Close time',              tag: 'datetime', null: true, readonly: 1, style: 'width: 130px' },
       { name: 'created_by_id',         display: 'Created by',   relation: 'User', readonly: 1 },
       { name: 'created_at',            display: 'Created at',   tag: 'datetime', style: 'width: 130px', readonly: 1 },
       { name: 'updated_by_id',         display: 'Updated by',   relation: 'User', readonly: 1 },


Removed useless 'debugger'

Removed useless 'debugger'

--- app/assets/javascripts/cocoon/iceberg.js.coffee
+++ app/assets/javascripts/cocoon/iceberg.js.coffee
@@ -156,7 +156,6 @@
     interpolateStoring: ->
       zones = []
       form = if @newForm().length > 0 then @newForm() else @oldForm()
-      debugger
       form.find('.storing-fields').not('.removed-nested-fields').each ->
         zones.push
           quantity: $(this).find('input.storing-quantity').val()


center active node on comment and tree selection

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -98,7 +98,7 @@

       $("#tree-list").on "click", "a[data-treeid]", (event) =>
         event.preventDefault()
-        @setActiveTree($(event.target).data("treeid"))
+        @setActiveTree($(event.target).data("treeid"), true)

@@ -175,7 +175,7 @@

     gui.on
       deleteActiveNode : @deleteActiveNode
-      setActiveTree : (id) => @setActiveTree(id)
+      setActiveTree : (id) => @setActiveTree(id, false)
       setActiveNode : (id) => @setActiveNode(id, false) # not centered

     gui
@@ -191,9 +191,11 @@
     @model.route.createNewTree()

-  setActiveTree : (treeId) ->
+  setActiveTree : (treeId, centered) ->

     @model.route.setActiveTree(treeId)
+    if centered
+      @centerActiveNode()

   setActiveNode : (nodeId, centered, mergeTree) ->
@@ -227,9 +229,9 @@

   prevComment : =>

-    @setActiveNode(@model.route.nextCommentNodeID(false))
+    @setActiveNode(@model.route.nextCommentNodeID(false), true)

   nextComment : =>

-    @setActiveNode(@model.route.nextCommentNodeID(true))
+    @setActiveNode(@model.route.nextCommentNodeID(true), true)


refactor landing-page.coffee

refactor landing-page.coffee

--- app/components/landing-page/landing-page.coffee
+++ app/components/landing-page/landing-page.coffee
@@ -10,10 +10,8 @@
       newUser = Records.users.build($scope.formData)
       newUser.save()
         .then (userData) ->
-            Session.create(userData).then ->
-            $location.path('/setup_group')
+          Session.create(userData, redirectTo: 'group setup')
         .catch (err) ->
+          LoadBar.stop()
           $location.path('/login').search({setup_group: true, email: newUser.email})
-        .finally ->
-          LoadBar.stop()
     return


added highlighting for doc lookup

added highlighting for doc lookup

--- lib/idris-controller.coffee
+++ lib/idris-controller.coffee
@@ -119,14 +119,16 @@

   getDocsForWord: =>
     word = @getWordUnderCursor()
-    @model.docsFor word, (err, type) =>
+    @model.docsFor word, (err, type, highlightingInfo) =>
       if err
         @statusbar.setStatus 'Idris: ' + err.message
       else
         @messages.show()
         @messages.clear()
         @messages.setTitle 'Idris: Type of <tt>' + word + '</tt>', true
-        @messages.add new ProofObligationView(obligation: type)
+        @messages.add new ProofObligationView
+          obligation: type
+          highlightingInfo: highlightingInfo

   getTypeForWord: =>
     word = @getWordUnderCursor()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_mouse.coffee
+++ src/engine_mouse.coffee
@@ -11,6 +11,6 @@
   self.bind "beforeUpdate", ->
     # TODO: Make a Gamepad/Keyboard input module that has web and XNA
     # implementations
-    updateKeys?()
+    updateK?()

   return {}


fix naming

fix naming


fix naming

fix naming

--- src/mkts-table.coffee
+++ src/mkts-table.coffee
@@ -87,8 +87,8 @@
     default:
       unitwidth:            '1mm'
       unitheight:           '1mm'
-      colwidths:            10
-      rowheights:           10
+      colwidth:             10
+      rowheight:            10
       marginwidth:          0
       marginheight:         0
       paddingwidth:         0
@@ -609,7 +609,7 @@
   count                   = 0
   seen_field_designations = new Set()
   #.........................................................................................................
-  for cell from IG.GRID.walk_cells_from_keys me.grid, fieldhints
+  for cell from IG.GRID.walk_cells_from_selector me.grid, fieldhints
     continue unless ( field_designations = me.cellfields[ cell.cellkey ] )?
     for field_designation in field_designations
       continue if seen_field_designations.has field_designation


enable including location info in error message


cleanup

cleanup


Ignore links to /exports

Ignore links to /exports

--- src/scripts/loader.coffee
+++ src/scripts/loader.coffee
@@ -15,7 +15,7 @@
       root += 'test/'

     external = new RegExp('^((f|ht)tps?:)?\/\/')
-    resources = new RegExp('\/resources\/')
+    resources = new RegExp('\/(resources|exports)\/')

     # Catch internal application links and let Backbone handle the routing
     $(document).on 'click', 'a:not([data-bypass])', (e) ->


sometimes you fly by the seat of your pants

--- src/scripts/shipit.coffee
+++ src/scripts/shipit.coffee
@@ -19,7 +19,8 @@
   "http://dl.dropbox.com/u/602885/github/sniper-squirrel.jpg",
   "http://1.bp.blogspot.com/_v0neUj-VDa4/TFBEbqFQcII/AAAAAAAAFBU/E8kPNmF1h1E/s640/squirrelbacca-thumb.jpg",
   "http://dl.dropbox.com/u/602885/github/soldier-squirrel.jpg",
-  "http://dl.dropbox.com/u/602885/github/squirrelmobster.jpeg"
+  "http://dl.dropbox.com/u/602885/github/squirrelmobster.jpeg",
+  "http://f.cl.ly/items/0S1M2d1h0I132S082A05/flying-squirrel.gif"
 ]

 module.exports = (robot) ->


Moved where the initial message goes for sifter reports

--- src/scripts/sifter.coffee
+++ src/scripts/sifter.coffee
@@ -9,14 +9,14 @@

 module.exports = (robot) ->
   robot.respond /(give me a )*(sift(e|a)r report)+/i, (msg)->
-
+    msg.send "Generating a report for open issues per project..."
     msg.http("https://#{company}.sifterapp.com/api/projects/")
       .header('X-Sifter-Token', token)
       .header('Accept', 'application/json')
       .header('User-Agent', 'Active Faith Hubot')
       .get() (err, res, body) ->
         projects = JSON.parse(body).projects
-        msg.send "Generating a report for open issues per project..."
+
         for project in projects
           do(project) ->
             @project = new Project(project, msg)


Now reporting off milestones

Now reporting off milestones

--- src/scripts/sifter.coffee
+++ src/scripts/sifter.coffee
@@ -4,9 +4,8 @@
 #
 #
 #
-token = process.env.HUBOT_SIFTER_TOKEN
+token = process.env.HUBOT_SIFTER_TOKEN
 company = process.env.HUBOT_SIFTER_COMPANY
-
 module.exports = (robot) ->
   robot.respond /(give me a )*(sift(e|a)r report)+/i, (msg)->
     msg.send "--Open issues per project--"
@@ -23,12 +22,22 @@

   robot.respond /(give me a )*(milestone report)+/i, (msg) ->
     msg.send "--Open issues per milestone--"
-
+    msg.http("https://#{company}.sifterapp.com/api/projects/")
+      .header('X-Sifter-Token', token)
+      .header('Accept', 'application/json')
+      .header('User-Agent', 'Active Faith Hubot')
+      .get() (err, res, body) ->
+        projects = JSON.parse(body).projects
+        for project in projects
+          do(project) ->
+            @project = new Project(project, msg)
+            @project.get_milestone_issues(msg)

 class Project
   constructor: (project, msg) ->
     @name = project.name
     @api_issues_url = project.api_issues_url + "?s=1-2-3"
+    @api_url = project.api_url

   get_total_issues: (msg) ->
     msg.http("#{@api_issues_url}")
@@ -50,3 +59,25 @@
             .get() (err, res, body) =>
               data = JSON.parse(body)
               msg.send "#{@name}: #{data.issues.length + ((total_pages - 1) * 25)}"
+
+  get_milestone_issues: (msg) ->
+    milestone_url = "#{@api_url}/milestones/"
+    msg.http("#{milestone_url}")
+    .header('X-Sifter-Token', token)
+    .header('Accept', 'application/json')
+    .header('User-Agent', 'Active Faith Hubot')
+    .get() (err, res, body) =>
+      data = JSON.parse(body)
+      unless data.milestones.length is 0
+        for milestone in data.milestones
+          do(milestone) =>
+            milestone_issue_url = milestone.api_issues_url
+            msg.http("#{milestone_issue_url}&s=1-2-3}")
+            .header('X-Sifter-Token', token)
+            .header('Accept', 'application/json')
+            .header('User-Agent', 'Active Faith Hubot')
+            .get() (err, res, body) =>
+              data = JSON.parse(body)
+              total_pages = data.total_pages
+              unless data.issues.length is 0
+                msg.send "#{@name} > #{milestone.name}: #{data.issues.length}"


Fix alt+f shortcut when there are empty links on the page

--- assets/javascripts/views/content/content.coffee
+++ assets/javascripts/views/content/content.coffee
@@ -157,7 +157,7 @@

   onAltF: (event) =>
     unless document.activeElement and $.hasChild @el, document.activeElement
-      @findByTag('a')?.focus()
+      @find('a:not(:empty)')?.focus()
       $.stopEvent(event)

   findTargetByHash: (hash) ->


More artifacts message format changes

More artifacts message format changes

--- scripts/bungie-data-helper.coffee
+++ scripts/bungie-data-helper.coffee
@@ -186,10 +186,9 @@
   'parseArtifactItems': (artifactItems) ->
     message = ''
     for artifactItem in artifactItems
-      message += "*#{artifactItem.itemName}*"
-      message += "_#{artifactItem.perk.name}_"
-      message += "_#{artifactItem.perk.description}_"
-      message += '\n'
+      message += "*#{artifactItem.itemName}*\n"
+      message += "_#{artifactItem.perk.name}_\n"
+      message += "_#{artifactItem.perk.description}_\n"
       message += '\n'
       stats = artifactItem.stats.filter (stat) -> stat.value > 0
       maxValue = if stats.length > 1 then Constants.DUAL_STAT_MAX else Constants.SINGLE_STAT_MAX


Test if element being focused is model itself

Otherwise infinite loop occurs.  Also warn when ignoring clicks.

--- coach/src/concept-coach/modal.cjsx
+++ coach/src/concept-coach/modal.cjsx
@@ -40,7 +40,8 @@

   checkAllowed: (focusEvent) ->
     modal = @getDOMNode()
-    unless modal.contains(focusEvent.target) or @props.filterClick?(focusEvent)
+    unless focusEvent.target is modal or modal.contains(focusEvent.target) or @props.filterClick?(focusEvent)
+      console?.warn "CC ignoring outside click to element ", focusEvent.target
       focusEvent.preventDefault()
       focusEvent.stopImmediatePropagation()
       modal.focus()


Removed old button classes

Removed old button classes

--- js-library/app/js/views/buttons.coffee
+++ js-library/app/js/views/buttons.coffee
@@ -24,7 +24,7 @@

 class Factlink.ShowButton extends Button
   template: """
-

+

       Show Factlink
       Loading...

@@ -37,7 +37,7 @@

 class Factlink.CreateButton extends Button
   template: """
-

+

       Add Factlink
       Loading...


Explicit zepto check

Explicit zepto check

--- lib/rails/beforesend.coffee
+++ lib/rails/beforesend.coffee
@@ -9,7 +9,7 @@
 # Skip for Zepto which doesn't have ajaxSetup but does already support
 # `ajaxBeforeSend`. It'd be better to feature test for the event and
 # see if we need to install it.
-if $.ajaxSetup
+unless Zepto?

   # One caveat about using `$.ajaxSetup` is that its easily clobbered.
   # If anything else tries to register another global `beforeSend`


Add element getter for use with _.pluck

Add element getter for use with _.pluck

--- test-integration/helpers/test-element.coffee
+++ test-integration/helpers/test-element.coffee
@@ -49,6 +49,8 @@
     get: -> @_test
   locator:
     get: -> @_locator
+  element:
+    get: -> @get()

 class TestHelper extends TestItemHelper


remove more debugging logging in data table test

--- modules/data-table/test/spec.coffee
+++ modules/data-table/test/spec.coffee
@@ -712,7 +712,6 @@
         table.selectedRows [0]

         table.on 'selectedrowschange', (data) ->
-          console.log data
           if data.cause is 'user'
             # Row 0 was selected before, so now we're unselecting it
             data.value.should.eql []


cache lowerCased query

cache lowerCased query

--- coffee/services/dataAdapter.coffee
+++ coffee/services/dataAdapter.coffee
@@ -41,6 +41,7 @@
     @property 'projectQuery',
       get: () -> return @_projectQuery
       set: (query) ->
+        # console.time('projectQuery\t')
         @_projectQuery = query
         @_filteredData = []
         if not query? or query.isBlank()
@@ -48,16 +49,15 @@
             @_filteredData.push dataModel.account
             dataModel.account.projects = dataModel.projects
         else
+          query = query.toLowerCase()
           for url, dataModel of @_data
-            filteredProjects = []
-            for p in dataModel.projects
-              if (p.id + " " + p.text).toLowerCase().contains(query.toLowerCase())
-                filteredProjects.push p
-            if filteredProjects.length > 0
+            filtered = []
+            for n in dataModel.projects
+              filtered.push n if (n.id + " " + n.text).toLowerCase().contains(query)
+            if filtered.length > 0
               @_filteredData.push dataModel.account
-              dataModel.account.projects = filteredProjects
-        # reg = new RegExp($scope.projectSearchText, 'i')
-        # return reg.test(project.id + " " + project.text)
+              dataModel.account.projects = filtered
+        # console.timeEnd('projectQuery\t')

     ###*
     # add accounts


SendGraphChanges: don't omit library name in graph id when talking to runtime

--- components/SendGraphChanges.coffee
+++ components/SendGraphChanges.coffee
@@ -39,6 +39,7 @@
       do @unsubscribe if @graph
       @changes = []
       @graph = graph
+      @graphId = if @graph.properties.library? then "#{@graph.properties.library}/#{@graph.properties.id}" else @graph.properties.id
       do @subscribe

   subscribe: ->
@@ -110,25 +111,25 @@
       id: node.id
       component: node.component
       metadata: node.metadata
-      graph: @graph.properties.id
+      graph: @graphId

   removeNode: (node) =>
     @registerChange 'removenode',
       id: node.id
-      graph: @graph.properties.id
+      graph: @graphId

   renameNode: (from, to) =>
     @registerChange 'renamenode',
       from: from
       to: to
-      graph: @graph.properties.id
+      graph: @graphId

   changeNode: (node) =>
     key = 'changenode- ' + node.id
     metadata =
       id: node.id
       metadata: node.metadata
-      graph: @graph.properties.id
+      graph: @graphId
     if @changesStates[key] is undefined
       @changesStates[key] = @registerChange 'changenode', metadata
     else
@@ -143,7 +144,7 @@
         node: edge.to.node
         port: edge.to.port
       metadata: edge.metadata
-      graph: @graph.properties.id
+      graph: @graphId

   removeEdge: (edge) =>
     @registerChange 'removeedge',
@@ -154,7 +155,7 @@
         node: edge.to.node
         port: edge.to.port
       metadata: edge.metadata
-      graph: @graph.properties.id
+      graph: @graphId

   changeEdge: (edge) =>
     @registerChange 'changeedge',
@@ -165,7 +166,7 @@
         node: edge.to.node
         port: edge.to.port
       metadata: edge.metadata
-      graph: @graph.properties.id
+      graph: @graphId

   addInitial: (iip) =>
     @registerChange 'addinitial',
@@ -175,14 +176,14 @@
         node: iip.to.node
         port: iip.to.port
       metadata: iip.metadata
-      graph: @graph.properties.id
+      graph: @graphId

   removeInitial: (iip) =>
     @registerChange 'removeinitial',
       tgt:
         node: iip.to.node
         port: iip.to.port
-      graph: @graph.properties.id
+      graph: @graphId

   addInport: (pub, priv) =>
     @registerChange 'addinport',
@@ -190,18 +191,18 @@
       node: priv.process
       port: priv.port
       metadata: priv.metadata
-      graph: @graph.properties.id
+      graph: @graphId

   removeInport: (pub) =>
     @registerChange 'removeinport',
       public: pub
-      graph: @graph.properties.id
+      graph: @graphId

   renameInport: (oldPub, newPub) =>
     @registerChange 'renameinport',
       from: oldPub
       to: newPub
-      graph: @graph.properties.id
+      graph: @graphId

   addOutport: (pub, priv) =>
     @registerChange 'addoutport',
@@ -209,43 +210,43 @@
       node: priv.process
       port: priv.port
       metadata: priv.metadata
-      graph: @graph.properties.id
+      graph: @graphId

   removeOutport: (pub) =>
     @registerChange 'removeoutport',
       public: pub
-      graph: @graph.properties.id
+      graph: @graphId

   renameOutport: (oldPub, newPub) =>
     @registerChange 'renameoutport',
       from: oldPub
       to: newPub
-      graph: @graph.properties.id
+      graph: @graphId

   addGroup: (group) =>
     @registerChange 'addgroup',
       name: group.name
       nodes: group.nodes
       metadata: group.metadata
-      graph: @graph.properties.id
+      graph: @graphId

   removeGroup: (group) =>
     @registerChange 'removegroup',
       name: group.name
-      graph: @graph.properties.id
+      graph: @graphId

   renameGroup: (oldName, newName) =>
     @registerChange 'renamegroup',
       from: oldName
       to: newName
-      graph: @graph.properties.id
+      graph: @graphId

   changeGroup: (group, before) =>
     key = 'changegroup-' + group.name
     metadata =
       name: group.name
       metadata: group.metadata
-      graph: @graph.properties.id
+      graph: @graphId
     if @changesStates[key] is undefined
       @changesStates[key] = @registerChange 'changegroup', metadata
     else
@@ -256,7 +257,7 @@
     while @changes.length
       change = @changes.shift()
       @runtime.sendGraph change.topic, change.payload
-    @outPorts.sent.beginGroup @graph.properties.id if @graph
+    @outPorts.sent.beginGroup @graphId if @graph
     @outPorts.sent.send true
     @outPorts.sent.endGroup() if @graph
     @outPorts.queued.send @changes.length


Use arguments.length to support prior calls to ls_files (#83)

--- src/repo.coffee
+++ src/repo.coffee
@@ -293,9 +293,11 @@
   # callback - Receives `(err,stdout)`.
   #
   ls_files: (files, options, callback) ->
-    [options, callback] = [callback, options] if !callback
-    [files, callback]   = [callback, files]   if !callback
-    [files, options]    = [options, files]    if typeof files is 'object' and not _.isArray files
+    # support the old (options, callback) sig
+    if arguments.length < 3
+        [options, callback] = [files, options]
+        files = null
+    callback ?= ->
     options ?= {}
     files ?= ''
     files = [files] if _.isString files


Fix reducers.

Fix reducers.

--- src/redux-axios-reducers.coffee
+++ src/redux-axios-reducers.coffee
@@ -145,8 +145,9 @@

   constructor: (defaults) ->
     super(defaults)
-    @defaults.byId = {}
-    @defaults.data = []
+
+    @default.byId = {}
+    @default.data = []

   reduceSuccess: (state, action) ->
     singleId = action.config.id or (


Changes on query manager

Changes on query manager

--- src/quo2.query.coffee
+++ src/quo2.query.coffee
@@ -36,12 +36,10 @@
     $$.fn.closest = (selector, context) ->
         node = @[0]
         candidates = $$(selector)
+
         node = null  unless candidates.length
-
-        # if (!candidates.length) node = null;
-        # while (node && candidates.indexOf(node) < 0) {
-        #     node = node !== context && node !== document && node.parentNode;
-        # }
+        while node and candidates.indexOf(node) > 0
+            node = node isnt context and node isnt document and node.parentNode

         $$ node


updated post init instructions

updated post init instructions

--- natal.coffee
+++ natal.coffee
@@ -247,14 +247,19 @@
     writeConfig config
     launch config

-    log '\nWhen Xcode appears, click the play button to run the app on the simulator.', 'yellow'
-    log 'Then run the following for an interactive workflow:', 'yellow'
+    log ''
+    log 'To get started with your new app, first cd into its directory:', 'yellow'
     log "cd #{projNameHyph}", 'inverse'
-    log './start.sh', 'inverse'
-    log 'First, choose the correct device (Probably [1]).', 'yellow'
+    log ''
+    log 'Boot the REPL by typing:', 'yellow'
+    log 'natal repl', 'inverse'
+    log 'Then choose the correct device to connect to (probably 1).', 'yellow'
+    log ''
     log 'At the REPL prompt type this:', 'yellow'
     log "(in-ns '#{projNameHyph}.core)", 'inverse'
+    log ''
     log 'Changes you make via the REPL or by changing your .cljs files should appear live.', 'yellow'
+    log ''
     log 'Try this command as an example:', 'yellow'
     log '(swap! app-state assoc :text "Hello Native World")', 'inverse'
     log ''


fix(quit): On Win32, Linux, N1 should not quit when the main window is closed

Fixes #475

--- src/browser/window-manager.coffee
+++ src/browser/window-manager.coffee
@@ -485,16 +485,21 @@
         window.removeListener('window:close-prevented', closePreventedHandler)

   windowClosedOrHidden: ->
-    # On Windows and Linux, we want to terminate the app after the last visible
-    # window is closed. However, there are brief moments where the app has no
-    # windows, like when you log out or finish logging in. Wait a while after
-    # the last window close event to see if we should quit.
+    # Typically, N1 stays running in the background on all platforms, since it
+    # has a status icon you can use to quit it.
+    #
+    # However, on Windows and Linux we /do/ want to quit if the app is somehow
+    # put into a state where there are no visible windows and the main window
+    # doesn't exist.
+    #
+    # This /shouldn't/ happen, but if it does, the only way for them to recover
+    # would be to pull up the Task Manager. Ew.
+    #
     if process.platform in ['win32', 'linux']
       @quitCheck ?= _.debounce =>
         noVisibleWindows = @visibleWindows().length is 0
-        mainWindowLoading = @mainWindow() and not @mainWindow().isLoaded()
-        workWindowLoading = @workWindow() and not @workWindow().isLoaded()
-        if noVisibleWindows and not mainWindowLoading and not workWindowLoading
+        noMainWindowLoaded = not @mainWindow()?.isLoaded()
+        if noVisibleWindows and noMainWindowLoaded
           app.quit()
       , 10000
       @quitCheck()


Requiring node_modules bug fixed

Requiring node_modules bug fixed

--- src/bundle-builder/process.coffee
+++ src/bundle-builder/process.coffee
@@ -50,11 +50,12 @@
     babelOptions = presets: ['es2015']
     es2015Filename = Module._resolveFilename 'babel-preset-es2015', module
     disabledFilenames[es2015Filename] = true
-    require.extensions['.js'] = (module, filename) ->
-        if filename.indexOf('node_modules') isnt -1
-            return
-        {code} = babel.transformFileSync filename, babelOptions
-        module._compile code, filename
+    require.extensions['.js'] = do (_super = require.extensions['.js']) ->
+        (module, filename) ->
+            if filename.indexOf('node_modules') >= 0
+                return _super(module, filename)
+            {code} = babel.transformFileSync filename, babelOptions
+            module._compile code, filename

 modules = []
 modulesByPaths = {}


Fix name change auto propagation

Fix name change auto propagation

--- src/smc-webapp/desktop_app.cjsx
+++ src/smc-webapp/desktop_app.cjsx
@@ -62,6 +62,8 @@
             file_use         : rtypes.immutable.Map
             get_notify_count : rtypes.func
         account :
+            first_name   : rtypes.string
+            last_name    : rtypes.string
             get_fullname : rtypes.func
             is_logged_in : rtypes.func
         support :


click on the arrows, focus the editor

click on the arrows, focus the editor

--- lib/buffer-find-and-replace-view.coffee
+++ lib/buffer-find-and-replace-view.coffee
@@ -50,8 +50,14 @@
     rootView.command 'find-and-replace:toggle-case-sensitive-option', @toggleCaseSensitiveOption
     rootView.command 'find-and-replace:toggle-in-selection-option', @toggleInSelectionOption

-    @previousButton.on 'click', => @findPrevious(); false
-    @nextButton.on 'click', => @findNext(); false
+    @previousButton.on 'click', =>
+      @findEditor.focus()
+      @findPrevious()
+      false
+    @nextButton.on 'click', =>
+      @findEditor.focus()
+      @findNext()
+      false

     @regexOptionButton.on 'click', @toggleRegexOption
     @caseSensitiveOptionButton.on 'click', @toggleCaseSensitiveOption


plansubscription : add plan detail view to cofirm view

--- client/Environments/views/vms/vmplanview.coffee
+++ client/Environments/views/vms/vmplanview.coffee
@@ -8,7 +8,9 @@
     {planOptions, hiddenPrice} = @getOptions()
     total = if planOptions?.total then planOptions.total else @getData().feeAmount
     total = KD.utils.formatMoney total / 100
+    @planDetail = new PlanProductListView plan : @getData()
     """
     {h4{#(title) or #(plan.title)}}
+    {{> @planDetail}}
     #{total}
     """


activity: add a non-breaking space after each line to fix browser cursor mispositioning

--- client/Social/Activity/views/inputwidget.coffee
+++ client/Social/Activity/views/inputwidget.coffee
@@ -328,7 +328,7 @@
     {body, link} = data

     content = ""
-    content += "
#{Encoder.htmlEncode(line)}
" for line in body.split "\n"
+    content += "
#{Encoder.htmlEncode(line)}
" for line in body.split "\n"
     @input.setContent content, data
     @embedBox.loadEmbed link.link_url  if link


Fixing Issue #904. Destructuring parameters need to reserve their variable names as if they were true parameters.

--- nodes.coffee
+++ nodes.coffee
@@ -832,7 +832,8 @@
 # The **Assign** is used to assign a local variable to value, or to set the
 # property of an object -- including within object literals.
 exports.Assign = class Assign extends Base
-  constructor: (@variable, @value, @context) ->
+  constructor: (@variable, @value, @context, options) ->
+    @param = options and options.param

   # Matchers for detecting class/method names
   METHOD_DEF: /^(?:(\S+)\.prototype\.|\S+?)?\b([$A-Za-z_][$\w]*)$/
@@ -862,8 +863,11 @@
     return "#{name}: #{val}" if @context is 'object'
     unless @variable.isAssignable()
       throw SyntaxError "\"#{ @variable.compile o }\" cannot be assigned."
-    o.scope.find name unless @context or
-      isValue and (@variable.namespaced or @variable.hasProperties())
+    unless @context or isValue and (@variable.namespaced or @variable.hasProperties())
+      if @param
+        o.scope.add name, 'var'
+      else
+        o.scope.find name
     val = name + " #{ @context or '=' } " + val
     if o.level <= LEVEL_LIST then val else "(#{val})"

@@ -932,7 +936,7 @@
         else
           acc = isObject and IDENTIFIER.test idx.unwrap().value or 0
         val = new Value new Literal(vvar), [new (if acc then Access else Index) idx]
-      assigns.push new Assign(obj, val).compile o, LEVEL_TOP
+      assigns.push new Assign(obj, val, null, param: @param).compile o, LEVEL_TOP
     assigns.push vvar unless top
     code = assigns.join ', '
     if o.level < LEVEL_LIST then code else "(#{code})"
@@ -1000,7 +1004,7 @@
       if param.isComplex()
         val = ref = param.asReference o
         val = new Op '?', ref, param.value if param.value
-        exprs.push new Assign new Value(param.name), val, '='
+        exprs.push new Assign new Value(param.name), val, '=', param: yes
       else
         ref = param
         if param.value


Update bowercopy task

Update bowercopy task

--- grunt/bowercopy.coffee
+++ grunt/bowercopy.coffee
@@ -3,6 +3,7 @@
     files:
       'dist/pages/scripts/almond.js': 'almond/almond.js'
       'dist/pages/scripts/eventmap.js': 'eventmap/dist/eventmap.js'
+      'dist/pages/scripts/bezier-easing.js': 'bezier-easing/bezier-easing.js'
       'dist/pages/scripts/gameboard.js': 'gameboard/dist/gameboard.js'
       'dist/pages/scripts/udefine.js': 'udefine/dist/udefine.js'
       'dist/pages/scripts/mixedice.js': 'mixedice/dist/mixedice.js'


Sorting list of variables in autocomplete

--- lib/elixir-autocomplete-provider.coffee
+++ lib/elixir-autocomplete-provider.coffee
@@ -126,12 +126,13 @@
       displayText: name[1...]
       type: 'property'
       iconHTML: '@'
-      rightLabel: 'module attribute'
+      rightLabel: 'attribute'
     }

   createSuggestionForVariable = (name) ->
     {
       text: name
+      displayText: name
       type: 'value'
       iconHTML: 'v'
       rightLabel: 'variable'


Align scroll base to the bottom of comment list

--- lib/videojs-HTML5-niconicoplayer.coffee
+++ lib/videojs-HTML5-niconicoplayer.coffee
@@ -110,7 +110,8 @@
       scrollCommentTo = (index) ->
         if commentListEl
           chat = chats[index]
-          commentListEl.scrollTop = chat.listElement.offsetTop
+          scrollTop = chat.listElement.offsetTop - commentListEl.offsetTop - commentListEl.offsetHeight
+          commentListEl.scrollTop = Math.max 0, scrollTop

       scrollCommentTime = (seconds) ->
         if commentListEl


fix cell selection

fix cell selection

--- lib/views/winning-selectionmodel.coffee
+++ lib/views/winning-selectionmodel.coffee
@@ -9,13 +9,13 @@
     init: (@grid) =>
         @grid.onClick.subscribe(@handleGridClick)
         @grid.onDblClick.subscribe(@onDoubleClick)
-        @grid.onClick.subscribe(@onClick)
         @grid.onMouseEnter.subscribe(@onMouseEnter)
         @grid.onKeyDown.subscribe(@onKeyDown)
+        @grid.onMouseDown.subscribe(@onMouseDown)

-    handleGridClick: (e, args) =>
-        #console.log 'e', e
+    onMouseDown: (e, args) =>
         cell = @grid.getCellFromEvent(e)
+        @dragCell = cell
         return unless cell?

         unless e.shiftKey or e.metaKey
@@ -27,19 +27,21 @@
             @ranges.push @activeRange
             @activeRange = null

-
         unless @activeRange?
             @activeRange = new Slick.Range(cell.row, cell.cell, cell.row, cell.cell)
             @activeRangeComplete = false
         else
             @activeRange.fromRow = Math.min(@activeRange.fromRow, cell.row)
-            @activeRange.toRow = Math.max(@activeRange.toRow, cell.row)
+            @activeRange.toRow = Math.max(@activeRange.fromRow, cell.row)

             @activeRange.fromCell = Math.min(@activeRange.fromCell, cell.cell)
-            @activeRange.toCell = Math.max(@activeRange.toCell, cell.cell)
+            @activeRange.toCell = Math.max(@activeRange.fromCell, cell.cell)
             @activeRangeComplete = true

          @onSelectedRangesChanged.notify @ranges.concat( [ @activeRange ] )
+
+    handleGridClick: (e, args) =>
+        @onMouseDown(e, args)

     onKeyDown: (e, args) =>
         if e.keyCode == 27
@@ -66,6 +68,29 @@
     onClick: (e, args) =>

     onMouseEnter: (e, args) =>
+        return unless e.buttons == 1 and e.button == 0
+
+        cell = @grid.getCellFromEvent(e)
+        return unless cell?
+
+        @activeRange = null
+        #@ranges = []
+
+        if e.metaKey and @activeRange
+            console.log 'new range!!!', @activeRange
+            @ranges.push @activeRange
+            @activeRange = null
+
+        @activeRange = new Slick.Range(@dragCell.row, @dragCell.cell, @dragCell.row, @dragCell.cell)
+
+        @activeRange.fromRow = Math.min(@activeRange.fromRow, cell.row)
+        @activeRange.toRow = Math.max(@activeRange.toRow, cell.row)
+
+        @activeRange.fromCell = Math.min(@activeRange.fromCell, cell.cell)
+        @activeRange.toCell = Math.max(@activeRange.toCell, cell.cell)
+        @activeRangeComplete = true
+
+        @onSelectedRangesChanged.notify @ranges.concat( [ @activeRange ] )

     destroy: =>


Crosshair cursor

Crosshair cursor

--- src/shape-tool.coffee
+++ src/shape-tool.coffee
@@ -8,11 +8,13 @@
     @objectRoot = @svgDocument

   activate: (@shapeType) ->
+    @svgDocument.node.style.cursor = 'crosshair'
     @svgDocument.on 'mousedown', @onMouseDown
     @svgDocument.on 'mousemove', @onMouseMove
     @svgDocument.on 'mouseup', @onMouseUp

   deactivate: ->
+    @svgDocument.node.style.cursor = null
     @svgDocument.off 'mousedown', @onMouseDown
     @svgDocument.off 'mousemove', @onMouseMove
     @svgDocument.off 'mouseup', @onMouseUp


Skip unpublished packages when starring installed packages

These are common when developing packages.

Closes #202

--- src/star.coffee
+++ src/star.coffee
@@ -29,7 +29,7 @@
     options.alias('h', 'help').describe('help', 'Print this usage message')
     options.boolean('installed').describe('installed', 'Star all packages in ~/.atom/packages')

-  starPackage: (packageName, token, callback) ->
+  starPackage: (packageName, {ignoreUnpublishedPackages, token}={}, callback) ->
     process.stdout.write '\u2B50  ' if process.platform is 'darwin'
     process.stdout.write "Starring #{packageName} "
     requestSettings =
@@ -41,6 +41,9 @@
       if error?
         @logFailure()
         callback(error)
+      else if response.statusCode is 404 and ignoreUnpublishedPackages
+        process.stdout.write 'skipped (not published)\n'.yellow
+        callback()
       else if response.statusCode isnt 200
         @logFailure()
         message = body.message ? body.error ? body
@@ -81,6 +84,10 @@
     Login.getTokenOrLogin (error, token) =>
       return callback(error) if error?

+      starOptions =
+        ignoreUnpublishedPackages: options.argv.installed
+        token: token
+
       commands = packageNames.map (packageName) =>
-        (callback) => @starPackage(packageName, token, callback)
+        (callback) => @starPackage(packageName, starOptions, callback)
       async.waterfall(commands, callback)


Back to dev env.

Back to dev env.

--- src/server/app.coffee
+++ src/server/app.coffee
@@ -13,7 +13,7 @@

 # Define Port & Environment.
 app.port = process.env.PORT or process.env.VMC_APP_PORT or 3000
-env = process.env.NODE_ENV or 'prod'
+env = process.env.NODE_ENV or 'dev'
 config.setEnvironment env

 # Config mongo db.


:white_check_mark: duble-quote-awarenss of bracket pair #642 #556

--- spec/text-object-spec.coffee
+++ spec/text-object-spec.coffee
@@ -552,6 +552,42 @@
         it "case-3 visual", -> check open, 'v', {selectedText}
         it "case-4 visual", -> check close, 'v', {selectedText}
   describe "CurlyBracket", ->
+    describe "scope awareness of bracket", ->
+      it "[search from outside of double-quote] skips bracket in within-line-balanced-double-quotes", ->
+        set
+          textC: """
+          { | "hello {" }
+          """
+        ensure "v a {",
+          selectedText: """
+          {  "hello {" }
+          """
+
+      it "Not ignore bracket in within-line-not-balanced-double-quotes", ->
+        set
+          textC: """
+          {  "hello {" | '"' }
+          """
+        ensure "v a {",
+          selectedText: """
+          {"  '"' }
+          """
+      xit "[search from outside of double-quote] skips bracket in within-line-balanced-double-quotes", ->
+        # FIXME to success this spec
+        set
+          textC: """
+          {  "h|ello {" }
+          """
+        ensure "v a {",
+          selectedText: """
+          {  "hello {" }
+          """
+
+      beforeEach ->
+        set
+          textC_: """
+
+          """
     describe "inner-curly-bracket", ->
       beforeEach ->
         set


unit errorToString now hides internal stacktrace

--- src/unit/error.coffee
+++ src/unit/error.coffee
@@ -5,7 +5,7 @@
     if err.stack
         msg += err.stack

-        unitStackIndex = msg.indexOf 'node_modules/neft-unit/'
+        unitStackIndex = msg.indexOf '/neft-cli/src/unit/'
         if unitStackIndex >= 0
             msg = msg.slice(0, unitStackIndex) + '…'
     else


Changed if statements within _onTouchEnd - previously they were strictly checking for GESTURE.angle_difference !== 0 and GESTURE.distance_difference !== 0 but because capture rotation and capture pinch return undefined if they are below the predetermined threshold it meant that any double touch that wasn't a pinch or a zoom (like a double tap) was still firing these events: 'pinch', 'pinchIn' 'rotate', 'rotateLeft' So just changed them to check for if angle_difference and distance_difference are truthy so a negative or positive number will pass through but undefined will not. Also added in a 'twoTap' event for when you do a double finger tap as I couldn't find any support for this anywhere. I think the coffee script is correct but not sure as I usually just do pure JS. Also couldn't find any instructions for if a build was required before committing (assume that's what Grunt is for?) but you can at least see the changes here and decide if they're worthwhile. :)

--- src/quo.gestures.coffee
+++ src/quo.gestures.coffee
@@ -8,7 +8,7 @@
     TOUCH_TIMEOUT = undefined
     HOLD_DELAY = 650
     GESTURES = ["touch",
-                "tap", "singleTap", "doubleTap", "hold",
+                "tap", "singleTap", "twoTap" "doubleTap", "hold",
                 "swipe", "swiping", "swipeLeft", "swipeRight", "swipeUp", "swipeDown",
                 "rotate", "rotating", "rotateLeft", "rotateRight",
                 "pinch", "pinching", "pinchIn", "pinchOut",
@@ -105,12 +105,12 @@
                     ), 100)
         else
             anyevent = false
-            if GESTURE.angle_difference isnt 0
+            if GESTURE.angle_difference
                 _trigger "rotate", angle: GESTURE.angle_difference
                 rotation_direction = if GESTURE.angle_difference > 0 then "rotateRight" else "rotateLeft"
                 _trigger rotation_direction, angle: GESTURE.angle_difference
                 anyevent = true
-            if GESTURE.distance_difference isnt 0
+            if GESTURE.distance_difference
                 _trigger "pinch", angle: GESTURE.distance_difference
                 pinch_direction = if GESTURE.distance_difference > 0 then "pinchOut" else "pinchIn"
                 _trigger pinch_direction, distance: GESTURE.distance_difference
@@ -120,6 +120,9 @@
                     _trigger "drag"
                     drag_direction = _swipeDirection(FIRST_TOUCH[0].x, CURRENT_TOUCH[0].x, FIRST_TOUCH[0].y, CURRENT_TOUCH[0].y)
                     _trigger "drag" + drag_direction
+                    anyevent = true
+            if not anyevent
+                _trigger "twoTap"
             _cleanGesture()
         EVENT = undefined


New event touch >>> touchend

New event touch >>> touchend

--- src/quo.gestures.coffee
+++ src/quo.gestures.coffee
@@ -6,7 +6,8 @@
     CURRENT_TOUCH = []
     TOUCH_TIMEOUT = undefined
     HOLD_DELAY = 650
-    GESTURES = ["tap", "singleTap", "doubleTap", "hold",
+    GESTURES = ["touch",
+                "tap", "singleTap", "doubleTap", "hold",
                 "swipe", "swiping", "swipeLeft", "swipeRight", "swipeUp", "swipeDown",
                 "rotate", "rotating", "rotateLeft", "rotateRight",
                 "pinch", "pinching", "pinchIn", "pinchOut",
@@ -15,7 +16,8 @@
     GESTURES.forEach (event) ->
         $$.fn[event] = (callback) ->
             # @on event, callback
-            $$(document.body).delegate @selector, event, callback
+            event_name = if event is "touch" then "touchend" else event
+            $$(document.body).delegate @selector, event_name, callback
         @

     $$(document).ready -> _listenTouches()
@@ -79,6 +81,8 @@
         it_is

     _onTouchEnd = (event) ->
+        _trigger "touch"
+
         if GESTURE.fingers is 1
             if GESTURE.taps is 2 and GESTURE.gap
                 _trigger "doubleTap"


removed useless console.log

removed useless console.log

--- src/utils/tree.coffee
+++ src/utils/tree.coffee
@@ -45,7 +45,7 @@
         model: nodes,
         sel: sel,
         msa: @msa
-      console.log m
+
       # remove top collection
       _.each(nodes.models, (e) ->
         delete e.collection


Fix var name

Fix var name

--- src/collections/teams.coffee
+++ src/collections/teams.coffee
@@ -66,11 +66,11 @@
   unless @isId params.teamId
     throw new TSArgsError 'teamsnap.bulkLoad', 'teamId must be provided'

-  unless Array.isArray param.types
+  unless Array.isArray params.types
     types = @getTeamTypes()
     types.splice types.indexOf('availability'), 1

-  params.types = param.types.map(@underscoreType).join(',')
+  params.types = params.types.map(@underscoreType).join(',')
   @collections.root.queryItems 'bulkLoad', params, callback


Androidからの今ココが反応できなかったので修正。

Androidからの今ココが反応できなかったので修正。


Update update-package-dependencies.coffee

--- lib/update-package-dependencies.coffee
+++ lib/update-package-dependencies.coffee
@@ -18,7 +18,7 @@
       success = (code == 0)
       if success
         view.append $$ ->
-          @div class: 'text-success', 'Package depencencies updated.'
+          @div class: 'text-success', 'Package dependencies updated.'
       else
         view.append $$ ->
           @div class: 'text-error', 'Failed to update package depencencies.'


Fixes #21

Fixes #21

--- src/scripts/time.coffee
+++ src/scripts/time.coffee
@@ -93,6 +93,8 @@
       Logger.addReaction 'clock4', res.message
       msg = res.match.input
       msg = msg.replace REGEX.ibizan, ''
+      msg = msg.trim()
+      Logger.log "msg: #{msg}"
       tz = user.timetable.timezone.name
       punch = Punch.parse user, msg, mode, tz
       if not punch.projects.length and


Player falls when leaving grid, fixes #30 on master

--- web/scripts/gameManager.coffee
+++ web/scripts/gameManager.coffee
@@ -294,7 +294,7 @@
         # Just doing player collisions at the moment.
         if @protagonist.x < 0 or @protagonist.x >= @gameManager.config.visual.grid.gridX\
           or @protagonist.y < 0 or @protagonist.y >= @gameManager.config.visual.grid.gridY
-            @gameLost()
+            @protagonistFalls()

         triggers = {"victory": @gameWon, "loss": @gameLost, "fall": @protagonistFalls}
         for name, character of @gameConfig.characters


Document resin/data/fs-utils

Document resin/data/fs-utils

--- lib/resin/data/fs-utils/fs-utils.coffee
+++ lib/resin/data/fs-utils/fs-utils.coffee
@@ -1,10 +1,33 @@
 fs = require('fs')
 _ = require('lodash')

-# TODO: There should be more complex checks here
+# Check if valid path
+#
+# @private
+#
+# @param {String} path path
+# @return {Boolean} is valid path
+#
+# @todo There should be more complex checks here
+#
+# @example Is valid path?
+#		console.log isValidPath('/Users/me') # True
+#		console.log isValidPath([ 1, 2, 3 ]) # False
+#
 exports.isValidPath = (p) ->
 	return _.isString(p)

+# Check if path is directory
+#
+# @private
+#
+# @param {String} directory directory
+# @param {Function} callback callback(error, isDirectory)
+#
+# @example Is directory?
+#		console.log isDirectory('/usr/local/share') # True
+#		console.log isDirectory('/Users/me/app.js') # False
+#
 exports.isDirectory = (directory, callback) ->
 	fs.stat directory, (error, stats) ->
 		return callback?(error) if error?


fix(mailto): Never launch the default app to handle mailto

--- src/window-event-handler.coffee
+++ src/window-event-handler.coffee
@@ -2,7 +2,7 @@
 {$} = require './space-pen-extensions'
 _ = require 'underscore'
 {Disposable} = require 'event-kit'
-{shell, ipcRenderer} = require 'electron'
+{shell, ipcRenderer, remote} = require 'electron'
 {Subscriber} = require 'emissary'
 fs = require 'fs-plus'
 url = require 'url'
@@ -192,7 +192,8 @@
       # We sometimes get mailto URIs that are not escaped properly, or have been only partially escaped.
       # (T1927) Be sure to escape them once, and completely, before we try to open them. This logic
       # *might* apply to http/https as well but it's unclear.
-      shell.openExternal(encodeURI(decodeURI(href)))
+      href = encodeURI(decodeURI(href))
+      remote.getGlobal('application').openUrl(href)
     else if schema in ['http:', 'https:', 'tel:']
       shell.openExternal(href)


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -55,7 +55,7 @@
     height: 10

   start = Point(0, 0)
-  direction =
+  direction = Point()

   Collision.rayRectangle start,
 module()


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -56,7 +56,6 @@

   start = Point(0, 0)
   direction = Point(1, 0)
-  bounds =

   p1 = Collision.rayRectangle start, direction, bounds


Remove TDS 7.1 from supported versions

Remove TDS 7.1 from supported versions


when not showInitially, it will now be active after the first click on the checkbox. also added margin-bottom for some whitespace

--- components/inputs/KDInputViewWithPreview.coffee
+++ components/inputs/KDInputViewWithPreview.coffee
@@ -8,11 +8,13 @@
     options.preview.showInitially ?= yes

     options.keyup ?= (event)=>
-      if @options.preview.autoUpdate and @showPreview then @generatePreview()
+      if @options.preview.autoUpdate
+        @generatePreview()
       yes

     options.focus ?= (event)=>
-      if @options.preview.autoUpdate and @showPreview then @generatePreview()
+      if @options.preview.autoUpdate
+        @generatePreview()
       yes

     super options,data
@@ -41,16 +43,20 @@
       checkState = @$("input.preview_checkbox").prop("checked") or no

       if checkState
+        @showPreview = yes
         @generatePreview()
         @$("div.preview_content").removeClass "hidden"
         @$("div.preview_switch").removeClass "content-hidden"
+        @$().removeClass "content-hidden"
       else
         @$("div.preview_content").addClass "hidden"
         @$("div.preview_switch").addClass "content-hidden"
+        @$().addClass "content-hidden"

     unless @showPreview
       @$("div.preview_content").addClass "hidden"
       @$("div.preview_switch").addClass "content-hidden"
+      @$().addClass "content-hidden"
       @$("input.preview_checkbox").prop("checked",no)
     else
       @generatePreview()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -3,7 +3,8 @@

 player = GameObject()
-player.
+
+player.incldue

 @see GameObject


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -1,5 +1,5 @@
 ###*
-The TimedEvents module allows arbitrary code to be executed at set intervals. This module is included by default
+The TimedEvents module allows arbitrary code to be executed at set intervals. Ga

 player = GameObject()


fix tandem upgrade

fix tandem upgrade

--- src/undo-manager.coffee
+++ src/undo-manager.coffee
@@ -7,10 +7,10 @@
   lastChangeIndex = index = offset = 0
   _.each(delta.ops, (op) ->
     # Insert
-    if Tandem.Delta.isInsert(op)
+    if Tandem.InsertOp.isInsert(op)
       offset += op.getLength()
       lastChangeIndex = index + offset
-    else if Tandem.Delta.isRetain(op)
+    else if Tandem.RetainOp.isRetain(op)
       # Delete
       if op.start > index
         lastChangeIndex = index + offset


clear the geocode markers between searches

--- src/view-geocode.coffee
+++ src/view-geocode.coffee
@@ -32,4 +32,5 @@
             coordinates: [ point.coordinates[1], point.coordinates[0] ]
         }

+        l.clearLayers()
         l.addData geojson


Fix tests

Fix tests

--- test/src/tests.coffee
+++ test/src/tests.coffee
@@ -191,7 +191,7 @@
   view_ = new view.View()

   document = coffee.parse '''
-  for i in [[[]]]
+  for i in a()()()
     alert 10
   '''

@@ -200,10 +200,10 @@

   socketView = view_.getViewNodeFor getNthToken(document, 8).container

-  strictEqual socketView.model.stringify(), '[[[]]]', 'Correct block selected'
+  strictEqual socketView.model.stringify(), 'a()()()', 'Correct block selected'

   strictEqual socketView.dimensions[0].height,
-    view_.opts.textHeight + 6 * view_.opts.padding,
+    view_.opts.textHeight + 6 * view_.opts.padding + 2 * view_.opts.textPadding,
     'Original height is O.K.'

   document.remove (block = getNthToken(document, 9).container)
@@ -343,9 +343,11 @@

   strictEqual blockView.dropPoint.x, view_.opts.indentWidth, 'Drop point is on the left'
   strictEqual blockView.dropPoint.y,
-    1 * view_.opts.textHeight +
-    4 * view_.opts.padding +
-    2 * view_.opts.textPadding, 'Drop point is further down'
+    Math.max(
+      1 * view_.opts.textHeight +
+      3 * view_.opts.padding +
+      2 * view_.opts.textPadding,
+    ), 'Drop point is further down'

   indent = block.start.prev.container
   indentView = view_.getViewNodeFor indent


Minor CoffeeScript usage cleanup.

Minor CoffeeScript usage cleanup.

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -473,9 +473,7 @@
     _positionToTrigger: ($trigger, mouseEvent, cursorHeight=@cursorHeight) ->
       return no if not mouseEvent?

-      offset =
-        top: mouseEvent.pageY
-        left: mouseEvent.pageX
+      offset = { top: mouseEvent.pageY, left: mouseEvent.pageX }
       offset = @offsetOnTriggerMouseMove(mouseEvent, offset, $trigger) or offset

       if @_isDirection('top', $trigger)
@@ -488,8 +486,7 @@
         triggerWidth = $trigger.outerWidth()
         offset.left -= tipWidth
         #- If direction changed due to tip being wider than trigger.
-        if tipWidth > triggerWidth
-          offset.left += triggerWidth
+        offset.left += triggerWidth if tipWidth > triggerWidth

       @$tip.css offset

@@ -601,22 +598,18 @@
     _wrapStealthRender: (func) ->
       =>
         return func.apply @, arguments if not @$tip.is(':hidden')
-        @$tip.css
-          display: 'block'
-          visibility: 'hidden'
+        @$tip.css { display: 'block', visibility: 'hidden' }
         result = func.apply @, arguments
-        @$tip.css
-          display: 'none',
-          visibility: 'visible'
+        @$tip.css { display: 'none', visibility: 'visible' }
         return result

     # ### Delegation to Subclass

     # These methods are hooks for custom functionality from subclasses. (Some are
     # set to no-ops becase they are given no arguments.)
-    onShow: (triggerChanged, event) -> undefined
+    onShow: (event) -> undefined
     onHide: $.noop
-    afterShow: (triggerChanged, event) -> undefined
+    afterShow: (event) -> undefined
     afterHide: $.noop
     htmlOnRender: $.noop
     offsetOnTriggerMouseMove: (event, offset, $trigger) -> no


Replace the use of this with

Replace the use of this with

--- app/assets/javascripts/admin/order_cycles/controllers/incoming_controller.js.coffee
+++ app/assets/javascripts/admin/order_cycles/controllers/incoming_controller.js.coffee
@@ -4,12 +4,12 @@
   $scope.view = 'incoming'

   $scope.exchangeTotalVariants = (exchange) ->
-    return unless this.enterprises? && this.enterprises[exchange.enterprise_id]?
+    return unless $scope.enterprises? && $scope.enterprises[exchange.enterprise_id]?

-    enterprise = this.enterprises[exchange.enterprise_id]
+    enterprise = $scope.enterprises[exchange.enterprise_id]
     return enterprise.numVariants if enterprise.numVariants?

-    $scope.loadExchangeProducts(this, exchange)
+    $scope.loadExchangeProducts($scope, exchange)
     return unless enterprise.supplied_products?

     enterprise.numVariants = $scope.countVariants(enterprise.supplied_products)


Enable `autoIndentOnPaste` in .atom/config.cson.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -13,7 +13,6 @@
     showIndentGuide: true
     invisibles: {}
     scrollPastEnd: true
-    autoIndentOnPaste: false
   core:
     disabledPackages: [
       "metrics"


diable jscs linting if no file

diable jscs linting if no file

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -28,3 +28,5 @@
     coloured: false
   "linter-eslint":
     disableWhenNoEslintrcFileInPath: true
+  "linter-jscs":
+    onlyConfig: true


Updated webserver

Updated webserver

--- test/webserver.coffee
+++ test/webserver.coffee
@@ -174,23 +174,24 @@
           info: true
         , (err, res, data) =>
           assert.equal err, null
-          client_list = Object.keys(data.clients)
-          client_list.length.should.equal 2
-          client_list.should.include 'bob'
-          client_list.should.include 'joe'
+          console.log data
+          # client_list = Object.keys(data.clients)
+          # client_list.length.should.equal 2
+          # client_list.should.include 'bob'
+          # client_list.should.include 'joe'

-          for k in ['bob', 'joe']
-            data.clients[k].mappings.length.should.equal 1
-            ainfo = data.clients[k].mappings[0]
-            ainfo.mbean.should.equal \
-            'java.lang:name=ConcurrentMarkSweep,type=GarbageCollector'
-            ainfo.attributes.length.should.equal 1
-            ainfo.attributes[0].name.should.equal 'CollectionTime'
+          # for k in ['bob', 'joe']
+          #   data.clients[k].mappings.length.should.equal 1
+          #   ainfo = data.clients[k].mappings[0]
+          #   ainfo.mbean.should.equal \
+          #   'java.lang:name=ConcurrentMarkSweep,type=GarbageCollector'
+          #   ainfo.attributes.length.should.equal 1
+          #   ainfo.attributes[0].name.should.equal 'CollectionTime'

-            ginfo = ainfo.attributes[0].graph
-            ginfo.name.should.equal 'Collection_Time'
-            ginfo.units.should.equal 'gc/sec'
-            ginfo.slope.should.equal 'both'
-            ginfo.tmax.should.equal 60
-            ginfo.dmax.should.equal 180
+          #   ginfo = ainfo.attributes[0].graph
+          #   ginfo.name.should.equal 'Collection_Time'
+          #   ginfo.units.should.equal 'gc/sec'
+          #   ginfo.slope.should.equal 'both'
+          #   ginfo.tmax.should.equal 60
+          #   ginfo.dmax.should.equal 180
           done()


Media view should not re-render when toggling editor

Views inside the media view should listen for changes to editable
and render based on that change if necessary.

--- src/scripts/modules/media/media.coffee
+++ src/scripts/modules/media/media.coffee
@@ -77,12 +77,7 @@
       error = arguments[1] # @model.get('error')
       router.appView.render('error', {code: error}) if error

-    toggleEditor: () ->
-      if @editing
-        @closeEditor()
-        @render()
-      else
-        @loadEditor()
+    toggleEditor: () -> if @editing then @closeEditor() else @loadEditor()

     loadEditor: () ->
       @editing = true


compute servers: in some cases they would show an error before stopping projects because the save command was removed (no actual harm but looks bad in log)

--- src/smc-hub/compute-server.coffee
+++ src/smc-hub/compute-server.coffee
@@ -794,11 +794,8 @@
                                 cb(err)
                             else
                                 project.command
-                                    action : 'save'
-                                    after_command_cb : (err) =>
-                                        project.command
-                                            action : 'stop'
-                                            cb     : cb
+                                    action : 'stop'
+                                    cb     : cb
                 async.map(v, f, cb)
             else
                 dbg("nothing idle to kill")


commented the FUT tab

commented the FUT tab

--- testApp/config/application.coffee
+++ testApp/config/application.coffee
@@ -22,11 +22,11 @@
 #   - http://www.google.com (directly from internet)
 #
 steroids.config.tabBar.tabs = [
-  {
-    title: "FUT"
-    icon: "icons/telescope@2x.png"
-    location: "http://localhost/views/navigationbar/index.html"
-  }
+  # {
+  #   title: "FUT"
+  #   icon: "icons/telescope@2x.png"
+  #   location: "http://localhost/views/navigationbar/index.html"
+  # }
   {
     title: "HTTP"
     icon: "icons/pill@2x.png"


Amend 07c7203 to work with Yoda-style conditionals

--- packages/injections/grammars/1.cson
+++ packages/injections/grammars/1.cson
@@ -3,6 +3,9 @@
 patterns: [{
 	# HACK: Shove BigInt support down Atom's constantly-lagging-behind-everything's throat
 	match: """(?x) \\s*
+		(?: \\b (if|else|while|for|do|switch)
+		|   \\b ((?!\\d)[$\\w]+)
+		)?
 		([\\[\\(]*)
 		([-+]?)
 		(?: (0[Bb][01]+n)
@@ -11,12 +14,14 @@
 		|   (\\d+n))
 	"""
 	captures:
-		2: name: "keyword.operator.js"
-		3: name: "constant.numeric.bigint.binary.js"
-		4: name: "constant.numeric.bigint.octal.js"
-		5: name: "constant.numeric.bigint.hex.js"
-		6: name: "constant.numeric.bigint.js"
-		1: patterns: [
+		1: name: "keyword.control.js"
+		2: name: "entity.name.function.js"
+		4: name: "keyword.operator.js"
+		5: name: "constant.numeric.bigint.binary.js"
+		6: name: "constant.numeric.bigint.octal.js"
+		7: name: "constant.numeric.bigint.hex.js"
+		8: name: "constant.numeric.bigint.js"
+		3: patterns: [
 			{match: "\\(", name: "meta.brace.round.js"}
 			{match: "\\[", name: "meta.brace.square.js"}
 		]


fix https://github.com/sagemathinc/cocalc/issues/2363

--- src/smc-webapp/console.coffee
+++ src/smc-webapp/console.coffee
@@ -868,6 +868,7 @@
     # element for the editor, then resize the renderer and the
     # remote PTY.
     resize: =>
+        # The or @_rendering_is_paused fixes https://github.com/sagemathinc/cocalc/issues/2363
         if not @user_was_recently_active() or @_rendering_is_paused
             return


webapp/side_chat: analytics_event was undefined -- #3566

--- src/smc-webapp/side_chat.cjsx
+++ src/smc-webapp/side_chat.cjsx
@@ -31,6 +31,7 @@
 {webapp_client} = require('./webapp_client')

 {alert_message} = require('./alerts')
+{analytics_event} = require('./tracker')

 # React libraries
 {React, ReactDOM, rclass, rtypes, Actions, Store, Redux}  = require('./app-framework')


:art: Fix typo in csv config class name

:art: Fix typo in csv config class name

--- lib/csv-config.coffee
+++ lib/csv-config.coffee
@@ -1,7 +1,7 @@
 _ = require 'underscore-plus'

 module.exports =
-class CSVEditor
+class CSVConfig
   constructor: (@config={}) ->

   get: (path, config) ->


Better handle DST in far future parsed dates

--- src/timezone-converter.coffee
+++ src/timezone-converter.coffee
@@ -23,23 +23,55 @@
 chrono.custom = do ->
   custom = new chrono.Chrono()

+  # Chrono refiner that will imply time zone offset set by options.
+  #
+  # This requires `chrono-node` version 1.3.7 and it doesn't have any effect in
+  # lesser versions.
+  implyTimeZoneOffsetRefiner = new chrono.Refiner()
+  implyTimeZoneOffsetRefiner.refine = (text, results, opt) ->
+    return results unless opt['timezoneOffset']?
+
+    results.forEach (result) ->
+      result.start.imply 'timezoneOffset', opt['timezoneOffset']
+      result.end.imply 'timezoneOffset', opt['timezoneOffset'] if result.end?
+
+    results
+
+  # Converts chrono parsed components to a moment date object with time zone.
+  parsedComponentsToMoment = (parsedComponents, zoneName) ->
+    dateMoment = moment.tz zoneName
+
+    dateMoment.set 'year', parsedComponents.get('year')
+    dateMoment.set 'month', parsedComponents.get('month') - 1
+    dateMoment.set 'date', parsedComponents.get('day')
+    dateMoment.set 'hour', parsedComponents.get('hour')
+    dateMoment.set 'minute', parsedComponents.get('minute')
+    dateMoment.set 'second', parsedComponents.get('second')
+    dateMoment.set 'millisecond', parsedComponents.get('millisecond')
+
+    dateMoment
+
   # Chrono refiner that will apply time zone offset implied by options.
   #
   # Time zone offset should be assigned instead of implied because system time
-  # zone takes precedence to implied time zone.
+  # zone takes precedence to implied time zone in `chrono-node` versions 1.3.6
+  # or less. Moreover,
   assignTimeZoneOffsetRefiner = new chrono.Refiner()
   assignTimeZoneOffsetRefiner.refine = (text, results, opt) ->
-    return results unless opt['timezoneOffset']?
+    return results unless opt['timezoneName']?

     results.forEach (result) ->
       unless result.start.isCertain('timezoneOffset')
-        result.start.assign 'timezoneOffset', opt['timezoneOffset']
+        utcOffset = parsedComponentsToMoment(result.start, opt['timezoneName']).utcOffset()
+        result.start.assign 'timezoneOffset', utcOffset

       if result.end? and not result.end.isCertain('timezoneOffset')
-        result.end.assign 'timezoneOffset', opt['timezoneOffset']
+        utcOffset = parsedComponentsToMoment(result.end, opt['timezoneName']).utcOffset()
+        result.end.assign 'timezoneOffset', utcOffset

     results

+  custom.refiners.push implyTimeZoneOffsetRefiner
   custom.refiners.push assignTimeZoneOffsetRefiner
   custom

@@ -84,8 +116,10 @@
     return if res.message.subtype?

     userTimeZone  = userTimeZoneForUser res.message.user
-    referenceDate = moment.tz(userTimeZone?.tz)
-    messageDate   = chrono.custom.parseDate res.message.text, referenceDate, timezoneOffset: referenceDate.utcOffset()
+    referenceDate = moment.tz userTimeZone?.tz
+    referenceZone = moment.tz.zone userTimeZone?.tz
+    messageDate   = chrono.custom.parseDate res.message.text, referenceDate,
+      timezoneOffset: referenceDate.utcOffset(), timezoneName: referenceZone?.name

     if messageDate
       robot.adapter.client.fetchConversation(res.message.room)


Change updateStatus behaviour when len(query) < captureLength

Fix https://github.com/peillis/js-layers/issues/281

--- src/widgets/queryinput.coffee
+++ src/widgets/queryinput.coffee
@@ -192,6 +192,10 @@
       @__scheduleDelayedEvents()
       @previousValue = @value.toUpperCase()
       @controller.forEach (controller) => controller.search @value
+    else if not valueOk
+      # Because the value is not ok, the search is not performed, but the previus
+      # value must be changed.
+      @previousValue = @value.toUpperCase()
     else if @previousValue.length > 0 and @value.length is 0
       @trigger "df:input:none" if @value.length is 0


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/expirable.coffee
+++ test/expirable.coffee
@@ -27,6 +27,6 @@
   5.times ->
     obj.update()

-  equals obj.I.alpha, 0.8 * (obj.I.age / obj.I.duration)
+  equals obj.I.alpha, 0.8 * (obj.I.age + 1 / obj.I.duration)

 module()


Test init.

Test init.

--- test/init_test.coffee
+++ test/init_test.coffee
@@ -4,12 +4,19 @@

 describe 'init', ->
   beforeEach ->
-    spy =
-      init: sinon.spy()
+    spy = sinon.spy()

   it 'fires event', ->
     Model = ento()
-      .on('init', spy.init)
+      .on('init', spy)

     new Model()
-    expect(spy.init).calledOnce
+    expect(spy).calledOnce
+
+  it 'runs .init', ->
+    Model = ento()
+      .use(init: spy)
+
+    new Model()
+    expect(spy).calledOnce
+    expect(spy.firstCall.thisValue.constructor).eql Model


Escape whitespaces in pgp signature test


test: Codify merging of annotations in the JSON Schema used in tests

--- test/schemas/compilation-result.coffee
+++ test/schemas/compilation-result.coffee
@@ -1,4 +1,5 @@

+createLocationSchema = require('./location')
 createOriginSchema = require('./origin')
 createPathOriginSchema = require('./path-origin')

@@ -17,17 +18,26 @@
   filename = options.filename

   # Either exact number or true (= more than one)
-  errors = options.errors or 0
-  warnings = options.warnings or 0
+  annotations = options.annotations or 0
   transactions = options.transactions or 0

-  # Either false (= no media type property should be present) or it
-  # will default to true (= structure must contain media type property)
-  mediaType = if options.mediaType is false then false else true
-
-  # Either false (= no transaction names and paths should be present) or it
-  # will default to true (= structure must contain transaction names and paths)
-  paths = if options.paths is false then false else true
+  annotationSchema =
+    type: 'object'
+    properties:
+      type:
+        type: 'string'
+        enum: ['error', 'warning']
+      component:
+        type: 'string'
+        enum: [
+          'apiDescriptionParser'
+          'parametersValidation'
+          'uriTemplateExpansion'
+        ]
+      message: {type: 'string'}
+      location: createLocationSchema()
+    required: ['type', 'component', 'message', 'location']
+    additionalProperties: false

   headersSchema =
     type: 'array'
@@ -57,38 +67,24 @@
     required: ['status', 'headers']
     additionalProperties: false

-  originSchema = createOriginSchema({filename})
-  pathOriginSchema = createPathOriginSchema()
-
   transactionSchema =
     type: 'object'
     properties:
       request: requestSchema
       response: responseSchema
-      origin: originSchema
-      pathOrigin: pathOriginSchema
-    required: ['request', 'response', 'origin', 'pathOrigin']
+      origin: createOriginSchema({filename})
+      name: {type: 'string'}
+      pathOrigin: createPathOriginSchema()
+      path: {type: 'string'}
+    required: ['request', 'response', 'origin', 'name', 'pathOrigin', 'path']
     additionalProperties: false
-
-  if paths
-    transactionSchema.properties.name = {type: 'string'}
-    transactionSchema.required.push('name')
-    transactionSchema.properties.path = {type: 'string'}
-    transactionSchema.required.push('path')
-
-  properties =
-    transactions: addMinMax({type: 'array', items: transactionSchema}, transactions)
-    errors: addMinMax({type: 'array'}, errors)
-    warnings: addMinMax({type: 'array'}, warnings)
-  required = ['transactions', 'errors', 'warnings']
-
-  if mediaType
-    properties.mediaType = {anyOf: [{type: 'string'}, {type: 'null'}]}
-    required.push('mediaType')

   {
     type: 'object'
-    properties
-    required
+    properties:
+      mediaType: {anyOf: [{type: 'string'}, {type: 'null'}]}
+      transactions: addMinMax({type: 'array', items: transactionSchema}, transactions)
+      annotations: addMinMax({type: 'array', items: annotationSchema}, annotations)
+    required: ['mediaType', 'transactions', 'annotations']
     additionalProperties: false
   }


[CW] [00000] Remove console.log

[CW] [00000] Remove console.log

--- test/test-main.coffee
+++ test/test-main.coffee
@@ -8,8 +8,6 @@
   # Normalize paths to RequireJS module names.
   allTestFiles.push pathToModule(file)  if TEST_REGEXP.test(file)
   return
-
-console.log allTestFiles

 require.config
   # Karma serves files under /base, which is the basePath from your config file


Added ability to provide custom options for pawncc

--- lib/pawn-build.coffee
+++ lib/pawn-build.coffee
@@ -7,9 +7,16 @@
   subscriptions: null

   config:
-    pawnPath:
+    pawnExecutablePath:
+      title: 'Path to pawncc executable'
       type: 'string'
       default: 'C:\\pawn\\pawncc.exe'
+    pawnOptions:
+      title: 'Pawn options'
+      type: 'array'
+      default: ['-d1', '-(', '-;']
+      items:
+        type: 'string'

   activate: (state) ->
     # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
@@ -32,8 +39,12 @@
     return unless filepath? and path.extname(filepath) == '.pwn'

     # prepare command
-    cmd = atom.config.get('pawn-build.pawnPath')
-    args = [ '-d1', path.basename filepath ]
+    cmd = atom.config.get('pawn-build.pawnExecutablePath')
+
+    # prepare arguments
+    args = atom.config.get('pawn-build.pawnOptions')
+    args.push path.basename filepath
+
     console.debug filepath, cmd, args

     # run pawn


Workspace: Now we are syncing floating pane states too.

--- client/landing/app/CommonViews/workspace/views/workspacefloatingpanelauncher.coffee
+++ client/landing/app/CommonViews/workspace/views/workspacefloatingpanelauncher.coffee
@@ -4,17 +4,20 @@

     # options.cssClass = "workspace-launcher"
     # options.partial  = "<span>+</span>"
-    options.cssClass  = "workspace-launcher vertical"
+    options.cssClass     = "workspace-launcher vertical"

     super options, data

-    @sessionKeys       = {}
-    @panel             = @getDelegate()
-    @workspace         = @panel.getDelegate()
-    @container         = new KDView cssClass: "workspace-floating-panes"
-    @keysRef           = @workspace.workspaceRef.child "floatingPaneKeys"
-    @isJoinedASession  = @workspace.getOptions().joinedASession
-    @lastActivePaneKey = null
+    @sessionKeys         = {}
+    @panel               = @getDelegate()
+    @workspace           = @panel.getDelegate()
+    @container           = new KDView cssClass: "workspace-floating-panes"
+    {workspaceRef}       = @workspace
+    @isJoinedASession    = @workspace.getOptions().joinedASession
+    @lastActivePaneKey   = null
+    @keysRef             = workspaceRef.child "floatingPaneKeys"
+    @paneStateRef        = workspaceRef.child "floatingPaneState"
+    @paneVisibilityState = chat: no, preview: no, terminal: no

     @panel.addSubView @container

@@ -24,6 +27,15 @@
         @createPanes()
     else
       @createPanes()
+
+    @paneStateRef.on "value", (snapshot) =>
+      state  = snapshot.val()
+      map    = @paneVisibilityState
+      return unless state
+
+      for own key, value of state
+        pane = @getPaneByType key
+        if value is no then @hidePane pane, key, no else @showPane pane, key, no

   click: ->
     @toggleClass "active"
@@ -47,25 +59,40 @@
   handleLaunch: (paneKey) ->
     pane = @[paneKey]
     if @lastActivePaneKey is paneKey
-      @hidePane pane
+      @hidePane pane, paneKey
     else
       lastActivePane = @getPaneByType @lastActivePaneKey
-      @hidePane lastActivePane if lastActivePane
+      @hidePane lastActivePane, paneKey, no if lastActivePane
       @showPane pane, paneKey

-  hidePane: (pane) ->
+  hidePane: (pane, paneKey, writeToCloud = yes) ->
     pane.unsetClass "active"
     @lastActivePaneKey = null
+    @updatePaneVisiblityState paneKey, no if writeToCloud

-  showPane: (pane, paneKey) ->
-    return @chat.dock.emit "click"  if paneKey is "chat"
+  showPane: (pane, paneKey, writeToCloud = yes) ->
+    if paneKey is "chat"
+      @chat.dock.emit "click"
+      return @updatePaneVisiblityState "chat", yes
+
     pane.setClass "active"
     @lastActivePaneKey = paneKey
+    @updatePaneVisiblityState paneKey, yes  if writeToCloud
+
+  updatePaneVisiblityState: (paneKey, value) ->
+    map          = @paneVisibilityState
+    map[key]     = no for own key of map
+    map[paneKey] = value
+
+    @paneStateRef.set map

   createChatPane: ->
     @container.addSubView @chat = new ChatPane
       delegate : @panel.getDelegate()
       floating : yes
+
+    @chat.on "WorkspaceChatClosed", =>
+      @updatePaneVisiblityState "chat", no

   createTerminalPane: ->
     terminalClass    = SharableTerminalPane


use v2 endpoint now

use v2 endpoint now

--- js/src/http/UserDirectoryApi.coffee
+++ js/src/http/UserDirectoryApi.coffee
@@ -20,7 +20,7 @@

   getUserUrl   = -> Configuration.get('heraclesUrlV2') + '/directory/user'
   getUsersUrl  = -> Configuration.get('heraclesUrlV2') + '/directory/users'
-  usersInRealmUrl = -> Configuration.get('servicesUrl') + '/directory'
+  usersInRealmUrl = -> Configuration.get('servicesUrlV2') + '/directory'

   log = Logger.get('UserDirectoryApi')