Fixed indentation that was causing completion handler to never register

--- lib/service/dataLink.coffee
+++ lib/service/dataLink.coffee
@@ -196,60 +196,56 @@
     unless task.collectionName?
       return callback new Error "CollectionName not found"

-      collectionToProcess = collection task.collectionName
-      dataOp = ''
-
-      completionHandler = initCompletionHandler task, (err, result) ->
-        if err?
-          return callback(convertToError err)
-
-        callback null, result
-
-      if task.method is 'POST'
-        dataOp = 'onInsert'
-      else if task.method is 'PUT'
-        dataOp = 'onUpdate'
-      else if task.method is 'GET' and task.endpoint isnt '_count'
-        if task.entityId?
-          dataOp = 'onGetById'
-        else if task.query?
-          dataOp = 'onGetByQuery'
-        else
-          dataOp = 'onGetAll'
-      else if task.method is 'GET' and task.endpoint is '_count'
-        if task.query?
-          dataOp = 'onGetCountWithQuery'
-        else
-          dataOp = 'onGetCount'
-      else if task.method is 'DELETE'
-        if task.entityId?
-          dataOp = 'onDeleteById'
-        else if task.query?
-          dataOp = 'onDeleteByQuery'
-        else
-          dataOp = 'onDeleteAll'
+    collectionToProcess = collection task.collectionName
+    dataOp = ''
+
+    completionHandler = initCompletionHandler task
+
+    if task.method is 'POST'
+      dataOp = 'onInsert'
+    else if task.method is 'PUT'
+      dataOp = 'onUpdate'
+    else if task.method is 'GET' and task.endpoint isnt '_count'
+      if task.entityId?
+        dataOp = 'onGetById'
+      else if task.query?
+        dataOp = 'onGetByQuery'
       else
-        return callback new Error "Cannot determine data operation"
-
-      operationHandler = collectionToProcess.resolve dataOp
-
-      if operationHandler instanceof Error
-        return callback(convertToError operationHandler)
-
-      # TODO Need to handle runtime errors/unhandled exceptions - or do we?
-      taskDomain = domain.create()
-
-      taskDomain.on 'error', (err) =>
-        err.metadata = {}
-        err.metadata.unhandled = true
-        err.taskId = task.taskId
-        err.requestId = task.requestId
-        return callback(confvertToError operationHandler)
-
-      domainBoundOperationHandler = taskDomain.bind operationHandler
-
-
-      domainBoundOperationHandler task.request, completionHandler
+        dataOp = 'onGetAll'
+    else if task.method is 'GET' and task.endpoint is '_count'
+      if task.query?
+        dataOp = 'onGetCountWithQuery'
+      else
+        dataOp = 'onGetCount'
+    else if task.method is 'DELETE'
+      if task.entityId?
+        dataOp = 'onDeleteById'
+      else if task.query?
+        dataOp = 'onDeleteByQuery'
+      else
+        dataOp = 'onDeleteAll'
+    else
+      return callback new Error "Cannot determine data operation"
+
+    operationHandler = collectionToProcess.resolve dataOp
+
+    if operationHandler instanceof Error
+      return callback(convertToError operationHandler)
+
+    # TODO Need to handle runtime errors/unhandled exceptions - or do we?
+    taskDomain = domain.create()
+
+    taskDomain.on 'error', (err) =>
+      err.metadata = {}
+      err.metadata.unhandled = true
+      err.taskId = task.taskId
+      err.requestId = task.requestId
+      return callback(confvertToError operationHandler)
+
+    domainBoundOperationHandler = taskDomain.bind operationHandler
+
+
+    domainBoundOperationHandler task.request, completionHandler

   obj =
     collection: collection


Check length of array instead of truthiness

--- lib/spell-check-view.coffee
+++ lib/spell-check-view.coffee
@@ -44,9 +44,8 @@
     _.contains config.get('spell-check.grammars'), grammar

   destroyViews: ->
-    if @views
-      view.destroy() for view in @views
-      @views = []
+    while view = @views.shift()
+      view.destroy()

   addViews: (misspellings) ->
     for misspelling in misspellings


Properly unset null variable overrides

Properly unset null variable overrides

--- lib/tap-theme-server.coffee
+++ lib/tap-theme-server.coffee
@@ -124,10 +124,14 @@

 Meteor.methods
   'TAPtheme_updateLessVariable' : (key, val) ->
-    console.log 'updating', key,val
-    update = {}
-    update["rule_overrides.#{key}"] = val || null
-    ThemeCollection.update {_id:'main'}, {$set: update}
+    if val
+      update = {$set: {}}
+      update.$set["rule_overrides.#{key}"] = val
+    else
+      update = {$unset: {}}
+      update.$unset["rule_overrides.#{key}"] = ""
+
+    ThemeCollection.update {_id:'main'}, update
     updateTheme()

   'TAPtheme_updateCustomCSS' : updateTheme


revert since spec failed

revert since spec failed

--- lib/text-object-pair.coffee
+++ lib/text-object-pair.coffee
@@ -52,10 +52,25 @@
   supportCount: true

   # Return 'open' or 'close'
-  getPairState: ({match}) ->
-    switch
-      when match[1] then 'open'
-      when match[2] then 'close'
+  getPairState: ({matchText, range, match}) ->
+    switch match.length
+      when 2
+        @pairStateInBufferRange(range, matchText)
+      when 3
+        switch
+          when match[1] then 'open'
+          when match[2] then 'close'
+
+  pairStateInBufferRange: (range, char) ->
+    text = getLineTextToBufferPosition(@editor, range.end)
+    escapedChar = _.escapeRegExp(char)
+    bs = backSlashPattern
+    patterns = [
+      "#{bs}#{bs}#{escapedChar}"
+      "[^#{bs}]?#{escapedChar}"
+    ]
+    pattern = new RegExp(patterns.join('|'))
+    ['close', 'open'][(countChar(text, pattern) % 2)]

   getFilters: (from) ->
     filters = []
@@ -253,20 +268,6 @@
   @extend(false)
   allowForwarding: true
   allowNextLine: false
-
-  getPairState: ({matchText, range, match}) ->
-    @pairStateInBufferRange(range, matchText)
-
-  pairStateInBufferRange: (range, char) ->
-    text = getLineTextToBufferPosition(@editor, range.end)
-    escapedChar = _.escapeRegExp(char)
-    bs = backSlashPattern
-    patterns = [
-      "#{bs}#{bs}#{escapedChar}"
-      "[^#{bs}]?#{escapedChar}"
-    ]
-    pattern = new RegExp(patterns.join('|'))
-    ['close', 'open'][(countChar(text, pattern) % 2)]

 class DoubleQuote extends Quote
   @extend(false)


Update detectUserAgent not to store state

--- js/flight.coffee
+++ js/flight.coffee
@@ -15,6 +15,9 @@
   speed: '0.4s'
   back: false

+  # Public:
+  #
+  # Returns nothing.
   constructor: (options = {}) ->
     if options.transitionAnimation?
       @transitionAnimation = options.transitionAnimation
@@ -22,18 +25,14 @@
     if options.speed?
       @speed = options.speed

-    if options.mainMenu?
-       if typeof options.mainMenu is "string"
-          @mainMenu = document.querySelector options.mainMenu or @mainMenu
-        else
-          @mainMenu = options.mainMenu
-
-    @detectUserAgent()
-
-    if @os.android and @os.version <= "2.1" then @transitionAnimation = false
+    @mainMenu = options.mainMenu if options.mainMenu?
+    @mainMenu = document.querySelector @mainMenu if typeof @mainMenu is "string"

-    @hideUrlBar() if @hideUrlBar
-
+    os = @detectUserAgent()
+    @transitionAnimation = false if os.android and os.version <= '2.1'
+
+    if @hideUrlBar is true then @hideUrlBar()
+
   # Goes to page, transitionAnimation defines if transition happens or not
   goto: (targetPage, options) =>

@@ -166,23 +165,27 @@
     @currentPage.style.left = "100%"
     @isTransitioning = false

-  # detects user agent being used
+  # Public: Get a Hash of browser user agent information.
+  #
+  # Returns a Hash of user agent information.
   detectUserAgent: ->
     userAgent = window.navigator.userAgent
-    @os.webkit = (if userAgent.match(/WebKit\/([\d.]+)/) then true else false)
-    @os.android = (if userAgent.match(/(Android)\s+([\d.]+)/) or userAgent.match(/Silk-Accelerated/) then true else false)
-    if @os.android
+    os = {}
+    os.webkit = (if userAgent.match(/WebKit\/([\d.]+)/) then true else false)
+    os.android = (if userAgent.match(/(Android)\s+([\d.]+)/) or userAgent.match(/Silk-Accelerated/) then true else false)
+    if os.android
       result = userAgent.match(/Android (\d+(?:\.\d+)+)/)
-      @os.version = result[1]
-    @os.ipad = (if userAgent.match(/(iPad).*OS\s([\d_]+)/) then true else false)
-    @os.iphone = (if not @os.ipad and userAgent.match(/(iPhone\sOS)\s([\d_]+)/) then true else false)
-    @os.webos = (if userAgent.match(/(webOS|hpwOS)[\s\/]([\d.]+)/) then true else false)
-    @os.touchpad = (if @os.webos and userAgent.match(/TouchPad/) then true else false)
-    @os.ios = @os.ipad or @os.iphone
-    @os.blackberry = (if userAgent.match(/BlackBerry/) or userAgent.match(/PlayBook/) then true else false)
-    @os.opera = (if userAgent.match(/Opera Mobi/) then true else false)
-    @os.fennec = (if userAgent.match(/fennec/i) then true else false)
-    @os.desktop = not (@os.ios or @os.android or @os.blackberry or @os.opera or @os.fennec)
+      os.version = result[1]
+    os.ipad = (if userAgent.match(/(iPad).*OS\s([\d_]+)/) then true else false)
+    os.iphone = (if not os.ipad and userAgent.match(/(iPhone\sOS)\s([\d_]+)/) then true else false)
+    os.webos = (if userAgent.match(/(webOS|hpwOS)[\s\/]([\d.]+)/) then true else false)
+    os.touchpad = (if os.webos and userAgent.match(/TouchPad/) then true else false)
+    os.ios = os.ipad or os.iphone
+    os.blackberry = (if userAgent.match(/BlackBerry/) or userAgent.match(/PlayBook/) then true else false)
+    os.opera = (if userAgent.match(/Opera Mobi/) then true else false)
+    os.fennec = (if userAgent.match(/fennec/i) then true else false)
+    os.desktop = not (os.ios or os.android or os.blackberry or os.opera or os.fennec)
+    os

   hideUrlBar: ->
     setTimeout ->


Update transition functions

Update transition functions

--- js/flight.coffee
+++ js/flight.coffee
@@ -76,65 +76,62 @@

     , 10

-  # performs slide animation transition
-  slideTransition: () ->
+  # Public: Perform a slide transition.
+  #
+  # Returns nothing.
+  slideTransition: ->
     @targetPage.style.display = "-webkit-box"

-    if @back is true
-      # must perform this initial tranform to get animation working in the next step
+    if @back
       @translate(@targetPage, "X", "-100%", "0ms")
       window.setTimeout =>
         @translate(@currentPage, "X", "100%")
       , 10
-
     else
-      #do forward transition
       @translate(@targetPage,"X","100%", "0ms")
-
       window.setTimeout =>
         @translate(@currentPage, "X", "-100%")
       , 10
-
-    # shortern the delay here to stop a gap appearing in android
+
     window.setTimeout =>
       @translate(@targetPage, "X", "0%")
       @currentPage.addEventListener "webkitTransitionEnd", @finishTransition, false
       @resetState()
     , 5

-  # slides panel from bottom to top and top to bottom
-  slideFromBottom: () ->
-     @targetPage.style.display = "-webkit-box"
-
-     if @back is true
-       # do reverse
-       window.setTimeout =>
-          @translate(@currentPage, "Y", "100%")
-       , 10
-
-     else
-       #do forward transition
-       @targetPage.style.display = "-webkit-box"
-       @translate(@targetPage, "Y", "100%","0ms")
-
-       window.setTimeout =>
-         @translate(@targetPage, "Y", "0%")
-       , 10
-
-     window.setTimeout =>
-       @targetPage.addEventListener "webkitTransitionEnd", @finishTransition, false
-     , 20
-     @resetState()
-
-  slideOutMenu: () =>
-    if @menuOpen is false
+  # Public: Perform a slide from bottom transition.
+  #
+  # Returns nothing.
+  slideFromBottom: ->
+    @targetPage.style.display = "-webkit-box"
+
+    if @back
+      window.setTimeout =>
+        @translate(@currentPage, "Y", "100%")
+      , 10
+    else
+      @targetPage.style.display = "-webkit-box"
+      @translate(@targetPage, "Y", "100%","0ms")
+      window.setTimeout =>
+        @translate(@targetPage, "Y", "0%")
+      , 10
+
+    window.setTimeout =>
+      @targetPage.addEventListener "webkitTransitionEnd", @finishTransition, false
+    , 20
+    @resetState()
+
+  # Public: Perform a slide out transition for the menu.
+  #
+  # Returns nothing.
+  slideOutMenu: =>
+    if @menuOpen
+      @translate(@currentPage, "X", "0%", "0.3s")
+      @menuOpen = false
+    else
       @mainMenu.style.display = "block"
