Local config; assets-static; livereload; css assets; bugfixes

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -2,8 +2,8 @@
 del           = require 'del'
 gulp          = require 'gulp'
 autoprefixer  = require 'gulp-autoprefixer'
+bourbon       = require 'node-bourbon'
 base64        = require 'gulp-base64'
-bourbon       = require 'node-bourbon'
 coffee        = require 'gulp-coffee'
 concat        = require 'gulp-concat'
 frontmatter   = require 'gulp-front-matter'
@@ -19,11 +19,20 @@
 swig          = require 'swig'
 through       = require 'through2'
 yaml          = require 'yamljs'
+objectMerge   = require 'object-merge'
 {argv}        = require 'yargs'

 # Load configuration
-config = yaml.load 'config.yml'
+configSite = yaml.load 'config.site.yml'
+
+try
+  configLocal = yaml.load 'config.local.yml'
+catch
+  configLocal = {}
+
+config = objectMerge configSite, configLocal
+

 # Dev environment by default
 if !argv.production
@@ -32,7 +41,7 @@

 # Tasks
 gulp.task 'css', ->
-  sassSettings =
+  sassSettings =
     includePaths: bourbon.includePaths

   base64Settings =
@@ -60,11 +69,11 @@
     .pipe gulpif !argv.dev, do minifyCSS
     .pipe gulp.dest(config.destination + '/css')

-  gulp.src 'css/images/**/**'
-    .pipe gulp.dest(config.destination + '/css/images')
+  gulp.src 'css/assets/images/**/**'
+    .pipe gulp.dest(config.destination + '/css/assets/images')

-  gulp.src 'css/fonts/**/**'
-    .pipe gulp.dest(config.destination + '/css/fonts')
+  gulp.src 'css/assets/fonts/**/**'
+    .pipe gulp.dest(config.destination + '/css/assets/fonts')

 gulp.task 'js', ->
@@ -91,8 +100,13 @@

 gulp.task 'assets', ->
+  gulp.src 'assets/**/**', dot: true
+  .pipe gulp.dest(config.destination)
+
+
+gulp.task 'assets-static', ->
   gulp.src [
-    'assets/**/**'
+    'assets-static/**/**'
   ]
   .pipe gulp.dest(config.destination)

@@ -111,17 +125,14 @@
     remove: true
   .pipe do ->
     through.obj (file, enc, cb) ->
-      layout = file.page.layout
+      swig.setDefaults { autoescape: false, cache: false }
+      layout = file.page.layout || 'default'
+      template = swig.compileFile path.join('views', '_layouts', layout + '.html')
       data =
         site: siteData
         page: file.page
-      content = swig.render file.contents.toString(), { locals: data }
-      if layout
-        template = swig.compileFile path.join('views', '_layouts', layout + '.html')
-        data['content'] = content
-        file.contents = new Buffer template(data), 'utf8'
-      else
-        file.contents = new Buffer content, 'utf8'
+      data['content'] = swig.render file.contents.toString(), { locals: data }
+      file.contents = new Buffer template(data), 'utf8'
       @push file
       do cb
   .pipe rename (path) ->
@@ -138,14 +149,14 @@
   console.log 'Watching for changes...'
   gulp.watch 'css/**/**', ['css']
   gulp.watch 'js/**/**', ['js']
-  gulp.watch 'assets/**/**', ['assets']
-  gulp.watch 'views/**/**', ['views']
+  gulp.watch 'views/**/**', ['views']

 gulp.task 'webserver', ->
   port = config.server_port || 8000
   gulp.src config.destination
     .pipe webserver
+      livereload: config.livereload || true
       port: port

@@ -160,9 +171,7 @@
     console.log 'Generating production build'

   del config.destination, ->
-    gulp.start ['css', 'js', 'assets', 'views']
+    gulp.start ['css', 'js', 'assets', 'assets-static', 'views']

-gulp.task 'serve', ['build'], ->
-  gulp.start 'webserver'
-  gulp.start 'watch'
+gulp.task 'serve', ['webserver', 'watch']


Fix CI errors

Fix CI errors

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -23,6 +23,8 @@
         .once 'end', ->
           cb(mochaErr)

+  undefined
+
 gulp.task 'jshint', ->
   gulp.src ['*.js', 'lib/**/*.js', 'test/**/*.js']
     .pipe jshint()


Use parcel without anything.

Use parcel without anything.

--- src/spec/integration/integration-order.spec.coffee
+++ src/spec/integration/integration-order.spec.coffee
@@ -193,18 +193,7 @@
         orderNew3 = JSON.parse(JSON.stringify(orderUpdated2))

         # add a parcel item
-        orderNew3.shippingInfo.deliveries[0].parcels.push
-          measurements:
-            heightInMillimeter: 200
-            lengthInMillimeter: 200
-            widthInMillimeter: 200
-            weightInGram: 200
-          trackingData:
-            trackingId: '1Z6185W16894827591'
-            carrier: 'UPS'
-            provider: 'shipcloud.io'
-            providerTransaction: '549796981774cd802e9636ded5608bfa1ecce9ad'
-            isReturn: true
+        orderNew3.shippingInfo.deliveries[0].parcels.push {}

         # sync a second parcel
         @sync.buildActions(orderNew3, orderUpdated2).update (error, response, body) ->


changed livereload value to 35729 from true

--- app/templates/Gruntfile.coffee
+++ app/templates/Gruntfile.coffee
@@ -5,7 +5,7 @@
       server:
         options:
           base: 'public/'
-          livereload: true
+          livereload: 35729
           port: 3000
     copy:
       main:


removed silly debug message

removed silly debug message

--- lib/model.coffee
+++ lib/model.coffee
@@ -11,7 +11,6 @@
     @attributes ||= []

     if typeof(attributes[attributes.length-1]) is "object"
-      console.log "Got a wee object"
       options = attributes.pop()

       if options.default


Make sure that the absence of state labels doesn't kill the front end

--- src/main/client/grid/grid-directives-tables.coffee
+++ src/main/client/grid/grid-directives-tables.coffee
@@ -380,7 +380,7 @@
             })

             handsonTable.trackerData = {
-              stateLabels: scope.trackerStudy.options.stateLabels
+              stateLabels: scope.trackerStudy.options?.stateLabels || {}
             }

             handsonTable.addHook 'beforeValidate', (value, row, fieldFunction, source) ->


Fixed lint errors.

Fixed lint errors.

--- src/alerts.coffee
+++ src/alerts.coffee
@@ -22,35 +22,37 @@
 #{(transactions.map (trx) -> trxURL trx).join '\n'}\n
 "

-htmlTemplate = (transactions, channelName, status) -> "
-
-
-
-
OpenHIM Transactions Alert

-

-
The following transaction(s) have completed with status #{status} on the OpenHIM instance running on #{config.alerts.himInstance}:

-

-
Channel - #{channelName}
-#{(transactions.map (trx) -> "
#{trxURL trx}
").join '\n'}
-

-

-
-
-"
-
-smsTemplate = (transactions, channelName, status) -> "
-Alert - #{
+htmlTemplate = (transactions, channelName, status) ->
+  """
+
+
+
+
OpenHIM Transactions Alert

+

+
The following transaction(s) have completed with status #{status} on the OpenHIM instance running on #{config.alerts.himInstance}:

+

+
Channel - #{channelName}
+  """
+  +
+    (transactions.map (trx) -> "
#{trxURL trx}
").join '\n'
+  +
+  """
+

+

+
+
+  """
+
+smsTemplate = (transactions, channelName, status) ->
+  alert = "Alert - "
   if transactions.length > 1
-    "#{transactions.length} transactions have"
+    alert += "#{transactions.length} transactions have"
   else if transactions.length is 1
-    "1 transaction has"
-  else
-    "no transactions have"
-}
-completed with status #{status} on the OpenHIM running on #{config.alerts.himInstance}
-(#{channelName})
-"
-
+    alert += "1 transaction has"
+  else
+    alert += "no transactions have"
+
+  alert += " completed with status #{status} on the OpenHIM running on #{config.alerts.himInstance} (#{channelName})"

 getAllChannels = (callback) -> Channel.find {}, callback


Both success and failure handlers should be optional.

--- lib/calatrava/templates/kernel/app/calatrava.coffee
+++ lib/calatrava/templates/kernel/app/calatrava.coffee
@@ -153,11 +153,11 @@
       failure: failure

   successfulResponse: (requestId, response) ->
-    successHandlersById[requestId](response)
+    successHandlersById[requestId](response) if successHandlersById[requestId]
     clearHandlers(requestId)

   failureResponse: (requestId, response) ->
-    failureHandlersById[requestId](response)
+    failureHandlersById[requestId](response) if failureHandlersById[requestId]
     clearHandlers(requestId)
 )()


Add a new keymap `ctrl-backspace`

Add a new keymap `ctrl-backspace`

--- keymaps/bracket-matcher.cson
+++ keymaps/bracket-matcher.cson
@@ -1,6 +1,7 @@
 '.editor':
   'ctrl-m': 'bracket-matcher:go-to-matching-bracket'
   'ctrl-]': 'bracket-matcher:remove-brackets-from-selection'
+  'ctrl-backspace': 'bracket-matcher:remove-matching-brackets'

 '.platform-darwin .editor':
   'ctrl-cmd-m': 'bracket-matcher:select-inside-brackets'


test(macTime): Removed jQuery dependency

Related to #7

--- test/unit/time.spec.coffee
+++ test/unit/time.spec.coffee
@@ -28,7 +28,7 @@
       element          = $compile("<mac-time ng-model='model' mac-time-default='06:30 PM'></mac-time>") $rootScope
       $rootScope.$digest()

-      element.click()
+      element.triggerHandler "click"
       $rootScope.$digest()

       expect($rootScope.model).toBe "06:30 PM"
@@ -38,7 +38,7 @@
       element          = $compile("<mac-time ng-model='model'></mac-time>") $rootScope
       $rootScope.$digest()

-      element.click()
+      element.triggerHandler "click"
       $rootScope.$digest()

       expect($rootScope.model).toBe "12:00 AM"
@@ -51,7 +51,7 @@
       $rootScope.model = ""
       $rootScope.$digest()

-      element.click()
+      element.triggerHandler "click"
       $rootScope.$digest()

       expect($rootScope.model).toBe "12:00 AM"
@@ -69,14 +69,14 @@

       changeInputValue = (element, value) ->
         element.val value
-        element.trigger (if $sniffer.hasEvent("input") then "input" else "change")
+        element.triggerHandler (if $sniffer.hasEvent("input") then "input" else "change")

     it "should update the model correctly", ->
       $rootScope.model = ""
       element          = $compile("<mac-time ng-model='model'></mac-time>") $rootScope
       $rootScope.$digest()

-      element.click()
+      element.triggerHandler 'click'
       changeInputValue element, "06:25 PM"

       expect($rootScope.model).toBe "06:25 PM"
@@ -86,11 +86,11 @@
       element          = $compile("<mac-time ng-model='model'></mac-time>") $rootScope
       $rootScope.$digest()

-      element.click()
+      element.triggerHandler 'click'
       $rootScope.$digest()

       changeInputValue element, "14:41 AM"
-      element.trigger 'blur'
+      element.triggerHandler 'blur'
       $rootScope.$digest()

       expect($rootScope.model).toBe("12:00 AM")
@@ -100,11 +100,11 @@
       element          = $compile("<mac-time ng-model='model'></mac-time>") $rootScope
       $rootScope.$digest()

-      element.click()
+      element.triggerHandler 'click'
       $rootScope.$digest()

       changeInputValue element, "123"
-      element.trigger 'blur'
+      element.triggerHandler 'blur'
       $rootScope.$digest()

       expect($rootScope.model).toBe("12:00 AM")


Added Batman.SessionStorage

Added Batman.SessionStorage

--- src/batman.coffee
+++ src/batman.coffee
@@ -2883,6 +2883,14 @@

     callback(@_filterData('after', 'destroy', err, record, options)...)

+class Batman.SessionStorage extends Batman.LocalStorage
+  constructor: ->
+    if typeof window.sessionStorage is 'undefined'
+      return null
+    super
+    @storage = sessionStorage
+    return
+
 class Batman.RestStorage extends Batman.StorageAdapter
   defaultOptions:
     type: 'json'


:bug: Prevent display of column related context menu when in the gutter

Fixes #19

--- lib/tablr.coffee
+++ lib/tablr.coffee
@@ -98,18 +98,27 @@
         when 'large' then @getLargeTable()
         when 'small' then @getSmallTable()

