callback

callback

--- evilbot.coffee
+++ evilbot.coffee
@@ -141,9 +141,9 @@
   message.say "PONG"

 hear /reload/, (message) ->
-  message.say "Reloading…"
-  exec "git fetch origin && git reset --hard origin/master", ->
-    process.exit(1)
+  message.say "Reloading…", ->
+    exec "git fetch origin && git reset --hard origin/master", ->
+      process.exit(1)

 hear /help/, (message) ->
   message.say "I listen for the following…", ->


Remove angular service not used

Remove angular service not used

--- assets/js/share.coffee
+++ assets/js/share.coffee
@@ -1,10 +1,4 @@
-#= require ../../node_modules/share/node_modules/browserchannel/dist/bcsocket-uncompressed
+#= require ./../../node_modules/share/node_modules/browserchannel/dist/bcsocket-uncompressed

-#= require ../../node_modules/share/webclient/share.uncompressed
-#= require ../../node_modules/share/webclient/json.uncompressed
-
-module = angular.module "plunker.share", []
-
-module.service "share", [ () ->
-
-]+#= require ./../../node_modules/share/webclient/share.uncompressed
+#= require ./../../node_modules/share/webclient/json.uncompressed


new way of dealing with histogram extremes

 also strip HTML from point detail tease

Former-commit-id: 55f5d3a906950e2f82d410b2ecb67caee4f97378 [formerly 3a4201ca5cd7c5d7f8011450cd8adbf5e8c0f267] [formerly cb77181505fef0f0fdd3feae579bb64fdc1c5041 [formerly 718303c61096d6c21e2855a4adf18ea4255103f6]] [formerly cdcad9799a7cc5f4b77e8eba0e938af2be3aa776 [formerly 774e6fcdc02befc88e9f603ea69206cf3b38fad9] [formerly b510902297793989c35d576e76584607461d1ea6 [formerly b510902297793989c35d576e76584607461d1ea6 [formerly 5cf492241dbb3feee86ff65167e1ecb1c7e9a23b]]]]
Former-commit-id: 729676a465a8a48ffcf97ae62e31ffeb1236692f [formerly f522406831a0812c9b2780391e3b5c21e1689c98] [formerly 8ccb2e6d7e4c56215b0651d212bc221e98b71d07 [formerly fb611972061cf9a0c9d30846145df15182b63c5b]]
Former-commit-id: 57e92b6dd7104c35b7459e858832e9ed5cd26506 [formerly b9acbb5b0b5f86f7bdf5993bba4a62304f8fbe09]
Former-commit-id: ef1d1a5decbfba603037f48c8921291d283bdbd3
Former-commit-id: 6ae3b1b9df7a9ebe49cc16ca94cc5f3cec12c8f1

--- @client/franklin.coffee
+++ @client/franklin.coffee
@@ -384,14 +384,10 @@

       # If this is an extreme opinion...
       else if segment in [0,6]
-        if segments[segment][0].length == max_opinions_in_column - 1
-          # subdivide this segment into 2x the columns with .5 the opinions from before
-          new_cols = []
-          for old_col in segments[segment]
-            new_cols.push old_col[0..Math.floor(old_col.length/2)]
-            new_cols.push old_col[Math.floor(old_col.length/2)..old_col.length]
-          segments[segment] = new_cols
-        segments[segment][opinions_in_segment[segment] % segments[segment].length].push opinion
+        if segments[segment][segments[segment].length - 1].length == max_opinions_in_column - 1
+          segments[segment].push []
+
+        segments[segment][segments[segment].length - 1].push opinion

       # If this opinion is somewhere else on the spectrum...
       else
@@ -631,6 +627,7 @@
       over : (ev, ui) => $el.addClass "user_is_hovering_on_a_drop_target"

   render : ->
+
     #filter to pros or cons & down to points that haven't been included
     points = _.filter fetch({ url: 'all_points' }), (pnt) =>
       is_correct_valence = pnt.is_pro == (@props.valence == 'pro')
@@ -702,7 +699,7 @@
             if @state.show_details
               R.div className: 'point_details', dangerouslySetInnerHTML:{__html: @props.text}
             else
-              R.span className: 'point_details_tease', @props.text[0..30] + "..."
+              R.span className: 'point_details_tease', $("<span>#{@props.text[0..30]}</span>").text() + "..."

         # R.a className:'open_point_link',
         #   "#{@props.comment_count} comment#{if @props.comment_count != 1 then 's' else ''}"


FIX: write PNG files for bitmaps with transparency

--- source/Art/Engine/File/V1Writer.coffee
+++ source/Art/Engine/File/V1Writer.coffee
@@ -100,7 +100,8 @@
     if !propsEq axis, point .5
       encodedProps.handle = "(#{axis.x},#{axis.y})"

-  _addTagForBitmap: ({bitmap, userProps}) ->
+  _addTagForBitmap: (element) ->
+    {bitmap, userProps} = element
     {uniqueId} = bitmap
     return Promise.resolve() if @bitmapTags[uniqueId]
     {encodedBitmap} = userProps if userProps
@@ -110,7 +111,13 @@
     #   encodedBitmap: encodedBitmap?.slice 0, 16

     Promise.resolve()
-    .then => encodedBitmap || bitmap.toJpg()
+    .then =>
+      if encodedBitmap
+        encodedBitmap
+      else if bitmap.hasAlpha
+        bitmap.toPng()
+      else
+        bitmap.toJpg()
     .then (encodedBitmap) =>
       @bitmapTags[uniqueId] = BitmapTag
         bitmap_id: @bitmapTagCount++


调整loading时间

调整loading时间

--- source/js/controllers/fragments.coffee
+++ source/js/controllers/fragments.coffee
@@ -32,11 +32,11 @@
     # 需要FragmentListController先render一次才行，因为item中的el
     # 是由ListControlller动态生成的
     #FragmentTag.bind("refresh", @add_fragments)
+    @el.loading()
     FragmentTag.bind("refresh", @render)
     FragmentTag.fetch()

   add_fragments: =>
-    @el.loading()
     tags = FragmentTag.all()
     fragments = (new FragmentItem(item: new Fragment(tag_id: tag.id)) for tag in tags)
     @add_fragment fragment for fragment in fragments


Add plugin state specs

Add plugin state specs

--- spec/coffeescripts/baDumTssSpec.coffee
+++ spec/coffeescripts/baDumTssSpec.coffee
@@ -21,3 +21,14 @@

       expect(plugin.settings.scrollOffset).toBe(customOptions.scrollOffset)

+  describe 'plugin state', ->
+    beforeEach ->
+      @plugin = new $.baDumTss(@$element)
+
+    it 'should have a ready state', ->
+      expect(@plugin.getState()).toBe 'ready'
+
+    it 'should be updatable', ->
+      @plugin.setState('new state')
+
+      expect(@plugin.getState()).toBe 'new state'


Add Cabin

Add Cabin

--- examples/Controls.cjsx
+++ examples/Controls.cjsx
@@ -75,6 +75,7 @@
   "Jura"
   "Bitter"
   "EB Garamond"
+  "Cabin"
 ]

 fonts = fonts.sort()


also set minutes and seconds

also set minutes and seconds

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -426,7 +426,13 @@
           (time) => @_delta (_1h * time)
         when 'tomorrow'
           (hour) ->
-            moment().add(1, 'day').hours(hour).utc().valueOf()
+            moment()
+              .add(1, 'day')
+              .hours(hour)
+              .minutes(0)
+              .seconds(0)
+              .utc()
+              .valueOf()
         when 'days'
           (time) => @_delta (_1d * time)
         when 'months'


fixed core.load

fixed core.load

--- lib/core.coffee
+++ lib/core.coffee
@@ -11,6 +11,10 @@
 defaultConfig =

 module.exports = core =
+
+  # for troubleshooting/wiretapping
+  bus: bus
+
   config:
     timeout: 2000

@@ -30,7 +34,6 @@

   load: (moduleName, module) ->
     {config, services} = module
-    moduleName = module.name

     for serviceName, options of config
       serviceChannel = "#{moduleName}.#{serviceName}"
@@ -226,10 +229,8 @@

     timers.setImmediate finish

-
+  # sends acknowledgement, error, completion to replyTo channels
   respond: (channel, handler) ->
-    # can respond to request or delegate (or should we split this out?)
-    # sends acknowledgement, error, completion to replyTo channels
     responderId = uuid.v1()

     callback = (message, envelope) ->
@@ -262,9 +263,8 @@
       topic: 'request.#'
       callback: callback

-
+  # just send the message
   send: (channel, data) ->
-    # just send the message
     topicId = uuid.v1()
     topic = topicId
     pub = bus.publish
@@ -272,14 +272,12 @@
       data: data
       topic: topic

-
+  # just listen
   listen: (channel, handler) ->
-    # just listen
     sub = bus.subscribe
       channel: channel
       topic: '#'
       callback: handler

-
+  # for sending interrupts
   signal: (channel, data) ->
-    # for sending interrupts


Add logic in 'core.init' for dynamic 'require' and 'core.load'-ing of modules

--- lib/core.coffee
+++ lib/core.coffee
@@ -10,7 +10,6 @@

 getTopicId = (topic) -> topic.split('.').pop()

-defaultConfig =

 module.exports = core =

@@ -18,6 +17,7 @@
   bus: bus

   config:
+    modules: []
     timeout: 2000

   # a place to record what responders we have attached
@@ -29,6 +29,9 @@

     # Require each axiom module.
     # Pass to load.
+    for moduleName in core.config.modules
+      module = require "axiom-#{moduleName}"
+      core.load moduleName, module

   reset: ->
     core.responders = {}
@@ -36,6 +39,7 @@

   load: (moduleName, module) ->
     {config} = module
+    config or= []
     services = law.create module

     for serviceName, options of config
@@ -48,7 +52,6 @@
             core.request baseChannel, {moduleName, serviceName, args, config: options, axiom: core}, done

     for serviceName, serviceDef of services
-
       # attach a responder for each service definition
       core.respond "#{moduleName}.#{serviceName}", serviceDef


moved around the api key check

moved around the api key check

--- lib/core.coffee
+++ lib/core.coffee
@@ -92,12 +92,17 @@
 		parsedParams = qs.stringify(params).replace(/\%2c/ig, ',')
 		fullUrl = baseUrl + path.join(module, method) + '.json'

-		if(_is('get', module, method) && typeof api_key == 'string')
-			# console.log('GET: ' + fullUrl + '?' + parsedParams)
-			get(fullUrl + '?' + parsedParams + '&api_key=' + api_key, callback)
+		if(typeof api_key == 'string')
+			if(_is('get', module, method))
+				# console.log('GET: ' + fullUrl + '?' + parsedParams)
+				get(fullUrl + '?' + parsedParams + '&api_key=' + api_key, callback)
+			else
+				console.log('Method not supported.')
+				new Error('Viralheat.callAPI: Method not supported.')
+				return
 		else
-			console.log('Method not supported.')
-			new Error('Viralheat.callAPI: Method not supported.')
+			console.log('No api key was provided.')
+			new Error('viralheat.callAPI: You failed to provide an api key or the api key provided was not valid.')
 			return

 	######


Set defaults for maxLineLength, ignoreErrorCodes

Specified a default max line length of 79 chars (PEP8 default) and report all error codes by default

--- lib/init.coffee
+++ lib/init.coffee
@@ -1,6 +1,8 @@
 module.exports =
   configDefaults:
     executableDir: null
+    maxLineLength: 79
+    ignoreErrorCodes: []

   activate: ->
     console.log 'activate linter-python-flake8'


rename integrated-learn-environment => learn-ide in config settings

--- lib/init.coffee
+++ lib/init.coffee
@@ -66,8 +66,8 @@
     token = url.parse(newURL, true).query.ide_token
     confirmOauthToken(token).then (res) ->
       return unless res?
-      atom.config.set('integrated-learn-environment.oauthToken', token)
-      atom.config.set('integrated-learn-environment.vmPort', res.vm_uid)
+      atom.config.set('learn-ide.oauthToken', token)
+      atom.config.set('learn-ide.vmPort', res.vm_uid)
       win.destroy()

       atom.commands.dispatch(workspaceView, 'learn-ide:toggle-terminal')
@@ -102,8 +102,8 @@
         confirmOauthToken(token).then (res) ->
           console.log "res: #{res}"
           return unless res
-          atom.config.set('integrated-learn-environment.oauthToken', token)
-          atom.config.set('integrated-learn-environment.vmPort', res.vm_uid)
+          atom.config.set('learn-ide.oauthToken', token)
+          atom.config.set('learn-ide.vmPort', res.vm_uid)
           atom.commands.dispatch(workspaceView, 'learn-ide:toggle-terminal', show: true)
     if newURL.match(/github_sign_in/)
       win.destroy()
@@ -149,8 +149,8 @@
       token = input.value.trim()
       confirmOauthToken(token).then (res) ->
         if res
-          atom.config.set('integrated-learn-environment.oauthToken', input.value)
-          atom.config.set('integrated-learn-environment.vmPort', res.vm_uid)
+          atom.config.set('learn-ide.oauthToken', input.value)
+          atom.config.set('learn-ide.vmPort', res.vm_uid)
           panel.destroy()
           atom.commands.dispatch(workspaceView, 'learn-ide:toggle-terminal')
           return true