-      @translate(@currentPage,"X", "250px","0.3s")
-
+      @translate(@currentPage, "X", "250px", "0.3s")
       @menuOpen = true
-    else
-      @translate(@currentPage,"X", "0%","0.3s")
-      @menuOpen = false

   # call on transition end
   finishTransition: =>


rendering platforms with graphics primitives

--- scrolling/level-controller.coffee
+++ scrolling/level-controller.coffee
@@ -8,6 +8,8 @@
         @description = level_descriptions[levelIdentifier]
         @setInitialPlayerPosition()

+        @addPlatformsToStage()
+
     update: (elapsedTime) ->

     render: (renderer) ->
@@ -15,3 +17,10 @@

     setInitialPlayerPosition: ->
         @player.jumpToPosition @description.startingPosition
+
+    addPlatformsToStage: ->
+        for {start, end, height} in @description.platforms
+            platform = new PIXI.Graphics()
+            platform.beginFill 0x000000
+            platform.drawRect start, height, end - start, 5
+            @stage.addChild platform


Add some documentation to AbstractLoader.

--- vendor/assets/javascripts/brainstem/loaders/abstract-loader.coffee
+++ vendor/assets/javascripts/brainstem/loaders/abstract-loader.coffee
@@ -15,6 +15,11 @@
     @deferred = $.Deferred()
     @deferred.promise(this)

+  ###*
+   * Setup the loader with a list of Brainstem specific loadOptions
+   * @param  {object} loadOptions Brainstem specific loadOptions (filters, include, only, etc)
+   * @return {object} externalObject that was created.
+  ###
   setup: (loadOptions) ->
     @_parseLoadOptions(loadOptions)
     @_createInternalObject()
@@ -22,6 +27,10 @@

     @externalObject

+  ###*
+   * Loads the model from memory or the server.
+   * @return {object} the loader's `externalObject`
+  ###
   load: ->
     if not @loadOptions
       throw "You must call #setup first or pass loadOptions into the constructor"
@@ -32,9 +41,18 @@
     else
       @_loadData()

+  ###*
+   * Returns the name of the collection that this loader maps to and will update in the storageManager.
+   * @return {string} name of the collection
+  ###
   getCollectionName: ->
     throw "Implement in your subclass"

+  ###*
+   * Parse supplied loadOptions, add defaults, transform them into appropriate structures, and pull out important pieces.
+   * @param  {object} loadOptions
+   * @return {object} transformed loadOptions
+  ###
   _parseLoadOptions: (loadOptions = {}) ->
     @originalOptions = _.clone(loadOptions)
     @loadOptions = _.clone(loadOptions)
@@ -55,6 +73,28 @@

     @loadOptions

+  ###*
+   * Creates a new proxy object on the loader (`internalObject`) that will serve as the middleman between the server and the external object.
+   * When the server responds with models/attributes it will update the internalObject first and when everything is complete it will update
+   * the externalObject.
+   * @return {undefined}
+  ###
+  _createInternalObject: ->
+    throw "Implement in your subclass"
+
+  ###*
+   * Creates a new object on the loader (`externalObject`) that will be returned by setup and resolved with the promise (as the first argument).  This object will not
+   * be updated until all loading (including additional loads) are complete.
+   * @return {undefined}
+  ###
+  _createExternalObject: ->
+    throw "Implement in your subclass"
+
+  ###*
+   * Checks to see if the current requested data is available in the caching layer.
+   * If it is available then update the externalObject with that data (via `_onLoadSuccess`).
+   * @return {[boolean|object]} returns false if not found otherwise returns the externalObject.
+  ###
   _checkCacheForData: ->
     if @loadOptions.only?
       @alreadyLoadedIds = _.select @loadOptions.only, (id) => @cachedCollection.get(id)?.associationsAreLoaded(@loadOptions.thisLayerInclude)
@@ -72,6 +112,10 @@

     return false

+  ###*
+   * Makes a GET request to the server via Backbone.sync with the built syncOptions.
+   * @return {object} externalObject that will be updated when everything is complete.
+  ###
   _loadData: ->
     jqXhr = Backbone.sync.call @internalObject, 'read', @internalObject, @_buildSyncOptions()

@@ -80,9 +124,18 @@

     @externalObject

+  ###*
+   * Decides whether or not the `only` filter should be applied in the syncOptions.
+   * Models will not use the `only` filter as they use show routes.
+   * @return {boolean} whether or not to use the `only` filter
+  ###
   _shouldUseOnly: ->
     @internalObject instanceof Backbone.Collection

+  ###*
+   * Generates the Brainstem specific options that are passed to Backbone.sync.
+   * @return {object} options that are passed to Backbone.sync
+  ###
   _buildSyncOptions: ->
     syncOptions =
       data: {}
@@ -109,13 +162,45 @@
     syncOptions.data.search = @loadOptions.search if @loadOptions.search
     syncOptions

+  ###*
+   * Called when the Backbone.sync successfully responds from the server.
+   * @param  {object} resp    JSON response from the server.
+   * @param  {string} _status
+   * @param  {object} _xhr    jQuery XHR object
+   * @return {undefined}
+  ###
   _onSyncSuccess: (resp, _status, _xhr) =>
     data = @_updateStorageManagerFromResponse(resp)
     @_onLoadSuccess(data)

-  _updateStorageManagerFromResponse: ->
-    throw "Implement in your subclass"
-
+  ###*
+   * Called when the server responds with data and needs to be persisted to the storageManager.
+   * @param  {object} resp JSON data from the server
+   * @return {[array|object]} array of models or model that was parsed.
+  ###
+  _updateStorageManagerFromResponse: (resp) ->
+    throw "Implement in your subclass"
+
+  ###*
+   * Updates the internalObject with the data in the storageManager and either loads more data or resolves this load.
+   * Called after sync + storage manager upadting.
+   * @param  {array|object} data array of models or model from _updateStorageManagerFromResponse
+   * @return {undefined}
+  ###
+  _onLoadSuccess: (data) ->
+    @_updateObjects(@internalObject, data)
+    @_calculateAdditionalIncludes()
+
+    if @additionalIncludes.length
+      @_loadAdditionalIncludes()
+    else
+      @_onLoadingCompleted()
+
+  ###*
+   * Called after the server responds with the first layer of includes to determine if any more loads are needed.
+   * It will only make additional loads if there were IDs returned during this load for a given association.
+   * @return {undefined}
+  ###
   _calculateAdditionalIncludes: ->
     @additionalIncludes = []

@@ -130,22 +215,31 @@
           ids: associationIds
           include: associationInclude

-  _onLoadSuccess: (data) ->
-    @_updateObjects(@internalObject, data)
-    @_calculateAdditionalIncludes()
-
-    if @additionalIncludes.length
-      @_loadAdditionalIncludes()
-    else
-      @_onLoadingCompleted()
-
+  ###*
+   * Called when all loading (including nested loads) are complete.
+   * Updates the `externalObject` with the data that was gathered and resolves the promise.
+   * @return {[type]} [description]
+  ###
   _onLoadingCompleted: =>
     @_updateObjects(@externalObject, @internalObject)
     @deferred.resolve(@externalObject)

-  _updateObjects: ->
-    throw "Implement in your subclass"
-
+  ###*
+   * Updates the object with the supplied data. Will be called:
+   *   + after the server responds, `object` will be `internalObject` and data will be the result of `_updateStorageManagerFromResponse`
+   *   + after all loading is complete, `object` will be the `externalObject` and data will be the `internalObject`
+   * @param  {object} object object that will receive the data
+   * @param  {object} data data that needs set on the object
+   * @return {undefined}
+  ###
+  _updateObjects: (object, data) ->
+    throw "Implement in your subclass"
+
+  ###*
+   * Loads the next layer of includes from the server.
+   * When all loads are complete, it will call `_onLoadingCompleted` which will resolve this layer.
+   * @return {undefined}
+  ###
   _loadAdditionalIncludes: ->
     promises = []

@@ -161,16 +255,36 @@

     $.when.apply($, promises).done(@_onLoadingCompleted)

+  ###*
+   * This needs to return a constructor for the model that associations will be compared with.
+   * This typically will be the current collection's model/current model constructor.
+   * @return {Brainstem.Model} [description]
+  ###
   _getModel: ->
     throw "Implement in your subclass"

-  _getModelsForAssociation: ->
-    throw "Implement in your subclass"
-
+  ###*
+   * This needs to return an array of models that correspond to the supplied association.
+   * @return {array} models that are associated with this association
+  ###
+  _getModelsForAssociation: (association) ->
+    throw "Implement in your subclass"
+
+  ###*
+   * Returns an array of IDs that need to be loaded for this association.
+   * @param  {string} association name of the association
+   * @return {array} array of IDs to fetch for this association.
+  ###
   _getIdsForAssociation: (association) ->
     models = @_getModelsForAssociation(association)
     _(models).chain().flatten().pluck("id").compact().uniq().sort().value()

+  ###*
+   * Parses the result of model.get(associationName) to either return a collection's models
+   * or the model itself.
+   * @param  {object|Backbone.Collection} obj result of calling `.get` on a model with an association name.
+   * @return {object|array} either a model object or an array of models from a collection.
+  ###
   _modelsOrObj: (obj) ->
     if obj instanceof Backbone.Collection
       obj.models


Use relative require paths

Use relative require paths

--- spec/autocomplete-spec.coffee
+++ spec/autocomplete-spec.coffee
@@ -1,6 +1,6 @@
 $ = require 'jquery'
-AutocompleteView = require 'autocomplete/lib/autocomplete-view'
-Autocomplete = require 'autocomplete/lib/autocomplete'
+AutocompleteView = require '../lib/autocomplete-view'
+Autocomplete = require '../lib/autocomplete'
 Buffer = require 'text-buffer'
 Editor = require 'editor'
 RootView = require 'root-view'


Use rootView.openSync

Use rootView.openSync

--- spec/autocomplete-spec.coffee
+++ spec/autocomplete-spec.coffee
@@ -5,7 +5,7 @@
 describe "Autocomplete", ->
   beforeEach ->
     window.rootView = new RootView
-    rootView.open('sample.js')
+    rootView.openSync('sample.js')
     rootView.simulateDomAttachment()

   describe "@activate()", ->
@@ -48,7 +48,7 @@

   beforeEach ->
     window.rootView = new RootView
-    editor = new Editor(editSession: project.open('sample.js'))
+    editor = new Editor(editSession: project.openSync('sample.js'))
     atom.activatePackage('autocomplete')
     autocomplete = new AutocompleteView(editor)
     miniEditor = autocomplete.miniEditor
@@ -120,7 +120,7 @@
       describe "when `autocomplete.includeCompletionsFromAllBuffers` is true", ->
         it "shows words from all open buffers", ->
           config.set('autocomplete.includeCompletionsFromAllBuffers', true)
-          project.open('sample.txt')
+          project.openSync('sample.txt')
           editor.getBuffer().insert([10,0] ,"extra:SO:extra")
           editor.setCursorBufferPosition([10,8])
           autocomplete.attach()
@@ -449,7 +449,7 @@

   it "includes completions for the scope's completion preferences", ->
     atom.activatePackage('css-tmbundle', sync: true)
-    cssEditor = new Editor(editSession: project.open('css.css'))
+    cssEditor = new Editor(editSession: project.openSync('css.css'))
     autocomplete = new AutocompleteView(cssEditor)

     cssEditor.attachToDom()


better slash-w.

better slash-w.


updated plufing comments

updated plufing comments

--- js/plugin.coffee
+++ js/plugin.coffee
@@ -7,24 +7,30 @@

 jQuery ->
     $.pluginName = (element, options) ->
+        # default plugin settings
         @defaults = {
             message     : 'hellow word' # setting description
             callback    : ->            # setting description
         }

-        # current state of the notification
+        ## private variables
+        # current state
         state = ''

-        # notification settings
+        ## public variables
+        # plugin settings
         @settings = {}

+        # jQuery version of DOM elment attached to the plugin
         @$element = $ element

         ## private methods
+        # set current state
         setState = (_state) ->
           state = _state

         ## public methods
+        #get current state
         @getState = ->
           state


 Merge branch 'master.failoverurl' into 'master'

Master.Failoverurl