+    mouseOverColumn = ({pageX, pageY, target}) ->
+      console.log target
+      return false unless target.getScreenColumnIndexAtPixelPosition?
+      column = target.getScreenColumnIndexAtPixelPosition(pageX, pageY)
+      column? and column >= 0
+
     @subscriptions.add atom.contextMenu.add
       'tablr-editor': [{
-        label: 'Table'
+        label: 'Tablr Column'
         submenu: [
           {label: 'Align left', command: 'tablr:align-left'}
           {label: 'Align center', command: 'tablr:align-center'}
           {label: 'Align right', command: 'tablr:align-right'}
         ]
+        shouldDisplay: mouseOverColumn
         created: (event) ->
           {pageX, pageY, target} = event
           return unless target.getScreenColumnIndexAtPixelPosition?
-          target.targetColumnForAlignment = target.getScreenColumnIndexAtPixelPosition(pageX, pageY)
+          targetColumnForAlignment = target.getScreenColumnIndexAtPixelPosition(pageX, pageY)
+          return unless targetColumnForAlignment? and targetColumnForAlignment >= 0
+          target.targetColumnForAlignment = targetColumnForAlignment
           setTimeout (-> delete target.targetColumnForAlignment), 10
       }]


fixed gunzip error

fixed gunzip error

--- src/middleware/router.coffee
+++ src/middleware/router.coffee
@@ -1,5 +1,6 @@
 util = require('util');
 zlib = require('zlib');
+gunzip = require('zlib').createGunzip()
 http = require 'http'
 https = require 'https'
 net = require 'net'
@@ -173,6 +174,14 @@
 		response.status = routeRes.statusCode
 		response.headers = routeRes.headers

+		uncompressedBody = ''
+		if routeRes.headers['content-encoding'] == 'gzip' #attempt to gunzip
+			routeRes.pipe(gunzip);
+
+		gunzip.on "data", (data) ->
+			uncompressedBody += data
+			return
+
 		bufs = []
 		routeRes.on "data", (chunk) ->
 			bufs.push chunk
@@ -182,14 +191,11 @@
 			response.timestamp = new Date()
 			charset = obtainCharset(routeRes.headers)
 			if routeRes.headers['content-encoding'] == 'gzip'