@@ -160,7 +160,7 @@
 getVMPort = ->
   confirmOauthToken(existingToken).then (res) ->
     if res
-      atom.config.set('integrated-learn-environment.vmPort', res.vm_uid)
+      atom.config.set('learn-ide.vmPort', res.vm_uid)
       atom.commands.dispatch(workspaceView, 'learn-ide:toggle-terminal')
       return true

@@ -175,13 +175,13 @@
   win.loadUrl('https://learn.co/sign_out')

 window.logout = ->
-  atom.config.unset('integrated-learn-environment.oauthToken')
-  atom.config.unset('integrated-learn-environment.vmPort')
+  atom.config.unset('learn-ide.oauthToken')
+  atom.config.unset('learn-ide.vmPort')
   learnLogout()
   githubLogout()

-existingToken = atom.config.get('integrated-learn-environment.oauthToken')
-vmPort = atom.config.get('integrated-learn-environment.vmPort')
+existingToken = atom.config.get('learn-ide.oauthToken')
+vmPort = atom.config.get('learn-ide.vmPort')

 if !existingToken
   learnSignIn()


:art: Use || instead of or

:art: Use || instead of or

--- lib/main.coffee
+++ lib/main.coffee
@@ -14,7 +14,7 @@
       scope: 'file'
       lintOnFly: true
       lint: (textEditor) ->
-        exePath = atom.config.get('linter-jshint.jshintExecutablePath') or jshintPath
+        exePath = atom.config.get('linter-jshint.jshintExecutablePath') || jshintPath
         filePath = textEditor.getPath()
         text = textEditor.getText()
         parameters = ['--reporter', reporter, '--extract', 'auto', '--filename', filePath, '-']


:bug: Add error type when non-existing Fixes AtomLinter/linter-php#51

--- lib/main.coffee
+++ lib/main.coffee
@@ -37,4 +37,5 @@
         text = textEditor.getText()
         return helpers.exec(command, parameters, {stdin: text}).then (output) =>
           messages = helpers.parse(output, @regex, {filePath: filePath})
+          messages.forEach (message) -> message.type = 'Error' unless message.type
           return messages


Improve handling of TextEditor subscriptions and only save on ActiveTextEditors.

--- lib/main.coffee
+++ lib/main.coffee
@@ -5,17 +5,25 @@

   activate: (state) ->
     @transpiler ?= new (require './transpiler')
-    # track any file save ( buffer save) events and transpile if babel
+    # track any file save events and transpile if babel
     @disposable = new CompositeDisposable
+    @textEditors = {}
+
     @disposable.add atom.project.onDidChangePaths =>
       @transpiler.stopUnusedTasks()
-    @disposable.add  atom.workspace.observeTextEditors (textEditor) =>
-      @disposable.add textEditor.onDidSave (event) =>
-        grammar = textEditor.getGrammar()
-        return if grammar.packageName isnt 'language-babel'
-        @transpiler.transpile(event.path, textEditor)
+
+    @disposable.add atom.workspace.observeTextEditors (textEditor) =>
+      @textEditors[textEditor.id] = new CompositeDisposable
+      @textEditors[textEditor.id].add textEditor.onDidSave (event) =>
+        if atom.workspace.getActiveTextEditor().id is textEditor.id and
+        textEditor.getGrammar().packageName is 'language-babel'
+          @transpiler.transpile(event.path, textEditor)
+      @textEditors[textEditor.id].add textEditor.onDidDestroy () =>
+        @textEditors[textEditor.id].dispose()
+        delete @textEditors[textEditor.id]

   deactivate: ->
-    if @disposable?
-      @disposable.dispose()
+    @disposable.dispose()
+    for id, disposeable of @textEditors
+      disposeable.dispose()
     @transpiler.stopAllTranspilerTask()