--- server/lib/server/services-presence.coffee
+++ server/lib/server/services-presence.coffee
@@ -8,6 +8,8 @@

   # piece the url together from the config:
   url = "#{ protocol }//#{ hostname }#{ if port then ":#{port}" else "" }"
+  console.warn "Serving failover url", url
+  return url

 failover = (req, res, multi) ->
   if req.params.service in supportedServices
@@ -17,6 +19,11 @@
     yes
   else
     no
+
+sendBody = (res, body)->
+  res.set 'Content-Type', 'application/json'
+  return res.send 200, body
+

 module.exports = (req, res, next) ->
   { params, query } = req
@@ -37,8 +44,22 @@
       else ''
     }"

-    request(url)
-      .on('error', (err) -> next err  unless failover req, res, multi)
-      .pipe(res)
-
+    request url, (error, response, body) ->
+      if not error and response.statusCode is 200 and body
+
+        # we can query for multiple services
+        if multi
+          try kontrolResponse = JSON.parse body
+
+          if Array.isArray kontrolResponse
+            if kontrolResponse.length isnt 0
+              return sendBody res, body
+
+        # if query is not multiple then
+        # no need for checking array length
+        else return sendBody res, body
+
+      return  if failover req, res, multi
+      return next()
+
   else next()  unless failover req, res, multi


improving specs

improving specs

--- spec/file-watcher-spec.coffee
+++ spec/file-watcher-spec.coffee
@@ -3,58 +3,62 @@
 FileWatcher = require '../lib/file-watcher'

 describe "FileWatcher", ->
-  [editor, initialFileContents] = []
+  [editor, initialFileContents, testFile] = []

   beforeEach ->
-    atom.project.setPaths __dirname
-    @testFile = path.join(__dirname, 'testFile.md')
-    initialFileContents = fs.readFileSync(@testFile)
+    testFile = path.join(__dirname, 'testFile.md')
+    initialFileContents = fs.readFileSync(testFile)

     waitsForPromise ->
       atom.packages.activatePackage('file-watcher')

     waitsForPromise ->
-      atom.workspace.open @testFile
-
-    runs ->
-      editor = atom.workspace.getActiveTextEditor()
+      atom.workspace.open(path.join(__dirname, 'testFile.md'))

   it 'does nothing when there is no conflict', ->
+    editor = atom.workspace.getActiveTextEditor()
+    expect(editor.getPath()).toContain 'testFile.md'
     expect(editor.fileWatcher).toBeTruthy()
     expect(editor.fileWatcher.shouldPromptToReload()).toBeFalsy()

   it 'should prompt when there is a conflict', ->
+    editor = atom.workspace.getActiveTextEditor()
     editor.buffer.conflict = true
     expect(editor.fileWatcher).toBeTruthy()
     expect(editor.fileWatcher.shouldPromptToReload()).toBeTruthy()

   it 'should not prompt when there is a conflict and the user has disabled prompts', ->
+    editor = atom.workspace.getActiveTextEditor()
     editor.buffer.conflict = true
     expect(editor.fileWatcher).toBeTruthy()
     expect(editor.fileWatcher.shouldPromptToReload()).toBeTruthy()

   it 'should reload if the user selects reload', ->
+    editor = atom.workspace.getActiveTextEditor()
+
     spyOn(atom, 'confirm').andReturn(0)
     spyOn(editor.buffer, 'reload')

     editor.moveToEndOfLine()
     editor.insertText('test')
-    fs.appendFileSync(@testFile, 'more text')
+    fs.appendFileSync(testFile, 'more text')

     expect(editor.fileWatcher).toBeTruthy()
     expect(editor.buffer.reload).toHaveBeenCalled()

-    fs.writeFile(@testFile, initialFileContents)
+    fs.writeFile(testFile, initialFileContents)

   it 'should not reload if the user selects ignore', ->
+    editor = atom.workspace.getActiveTextEditor()
+
     spyOn(atom, 'confirm').andReturn(1)
     spyOn(editor.buffer, 'reload')

     editor.moveToEndOfLine()
     editor.insertText('test')
-    fs.appendFileSync(@testFile, 'more text')
+    fs.appendFileSync(testFile, 'more text')

     expect(editor.fileWatcher).toBeTruthy()
     expect(editor.buffer.reload).not.toHaveBeenCalled()

-    fs.writeFile(@testFile, initialFileContents)
+    fs.writeFile(testFile, initialFileContents)


:white_check_mark: Improve spec language

--- spec/path-scanner-spec.coffee
+++ spec/path-scanner-spec.coffee
@@ -126,7 +126,7 @@
           expect(paths).not.toContain path.join(rootPath, 'sample.js')
           expect(paths).toContain path.join(rootPath, 'sample.txt')

-      it "local directory inclusions override global directory exclusions", ->
+      it "allows a local directory inclusion to override a matching global exclusion", ->
         scanner = new PathScanner(rootPath, inclusions: ['dir'], globalExclusions: ['dir'])
         scanner.on('path-found', pathHandler = createPathCollector())
         scanner.on('finished-scanning', finishedHandler = jasmine.createSpy())
@@ -136,7 +136,7 @@
         runs ->
           expect(paths).toContain path.join(rootPath, 'dir', 'file7_ignorable.rb')

-      it "local directory inclusions don't override global subdirectory exclusions", ->
+      it "doesn't allow a local directory inclusion to override a global exclusion of a subdirectory", ->
         scanner = new PathScanner(rootPath, inclusions: ['newdir'], globalExclusions: ['seconddir'])
         scanner.on('path-found', pathHandler = createPathCollector())
         scanner.on('finished-scanning', finishedHandler = jasmine.createSpy())
@@ -147,7 +147,7 @@
           expect(paths).toContain path.join(rootPath, 'newdir', 'deep_dir.js')
           expect(paths).not.toContain path.join(rootPath, 'newdir', 'seconddir', 'very_deep_dir.js')

-      it "local file inclusions override global file exclusions", ->
+      it "allows a local file inclusion to override a global file exclusion", ->
         scanner = new PathScanner(rootPath, inclusions: ['*.txt'], globalExclusions: ['*.txt', '.root' + path.sep])
         scanner.on('path-found', pathHandler = createPathCollector())
         scanner.on('finished-scanning', finishedHandler = jasmine.createSpy())


Test for show usages with no results

Test for show usages with no results

--- spec/python-tools-spec.coffee
+++ spec/python-tools-spec.coffee
@@ -33,6 +33,14 @@
           expect(editor.getSelectedBufferRanges()).toEqual([
             new Range(new Point(0, 4), new Point(0, 15)),
             new Range(new Point(3, 6), new Point(3, 17)),
+          ])
+
+    it "does alter current selection on no results", ->
+      editor.setCursorBufferPosition(new Point(3, 2))
+      waitsForPromise ->
+        pythonTools.jediToolsRequest('usages').then ->
+          expect(editor.getSelectedBufferRanges()).toEqual([
+              new Range(new Point(3, 2), new Point(3, 2))
           ])

   describe "when running the select string command", ->


Wait for package activation promise

Wait for package activation promise

--- spec/symbols-view-spec.coffee
+++ spec/symbols-view-spec.coffee
@@ -4,11 +4,11 @@
 TagGenerator = require '../lib/tag-generator'

 describe "SymbolsView", ->
-  [symbolsView, setArraySpy] = []
+  [symbolsView, setArraySpy, setErrorSpy, activationPromise] = []

   beforeEach ->
     atom.workspaceView = new WorkspaceView
-    atom.packages.activatePackage("symbols-view")
+    activationPromise = atom.packages.activatePackage("symbols-view")

     atom.workspaceView.attachToDom()
     setArraySpy = spyOn(SymbolsView.prototype, 'setArray').andCallThrough()
@@ -25,8 +25,13 @@
     it "initially displays all JavaScript functions with line numbers", ->
       atom.workspaceView.openSync('sample.js')
       atom.workspaceView.getActiveView().trigger "symbols-view:toggle-file-symbols"
-      symbolsView = atom.workspaceView.find('.symbols-view').view()
-      expect(symbolsView.loading).toHaveText 'Generating symbols...'
+
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        symbolsView = atom.workspaceView.find('.symbols-view').view()
+        expect(symbolsView.loading).toHaveText 'Generating symbols...'

       waitsFor ->
         setArraySpy.callCount > 0
@@ -44,7 +49,12 @@
     it "caches tags until the buffer changes", ->
       editor = atom.workspaceView.openSync('sample.js')
       atom.workspaceView.getActiveView().trigger "symbols-view:toggle-file-symbols"
-      symbolsView = atom.workspaceView.find('.symbols-view').view()
+
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        symbolsView = atom.workspaceView.find('.symbols-view').view()

       waitsFor ->
         setArraySpy.callCount > 0
@@ -80,7 +90,12 @@
     it "displays error when no tags match text in mini-editor", ->
       atom.workspaceView.openSync('sample.js')
       atom.workspaceView.getActiveView().trigger "symbols-view:toggle-file-symbols"
-      symbolsView = atom.workspaceView.find('.symbols-view').view()
+
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        symbolsView = atom.workspaceView.find('.symbols-view').view()

       waitsFor ->
         setArraySpy.callCount > 0
@@ -105,8 +120,13 @@
     it "shows an error message when no matching tags are found", ->
       atom.workspaceView.openSync('sample.txt')
       atom.workspaceView.getActiveView().trigger "symbols-view:toggle-file-symbols"
-      symbolsView = atom.workspaceView.find('.symbols-view').view()
-      setErrorSpy = spyOn(symbolsView, "setError").andCallThrough()
+
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        symbolsView = atom.workspaceView.find('.symbols-view').view()
+        setErrorSpy = spyOn(symbolsView, "setError").andCallThrough()

       waitsFor ->
         setErrorSpy.callCount > 0
@@ -167,7 +187,12 @@
       editor = atom.workspaceView.getActivePaneItem()
       editor.setCursorBufferPosition([0,2])
       atom.workspaceView.getActiveView().trigger 'symbols-view:go-to-declaration'
-      expect(editor.getCursorBufferPosition()).toEqual [0,2]
+
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        expect(editor.getCursorBufferPosition()).toEqual [0,2]

     it "moves the cursor to the declaration", ->
       atom.workspaceView.openSync("tagged.js")
@@ -176,6 +201,9 @@
       spyOn(SymbolsView.prototype, "moveToPosition").andCallThrough()
       atom.workspaceView.getActiveView().trigger 'symbols-view:go-to-declaration'

+      waitsForPromise ->
+        activationPromise
+
       waitsFor ->
         SymbolsView.prototype.moveToPosition.callCount == 1

@@ -188,11 +216,15 @@
       editor.setCursorBufferPosition([8,14])
       atom.workspaceView.getActiveView().trigger 'symbols-view:go-to-declaration'

-      symbolsView = atom.workspaceView.find('.symbols-view').view()
-      expect(symbolsView.list.children('li').length).toBe 2
-      expect(symbolsView).toBeVisible()
-      spyOn(SymbolsView.prototype, "moveToPosition").andCallThrough()
-      symbolsView.confirmed(symbolsView.array[0])
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        symbolsView = atom.workspaceView.find('.symbols-view').view()
+        expect(symbolsView.list.children('li').length).toBe 2
+        expect(symbolsView).toBeVisible()
+        spyOn(SymbolsView.prototype, "moveToPosition").andCallThrough()
+        symbolsView.confirmed(symbolsView.array[0])

       waitsFor ->
         SymbolsView.prototype.moveToPosition.callCount == 1
@@ -208,17 +240,27 @@
         editor = atom.workspaceView.getActivePaneItem()
         editor.setCursorBufferPosition([8,14])
         atom.workspaceView.getActiveView().trigger 'symbols-view:go-to-declaration'
-        symbolsView = atom.workspaceView.find('.symbols-view').view()
-        expect(symbolsView.list.children('li').length).toBe 1
-        expect(symbolsView.list.children('li:first').find('.primary-line')).toHaveText 'tagged.js'
+
+        waitsForPromise ->
+          activationPromise
+
+        runs ->
+          symbolsView = atom.workspaceView.find('.symbols-view').view()
+          expect(symbolsView.list.children('li').length).toBe 1
+          expect(symbolsView.list.children('li:first').find('.primary-line')).toHaveText 'tagged.js'

   describe "project symbols", ->
     it "displays all tags", ->
       atom.workspaceView.openSync("tagged.js")
       expect(atom.workspaceView.find('.symbols-view')).not.toExist()
       atom.workspaceView.trigger "symbols-view:toggle-project-symbols"
-      symbolsView = atom.workspaceView.find('.symbols-view').view()
-      expect(symbolsView.loading).toHaveText 'Loading symbols...'
+
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        symbolsView = atom.workspaceView.find('.symbols-view').view()
+        expect(symbolsView.loading).toHaveText 'Loading symbols...'

       waitsFor ->
         setArraySpy.callCount > 0