-				zlib.gunzip(
-					Buffer.concat bufs,
-					(gunzipError, buf) ->
-						if gunzipError then logger.error gunzipError
-						else response.body = buf.toString charset
-						if not defered.promise.isRejected()
-							defered.resolve response
-				)
+				gunzip.on "end", ->
+					response.body = uncompressedBody
+					if not defered.promise.isRejected()
+						defered.resolve response
+					return
 			else if routeRes.headers['content-encoding'] == 'deflate'
 				zlib.inflate(
 					Buffer.concat bufs,


number_extensions

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -3,7 +3,7 @@

 (-4).abs()
-=> 4
+# => 4

 @name abs


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -73,7 +73,7 @@
 Number::round = ->
   Math.round(this)

-Number::circularPoints = (n, ) ->
+Number::circularPoints = (n, block) ->
   n.times

 ###*


Refactor async-ness of init / ready gamestate

--- src/plugins/gamestate.coffee
+++ src/plugins/gamestate.coffee
@@ -12,16 +12,15 @@
     state.initialized = true
     that = @

-    # we want to be certain that 'done' is only called once
-    asyncDone = false
     done = () ->
-      return if asyncDone
       state.isReady = asyncDone = true
       that.events.trigger "state:ready"
+
+    state.init.call(state, if state.async then done else null)

-    state.init.call(state, done)
     # synchronous version
-    done() unless asyncDone
+    done() if not state.async
+

   "step": (params...) ->
@@ -67,9 +66,9 @@
 # The state object, created with StateManager::add
 #
 State = (aghs, @state, options = {}) ->
-
   @name = options.name
   @isReady = if options.isReady then options.isReady else false
+  @async = false
   config = {
     "fullscreen": options.fullscreen
     "width":      options.width
@@ -141,7 +140,7 @@
       @events.trigger "state:init"

     if state.isReady
-      @events.trigger "state:enter"
+      @events.trigger "state:enter", @aghs.time(), @aghs.renderer
   else
     console.warn "A GameState with a name of '#{name}' doesn't exist yet"
   return @


bounded

bounded

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -203,7 +203,7 @@
   player.include(Bounded)

   player.center()
-  => {x: 30, y: 35}
+  # => {x: 30, y: 35}

   @name center
@@ -227,11 +227,12 @@
   player.include(Bounded)

   player.circle()
-  => {radius: 5, x: 50, y: 50}
+  # => {radius: 5, x: 50, y: 50}

   @name circle
   @methodOf Bounded#
+  @returns {Object} An object with a position and a radius
   ###
   circle: () ->
     circle = self.center()


Disable slider movement with keydown, fix amount display.

--- src/browser.coffee
+++ src/browser.coffee
@@ -4,7 +4,7 @@

 begin_time = null
 end_time = null
-slider_ticks = 1000
+slider_ticks = 100000

 info = ->
     ###
@@ -32,12 +32,14 @@
     ###
     null

-on_slide = (value) ->
+on_slide = (amount) ->
     ###
     # Do AJAX associated with slider movement.
     ###

-    url = 'service/slide?time1=' + begin_time + '&time2=' + end_time + '&amount=' + value
+    $('#slider_amount').text amount.toFixed(3)
+
+    url = 'service/slide?time1=' + begin_time + '&time2=' + end_time + '&amount=' + amount
     $.ajax url,
         async    : false
         dataType : 'json'
@@ -60,17 +62,12 @@
         max: slider_ticks-1
         value: slider_ticks/2
         create: (event, ui) ->
-            $('#slider_value').text parseInt(slider_ticks/2)
-            $('#slider_min').text 0
-            $('#slider_max').text slider_ticks-1
             on_slide(0.5)
         slide: (event, ui) ->
-            amount = ui.value/slider_ticks
-            on_slide(amount)
-            $('#slider_value').text ui.value
-            $('#slider_amount').text amount
-
-
+            on_slide(ui.value/(slider_ticks-1))
+    $('#slider').bind 'keydown', (event) ->
+        null
+
 $ ->
     ###
     # Run when document has loaded.


remove logs

remove logs

--- apps/about2/client/edit.coffee
+++ apps/about2/client/edit.coffee
@@ -4,11 +4,9 @@
 { DATA } = require('sharify').data

 hulkCallback = (data) ->
-  console.log("Here's the updated data: ", data);
   render(data)

 render = (data) ->
-  console.log data
   $('#about2-edit-example').html sectionsTemplates(data)

 module.exports.init = ->


ContextMenuItem: Empty menu item title if it is separator.

--- src/components/contextmenu/contextmenuitem.coffee
+++ src/components/contextmenu/contextmenuitem.coffee
@@ -7,6 +7,7 @@

     options.type       = "contextitem"
     options.cssClass or= "default #{KD.utils.slugify data.title}"
+    data.title        ?= ''

     super options, data


Add explicit returns to prevent array collection

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -260,6 +260,7 @@
       for keyBinding in addedKeyBindings
         index = @keyBindings.indexOf(keyBinding)
         @keyBindings.splice(index, 1) unless index is -1
+      return

   remove: (source) ->
     Grim.deprecate("Call .dispose() on the Disposable returned from KeymapManager::add instead")
@@ -590,6 +591,7 @@
     binding.enabled = false for binding in bindingsToDisable
     @handleKeyboardEvent(event, true) for event in eventsToReplay
     binding.enabled = true for binding in bindingsToDisable
+    return

   # After we match a binding, we call this method to dispatch a custom event
   # based on the binding's command.
@@ -628,6 +630,7 @@
       break if commandEvent.propagationStopped
       break if currentTarget is window
       currentTarget = currentTarget.parentNode ? window
+    return

   # Deprecated: Use {::add} instead.
   addKeymap: (source, bindings) ->


Make console.log working in IE8.

Make console.log working in IE8.

--- app/assets/javascripts/app/lib/app_init/log.js.coffee
+++ app/assets/javascripts/app/lib/app_init/log.js.coffee
@@ -11,10 +11,10 @@
       _instance ?= new _Singleton
     _instance.log( module, level, args )

-
 class _Singleton
   constructor: ->
     @config = {}
+#    @config['Collection'] = true
 #      Session: true
 #      ControllerForm: true

@@ -24,9 +24,9 @@
     else if @config[ module ]
       @_log( module, level, args )

-
   _log: ( module, level, args ) ->
-    return if !console
-    return if !console.log
+    return if !'console' in window
+    return if !'log' in console
+    return if !typeof console.log isnt 'function'
     console.log "App.#{module}(#{level})", args


Update width/height in the snippet block if changed

--- app/assets/javascripts/application/widget_edit.coffee
+++ app/assets/javascripts/application/widget_edit.coffee
@@ -18,3 +18,16 @@
     $.post $form.attr('action'), $form.serialize(), (data) ->
       $('textarea#snippet').val(data.snippet)
     , "json"
+
+  this.on 'change', 'input[type="text"]', ->
+    # TODO: use angular instead
+    $this    = $(this)
+    name     = $this.attr('name')
+    value    = $this.val()
+    $snippet = $('textarea#snippet')
+    snippet  = $snippet.val()
+
+    if name.match /width/
+      $snippet.val(snippet.replace(/width='\d+'/, "width='#{value}'"))
+    else if name.match /height/
+      $snippet.val(snippet.replace(/height='\d+'/, "height='#{value}'"))


Add session release level report

Add session release level report

Fixes #4740

--- web/volume/spreadsheet.coffee
+++ web/volume/spreadsheet.coffee
@@ -1400,10 +1400,19 @@
               rows: ["participant race"]
               rendererName: "Table"
               aggregatorName: "Count"
-            filter: [ # these aren't actually used (yet)
+            filter: [
               {c: "slot", m: "top", op: "false"},
               {c: "slot", m: "date", op: "ge"},
               {c: "slot", m: "date", op: "le", v: $filter('date')(new Date(), 'yyyy-MM-dd')}]
+          volume.state['Session release level summary'] ?=
+            key: 'slot'
+            pivot:
+              cols: []
+              rows: ["release"]
+              aggregatorName: "Count"
+              rendererName: "Table"
+            filter: [
+              {c: "slot", m:"top", op:"false"}]

         $scope.zip = (event) ->
           event.preventDefault()


Fix cursor display in "pre" renderer

Fix cursor display in "pre" renderer

--- app/assets/javascripts/player/renderers/pre.js.coffee
+++ app/assets/javascripts/player/renderers/pre.js.coffee
@@ -50,7 +50,7 @@
     for fragment in fragments
       [text, brush] = fragment

-      if cursorX isnt undefined and rendered < cursorX < rendered + text.length
+      if cursorX isnt undefined and rendered <= cursorX < rendered + text.length
         left = text.slice(0, cursorX - rendered)
         cursor =
           '' + text[cursorX - rendered] + ''


Use last bill/ship address when preloading order data

--- app/assets/javascripts/sprangular/models/order.coffee
+++ app/assets/javascripts/sprangular/models/order.coffee
@@ -107,10 +107,10 @@

   resetAddresses: (user) ->
     if @billingAddress.isEmpty() && user && user.addresses.length > 0
-      @billingAddress = user.addresses[0]
+      @billingAddress = user.billingAddress

     if @shippingAddress.isEmpty() && user && user.addresses.length > 0 && !@shipToBillAddress
-      @shippingAddress = user.addresses[0]
+      @shippingAddress = user.shippingAddress

   resetCreditCard: (user) ->
     if user && user.creditCards.length > 0


Can pass space-separated string of types

--- src/classof.coffee
+++ src/classof.coffee
@@ -30,6 +30,9 @@
 @param {String+} names
 @return {Boolean}
 ###
-isClass = exports.isClass = (obj, names...) -> classOf(obj) in names
+isClass = exports.isClass = (obj, names...) ->
+  if names.length is 1 and typeof names[0] is 'string'
+    names = names[0].split ' '
+  classOf(obj) in names


Some more AsCallback docs

Some more AsCallback docs

--- src/lib/AsCallback.coffee
+++ src/lib/AsCallback.coffee
@@ -23,6 +23,13 @@
 #
 # If there was anything sent to an `error` outport, this will
 # be provided as the error argument to the callback.
+
+# ### Option normalization
+#
+# Here we handle the input valus given to the `asCallback`
+# function. This allows passing things like a pre-initialized
+# NoFlo ComponentLoader, or giving the component loading
+# baseDir context.
 normalizeOptions = (options, component) ->
   options = {} unless options
   options.name = component unless options.name
@@ -35,6 +42,11 @@
   options.raw = false unless options.raw
   options

+# ### Network preparation
+#
+# Each invocation of the asCallback-wrapped NoFlo graph
+# creates a new network. This way we can isolate multiple
+# executions of the function in their own contexts.
 prepareNetwork = (component, options, callback) ->
   # If we were given a graph instance, then just create a network
   if typeof component is 'object'
@@ -68,6 +80,16 @@
       return callback err if err
       callback null, network

+# ### Network execution
+#
+# Once network is ready, we connect to all of its exported
+# in and outports and start the network.
+#
+# Input data is sent to the inports, and we collect IP
+# packets received on the outports.
+#
+# Once the network finishes, we send the resulting IP
+# objects to the callback.
 runNetwork = (network, inputs, options, callback) ->
   # Prepare inports
   inPorts = Object.keys network.graph.inports


Force defineProperty to darwin in test.

Force defineProperty to darwin in test.

--- spec/paths-spec.coffee
+++ spec/paths-spec.coffee
@@ -34,6 +34,13 @@
         expect(parsed.base).toBe('kernels')

 describe "JupyterPath mocked", ->
+  beforeEach ->
+      # process.platform is not writeable but it can be overridden with
+      # defineProperty
+      Object.defineProperty(process, 'platform', {
+        value: 'darwin'
+      })
+
   describe "when on OS X", ->
     it "has OS X specific system directories", ->
       spyOn(process, 'platform')


Fix hdfs permission on keytab dir

Fix hdfs permission on keytab dir

--- hdp/hdfs.coffee
+++ hdp/hdfs.coffee
@@ -32,7 +32,8 @@
   ctx.config.hdp.snn_port ?= '50090'
   # Options for "hdfs-site.xml"
   ctx.config.hdp.hdfs_site ?= {}
-  ctx.config.hdp.hdfs_site['dfs.datanode.data.dir.perm'] ?= '750'
+  # ctx.config.hdp.hdfs_site['dfs.datanode.data.dir.perm'] ?= '750'
+  ctx.config.hdp.hdfs_site['dfs.datanode.data.dir.perm'] ?= '700'
   ctx.config.hdp.hdfs_site['dfs.journalnode.edits.dir'] ?= '/var/run/hadoop-hdfs/journalnode_edit_dir'
   ctx.config.hdp.hdfs_site['fs.permissions.umask-mode'] ?= '027' # 0750
   # Options for "hadoop-policy.xml"
@@ -253,7 +254,7 @@
     destination: '/etc/security/keytabs'
     uid: 'root'
     gid: 'hadoop'
-    mode: 0o755
+    mode: 0o750
   , (err, created) ->
     ctx.log 'Creating HTTP Principals and SPNEGO keytab'
     ctx.krb5_addprinc
@@ -305,7 +306,6 @@
   hdfs_site['dfs.namenode.kerberos.internal.spnego.principal'] ?= "HTTP/_HOST@#{realm}"
   # # Default to ${dfs.web.authentication.kerberos.principal}, but documented in hdp 1.3.2 manual install
   hdfs_site['dfs.secondary.namenode.kerberos.internal.spnego.principal'] ?= "HTTP/_HOST@#{realm}"
-  hdfs_site['dfs.datanode.data.dir.perm'] ?= '700'
   # The address, with a privileged port - any port number under 1023. Example: 0.0.0.0:1019
   hdfs_site['dfs.datanode.address'] ?= '0.0.0.0:1019'
   # The address, with a privileged port - any port number under 1023. Example: 0.0.0.0:1022


Add ReactEditorView::insertText shim

Add ReactEditorView::insertText shim

--- src/react-editor-view.coffee
+++ src/react-editor-view.coffee
@@ -145,6 +145,9 @@
   setText: (text) ->
     @editor.setText(text)

+  insertText: (text) ->
+    @editor.insertText(text)
+
   requestDisplayUpdate: -> # No-op shim for find-and-replace

   updateDisplay: -> # No-op shim for package specs


Add logs to sendMessage

Add logs to sendMessage

--- src/rocketchat_driver.coffee
+++ src/rocketchat_driver.coffee
@@ -67,6 +67,10 @@

 	sendMessageByRoomId: (text, roomid) =>
 		@asteroid.call('sendMessage', {msg: text, rid: roomid})
+		.then (result)->
+			@logger.debug('[sendMessage] Success:', result)
+		.catch (error) ->
+			@logger.error('[sendMessage] Error:', error)

 	customMessage: (message) =>
 		@logger.info "Sending Custom Message To Room: #{message.channel}"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -34,7 +34,7 @@
       y: centerOffset.y - deadzone.y
       width: 2 * deadzone.x
       height: 2 * deadzone.y
-
+
     I.scroll = Point(
       I.scroll.x.clamp(centerRect.left, centerRect.right)
       I.scroll.y.clamp(centerRect.top, centerRect.bottom)


Cleanup for weakRefList in BrowserManager [name will be changed].

--- src/server/browser_manager/browser_manager.coffee
+++ src/server/browser_manager/browser_manager.coffee
@@ -2,31 +2,26 @@
 Weak = require('weak')
 ko   = require('../../api/ko')

-if !global.browserList?
-    global.browserList = ko.observableArray()
-
-# TODO: something better than both of these.
+# TODO: rename, make this a real global browser list.
+#       need to change debug page to use this.
 global.weakRefList = {}

 # Base class for browser management strategies.
 # Must also define @mountPoint
 class BrowserManager
     addToBrowserList : (browser) ->
-        console.log("Pushing to browserList")
-        #global.browserList.push(browser)
         if process.env.WAS_FORKED
             id = browser.id
             process.send
                 type: 'browserCreated'
                 id: id
-            weakRefList[id] = Weak browser, () ->
-                delete weakRefList[id]
+            global.weakRefList[id] = Weak browser, () ->
+                delete global.weakRefList[id]
                 process.send
                     type: 'browserCollected'
                     id: id

     removeFromBrowserList : (browser) ->
-        #global.browserList.remove(browser)

     find : () ->
         throw new Error("BrowserManager subclass must implement find.")


fix for nested user data

fix for nested user data

--- assets/scripts/app/auth.coffee
+++ assets/scripts/app/auth.coffee
@@ -25,6 +25,7 @@

   userDataFrom: (storage) ->
     user  = JSON.parse storage.getItem('travis.user')
+    user  = user.user is user.user
     token = storage.getItem('travis.token')
     if user && token && @validateUser(user)
       { user: user, token: token }


fix ssh_platform_detect test

fix ssh_platform_detect test

--- test/integration/ssh_platform_detect.coffee
+++ test/integration/ssh_platform_detect.coffee
@@ -2,11 +2,10 @@
 {wd40, browser, login_url, home_url, prepIntegration} = require './helper'

 clickSSHButton = (done) ->
-  wd40.elementByPartialLinkText 'Tools', (err, link) =>
-    link.click ->
-      wd40.elementByPartialLinkText 'Test app', (err, link) ->
-        link.elementByCss '.ssh-in', (err, sshLink) ->
-          sshLink.click done
+  wd40.elementByCss '#dataset-tools-toggle', (err, link) ->
+    browser.moveTo link, (err) ->
+      wd40.elementByCss '#dataset-tools a[href$="/settings"] .ssh-in', (err, sshLink) ->
+        sshLink.click done

 describe 'Platform-specific SSH instructions', ->
   prepIntegration()


fix issue with processing math in markdown

--- src/smc-util/mathjax-utils-2.coffee
+++ src/smc-util/mathjax-utils-2.coffee
@@ -11,7 +11,7 @@
 exports.apply_without_math = (string, v) ->
     if not is_array(v)
         v = [v]
-    [string, math] = remove_math(string)
+    [string, math] = remove_math(string, true)  # true so doesn't mess with &, etc.
     for f in v
         string = f(string)
     return replace_math(string, math)


Add test for update on inventory sync.

Add test for update on inventory sync.

--- src/spec/inventory-sync.spec.coffee
+++ src/spec/inventory-sync.spec.coffee
@@ -63,4 +63,22 @@
     update = @sync.buildActions(ieNew, ieOld).get()
     expect(update).toBeDefined()
     expect(update.actions[0].action).toBe 'removeQuantity'
-    expect(update.actions[0].quantity).toBe 2+    expect(update.actions[0].quantity).toBe 2
+
+describe "#update", ->
+
+  beforeEach ->
+    @sync = new InventorySync config: Config
+
+  it "should send update request", (done)->
+    spyOn(@sync._rest, "POST").andCallFake((path, payload, callback)-> callback(null, null, {id: "123"}))
+    @sync._data =
+      update:
+        actions: [ { name: "addQuantity", quantity: 7 } ]
+        version: 1
+      updateId: "123"
+    callMe = (e, r, b)->
+      expect(b.id).toBe "123"
+      done()
+    @sync.update(callMe)
+    expect(@sync._rest.POST).toHaveBeenCalledWith("/inventory/123", JSON.stringify(@sync._data.update), jasmine.any(Function))


simplify

simplify

--- spec/vim-mode-plus-move-selected-text-spec.coffee
+++ spec/vim-mode-plus-move-selected-text-spec.coffee
@@ -129,16 +129,8 @@
         ensureOverwriteClassForVimStates(allVimState, false)

   describe "move up/down", ->
-    textData = null
     beforeEach ->
-      textData = new TextData """
-        line0
-        line1
-        line2\n
-        """
-      set
-        text: textData.getRaw()
-        cursor: [0, 0]
+      set textC: "|line0\nline1\nline2\n"

     describe "linewise", ->
       describe "overwrite: false", ->


add comment warning about potential for undefined

--- lib/analytics.coffee
+++ lib/analytics.coffee
@@ -125,9 +125,11 @@
       , (500 * index) + 1)
     )(@encodeMulti(chunk))

+# Code using this function should cope with it returning undefined
 module.exports.getProperty = (property) =>
   mixpanel?.get_property property

+# Code using this function should cope with it returning undefined
 module.exports.setProperty = (hash) =>
   mixpanel?.register_once hash


fixes #433

fixes #433

--- lib/image/png.coffee
+++ lib/image/png.coffee
@@ -8,28 +8,28 @@
     @height = @image.height
     @imgData = @image.imgData
     @obj = null
-
+
   embed: (@document) ->
     return if @obj
-
-    @obj = document.ref
+
+    @obj = @document.ref
       Type: 'XObject'
       Subtype: 'Image'
       BitsPerComponent: @image.bits
       Width: @width
       Height: @height
       Filter: 'FlateDecode'
-
+
     unless @image.hasAlphaChannel
-      params = document.ref
+      params = @document.ref
         Predictor: 15
         Colors: @image.colors
         BitsPerComponent: @image.bits
         Columns: @width
-
+
       @obj.data['DecodeParms'] = params
       params.end()
-
+
     if @image.palette.length is 0
       @obj.data['ColorSpace'] = @image.colorSpace
     else
@@ -39,7 +39,7 @@

       # build the color space array for the image
       @obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', (@image.palette.length / 3) - 1, palette]
-
+
     # For PNG color types 0, 2 and 3, the transparency data is stored in
     # a dedicated PNG chunk.
     if @image.transparency.grayscale
@@ -57,21 +57,21 @@
         mask.push x, x

       @obj.data['Mask'] = mask
-
+
     else if @image.transparency.indexed
-      # Create a transparency SMask for the image based on the data
+      # Create a transparency SMask for the image based on the data
       # in the PLTE and tRNS sections. See below for details on SMasks.
       @loadIndexedAlphaChannel()
-
+
     else if @image.hasAlphaChannel
       # For PNG color types 4 and 6, the transparency data is stored as a alpha
       # channel mixed in with the main image data. Separate this data out into an
       # SMask object and store it separately in the PDF.
       @splitAlphaChannel()
-
+
     else
       @finalize()
-
+
   finalize: ->
     if @alphaChannel
       sMask = @document.ref
@@ -89,11 +89,11 @@

     # add the actual image data
     @obj.end @imgData
-
+
     # free memory
     @image = null
     @imgData = null
-
+
   splitAlphaChannel: ->
     @image.decodePixels (pixels) =>
       colorByteSize = @image.colors * @image.bits / 8
@@ -117,7 +117,7 @@
       zlib.deflate alphaChannel, (err, @alphaChannel) =>
         throw err if err
         @finalize() if ++done is 2
-
+
   loadIndexedAlphaChannel: (fn) ->
     transparency = @image.transparency.indexed
     @image.decodePixels (pixels) =>
@@ -130,5 +130,5 @@
       zlib.deflate alphaChannel, (err, @alphaChannel) =>
         throw err if err
         @finalize()
-
+
 module.exports = PNGImage


Add YAML to the mix

Add YAML to the mix

--- lib/languages.coffee
+++ lib/languages.coffee
@@ -84,3 +84,8 @@
     nameMatchers:      ['.sh']
     pygmentsLexer:     'sh'
     singleLineComment: ['#']
+
+  YAML:
+    nameMatchers:      ['.yml', '.yaml']
+    pygmentsLexer:     'yaml'
+    singleLineComment: ['#']


Fix Uncaught Error: ENOENT: no such file or directory

Use JSON.stringify and JSON.parse since setting initialPaths using
e.dataTransfer.setData sets the array value to a DOMString.

Relevant links

https://developer.mozilla.org/en-US/docs/Web/API/DOMString
https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/setData

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -1111,7 +1111,7 @@

       e.dataTransfer.effectAllowed = "move"
       e.dataTransfer.setDragImage(dragImage, 0, 0)
-      e.dataTransfer.setData("initialPaths", initialPaths)
+      e.dataTransfer.setData("initialPaths", JSON.stringify(initialPaths))
       e.dataTransfer.setData("atom-tree-view-event", "true")

       window.requestAnimationFrame ->
@@ -1146,7 +1146,7 @@

       if initialPaths
         # Drop event from Atom
-        initialPaths = initialPaths.split(',')
+        initialPaths = JSON.parse(initialPaths)
         return if initialPaths.includes(newDirectoryPath)

         entry.classList.remove('drag-over', 'selected')


Teams: Invitation: Add protocol and hostname to image urls and change inviter link.

--- workers/social/lib/social/models/invitation.coffee
+++ workers/social/lib/social/models/invitation.coffee
@@ -253,20 +253,18 @@

     inviter       = getName delegate
     groupLink     = "#{protocol}//#{invitation.groupName}.#{hostname}/"
-    inviterLink   = "#{protocol}//#{hostname}/" + profile.nickname
-
-    imgURL   = "//gravatar.com/avatar/#{profile.hash}?size=65&d=https://koding-cdn.s3.amazonaws.com/images/default.avatar.140.png&r=g"
+
+    imgURL   = "#{protocol}//gravatar.com/avatar/#{profile.hash}?size=65&d=https://koding-cdn.s3.amazonaws.com/images/default.avatar.140.png&r=g"

     if profile.avatar
-      imgURL = "/-/image/cache?endpoint=crop&grow=false&width=65&height=65&url=#{encodeURIComponent profile.avatar}"
+      imgURL = "#{protocol}//#{hostname}/-/image/cache?endpoint=crop&grow=false&width=65&height=65&url=#{encodeURIComponent profile.avatar}"

     properties =
       groupName    : invitation.groupName
       groupLink    : groupLink
       inviter      : inviter
-      inviterLink  : inviterLink
+      inviterLink  : groupLink + profile.nickname
       inviterImage : imgURL
-      invitee      : inviter # TODO: this field is deprecated, remove after updating template
       link         : groupLink + "Invitation/#{encodeURIComponent invitation.code}"

     Tracker.identifyAndTrack invitation.email, { subject : Tracker.types.INVITED_GROUP }, properties


oauth: fix scheme field names

oauth: fix scheme field names

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -128,23 +128,22 @@
       sshKeys       : [Object]
       foreignAuth            :
         github               :
-          token              : String
           foreignId          : String
           username           : String
+          token              : String
           firstName          : String
           lastName           : String
           email              : String
         odesk                :
-          accessToken        : String
+          foreignId          : String
+          token              : String
           accessTokenSecret  : String
-          foreignId          : String
           requestToken       : String
           requestTokenSecret : String
         facebook             :
-          access_token       : String
+          foreignId          : String
           username           : String
-          foreignId          : String
-          provider           : String
+          token              : String
     relationships       :
       ownAccount        :
         targetType      : JAccount


social: add index for all foreignAuths

social: add index for all foreignAuths

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -69,6 +69,9 @@
       'foreignAuth.github.foreignId'   : 1
       'foreignAuth.odesk.foreignId'    : 1
       'foreignAuth.facebook.foreignId' : 1
+      'foreignAuth.google.foreignId'   : 1
+      'foreignAuth.linkedin.foreignId' : 1
+      'foreignAuth.twitter.foreignId'  : 1

     sharedEvents    :
       static        : [


social: log when user tries to confirm unconfirmed email

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1326,6 +1326,7 @@
   confirmEmail: (callback)->

     if (@getAt 'status') isnt 'unconfirmed'
+      console.log "ALERT: #{@getAt 'username'} is trying to confirm '#{@getAt 'status'}' email"
       return callback null

     @update {$set: status: 'confirmed'}, (err, res)=>


JUser::authenticateClient: add comments, tidy up the code

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -299,68 +299,81 @@

   @authenticateClient: (clientId, context, callback)->

-    error = (message, rest...)->
+    logError = (message, rest...) ->
       console.error "[JUser::authenticateClient] #{message}", rest...

+    logout = (reason, clientId, callback) =>
+
+      @logout clientId, (err)->
+        logError reason, clientId
+        callback createKodingError reason
+
+    # Let's try to lookup provided session first
     JSession.one { clientId }, (err, session)=>

       if err

-        error "error finding session", { err, clientId }
+        # This is a very rare state here
+        logError "error finding session", { err, clientId }
         callback createKodingError err

       else unless session?

+        # We couldn't find the session with given token
+        # so we are creating a new one now.
         JSession.createSession (err, { session, account })->

           if err?
-            error "failed to create session", { err }
-            return callback err
+            logError "failed to create session", { err }
+            callback err
           else
+
+            # Voila session created and sent back, scenario #1
             callback null, { session, account }

       else

+        # So we have a session, let's check it out if its a valid one
         { username } = session

         unless username?

-          error "no username found", { session }
-          @logout clientId, (err)->
-
-            callback createKodingError "no username found, logged out."
+          # A session without a username is nothing, let's kill it
+          # and logout the user, this is also a rare condition
+          logout "no username found", clientId, callback

           return

+        # If we are dealing with a guest session we know that we need to
+        # use guestuser's JUser, we are overriding it here
         username = 'guestuser'  if /^guest-/.test username

         JUser.one {username}, (err, user)=>

           if err?

-            error "error finding user with username", { err, username }
-            callback createKodingError err
+            logout "error finding user with username", clientId, callback

           else unless user?

-            error "no user found with username", { username }
-            @logout clientId, callback
+            logError "no user found with username", { username }
+            logout   "no user found with username", clientId, callback

           else

             user.fetchAccount context, (err, account)->

               if err?
-
-                error "error fetching account", { context }
-                callback createKodingError err
+                logout "error fetching account", clientId, callback

               else

+                # A valid session, a valid user attached to
+                # it voila, scenario #2
                 callback null, { session, account }

-  getHash =(value)->
+  getHash = (value)->
     require('crypto').createHash('md5').update(value.toLowerCase()).digest('hex')

   @whoami = secure ({connection:{delegate}}, callback)-> callback null, delegate


Cleaned code.

Cleaned code.

--- src/starshape.coffee
+++ src/starshape.coffee
@@ -118,21 +118,6 @@
   $("#star").attr("d", pathString);

-# Does not account for control points, so is no longer used
-drawCubicStar = () ->
-  innerPoints = innerPointStrings()
-  outerPoints = outerPointStrings()
-
-  firstPoint = innerPoints[0]
-  innerPoints.push(innerPoints.shift())
-  points = _.zip(outerPoints, innerPoints)
-
-  sectionStrings = points.map ([outerPoint, innerPoint]) ->
-    " C " + outerPoint + " " + outerPoint + " " + innerPoint
-
-  pathString = "M " + firstPoint + sectionStrings.join('') + " Z"
-  $("#star").attr("d", pathString);
-
 drawStarWithCircularTips = () ->
   innerPoints = calculateInnerPoints()
   outerPoints = calculateOuterPoints()
@@ -216,17 +201,12 @@
   uri = new URI()
   options = uri.search(true)

-  if options.s == undefined
-    options.s = "cubic"
-  if options.r == undefined
-    options.r = 1 - (2 / (1 + Math.sqrt(5)))
-  if options.l == undefined
-    options.l = 75
-  if options.c == undefined
-    options.c = 100
-  if options.fg == undefined
-    options.fg = "fddc34"
-  if options.bg == undefined
+  options.s ?= "cubic"
+  options.r ?= 1 - (2 / (1 + Math.sqrt(5)))
+  options.l ?= 75
+  options.c ?= 100
+  options.fg ?= "fddc34"
+  if options.bg == undefined    # null is a valid value for bg, so don't replace it with the default
     options.bg = "000000"

   options


gaaaaaaaaaaaaaah

gaaaaaaaaaaaaaah

--- src/editor.coffee
+++ src/editor.coffee
@@ -23,7 +23,7 @@
 setTimeout ->
   editor.focus()
   editor.resize()
-, 50
+, 100

 filename = null
 editor.getSession().on 'change', ->


Place hidden input on the underlayer.

Place hidden input on the underlayer.

Also brings the input into view when the input is focused so we
dont scroll the scrollView and blank the editor

--- src/editor.coffee
+++ src/editor.coffee
@@ -43,10 +43,10 @@
     @div attributes, =>
       @subview 'gutter', new Gutter
       @div class: 'scroll-view', outlet: 'scrollView', =>
-        @div class: 'overlayer', outlet: 'overlayer', =>
+        @div class: 'overlayer', outlet: 'overlayer'
+        @div class: 'lines', outlet: 'renderedLines'
+        @div class: 'underlayer', outlet: 'underlayer', =>
           @input class: 'hidden-input', outlet: 'hiddenInput'
-        @div class: 'lines', outlet: 'renderedLines'
-        @div class: 'underlayer', outlet: 'underlayer'
       @div class: 'vertical-scrollbar', outlet: 'verticalScrollbar', =>
         @div outlet: 'verticalScrollbarContent'

@@ -640,6 +640,7 @@

   handleEvents: ->
     @on 'focus', =>
+      @bringHiddenInputIntoView()
       @hiddenInput.focus()
       false

@@ -650,7 +651,6 @@
     @hiddenInput.on 'focusout', =>
       @isFocused = false
       @removeClass 'is-focused'
-      @hiddenInput.offset(top: 0, left: 0)

     @underlayer.on 'mousedown', (e) =>
       @renderedLines.trigger(e)
@@ -737,6 +737,9 @@
       @hiddenInput.val(lastInput)
       false

+  bringHiddenInputIntoView: ->
+    @hiddenInput.css(top: @scrollTop() + 'px', left: 0)
+
   selectOnMousemoveUntilMouseup: ->
     lastMoveEvent = null
     moveHandler = (event = lastMoveEvent) =>
@@ -851,6 +854,7 @@
     @underlayer.css('top', -scrollTop)
     @overlayer.css('top', -scrollTop)
     @gutter.lineNumbers.css('top', -scrollTop)
+
     if options?.adjustVerticalScrollbar ? true
       @verticalScrollbar.scrollTop(scrollTop)
     @activeEditSession.setScrollTop(@scrollTop())


adding elapsedTime

adding elapsedTime

--- src/engine.coffee
+++ src/engine.coffee
@@ -136,8 +136,8 @@
         window.requestAnimationFrame(animLoop)

     update = (elapsedTime) ->
-      self.trigger "beforeUpdate", ela
-      self.trigger "update"
+      self.trigger "beforeUpdate", elapsedTime
+      self.trigger "update", elapsedTime
       self.trigger "afterUpdate"

     draw = ->


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -13,6 +13,7 @@
 enemy = GameObject
   x: 100
   y: 50
+  width: 10

 player.update()


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -25,7 +25,7 @@

 enemy.update()

-enemyI
+enemy.I.


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -1,7 +1,7 @@
 ###*
 The Follow module provides a simple method to set an object's
 velocity so that it will approach another object. This method
-relies on the objects having `bou`
+relies on the objects having `bounds`

 player = GameObject


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -2,7 +2,7 @@
   Object.reverseMerge I,
     velocity: Point(0, 0)

-  se
+

   self.bind "update", ->
     I.velocity = player.position().subtract(self.position()).norm().scale(5)


tweak for phantomjs

tweak for phantomjs

--- src/format.coffee
+++ src/format.coffee
@@ -113,14 +113,14 @@
     node.removeAttribute('style')
     if styleObj[@cssName]
       style = this.approximate(styleObj[@cssName])
-      node.style[_.str.camelize(@cssName)] = @styles[style] if style
+      node.setAttribute('style', "#{@cssName}: #{@styles[style]};") if style    # PhantomJS adds a trailing space if we use node.style
     return node

   createContainer: (value) ->
     container = super(value)
     cssName = _.str.camelize(@cssName)
     style = this.approximate(value)
-    container.style[cssName] = @styles[style] if style
+    container.setAttribute('style', "#{@cssName}: #{@styles[style]};") if style # PhantomJS adds a trailing space if we use node.style
     return container

   matchContainer: (container) ->


&R -> &s

&R -> &s

--- menus/win32.cson
+++ menus/win32.cson
@@ -126,7 +126,7 @@
       { type: 'separator' }
       { label: '&Increase Font Size', command: 'window:increase-font-size' }
       { label: '&Decrease Font Size', command: 'window:decrease-font-size' }
-      { label: '&Reset Font Size', command: 'window:reset-font-size' }
+      { label: 'Re&set Font Size', command: 'window:reset-font-size' }
       { type: 'separator' }
       { label: 'Toggle Soft &Wrap', command: 'editor:toggle-soft-wrap' }
     ]


Fix error in github-pull-request-notifier when body is empty.

When someone submits a pull request without a message the JSON
document contains `null` for `body`. The notifier script was
trying to call `match` on `body` to extract the list of user
mentions. That would fail when it was `null`, resulting in:

    Whoa, I got an error: TypeError: Cannot call method 'match' of null

This changes it to use the existential access operator, so
when body is `null` the mention list is `undefined`.

--- src/scripts/github-pull-request-notifier.coffee
+++ src/scripts/github-pull-request-notifier.coffee
@@ -50,7 +50,7 @@

 announcePullRequest = (data, cb) ->
   if data.action == 'opened'
-    mentioned = data.pull_request.body.match(/(^|\s)(@[\w\-\/]+)/g)
+    mentioned = data.pull_request.body?.match(/(^|\s)(@[\w\-\/]+)/g)

     if mentioned
       unique = (array) ->


axis mouseover labels now respect affixes

--- src/viz/types/helpers/graph/includes/mouse.coffee
+++ src/viz/types/helpers/graph/includes/mouse.coffee
@@ -125,7 +125,8 @@
     .text (d) ->
       axis = vars.axes.stacked or d
       val  = fetchValue vars, node, vars[axis].value
-      vars.format.value val, {key: vars[axis].value, vars: vars}
+      vars.format.value val,
+        key: vars[axis].value, vars: vars, labels: vars[axis].affixes.value

   if timing
     texts.transition().duration(timing).delay timing


backup before removing from disk

backup before removing from disk

--- src/HTML5Slider.Slide.coffee
+++ src/HTML5Slider.Slide.coffee
@@ -43,8 +43,8 @@
         elem.addEventListener 'click', action.bind(@, false)

     initButtons: ->
-        @initButton @config.next, @nextSlide if @config.prev
-        @initButton @config.prev, @prevSlide if @config.prev
+        @initButton @config.next, @prevSlide if @config.next
+        @initButton @config.prev, @nextSlide if @config.prev

     hideChild: (child) ->
         @data.anim.hide child
@@ -84,12 +84,17 @@
         lastNumber  = setOn - 1
         chLength    = @data.children.length

+        if setOn > @data.currSlide
+            lastNumber = setOn - 1
+        else
+            lastNumber = setOn + 1
+
         if setOn >= chLength
             setOn = 0
         else if setOn < 0
             setOn       = chLength - 1
             lastNumber  = 0
-        else if setOn is 0
+        else if setOn is 0 and setOn > @data.currSlide
             lastNumber = chLength - 1

         lastSlide   = @data.children[lastNumber]


add version

add version

--- lib/dobi.coffee
+++ lib/dobi.coffee
@@ -154,7 +154,8 @@

   # check your dobi version
   when 'version'
-    exit 'not available yet'
+    pkg = require path.join '..', 'package'
+    exit pkg.version

   # check your authentication status
   when 'whoami'


Correct names of some tests.

Correct names of some tests.

--- test/integration/datatypes-in-results-test.coffee
+++ test/integration/datatypes-in-results-test.coffee
@@ -92,7 +92,7 @@
 exports.smallMoneyNegative = (test) ->
   execSql(test, "select cast(-1.22229 as smallmoney)", -1.2223)

-exports.smallMoneyN = (test) ->
+exports.smallMoneyNull = (test) ->
   execSql(test, "select cast(null as smallmoney)", null)

 exports.money = (test) ->
@@ -107,7 +107,7 @@
 exports.moneyLargeNegative = (test) ->
   execSql(test, "select cast(-123456789012345.11 as money)", -123456789012345.11)

-exports.moneyN = (test) ->
+exports.moneyNull = (test) ->
   execSql(test, "select cast(null as money)", null)

 exports.varchar = (test) ->


Remove trailing whitespace

Remove trailing whitespace

--- src/beautifiers/black.coffee
+++ src/beautifiers/black.coffee
@@ -15,7 +15,7 @@
       homepage: "https://github.com/ambv/black"
       installation: "https://github.com/ambv/black#installation"
       version: {
-        parse: (text) ->
+        parse: (text) ->
           # Try to read beta values, eg "black, version 18.6b4" -> 18.6.4
           try
             text.match(/black, version (\d+\.\d+)/)[1] + "." + text.match(/b(\d+)$/)[1]


Remove unnecessary include

Remove unnecessary include

--- src/components/Repeat.coffee
+++ src/components/Repeat.coffee
@@ -1,5 +1,4 @@
 noflo = require "../../lib/NoFlo"
-util = require "util"

 class Repeat extends noflo.Component


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -17,7 +17,7 @@
     # Handle state change
     if requestedState?
       I.currentState.trigger "exit", requestedState
-      self.trigger 'stateExited',
+      self.trigger 'stateExited', I.c

       previousState = I.currentState
       I.currentState = requestedState


try new current tab selector

try new current tab selector

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -223,10 +223,7 @@
   , cb

 getCurrentTab = (cb) ->
-  chrome.tabs.query
-    windowId: chrome.windows.WINDOW_ID_CURRENT
-    active: true
-  , (tabs) ->
+  chrome.tabs.query { currentWindow: true, active: true }, (tabs) ->
     cb tabs[0]

 removeTabsPred = (pred, cb) ->


remove sag if nothing to show

remove sag if nothing to show

--- src/scripts/directives/artist-view/artist-view.coffee
+++ src/scripts/directives/artist-view/artist-view.coffee
@@ -4,47 +4,51 @@
     templateUrl: "partials/artist-view.html"
     link: (scope, element) ->
       render = (data) ->
-        votes = [
-          {
-            value: data[scope.activeSong].likes
-            class: "like"
-          }
-          {
-            value: data[scope.activeSong].dislikes
-            class: "dislike"
-          }
-        ]
+        svg = d3.select ".song-chart svg"

-        svg = d3.select ".song-chart svg"
-        radius = 100
-        pi = Math.PI
-        arc = d3.svg.arc()
-          .outerRadius radius
-        pie = d3.layout.pie()
-          .sort null
-          .startAngle -90 * (pi / 180)
-          .endAngle 90 * (pi / 180)
-          .value (d) -> d.value
+        if data[scope.activeSong].likes is 0 and data[scope.activeSong].dislikes is 0
+          svg.select(".chart").selectAll("*").remove()
+        else
+          votes = [
+            {
+              value: data[scope.activeSong].likes
+              class: "like"
+            }
+            {
+              value: data[scope.activeSong].dislikes
+              class: "dislike"
+            }
+          ]

-        chart = svg.selectAll ".chart"
-          .data [votes]
+          radius = 100
+          pi = Math.PI
+          arc = d3.svg.arc()
+            .outerRadius radius
+          pie = d3.layout.pie()
+            .sort null
+            .startAngle -90 * (pi / 180)
+            .endAngle 90 * (pi / 180)
+            .value (d) -> d.value

-        chart
-          .enter()
-            .append "g"
-            .attr "class", "chart"
-            .attr "transform", "translate(#{radius}, #{radius})"
+          chart = svg.selectAll ".chart"
+            .data [votes]

-        slices = chart.selectAll ".slice"
-          .data pie
+          chart
+            .enter()
+              .append "g"
+              .attr "class", "chart"
+              .attr "transform", "translate(#{radius}, #{radius})"

-        slices
-          .enter()
-            .append "path"
-            .attr "class", (d) -> "#{d.data.class} slice"
-        slices
-          .transition().duration(1000)
-            .attr "d", arc
+          slices = chart.selectAll ".slice"
+            .data pie
+
+          slices
+            .enter()
+              .append "path"
+              .attr "class", (d) -> "#{d.data.class} slice"
+          slices
+            .transition().duration(1000)
+              .attr "d", arc

       scope.$watch "render", (value) ->
         render scope.songs if value isnt 0


heroku: Move app specific start checks out

--- src/heroku.coffee
+++ src/heroku.coffee
@@ -48,7 +48,7 @@
   return str.indexOf(prefix) == 0

-eventsFromLog = (logdata) ->
+eventsFromLog = (logdata, started) ->
   events = []

   # timestamp, target (app|heroku), action
@@ -95,7 +95,7 @@

     else if startsWith info, 'Starting process with command'
       events.push { type: 'process-starting', time: timestamp, dyno: dyno, msg: info }
-    else if startsWith info, 'noflo-runtime-msgflo started'
+    else if started info
       events.push { type: 'process-started', time: timestamp, dyno: dyno, msg: info }

     else if startsWith info, 'Process exited with status'
@@ -115,8 +115,6 @@
   state.startups = [] if not state.startups
   state.shutdowns = [] if not state.shutdowns

-
-  # TODO: reject invalid transitions.\
   # Note, they can happen initially because we don't generally know initial state
   if event.dyno
     # Dyno-specific events
@@ -167,13 +165,13 @@
     # FIXME: handle. Maybe outside/before. Particularly scale-to?

-analyzeStartups = (filename, callback) ->
+analyzeStartups = (filename, started, callback) ->
   fs = require 'fs'

   state = {}
   fs.readFile filename, {encoding: 'utf-8'}, (err, contents) ->
     return callback err if err
-    events = eventsFromLog contents
+    events = eventsFromLog contents, started
     #results = events.map (e) -> "#{e.dyno or ''} #{e.type}"
     for e in events
       applyEvent state, e
@@ -189,12 +187,10 @@
       shutdown_length: stops.length
     return callback null, results

-# FIXME: Rename to guv-heroku-workerstats
 # TODO: add a guv-update-jobstats tool
 # TODO: allow to separate between (module) loading time, and startup time
-# TODO: callculate whole delay from scaling to up by default
-# TODO: allow to calculate shutdown time
-# TODO: add tool for calculating 'waste' percentage. Ratio of time spent processing versus startup+shutdownl
+# TODO: calculate whole delay from scaling to up by default, and scaling down to down
+# TODO: add tool for calculating 'waste' percentage. Ratio of time spent processing versus startup+shutdown
 exports.startuptime_main = () ->
   program = require 'commander'

@@ -208,7 +204,13 @@
       filename = f
     .parse(process.argv)

-  analyzeStartups filename, (err, res) ->
+  # FIXME: make configurable
+  started = (info) ->
+    return true if startsWith info, 'noflo-runtime-msgflo started'
+    return true if info.indexOf('started using broker') != -1
+    return false
+
+  analyzeStartups filename, started, (err, res) ->
     throw err if err
     console.log res


(Feat) Added function to forcingly copy a single solution from the sharejs table into the solution.

--- src/manage.coffee
+++ src/manage.coffee
@@ -138,6 +138,30 @@
         })
       )).coerceTo("array").run con