[Fixes #138060363] iPad: Document title doesn't get deselected once it is tapped

Added event listeners for mousedown and touchstart during componentWillMount

--- src/code/views/dropdown-view.coffee
+++ src/code/views/dropdown-view.coffee
@@ -52,6 +52,20 @@
     timeout: null
     subMenu: null

+  componentWillMount: ->
+    if window.addEventListener
+      window.addEventListener 'mousedown', @checkBlur, true
+      window.addEventListener 'touchstart', @checkBlur, true
+
+  componentWillUnmount: ->
+    if window.removeEventListener
+      window.removeEventListener 'mousedown', @checkBlur, true
+      window.removeEventListener 'touchstart', @checkBlur, true
+
+  checkBlur: ->
+    if @state.showingMenu
+      @blur()
+
   blur: ->
     @unblur()
     timeout = setTimeout ( => @setState {showingMenu: false, subMenu: false} ), 500


Avoid the creation of two channels.

Avoid the creation of two channels.

--- src/coffee/orderdistribution.coffee
+++ src/coffee/orderdistribution.coffee
@@ -77,12 +77,12 @@

   run: ->
     @inventoryUpdater.ensureChannelByKey(@masterClient._rest, @retailerProjectKey, CHANNEL_ROLES)
-    .then (channel) =>
-      @getUnSyncedOrders @masterClient, channel.id
-    .then (masterOrders) =>
-      @distributeOrders masterOrders
-
-  distributeOrders: (masterOrders) ->
+    .then (channelInMaster) =>
+      @getUnSyncedOrders @masterClient, channelInMaster.id
+      .then (masterOrders) =>
+        @distributeOrders masterOrders, channelInMaster
+
+  distributeOrders: (masterOrders, channelInMaster) ->
     if _.size(masterOrders) is 0
       Q('Nothing to do.')
     else
@@ -93,19 +93,18 @@
         console.error "The order '#{bad.id}' has different channels set!"

       distributions = _.map validOrders, (order) =>
-        @distributeOrder order
+        @distributeOrder order, channelInMaster

       Q.all(distributions)

-  distributeOrder: (masterOrder) ->
+  distributeOrder: (masterOrder, channelInMaster) ->
     masterSKUs = @extractSKUs masterOrder

     Q.all([
       @getTaxCategory @retailerClient
-      @inventoryUpdater.ensureChannelByKey @masterClient._rest, @retailerProjectKey, CHANNEL_ROLES
       @inventoryUpdater.ensureChannelByKey @retailerClient._rest, 'master', CHANNEL_ROLES
     ])
-    .spread (taxCategory, channelInMaster, channelInRetailer) =>
+    .spread (taxCategory, channelInRetailer) =>
       gets = _.map masterSKUs, (sku) =>
         @getRetailerProductByMasterSKU(sku)
       Q.all(gets)


Defer

Defer

--- src/assets/javascripts/backdrop.coffee
+++ src/assets/javascripts/backdrop.coffee
@@ -8,12 +8,14 @@
   scale: ->
     { width, height } = dimensions = @dimensions()

-    @$el
-      .addClass('is-ready')
-      .css
-        transform: "scale(#{@factor(dimensions)})"
-        marginTop: -(height / 2)
-        marginLeft: -(width / 2)
+    setTimeout =>
+      @$el
+        .addClass('is-ready')
+        .css
+          transform: "scale(#{@factor(dimensions)})"
+          marginTop: -(height / 2)
+          marginLeft: -(width / 2)
+    , 1

   dimensions: ->
     if @$el.is ':visible'


Overhaul

Overhaul

--- paginate.coffee
+++ paginate.coffee
@@ -1,17 +1,13 @@
 @__Pages = class Pages
   constructor: (collection, settings = {}) ->
     @setCollection collection
-<<<<<<< HEAD
-    @setId collection
+    @setId @Collection._name
     @requested = []
     @received = []
     @queue = []
     @cache = {}
     @timeouts = {}
     @subscriptions = {}
-=======
-    @setId @Collection._name
->>>>>>> 6a5ad9c74ea7237a755502274a298441bad20d6b
     Pages.prototype.paginations[@name] = @
     for key, value of settings
       @set key, value, false


Account for global header

Account for global header

--- framer/Components/FlowComponent.coffee
+++ framer/Components/FlowComponent.coffee
@@ -243,12 +243,15 @@
 		for scroll in [layer, layer.children...]
 			@_forwardScrollEvents(layer)

+			if scroll instanceof ScrollComponent
+				inset = {}
+				inset.top = @header?.height or 0 if scroll.y is 0
+				inset.bottom = @footer?.height or 0 if scroll.maxY is @height
+				scroll.contentInset = inset
+
 		# Set the background color for he created scroll component
 		if layer instanceof ScrollComponent
-			scroll.backgroundColor = @backgroundColor
-			scroll.contentInset =
-				top: @header?.height or 0
-				bottom: @footer?.height or 0
+			layer.backgroundColor = @backgroundColor

 		return layer

@@ -390,7 +393,7 @@
 ##############################################################
 # Layout helpers

-detectHeaderFooter = (layer) ->
+pageLayout = (layer) ->

 	# This function tries to detect a stack of:
 	# 1) header / body / footer
@@ -461,7 +464,7 @@

 layoutPage = (layer, size) ->

-	split = detectHeaderFooter(layer)
+	split = pageLayout(layer)

 	return layer unless split.body

@@ -493,6 +496,7 @@
 		size: size
 		name: "scroll"

+	height = layer.height
 	scroll.propagateEvents = false

 	constraints = layer.constraintValues


KDListView: removeitem w/o data argument guard added

--- src/components/list/listview.coffee
+++ src/components/list/listview.coffee
@@ -68,7 +68,8 @@
       {dataPath} = @getOptions()
       # fix it w/ indexOf
       for item, i in @items
-        if itemInstance is item or itemData[dataPath] is item.getData()?[dataPath]
+        if itemInstance.getId() is item.getId() or \
+           itemData?[dataPath] is item.getData()?[dataPath]
           @emit 'ItemIsBeingDestroyed', { view : item, index : i }
           @items.splice i, 1
           item.destroy()


More debug output with sandboxes

More debug output with sandboxes

--- lib/util.coffee
+++ lib/util.coffee
@@ -78,23 +78,38 @@
     if rootPath
       sbd = false
       if atom.config.get('haskell-ghc-mod.cabalSandbox')
+        Util.debug("Looking for cabal sandbox...")
         sbc = Util.parseSandboxConfig("#{rootPath}#{sep}cabal.sandbox.config")
         if sbc?['install-dirs']?['bindir']?
           sandbox = sbc['install-dirs']['bindir']
+          Util.debug("Found cabal sandbox: ", sandbox)
           if Util.isDirectory(sandbox)
             sbd = true
             apd.unshift sandbox
+          else
+            Util.warn("Cabal sandbox ", sandbox, " is not a directory")
+        else
+          Util.warn("No cabal sandbox found")
       if atom.config.get('haskell-ghc-mod.stackSandbox')
+        Util.debug("Looking for stack sandbox...")
         env.PATH = joinPath(apd)
+        Util.debug("Running stack with PATH ", env.PATH)
         stackpath =
-          try CP.execSync "stack path --bin-path",
-            encoding: 'utf-8'
-            cwd: rootPath
-            env: env
-            timeout: atom.config.get('haskell-ghc-mod.syncTimeout')
+          try
+            CP.execSync "stack path --bin-path",
+              encoding: 'utf-8'
+              cwd: rootPath
+              env: env
+              timeout: atom.config.get('haskell-ghc-mod.syncTimeout')
+          catch error
+            Util.warn("Running stack failed with ", error)
         if stackpath
+          Util.debug("Found stack sandbox ", stackpath)
           apd = stackpath.split(delimiter).concat apd
+        else
+          Util.warn("No stack sandbox found")
         if sbd
+          Util.debug("Reinstating cabal sandbox as first in PATH")
           apd.unshift sandbox
     env.PATH = joinPath(apd)
     Util.debug "PATH = #{env.PATH}"


Cleanup spec.

Cleanup spec.

--- spec/CreateLayer.coffee
+++ spec/CreateLayer.coffee
@@ -6,7 +6,6 @@
 describe 'CreateLayer component', ->
   component = null
   start = null
-  properties = null
   layer = null

@@ -23,7 +22,7 @@
   describe 'creating a layer', ->
     it 'should produce a new layer', (done) ->
       layer.on 'data', (data) ->
-        expect(data).to.be.instanceof Layer
+        expect(data).to.be.instanceof window.Layer
         done()

       start.send()


separete killing

separete killing

--- spec/deploy.spec.coffee
+++ spec/deploy.spec.coffee
@@ -94,4 +94,6 @@

     runs ->
       expect( teamSupportAnnounced ).toBeTruthy()
-      @testRunDone = true
+
+  it "kills the testrun", =>
+    @testRunDone = true


Extracted search events

Extracted search events

--- SingularityUI/app/views/requests.coffee
+++ SingularityUI/app/views/requests.coffee
@@ -35,6 +35,10 @@
         'click [data-action="starToggle"]': 'toggleStar'
         'click [data-action="run-now"]': 'runRequest'
         'click [data-requests-active-filter]': 'changeFilters'
+
+        'change input[type="search"]': 'searchChange'
+        'keyup input[type="search"]': 'searchChange'
+        'input input[type="search"]': 'searchChange'

     initialize: ->
         @lastRequestsFilter = @options.requestsFilter
@@ -169,7 +173,6 @@
                 requestsBody: templateBody

         $search = @$el.find('input[type="search"]')
-        searchWasFocused = $search.is(':focus')

         $requestsBodyContainer =  @$el.find('[data-requests-body-container]')

@@ -187,8 +190,7 @@
             $requestsBodyContainer.html templateBody context

             @$el.find('input[type="search"]').val context.searchFilter
-
-        @setUpSearchEvents(refresh, searchWasFocused)
+
         utils.setupSortableTables()

         @
@@ -289,17 +291,11 @@
         @lastSearchFilter = _.trim @$el.find('input[type="search"]').val()
         app.router.navigate "/requests/#{ @lastRequestsFilter }/#{ requestsActiveFilter }/#{ @lastSearchFilter }", trigger: true

-    setUpSearchEvents: (refresh, searchWasFocused) ->
-        $search = @$el.find('input[type="search"]')
-
-        if not app.isMobile and (not refresh or searchWasFocused)
-            setTimeout -> $search.focus()
-
-        $rows = @$('tbody > tr')
-
-        previousLastSearchFilter = ''
-
+    searchChange: (event) =>
         onChange = =>
+            $search = $(event.currentTarget)
+            previousLastSearchFilter = ''
+            $rows = @$('tbody > tr')
             return unless @ is app.views.current

             @lastSearchFilter = _.trim $search.val()
@@ -327,11 +323,7 @@
             @$('table').each ->
                 utils.handlePotentiallyEmptyFilteredTable $(@), 'request', @lastSearchFilter

-        onChangeDebounced = _.debounce onChange, 200
-
-        $search.unbind().on 'change keypress paste focus textInput input click keydown', onChangeDebounced
-
-        if refresh
-            onChange()
+        (_.debounce onChange, 200)()
+

 module.exports = RequestsView


Set desktop notifcation for visable pages on app startup.

--- app/assets/javascripts/app/controllers/notify.js.coffee
+++ app/assets/javascripts/app/controllers/notify.js.coffee
@@ -22,8 +22,9 @@
     # request desktop notification after login
     App.Event.bind 'auth', (data) ->
       if !_.isEmpty(data)
-        notify.config( pageVisibility: false )
         notify.requestPermission()
+
+    notify.config( pageVisibility: false )

   render: (data) ->
 #    notify = App.view('notify')(data: data)


Only show the add-cohorts button if a course is already submitted.

Resolves #343

--- app/assets/javascripts/components/overview/details.cjsx
+++ app/assets/javascripts/components/overview/details.cjsx
@@ -142,7 +142,7 @@
         </fieldset>

           Cohorts: {cohorts}
-          <CohortButton {...@props} show={@props.editable && @props.current_user.admin} />
+          <CohortButton {...@props} show={@props.editable && @props.current_user.admin && @props.course.submitted} />

           Tags: {tags}


Fokus ins erste Zeitfeld setzen, wenn gütlig ausgewählt

--- app/assets/javascripts/controllers/score/runs.js.coffee
+++ app/assets/javascripts/controllers/score/runs.js.coffee
@@ -3,7 +3,7 @@
     context = $(this)
     disableHandler = () ->
       if $(this).val() == "valid"
-        context.find('.time-entries').removeClass('disabled')
+        context.find('.time-entries').removeClass('disabled').find('input:first').focus()
       else
         context.find('.time-entries').addClass('disabled')
     $('input:radio', context).on 'ifChecked', disableHandler


Date extensions cleaned up.

Date extensions cleaned up.

--- helpers.coffee
+++ helpers.coffee
@@ -1,54 +1,52 @@
-###
-Remove an item e from an array.
-###
-Array::remove = (e) -> @[t..t] = [] if (t = @indexOf(e)) > -1
+_.extend(Array::,
+  remove: (e) -> @[t..t] = [] if (t = @indexOf(e)) > -1

-###
-Clone an array
-###
-Array::clone = -> this[..]
+  clone: -> this[..]
+)

-###
-Pass a string 'trigger' where the callback should go and this
-should wait for the callback and returning its arguments.
-###
-Function::wait = (args...) ->
-  error = true
-  result = undefined
-  f = (args...) -> result = args
-  for a, i in args
-    if a is 'trigger'
-      args[i] = f
-      error = false
-  return if error
-  @(args...)
-  while not result? then
-  return result
-
-Date::smart = ->
-  #Get smart timestamp
-  timediff = (new Date()).getTime() - this.getTime()
-  if timediff < (1000*60) #under one minute ago
-    smart = "#{Math.round(timediff / 1000)} seconds ago"
-  else if timediff < (1000*60*60) #under one hour ago
-    smart = "#{Math.round(timediff / 1000 / 60)} minutes ago"
-  else if timediff < (1000*60*60*24) #under one day ago
-    smart = "#{Math.round(timediff / 1000 / 60 / 60)} hours ago"
-  else if timediff < (1000*60*60*24*7) #under one week ago
-    days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
-    smart = "on #{days[this.getDay()]}"
-  else
-    smart = this.toDateString()
-  return smart
+_.extend(Date::,
+  days_of_week: [
+    'Sunday',
+    'Monday',
+    'Tuesday',
+    'Wednesday',
+    'Thursday',
+    'Friday',
+    'Saturday'
+  ]

-Date::readable = ->
-  #Get full readable timestamp
-  ap = 'am'
-  hour = this.getHours()
-  ap = 'pm' if hour > 11
-  hour = hour - 12 if hour > 12
-  hour = 12 if hour == 0
-  minutes = this.getMinutes()
-  minutes = "0#{minutes}" if minutes < 10
-  full = "#{this.toDateString()} at #{hour}:#{minutes}#{ap}"
-  return full
+  relative: ->
+    #Get smart timestamp
+    time_diff = (new Date()).getTime() - @getTime()
+    time_dict =
+      minute:
+        size: 60000
+        to_string: => "#{Math.round(time_diff / 1000)} seconds ago"
+      hour:
+        size: 3600000
+        to_string: => "#{Math.round(time_diff / 60000)} minutes ago"
+      day:
+        size: 86400000
+        to_string: => "#{Math.round(time_diff / 3600000)} hours ago"
+      week:
+        size: 604800000
+        to_string: => "on #{@days_of_week[@getDay()]}"
+
+    for k, v of time_dict
+      if time_diff < v.size
+        return v.to_string()
+
+    return @toDateString()
+
+  readable: ->
+    #Get full readable timestamp
+    ap = 'am'
+    hour = @getHours()
+    ap = 'pm' if hour > 11
+    hour = hour - 12 if hour > 12
+    hour = 12 if hour == 0
+    minutes = @getMinutes()
+    minutes = "0#{minutes}" if minutes < 10
+    full = "#{@toDateString()} at #{hour}:#{minutes}#{ap}"
+    return full
+)


Accommodate rounding errors when comparing focused times

--- app/assets/javascripts/modules/timeline/timeline.coffee
+++ app/assets/javascripts/modules/timeline/timeline.coffee
@@ -350,7 +350,8 @@
       overlay = @focusOverlay
       @_empty(overlay)

-      t0 = null if t0 == @_focus
+      console.log "t0: #{t0}, @_focus: #{@_focus}"
+      t0 = null if Math.abs(t0 - @_focus) < 1000
       @_focus = t0

       if t0?


adding _.each to util so that we can iterate with (perisistent) scope closure easily

--- app/assets/javascripts/monster_mithril/core/util.coffee
+++ app/assets/javascripts/monster_mithril/core/util.coffee
@@ -29,6 +29,15 @@
     else
       target[prop] = source[prop]
   target
+
+util.each = (target, iterator) ->
+  if(target instanceof Array)
+    for value in target
+      iterator(value)
+  else
+    for key, value of target
+      iterator(value, key)
+

 util.any = (arr,fun=null) ->
   val = false
@@ -78,6 +87,7 @@
 window._.generate_UUID ||= util.generate_UUID
 window._.create_path ||= util.create_path
 window._.extend     ||= util.extend
+window._.each     ||= util.each
 window._.any        ||= util.any
 window._.find_by_id ||= util.find_by_id
 window._.is_array   ||= util.is_array


Do not require turbolinks.

Do not require turbolinks.

--- app/assets/javascripts/slashadmin/application.js.coffee
+++ app/assets/javascripts/slashadmin/application.js.coffee
@@ -1,6 +1,5 @@
 #= require jquery
 #= require jquery_ujs
-#= require turbolinks
 #= require bootstrap
 #= require_tree .
 #= require_self


revoke self access to an object

revoke self access to an object

--- js/src/http/ObjectAuthorizationApi.coffee
+++ js/src/http/ObjectAuthorizationApi.coffee
@@ -26,6 +26,7 @@
   ownersUrl  = (objectId) -> accessUrl() + '/owners/' + objectId
   readersUrl = (objectId) -> accessUrl() + '/readers/' + objectId
   writersUrl = (objectId) -> accessUrl() + '/writers/' + objectId
+  revokeUrl  = (objectId) -> accessUrl() + '/revoke/' + objectId

   logger = Logger.get('ObjectAuthorizationApi')

@@ -82,4 +83,14 @@
         else
           return null

+    revokeAccessToObject: (objectId) ->
+      Promise.resolve(
+        axios(
+          @wrapCredentials({
+            method : 'GET'
+            url    : revokeUrl(objectId)
+          })
+        )
+      )
+
   return ObjectAuthorizationApi


Fix materials not being loaded when render is paused (until force-render of loaded stuff is added)

--- engine/fetch_assets.coffee
+++ engine/fetch_assets.coffee
@@ -234,7 +234,7 @@
                 # Check if material was already loaded; if it was, we'll assume
                 # it was rendered at this point, even though it may not
                 if not mat
-                    if scene.enabled
+                    if scene.enabled and scene.context.main_loop.enabled
                         # TODO: compile material even though is not
                         # going to be used
                         promise = material_promises[mat_name]


check for existence of $.fn before extending it

--- src/bacon.jquery.coffee
+++ src/bacon.jquery.coffee
@@ -20,8 +20,6 @@
   }

   Bacon.$.Model = Bacon.Model
-
-  $.fn.asEventStream = Bacon.$.asEventStream

   # Input element bindings
   Bacon.$.textFieldValue = (element, initValue) ->
@@ -138,8 +136,6 @@
     do (e) ->
       events[e + 'E'] = (args...) -> @asEventStream e, args...

-  $.fn.extend events
-
   # jQuery Effects

   effectNames = [
@@ -152,7 +148,10 @@
     do (e) ->
       effects[e + 'E'] = (args...) -> Bacon.fromPromise @[e](args...).promise()

-  $.fn.extend effects
+  if $?.fn
+    $.fn.extend events
+    $.fn.extend effects
+    $.fn.asEventStream = Bacon.$.asEventStream

   Bacon.$


remove crashing

remove crashing

--- src/controllers/main-controller.coffee
+++ src/controllers/main-controller.coffee
@@ -71,9 +71,7 @@
       LogService.add error.message

     $scope.$on "gateblu:config", ($event, config) =>
-      console.log("I SHOULD CRASH NOW", config.crashPath);
       gui.App.setCrashDumpDir config.crashPath
-      gui.App.crashBrowser()
       LogService.add "Gateway ~#{config.uuid} is online"
       $scope.name = config.name || config.uuid


fix cache for presentation bug...

fix cache for presentation bug...

--- lib/markdown-preview-enhanced-view.coffee
+++ lib/markdown-preview-enhanced-view.coffee
@@ -23,6 +23,8 @@
   html: @element.innerHTML
   codeChunksData: @codeChunksData
   graphData: @graphData
+  presentationMode: @presentationMode
+  slideConfigs: @slideConfigs
 }
 ###
 CACHE = {}
@@ -147,7 +149,9 @@
       CACHE[@editor.getPath()] = {
         html: @element?.innerHTML or '',
         codeChunksData: @codeChunksData,
-        graphData: @graphData
+        graphData: @graphData,
+        presentationMode: @presentationMode,
+        slideConfigs: @slideConfigs
       }

       @element.innerHTML = '<p style="font-size: 24px;"> loading preview... </p>'
@@ -184,6 +188,13 @@
       @element.innerHTML = d.html
       @graphData = d.graphData
       @codeChunksData = d.codeChunksData
+      @presentationMode = d.presentationMode
+      @slideConfigs = d.slideConfigs
+
+      if @presentationMode
+        @element.setAttribute 'data-presentation-preview-mode', ''
+      else
+        @element.removeAttribute 'data-presentation-preview-mode'

       @setInitialScrollPos()
       # console.log 'restore ' + @editor.getPath()


enable certain controls only if balance_classes is enabled

--- src/ext/components/automl-input.coffee
+++ src/ext/components/automl-input.coffee
@@ -71,11 +71,17 @@
       response,
       ignoredColumns,
       monotoneConstraints,
+      balanceClasses,
+      classSamplingFactors,
+      maxAfterBalanceSize,
     ] = map [
       'training_frame',
       'response_column',
       'ignored_columns',
       'monotone_constraints',
+      'balance_classes',
+      'class_sampling_factors',
+      'max_after_balance_size',
     ], _controlGroups.findControl
     columnControls = map columnParameterNames, _controlGroups.findControl