@@ -240,7 +282,12 @@

         it "doesn't open the editor", ->
           atom.workspaceView.trigger "symbols-view:toggle-project-symbols"
-          symbolsView = atom.workspaceView.find('.symbols-view').view()
+
+          waitsForPromise ->
+            activationPromise
+
+          runs ->
+            symbolsView = atom.workspaceView.find('.symbols-view').view()

           waitsFor ->
             setArraySpy.callCount > 0


major upgrades to the API ABC

major upgrades to the API ABC

--- library/membrane/api.coffee
+++ library/membrane/api.coffee
@@ -64,6 +64,14 @@
     # takes place. See the `Archetype::composition` hook definition
     # for more information. Keys are names, values can be anything.
     @COMPOSITION_EXPORTS = definitions: yes
+
+    # Walk over list of supported HTTP methods/verbs, defined in
+    # the `RestfulService` abstract base class member `SUPPORTED`
+    # and create a shorthand route definition for an every method.
+    # These shorthand definitions will greatly simplify making new
+    # routes, since they are much shorter than using a full blown
+    # signature of the `define` method in this abstract base class.
+    (do (m) => @[m] = -> @api m, arguments...) for m in @SUPPORTED

     # Process the already macted HTTP request according to the REST
     # specification. That is, see if the request method conforms to
@@ -82,7 +90,7 @@
         assert mw = @constructor.middleware().bind this
         signature = [request, response, variables...]
         message = "Executing Crossroads routing in %s"
-        intake = (func) => downstream processing: func
+        intake = (func) => @downstream processing: func
         go = (fn) => usp = intake fn; usp signature...
         go => mw(signature) (error, results, misc) =>
             assert expanded = _.clone variables or []
@@ -114,7 +122,9 @@
         p = (i, cn) -> i.limitation request, response, cn
         fails = "Service #{identify} fails some conditions"
         notify = "Running %s service conditional sequences"
+        message = "Polling %s service for Crossroads match"
         logger.debug notify.toString(), identify.toString()
+        logger.debug message.toString().cyan, identify
         return async.every conditions, p, (confirms) =>
             logger.debug fails.yellow unless confirms
             return decide false unless confirms is yes
@@ -136,11 +146,12 @@
     # This implementation sets up the internals of the API service
     # that will allow to properly expose and execute the methods.
     register: (kernel, router, next) ->
-        msgs = "Setting up Crossroads service in %s"
+        noted = "Setting up %s Crossroads routes in %s"
         invalidDefs = "invalid type of the definitions"
         emptyDoc = "the document sequence is not emptied"
         noCrossr = "unable to load a Crossroads library"
         noInh = "cannot be inherited from both toolkits"
+        createRouteWrapper = @createRouteWrapper.bind this
         try identify = @constructor.identify().underline
         assert not (try this.objectOf Screenplay), noInh
         assert crs = @crossroads = crossroads.create()
@@ -148,23 +159,34 @@
         assert _.isArray(defines or null), invalidDefs
         assert _.isEmpty(@constructor.doc()), emptyDoc
         assert _.isObject(@crossroads or 0), noCrossr
-        logger.debug msgs.yellow, identify.toString()
-        execute = (fn) -> async.each defines, fn, next
-        return execute (definition, next) => # register
-            register = "Adding Crossroads route %s to %s"
-            assert method = try definition.method or null
-            assert implement = definition.implement or 0
-            assert rules = rls = definition.rules or {}
-            assert not _.isEmpty mask = definition.mask
-            assert p = (mask.source or mask).underline
-            logger.debug register.magenta, p, identify
-            fr = (q) => q.method.toUpperCase() is method
-            rx = (r) => r.rules = rls; rls.request_ = fr
-            fx = (f) => rx crs.addRoute mask, f; next()
-            fx (shadow, parameters...) -> # the wrapper
-                assert (shadow.__isolated or 0) is yes
-                assert _.isObject shadow.__origin or 0
-                implement.apply shadow, parameters
+        assert amount = defines.length.toString().bold
+        try logger.debug noted.yellow, amount, identify
+        async.each defines, createRouteWrapper, next
+
+    # Part of the internal implementation of the API engine. It
+    # is used to create the wrapping around the Crossroads route
+    # handler. This wrapping is very tightly inegrated with this
+    # abstract base class internals, as well as with the internal
+    # design of some of the parent classes, namely `RestfulService`.
+    # The wrapping provides important boilerplate to set up scope.
+    createRouteWrapper: (definition, next) ->
+        register = "Adding Crossroads route %s to %s"
+        noCross = "no Crossroads router in the service"
+        assert _.isObject(crs = @crossroads), noCross
+        identify = @constructor.identify().underline
+        assert method = try definition.method or null
+        assert implement = definition.implement or 0
+        assert rules = rls = definition.rules or {}
+        assert not _.isEmpty mask = definition.mask
+        assert p = (mask.source or mask).underline
+        logger.debug register.magenta, p, identify
+        fr = (q) => q.method.toUpperCase() is method
+        rx = (r) => r.rules = rls; rls.request_ = fr
+        fx = (f) => rx crs.addRoute mask, f; next()
+        fx (shadow, parameters...) -> # the wrapper
+            assert (shadow.__isolated or 0) is yes
+            assert _.isObject shadow.__origin or 0
+            implement.apply shadow, parameters

     # Class directive that sets the specified documentations in
     # the documentation sequence that will be used & emptied when
@@ -224,11 +246,10 @@
     # for matching this API. Please consult with a `Crossroads`
     # package for information on the mask, which can be string
     # or a regular expression. Also, please see implementation.
-    this.define = this.api = (method, mask, ximplement) ->
+    this.define = this.api = (method, mask, implement) ->
         wrongMask = "a mask has to be string or regex"
         noImplement = "no implementation fn supplied"
         invalidMeth = "an HTTP method is not a string"
-        implement = _.find(arguments, _.isFunction)
         maskStr = _.isString(mask or null) or false
         maskReg = _.isRegExp(mask or null) or false
         assert previous = @definitions or new Array()


allows passing the rank scope to the autocomplete end-point from the select2 taxon concept selectors

--- app/assets/javascripts/admin/nomenclature_changes.js.coffee
+++ app/assets/javascripts/admin/nomenclature_changes.js.coffee
@@ -14,11 +14,17 @@
       url: '/admin/taxon_concepts/autocomplete'
       dataType: 'json'
       data: (query, page) ->
+        form = $(@).closest('form')
+        taxonomySelector = form && form.find('.taxonomy-selector')
+        rankSelector = form && form.find('.rank-selector')
         search_params:
           scientific_name: query
-          name_status: this.data('name-status-filter')
+          name_status: @data('name-status-filter')
           taxonomy:
-            id: this.data('taxonomy-id')
+            id: taxonomySelector && taxonomySelector.val() || @data('taxonomy-id')
+          rank:
+            id: rankSelector && rankSelector.val() || @data('rank-id')
+            scope: $(@).attr('data-rank-scope')
         per_page: 25
         page: 1
       results: (data, page) => # parse the results into the format expected by Select2.


Fixes #4291: save theme change on keyboard shortcut

--- app/assets/javascripts/app/controllers/_plugin/theme.coffee
+++ app/assets/javascripts/app/controllers/_plugin/theme.coffee
@@ -26,6 +26,7 @@
   toggleDarkMode: =>
     @set(
       theme: if document.documentElement.dataset.theme == 'dark' then 'light' else 'dark'
+      save: true
     )

   set: (data) ->


Accounts are not checked anymore when app starts

--- server/lib/accounts-poller.coffee
+++ server/lib/accounts-poller.coffee
@@ -5,7 +5,7 @@
 class AccountsPoller

     start: ->
-        @checkAllAccesses()
+        @prepareNextCheck()

     prepareNextCheck: ->
         # day after between 00:00am and 04:00am


Lightweight: Replaced or with existential operator.

--- server/lib/crawler/helpers.coffee
+++ server/lib/crawler/helpers.coffee
@@ -4,10 +4,10 @@

 getProfile = (account) ->
   {profile:{nickname, firstName, lastName, about, hash, avatar}} = account if account
-  fullName = firstName + " " + lastName  if firstName and lastName
+  fullName = firstName + " " + lastName ? (firstName and lastName)

   unless fullName
-    fullName = firstName or lastName or nickname
+    fullName = firstName ? lastName ? nickname

   # if firstname and lastname is empty, assign nickname to firstname.
   # it is used in profile.coffee
@@ -19,10 +19,10 @@
     fullName = "A koding user"

   userProfile =
-    nickname    : encoder.XSSEncode nickname  or "A koding nickname"
+    nickname    : encoder.XSSEncode nickname ? "A koding nickname"
     firstName   : encoder.XSSEncode firstName
     lastName    : encoder.XSSEncode lastName
-    about       : encoder.XSSEncode about or ""
+    about       : encoder.XSSEncode about ? ""
     hash        : hash or ''
     avatar      : avatar or no
     fullName    : encoder.XSSEncode fullName


Use a class for the season objects

Use a class for the season objects

--- src/Lidsys/Application/Controller/assets/lidsys/football-models.js.coffee
+++ src/Lidsys/Application/Controller/assets/lidsys/football-models.js.coffee
@@ -24,6 +24,20 @@

+window.FootballSeason = class FootballSeason
+    constructor: (season_data) ->
+        @year = null
+        @setFromApi(season_data) if season_data
+
+    getYear: ->
+        @year
+
+    setFromApi: (options) ->
+        @year = options.year
+
+
+
+
 window.FootballScheduleService = class FootballScheduleService
     constructor: (@$http, @$q, @teamService) ->
         @seasons = null
@@ -77,7 +91,12 @@
     loadSeasons: ->
         return @seasons if @seasons?
         @$http.get("/api/v1.0/football/seasons")
-            .success((response) => @seasons = response.seasons)
+            .success((response) =>
+                @seasons = {}
+                for own year, season_data of response.seasons
+                    season = new FootballSeason(season_data)
+                    @seasons[year] = season
+            )

     loadWeeks: (year) ->


added text for movementless journeys

added text for movementless journeys

--- tests/single_journey.coffee
+++ tests/single_journey.coffee
@@ -114,14 +114,15 @@
       equals journey1.state(), 'finished'
       equals vehicle.get('state'), 'stopped'

-# test 'An ignition on and off with no movement', ->
-#   messages = [{usn: Sensor.IGNITION_ON,  time: '01:10'},
-#    {usn: Sensor.IGNITION_OFF,       time: '01:11'}
-#   ].map (message) -> {usn: message.usn, datetime: "2011-08-27T#{message.time}:11Z"}
-#   vehicle.updateWithMessages(messages)
-#   atTime '2011-08-27T09:00:00Z', ->
-#     SC.run.sync()
-#     equal vehicle.getPath('journeys.length'), 0
+test 'An ignition on and off with no movement', ->
+  messages = messageFactory [
+    {usn: Sensor.IGNITION_ON,  time: '01:10'},
+    {usn: Sensor.IGNITION_OFF, time: '01:11'},
+  ]
+  vehicle.updateWithMessages(messages)
+  atTime '2011-08-27T09:00:00Z', ->
+    SC.run.sync()
+    equal vehicle.getPath('journeys.length'), 0

 test 'Random moving GPS blips', ->
   messages = messageFactory


Fix videoLoader. Run `load` for HTMLElement instead jQuery el.

--- app/assets/javascripts/uploadcare/utils/image-loader.coffee
+++ app/assets/javascripts/uploadcare/utils/image-loader.coffee
@@ -37,5 +37,6 @@
       .on('loadeddata', def.resolve)
       .on('error', def.reject)
       .attr('src', src)
+      .get(0)
       .load()
     def.promise()


Add masking to the patient_results view.

--- app/assets/javascripts/views/patient_results_view.js.coffee
+++ app/assets/javascripts/views/patient_results_view.js.coffee
@@ -18,7 +18,7 @@
   fetchTriggerPoint: 500 # fetch data when we're 500 pixels away from the bottom
   patientContext: (patient) ->
     _(patient.toJSON()).extend
-      formatted_birthdate: moment(patient.get('birthdate')).format('MM/DD/YYYY') if patient.get('birthdate')
+      formatted_birthdate: moment(patient.get('birthdate')).format(Handlebars.helpers.maskDate('MM/DD/YYYY')) if patient.get('birthdate')
       age: moment(patient.get('birthdate')).fromNow().split(' ')[0] if patient.get('birthdate')

   events:


More moving type/id to top

More moving type/id to top

This shall be the last one I missed during the unfucking process.

--- resources/assets/coffee/react/_components/comments-manager.coffee
+++ resources/assets/coffee/react/_components/comments-manager.coffee
@@ -95,7 +95,7 @@

   jsonStorageId: =>