+    # Store just one solution, does not perform checkings and is meant to be used for admins.
+    storeSolution: (sid) ->
+      rdb.table("Solutions").eqJoin("exercise", rdb.table("Exercises"))
+      .filter(rdb.row("left")("id").eq(sid))
+      .map( (solution) ->
+        solution.merge({
+          finalSolutionStored: rdb.table("Exercises").get(solution("right")("id")),
+          tasks: solution("right")("tasks").map( (task) ->
+            rdb
+            .db(config.sharejs.rethinkdb.db)
+            .table(config.sharejs.tableName)
+            .get(
+              rdb.add(
+                solution("left")("group").coerceTo("string"),
+                ":",
+                solution("left")("exercise").coerceTo("string"),
+                ":",
+                task("number").coerceTo("string")
+              )
+            )
+          )
+        })
+      ).coerceTo('array').run con
+
     updateOldestSolution: (minAge) ->
       minAge = minAge or 300
       if !config.sharejs?.rethinkdb?.db?


If you run mufin deploy without a taret, and deploy.sh exists, Muffin simply invokes the script.

--- src/command.coffee
+++ src/command.coffee
@@ -362,23 +362,39 @@
 task 'deploy', 'deploy the app', ->
   dest = opts.arguments[1]?.toLowerCase()
   platforms = ['heroku', 'jitsu', 'gae', 'gh-pages']