@@ -99,6 +105,10 @@
             populateColumns columns
       else
         populateColumns []
+
+    act balanceClasses.value, (enabled) ->
+      classSamplingFactors.isVisible enabled
+      maxAfterBalanceSize.isVisible enabled

   exception: _exception
   form: _form


Fix GBM/DRF model output visualizations

Fix GBM/DRF model output visualizations

--- src/ext/components/model-output.coffee
+++ src/ext/components/model-output.coffee
@@ -69,24 +69,24 @@
           )

     when 'gbm', 'drf'
-      if table = _.inspect 'scoring_history', _model
-        if table.schema['validation_mse']
+      if table = _.inspect 'output - Scoring History', _model
+        if table.schema['validation_MSE']
           renderPlot 'Scoring History', _.plot (g) ->
             g(
               g.path(
-                g.position 'number_of_trees', 'training_mse'
+                g.position 'number_of_trees', 'training_MSE'
                 g.strokeColor g.value '#1f77b4'
               )
               g.path(
-                g.position 'number_of_trees', 'validation_mse'
+                g.position 'number_of_trees', 'validation_MSE'
                 g.strokeColor g.value '#ff7f0e'
               )
               g.point(
-                g.position 'number_of_trees', 'training_mse'
+                g.position 'number_of_trees', 'training_MSE'
                 g.strokeColor g.value '#1f77b4'
               )
               g.point(
-                g.position 'number_of_trees', 'validation_mse'
+                g.position 'number_of_trees', 'validation_MSE'
                 g.strokeColor g.value '#ff7f0e'
               )
               g.from table
@@ -95,18 +95,30 @@
           renderPlot 'Scoring History', _.plot (g) ->
             g(
               g.path(
-                g.position 'number_of_trees', 'training_mse'
+                g.position 'number_of_trees', 'training_MSE'
                 g.strokeColor g.value '#1f77b4'
               )
               g.point(
-                g.position 'number_of_trees', 'training_mse'
+                g.position 'number_of_trees', 'training_MSE'
                 g.strokeColor g.value '#1f77b4'
               )
               g.from table
             )

+      if table = _.inspect 'output - training_metrics - Thresholds x Metric Scores', _model
+       renderPlot 'ROC Curve', _.plot (g) ->
+         g(
+           g.path g.position 'FPR', 'TPR'
+           g.line(
+             g.position (g.value 1), (g.value 0)
+             g.strokeColor g.value 'red'
+           )
+           g.from table
+           g.domainX_HACK 0, 1
+           g.domainY_HACK 0, 1
+         )

-      if table = _.inspect 'variable_importances', _model
+      if table = _.inspect 'output - Variable Importances', _model
         renderPlot 'Variable Importances', _.plot (g) ->
           g(
             g.rect(


added warning to CoffeeScript example

added warning to CoffeeScript example

--- examples/diskUsage2.coffee
+++ examples/diskUsage2.coffee
@@ -25,6 +25,7 @@

 fileFunnel = flows.funnel 20

+console.error("WARNING: this example is broken because of a bug in CoffeeScript 1.0.0 and 1.0.1 -- use CoffeeScript 0.9.6");
 du = (_, path) ->
 	total = 0
 	stat = fs.stat path, _


Added hover support.

Added hover support.

Now if you hover over points you get fairly useful information. Happily, it even works while the simulation is still running, which suggests that Rickshaw does a pretty good job of not dominating the CPU.

--- src/lib/rickshaw_strip_chart.coffee
+++ src/lib/rickshaw_strip_chart.coffee
@@ -6,6 +6,7 @@
   numYears: 100
   runNumber: 0
   chart: null
+  hoverDetail: null

   constructor: ->
     @values = []
@@ -21,6 +22,11 @@
       series: @values
       min: 'auto'
     })
+    @hoverDetail = new Rickshaw.Graph.HoverDetail({
+      graph: @chart
+      xFormatter: (year) -> "Year #{year}"
+      yFormatter: (numCranes) -> "#{Math.round(numCranes)} cranes"
+    })

   drawChart: ->
     @chart.render()
@@ -34,7 +40,10 @@
     if firstZero > -1
       newVals = newVals[..firstZero]
     newData = newVals.map (v, i) => { x: @year+i, y: v }
-    @values.push({color: "rgba(0, 0, 0, 0.1)", data: newData})
+    @values.push({
+      name: "Run ##{@runNumber}"
+      color: "rgba(0, 0, 0, 0.1)"
+      data: newData})

   tick: =>
     @extendData()


removed '??' from selihot for weeks that selihot begins/ends

--- js/vatikin.coffee
+++ js/vatikin.coffee
@@ -16,7 +16,7 @@
   updateDOM: ->
     $(".#{@day} .amidah").html(if @sunrise? then show_hours_minutes_seconds(@sunrise) else "??")
     for step in ['yishtabach', 'hodu', 'korbanot', 'selihot']
-      $(".#{@day} .#{step}").html(if @sunrise? && @schedule[step] then show_hours_minutes_seconds(@sunrise.subtract(@schedule[step], 'minutes')) else "??")
+      $(".#{@day} .#{step}").html(if @sunrise? && @schedule[step] then show_hours_minutes_seconds(@sunrise.subtract(@schedule[step], 'minutes')) else "")
     $(".#{@day} .selihot").removeClass('hidden') if @schedule.selihot

 (->


ClampBounds docs

ClampBounds docs

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -1,6 +1,6 @@
 ###*
 The ClampBounds module adds a check to make sure
-that the including GameObject
+that the including GameObject doe

 # Health regeneration


Print simple error message

Print simple error message

--- src/commands/run.coffee
+++ src/commands/run.coffee
@@ -90,24 +90,14 @@
       when ctx.OK then "\x1b[36m#{ctx.OK_MSG}\x1b[39m"
       when ctx.FAILED then "\x1b[36m#{ctx.FAILED_MSG}\x1b[39m"
       else "INVALID CODE"
-    # rl.write '\n'
     rl.write line
     rl.write '\n'
   .on 'end', ->
     rl.write "\x1b[32mInstallation is finished\x1b[39m\n"
     rl.close()
   .on 'error', (err) ->
-    print = (err) ->
-      rl.write '\n'
-      line = ''
-      # line += "#{pad ctx.config.host, hostlength+40}"
-      line += "\x1b[31m#{err.stack or err.message}\x1b[39m"
-      rl.write line
-    if err.errors
-      for error in err.errors
-        print error
-    else
-      print err
+    rl.write '\n'
+    rl.write "\x1b[31m#{err.stack or err.message}\x1b[39m"
     rl.close()


IDE: Add IDE and IDEBundle into projects.coffee.

--- projects.coffee
+++ projects.coffee
@@ -95,6 +95,12 @@
     script        : "website/a/js/__app.teamwork.js"
     sourceMapRoot : "Teamwork/"

+  IDE             :
+    path          : "client/IDE"
+    style         : "website/a/css/__app.ide.css"
+    script        : "website/a/js/__app.ide.js"
+    sourceMapRoot : "IDE/"
+
   About           :
     path          : "client/About"
     style         : "website/a/css/__app.about.css"
@@ -165,10 +171,10 @@
     style         : "website/a/css/koding.css"
     script        : "website/a/js/koding.js"

-  TeamworkBundle  :
-    projects      : ['Ace', 'Terminal', 'Viewer', 'Teamwork', 'DevTools']
-    style         : "website/a/css/__teamwork.css"
-    script        : "website/a/js/__teamwork.js"
+  IDEBundle       :
+    projects      : ['Ace', 'Terminal', 'Viewer', 'IDE']
+    style         : "website/a/css/__ide.css"
+    script        : "website/a/js/__ide.js"

   Payment         :
     projects      : ['Environments', 'Dashboard', 'Pricing', 'Account']


set loglevel of sample server

set loglevel of sample server

--- sample/server.coffee
+++ sample/server.coffee
@@ -16,6 +16,8 @@
 app = http.createServer(app_handler)
 io = require('socket.io').listen(app)
 linda = require('../').Linda.listen(io: io, server: app)
+io.configure 'development', ->
+  io.set 'log level', 2

 io.sockets.on 'connection', (socket) ->
   socket.once 'disconnect', ->


added phantomjs to default browser as running local unit tests

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -43,7 +43,7 @@
     # - Safari (only Mac)
     # - PhantomJS
     # - IE (only Windows)
-    browsers: ["Chrome"]
+    browsers: ['Chrome', 'PhantomJS']

     # Continuous Integration mode
     # if true, it capture browsers, run tests and exit


adds warning to karma.conf

adds warning to karma.conf

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -5,6 +5,7 @@
     files: [
       'public/vendor/zepto-1.1.4.js'
       'spec/**/*.coffee'
+      # adding public/site.js here breaks the correct loading order
     ]
     exclude: []
     preprocessors: {


Use PORT env var.

Use PORT env var.

--- prviewer.coffee
+++ prviewer.coffee
@@ -23,12 +23,12 @@

 argv = optimist
   .usage('Usage: $0 settings.json')
-  .options('-h', alias: 'host', default: 'localhost')
-  .options('-p', alias: 'port', default: 8000)
   .demand(1)
   .argv

 settings = JSON.parse fs.readFileSync argv._[0]
+
+port = process.env.PORT or 8000

 requireEnv = (name) ->
   value = process.env[name]
@@ -72,7 +72,7 @@
 app = express()

 app.configure ->
-  app.set 'port', argv.port
+  app.set 'port', port
   app.set 'views', "#{ __dirname }/views"
   app.set 'view engine', 'hjs'
   app.use express.favicon()
@@ -347,6 +347,6 @@
 # SERVER STARTUP
 # -------------------------------------------------------------------------

-http.createServer(app).listen argv.port, argv.host, ->
-  console.log "Pull Request Viewer listening on port http://#{ argv.host }:#{ argv.port }"
-
+http.createServer(app).listen port, ->
+  console.log "Pull Request Viewer listening on http://localhost:#{ argv.port }"
+


CartoDB getter stores indicator reference

--- getters/cartodb.coffee
+++ getters/cartodb.coffee
@@ -1,2 +1,3 @@
-
 module.exports = class CartoDBGetter
+  constructor: (indicator) ->
+    @indicator = indicator


init-nw.coffee: revised the code and added comments

--- init-nw.coffee
+++ init-nw.coffee
@@ -3,24 +3,25 @@
   gui = require 'nw.gui'; crypto = require 'crypto'; fs = require 'fs'; nomnom = require 'nomnom'
   path = require 'path'; {spawn} = require 'child_process'; proxy = require './proxy'

-  segmentsIntersect = (a, b, c, d) -> a < d && c < b
-
-  rectanglesIntersect = (r0, r1) -> (
-    segmentsIntersect(r0.x, r0.x + r0.width,  r1.x, r1.x + r1.width) &&
-    segmentsIntersect(r0.y, r0.y + r0.height, r1.y, r1.y + r1.height)
+  segmOverlap = (a, b, c, d) -> a < d && c < b # Do the two segments ab and cd overlap?
+
+  rectOverlap = (r0, r1) -> ( # A rectangle is {x,y,width,height}.  Do the two overlap?
+    segmOverlap(r0.x, r0.x + r0.width,  r1.x, r1.x + r1.width) &&
+    segmOverlap(r0.y, r0.y + r0.height, r1.y, r1.y + r1.height)
   )

-  segmentFitWithin = (a, b, A, B) ->
+  segmFit = (a, b, A, B) -> # Nudge and/or squeeze "ab" as necessary so it fits into "AB".
     if b - a > B - A then [A, B] else if a < A then [A, A + b - a] else if b > B then [B - b + a, B] else [a, b]

-  rectangleFitWithin = (r, R) ->
-    [x, x1] = segmentFitWithin r.x, r.x + r.width,  R.x, R.x + R.width
-    [y, y1] = segmentFitWithin r.y, r.y + r.height, R.y, R.y + R.height
+  rectFit = (r, R) -> # like segmFit but for for rectangles
+    [x, x1] = segmFit r.x, r.x + r.width,  R.x, R.x + R.width
+    [y, y1] = segmFit r.y, r.y + r.height, R.y, R.y + R.height
     {x, y, width: x1 - x, height: y1 - y}

   restoreWindow = (w, r) -> # w: NWJS window, r: rectangle
-    for scr in gui.Screen.screens when rectanglesIntersect scr.work_area, r
-      r = rectangleFitWithin r, scr.work_area
+    # Find a screen that overlaps with "r" and fit "w" inside it:
+    for scr in gui.Screen.screens when rectOverlap scr.work_area, r
+      r = rectFit r, scr.work_area
       w.moveTo r.x, r.y; w.resizeTo r.width, r.height
       process.nextTick ->
         w.dx = w.x      - r.x
@@ -38,7 +39,7 @@

   urlParams = {}
   for kv in (location + '').replace(/^[^\?]*($|\?)/, '').split '&'
-    [_, k, v] = /^([^=]*)=?(.*)$/.exec kv; urlParams[unescape(k or '')] = unescape(v or '')
+    [_, k, v] = /^([^=]*)=?(.*)$/.exec kv; urlParams[unescape k || ''] = unescape v || ''

   do -> # restore window state:
     if D.floating
@@ -58,7 +59,9 @@
     return
   nww.show(); nww.focus() # focus() is needed for the Mac

+  # To "throttle" a function is to make it execute no more often than once every X milliseconds.
   throttle = (f) -> tid = null; -> tid ?= setTimeout (-> f(); tid = null; return), 500; return
+
   saveWindowState = throttle ->
     posStr = JSON.stringify [
       nww.x      - (nww.dx || 0)
@@ -84,19 +87,19 @@
     return

   $ ->
-    contextMenu = null
+    cmenu = null # context menu on right-click, lazily initialized
     $ document
       .on 'keydown', '*', 'f12', -> nww.showDevTools(); false
       .on 'contextmenu', (e) ->
-        if !contextMenu
-          contextMenu = new gui.Menu
+        if !cmenu
+          cmenu = new gui.Menu
           ['Cut', 'Copy', 'Paste'].forEach (x) ->
-            contextMenu.append new gui.MenuItem label: x, click: (-> document.execCommand x; return); return
-        contextMenu.popup e.clientX, e.clientY
+            cmenu.append new gui.MenuItem label: x, click: (-> document.execCommand x; return); return
+        cmenu.popup e.clientX, e.clientY
         false
     return

-  D.readFile = fs.readFile
+  D.readFile = fs.readFile # needed for presentation mode

   # external editors (available only under nwjs)
   tmpDir = process.env.TMPDIR || process.env.TMP || process.env.TEMP || '/tmp'
@@ -118,7 +121,7 @@
       return

   D.createSocket = ->
-    class LocalSocket
+    class LocalSocket # imitate socket.io's API
       emit: (a...) -> @other.onevent data: a
       onevent: ({data}) -> (for f in @[data[0]] or [] then f data[1..]...); return
       on: (e, f) -> (@[e] ?= []).push f; @


Removed not-finished script

Removed not-finished script

--- scripts/grazi.coffee
+++ scripts/grazi.coffee
@@ -1,113 +0,0 @@
-# Description:
-#   Example scripts for you to examine and try out.
-#
-# Notes:
-#   They are commented out by default, because most of them are pretty silly and
-#   wouldn't be useful and amusing enough for day to day huboting.
-#   Uncomment the ones you want to try and experiment with.
-#
-#   These are from the scripting documentation: https://github.com/github/hubot/blob/master/docs/scripting.md
-
-fs = require('fs')
-filename = "/usr/share/dict/words"
-
-module.exports = (robot) ->
-
-  robot.hear /.*/, (msg) ->
-    fs.readFile(filename, (err, data) ->
-      throw err if (err)
-
-      # Buffer class!?
-      contents = data.toString().split('\n')
-
-      # Check each word
-      msg.split(' ').forEach (word) ->
-        if not wordList.includes(word)
-          console.log word + " is misspelled"
-    )
-    # msg.send
-
-  #
-  # lulz = ['lol', 'rofl', 'lmao']
-  #
-  # robot.respond /lulz/i, (msg) ->
-  #   msg.send msg.random lulz
-  #
-  # robot.topic (msg) ->
-  #   msg.send "#{msg.message.text}? That's a Paddlin'"
-  #
-  #
-
-  enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
-  leaveReplies = ['Are you still there?', 'Target lost', 'Searching']
-
-  robot.enter (msg) ->
-    msg.send msg.random enterReplies
-  robot.leave (msg) ->
-    msg.send msg.random leaveReplies
-
-  #
-  # answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
-  #
-  # robot.respond /what is the answer to the ultimate question of life/, (msg) ->
-  #   unless answer?
-  #     msg.send "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
-  #     return
-  #   msg.send "#{answer}, but what is the question?"
-  #
-  # robot.respond /you are a little slow/, (msg) ->
-  #   setTimeout () ->
-  #     msg.send "Who you calling 'slow'?"
-  #   , 60 * 1000
-  #
-  # annoyIntervalId = null
-  #
-  # robot.respond /annoy me/, (msg) ->
-  #   if annoyIntervalId
-  #     msg.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
-  #     return
-  #
-  #   msg.send "Hey, want to hear the most annoying sound in the world?"
-  #   annoyIntervalId = setInterval () ->
-  #     msg.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
-  #   , 1000
-  #
-  # robot.respond /unannoy me/, (msg) ->
-  #   if annoyIntervalId
-  #     msg.send "GUYS, GUYS, GUYS!"
-  #     clearInterval(annoyIntervalId)
-  #     annoyIntervalId = null
-  #   else
-  #     msg.send "Not annoying you right now, am I?"
-  #
-  #
-  # robot.router.post '/hubot/chatsecrets/:room', (req, res) ->
-  #   room   = req.params.room
-  #   data   = JSON.parse req.body.payload
-  #   secret = data.secret
-  #
-  #   robot.messageRoom room, "I have a secret: #{secret}"
-  #
-  #   res.send 'OK'
-  #
-  # robot.error (err, msg) ->
-  #   robot.logger.error "DOES NOT COMPUTE"
-  #
-  #   if msg?
-  #     msg.reply "DOES NOT COMPUTE"
-  #
-  # robot.respond /have a soda/i, (msg) ->
-  #   # Get number of sodas had (coerced to a number).
-  #   sodasHad = robot.brain.get('totalSodas') * 1 or 0
-  #
-  #   if sodasHad > 4
-  #     msg.reply "I'm too fizzy.."
-  #
-  #   else
-  #     msg.reply 'Sure!'
-  #
-  #     robot.brain.set 'totalSodas', sodasHad+1
-  #
-  # robot.respond /sleep it off/i, (msg) ->
-  #   robot.brain.set 'totalSodas', 0
-  #   robot.respond 'zzzzz'


debug config turns on debug logging

debug config turns on debug logging

--- lib/Server.coffee
+++ lib/Server.coffee
@@ -20,8 +20,9 @@

 module.exports = class Server
   constructor: ->
+    @config = @readConfig()
     winston.configure
-      level: 'verbose'
+      level: if @config.debug then 'debug' else 'verbose'
       format: winston.format.simple()
       transports: [
         new winston.transports.File(filename: 'jarvis.log'),
@@ -30,7 +31,6 @@
             winston.format.colorize(),
             winston.format.simple()))]
     @log 'info', 'Jarvis Home Automation server'
-    @config = @readConfig()
     if @config.debug then require('longjohn')
     @persistence = new Persistence()
     @events = new Events()


make adapters easier to override

make adapters easier to override

--- lib/Server.coffee
+++ lib/Server.coffee
@@ -4,22 +4,35 @@
 defaultAdapter =
   users: {}
   register: (req, cb) ->
-    defaultAdapter.users[req.name] = req.socket.id
+    @users[req.name] = req.socket.id
     cb()

   getId: (name, cb) ->
-    cb defaultAdapter.users[name]
+    cb @users[name]

   unregister: (req, cb) ->
-    delete defaultAdapter.users[req.name]
+    delete @users[req.name]
     cb()

   getPresenceTargets: (req, cb) ->
-    cb (id for user, id of defaultAdapter.users when user isnt req.name)
+    cb (id for user, id of @users when user isnt req.name)

 class Server extends EventEmitter
   constructor: (@httpServer, @options={}) ->
-    @adapter = @options.adapter or defaultAdapter
+    @adapter = {}
+    for k,v of defaultAdapter
+      if typeof v is "function"
+        @adapter[k]=v.bind @adapter
+      else
+        @adapter[k]=v
+
+    if @options.adapter
+      for k,v of @options.adapter
+        if typeof v is "function"
+          @adapter[k]=v.bind @adapter
+        else
+          @adapter[k]=v
+
     @options.path ?= "/holla"
     @options.destroyUpgrade ?= false
     @server = engineServer.attach @httpServer, @options


Test for allowed extensions by converting list to regexp and testing.

--- lib/action.coffee
+++ lib/action.coffee
@@ -22,13 +22,10 @@
     return;

   urlExtension = @pathinfo.path.extension
-  if urlExtension
+  if urlExtension && @meta._allowed_url_extensions
     aue= @meta._allowed_url_extensions
-    unless aue.set
-      s= {}
-      s['.'+key]=true for key in aue
-      aue.set= s
-    unless aue.set[urlExtension]
+    aue.regexp ?= new RegExp('((\.' + aue.join(')|(\.') + '))$')
+    unless aue.regexp.test(urlExtension)
       @res.statusCode = 415;
       @res.end('Unsupported Media Type');
       return;


Update lyriaData task to use variables and new naming scheme

--- grunt/lyriaData.coffee
+++ grunt/lyriaData.coffee
@@ -4,5 +4,5 @@
   all:
     options:
       name: 'i18n'
-    dest: 'build/debug/js/i18n.js',
-    src: ['assets/i18n/**/*.json']+    dest: '<%= buildDebug %>/js/i18n.js',
+    src: ['assets/i18n/**/*.json']


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/controllable.coffee
+++ src/controllable.coffee
@@ -21,7 +21,7 @@
   # => 13
 </pre></code>

-@name Cont
+@name Controllable
 @module
 @constructor
 @param {Object} I Instance variables


remove some debugging logging

remove some debugging logging

--- src/smc-webapp/redux_account.coffee
+++ src/smc-webapp/redux_account.coffee
@@ -243,10 +243,7 @@
     if autosave
         save_all_files = () ->
             if salvus_client.is_connected()
-                console.log 'saving all files'
                 redux.getActions('projects').save_all_files()
-            else
-                console.log 'not saving'
         _autosave_interval = setInterval(save_all_files, autosave * 1000)

 account_store = redux.getStore('account')


regex whitespace

regex whitespace

--- src/custom_types.coffee
+++ src/custom_types.coffee
@@ -4,23 +4,23 @@

 # need to use #{' '} b/c json-schema pattern also does not understand '\s'
 # coffeescript concatenates the whitespace before interpolation
-DATE_OR_DATETIME_REGEX = ///     #          req?
-  ^ [0-9]{4}                     # year      Y
-  ([-/.](0[1-9]|1[012])          # month     Y
-  ([-/.](0[1-9]|[12][0-9]|3[01]) # day       Y
-      ([Tt#{' '}]                # sep       Y, if TZ
-        ( #{hour}                # hour 00   N
+DATE_OR_DATETIME_REGEX = ///       #          req?
+  ^ [0-9]{4}                       # year      Y
+  ([-/.](0[1-9]|1[012])            # month     Y
+  ([-/.](0[1-9]|[12][0-9]|3[01])   # day       Y
+      ([Tt#{' '}]                  # sep       Y, if TZ
+        (#{hour}                   # hour 00   N
           (#{min_or_sec}           # min :00   N
             (#{min_or_sec}         # sec :00   N
               (\.[0-9][0-9][0-9])? # msec .000 N
             )?
           )?
-        )?                       #           -
-        (                        #           -
-          [zZ]                   # Zz        N
-          | ( [-+]               # -/+       Y, if TZ
-              #{hour}            # hour off  Y, if TZ
-              #{min_or_sec}?     # min off   N
+        )?                         #           -
+        (                          #           -
+          [zZ]                     # Zz        N
+          | ( [-+]                 # -/+       Y, if TZ
+              #{hour}              # hour off  Y, if TZ
+              #{min_or_sec}?       # min off   N
             )?
         )?
       )?


entity handling (for lt / gt / amp)

entity handling (for lt / gt / amp)

--- src/textorum/ckeditor/plugin.coffee
+++ src/textorum/ckeditor/plugin.coffee
@@ -27,7 +27,12 @@
     beforeInit = (editor) ->
       CKEDITOR.tools.extend editor.config, {
         enterMode: CKEDITOR.ENTER_DIV
-        fillEmptyBlocks: false
+        fillEmptyBlocks: false,
+        basicEntities: false,
+        entities_latin: false,
+        entities_greek: false,
+        entities: true,
+        entities_additional: 'gt,lt,amp'
         }, true

     onLoad = ->
@@ -72,7 +77,7 @@
       editor._.elementsPath?.filters?.push (element, name) ->
         # Hide elementsPath breadcrumbs for non-schema elements
         if name is 'body' or !element.getAttribute('data-xmlel')
-          return false
+          return "[" + name + "]"
         element.getAttribute('data-xmlel') or null

     CKEDITOR.plugins.add 'textorum', {


atom: disable vim-mode package

atom: disable vim-mode package

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -16,6 +16,7 @@
     disabledPackages: [
       "metrics"
       "select-rectangle"
+      "vim-mode"
     ]
     themes: [
       "atom-dark-ui-slim"


handle nil headers

handle nil headers

--- lib/logger.coffee
+++ lib/logger.coffee
@@ -5,7 +5,7 @@
   constructor: (@subject, @options={}) ->

   write: (subject, options={}) ->
-    message = ("#{key}=\"#{val.toString().replace('"', '\\"')}\"" for key, val of coffee.helpers.merge(subject:subject, options))
+    message = ("#{key}=\"#{(val || "").toString().replace('"', '\\"')}\"" for key, val of coffee.helpers.merge(subject:subject, options))
     console.log message.join(" ")

   log: (opts={}, cb) ->


Not display blank dataset

Not display blank dataset

--- projects/static/coffee/project.coffee
+++ projects/static/coffee/project.coffee
@@ -100,23 +100,24 @@
                     datasets = _.map _.pairs(data.violations[name].plots), (plotPair) =>
                         plotName = plotPair[0]
                         plot = plotPair[1]
-
                         preparedPlot = _.flatten [_.map(_.range(30), -> 0), [plot.reverse()]]
+                        preparedPlot = _.last preparedPlot, 30

                         color = colorer.getColor()
                         colorNames.push([plotName, color])

                         _.extend
-                            data: _.last preparedPlot, 30
+                            data: preparedPlot
                         , color

-                    view = new app.views.TrendChartView
-                        labels: _.map _.range(30), -> ''
-                        datasets: datasets
-                        name: name
-                        colorNames: colorNames
-                    view.render()
-                    view.$el.appendTo $('.js-charts-holder')
+                    if datasets.length
+                        view = new app.views.TrendChartView
+                            labels: _.map _.range(30), -> ''
+                            datasets: datasets
+                            name: name
+                            colorNames: colorNames
+                        view.render()
+                        view.$el.appendTo $('.js-charts-holder')
                     NProgress.inc()

                 NProgress.done()


fix(editable): removed unnecessary checks

--- src/lib/editable.coffee
+++ src/lib/editable.coffee
@@ -2,11 +2,7 @@
   editing_property = 'editing_' + property

   Observable self, property
-
-  if typeof self[property] != 'function'
-    console.error 'Editable field "' + property + '" is not a valid Observable'
-    return false
-
+
   # Editing states:
   # -1: error
   # 0: default
@@ -39,7 +35,7 @@
       , delay)

 EncryptedDelayedSave = (options, self) ->
-  (value, prop, delay = 250) =>
+  (value, prop, delay = 250) =>
     if typeof self[options.encrypted_container] != 'object' || self[options.encrypted_container] == null
       self[options.encrypted_container] = {}

@@ -62,4 +58,4 @@
         edit_value = {}
         edit_value[prop] = value
         self.update edit_value
-      , delay)+      , delay)


removing types / relations from schema

removing types / relations from schema

--- lib/schema.coffee
+++ lib/schema.coffee
@@ -7,7 +7,7 @@
 module.exports = class Schema
   constructor: (@model_type) ->

-    @fields ={}; @relations ={}; @ids_accessor = {}
+    @fields ={}; @ids_accessor = {}
     @_parse()

   initialize: ->


Added Smooth Panning

Added Smooth Panning

--- js/main.coffee
+++ js/main.coffee
@@ -14,9 +14,9 @@
   PARTICLE_COUNT = 100
   MIN_MASS = 1
   MAX_MASS = 10
-  PAN_SPEED = 10
+  PAN_SPEED = 1
   ROTATE_SPEED = 0.001
-  SCALE_SPEED = 0.001
+  ZOOM_SPEED = 0.001
   TIME_SCALE_SPEED = 1.1

   width = window.innerWidth
@@ -33,7 +33,6 @@

   $(window).bind 'wheel', (e) ->
     e.preventDefault()
-    console.log e.originalEvent.deltaZ
     offset = offset.add new Vector
       x: -e.originalEvent.deltaX
       y: -e.originalEvent.deltaY
@@ -49,15 +48,6 @@
       timeScale *= TIME_SCALE_SPEED
     if e.keyCode == 189
       timeScale /= TIME_SCALE_SPEED
-    if e.keyCode == 37 || e.keyCode == 65
-      offset = offset.add new Vector(PAN_SPEED, 0)
-    if e.keyCode == 38 || e.keyCode == 87
-      offset = offset.add new Vector(0, PAN_SPEED)
-    if e.keyCode == 39 || e.keyCode == 68
-      offset = offset.add new Vector(-PAN_SPEED, 0)
-    if e.keyCode == 40 || e.keyCode == 83
-      offset = offset.add new Vector(0, -PAN_SPEED)
-    rotation = ((rotation%(Math.PI*2))+(Math.PI*2))%(Math.PI*2)

   ctx = $('canvas')[0].getContext '2d'

@@ -74,8 +64,13 @@

     rotation += ROTATE_SPEED * deltaTime if Keyboard.isDown 69
     rotation -= ROTATE_SPEED * deltaTime if Keyboard.isDown 81
-    scale *= 1 + SCALE_SPEED * deltaTime if Keyboard.isDown 88
-    scale /= 1 + SCALE_SPEED * deltaTime if Keyboard.isDown 90
+    rotation = ((rotation%(Math.PI*2))+(Math.PI*2))%(Math.PI*2)
+    scale *= 1 + ZOOM_SPEED * deltaTime if Keyboard.isDown 88
+    scale /= 1 + ZOOM_SPEED * deltaTime if Keyboard.isDown 90
+    offset = offset.add new Vector(PAN_SPEED * deltaTime, 0) if Keyboard.isDown 65
+    offset = offset.add new Vector(0, PAN_SPEED * deltaTime) if Keyboard.isDown 87
+    offset = offset.add new Vector(-PAN_SPEED * deltaTime, 0) if Keyboard.isDown 68
+    offset = offset.add new Vector(0, -PAN_SPEED * deltaTime) if Keyboard.isDown 83

     ctx.clearRect 0, 0, width, height
     ctx.translate width/2, height/2


Allow arbitrary return codes from fail

Allow arbitrary return codes from fail

--- lib/mocha-phantomjs.coffee
+++ lib/mocha-phantomjs.coffee
@@ -34,9 +34,9 @@

   # Private

-  fail: (msg) ->
+  fail: (msg, errno) ->
     console.log msg if msg
-    phantom.exit 1
+    phantom.exit errno || 1

   finish: ->
     phantom.exit @page.evaluate -> mochaPhantomJS.failures


corrected a comment

corrected a comment

--- infinite-slider.coffee
+++ infinite-slider.coffee
@@ -46,7 +46,7 @@
   angular.module('gilbox.infiniteSlider', ['monospaced.mousewheel', 'gilbox.infiniteSlider.helpers'])

     # define a touch-region using infinite-slider-boundary
-    # must be a parent of the infinite-slider element.
+    # may be a parent, or child, of the infinite-slider element.
     .directive 'infiniteSliderBoundary', ->
       restrict: 'AE'
       scope: {} # necessary to isolate msd-wheel scope with multiple sliders


rewriter: no longer tries to include dynamic keys into braceless objects

--- rewriter.coffee
+++ rewriter.coffee
@@ -123,7 +123,7 @@
       {(i+1): one, (i+2): two, (i+3): three} = @tokens
       [tag] = token
       tag in ['TERMINATOR', 'OUTDENT'] and
-        not (two?[0] is ':' or one?[0] is '@' and three?[0] is ':' or one?[0] is '(') or
+        not (two?[0] is ':' or one?[0] is '@' and three?[0] is ':') or
       tag is ',' and one and
         one[0] not in ['IDENTIFIER', 'NUMBER', 'STRING', '@', 'TERMINATOR', 'OUTDENT', '(']
     action = (token, i) -> @tokens.splice i, 0, ['}', '}', token[2]]


sugar for audio#play

sugar for audio#play

--- src/models/Audio.coffee
+++ src/models/Audio.coffee
@@ -2,12 +2,12 @@
 class Audio

   play: (options, callbacks={})->
+    mediaPath = if options.constructor.name == "String"
+      options
+    else
+      options.absolutePath ? "#{Steroids.app.path}/#{options.path}"
+
     Steroids.on "ready", ()=>
-      if options.absolutePath
-        mediaPath = options.absolutePath
-      else
-        mediaPath = "#{Steroids.app.path}/#{options.path}"
-
       steroids.nativeBridge.nativeCall
         method: "play"
         parameters: {


Fix abstraction definition

Fix abstraction definition

--- library/nucleus/api.coffee
+++ library/nucleus/api.coffee
@@ -111,9 +111,9 @@

     # This is a marker that indicates to some internal substsems
     # that this class has to be considered abstract and therefore
-    # can not be treated as a complete service implementation. It
-    # mainly is used by the `Watcher` to distinguish the abstracts.
-    @ABSTRACT = this
+    # can not be treated as a complete class implementation. This
+    # mainly is used to exclude or account for abstract classes.
+    @abstract yes

     # A hook that will be called prior to invoking the API method
     # implementation. Please refer to this prototype signature for


remove unnecessary encapsulation

remove unnecessary encapsulation

--- src/pages/download.iced
+++ src/pages/download.iced
@@ -21,27 +21,20 @@
   await helpers.getTarget(config, argv, defer(err, target))
   if err? then return done(err)

-  await
+  await helpers.getShopPages(target, defer(err, pages))
+  if err? then return cb(err)

-    # PAGES
-    ((cb)->
+  for page in pages
+    key = "pages/#{page.handle}.html"
+    # Ignore paths configured in ignore file
+    if ignore and ignore.denies(key)
+      continue

-      await helpers.getShopPages(target, defer(err, pages))
-      if err? then return cb(err)
+    if not filter? or key.match(new RegExp("^#{filter}"))
+      await mkdirp(path.dirname(key), defer(err))
+      await fs.writeFile(key, page.body_html, defer(err))
+      console.log colors.green("Downloaded #{key}")

-      for page in pages
-        key = "pages/#{page.handle}.html"
-        # Ignore paths configured in ignore file
-        if ignore and ignore.denies(key)
-          continue
-
-        if not filter? or key.match(new RegExp("^#{filter}"))
-          await mkdirp(path.dirname(key), defer(err))
-          await fs.writeFile(key, page.body_html, defer(err))
-          console.log colors.green("Downloaded #{key}")
-
-      return cb(null)
-
-    )(defer(err))
+  return cb(null)

   done()


validation stuff

validation stuff

--- sockets/index.coffee
+++ sockets/index.coffee
@@ -1,5 +1,15 @@
 http = require 'http'
 querystring = require 'querystring'
+
+attackError =
+  attack: "window.location = 'https://www.google.com/search?q=dont+hack+me+bro'"
+
+niceError = 'Name Your Stack Correctly'
+
+rooms = [
+  'newStacks'
+  'topStacks'
+]

 sockets = (io, db) ->
   # Request Options
@@ -30,9 +40,9 @@
       req.end()

     socket.on 'share', (data) ->
-      error = 'Name Your Stack Correctly'
-      return socket.emit 'error', error unless data? and data.name?
-      return socket.emit 'error', error unless String(data.name).length < 30
+      return socket.emit 'error', attackError unless data? and data.name? and data.packages? and data.upVote? and data.dnVote? and data.score?
+      return socket.emit 'error', attackError unless data.upVote is 0 and data.dnVote is 0 and data.score is 0
+      return socket.emit 'error', niceError unless String(data.name).length < 30
       db.create data, (err, stack) ->
         upvotes[stack._id] = []
         dnvotes[stack._id] = []
@@ -41,9 +51,7 @@
           io.sockets.in('topStacks').emit 'topStack', stacks

     socket.on 'upvote', (id) ->
-      error =
-        attack: "window.location = 'https://www.google.com/search?q=dont+hack+me+bro'"
-      return socket.emit 'error', error if id?
+      return socket.emit 'error', attackError if id?
       unless socket.handshake.address in upvotes[id]
         upvotes[id].push socket.handshake.address
         db.findById id, (err, stack) ->
@@ -58,9 +66,7 @@
           msg: 'You Already Up-Voted This'

     socket.on 'dnvote', (id) ->
-      error =
-        attack: "window.location = 'https://www.google.com/search?q=dont+hack+me+bro'"
-      return socket.emit 'error', error if id?
+      return socket.emit 'error', attackError if id?
       unless socket.handshake.address in dnvotes[id]
         dnvotes[id].push socket.handshake.address
         db.findById id, (err, stack) ->
@@ -75,6 +81,7 @@
           msg: 'You Already Down-Voted This'

     socket.on 'join', (room) ->
+      return socket.emit 'error', attackError unless room in rooms
       console.log "joined room: #{room}"
       socket.join room
       db.findSort (err, stacks) ->


Fix defect from review

Fix defect from review

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -59,7 +59,6 @@
                     value = result
                 if typeof value._next is 'function' # if it's a cursor
                     @results = []
-                    @truncated = false
                     @cursor = value
                     @is_feed = @cursor.toString() == '[object Feed]'
                     @missing = 0
@@ -98,7 +97,6 @@
             delete @profile
             delete @value
             delete @results
-            delete @truncated
             @cursor?.close()
             delete @cursor

@@ -3613,7 +3611,7 @@
                     flatten_attr: flatten_attr

         tag_record: (doc, i) =>
-            doc.record = @metadata.skip_value + 1
+            doc.record = @metadata.skip_value + i

         render_result: (args) =>


Hide 'load more' when on the profiler view

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -3282,6 +3282,11 @@
                 no_results: @metadata.has_more_data isnt true and @results?.length is 0 and @metadata.skip_value is 0
                 num_results: num_results

+            if @view is 'profile'
+                @$('.more_results').hide()
+            else
+                @$('.more_results').show()
+
             switch @view
                 when 'profile'
                     if @profile is null


Check for ArrayResult AND Array before building an array of result

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -2862,7 +2862,9 @@
         # Build the table
         # We order by the most frequent keys then by alphabetic order
         json_to_table: (result) =>
-            if not (result.constructor? and result.constructor is ArrayResult)
+            # While an Array type is never returned by the driver, we still build an Array in the data explorer
+            # when a cursor is returned (since we just print @limit results)
+            if not result.constructor? or (result.constructor isnt ArrayResult and result.constructor isnt Array)
                 result = [result]

             keys_count =


Remove page load binding to avoid multiple Ajax submit.

--- app/assets/javascripts/check_ins.coffee
+++ app/assets/javascripts/check_ins.coffee
@@ -1,7 +1,7 @@
 # Place all the behaviors and hooks related to the matching controller here.
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
-$(document).on 'ready page:load turbolinks:load', ->
+$(document).on 'turbolinks:load', ->
   event_id = $('#network_event_id').val()
   member_level = $('#level').val()
   participation_type = $('#participation_type').val()


sankey: stack nodes

sankey: stack nodes

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -61,7 +61,15 @@
   Node: class extends Backbone.Model
     @width: 170
     @horizontal_spacing: 400
-    y_offset: => @get('row') * 100
+
+    y_offset: =>
+      offset = 0
+      margin = 35
+      for n in @siblings()
+        break if n == this
+        offset += n.height() + margin
+      offset
+
     x_offset: => @get('column') * D3.sankey.Node.horizontal_spacing + 20
     x_center: => @x_offset() + D3.sankey.Node.width / 2
     y_center: => @y_offset() + @value() / 2
@@ -73,12 +81,18 @@
       ])

     initialize: =>
+      @module = D3.sankey
       if @get('gquery')
         @gquery = new Gquery({key: @get('gquery')})
       @right_links = []
       @left_links = []

     height: => @value()
+
+    siblings: =>
+      items = _.groupBy(@module.nodes.models, (node) -> node.get('column'))
+      items[@get 'column']
+

   Link: class extends Backbone.Model
     initialize: =>
@@ -129,7 +143,7 @@

     value: =>
       if @gquery
-        @gquery.get('future_value') / 10
+        @gquery.get('future_value') / 8
       else
         10

@@ -202,7 +216,8 @@
     refresh: =>
       @nodes.data(@module.nodes.models, (d) -> d.get('id')).
         transition().duration(500).
-        attr("height", (d) => @y d.height())
+        attr("height", (d) => @y d.height()).
+        attr("y", (d) => @y d.y_offset())

       @labels.data(@module.nodes.models, (d) -> d.get('id')).
         transition().duration(500).


when a message is marked as read locally, the message is mark as read remotely

--- app/controllers/mails_controller.coffee
+++ app/controllers/mails_controller.coffee
@@ -10,11 +10,11 @@

 # shared functionnality : find the mail via its ID
 before ->
-    Mail.find req.params.id, (err, box) =>
-        if err or !box
+    Mail.find req.params.id, (err, mail) =>
+        if err or not mail
             send 404
         else
-            @box = box
+            @mail = mail
             next()
 , { only: ['show', 'update', 'destroy', 'getattachmentslist'] }

@@ -27,21 +27,36 @@

 # GET /mails/:id
 action 'show', ->
-    send @box
+    send @mail

 # PUT /mails/:id
+# Modify given mail. If mail read flag is changed, the remote mail version is
+# updated too.
 action 'update', ->
-    @box.updateAttributes body, (err) =>
+    markRead = false
+    if body.read and not @mail.read
+        markRead = true
+
+    @mail.updateAttributes body, (err) =>
         if err
             send 500
         else
-            send 200
+            if markRead
+                Mailbox.find @mail.mailbox, (err, mailbox) =>
+                    if err or not mailbox?
+                        send error: "unknown mailbox can't update read flag", 500
+                    else
+                        mailbox.markMailAsRead @mail, (err) ->
+                            if err
+                                send error: "can't update read flag", 500
+                            else
+                                send 200

 # DELETE /mails/:id
 action 'destroy', ->
-    @box.destroy (err) =>
+    @mail.destroy (err) =>
         if err
             send 500
         else
@@ -119,7 +134,7 @@
 # GET '/getattachments/:id
 action 'getattachmentslist', ->
     query =
-        key: @box.id
+        key: @mail.id

     Attachment.fromMail query, (err, attachments) ->
         if err


LO-1007 hubot pager maintenance <minutes> <service_id1> <service_id2>

--- src/scripts/part.coffee
+++ src/scripts/part.coffee
@@ -19,9 +19,9 @@
       renderSchedule = (s, cb) ->
         withCurrentOncallId msg, s, (oncallUserid, oncallUsername, schedule) ->
           if userId == oncallUserid
-            cb null, "* Yes, you are on call for #{schedule.name} - https://#{pagerduty.subdomain}.pagerduty.com/schedules##{schedule.id}"
+            cb null, "* Yes, you are on call for #{schedule.name} - #{schedule.html_url}"
           else
-            cb null, "* No, you are NOT on call for #{schedule.name} (but #{oncallUsername} is)- https://#{pagerduty.subdomain}.pagerduty.com/schedules##{schedule.id}"
+            cb null, "* No, you are NOT on call for #{schedule.name} (but #{oncallUsername} is)- #{schedule.html_url}"

       if !userId?
         msg.send "Couldn't figure out the pagerduty user connected to your account."
@@ -108,15 +108,15 @@
       start_time = moment().format()
       end_time = moment().add('minutes', minutes).format()

-      maintenance_window = {
-        'start_time': start_time,
-        'end_time': end_time,
-        'service_ids': service_ids
-      }
-      data = { 'maintenance_window': maintenance_window, 'requester_id': requester_id }
+      services = []
+      for service_id in service_ids
+        services.push id: service_id, type: 'service_reference'
+
+      maintenance_window = { start_time, end_time, services }
+      data = { maintenance_window, services }

       msg.send "Opening maintenance window for: #{service_ids}"
-      pagerduty.post "/maintenance_windows", data, (err, json) ->
+      pagerduty.post '/maintenance_windows', data, (err, json) ->
         if err?
           robot.emit 'error', err, msg
           return
@@ -213,7 +213,7 @@
         msg.send "Sorry, I can't figure out #{possessive} email address :( Can #{addressee} tell me with `#{robot.name} pager me as you@yourdomain.com`?"
         return

-    pagerduty.get "/users", {query: email}, (err, json) ->
+    pagerduty.get "/users", { query: email }, (err, json) ->
       if err?
         robot.emit 'error', err, msg
         return


sudo no longer captures space following command

The regular expression used here used to capture the space
separating sudo from the rest of the command. When interpolated,
this resulted in a double space like:

  Alright, I'll  make you a sandwich.

--- src/scripts/sudo.coffee
+++ src/scripts/sudo.coffee
@@ -14,5 +14,5 @@
 #   searls

 module.exports = (robot) ->
-  robot.respond /(sudo)(.*)/i, (msg) ->
-    msg.send "Alright. I'll #{msg.match?[2] || "do whatever it is you wanted."}"
+  robot.respond /(?:sudo) ?(.*)/i, (msg) ->
+    msg.send "Alright. I'll #{msg.match?[1] || "do whatever it is you wanted."}"


Color back + left/right arrow added

Color back + left/right arrow added

--- site/app.coffee
+++ site/app.coffee
@@ -461,7 +461,7 @@
       datestr = "(" + date2String(date)
       date.setDate(date.getDate() + 6)
       datestr = datestr + "-" + date2String(date) + ")"
-      @dataTimeline.html("Nowcasting epi-week " + epiweek2%100 + " " + datestr)
+      @dataTimeline.html("")
       @currentEpweek = epiweek2
       callback1 = (epidata) =>
         NonInfluenzaData = calculateNonInfluenzaData(epidata, @nonInfluenzaWeekSeason)
@@ -477,6 +477,7 @@
               @colors[row.location] = '#' + c + '4040'
               @mapData[row.location] = row
           @renderMap()
+          @dataTimeline.html("Nowcasting epi-week " + epiweek2%100 + " " + datestr)
         handler = getEpidataHander(callback2)
         Epidata_nowcast_multi(handler, LOCATIONS, epiweek1, epiweek2)
       handler = getEpidataHander(callback1)


If there is no file associated with the buffer, then do not try to load project settings.

--- lib/project-checker.coffee
+++ lib/project-checker.coffee
@@ -136,6 +136,10 @@
       { regex: r, text: input, flags: f }

   getProject: (buffer) ->
+    # If there is no file, we can't find a project.
+    if not buffer?.file?.path
+      return { valid: false, json: null }
+
     # First see if we have the item already cached. If we do, then just use that.
     [projectPath, relativePath] = atom.project.relativizePath(buffer.file.path)
     if @projects.hasOwnProperty projectPath


Perf: File only reanalyzed at saving if spec

--- lib/rspec-tree-view.coffee
+++ lib/rspec-tree-view.coffee
@@ -4,6 +4,7 @@

 PluginState = require './plugin-state'
 RSpecTestDetails = require './rspec-test-details'
+RailsRSpecFinder = require './rails-rspec-finder'

 module.exports =
 class RSpecTreeView extends View
@@ -35,6 +36,8 @@
   initialize:  ->
     @currentState = new PluginState

+    @railsRSpecFinder = new RailsRSpecFinder
+
     @currentState.onTreeBuilt (result) =>
       @setStdErrorNotification(result)
       @redrawTree(result.asTree, result)
@@ -174,7 +177,8 @@

     if currentBuffer?
       @currentEditorSubscriptions.add currentBuffer.onDidSave =>
-        @setCurrentAndCorrespondingFile(editor)
+        if @railsRSpecFinder.isSpec(editor.getBuffer().file.path)
+          @setCurrentAndCorrespondingFile(editor)

     if editor?
       @setCurrentAndCorrespondingFile(editor)


Override any core movement listeners by binding to the object as the first listener

--- lib/suggestion-list.coffee
+++ lib/suggestion-list.coffee
@@ -13,7 +13,6 @@
       'autocomplete-plus:confirm': @confirmSelection,
       'autocomplete-plus:confirmIfNonDefault': @confirmSelectionIfNonDefault,
       'autocomplete-plus:cancel': @cancel
-    @subscriptions.add atom.config.observe 'autocomplete-plus.useCoreMovementCommands', => @bindToMovementCommands()
     @subscriptions.add(atom.config.observe('autocomplete-plus.enableExtendedUnicodeSupport', (enableExtendedUnicodeSupport) =>
       if enableExtendedUnicodeSupport
         @wordPrefixRegex = new RegExp("^[#{UnicodeLetters}\\d_-]")
@@ -21,42 +20,10 @@
         @wordPrefixRegex = /^[\w-]/
     ))

-  bindToMovementCommands: ->
-    useCoreMovementCommands = atom.config.get('autocomplete-plus.useCoreMovementCommands')
-    commandNamespace = if useCoreMovementCommands then 'core' else 'autocomplete-plus'
-
-    commands = {}
-    commands["#{commandNamespace}:move-up"] = (event) =>
-      if @isActive() and @items?.length > 1
-        @selectPrevious()
-        event.stopImmediatePropagation()
-    commands["#{commandNamespace}:move-down"] = (event) =>
-      if @isActive() and @items?.length > 1
-        @selectNext()
-        event.stopImmediatePropagation()
-    commands["#{commandNamespace}:page-up"] = (event) =>
-      if @isActive() and @items?.length > 1
-        @selectPageUp()
-        event.stopImmediatePropagation()
-    commands["#{commandNamespace}:page-down"] = (event) =>
-      if @isActive() and @items?.length > 1
-        @selectPageDown()
-        event.stopImmediatePropagation()
-    commands["#{commandNamespace}:move-to-top"] = (event) =>
-      if @isActive() and @items?.length > 1
-        @selectTop()
-        event.stopImmediatePropagation()
-    commands["#{commandNamespace}:move-to-bottom"] = (event) =>
-      if @isActive() and @items?.length > 1
-        @selectBottom()
-        event.stopImmediatePropagation()
-
-    @movementCommandSubscriptions?.dispose()
-    @movementCommandSubscriptions = new CompositeDisposable
-    @movementCommandSubscriptions.add atom.commands.add('atom-text-editor.autocomplete-active', commands)
-
-  addKeyboardInteraction: ->
-    @removeKeyboardInteraction()
+  addBindings: (editor) ->
+    @bindings?.dispose()
+    @bindings = new CompositeDisposable
+
     completionKey = atom.config.get('autocomplete-plus.confirmCompletion') or ''

     keys = {}
@@ -67,13 +34,45 @@
       else
         keys['enter'] = 'autocomplete-plus:confirm'

-    @keymaps = atom.keymaps.add('atom-text-editor.autocomplete-active', {'atom-text-editor.autocomplete-active': keys})
-    @subscriptions.add(@keymaps)
-
-  removeKeyboardInteraction: ->
-    @keymaps?.dispose()
-    @keymaps = null
-    @subscriptions.remove(@keymaps)
+    @bindings.add atom.keymaps.add(
+      'atom-text-editor.autocomplete-active',
+      {'atom-text-editor.autocomplete-active': keys})
+
+    useCoreMovementCommands = atom.config.get('autocomplete-plus.useCoreMovementCommands')
+    commandNamespace = if useCoreMovementCommands then 'core' else 'autocomplete-plus'
+
+    commands = {}
+    commands["#{commandNamespace}:move-up"] = (event) =>
+      if @isActive() and @items?.length > 1
+        @selectPrevious()
+        event.stopImmediatePropagation()
+    commands["#{commandNamespace}:move-down"] = (event) =>
+      if @isActive() and @items?.length > 1
+        @selectNext()
+        event.stopImmediatePropagation()
+    commands["#{commandNamespace}:page-up"] = (event) =>
+      if @isActive() and @items?.length > 1
+        @selectPageUp()
+        event.stopImmediatePropagation()
+    commands["#{commandNamespace}:page-down"] = (event) =>
+      if @isActive() and @items?.length > 1
+        @selectPageDown()
+        event.stopImmediatePropagation()
+    commands["#{commandNamespace}:move-to-top"] = (event) =>
+      if @isActive() and @items?.length > 1
+        @selectTop()
+        event.stopImmediatePropagation()
+    commands["#{commandNamespace}:move-to-bottom"] = (event) =>
+      if @isActive() and @items?.length > 1
+        @selectBottom()
+        event.stopImmediatePropagation()
+
+    @bindings.add atom.commands.add(
+      atom.views.getView(editor), commands)
+
+    @bindings.add(
+      atom.config.onDidChange 'autocomplete-plus.useCoreMovementCommands', =>
+        @addBindings(editor))

   ###
   Section: Event Triggers
@@ -181,7 +180,7 @@
       @displayBufferPosition = bufferPosition
       marker = @suggestionMarker = editor.markBufferRange([bufferPosition, bufferPosition])
       @overlayDecoration = editor.decorateMarker(marker, {type: 'overlay', item: this, position: 'tail'})
-      @addKeyboardInteraction()
+      @addBindings(editor)

   showAtCursorPosition: (editor) =>
     return if @activeEditor is editor or not editor?
@@ -190,12 +189,12 @@
     if marker = editor.getLastCursor()?.getMarker()
       @activeEditor = editor
       @overlayDecoration = editor.decorateMarker(marker, {type: 'overlay', item: this})
-      @addKeyboardInteraction()
+      @addBindings(editor)

   hide: =>
     return if @activeEditor is null
     @destroyOverlay()
-    @removeKeyboardInteraction()
+    @bindings?.dispose()
     @activeEditor = null

   destroyOverlay: =>
@@ -212,6 +211,6 @@
   # Public: Clean up, stop listening to events
   dispose: ->
     @subscriptions.dispose()
-    @movementCommandSubscriptions?.dispose()
+    @bindings?.dispose()
     @emitter.emit('did-dispose')
     @emitter.dispose()


Small regex format update

Small regex format update

--- lib/table-formatter.coffee
+++ lib/table-formatter.coffee
@@ -154,7 +154,8 @@
         \|\ *:?-+:?\ *            # format starting w/pipe
         |\|?(?:\ *:?-+:?\ *\|)+   # or separated by pipe
       )
-      (?:\ *:?-+:?\ *)?\ *        # maybe w/o trailing pipe
+      (?:\ *:?-+:?\ *)?           # maybe w/o trailing pipe
+      \ *                         # maybe trailing whitespace
       \r?\n                       # newline
     )
     ( # body capture


Stop consuming CMD+1 on MacOS

Stop consuming CMD+1 on MacOS

--- keymaps/atom-dart.cson
+++ keymaps/atom-dart.cson
@@ -21,8 +21,8 @@
   'ctrl-shift-O': 'dartlang:organize-directives'

 # Dart editor commands (mac)
+# TODO: Add default keybinding for dartlang:quick-fix on MacOS.
 'body.platform-darwin atom-text-editor[data-grammar~="dart"]:not([mini])':
-  'cmd-1': 'dartlang:quick-fix'
   'cmd-alt-b': 'dartlang:dart-format'
   'shift-cmd-g': 'dartlang:find-references'


keymap disable

keymap disable

--- keymaps/transform.cson
+++ keymaps/transform.cson
@@ -1,3 +0,0 @@
-# 'atom-text-editor':
-#   'cmd-i':       'transformer:run'
-#   'shift-cmd-i': 'transformer:compile'


Add pt translation to d3 and c3 renderers

--- pivot.pt.coffee
+++ pivot.pt.coffee
@@ -12,6 +12,8 @@
     tpl = $.pivotUtilities.aggregatorTemplates
     r = $.pivotUtilities.renderers
     gcr = $.pivotUtilities.gchart_renderers
+    d3r = $.pivotUtilities.d3_renderers
+    c3r = $.pivotUtilities.c3_renderers

     frFmt =    nf(thousandsSep: ".", decimalSep: ",")
     frFmtInt = nf(digitsAfterDecimal: 0, thousandsSep: ".", decimalSep: ",")
@@ -64,4 +66,13 @@
             "Gráfico de Barras Empilhadas": gcr["Stacked Bar Chart"]
             "Gráfico de Área":       gcr["Area Chart"]

+    if d3r
+        $.pivotUtilities.locales.pt.d3_renderers =
+            "Mapa de Árvore": d3r["Treemap"]
+
+    if c3r
+      $.pivotUtilities.locales.pt.c3_renderers =
+        "Gráfico de Linhas": c3r["Line Chart C3"]
+        "Gráfico de Barras": c3r["Bar Chart C3"]
+
     return $.pivotUtilities.locales.pt


actions/app: Drop resin-sdk-preconfigured

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

--- lib/actions/app.coffee
+++ lib/actions/app.coffee
@@ -44,7 +44,8 @@
 	]
 	permission: 'user'
 	action: (params, options, done) ->
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
+
 		patterns = require('../utils/patterns')

 		# Validate the the application name is available
@@ -57,7 +58,10 @@
 		.then ->
 			return options.type or patterns.selectDeviceType()
 		.then (deviceType) ->
-			return resin.models.application.create(params.name, deviceType)
+			return resin.models.application.create({
+				name: params.name
+				deviceType
+			})
 		.then (application) ->
 			console.info("Application created: #{application.app_name} (#{application.device_type}, id #{application.id})")
 		.nodeify(done)
@@ -78,7 +82,7 @@
 	permission: 'user'
 	primary: true
 	action: (params, options, done) ->
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		visuals = require('resin-cli-visuals')

 		resin.models.application.getAll().then (applications) ->
@@ -104,7 +108,7 @@
 	permission: 'user'
 	primary: true
 	action: (params, options, done) ->
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		visuals = require('resin-cli-visuals')

 		resin.models.application.get(params.name).then (application) ->
@@ -129,7 +133,7 @@
 	'''
 	permission: 'user'
 	action: (params, options, done) ->
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		resin.models.application.restart(params.name).nodeify(done)

 exports.remove =
@@ -149,7 +153,7 @@
 	options: [ commandOptions.yes ]
 	permission: 'user'
 	action: (params, options, done) ->
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		patterns = require('../utils/patterns')

 		patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then ->


removed checks for caboose default initializers

--- lib/application.coffee
+++ lib/application.coffee
@@ -10,6 +10,10 @@

   post_boot: (method) ->
     @_post_boot.push method
+
+  # read_config_files: (callback) ->
+  #   for file in Caboose.path.config.readdir_sync()
+  #

   configure: (callback) ->
     @config = {}
@@ -41,9 +45,7 @@
     next()

   run_initializers: (callback) ->
-    @run_initializers_in_path new Path(__dirname).join('initializers'), (err) =>
-      return callback(err) if err?
-      @run_initializers_in_path Caboose.path.config.join('initializers'), callback
+    @run_initializers_in_path Caboose.path.config.join('initializers'), callback

   initialize: (callback) ->
     return callback() if @_state.initialized


- fix internal link - avoid coffeescript complaints about line wrapping in comments

--- lib/casper-chai.coffee
+++ lib/casper-chai.coffee
@@ -188,7 +188,7 @@
     Compare the remote evaluation to the given expression, and return
     true when they match. The expression can be a string or a regular
     expression. The evaluation is the same as for
-    [`trueOnRemote`](#trueOnRemote).
+    [`trueOnRemote`](#trueonremote).

     ```javascript
     expect("return 123").to.matchOnRemote(123)<br/>
@@ -308,8 +308,8 @@

     expect("return false").to.not.be.trueOnRemote;

-    (function () { return typeof jQuery == typeof void 0
-    }).should.be.trueOnRemote;
+    var foo = function () { return typeof jQuery == typeof void 0; )
+    foo.should.be.trueOnRemote;

     expect("function () { return 1 == 0 }").to.not.be.trueOnRemote;
     ```


fixed missing @ in model.trigger('ajaxSuccess'....

--- src/ajax.coffee
+++ src/ajax.coffee
@@ -69,7 +69,7 @@
     success = params.success

     (data, status, xhr) =>
-      model.trigger("ajaxSuccess", null, status, xhr)
+      @model.trigger("ajaxSuccess", null, status, xhr)
       success?(@model.fromJSON(data))

   errorResponse: (params = {}) =>


test(defining): create tests for defining relations

--- test/backbone-relatable-test.coffee
+++ test/backbone-relatable-test.coffee
@@ -12,19 +12,44 @@
     it 'should be defined', ->
       expect(Backbone.RelatableModel).to.exist

-    describe 'Relations', ->
+    describe 'Defining Relations', ->
+      class TestModel2 extends Backbone.RelatableModel
+      class TestCollection extends Backbone.Collection
+
+      class TestModel extends Backbone.RelatableModel
+        @relation 'something', type: 'one', inverse: 'test', class: TestModel2
+        @relation 'test2', type: 'many', inverse: 'test', class: TestCollection
+
       model = null

       beforeEach ->
-        class TestModel extends Backbone.RelatableModel
-
         model = new TestModel()

-      it 'should exist', ->
+      it 'should be possible', ->
         expect(model.relations).to.exist

+      it 'should throw an error if no parameters are given', ->
+        expect(->
+          Backbone.RelatableModel.relation()
+        ).to.throw Error

+      it 'should throw an error if no options are given', ->
+        expect(->
+          Backbone.RelatableModel.relation 'name'
+        ).to.throw Error

+      it 'should throw an error if invalid options are given', ->
+        expect(->
+          Backbone.RelatableModel.relation 'test', type: 'invalid'
+        ).to.throw Error
+
+      it 'should throw an error if an invalid model is given', ->
+        expect(->
+          Backbone.RelatableModel.relation 'test2', type: 'one', inverse: 'test', class: undefined
+        ).to.throw Error
+
+      it 'should populate the relations object', ->
+        expect(model.relations['something']).to.exist

   # START THE TESTS


split cmp op associativity

split cmp op associativity

--- grammar.coffee
+++ grammar.coffee
@@ -55,7 +55,10 @@
 q('bin_op',  '+|-')                                     .mx('priority=6  right_assoc=1')
 q('bin_op',  '<<|>>|>>>')                               .mx('priority=7  right_assoc=1')
 q('bin_op',  'instanceof')                              .mx('priority=8  right_assoc=1')
-q('bin_op',  '<|<=|>|>=|!=|==')                         .mx('priority=9  right_assoc=1') # NOTE == <= has same priority
+q('bin_op',  '<|<=|>|>=')                               .mx('priority=9')                 # NOTE NOT associative, because chained comparison
+q('bin_op',  '!=|==')                                   .mx('priority=9  right_assoc=1') # NOTE == <= has same priority
+# WARNING a == b < c is bad style. So all fuckups are yours
+
 q('bin_op',  '&&|and|or|[PIPE][PIPE]')                  .mx('priority=10 right_assoc=1')

 q('assign_bin_op',  '=|+=|-=|*=|/=|%=|<<=|>>=|>>>=|**=|//=|%%=|[QUESTION]=').mx('priority=3')


Rudimentary first support for `cabal new-build`

This provides a new builder `cabal-nix` (as a third choice to `cabal`,
`stack`). Currently the only command support is `build` (which corresponds to
`cabal new-build`).

--- lib/ide-backend.coffee
+++ lib/ide-backend.coffee
@@ -84,6 +84,19 @@
           when 'clean'
             cabalArgs.push '--save-configure'
         cabalArgs.push '--builddir=' + buildDir
+        cabalArgs.push target if target? and cmd is 'build'
+        CabalProcess ?= require './cabal-process'
+        cabalProcess = new CabalProcess 'cabal', cabalArgs, @spawnOpts(cabalRoot), opts
+      else if @buildBuilder is 'cabal-nix'
+        # TODOs:
+        #   * Commands other than 'build'
+        #   * Support for buildDir
+        if cmd is 'build'
+          cabalArgs = ['new-build']
+        else
+          throw new Error("Unsupported command '#{cmd}'")
+
+        target = opts.target.target
         cabalArgs.push target if target? and cmd is 'build'
         CabalProcess ?= require './cabal-process'
         cabalProcess = new CabalProcess 'cabal', cabalArgs, @spawnOpts(cabalRoot), opts
@@ -254,7 +267,7 @@
   setBuilder: ({onComplete}) ->
     BuilderListView ?= require './views/builder-list-view'

-    builders = [{name: 'cabal'}, {name: 'stack'}]
+    builders = [{name: 'cabal'}, {name: 'stack'}, {name: 'cabal-nix'}]

     new BuilderListView
       items: builders