-    "json-comments-manager-state-#{@props.componentProps?.commentableType}-#{@props.componentProps?.commentableId}"
+    "json-comments-manager-state-#{@props.commentableType}-#{@props.commentableId}"

   saveState: =>


increased timeout to get server token

increased timeout to get server token

--- tests/specs/05.relay.coffee
+++ tests/specs/05.relay.coffee
@@ -26,7 +26,7 @@

         r = new Relay(__globalTest.host)
         tm = Config.RELAY_TOKEN_TIMEOUT
-        Config.RELAY_TOKEN_TIMEOUT = 1
+        Config.RELAY_TOKEN_TIMEOUT = 2

         r.getServerToken().then ->


add \ before line return

add \ before line return

--- server/controllers/binaries.coffee
+++ server/controllers/binaries.coffee
@@ -177,8 +177,8 @@

                     dbHelper.remove binary, (err) =>
                         if err
-                            console.log "[Attachment] err: " +
-                                JSON.stringify err
+                            console.log "[Attachment] err: " + \
+                                                        JSON.stringify err
                             next err
                         else
                             res.send 204, success: true


change names

change names

--- shared/ironTableController.coffee
+++ shared/ironTableController.coffee
@@ -428,8 +428,8 @@
   saveRecord: (yesNo, rec) =>
     if yesNo
       if @collection().editOk(rec) and @checkRequiredFields(rec)
-        if @collection.methodOnSave
-          Meteor.call @collection.methodOnSave, @_sess("currentRecordId"), rec, (error) =>
+        if @collection.methodOnUpdate
+          Meteor.call @collection.methodOnUpdate, @_sess("currentRecordId"), rec, (error) =>
             if error
               console.log("Error updating " + @_recordName(), error)
               CoffeeAlerts.error("Error updating " + @_recordName() + " : #{error.reason}")


console log change about what has started

--- web/app.coffee
+++ web/app.coffee
@@ -104,4 +104,5 @@
 # Start the server.
 port = parseInt(process.env.PORT || process.env.VMC_APP_PORT || process.env.C9_PORT || 8001)
 app.listen port, null # app.address().port # null host will accept connections from other instances
-console.log "Express been started on :%s", port
+console.log "Astrolin has started on http://localhost:%s", port
+


fixed #42

fixed #42

--- src/js/core/playlist.coffee
+++ src/js/core/playlist.coffee
@@ -1,4 +1,4 @@
-do (root = this, factory = (utils, Events) ->
+do (root = @, factory = (utils, Events) ->
     class Playlist
         constructor: (options) ->
             @opts = $.extend({}, @defaults, options)
@@ -70,7 +70,7 @@

         prev: ->
             list = @list
-            i = $.inArray(this.cur, list)
+            i = $.inArray(@cur, list)
             l = list.length
             prev = i - 1

@@ -96,7 +96,7 @@

         next: ->
             list = @list
-            i = $.inArray(this.cur, list)
+            i = $.inArray(@cur, list)
             l = list.length
             next = i + 1

@@ -120,10 +120,10 @@
             @cur = list[next]

         setCur: (sid) ->
-            sid = sid + ''
+            sid = @_formatSid(sid)
             unless sid in @list
                 @add(sid)
-            @cur = '' + sid
+            @cur = sid

     Events.mixTo(Playlist)
     Playlist


Adjusting the checkbox javascript.

Adjusting the checkbox javascript.

--- sli/admin-tools/admin-rails/app/assets/javascripts/apps.js.coffee
+++ sli/admin-tools/admin-rails/app/assets/javascripts/apps.js.coffee
@@ -5,7 +5,6 @@
 jQuery ->
     $("#state-menu select").change ->
         selected = $(@).find("option:selected")
-        console.log("Changed to: " +selected.val())
         $.get("/lea?state=" + selected.val(), (data) ->
             $("#lea-menu").html(data)
             $("#lea-menu ul").trigger("change")
@@ -22,11 +21,8 @@
 jQuery ->
     $("#lea-menu ul li input").live 'change', ->
         id = $(@).parent().attr('id')
-        console.log $(@)
-
-        $(@).prop('checked', !$(@).checked)
         edorgs = getEdorgs()
-        if $(@).checked
+        if $(@).is(':checked')
             edorgs.push id
         else
             #Remove the element from the array
@@ -36,13 +32,11 @@
         $("input#app_authorized_ed_orgs").attr("value", JSON.stringify(edorgs))
 jQuery ->
     $("div.enable-disable a#enable-all").click ->
-        alert "Enable"
-        $("#lea-menu ul li input:not(:checked)").trigger("change")
+        $("#lea-menu ul li input:not(:checked)").click()
+        false
     $("div.enable-disable a#disable-all").click ->
-        alert "Disable"
-        $("#lea-menu ul li input:checked").trigger("change")
-    false
-
+        $("#lea-menu ul li input:checked").click()
+        false
 jQuery ->
   $("#applications tr:odd").addClass("odd")
   $("#applications tr:not(.odd)").hide()


save uid when migrating

save uid when migrating

--- util/migrateBoxToServer.coffee
+++ util/migrateBoxToServer.coffee
@@ -60,10 +60,14 @@
   # TODO: Add UID to box in DB
   console.log "Migrating passwd entry..."
   boxExec "id -u", box, user, (err, res, uid) ->
-    uid = uid.replace('\n', '')
-    exec "util/addUnixUser.sh #{box.name} #{uid}", (err, stdout, stderr) ->
-      checkVerboseAndPrint "migratePasswdEntry", err, stdout, stderr
-      callback()
+    box.uid = uid.replace('\n', '')
+    box.save (err) ->
+      if err?
+        checkVerboseAndPrint "box save error", err
+
+      exec "util/addUnixUser.sh #{box.name} #{box.uid}", (err, stdout, stderr) ->
+        checkVerboseAndPrint "migratePasswdEntry", err, stdout, stderr
+        callback()

 transferSSHKeys = (box, user) ->
   box.server = NEW_BOX_SERVER


Reorder control item to avoid misclick

Reorder control item to avoid misclick

--- views/components/info/control.cjsx
+++ views/components/info/control.cjsx
@@ -112,14 +112,14 @@
           <OverlayTrigger placement='right' overlay={<Tooltip id='poi-screenshot-dir-button'>{__ 'Open screenshot dir'}</Tooltip>}>
             <Button onClick={@handleOpenScreenshotFolder} bsSize='small'><FontAwesome name='photo' /></Button>
           </OverlayTrigger>
-          <OverlayTrigger placement='right' overlay={<Tooltip id='poi-adjust-button'>{__ 'Auto adjust'}</Tooltip>}>
-            <Button onClick={@handleJustifyLayout} onContextMenu={@handleUnlockWebview} bsSize='small'><FontAwesome name='arrows-alt' /></Button>
-          </OverlayTrigger>
           <OverlayTrigger placement='right' overlay={<Tooltip id='poi-always-on-top-button'>{if @state.alwaysOnTop then __ 'Dont always on top' else __ 'Always on top'}</Tooltip>}>
             <Button onClick={@handleSetAlwaysOnTop} bsSize='small' className={if @state.alwaysOnTop then 'active' else ''}><FontAwesome name={if @state.alwaysOnTop then 'arrow-down' else 'arrow-up'} /></Button>
           </OverlayTrigger>
           <OverlayTrigger placement='right' overlay={<Tooltip id='poi-always-on-top-button'>{if @state.resizeable then __ 'Not resizeable' else __ 'Resizeable'}</Tooltip>}>
             <Button onClick={@handleSetResizable} bsSize='small' className={if @state.resizeable then '' else 'active'}><FontAwesome name={if @state.resizeable then 'unlock-alt' else 'lock'} /></Button>
+          </OverlayTrigger>
+          <OverlayTrigger placement='right' overlay={<Tooltip id='poi-adjust-button'>{__ 'Auto adjust'}</Tooltip>}>
+            <Button onClick={@handleJustifyLayout} onContextMenu={@handleUnlockWebview} bsSize='small'><FontAwesome name='arrows-alt' /></Button>
           </OverlayTrigger>
         </div>
       </Collapse>


Fixed: request key was title instead of raw

--- server/models/bankoperation.coffee
+++ server/models/bankoperation.coffee
@@ -31,7 +31,7 @@
     date = new Date(operation.date).toISOString()
     params =
         key: [operation.bankAccount, date, \
-             operation.amount, operation.title]
+             operation.amount, operation.raw]
     BankOperation.request "allLike", params, callback

 BankOperation.destroyByAccount = (accountNum, callback) ->


pass extra arguments into special $1, $2, $n strings in the command

Example:
ring=goo --name=$1
hubot ring john` => `hubot goo --name=john

Extra arguments will just be appended:
hubot ring john test` => `hubot goo --name=john test

--- src/scripts/alias.coffee
+++ src/scripts/alias.coffee
@@ -13,6 +13,15 @@

 ALIAS_TABLE_KEY = 'hubot-alias-table'

+loadArgumentsInAction = (args, action) ->
+  argItems = args.trim().split(' ')
+
+  for val, i in argItems
+	  if action.indexOf('$'+(i+1)) > -1 then action = action.replace('$'+(i+1), val) else action += " #{val}"
+
+  action.trim()
+
+
 module.exports = (robot) ->
   receiveOrg = robot.receive
   robot.receive = (msg)->
@@ -23,9 +32,13 @@
       sp = RegExp.$2
       action = RegExp.$3
       rest = RegExp.$4
-      msg.text = "#{name}#{sp}#{table[action] || action}#{rest}" if action != 'alias'
+
+      if action != 'alias'
+        action = table[action] or action
+        msg.text = "#{name}#{sp}"
+        msg.text += loadArgumentsInAction(rest, action)
+
     console.log "Replace \"#{orgText}\" as \"#{msg.text}\"" if orgText != msg.text
-
     receiveOrg.bind(robot)(msg)

   robot.respond /alias(.*)$/i, (msg)->


increase pack migrate delay to 2 seconds

--- services/track-changes/pack.coffee
+++ services/track-changes/pack.coffee
@@ -29,7 +29,9 @@
 		if err? or result.nInserted != expect_nInserted or result.nRemoved != expect_nRemoved
 			console.log err, result
 			console.log 'nInserted', result.nInserted, 'nRemoved', result.nRemoved
-		callback(err, result)
+		setTimeout () ->
+			callback(err, result)
+		, 2000

 packDocHistory = (doc_id, callback) ->
 	util.log "starting pack operation for #{doc_id}"


reactivity: chtinput: prop types and default values

--- client/activity/lib/components/chatinputwidget/index.coffee
+++ client/activity/lib/components/chatinputwidget/index.coffee
@@ -16,19 +16,49 @@
   { TAB, ESC, ENTER, UP_ARROW, RIGHT_ARROW, DOWN_ARROW, LEFT_ARROW } = KeyboardKeys

   @propTypes =
-    onReady     : React.PropTypes.func
-    onResize    : React.PropTypes.func
-    placeholder : React.PropTypes.string
-    onChange    : React.PropTypes.func
-    tokens      : React.PropTypes.array
+    onResize                  : React.PropTypes.func
+    placeholder               : React.PropTypes.string
+    onChange                  : React.PropTypes.func
+    onEnter                   : React.PropTypes.func
+    onEsc                     : React.PropTypes.func
+    onRightArrow              : React.PropTypes.func
+    onDownArrow               : React.PropTypes.func
+    onTab                     : React.PropTypes.func
+    onLeftArrow               : React.PropTypes.func
+    onUpArrow                 : React.PropTypes.func
+    onDropboxItemSelected     : React.PropTypes.func
+    onDropboxItemConfirmed    : React.PropTypes.func
+    onDropboxClose            : React.PropTypes.func
+    onSelectBoxVisible        : React.PropTypes.func
+    onSelectBoxItemSelected   : React.PropTypes.func
+    onSelectBoxItemUnselected : React.PropTypes.func
+    onSelectBoxItemConfirmed  : React.PropTypes.func
+    onSelectBoxTabChange      : React.PropTypes.func
+    onSelectBoxClose          : React.PropTypes.func
+    onSelectBoxSearch         : React.PropTypes.func

   @defaultProps =
-    onReady     : kd.noop
-    onResize    : kd.noop
-    placeholder : ''
-    onChange    : kd.noop
-    tokens      : []
+    onResize                  : kd.noop
+    placeholder               : ''
+    onChange                  : kd.noop
+    onEnter                   : kd.noop
+    onEsc                     : kd.noop
+    onRightArrow              : kd.noop
+    onDownArrow               : kd.noop
+    onTab                     : kd.noop
+    onLeftArrow               : kd.noop
+    onUpArrow                 : kd.noop
+    onDropboxItemSelected     : kd.noop
+    onDropboxItemConfirmed    : kd.noop
+    onDropboxClose            : kd.noop
+    onSelectBoxVisible        : kd.noop
+    onSelectBoxItemSelected   : kd.noop
+    onSelectBoxItemUnselected : kd.noop
+    onSelectBoxItemConfirmed  : kd.noop
+    onSelectBoxTabChange      : kd.noop
+    onSelectBoxClose          : kd.noop
+    onSelectBoxSearch         : kd.noop

   componentDidMount: ->


Add world bank response data to world bank test

--- server/test/integration/indicatoration.coffee
+++ server/test/integration/indicatoration.coffee
@@ -116,8 +116,24 @@
     }
   )