-  unless dest and dest in platforms
+  deployScriptExists = fs.existsSync('deploy.sh')
+
+  createDeployScript = ->
+    buildDir = sysPath.relative(process.cwd(), project.buildDir)
+    serverDir = sysPath.relative(process.cwd(), project.serverDir)
+
+    from = sysPath.join(__dirname, "deploy/#{dest}.sh")
+    to = sysPath.join(process.cwd(), 'deploy.sh')
+    deployScript = _.template(fs.readFileSync(from).toString(), {buildDir, serverDir})
+    fs.writeFileSync(to, deployScript)
+
+  invokeScript = ->
+    deploy = spawn 'sh', ['deploy.sh'], {stdio: 'inherit'}
+    deploy.on 'close', (code) ->
+      if code is 0
+        logging.info "The application has been successfully deployed."
+      else
+        logging.error "Failed to deploy the application."
+
+  fatal = ->
     logging.fatal "Must choose a platform from the following: heroku, jitsu, gae, gh-pages"

-  buildDir = sysPath.relative(process.cwd(), project.buildDir)
-  serverDir = sysPath.relative(process.cwd(), project.serverDir)
-
-  from = sysPath.join(__dirname, "deploy/#{dest}.sh")
-  to = sysPath.join(process.cwd(), 'deploy.sh')
-  deployScript = _.template(fs.readFileSync(from).toString(), {buildDir, serverDir})
-  fs.writeFileSync(to, deployScript)
-
-  deploy = spawn 'sh', ['deploy.sh'], {stdio: 'inherit'}
-  deploy.on 'close', (code) ->
-    if code is 0
-      logging.info "Deployment succeeded."
+  if dest
+    if dest in platforms
+      createDeployScript()
+      invokeScript()
     else