-  worldBankResponse = {
-  }
+  worldBankResponse = [
+    {},
+    [
+      {
+        "indicator": {
+          "id": "AG.LND.FRST.ZS",
+          "value": "Forest area (% of land area)"
+        },
+        "country": {
+          "id": "MU",
+          "value": "Mauritius"
+        },
+        "value": "17.2512315270936",
+        "decimal": "1",
+        "date": "1969"
+      }
+    ]
+  ]

   requestGetStub = sinon.stub(request, 'get', (options, cb) ->
     cb(null, {body: JSON.stringify(worldBankResponse)})


Use Q.spread in Condition.interpretCondition

--- src/liquid/condition.coffee
+++ src/liquid/condition.coffee
@@ -80,5 +80,5 @@
     left = context.get(left)
     right = context.get(right)

-    Q.all([left, right]).then ([left, right]) =>
+    Q.spread [left, right], (left, right) =>
       operation @, left, right


fix keybindings for real

fix keybindings for real

--- keymaps/ink.cson
+++ keymaps/ink.cson
@@ -10,7 +10,6 @@

 'ink-console .editor':
   'enter': 'console:evaluate'
-  'alt-t': 'inline-results:toggle'

 'ink-console':
   'shift-space': 'unset!'
@@ -23,6 +22,7 @@

 'atom-text-editor:not([mini])':
   'escape': 'inline:clear-current'
+  'alt-t': 'inline-results:toggle'

 '.platform-darwin atom-text-editor:not([mini])':
   'cmd-i cmd-c': 'inline-results:clear-all'


createteam: add a team suggestion mechanism

--- servers/lib/server/handlers/createteam.coffee
+++ servers/lib/server/handlers/createteam.coffee
@@ -86,7 +86,22 @@
   ]

   async.series queue, (err) ->
-    return res.status(err.status).send err.message  if err
+
+    index = 1
+    generateTeamName = (next) ->
+      _slug = "#{slug}#{index++}"
+      JGroup.one { slug: _slug }, (_err, existingGroup) ->
+        if _err or not existingGroup
+          err.suggested = _slug
+          return res.status(err.status).json err
+        next { message: 'try again with new group slug' }
+
+    # if err means that this group exists, try to find a new group name to be
+    # suggested.
+    if err
+      return res.status(err.status).json err  unless err.status is 403
+      return async.retry 20, generateTeamName, ->
+        return res.status(err.status).json err

     # generating callback function to be used in both login and convert
     createGroup = createGroupKallback client, req, res, body


refactoring histogram render areas into functions

Former-commit-id: b173c243ce4b67f8bc0cc650f5ee00ec0aa6bd09 [formerly 005497881412480395a83eaf74170608d9996361] [formerly 9177c5df5a578bbb159bc557819d6afe4dfe350e [formerly 04a67eae412560a38368ae22d4c08e8ae0adbe34]] [formerly 41744a6f233442d3e9b8aeab38710656480b7fb0 [formerly 22a2d69abf17a82b6dab1a2bb4d09002f53bdab0] [formerly 80028ea3b83a0d9be281b429f6594d94854eed4d [formerly 80028ea3b83a0d9be281b429f6594d94854eed4d [formerly 142374f144311ecc1bffb5a6e73b657bb2a142cb]]]]
Former-commit-id: 74cc3706de0cfdecdb0ed12876f253fbb9448714 [formerly 943a296b31cdd5d057200c783388d973bfcbbccf] [formerly 1c40f5a69df99184abad82e95b2cf80d4ef45174 [formerly c72bae5e98fcbf6526df5e3a8c86a69c570f632e]]
Former-commit-id: 24c9582a9bc1f145cf06f7861006a07b2e0bee37 [formerly c1a4b2088c3cd19b58d7d3d3923f645d41a57454]
Former-commit-id: 60b4762a9fffb0693557a25769ade58c735c3329
Former-commit-id: 9a36d602bf87588dd15374922a6e98bde2fe8d78

--- @client/histogram.coffee
+++ @client/histogram.coffee
@@ -8,6 +8,15 @@
 #
 # The pros and cons can be filtered to specific opinion regions
 # (individual and collective).
+#
+# TODO:
+#   - is it correct to store selected_opinion, selected_opinions,
+#     highlighted_users at the histogram's key?
+#   - reconsider how "selection" is incorporated. Selection seems
+#     almost like a mixin. Now it is baked in, with the option to
+#     opt out if necessary, but the code wouldn't be very nice
+#     to work with to extend in a different direction other than
+#     selection.
 #
 ##
 # Props
@@ -132,27 +141,136 @@
       @local.avatar_size = avatar_radius * 2
       save @local

-    # There are a few avatar styles that might be applied depending on state:
-    # 1) Regular, for when no user is selected
-    regular_avatar_style =
-      width: @local.avatar_size
-      height: @local.avatar_size
-      position: 'absolute'
-      cursor: if @props.enable_selection then 'pointer' else 'auto'
-
-    # 2) The style of a selected avatar
-    selected_avatar_style = _.extend {}, regular_avatar_style,
-      zIndex: 9
-      backgroundColor: focus_blue
-    css.crossbrowserify selected_avatar_style
-    # 3) The style of an unselected avatar when some other avatar(s) is selected
-    unselected_avatar_style = css.grayscale _.extend {}, regular_avatar_style,
-      opacity: .2
-
-    # 4) The style of the avatar when the histogram is backgrounded
-    #    (e.g. on the crafting page)
-    backgrounded_page_avatar_style = _.extend {}, unselected_avatar_style,
-      opacity: .1
+    # Controls the size of the vertical space at the top of
+    # the histogram that gives some space for users to hover over
+    # the most populous areas
+    region_selection_vertical_padding = if @props.enable_selection then 30 else 0
+    if @local.region_selection_vertical_padding != region_selection_vertical_padding
+       @local.region_selection_vertical_padding = region_selection_vertical_padding
+       save @local
+
+    # whether to show the shaded opinion selection region in the histogram
+    draw_selection_area = @props.enable_selection &&
+                            !hist.selected_opinion &&
+                            !@props.backgrounded &&
+                            (hist.selected_opinions ||
+                              (!@local.touched &&
+                                @local.mouse_opinion_value &&
+                                !@local.hoving_over_avatar))
+
+    histogram_props =
+      className: 'histogram'
+      style: css.crossbrowserify
+        width: @props.width
+        height: @props.height + @local.region_selection_vertical_padding
+        position: 'relative'
+        borderBottom: if @props.draw_base then '1px solid #999'
+        visibility: if @props.opinions.length == 0 then 'hidden'
+        userSelect: 'none'
+
+    if @props.enable_selection
+      _.extend histogram_props,
+        onClick: @onClick
+        onMouseMove: @onMouseMove
+        onMouseLeave: @onMouseLeave
+        onMouseUp: @onMouseUp
+        onMouseDown: @onMouseDown
+
+        onTouchStart: (ev) =>
+          ev.preventDefault()
+          @local.touched = true
+          save hist
+          save @local
+          @onClick(ev)
+
+        onTouchMove: (ev) => ev.preventDefault(); @onMouseMove(ev)
+        onTouchEnd: (ev) => ev.preventDefault(); @onMouseUp(ev)
+        onTouchCancel: (ev) => ev.preventDefault(); @onMouseUp(ev)
+
+    DIV histogram_props,
+
+      if @props.draw_base
+        @drawHistogramBase()
+
+      if @props.enable_selection
+        # A little padding at the top to give some space for selecting
+        # opinion regions with lots of people stacked high
+        DIV style: {height: @local.region_selection_vertical_padding}
+
+      # Draw the opinion selection area + region resizing border
+      if draw_selection_area
+        @drawSelectionArea()
+
+      @drawAvatars()
+
+
+  drawHistogramBase: ->
+    [SPAN
+      style:
+        position: 'absolute'
+        left: -21
+        bottom: -12
+        fontSize: 19
+        fontWeight: 500
+        color: '#999'
+      '–'
+    SPAN
+      style:
+        position: 'absolute'
+        right: -21
+        bottom: -13
+        fontSize: 19
+        fontWeight: 500
+        color: '#999'
+      '+']
+
+  drawSelectionArea: ->
+    hist = fetch @props.key
+    anchor = hist.selected_opinion_value or @local.mouse_opinion_value
+    left = ((anchor + 1)/2 - hist.region_selection_width/2) * @props.width
+    base_width = hist.region_selection_width * @props.width
+    selection_width = Math.min( \
+                        Math.min(base_width, base_width + left), \
+                        @props.width - left)
+    selection_left = Math.max 0, left
+
+    DIV null,
+      if hist.selected_opinions
+        DIV
+          className: 'selection_region_resizer'
+          style:
+            borderBottom: "3px solid #{focus_blue}"
+            height: 15
+            width: selection_width
+            position: 'absolute'
+            left: selection_left
+            top: -15
+            cursor: 'col-resize'
+
+      DIV
+        style:
+          height: @props.height + @local.region_selection_vertical_padding
+          position: 'absolute'
+          width: selection_width
+          backgroundColor: "rgb(246, 247, 249)"
+          cursor: 'pointer'
+          left: selection_left
+          top: 0
+
+        if !hist.selected_opinions
+          DIV
+            style: css.crossbrowserify
+              fontSize: 12
+              textAlign: 'center'
+              whiteSpace: 'nowrap'
+              marginTop: -18
+              userSelect: 'none'
+              pointerEvents: 'none'
+
+            'Select these opinions'
+
+  drawAvatars: ->
+    hist = fetch @props.key

     # Highlighted users are the users whose avatars are colorized and fully
     # opaque in the histogram. It is based on the current opinion selection and
@@ -169,156 +287,61 @@
       else
         highlighted_users = (fetch(o).user for o in selected_users)

-    # whether to show the shaded opinion selection region in the histogram
-    draw_selection_area = @props.enable_selection &&
-                            !hist.selected_opinion &&
-                            !@props.backgrounded &&
-                            (hist.selected_opinions ||
-                              (!@local.touched &&
-                                @local.mouse_opinion_value &&
-                                !@local.hoving_over_avatar))
-
-    # Controls the size of the vertical space at the top of
-    # the histogram that gives some space for users to hover over
-    # the most populous areas
-    region_selection_vertical_padding = if @props.enable_selection then 30 else 0
-
-
-    histogram_props =
-      className: 'histogram'
-      style: css.crossbrowserify
-        width: @props.width
-        height: @props.height + region_selection_vertical_padding
+
+    # There are a few avatar styles that might be applied depending on state:
+    # 1) Regular, for when no user is selected
+    regular_avatar_style =
+      width: @local.avatar_size
+      height: @local.avatar_size
+      position: 'absolute'
+      cursor: if @props.enable_selection then 'pointer' else 'auto'
+
+    # 2) The style of a selected avatar
+    selected_avatar_style = _.extend {}, regular_avatar_style,
+      zIndex: 9
+      backgroundColor: focus_blue
+    css.crossbrowserify selected_avatar_style
+    # 3) The style of an unselected avatar when some other avatar(s) is selected
+    unselected_avatar_style = css.grayscale _.extend {}, regular_avatar_style,
+      opacity: .2
+    # 4) The style of the avatar when the histogram is backgrounded
+    #    (e.g. on the crafting page)
+    backgrounded_page_avatar_style = _.extend {}, unselected_avatar_style,
+      opacity: .1
+
+    # Draw the avatars in the histogram. Placement will be determined later
+    # by the physics sim
+    DIV
+      ref: 'histo'
+      style:
+        height: @props.height
         position: 'relative'