-      logging.error "Deployment failed."
+      fatal()
+  else
+    if deployScriptExists
+      invokeScript()
+    else
+      fatal()

 # Print the `--help` usage message and exit.
 usage = ->


Fixing broken kill reporter

Fixing broken kill reporter

Switched from a short time period to absolute start time on query.
Switched from zkillboard.com to beta.evekill.netto beta.evekill.net.
Kill reporter now takes variable window to report on.

--- kills.coffee
+++ kills.coffee
@@ -1,7 +1,7 @@
 # OscarRobo will fill the world with bad attitude.
 #
-# OscarRobo losses - Ask about recent losses by your corp
-# OscarRobo kills - Ask about recent kills by your corp
+# !losses - Ask about recent losses by your corp
+# !kills - Ask about recent kills by your corp
 # OscarRobo sleep - Mute OscarRobo's snark for 15 minutes
 # OscarRobo wake - Unmute OscarRobo's snark
 # I hate <target> - Oscar will insult <target>
@@ -12,7 +12,7 @@
 # TODO ensure these are set
 corp_id = process.env.HUBOT_CORP_ID
 email = process.env.HUBOT_OPERATOR_EMAIL
-version = "0.0.2"
+version = "0.0.3"

 #   depends on xml2json - so `npm install xml2json` already
 #   depends on moment - so `npm install moment` already
@@ -20,17 +20,21 @@
 parser = require('xml2json')
 zlib = require('zlib')
 moment = require('moment')
+Util = require('util')

 module.exports = (robot) ->
-  robot.respond /(kills|losses)/i, (msg) ->
+  robot.hear /^!(kills|losses) in ([0-9]+) ([a-zA-Z]+)/i, (msg) ->
     if !corp_id?
       msg.send "I can't do that. Someone forgot to tell me the corporation ID you care about."
       return

     msg.send "Give me a moment..."
     data = ""
-    # TODO Number of hours should be from ENV
-    msg.http("https://zkillboard.com/api/#{msg.match[1]}/corporationID/#{corp_id}/pastSeconds/108000/")
+    window = msg.match[2]
+    unit = msg.match[3].toLowerCase()
+    # TODO corp ID from brain by ticker
+    from_time = moment().utc().subtract(window, unit).format("YYYYMMDDHHmmss")
+    msg.http("https://beta.eve-kill.net/api/#{msg.match[1]}/corporationID/#{corp_id}/startTime/#{from_time}/")
       .headers('Accept': 'application/json', 'Accept-Encoding': 'gzip', 'User-Agent': "OscarRobo/#{version} [#{email}] (on Hubot/2.9.0)")
       .get( (err, req) ->
         req.addListener "response", (res)->
@@ -44,26 +48,18 @@
             parsedData = JSON.parse(data)
             if parsedData.error
               robot.emit 'error', parsedData.error.message
+              msg.send parsedData.error.message
               return

             if (parsedData.length == 0)
               msg.send "Nothing showing recently."
               return

-            robot.http("https://api.eveonline.com/Eve/TypeName.xml.aspx?ids=#{parsedData[0].victim.shipTypeID}")
-              .get() (err2, res2, xml) ->
-                if res2.statusCode isnt 200 or err2
-                  robot.emit 'error', err2, res2
-                  return
-                ship = JSON.parse(parser.toJson(xml)).eveapi.result.rowset.row.typeName
-                msg.send "#{parsedData[0].victim.characterName} died last in a #{ship} " +
-                  "and there are #{parsedData.length - 1} other #{msg.match[1]} recently."
+            for idx in [0..parsedData.length-1]
+              report_kill(robot, msg, parsedData[idx])
       )()
-  robot.hear /^kills$|^losses$/i, (msg) ->
-    if (awake(robot))
-      msg.send "you didn't say Simon Says!"

-  robot.hear /damn|fucking/i, (msg) ->
+  robot.hear /damn |fucking /i, (msg) ->
     if (awake(robot))
       msg.send msg.random schadenfreude

@@ -95,7 +91,7 @@
     if (awake(robot))
       msg.send "http://imagemacros.files.wordpress.com/2010/02/roger_american_dad_technical_problems.png"

-  robot.respond /stfu|shut up|mute|sleep/i, (msg) ->
+  robot.respond /stfu|shut up|mute|sleep|diaf/i, (msg) ->
     wake = moment().add(15, 'm')
     robot.brain.set 'sleepUntil', wake
     msg.send "Do you want me to sit in a corner and rust or just fall apart where I'm standing?"
@@ -109,6 +105,24 @@

 #semi-mute
 #if (msg.random([0..10]) < 3)
+
+report_kill = (robot, msg, kill) ->
+  name_for_id(robot, msg, kill.victim.shipTypeID, (msg, ship) ->
+    msg.send "#{kill.victim.characterName} died last in a #{ship}" # + " https://beta.eve-kill.net/kill/#{kill.killID}"
+  )
+
+name_for_id = (robot, msg, id, callback) ->
+  name = robot.brain.get "itemID.#{id}"
+  if name?
+    callback(msg, name)
+  else
+    msg.http("https://api.eveonline.com/Eve/TypeName.xml.aspx?ids=#{id}")
+      .get() (err2, res2, xml) ->
+        if res2.statusCode isnt 200 or err2
+          return '<unknown: #{id}>'
+        tree = JSON.parse(parser.toJson(xml))
+        robot.brain.set "itemID.#{id}", tree.eveapi.result.rowset.row.typeName
+        callback(msg, tree.eveapi.result.rowset.row.typeName)

 awake = (robot) ->
   wake = robot.brain.get 'sleepUntil'


remove global process

remove global process

--- lib/connection/process.coffee
+++ lib/connection/process.coffee
@@ -134,7 +134,6 @@
     conn.proc.on 'exit', (code, signal) =>
       client.stderr "Julia has stopped"
       if not @useWrapper then client.stderr ": #{code}, #{signal}"
-      delete @proc
       client.cancelBoot()
     conn.proc.stdout.on 'data', (data) => client.stdout data.toString()
     conn.proc.stderr.on 'data', (data) =>
@@ -148,7 +147,6 @@
     client.connected conn

   start: (port) ->
-    return if @proc?
     client.booting()
     @checkPath @jlpath()
       .then =>
@@ -168,7 +166,7 @@
                                             .output[1].toString()) > 2
         if @useWrapper
           @getFreePort =>
-            @proc = child_process.spawn("powershell",
+            proc = child_process.spawn("powershell",
                                         ["-NoProfile", "-ExecutionPolicy", "bypass",
                                          "& \"#{@script "spawnInterruptible.ps1"}\"
                                          -cwd \"#{workingdir}\"
@@ -176,12 +174,12 @@
                                          -wrapPort #{@wrapPort}
                                          -jlpath \"#{@jlpath()}\"
                                          -boot \"#{@script 'boot.jl'}\""])
-            fn @proc
+            fn proc
           return
         else
           client.stderr "PowerShell version < 3 encountered. Running without wrapper (interrupts won't work)."
-      @proc = child_process.spawn(@jlpath(), ["-i", @script("boot.jl"), port], cwd: workingdir)
-      fn @proc
+      proc = child_process.spawn(@jlpath(), ["-i", @script("boot.jl"), port], cwd: workingdir)
+      fn proc

   getFreePort: (fn) ->
     server = net.createServer()


activity: refactor `CommentEditForm::submit` as 'Enter' event handler

--- client/Social/Activity/views/comments/editform.coffee
+++ client/Social/Activity/views/comments/editform.coffee
@@ -12,9 +12,7 @@
     @input.on 'EscapePerformed', @bound 'cancel'

-  submit: ->
-
-    body = @input.getValue().trim()
+  enter: (body) ->

     return  unless body.length


webapp/new project quota ui: only set the initial state once on mounting -- otherwise creating a project will have noupgrades due to a quick reset

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -645,9 +645,11 @@
             description_text : ''
             error            : ''

+    componentWillMount : ->
+        @setState(@getInitialUpgraderState())
+
     componentWillReceiveProps : (nextProps) ->
         # https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops
-        @setState(@getInitialUpgraderState())
         subs = @props.customer?.subscriptions?.total_count ? 0
         if subs > 0 and not @state["has_subbed"]
             @setState(has_subbed: true)


fix #2070

fix #2070

--- src/smc-webapp/sagews.coffee
+++ src/smc-webapp/sagews.coffee
@@ -2276,6 +2276,9 @@
     output_elements: () =>
         cm = @editor.codemirror
         v = []
+        if not cm?
+            # See https://github.com/sagemathinc/cocalc/issues/2070
+            return v
         for line in [0...cm.lineCount()]
             marks = cm.findMarksAt({line:line, ch:1})
             if not marks? or marks.length == 0


comment out distributed messaging test as it still fails randomly

--- tests/infrastructure/messaging.tests.coffee
+++ tests/infrastructure/messaging.tests.coffee
@@ -53,6 +53,7 @@
     )
     .expect(generatedEventsForCustomerRegistration())

+  '''
   it 'supports distributed messaging via a shared commits collection', (test, done) ->

     SecondApp = Space.Application.extend {
@@ -88,3 +89,4 @@
       expect(secondApp.publishedEvents).toMatch expectedEvents
     finally
       secondApp.stop()
+  '''


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -130,8 +130,8 @@
     JSON.parse(this.toString())
   catch e
     this.toString()
-
-String::startsWith
+
+String::startsWith = () ->

 ###*
 Returns a new string in Title Case.


Added proper reporter options support, along with interfaces to the suite.

--- lib/dork.coffee
+++ lib/dork.coffee
@@ -6,14 +6,14 @@
 #
 {Suite} = require './suite'
 {Test} = require './test'
-{StdoutReporter} = require
+{StdoutReporter} = require './reporters'

 DEFAULT_OPTIONS =
   global: false
-  #reporters: [StdoutReporter]
+  reporters: [new StdoutReporter()]

@@ -49,27 +49,61 @@
                 global.it = undefined
     @_options.global = value

-  after_all: =>
+  _option_reporters: (user_reporters) ->
+    indexOf = (list, target) -> return i for v, i in list when v is target
+
+    if @_options.reporters?
+      rem_reporters = @_options.reporters[..]
+      new_reporters = []
+      for reporter in user_reporters
+        if not (reporter in @_options.reporters)
+          new_reporters.push reporter
+        else
+          index = indexOf rem_reporters, reporter
+          rem_reporters.splice index, 0
+    else
+      rem_reporters = []
+      new_reporters = user_reporters
+
+    # Make the new reporters listen to the base suite.
+    for reporter in new_reporters
+      console.log 'Listening to reporter!'
+      reporter.listen @_base_suite
+
+    # And remove the old reporters.
+    for reporter in rem_reporters
+      reporter.remove @_base_suite

-  after_each: =>
+  after_all: (fn) =>
+    @_active_suite.add_after_all = new Runner fn

-  before_all: =>
+  after_each: (fn) =>
+    @_active_suite.add_after_each = new Runner fn