-        borderBottom: if @props.draw_base then '1px solid #999'
-        visibility: if @props.opinions.length == 0 then 'hidden'
-        userSelect: 'none'
-
-    if @props.enable_selection
-      _.extend histogram_props,
-        onClick: @onClick
-        onMouseMove: @onMouseMove
-        onMouseLeave: @onMouseLeave
-        onMouseUp: @onMouseUp
-        onMouseDown: @onMouseDown
-
-        onTouchStart: (ev) =>
-          ev.preventDefault()
-          @local.touched = true
-          save hist
-          save @local
-          @onClick(ev)
-
-        onTouchMove: (ev) => ev.preventDefault(); @onMouseMove(ev)
-        onTouchEnd: (ev) => ev.preventDefault(); @onMouseUp(ev)
-        onTouchCancel: (ev) => ev.preventDefault(); @onMouseUp(ev)
-
-    DIV histogram_props,
-
-      if @props.draw_base
-        [SPAN
-          style:
-            position: 'absolute'
-            left: -21
-            bottom: -12
-            fontSize: 19
-            fontWeight: 500
-            color: '#999'
-          '–'
-        SPAN
-          style:
-            position: 'absolute'
-            right: -21
-            bottom: -13
-            fontSize: 19
-            fontWeight: 500
-            color: '#999'
-          '+']
-
-      if @props.enable_selection
-        # A little padding at the top to give some space for selecting
-        # opinion regions with lots of people stacked high
-        DIV style: {height: region_selection_vertical_padding}
-
-      # Draw the opinion selection area + region resizing border
-      if draw_selection_area
-        anchor = hist.selected_opinion_value or @local.mouse_opinion_value
-        left = ((anchor + 1)/2 - hist.region_selection_width/2) * @props.width
-        base_width = hist.region_selection_width * @props.width
-        selection_width = Math.min( \
-                            Math.min(base_width, base_width + left), \
-                            @props.width - left)
-        selection_left = Math.max 0, left
-
-        DIV null,
-          if hist.selected_opinions
-            DIV
-              className: 'selection_region_resizer'
-              style:
-                borderBottom: "3px solid #{focus_blue}"
-                height: 15
-                width: selection_width
-                position: 'absolute'
-                left: selection_left
-                top: -15
-                cursor: 'col-resize'
-
-          DIV
-            style:
-              height: @props.height + region_selection_vertical_padding
-              position: 'absolute'
-              width: selection_width
-              backgroundColor: "rgb(246, 247, 249)"
-              cursor: 'pointer'
-              left: selection_left
-              top: 0
-
-            if !hist.selected_opinions
-              DIV
-                style: css.crossbrowserify
-                  fontSize: 12
-                  textAlign: 'center'
-                  whiteSpace: 'nowrap'
-                  marginTop: -18
-                  userSelect: 'none'
-                  pointerEvents: 'none'
-
-                'Select these opinions'
-
-      # Draw the avatars in the histogram. Placement will be determined later
-      # by the physics sim
-      DIV
-        ref: 'histo'
-        style:
-          height: @props.height
-          position: 'relative'
-          top: -1
-          cursor: if !@props.backgrounded &&
-                      @props.enable_selection then 'pointer'
-
-        for opinion in @props.opinions
-          user = opinion.user
-          fetch(opinion) # subscribe to changes so physics sim will get rerun...
-
-          if @props.backgrounded
-            avatar_style = if fetch('/current_user').user == user
-                             _.extend({}, regular_avatar_style, {opacity: .25})
-                           else
-                             backgrounded_page_avatar_style
-          else if highlighted_users
-            if _.contains(highlighted_users, opinion.user)
-              avatar_style = selected_avatar_style
-            else
-              avatar_style = unselected_avatar_style
+        top: -1
+        cursor: if !@props.backgrounded &&
+                    @props.enable_selection then 'pointer'
+
+      for opinion in @props.opinions
+        user = opinion.user
+        fetch(opinion) # subscribe to changes so physics sim will get rerun...
+
+        if @props.backgrounded
+          avatar_style = if fetch('/current_user').user == user
+                           _.extend({}, regular_avatar_style, {opacity: .25})
+                         else
+                           backgrounded_page_avatar_style
+        else if highlighted_users
+          if _.contains(highlighted_users, opinion.user)
+            avatar_style = selected_avatar_style
           else
-            avatar_style = regular_avatar_style
-
-          Avatar
-            key: user
-            user: user
-            hide_tooltip: @props.backgrounded
-            style: avatar_style
-
+            avatar_style = unselected_avatar_style
+        else
+          avatar_style = regular_avatar_style
+
+        Avatar
+          key: user
+          user: user
+          hide_tooltip: @props.backgrounded
+          style: avatar_style

   onClick: (ev) ->

@@ -566,8 +589,6 @@
       y: y
     }

-
-
   # Called after the simulation stops
   end = ->
     total_energy = calculate_global_energy()


Modify checkWhiteSpace, anytime run the function.


Removed clearing of message input.

Removed clearing of message input.

This will keep the typed message in the input area, allowing to easily send the same message to another user.

--- core/app/backbone/views/conversations/start_conversation_view.coffee
+++ core/app/backbone/views/conversations/start_conversation_view.coffee
@@ -85,6 +85,5 @@
   clearForm: ->
     @auto_complete_view.clearSearch()
     @recipients.reset []
-    @ui.messageTextarea.val ''

 _.extend(StartConversationView.prototype, Backbone.Factlink.AlertMixin)


Use package name in menu

Use package name in menu

--- menus/markdown-preview.cson
+++ menus/markdown-preview.cson
@@ -1,7 +1,7 @@
 'menu': [
   'label': 'Packages'
   'submenu': [
-    'label': 'Markdown'
+    'label': 'Markdown Preview'
     'submenu': [
       'label': 'Toggle Preview'
       'command': 'markdown-preview:toggle'


use `m`(override) in spec(I want use m for create-range-marker!)

--- spec/operator-modifier-spec.coffee
+++ spec/operator-modifier-spec.coffee
@@ -365,7 +365,7 @@
       beforeEach ->
         atom.keymaps.add "create-range-marker",
           'atom-text-editor.vim-mode-plus:not(.insert-mode)':
-            'g m': 'vim-mode-plus:create-range-marker'
+            'm': 'vim-mode-plus:create-range-marker'

         set
           text: """
@@ -380,7 +380,7 @@
           [[0, 0], [2, 0]]
           [[3, 0], [4, 0]]
         ]
-        ensure 'V j g m G V g m',
+        ensure 'V j m G m m',
           rangeMarkerBufferRange: rangeMarkerBufferRange
       describe "from inside of range-marker", ->
         it "select occurrence in all range-marker", ->


Unfocus TextMateGrammar specs

Unfocus TextMateGrammar specs

--- spec/text-mate-grammar-spec.coffee
+++ spec/text-mate-grammar-spec.coffee
@@ -2,7 +2,7 @@
 TextMatePackage = require '../src/text-mate-package'
 {_, fs} = require 'atom'

-fdescribe "TextMateGrammar", ->
+describe "TextMateGrammar", ->
   grammar = null

   beforeEach ->


just some commented code change

just some commented code change

--- src/app/coffeescad.app.coffee
+++ src/app/coffeescad.app.coffee
@@ -190,7 +190,7 @@

       ###
       deferredList = []
-      #dynamic load, problematic ...not useable for now
+      #dynamic load, problematic
           console.log "editorName",editorName
           editorPath = "./editors/#{editorName}Editor/#{editorName}Editor"
           console.log "editorPath: #{editorPath}"


modified init to support lib selection

modified init to support lib selection

--- natal.coffee
+++ natal.coffee
@@ -144,7 +144,7 @@
     logErr message

-init = (projName) ->
+init = (projName, lib) ->
   projNameHyph = projName.replace(camelRx, '$1-$2').toLowerCase()
   projNameUs   = toUnderscored projName

@@ -173,11 +173,14 @@
     log 'Updating Leiningen project'
     process.chdir projNameHyph
     exec "cp #{resources}project.clj project.clj"
-    edit 'project.clj', [[projNameHyphRx, projNameHyph]]
+    edit \
+      'project.clj',
+      [[projNameHyphRx, projNameHyph], [/\$CLJS_REACT_LIB\$/, reactLibs[lib]]]
+
     corePath = "src/#{projNameUs}/core.clj"
     fs.unlinkSync corePath
     corePath += 's'
-    exec "cp #{resources}core.cljs #{corePath}"
+    exec "cp #{resources}#{lib}.cljs #{corePath}"
     edit corePath, [[projNameHyphRx, projNameHyph], [projNameRx, projName]]

     log 'Compiling ClojureScript'


:green_heart: Add missing runs -> for async test case

--- spec/fs-plus-spec.coffee
+++ spec/fs-plus-spec.coffee
@@ -511,9 +511,10 @@

       waitsForPromise -> fs.move(directoryPath, newDirectoryPath)

-      # If the filesystem is case-insensitive, the old directory should still exist.
-      expect(fs.existsSync(directoryPath)).toBe fs.isCaseInsensitive()
-      expect(fs.existsSync(newDirectoryPath)).toBe true
+      runs ->
+        # If the filesystem is case-insensitive, the old directory should still exist.
+        expect(fs.existsSync(directoryPath)).toBe fs.isCaseInsensitive()
+        expect(fs.existsSync(newDirectoryPath)).toBe true

     it 'renames to a target with an existent parent directory', ->
       directoryPath = path.join(tempDir, 'subdir')


Updated tests

Updated tests

--- spec/latexer-spec.coffee
+++ spec/latexer-spec.coffee
@@ -17,13 +17,13 @@
   describe "new citation is created", ->
     it "extracts the correct values", ->
       testCite = """
-      @test {key,
-      field0 = {vfield0},
-      field1 = {vfield1},
-      field2 = "vfield2",
-      field3 = "vfield3"
-      }
-      """
+                 @test {key,
+                 field0 = {vfield0},
+                 field1 = {vfield1},
+                 field2 = "vfield2",
+                 field3 = "vfield3"
+                 }
+                 """
       cite = new Citation
       cite.parse(testCite)
       expect(cite.get("key")).toBe "key"
@@ -88,3 +88,37 @@
           expect(info.length).toBe 2
           expect(info[0].textContent).toBe "title#{i}"
           expect(info[1].textContent).toBe "author#{i}"
+
+  describe "typing \\begin{evironment} or \\[", ->
+    [workspaceElement, editor] = []
+    beforeEach ->
+      runs ->
+        workspaceElement = atom.views.getView(atom.workspace)
+      waitsFor ->
+        workspaceElement
+      runs ->
+        jasmine.attachToDOM(workspaceElement)
+      waitsForPromise ->
+        atom.workspace.open("sample.tex")
+      waitsFor ->
+        editor = atom.workspace.getActiveTextEditor()
+      waitsForPromise ->
+        atom.packages.activatePackage("latexer")
+    it "autocompletes the environment", ->
+      editor.setText "\\begin{env}\n"
+      advanceClock(editor.getBuffer().getStoppedChangingDelay())
+      expect(editor.getText()).toBe "\\begin{env}\n\n\\end{env}"
+      editor.setText "\\[\n"
+      advanceClock(editor.getBuffer().getStoppedChangingDelay())
+      expect(editor.getText()).toBe "\\[\n\n\\]"
+    it "ignores comments", ->
+      editor.setText "%\\begin{env}\n"
+      advanceClock(editor.getBuffer().getStoppedChangingDelay())
+      expect(editor.getText()).toBe "%\\begin{env}\n"
+      editor.setText "%\\[\n"
+      advanceClock(editor.getBuffer().getStoppedChangingDelay())
+      expect(editor.getText()).toBe "%\\[\n"
+    it "ignores extra backslashes for \\[", ->
+      editor.setText "\\\\[\n"
+      advanceClock(editor.getBuffer().getStoppedChangingDelay())
+      expect(editor.getText()).toBe "\\\\[\n"


Workspace: Added getActivePane method to TabbedEditor.

--- client/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
+++ client/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
@@ -37,6 +37,9 @@
   getActivePaneFileData: ->
     return @tabView.getActivePane().subViews[0].getData()

+  getActivePane: ->
+    return @tabView.getActivePane()
+
   createEditorTabs: ->
     @tabHandleContainer = new ApplicationTabHandleHolder
       delegate          : @
@@ -51,7 +54,7 @@
     @tabView.on "PaneAdded", (pane) =>
       {tabHandle} = pane
       tabHandle.on "click", =>
-        activeTab = @tabView.getActivePane()
+        activeTab = @getActivePane()
         newIndex  = @tabView.getPaneIndex activeTab
         return  if newIndex is @activeTabIndex


Workspace: Added tab change support between clients.

--- client/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
+++ client/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
@@ -12,6 +12,7 @@
     @workspaceRef     = @workspace.firepadRef.child @sessionKey
     @isJoinedASession = @getOptions().sessionKey
     @openedFiles      = []
+    @activeTabIndex   = 0

     log "joined an old session again, creating new tabbed editor" if @isJoinedASession

@@ -22,7 +23,11 @@
       val  = snapshot.val()
       return unless val

-      {tabs} = snapshot.val()
+      if val.ActiveTabIndex?
+        @tabView.showPaneByIndex val.ActiveTabIndex
+        return @workspaceRef.child("ActiveTabIndex").remove()
+
+      {tabs} = val
       return unless tabs

       for key, data of tabs
@@ -40,6 +45,15 @@
     @tabView = new ApplicationTabView
       delegate           : @
       tabHandleContainer : @tabHandleContainer
+
+    @tabView.on "PaneAdded", (pane) =>
+      {tabHandle} = pane
+      tabHandle.on "click", =>
+        newIndex = @tabView.getPaneIndex @tabView.getActivePane()
+        return  if newIndex is @activeTabIndex
+
+        @workspaceRef.child("ActiveTabIndex").set newIndex
+        @activeTabIndex = newIndex

   createEditorInstance: (file, content, sessionKey) ->
     if file
@@ -62,6 +76,7 @@

     pane.addSubView editor
     @tabView.addPane pane
+    @activeTabIndex = @tabView.panes.length

     pane.on "KDTabPaneDestroy", =>
       @workspaceRef.once "value", (snapshot) =>


:lipstick:

:lipstick:

--- src/app/display-buffer.coffee
+++ src/app/display-buffer.coffee
@@ -45,8 +45,8 @@
     @updateAllScreenLines()
     @tokenizedBuffer.on 'grammar-changed', (grammar) => @trigger 'grammar-changed', grammar
     @tokenizedBuffer.on 'changed', @handleTokenizedBufferChange
-    @subscribe @buffer, 'markers-updated', @handleMarkersUpdated
-    @subscribe @buffer, 'marker-created', @handleMarkerCreated
+    @subscribe @buffer, 'markers-updated', @handleBufferMarkersUpdated
+    @subscribe @buffer, 'marker-created', @handleBufferMarkerCreated

   serialize: -> @state.clone()
   getState: -> @state
@@ -135,7 +135,7 @@
   createFold: (startRow, endRow) ->
     foldMarker =
       @findFoldMarker({startRow, endRow}) ?
-        @buffer.markRange([[startRow, 0], [endRow, Infinity]], @foldMarkerAttributes())
+        @buffer.markRange([[startRow, 0], [endRow, Infinity]], @getFoldMarkerAttributes())
     @foldForMarker(foldMarker)

   isFoldedAtBufferRow: (bufferRow) ->
@@ -514,9 +514,9 @@
     @findFoldMarkers(attributes)[0]

   findFoldMarkers: (attributes) ->
-    @buffer.findMarkers(@foldMarkerAttributes(attributes))
-
-  foldMarkerAttributes: (attributes={}) ->
+    @buffer.findMarkers(@getFoldMarkerAttributes(attributes))
+
+  getFoldMarkerAttributes: (attributes={}) ->
     _.extend(attributes, class: 'fold', displayBufferId: @id)

   pauseMarkerObservers: ->
@@ -641,14 +641,17 @@
         @longestScreenRow = maxLengthCandidatesStartRow + screenRow
         @maxLineLength = length

-  handleMarkersUpdated: =>
+  handleBufferMarkersUpdated: =>
     if event = @pendingChangeEvent
       @pendingChangeEvent = null
       @triggerChanged(event, false)

-  handleMarkerCreated: (marker) =>
-    new Fold(this, marker) if marker.matchesAttributes(@foldMarkerAttributes())
+  handleBufferMarkerCreated: (marker) =>
+    @createFoldForMarker(marker) if marker.matchesAttributes(@getFoldMarkerAttributes())
     @trigger 'marker-created', @getMarker(marker.id)
+
+  createFoldForMarker: (marker) ->
+    new Fold(this, marker)

   foldForMarker: (marker) ->
     @foldsByMarkerId[marker.id]


test the bug

test the bug

--- spec/motions-spec.coffee
+++ spec/motions-spec.coffee
@@ -1596,6 +1596,13 @@
       normalModeInputKeydown('b')
       expect(editor.getText()).toBe 'abcbcabc\n'

+    it "selects character under cursor even when no movement happens", ->
+      editor.setCursorBufferPosition([0, 0])
+      keydown('d')
+      keydown('t')
+      commandModeInputKeydown('b')
+      expect(editor.getText()).toBe 'bcabcabcabc\n'
+
   describe 'the V keybinding', ->
     beforeEach ->
       editor.setText("01\n002\n0003\n00004\n000005\n")


Adapt to new network interface

Adapt to new network interface

--- spec/NetworkLifecycle.coffee
+++ spec/NetworkLifecycle.coffee
@@ -296,7 +296,14 @@
           wasStarted = false
           c.network.once 'start', checkStart
           c.network.once 'end', checkEnd
-          g.addInitial 'world', 'Pc', 'in'
+          c.network.addInitial
+            from:
+              data: 'world'
+            to:
+              node: 'Pc'
+              port: 'in'
+           , (err) ->
+             return done err if err
           return
         chai.expect(received).to.eql expected
         done()
@@ -330,7 +337,14 @@
             throw new Error 'Unexpected network start'
           c.network.once 'end', ->
             throw new Error 'Unexpected network end'
-          g.addInitial 'world', 'Pc', 'in'
+          c.network.addInitial
+            from:
+              data: 'world'
+            to:
+              node: 'Pc'
+              port: 'in'
+           , (err) ->
+             return done err if err
           setTimeout ->
             chai.expect(received).to.eql expected
             done()
@@ -1061,16 +1075,23 @@
       c.network.on 'disconnect', receiveDisconnect
       c.network.once 'end', checkEnd

-      c.network.graph.addInitial 'foo', 'Leg2', 'in'
-      c.start (err) ->
-        return done err if err
-        in1.connect()
-        in1.beginGroup 1
-        in1.beginGroup 'a'
-        in1.send 'baz'
-        in1.endGroup()
-        in1.endGroup()
-        in1.disconnect()
+      c.network.addInitial
+        from:
+          data: 'foo'
+        to:
+          node: 'Leg2'
+          port: 'in'
+       , (err) ->
+         return done err if err
+        c.start (err) ->
+          return done err if err
+          in1.connect()
+          in1.beginGroup 1
+          in1.beginGroup 'a'
+          in1.send 'baz'
+          in1.endGroup()
+          in1.endGroup()
+          in1.disconnect()
     it 'should forward new-style brackets as expected regardless of sending order', (done) ->
       expected = [
         'CONN'


Support for changing current working directory.

--- lib/build.coffee
+++ lib/build.coffee
@@ -1,5 +1,6 @@
 child_process = require 'child_process'
 fs = require 'fs'
+path = require 'path'
 qs = require 'querystring'
 _ = require 'underscore'

@@ -35,7 +36,7 @@
       realAtomBuild = fs.realpathSync @root + '/.atom-build.json'
       delete require.cache[realAtomBuild]
       build = require realAtomBuild
-      [exec, env, args] = [ build.cmd, build.env, build.args ]
+      [ exec, env, args, cwd ] = [ build.cmd, build.env, build.args, build.cwd ]

     if !exec && fs.existsSync @root + '/package.json'
       realPackage = fs.realpathSync @root + '/package.json'
@@ -60,11 +61,14 @@
     return {
       exec: exec,
       env: env || {},
-      args: args || []
+      args: args || [],
+      cwd: cwd || @root
     }

   replace: (value) ->
-    value = value.replace '{FILE_ACTIVE}', fs.realpathSync atom.workspace.getActiveEditor().getPath() if atom.workspace.getActiveEditor()
+    activeFile = fs.realpathSync atom.workspace.getActiveEditor().getPath() if atom.workspace.getActiveEditor()
+    value = value.replace '{FILE_ACTIVE}', activeFile if atom.workspace.getActiveEditor()
+    value = value.replace '{FILE_ACTIVE_PATH}', path.dirname(activeFile) if atom.workspace.getActiveEditor()
     value = value.replace '{PROJECT_PATH}', fs.realpathSync atom.project.getPath()
     value = value.replace '{REPO_BRANCH_SHORT}', atom.project.getRepo().getShortHead() if atom.project.getRepo()
     return value;
@@ -84,7 +88,7 @@
     @child = child_process.spawn(
       '/bin/sh',
       [ '-c', [cmd.exec].concat(args).join(' ') ],
-      { cwd : @root, env: env }
+      { cwd : @replace cmd.cwd, env: env }
     )

     @child.stdout.on 'data', @buildView.append


Reload the paper and close card on manuscript upload

[#69310054]

--- app/assets/javascripts/views/overlays/upload_manuscript_overlay_view.js.coffee
+++ app/assets/javascripts/views/overlays/upload_manuscript_overlay_view.js.coffee
@@ -42,6 +42,6 @@
         isUploading: false
       $('ul#paper-manuscript-upload').hide()
       $('#task_completed:not(:checked)').click()
-      # TODO: make this work AFTER manuscript page is converted to Ember
-      # @controoler.transitionToRoute('paper', @get('model.paper'))
-      $('.overlay').hide()
+      paper = @controller.get('model.paper')
+      paper.reload().then =>
+        @controller.send('closeAction')


Improved javascript-only tags processing

--- app/assets/js/shared.coffee
+++ app/assets/js/shared.coffee
@@ -1,6 +1,6 @@
 $ ->
   # Display javascript only tags
-  $('.javascript-only').css('display', 'inline')
+  $('.javascript-only').removeClass('javascript-only')

   # Remove noscript tags for the lulz
   $('noscript').remove()


Multiprocess tidy up

Multiprocess tidy up

--- lib/dscan.coffee
+++ lib/dscan.coffee
@@ -200,7 +200,7 @@
   scanTask = makeTask(multiProcessScan)
   searchTask = makeTask(multiProcessSearch)

-  console.time 'Multi Process Scan'
+  console.time 'Multi Process Search'

   searches = 0
   finished = false
@@ -216,7 +216,6 @@
   scanProcess.stdout.pipe(split()).on 'data', (data) ->
     if data[0] == STOP_CHAR
       scanFinished = true
-      console.log 'Scan done'
       maybeEnd()
     else
       searches++
@@ -231,10 +230,7 @@
       searches--
       console.log "search end #{searches}" if options.verbose
     else if line and line.length
-      try
-        results = JSON.parse(line)
-      catch e
-        console.log "AHH", e
+      results = JSON.parse(line)
       if results
         pathCount++
         resultCount += results.results.length
@@ -256,7 +252,7 @@

   end = ->
     finished = true
-    console.timeEnd 'Multi Process Scan'
+    console.timeEnd 'Multi Process Search'
     console.log "#{resultCount} matches in #{pathCount} files"

 buildRegex = (pattern) ->


Fix #10

Fix #10

Resolve several Internet Explorer 8 issues:

 * `document.ELEMENT_NODE` and `document.TEXT_NODE` are not defined constants
   on early IE version.

 * Emptying out a table element using `innerHTML = ""` causes a runtime error.

 * `textContent` is not supported on all IE versions. Use `innerText` instead.

 * Text nodes do not support neither of `textContent` nor `innerText` and
   should be set using `nodeValue`.

--- lib/elvis.coffee
+++ lib/elvis.coffee
@@ -1,12 +1,16 @@
 doc = @document

+ELEMENT_NODE = 1
+TEXT_NODE = 3
+
+
 isElement = (el) ->
-  el?.nodeType and el.nodeType is doc.ELEMENT_NODE
+  el?.nodeType and el.nodeType is ELEMENT_NODE

 isText = (el) ->
-  el?.nodeType and el.nodeType is doc.TEXT_NODE
+  el?.nodeType and el.nodeType is TEXT_NODE

 merge = (left, right) ->
@@ -136,11 +140,17 @@
   doc.createTextNode(text)

+# Some browsers support `innerText` while others use `textContent`
+textAttr = do ->
+  element = doc.createElement('div')
+  if 'textContent' of element then 'textContent' else 'innerText'
+
+
 directAttributes =
   'className': 'className'
   'id': 'id'
   'html': 'innerHTML'
-  'text': 'textContent'
+  'text': textAttr
   'value': 'value'

@@ -237,13 +247,16 @@
         el.setAttribute(attr, value)
       else
         if attr is 'html' and typeof value isnt 'string'
-          el.innerHTML = ''
+          el.removeChild(el.lastChild) while el.lastChild
           if isElement(value)
             el.appendChild(value)
           else if value instanceof Array
             exports.appendChildren(el, value)
+        else if attr is 'text' and isText(el)
+          el.nodeValue = value
         else
           el[directAttr] = value
+  null

 class SafeString extends exports.Element


moved small methods to one line

moved small methods to one line

--- app/js/views/balloon.coffee
+++ app/js/views/balloon.coffee
@@ -24,11 +24,9 @@
     @$el.fadeOut "fast", callback
     @_fact.stopHighlighting()

-  isVisible: ->
-    @$el.is ":visible"
+  isVisible: -> @$el.is ":visible"

-  destroy: ->
-    @$el.remove()
+  destroy: -> @$el.remove()

   startLoading: ->
     @_loading = true