-  before_each: =>
+  before_all: (fn) =>
+    @_active_suite.add_before_all = new Runner fn
+
+  before_each: (fn) =>
+    @_active_suite.add_before_each = new Runner fn

   describe: (description, context_fn) =>
-    @_active_suite = new Suite description
+    old_suite = @_active_suite
+    new_suite = new Suite description
+    @_active_suite.add_suite new_suite

+    @_active_suite = new_suite
     context_fn()
+    @_active_suite = old_suite

-  it: (description, test_fn) =>
-    @_active_suite.add_test = new Test description
+  it: (args...) =>
+    @_active_suite.add_test new Test args...

   options: (options={}) ->
     for k, v of options
       if @["_option_#{k}"]? then @["_option_#{k}"](v)

   run: ->
+    @_base_suite.run()


Publish tcp ports 80 and 443 of the container.

Conflicts:
	src/application.coffee

Conflicts:
	src/application.coffee

--- src/application.coffee
+++ src/application.coffee
@@ -69,11 +69,17 @@
 			Volumes:
 				'/dev': {}
 			Env: _.map app.env, (v, k) -> k + '=' + v
+			ExposedPorts:
+				'80/tcp': {}
+				'443/tcp': {}
 		)
 	.then (container) ->
 		console.log('Starting container:', app.imageId)
 		container.startAsync(
 			Privileged: true
+			PortBindings:
+				'80/tcp': [ HostPort: '80' ]
+				'443/tcp': [ HostPort: '443' ]
 			Binds: [
 				'/dev:/dev'
 				'/var/run/docker.sock:/run/docker.sock'


fixed dumb bug

fixed dumb bug

--- src/application.coffee
+++ src/application.coffee
@@ -106,7 +106,7 @@
 	.then ([[apiKey], [uuid], [version], apps]) ->
 		apiKey = apiKey.value
 		uuid = uuid.value
-		version = uuid.value
+		version = version.value
 		###
 		# should I post 'resin-supervisor' version every 15-minutes?
 		# with something like


Remove unused method

Remove unused method

--- src/atom-window.coffee
+++ src/atom-window.coffee
@@ -36,5 +36,3 @@
   focus: -> @browserWindow.focus()

   isFocused: -> @browserWindow.isFocused()
-
-  show: -> @browserWindow.show()


Vue Router snippet

Vue Router snippet

Signed-off-by: Caddy Dz <cbc6199cd1cb93e9d3589fb9aa7ecaece7b37833@gmail.com>

--- src/atom/snippets.cson
+++ src/atom/snippets.cson
@@ -6,11 +6,13 @@
   'bstext':
     'prefix' : 'bstext'
     'body': '
\n\t$2:\n\t<input type="text" class="form-control" name="$1" id="$1">\n
\n$3'
+  'Vue JS Router':
+    'prefix' : 'router'
+    'body': '<router-link to="${1:url}">${2:title}</router-link>\n\t$3'
 '.source.js':
   'vue.component':
     'prefix' : 'vue.component'
     'body': 'Vue.component (\'${1:name}\', {\n\tprops: [\n\t\t\'${2:property}\',\n\t],\n\ttemplate: `\n\t\t${3:html}\n\t`,\n\tmethods: {\n\t\t${4:method_name}() {\n\t\t\t${5:// code}\n\t\t}\n\t}\n});\n$6'
-'.source.js':
   'new vue':
-    'prefix' : 'new vue'
-    'body': 'new Vue({\n\tel: \'${1:#app}\',\n\tdata: {\n\t\t${2:name} : \'${3:value}\',\n\t}\n});\n$7'
+  'prefix' : 'new vue'
+  'body': 'new Vue({\n\tel: \'${1:#app}\',\n\tdata: {\n\t\t${2:name} : \'${3:value}\',\n\t}\n});\n$7'


adding optional whitespace check to shipit; This enables github :shipit: listeners.

--- src/scripts/shipit.coffee
+++ src/scripts/shipit.coffee
@@ -39,9 +39,9 @@

   # Enable a looser regex if environment variable is set
   if process.env.HUBOT_SHIP_EXTRA_SQUIRRELS
-    regex = /ship(ping|z|s|ped)? it/i
+    regex = /ship(ping|z|s|ped)?\s*it/i
   else
-    regex = /ship it/i
+    regex = /ship\s*it/i

   robot.hear regex, (msg) ->
     msg.send msg.random squirrels


First cut at Shodan script

First cut at Shodan script

--- src/scripts/shodan.coffee
+++ src/scripts/shodan.coffee
@@ -20,8 +20,31 @@
 module.exports = (robot) ->
   robot.respond /shodan (.*)/i, (msg) ->
     shodan_term = msg.match[1].toLowerCase()
+    api_url = shodan_url + "/api/host?key=#{SHODAN_KEY}&ip=#{shodan_term}"

     robot.http(shodan_url + "/api/host?key=#{SHODAN_KEY}&ip=#{shodan_term}")
       #.query('ip': shodan_term, 'key': SHODAN_KEY)
       .get() (err, res, body) ->
-        msg.send body
+
+        shodan_json = JSON.parse body
+
+        if shodan_json.error
+          shodan_profile = "No Shodan information found for #{shodan_term}"
+
+        else
+
+          shodan_profile = """Shodan Result for #{shodan_term}
+          --------------------------------------------
+          - IP:  #{shodan_json.ip}
+          - Geo: #{shodan_json.city}, #{shodan_json.region_name}, #{shodan_json.country_name}
+
+          """
+
+          for host in shodan_json.data
+            banner_array = host.banner.split "\r\n"
+
+            banner_string = ""
+            banner_string += " - #{banner_item}\n" for banner_item in banner_array when banner_item != ''
+            shodan_profile += "\n~ #{host.ip}\n------\n- Hostname:     #{host.hostnames.toString()}\n- Organization: #{host.org}\n- Port:         #{host.port}\n- Banner:       \n#{banner_string}"
+
+        msg.send shodan_profile


OX-654 scroll to active tree/comment when switching to according tab

--- app/assets/javascripts/oxalis/view.coffee
+++ app/assets/javascripts/oxalis/view.coffee
@@ -72,6 +72,11 @@
                 =>return null)
               window.location.reload() )},
             {id : "cancel-button", label : "Cancel", callback : ( => @reloadDenied = true ) } ] )
+
+    $("a[href=#tab-comments]").on "shown", (event) =>
+      @updateActiveComment()
+    $("a[href=#tab-trees]").on "shown", (event) =>
+      @updateActiveTree()

     @updateComments()
     @updateTrees()
@@ -171,7 +176,7 @@
       if treeId != lastTreeId
         newContent.appendChild((
           $('<li>').append($('<i>', {"class": "icon-sitemap"}),
-          $('<span>', {"text": @model.route.getTree(treeId).name})))[0])
+          $('<span>', {"data-treeid": treeId, "text": @model.route.getTree(treeId).name})))[0])
         lastTreeId = treeId
       newContent.appendChild((
         $('<li>').append($('<i>', {"class": "icon-angle-right"}),
@@ -204,7 +209,12 @@

       # animate scrolling to the new comment
       $("#comment-container").animate({
-        scrollTop: newIcon.offset().top - $("#comment-container").offset().top + $("#comment-container").scrollTop()}, 500)
+        scrollTop: newIcon.offset().top - $("#comment-container").offset().top + $("#comment-container").scrollTop()}, 250)
+    else
+      activeTree = $("#comment-container span[data-treeid=#{@model.route.getActiveTreeId()}]")
+      if activeTree.length
+        $("#comment-container").animate({
+          scrollTop: activeTree.offset().top - $("#comment-container").offset().top + $("#comment-container").scrollTop()}, 250)

   updateActiveTree : ->
@@ -229,7 +239,7 @@

       # animate scrolling to the new tree
       $("#tree-list").animate({
-        scrollTop: newIcon.offset().top - $("#tree-list").offset().top + $("#tree-list").scrollTop()}, 500)
+        scrollTop: newIcon.offset().top - $("#tree-list").offset().top + $("#tree-list").scrollTop()}, 250)

   # avoid lags caused by frequent DOM modification


some more fixes to ensure the anagrams-displayed header is shown correctly and at the right times

--- app/assets/javascripts/personal.js.coffee
+++ app/assets/javascripts/personal.js.coffee
@@ -23,6 +23,7 @@
       body.css 'cursor', 'progress'
       filterings.passed = []
       filterings.failed = []
+      shown.hide()
       $('#trial').submit()
       $('#full input').first().attr 'disabled', 'disabled'
   $('#clear').click ->
@@ -31,6 +32,9 @@
     filterTable.hide()
     $(filterings.failed).show()
     filterings.passed = filterings.passed.concat filterings.failed
+    if filterings.passed.length
+      shown.find('span').text filterings.passed.length
+      shown.show()
     filterings.failed = []
   $('#filter').click ->
     addFilter()


Fixed ixor

Fixed ixor

--- src/builtin/set.coffee
+++ src/builtin/set.coffee
@@ -71,15 +71,11 @@
     return new Iterator(@)

   __ixor__: (set) ->
-    xorKeys = List()
-    for item in set.value
-      if not @__contains__(item)
-        xorKeys.append(item)
-    for item in @value.value
-      if not set.__contains__(item)
-        xorKeys.append(item)
+    diff1 = @__sub__(set)
+    diff2 = set.__sub__(xor)
+    xor = diff1.__or__(diff2)
     @clear()
-    for item in xorKeys.value
+    for item in xor.value
         @add(item)
     return @


End expanding when finding a resolved data structure

--- src/drafter.coffee
+++ src/drafter.coffee
@@ -221,6 +221,9 @@
           dataStructure.element = 'resolvedDataStructure'
           @appendResolved[name].push dataStructure

+    # Don't do anything if already resolved
+    return if elementType is 'resolvedDataStructure'
+
     # Don't expand data structures in place
     if elementType is 'dataStructure'
       newNode = deepcopy node


String comparator :D

String comparator :D

--- core/app/backbone/collections/favourite_topics.coffee
+++ core/app/backbone/collections/favourite_topics.coffee
@@ -2,9 +2,7 @@
   _.extend @prototype, Backbone.Factlink.ActivatableCollectionMixin

   model: Topic
-
-  # TODO: when updating Backbone this can just be a string 'slug_title'
-  comparator: (model) -> model.get('slug_title')
+  comparator: 'slug_title'

   initialize: (models, options) ->
     @user = options.user


Update .atom/config.cson after Atom 1.4.0 reformatting.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -1,19 +1,24 @@
 "*":
-  "exception-reporting":
-    userId: "3e6166fe-00f6-49c7-842f-659cef01c050"
-  "release-notes":
-    viewedVersion: "0.94.0"
-  welcome:
-    showOnStartup: false
-  metrics:
-    userId: "2e9001b50edf75df09b8160944167f2ca1822759"
-  editor:
-    fontSize: 11
-    showInvisibles: true
-    showIndentGuide: true
-    invisibles: {}
-    scrollPastEnd: true
-    backUpBeforeSaving: true
+  "atom-material-ui":
+    accentColor: "Pink"
+    compactTreeView: true
+    fonts: {}
+    panels:
+      depth: true
+      panelContrast: true
+    rippleAccentColor: true
+    slimScrollbar: true
+    tabSize: "Small"
+    tabs:
+      rippleAccentColor: true
+      tabSize: "Small"
+    treeView:
+      compactTreeView: true
+    ui:
+      accentColor: "Pink"
+      slimScrollbar: true
+  "autocomplete-plus":
+    useAlternateScoring: true
   core:
     disabledPackages: [
       "metrics"
@@ -33,41 +38,41 @@
       "nuclide-arcanist"
       "nuclide-blame"
     ]
+    packagesWithKeymapsDisabled: [
+      "jumpy"
+    ]
     themes: [
       "atom-material-ui"
       "seti-syntax"
     ]
-    packagesWithKeymapsDisabled: [
-      "jumpy"
-    ]
-  linter:
-    gutterPosition: "Left"
-    lintOnFly: false
-    showErrorTabLine: true
-    statusIconScope: "Line"
-    showErrorInline: false
-  "go-plus":
-    goPath: "~/.go"
-  "tree-view": {}
-  "autocomplete-plus":
-    useAlternateScoring: true
+  editor:
+    autoIndentOnPaste: false
+    backUpBeforeSaving: true
+    fontSize: 11
+    invisibles: {}
+    scrollPastEnd: true
+    showIndentGuide: true
+    showInvisibles: true
+  "exception-reporting":
+    userId: "3e6166fe-00f6-49c7-842f-659cef01c050"
   "file-types":
-    "^Baskfile$": "source.shell"
+    ".*.sshconfig": "source.ssh-config"
+    ".*muttrc": "source.shell"
+    ".zsh-theme$": "source.shell"
     "^.ackrc": "text.generic-config"
     "^.amethyst": "source.js"
     "^.bindlerc$": "source.shell"
     "^.curlrc": "text.generic-config"
     "^.gemrc": "text.generic-config"
+    "^.git.scmbrc": "source.shell"
     "^.gitconfig.local": "source.git-config"
     "^.gitignore": "source.git-config"
-    "^.git.scmbrc": "source.shell"
     "^.gntrc": "source.ini"
     "^.gvimrc.after": "source.viml"
     "^.hgrc*": "source.ini"
     "^.htoprc": "text.generic-config"
     "^.inputrc": "text.generic-config"
     "^.mailcap": "source.shell"
-    ".*muttrc": "source.shell"
     "^.notesrc": "source.shell"
     "^.offlineimaprc$": "source.ini"
     "^.profile$": "source.shell"
@@ -77,15 +82,14 @@
     "^.scmbrc$": "text.generic-config"
     "^.shared_env.*": "source.shell"
     "^.spacemacs$": "source.lisp"
-    ".*.sshconfig": "source.ssh-config"
     "^.tigrc$": "text.generic-config"
+    "^.vimrc.after$": "source.viml"
     "^.vimrc.before$": "source.viml"
-    "^.vimrc.after$": "source.viml"
     "^.wgetrc": "text.generic-config"
     "^.zprofile$": "source.shell"
-    ".zsh-theme$": "source.shell"
     "^.zshenv.local.*$": "source.shell"
     "^.zshrc.local.*$": "source.shell"
+    "^Baskfile$": "source.shell"
   "fuzzy-finder":
     ignoredNames: [
       "usr"
@@ -93,31 +97,28 @@
     ]
     preserveLastSearch: true
     useAlternateScoring: true
-  "symbols-tree-view": {}
+  "go-plus":
+    goPath: "~/.go"
+  jumpy:
+    highContrast: true
+  linter:
+    gutterPosition: "Left"
+    lintOnFly: false
+    showErrorInline: false
+    showErrorTabLine: true
+    statusIconScope: "Line"
   "linter-eslint":
     useGlobalEslint: true
+  metrics:
+    userId: "2e9001b50edf75df09b8160944167f2ca1822759"
   minimap:
     absoluteMode: true
+    autoToggle: false
     scrollAnimation: true
-    autoToggle: false
-  "atom-material-ui":
-    accentColor: "Pink"
-    compactTreeView: true
-    rippleAccentColor: true
-    slimScrollbar: true
-    tabSize: "Small"
-    ui:
-      accentColor: "Pink"
-      slimScrollbar: true
-    tabs:
-      rippleAccentColor: true
-      tabSize: "Small"
-    treeView:
-      compactTreeView: true
-    fonts: {}
-    panels:
-      panelContrast: true
-      depth: true
-  jumpy:
-    highContrast: true
+  "release-notes":
+    viewedVersion: "0.94.0"
+  "symbols-tree-view": {}
+  "tree-view": {}
   "vim-mode": {}
+  welcome:
+    showOnStartup: false


remove unused variable

remove unused variable

--- lib/main.coffee
+++ lib/main.coffee
@@ -4,7 +4,6 @@
 module.exports = InnoSetupCore =
   subscriptions: null
   which: null
-  prefix: null

   activate: (state) ->
     {CompositeDisposable} = require 'atom'
@@ -67,7 +66,5 @@

     if os.platform() is 'win32'
       @which  = "where"
-      @prefix = "/"
     else
       @which  = "which"
-      @prefix = "-"


Add maxVisibleSuggestions

Add maxVisibleSuggestions

--- lib/main.coffee
+++ lib/main.coffee
@@ -21,22 +21,30 @@
       title: 'Maximum Suggestions'
       description: 'The list of suggestions will be limited to this number.'
       type: 'integer'
+      default: 200
+      minimum: 1
+      order: 3
+    maxVisibleSuggestions:
+      title: 'Maximum Visible Suggestions'
+      description: 'The autocomplete popup will only show this many suggestions.'
+      type: 'integer'
       default: 10
-      order: 3
+      minimum: 1
+      order: 4
     confirmCompletion:
       title: 'Keymap For Confirming A Suggestion'
       description: 'You should use the key(s) indicated here to confirm a suggestion from the suggestion list and have it inserted into the file.'
       type: 'string'
       default: 'tab'
       enum: ['tab', 'enter', 'tab and enter']
-      order: 4
+      order: 5
     navigateCompletions:
       title: 'Keymap For Navigating The Suggestion List'
       description: 'You should use the keys indicated here to select suggestions in the suggestion list (moving up or down).'
       type: 'string'
       default: 'up,down'
       enum: ['up,down', 'ctrl-p,ctrl-n']
-      order: 5
+      order: 6
     fileBlacklist:
       title: 'File Blacklist'
       description: 'Suggestions will not be provided for files matching this list.'
@@ -44,7 +52,7 @@
       default: ['.*']
       items:
         type: 'string'
-      order: 6
+      order: 7
     scopeBlacklist:
       title: 'Scope Blacklist'
       description: 'Suggestions will not be provided for scopes matching this list. See: https://atom.io/docs/latest/advanced/scopes-and-scope-descriptors'
@@ -52,55 +60,55 @@
       default: []
       items:
         type: 'string'
-      order: 7
+      order: 8
     includeCompletionsFromAllBuffers:
       title: 'Include Completions From All Buffers'
       description: 'For grammars with no registered provider(s), FuzzyProvider will include completions from all buffers, instead of just the buffer you are currently editing.'
       type: 'boolean'
       default: false
-      order: 8
+      order: 9
     strictMatching:
       title: 'Use Strict Matching For Built-In Provider'
       description: 'Fuzzy searching is performed if this is disabled; if it is enabled, suggestions must begin with the prefix from the current word.'
       type: 'boolean'
       default: false
-      order: 9
+      order: 10
     minimumWordLength:
       description: "Only autocomplete when you've typed at least this many characters."
       type: 'integer'
       default: 1
-      order: 10
+      order: 11
     enableBuiltinProvider:
       title: 'Enable Built-In Provider'
       description: 'The package comes with a built-in provider that will provide suggestions using the words in your current buffer or all open buffers. You will get better suggestions by installing additional autocomplete+ providers. To stop using the built-in provider, disable this option.'
       type: 'boolean'
       default: true
-      order: 10
+      order: 12
     builtinProviderBlacklist:
       title: 'Built-In Provider Blacklist'
       description: 'Don\'t use the built-in provider for these selector(s).'
       type: 'string'
       default: '.source.gfm'
-      order: 11
+      order: 13
     backspaceTriggersAutocomplete:
       title: 'Allow Backspace To Trigger Autocomplete'
       description: 'If enabled, typing `backspace` will show the suggestion list if suggestions are available. If disabled, suggestions will not be shown while backspacing.'
       type: 'boolean'
       default: true
-      order: 12
+      order: 14
     suggestionListFollows:
       title: 'Suggestions List Follows'
       description: 'With "Cursor" the suggestion list appears at the cursor\'s position. With "Word" it appers at the beginning of the word that\'s being completed.'
       type: 'string'
       default: 'Cursor'
       enum: ['Cursor', 'Word']
-      order: 13
+      order: 15
     defaultProvider:
       description: 'Using the Symbol provider is experimental. You must reload Atom to use a new provider after changing this option.'
       type: 'string'
       default: 'Fuzzy'
       enum: ['Fuzzy', 'Symbol']
-      order: 14
+      order: 16

   # Public: Creates AutocompleteManager instances for all active and future editors (soon, just a single AutocompleteManager)
   activate: ->


:art: cleanup further

:art: cleanup further

--- lib/main.coffee
+++ lib/main.coffee
@@ -60,9 +60,6 @@
       'quick-highlight:toggle': => @toggle()
       'quick-highlight:clear':  => @clear()

-    subs.add atom.config.observe 'quick-highlight.decorate', (value) =>
-      @decorationPreference = value
-
     subs.add atom.workspace.observeTextEditors (editor) =>
       subs.add editor.onDidStopChanging =>
         return unless getEditor() is editor
@@ -107,6 +104,31 @@
     @refreshEditor(e) for e in getVisibleEditor()
     editor.setCursorBufferPosition point

+  refreshEditor: (editor) ->
+    @clearEditor editor
+    @renderEditor editor
+
+  renderEditor: (editor) ->
+    decorationStyle = atom.config.get('quick-highlight.decorate')
+    scanRange = getVisibleBufferRange(editor)
+    markerOptions = {invalidate: 'inside', persistent: false}
+
+    decorations = []
+    for keyword, color of @keyword2color
+      klass = "quick-highlight #{decorationStyle}-#{color}"
+      decorationOptions = {type: 'highlight', class: klass}
+
+      pattern = ///#{_.escapeRegExp(keyword)}///g
+      editor.scanInBufferRange pattern, scanRange, ({range}) ->
+        marker = editor.markBufferRange range, markerOptions
+        decorations.push editor.decorateMarker marker, decorationOptions
+    @editors.set(editor, decorations)
+
+  clearEditor: (editor) ->
+    if decorations = @editors.get(editor)
+      d.getMarker().destroy() for d in decorations
+      @editors.delete(editor)
+
   clear: ->
     @editors.forEach (decorations, editor) =>
       @clearEditor editor
@@ -115,39 +137,10 @@
     @colorIndex  = -1
     @statusBarManager?.clear()

-  refreshEditor: (editor) ->
-    @clearEditor editor
-    @renderEditor editor
-
-  renderEditor: (editor) ->
-    scanRange = getVisibleBufferRange(editor)
-
-    decorations = []
-    for keyword, color of @keyword2color
-      pattern = ///#{_.escapeRegExp(keyword)}///g
-      editor.scanInBufferRange pattern, scanRange, ({range}) =>
-        decorations.push @decorate(editor, range, color)
-
-    @editors.set(editor, decorations)
-
-  clearEditor: (editor) ->
-    if decorations = @editors.get(editor)
-      d.getMarker().destroy() for d in decorations
-      @editors.delete(editor)
-
   getCountForKeyword: (editor, keyword) ->
     count = 0
     editor.scan ///#{_.escapeRegExp(keyword)}///g, -> count++
     count
-
-  decorate: (editor, range, color) ->
-    marker = editor.markBufferRange range,
-      invalidate: 'inside'
-      persistent: false
-
-    editor.decorateMarker marker,
-      type: 'highlight'
-      class: "quick-highlight #{@decorationPreference}-#{color}"

   consumeStatusBar: (statusBar) ->
     return unless @statusBarManager?


Don't spell check large files (for now)

Don't spell check large files (for now)

--- lib/main.coffee
+++ lib/main.coffee
@@ -50,6 +50,9 @@
     @viewsByEditor = new WeakMap
     @contextMenuEntries = []
     @disposable = atom.workspace.observeTextEditors (editor) =>
+      # For now, just don't spell check large files.
+      return if editor.largeFileMode
+
       SpellCheckView ?= require './spell-check-view'

       # The SpellCheckView needs both a handle for the task to handle the


Only migrate config when it exists

Only migrate config when it exists

--- lib/main.coffee
+++ lib/main.coffee
@@ -104,7 +104,7 @@
   activate: ->
     # Upgrade to the new config key name
     oldMax = atom.config.get('autocomplete-plus.maxSuggestions')
-    if oldMax isnt 10
+    if oldMax? and oldMax isnt 10
       atom.config.transact ->
         atom.config.set('autocomplete-plus.maxVisibleSuggestions', oldMax)
         atom.config.unset('autocomplete-plus.maxSuggestions')


round instead of floor for remaining seconds

--- lib/misc.coffee
+++ lib/misc.coffee
@@ -60,7 +60,7 @@
     return unless dt > 1
     h = Math.floor dt/(60*60)
     m = Math.floor (dt -= h*60*60)/60
-    s = Math.floor (dt - m*60)
+    s = Math.round (dt - m*60)
     parts = [h, m, s]
     for i, dt of parts
       parts[i] = if dt < 10 then "0#{dt}" else "#{dt}"


add a config to disable update

add a config to disable update

--- views/components/settings/parts/plugin-config.cjsx
+++ views/components/settings/parts/plugin-config.cjsx
@@ -234,7 +234,7 @@
   onSelectOpenFolder: ->
     shell.openItem path.join PLUGIN_PATH, 'node_modules'
   componentDidMount: ->
-    @checkUpdate(@solveUpdate, true)
+    @checkUpdate(@solveUpdate, true) if config.get('poi.update.plugin', true)
   render: ->
     <form>
       <Divider text={__ 'Plugins'} />