Changed map generation to create a texture instead of coloring faces.

--- app/scripts/client/webgl/map.coffee
+++ app/scripts/client/webgl/map.coffee
@@ -3,6 +3,14 @@
 Map = require '../../game/map.coffee'

 class MapObject
+    types: {
+        water: new THREE.Color 0x003fff
+        tideWater: new THREE.Color 0x1111aa
+        sand: new THREE.Color 0xf2e077
+        grass: new THREE.Color 0x4bdb33
+        hills: new THREE.Color 0x228b22
+    }
+
     constructor: (width, height) ->
         @width = width
         @height = height
@@ -15,59 +23,58 @@
         @initializeGeometry()

     initializeGeometry: ->
-        mWater = new THREE.MeshLambertMaterial({ color: 0x003fff })
-        mTideWater = new THREE.MeshLambertMaterial({ color: 0x1111aa })
-        mSand = new THREE.MeshLambertMaterial({ color: 0xf2e077 })
-        mGrass = new THREE.MeshLambertMaterial({ color: 0x4dbd33 })
-        mHills = new THREE.MeshLambertMaterial({ color: 0x228b22 })
-        materials = new THREE.MeshFaceMaterial([
-            mWater
-            mTideWater
-            mSand
-            mGrass
-            mHills
-        ])
+        geometry = new THREE.PlaneGeometry 1, 1, 20, 20
+        material = new THREE.MeshLambertMaterial {
+            color: 0xffffff
+            map: @generateTexture()
+        }
+        @map = new THREE.Mesh geometry, material
+        @map.scale.set @width, @height, 1

-        geometry = new THREE.PlaneGeometry(@width, @height, @width,@height)
+    generateTexture: ->
+        canvas = document.createElement 'canvas'
+        ctx = canvas.getContext '2d'
+
+        canvas.width = @width
+        canvas.height = @height
+        ctx.clearRect 0, 0, @width, @height
+
+        imagedata = ctx.getImageData 0, 0, @width, @height
+        data = imagedata.data

         for y in [0...@height]
             for x in [0...@width]
                 value = @mapData.tileAt x, y
-                # faceIndex = (y * width * 2) + x * 2
-                faceIndex = (x * @height * 2) + y * 2
-                face = geometry.faces[faceIndex]
-                face2 = geometry.faces[faceIndex + 1]
-
-                vertexIndex = (y * @width) + x
+                i = (y * @width * 4) + x * 4
+                color = null

                 if value < 0
-                    materialIndex = 0
+                    color = @types.water
                 else if value > 0 and value < 0.008
-                    materialIndex = 1
+                    color = @types.tideWater
                 # Sand
                 else if value > 0.008 and value < 0.0161
-                    materialIndex = 2
+                    color = @types.sand
                 # Grass
                 else if value > 0.0161 and value < 0.2
-                    materialIndex = 3
+                    color = @types.grass
                 # Tundra
                 else
-                    materialIndex = 4
+                    color = @types.hills

-                face.materialIndex = face2.materialIndex = materialIndex
+                if color
+                    data[i] = Math.round color.r * 255
+                    data[i + 1] = Math.round color.g * 255
+                    data[i + 2] = Math.round color.b * 255
+                    data[i + 3] = 255

-                # if geometry.vertices[vertexIndex]
-                #     geometry.vertices[vertexIndex].z = value * 30
-                # if geometry.vertices[vertexIndex + 1]
-                #     geometry.vertices[vertexIndex + 1].z = value * 30
-                # if geometry.vertices[vertexIndex + 2]
-                #     geometry.vertices[vertexIndex + 2].z = value * 30
-                # geometry.vertices[vertexIndex + 3] = value * 10
-                # geometry.vertices[vertexIndex + 4] = value * 10
-                # geometry.vertices[vertexIndex + 5] = value * 10
+        ctx.putImageData imagedata, 0, 0

-        geometry.facesNeedUpdate
-        geometry.verticesNeedUpdate
-        @map = new THREE.Mesh(geometry, materials)
+        @texture = new THREE.Texture canvas
+        @texture.magFilter = THREE.NearestFilter
+        @texture.minFilter = THREE.NearestFilter
+        @texture.needsUpdate = true
+
+        return @texture

 module.exports = MapObject


don't coerce integer-y, string ids to integers

--- src/model/model.coffee
+++ src/model/model.coffee
@@ -292,10 +292,6 @@
       else
         @get(primaryKey)
     set: (key, value) ->
-      # naively coerce string ids into integers
-      if (typeof value is "string") and (value.match(/[^0-9]/) is null) and ("#{parsedValue = parseInt(value, 10)}" is value)
-        value = parsedValue
-
       primaryKey = @constructor.primaryKey
       if primaryKey == 'id'
         @_willSet(key)


WSKernelPicker: notification if no gateways specified in settings

--- lib/ws-kernel-picker.coffee
+++ lib/ws-kernel-picker.coffee
@@ -42,17 +42,20 @@
         @_onChosen = onChosen

     toggle: (grammar) ->
+        gateways = Config.getJson('gateways', [])
+        if _.isEmpty gateways
+            atom.notifications.addError 'No remote kernel gateways available',
+                description: 'Use the Hydrogen package settings to specify the list
+                of remote servers. Hydrogen can use remote kernels on either a
+                Jupyter Kernel Gateway or Jupyter notebook server.'
+            return
+
         @_grammar = grammar
         @_path = atom.workspace.getActiveTextEditor().getPath() + '-' + uuid.v4()
         gatewayListing = new CustomListView('No gateways available', @onGateway.bind this)
         @previouslyFocusedElement = gatewayListing.previouslyFocusedElement
-        gatewayListing.setLoading 'Loading gateways...'
-        items = Config.getJson('gateways', [])
-        if not _.isEmpty items
-            gatewayListing.setItems items
-            gatewayListing.setError 'Select a gateway' # TODO(nikita): maybe don't misuse error
-        else
-            gatewayListing.setError 'Use the package settings to specify gateway information'
+        gatewayListing.setItems gateways
+        gatewayListing.setError 'Select a gateway' # TODO(nikita): maybe don't misuse error

     onGateway: (spec) ->
         console.log('Picked a gateway')


bugfix

bugfix

--- imports/api/projects/methods/main.coffee
+++ imports/api/projects/methods/main.coffee
@@ -58,7 +58,7 @@
 						type: String
 						allowedValues: [
 							'name'
-							'img'
+							'description'
 							'link'
 						]
 					value: type: String


Added check if pointerLocked changed state

--- src/components/Blocker.coffee
+++ src/components/Blocker.coffee
@@ -17,6 +17,9 @@
 {div, span} = React.DOM

 Blocker = React.createClass
+
+  locked: false
+
   getInitialState: ->
     havePointerLock: false
     pointerLocked: false
@@ -35,9 +38,11 @@
       document["pointerLockElement"] is blocker or
       document["mozPointerLockElement"] is blocker or
       document["webkitPointerLockElement"] is blocker
-    @setState
-      pointerLocked: pointerLocked
-    @props.sendState(@state)
+
+    if @locked != pointerLocked
+      @locked = pointerLocked
+      @setState(pointerLocked: pointerLocked)
+      @props.sendState(pointerLocked)

   componentDidMount: ->
     havePointerLock =
@@ -75,4 +80,4 @@
         span {}, title
         span {}, message

-module.exports = (props = null) -> React.createElement(Blocker, props)+module.exports = (props) -> React.createElement(Blocker, props)


Removed console.info's

Removed console.info's

--- core/app/backbone/models/channel.coffee
+++ core/app/backbone/models/channel.coffee
@@ -95,7 +95,6 @@
     $.ajax
       url: followUrl
       type: 'post'
-      success: => console.info 'Hey, we just followed a channel!', this
       error: => @set('followed?', false)

   unfollow: ->
@@ -104,5 +103,4 @@
     $.ajax
       url: unfollowUrl
       type: 'post'
-      success: => console.info 'we unfollowed channel!', this
       error: => @set('followed?', true)


export SingleCoffeeScriptCellNotebookComponent

--- app/app.coffee
+++ app/app.coffee
@@ -214,6 +214,8 @@
 exports.raw_cell_url = (value) ->
   URI(makeHref 'raw_notebook', splat: btoa value).absoluteTo(location.href).toString()

+exports.SingleCoffeeScriptCellNotebookComponent = SingleCoffeeScriptCellNotebookComponent
+
 window.lead =
   settings: Settings
   init_app: exports.init_app


chnage file system search

chnage file system search

--- src/backside/filesystem_chizelin.coffee
+++ src/backside/filesystem_chizelin.coffee
@@ -1,59 +1,84 @@
+###
+File: filesystem_chizelin.coffee
+Purpose:
+Indexes the file system to find files related to a Cloud Storage Service.
+
+###
 fs = require 'fs'
 path = require 'path'
+fileTree = require 'file-size-tree'
+

 module.exports =
 class chizelFS
-    dirToTreeObj: (dir, done) ->
-        results = []

-        fs.readdir(dir, (err, list) ->
+    results = []
+    dirList = (_dir) ->
+        try
+            content = fs.readdirSync(_dir)
+            for _path in content
+                _absPath = path.join(_dir,_path)
+                _stat = fs.statSync(_absPath)
+                if _stat && _stat.isFile()
+                    found = _absPath.search(/OneDrive/ig)
+                    if found >= 0
+                        results.push _absPath
+                if _stat && _stat.isDirectory()
+                    dirList( _absPath)
+        catch error
+            #console.error error

-            if(err)
-                return done(err)

-            pending = list.length
+    dirlist: (dir) ->
+        serviceResults = []
+        console.log 'start search'
+        dirList('C:\\Users')
+        console.log 'end search'
+        console.log fileTree(results)
+        return results

-            if(!pending)
-                return done(null, {name: path.basename(dir), type: 'folder', children: results})
-
-            for file in list
-                do (file) ->
-                    file = path.resolve(dir, file)
-                    fs.stat(file, (err, stat) ->
-                        if(stat && stat.isDirectory())
-                            directoryTreeToObj(file, (err, file) ->
-                                str_to_match = file.match(/OneDrive/g)
-                                if str_to_match?
-                                    console.log(file))
-                        else
-                            results.push({type: 'file', name: path.basename(file)})
-                            done(null, results) if(!--pending)))

     treeObjToView: (results) ->
-        view = null
-        for path in results
-            pathInfo = path.parse(path)
-            pathSplit = path.split(path.sep)
-            if view?
-                view = { text: pathSplit[0], nodes:[]}
-            view =  treeObjToViewHelp(path, view)
+        view = []
+        for _path in results
+            console.log 'hi'
+            pathSplit = _path.split(path.sep)
+            currentNode = view
+            for _subPath in pathSplit
+                console.log 'hello'
+                wantedNode = _subPath
+                lastNode = currentNode
+                for cn in currentNode
+                    console.log 'yoyoyo'
+                    if cn.text is wantedNode
+                        currentNode = cn.nodes
+                        break
+                if lastNode == currentNode
+                    newNode = {text: wantedNode, nodes:[] }
+                    currentNode = newNode.nodes
+        console.log view
         return view

-    treeObjToViewHelp = (list, node) ->
-        root = list.pop()
-
-        if node.text is root
-            for child in node.nodes
-                if child.text is root
-                    treeObjToViewHelp(list, node)
-            newNode = {text: root, node:[]}
-            node.nodes.push newNode
-        else
-            console.log 'root does not match'
-
-        return node
+    # treeObjToViewHelp = (list, node) ->
+    #
+    #     if list.length =< 0
+    #         return node
+    #
+    #     if !node?
+    #         node = {text: list.shift(), nodes:[]}
+    #         treeObjToViewHelp(list, node)
+    #
+    #     if node?
+    #
+    #         newNode = {text: list.shift() , nodes:[]}
+    #         node.nodes.push newNode
+    #         treeObjToViewHelp(list, node.nodes[0])
+    #     else
+    #         console.log 'root does not match'
+    #
+    #     #return node

@@ -81,29 +106,13 @@
 #
 #         list.forEach(function(file) {
 #             file = path.resolve(dir, file);
-#             //console.log(file);
 #             fs.stat(file, function(err, stat) {
 #                 if (stat && stat.isDirectory()) {
 #                     diretoryTreeToObj(file, function(err, res) {
-#                         //		console.log(file);
-#
 # 						var str_to_match = file.match(/OneDrive/g);
 #                         if (str_to_match != null){
-#                           // fs.stat(file && function(err, stat) {
-#                           //   if (stat && stat.isDirectory()) {
-#                           //     directoryTreeToObj
-#                           //   }
-#                           // }
 #                           console.log(file)
 #                         }
-#                         // console.log(str_to_match);
-#                         // results.push({
-#                         //     name: path.basename(file),
-#                         //     type: 'folder',
-#                         //     children: res
-#                         // });
-#                         // if (!--pending)
-#                         //     done(null, results);
 #                     });
 #                 }
 #                 else {


JAccount.fetchMetaInformation: pass profile along with other informations

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -1255,7 +1255,7 @@
       return callback new KodingError 'Failed to fetch user!'  unless user

       { registeredAt, lastLoginDate, email, status } = user
-      { referrerUsername, referralUsed } = this
+      { profile, referrerUsername, referralUsed } = this

       fakeClient = connection: delegate: this

@@ -1273,7 +1273,7 @@
           if err? then machines = err

           callback null, {
-            registeredAt, lastLoginDate, email, status
+            profile, registeredAt, lastLoginDate, email, status
             referrerUsername, referralUsed, plan, machines
           }


Require JUser

Require JUser

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -180,6 +180,7 @@
         callback err, results

   setEmailPreferences: secure (client, prefs, callback)->
+    JUser = require './user'
     JUser.fetchUser client, (err, user)->
       if err
         callback err


SocialWorker: check delegate existance

SocialWorker: check delegate existance

--- workers/social/lib/social/models/payment.coffee
+++ workers/social/lib/social/models/payment.coffee
@@ -126,6 +126,9 @@
   @canUserPurchase = secure (client, callback) ->
     { connection : { delegate } } = client

+    unless delegate
+      return callback new KodingError 'Account not found'
+
     if delegate.type isnt 'registered'
       return callback new KodingError 'guests are not allowed'

@@ -141,10 +144,10 @@
     callback null

   getAccountId = (client) ->
-    return client.connection.delegate.getId()
+    return client?.connection?.delegate?.getId()

   getUserName = (client) ->
-    return client.connection.delegate.profile.nickname
+    return client?.connection?.delegate?.profile?.nickname

   prettifyFeature = (name) ->
     switch name


storage: remove shared methods block

storage: remove shared methods block

--- workers/social/lib/social/models/storage.coffee
+++ workers/social/lib/social/models/storage.coffee
@@ -10,9 +10,6 @@
     sharedEvents  :
       static      : []
       instance    : ['updateInstance']
-    sharedMethods :
-      static      : []
-      instance    : []
     schema        :
       name        : String
       content     :


use redis as store

use redis as store

--- coffee/index.coffee
+++ coffee/index.coffee
@@ -4,8 +4,8 @@
 Util = require("./Util")
 PrimeNumberStore = require("./PrimeNumberStore")
 PrimalityTester = require("./TrailDivPrimality")
-# Store = require("./Store")
-Store = require("../../test/MockStore")
+Store = require("./Store")
+# Store = require("../../test/MockStore")
 logger = require("./Log").getLogger("PrimeNumberApp")
 Q = require("q")


activity: bind "MessageAdded" event of `SocialMessage` class

--- client/Social/Activity/messagepane.coffee
+++ client/Social/Activity/messagepane.coffee
@@ -23,6 +23,7 @@

     return  unless channel

+    channel.on "MessageAdded", @bound "addMessage"

   addMessage: (message) ->


client: assign logged in user's account id to created channel

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -217,9 +217,10 @@
     mappedChannels = []

     for channelContainer in messages
-      message             = mapActivity channelContainer.lastMessage
-      channel             = mapChannel channelContainer
-      channel.lastMessage = message
+      message              = mapActivity channelContainer.lastMessage
+      channel              = mapChannel channelContainer
+      channel.accountOldId = whoami()._id  unless channel.accountOldId
+      channel.lastMessage  = message

       mappedChannels.push channel


Add filter widget to models list. PP-8

Add filter widget to models list.
PP-8

--- client/src/scripts/model-list-view.coffee
+++ client/src/scripts/model-list-view.coffee
@@ -1,5 +1,17 @@
 Steam.ModelListView = (_) ->
+  _predicate = node$ type: 'all'
   _items = do nodes$
+  _hasItems = lift$ _items, (items) -> items.length > 0
+
+  _canClearPredicate = lift$ _predicate, (predicate) -> predicate.type isnt 'all'
+  _predicateCaption = lift$ _predicate, (predicate) ->
+    switch predicate.type
+      when 'all'
+        'Showing\nall models'
+      when 'compatibleWithFrame'
+        "Showing models compatible with\n#{predicate.frameKey}"
+      else
+        ''

   #TODO ugly
   _isLive = node$ yes
@@ -32,17 +44,18 @@
       isSelected: node$ no

     apply$ _isLive, self.isSelected, (isLive, isSelected) ->
-      _.modelSelectionChanged isSelected, _predicate, model if isLive
+      _.modelSelectionChanged isSelected, _predicate(), model if isLive
     self

   displayModels = (models) ->
     _items items = map models, createItem
-    activateItem head items unless isEmpty items
+    if isEmpty items
+      _.displayEmpty()
+    else
+      activateItem head items

-  _predicate = null
-  loadModels = (predicate) ->
+  apply$ _predicate, (predicate) ->
     console.assert isDefined predicate
-    _predicate = predicate
     switch predicate.type
       when 'all'
         _.requestModels (error, data) ->
@@ -60,7 +73,9 @@
             displayModels (head data.frames).compatible_models
     return

-  link$ _.loadModels, loadModels
+  clearPredicate = -> _predicate type: 'all'
+
+  link$ _.loadModels, _predicate
   link$ _.deselectAllModels, ->
     #TODO ugly
     _isLive no
@@ -69,5 +84,9 @@
     _isLive yes

   items: _items
+  hasItems: _hasItems
+  predicateCaption: _predicateCaption
+  clearPredicate: clearPredicate
+  canClearPredicate: _canClearPredicate
   template: 'model-list-view'


typos

typos

--- src/driver/druidDriver.coffee
+++ src/driver/druidDriver.coffee
@@ -314,7 +314,7 @@
         throw new Error("must have an aggregate or an arithmetic")

   addApply: (apply) ->
-    throw new Error("filtered applies are not supported yet") in apply.filter
+    throw new Error("filtered applies are not supported yet") if apply.filter
     @addApplyHelper(apply, false)
     return this

@@ -539,9 +539,9 @@
 }

-module.exports = ({requester, dataSource, timeAttribute, aproximate, filter}) ->
+module.exports = ({requester, dataSource, timeAttribute, approximate, filter}) ->
   timeAttribute or= 'time'
-  aproximate ?= true
+  approximate ?= true
   return (query, callback) ->
     condensedQuery = driverUtil.condenseQuery(query)

@@ -552,7 +552,7 @@
       if condensedQuery.split
         switch condensedQuery.split.bucket
           when 'identity'
-            if aproximate
+            if approximate
               queryFn = druidQueryFns.topN
             else
               done('not implemented yet'); return


Make the default number of steps in a simulation 20.

[#153639811]

https://www.pivotaltracker.com/story/show/153639811

--- src/code/stores/simulation-store.coffee
+++ src/code/stores/simulation-store.coffee
@@ -5,6 +5,7 @@
 TimeUnits          = require '../utils/time-units'
 tr                 = require '../utils/translate'

+DEFAULT_SIMULATION_STEPS = 20
 SimulationActions = Reflux.createActions(
   [
     "expandSimulationPanel"
@@ -39,7 +40,6 @@
     @defaultUnit = TimeUnits.defaultUnit
     @unitName    = TimeUnits.toString(@defaultUnit,true)
     @defaultCollectorUnit = TimeUnits.defaultCollectorUnit
-    defaultDuration = 10
     timeUnitOptions = ({name: TimeUnits.toString(unit, true), unit: unit} for unit in TimeUnits.units)

     @nodes = []
@@ -47,7 +47,7 @@

     @settings =
       simulationPanelExpanded: false
-      duration: defaultDuration
+      duration: DEFAULT_SIMULATION_STEPS
       experimentNumber: 1
       experimentFrame: 0
       stepUnits: @defaultUnit


[up-count] attribute for .up-overlays

[up-count] attribute for .up-overlays

--- lib/assets/javascripts/unpoly/classes/layer_stack.coffee
+++ lib/assets/javascripts/unpoly/classes/layer_stack.coffee
@@ -22,10 +22,12 @@
   remove: (layer, options) ->
     @asap options, =>
       u.remove(@all, layer)
+      @updateOverlayCount()

   push: (layer, options) ->
     @asap options, =>
       @all.push(layer)
+      @updateOverlayCount()

   peel: (layer, options = {}) ->
     @asap options, (lock) ->
@@ -74,8 +76,12 @@
   @getter 'overlayContainer', ->
     unless @overlayContainer
       @overlayContainer = e.createFromSelector(OVERLAY_CONTAINER_SELECTOR)
+      @updateOverlayCount()
       @attachOverlayContainer()
     @overlayContainer
+
+  updateOverlayCount: ->
+    @overlayContainer.setAttribute('up-count', @all.length - 1)

   syncHistory: ->
     historyLayers = u.filter(@allReversed(), 'history')


Fix in dummy app

Fix in dummy app

--- test/dummy/app/assets/javascripts/views/shared/flash.coffee
+++ test/dummy/app/assets/javascripts/views/shared/flash.coffee
@@ -23,9 +23,9 @@
       node.addClass 'warning'
       node.find('p').text @warning
     node.slideDown 'normal'
-    this.hide() if @hide
+    this.hideAfterTime() if @hide

-  hide: (time = 4000) ->
+  hideAfterTime: (time = 4000) ->
     setTimeout ->
       $('.flash').slideUp 'normal'
     , time


Select current branch in status images popup.

--- assets/scripts/app/views/repo/show.coffee
+++ assets/scripts/app/views/repo/show.coffee
@@ -173,6 +173,13 @@
       @get('repo.branches') if @get('active')
     ).property('active', 'repo.branches')

+    setSelectedBranch: (->
+      if @get('repo.branches.isLoaded')
+        @set('branch', @get('repo.branches').findProperty('commit.branch', @get('build.commit.branch')))
+      else
+        @set('branch', null)
+    ).observes('repo.branches', 'repo.branches.isLoaded', 'build.commit.branch')
+
     urlRepo: (->
       'https://' + location.host + Travis.Urls.repo(@get('repo.slug'))
     ).property('repo.slug')


Remove testing view

Remove testing view

--- lib/debug-window.coffee
+++ lib/debug-window.coffee
@@ -1,16 +0,0 @@
-{View} = require 'atom'
-
-module.exports =
-
-class VimDebugView extends View
-  @content: >
-    @div outlet: 'main', class: 'debug-window', =>
-      @div class: 'keys'
-      @div class: 'commands'
-      @div class: 'register'
-
-  initialize: (@state)->
-    # add some events?
-
-  toggle: =>
-    @main.toggleClass('hidden')


Mr note fixes.

Mr note fixes.

--- components/contextmenu/contextmenu.coffee
+++ components/contextmenu/contextmenu.coffee
@@ -14,9 +14,9 @@

     @topMargin  = 0
     @leftMargin = 0
-    @sticky     = no

     o = @getOptions()
+    @sticky = o.sticky

     KD.getSingleton("windowController").addLayer @

@@ -45,8 +45,7 @@

     @appendToDomBody()

-  setSticky:   -> @sticky = yes
-  unsetSticky: -> @sticky = no
+  changeStickyState: (state)-> @sticky = state

   childAppended:->


CUI.Form

CUI.Form

- Fixed "ng" rendering in cases where a label is not String but instanceof
  Label.

--- src/elements/Form/Form.coffee
+++ src/elements/Form/Form.coffee
@@ -526,7 +526,7 @@
 				label.textContent = lbl
 				return label

-			return lbl
+			return get_append(lbl)

 		fields = @getFields()
 		len = fields.length


Terminal: Cursor: remove jQuery dependency, instead object clone create new Style instance

--- client/app/lib/terminal/cursor.coffee
+++ client/app/lib/terminal/cursor.coffee
@@ -1,4 +1,4 @@
-$ = require 'jquery'
+Style      = require './style'
 StyledText = require './styledtext'

@@ -96,15 +96,22 @@
     return content  unless @visible

     newContent = content.substring 0, @x
-    newContent.merge = false
-    @element = content.substring(@x, @x + 1).get(0) ? new StyledText(" ", @terminal.currentStyle)
-    @element.spanForced = true
-    @element.style = $.extend true, {}, @element.style
-    @element.style.outlined = not @focused
-    @element.style.inverse = @focused and @inversed
+    newContent.merge = no
+
+    if @element = content.substring(@x, @x + 1).get 0
+      @element.style = new Style @element.style
+    else
+      @element = new StyledText ' ', @terminal.currentStyle
+
+    @element.spanForced     = yes
+    @element.style.outlined = !@focused
+    @element.style.inverse  =  @focused and @inversed
+
     newContent.push @element
-    newContent.pushAll content.substring(@x + 1)
-    newContent
+    newContent.pushAll content.substring @x + 1
+
+    return newContent
+

   updateCursorElement: ->


Minor adjustment to order of output config

--- includes/webpack-config.coffee
+++ includes/webpack-config.coffee
@@ -113,10 +113,10 @@
 			# include: [fontFiles]
 		]
 	output:
+		filename: 'bundle.js'
 		path: __production and './web/' or '/'
 		pathinfo: not __production
 		publicPath: '/'
-		filename: 'bundle.js'
 	plugins: __production and [
 		# new ExtractTextPlugin 'css/[name].css', allChunks: false
 		new webpack.IgnorePlugin /^\.\/locale$/, [/moment$/]


Fixed WiFi view spec

Fixed WiFi view spec

--- spec/views/select-wifi-view-spec.coffee
+++ spec/views/select-wifi-view-spec.coffee
@@ -118,14 +118,15 @@
         expect(selectWifiView.listNetworksDarwin).toHaveBeenCalled()

         expect(selectWifiView.setItems).toHaveBeenCalled()
-        expect(selectWifiView.setItems.calls.length).toEqual(3)
-
-        args = selectWifiView.setItems.calls[1].args[0]
+        expect(selectWifiView.setItems.calls.length).toEqual(2)
+
+        args = selectWifiView.setItems.calls[0].args[0]
+        console.log args
         expect(args.length).toEqual(1)
         expect(args[0].ssid).toEqual('Enter SSID manually')
         expect(args[0].security).toBe(null)

-        args = selectWifiView.setItems.calls[2].args[0]
+        args = selectWifiView.setItems.calls[1].args[0]
         expect(args.length).toEqual(5)
         expect(args[0].ssid).toEqual('foo')
         expect(args[0].bssid).toEqual('fc:94:e3:21:92:d3')
@@ -177,14 +178,14 @@
         expect(selectWifiView.listNetworksWindows).toHaveBeenCalled()

         expect(selectWifiView.setItems).toHaveBeenCalled()
-        expect(selectWifiView.setItems.calls.length).toEqual(3)
-
-        args = selectWifiView.setItems.calls[1].args[0]
+        expect(selectWifiView.setItems.calls.length).toEqual(2)
+
+        args = selectWifiView.setItems.calls[0].args[0]
         expect(args.length).toEqual(1)
         expect(args[0].ssid).toEqual('Enter SSID manually')
         expect(args[0].security).toBe(null)

-        args = selectWifiView.setItems.calls[2].args[0]
+        args = selectWifiView.setItems.calls[1].args[0]
         expect(args.length).toEqual(6)
         expect(args[0].ssid).toEqual('foo')
         expect(args[0].bssid).toEqual('c8:d7:19:39:a6:74')
@@ -229,7 +230,7 @@
         selectWifiView.trigger 'core:confirm'

         expect(atom.workspaceView.trigger).toHaveBeenCalled()
-        expect(atom.workspaceView.trigger).toHaveBeenCalledWith('spark-ide:enter-wifi-credentials', ['foo', 'Enter SSID manually', null])
+        expect(atom.workspaceView.trigger).toHaveBeenCalledWith('spark-ide:enter-wifi-credentials', ['foo'])

         jasmine.unspy atom.workspaceView, 'trigger'
         jasmine.unspy selectWifiView, 'listNetworksDarwin'


Single line comprehension.

Single line comprehension.

--- lib/formats/json.coffee
+++ lib/formats/json.coffee
@@ -67,8 +67,7 @@

 buildObjectRepr = ({resolvedProps}, cb) ->
   repr = {}
-  for resolvedProp in resolvedProps
-    repr[resolvedProp.name] = resolvedProp.repr
+  repr[rp.name] = rp.repr for rp in resolvedProps
   cb null, repr


Fix random non-method functions

Fix random non-method functions

--- Web/js/classes/chat.coffee
+++ Web/js/classes/chat.coffee
@@ -98,7 +98,7 @@
 			Date: Date.now() / 1000
 			Content: text

-	formatTime = ( date ) ->
+	formatTime: ( date ) ->
 		hours = date.getHours()
 		suffix = 'AM'
 		if hours >= 12
@@ -114,7 +114,7 @@

 		return "#{hours}:#{minutes} #{suffix}"

-	linkify = (text) ->
+	linkify: (text) ->
 		# Put spaces infront of <s to stop urlize seizing them as urls
 		text = text.replace '\n', '
' # whitespace infront of a
 isn't noticable
 		text = text.replace /ː(.+?)ː/g, ' ː' # It is on an  though
@@ -125,7 +125,7 @@
 	addLine: (data, prepend) ->
 		date = new Date( data.Date * 1000 )
 		line =
-			Time: formatTime date
+			Time: @formatTime date
 			DateTime: date.toISOString()
 			Message: data.Content
 		switch data.Type
@@ -139,8 +139,7 @@
 					senderClasses = 'inGame'
 				line.Sender = data.Sender
 				line.SenderClasses = senderClasses
-				# FIXME linkify global function
-				line.Message = linkify line.Message
+				line.Message = @linkify line.Message
 			when 'state' then
 				# Seems fine
 			when 'whisper'


Add get id attributes method

Add get id attributes method

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -1422,4 +1422,14 @@
 	console.warn(message)
 	return null

+Utils.getIdAttributesFromString = (string) ->
+	regex = /id=['"]([^'"]+)["']/g
+	matches = []
+	while (m = regex.exec(string))
+		id = m[1]
+		if id?
+			matches.push(id)
+	matches
+
+
 _.extend exports, Utils


query registry: fix over-indentation

query registry: fix over-indentation

--- workers/social/lib/social/models/graph/queryregistry.coffee
+++ workers/social/lib/social/models/graph/queryregistry.coffee
@@ -2,50 +2,9 @@

 module.exports =

-    member      :
-      following : (orderByQuery)->
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[r:member]->members-[:follower]->currentUser
-          WHERE currentUser.id = {currentUserId}
-          RETURN members
-          #{orderByQuery}
-          SKIP {skipCount}
-          LIMIT {limitCount}
-        """
-      onlineFollowing : (orderByQuery)->
-        """
-          START user=node:koding(id={currentUserId})
-          MATCH user-[:follower]->members
-          WHERE members.onlineStatus = "online"
-          RETURN members
-          #{orderByQuery}
-          SKIP {skipCount}
-          LIMIT {limitCount}
-        """
-      follower  : (orderByQuery)->
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[r:member]->members<-[:follower]-currentUser
-          WHERE currentUser.id = {currentUserId}
-          RETURN members
-          #{orderByQuery}
-          SKIP {skipCount}
-          LIMIT {limitCount}
-        """
-      list: (exemptClause, orderByQuery)->
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[r:member]->members
-          WHERE members.name="JAccount"
-          #{exemptClause}
-          RETURN members
-          #{orderByQuery}
-          SKIP {skipCount}
-          LIMIT {limitCount}
-        """
-      count: (exemptClause)->
-        """
+  member      :
+    following : (orderByQuery)->
+      """
         START group=node:koding(id={groupId})
         MATCH group-[r:member]->members-[:follower]->currentUser
         WHERE currentUser.id = {currentUserId}
@@ -54,6 +13,16 @@
         SKIP {skipCount}
         LIMIT {limitCount}
       """
+    onlineFollowing : (orderByQuery)->
+      """
+        START user=node:koding(id={currentUserId})
+        MATCH user-[:follower]->members
+        WHERE members.onlineStatus = "online"
+        RETURN members
+        #{orderByQuery}
+        SKIP {skipCount}
+        LIMIT {limitCount}
+      """
     follower  : (orderByQuery)->
       """
         START group=node:koding(id={groupId})
@@ -78,33 +47,64 @@
     count: (exemptClause)->
       """
       START group=node:koding(id={groupId})
-      MATCH group-[:member]->members
+      MATCH group-[r:member]->members-[:follower]->currentUser
+      WHERE currentUser.id = {currentUserId}
+      RETURN members
+      #{orderByQuery}
+      SKIP {skipCount}
+      LIMIT {limitCount}
+    """
+  follower  : (orderByQuery)->
+    """
+      START group=node:koding(id={groupId})
+      MATCH group-[r:member]->members<-[:follower]-currentUser
+      WHERE currentUser.id = {currentUserId}
+      RETURN members
+      #{orderByQuery}
+      SKIP {skipCount}
+      LIMIT {limitCount}
+    """
+  list: (exemptClause, orderByQuery)->
+    """
+      START group=node:koding(id={groupId})
+      MATCH group-[r:member]->members
       WHERE members.name="JAccount"
       #{exemptClause}
-      RETURN count(members) as count
-      """
-
-    search: (options)->
-      {seed, firstNameRegExp, lastNameRegexp, blacklistQuery, exemptClause} = options
-      """
-        START koding=node:koding(id={groupId})
-        MATCH koding-[r:member]->members
-
-        WHERE  (
-          members.`profile.nickname` =~ '(?i)#{seed}'
-          and members.type = 'registered'
-          or members.`profile.firstName` =~ '(?i)#{firstNameRegExp}'
-          or members.`profile.lastName` =~ '(?i)#{lastNameRegexp}'
-        )
-
-        #{blacklistQuery}
-        #{exemptClause}
-
-        RETURN members
-        ORDER BY members.`profile.firstName`
-        SKIP {skipCount}
-        LIMIT {limitCount}
-      """
+      RETURN members
+      #{orderByQuery}
+      SKIP {skipCount}
+      LIMIT {limitCount}
+    """
+  count: (exemptClause)->
+    """
+    START group=node:koding(id={groupId})
+    MATCH group-[:member]->members
+    WHERE members.name="JAccount"
+    #{exemptClause}
+    RETURN count(members) as count
+    """
+
+  search: (options)->
+    {seed, firstNameRegExp, lastNameRegexp, blacklistQuery, exemptClause} = options
+    """
+      START koding=node:koding(id={groupId})
+      MATCH koding-[r:member]->members
+
+      WHERE  (
+        members.`profile.nickname` =~ '(?i)#{seed}'
+        and members.type = 'registered'
+        or members.`profile.firstName` =~ '(?i)#{firstNameRegExp}'
+        or members.`profile.lastName` =~ '(?i)#{lastNameRegexp}'
+      )
+
+      #{blacklistQuery}
+      #{exemptClause}
+
+      RETURN members
+      ORDER BY members.`profile.firstName`
+      SKIP {skipCount}
+      LIMIT {limitCount}
+    """
   bucket      :
     newMembers :
       """


Simplified the 'standalone' build tasks

Simplified the 'standalone' build tasks

--- dist/buildscripts/src/standalone.coffee
+++ dist/buildscripts/src/standalone.coffee
@@ -31,18 +31,29 @@
       ignore: path.join(scriptdir, 'releases')
     (o...) -> _.extend(_.clone(defaltOptions), o...)

-# Build class
-PackBuild = exports['PackBuild'] = class extends tools.Build
-    constructor: (platform, arch='all', rest...) ->
+exports['Pack'] = class Pack extends tools.Task
+    constructor: (@platform, @arch='all', rest...) ->
         super()
+
+        @promptMessage = "package as standalone application for #{@platform}/#{@arch}?"
+        @promptDefault = yes
+
         @options = options({
-            platform: platform
-            arch: arch
+            platform: @platform
+            arch: @arch
         }, rest...)

-    execute: (callback) ->
-        packager(@options, callback)
+    perform: ( cb ) ->
+        packager(@options, cb)

-    confirm: (callback) ->
-        tools.promptBool("package for #{@options.platform} #{@options.arch}?", no, callback)
+exports['PackAll'] = class PackAll extends tools.Batch
+    constructor: ->
+        super()
+
+        @tasks = [
+            new Pack('linux', 'x64')
+            new Pack('win32', 'x64')
+        ]

+        @promptMessage = "package app?"
+        @promptDefault = yes


redesigned. now using setImmediate to become async

--- tools/parallel.coffee
+++ tools/parallel.coffee
@@ -1,25 +1,63 @@
+##
+## executes all threads and adds the result of the
+## callback to the results array in the same order.
+## the result of threads[i] will be placed in results[i]
+##
+## examples
+##
+## 1. an arbitrary number of thread functions followed by
+##    a callback function which recieves the results array
+##    or an error object
+##
+##    floyd.tools.parallel (fn)->
+##        fn null, 'res1'
+##    , (fn)->
+##        fn null, 'res2'
+##    , (fn)->
+##        fn null, 'res3'
+##    , (fn)->
+##        fn null, 'res4'
+##    , (err, [r1, r2, r3, r4])->
+##        console.log r1, r2, r3, r4
+##
+##    >> res1 res2 res3 res4
+##
+## 2. an array with thread functions followed by a callback
+##    function which recieves the results array or an error object
+##
+##    threads = for i in [1..4]
+##        do (i)->
+##            (fn)-> fn null, 'res'+i
+##
+##    floyd.tools.parallel threads, (err, [r1, r2, r3, r4])->
+##        console.log r1, r2, r3, r4
+##
+##    >> res1 res2 res3 res4
+##
+module.exports = (threads..., done)->

-##
-##
-##
-
-module.exports = (threads..., done)->
     if typeof threads[0] isnt 'function'
         threads = threads[0]
-
+
     if !(count = threads.length)
         return done()
-
-    floyd.tools.objects.process threads,
-
-        each: (worker, next)->
-            process.nextTick ->
+
+    error = null
+    results = []
+
+    for i in [0..threads.length-1]
+        do (i)->
+            setImmediate ->

-                worker (err)->
-                    done(err) if err
+                threads[i] (err, res)->

+                    if err
+                        error = err
+
+                    else
+                        results[i] = res
+
                     if --count is 0
-                        done()
+                        done error, results

-            next()
-            +    return undefined


gulp: rename tasks

gulp: rename tasks

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -18,19 +18,19 @@
 cfg = require './gulp_config' # gulpConfig

 gulp.task 'dev', ['dev:webpack-server', 'dev:server']
-gulp.task 'test', ['test:lint', 'test:coverage', 'test:karma']
+gulp.task 'test', ['lint', 'test:coverage', 'test:browser']
 gulp.task 'dist', ['dist:scripts', 'dist:static', 'dist:manifest']

 gulp.task 'watch', ->
   gulp.watch cfg.paths.coffee, ['test:unit']
 gulp.task 'watch:phantom', ->
-  gulp.watch cfg.paths.coffee, ['test:karma:phantom']
+  gulp.watch cfg.paths.coffee, ['test:browser:phantom']
 gulp.task 'watch:server', ->
   gulp.watch cfg.paths.coffee, ['test:server']
 gulp.task 'watch:functional', ->
   gulp.watch cfg.paths.coffee, ['test:functional']

-gulp.task 'test:lint', ->
+gulp.task 'lint', ->
   gulp.src cfg.paths.coffee
     .pipe coffeelint(null, clayLintConfig)
     .pipe coffeelint.reporter()
@@ -50,7 +50,7 @@
   gulp.src cfg.paths.unitTests
     .pipe mocha()

-gulp.task 'test:karma:phantom', ['build:scripts:test'], (cb) ->
+gulp.task 'test:browser:phantom', ['build:scripts:test'], (cb) ->
   karma.start _.defaults({
     browsers: ['PhantomJS']
   }, cfg.karma), cb
@@ -59,7 +59,7 @@
   gulp.src cfg.paths.serverTests
     .pipe mocha()

-gulp.task 'test:karma', ['build:scripts:test'], (cb) ->
+gulp.task 'test:browser', ['build:scripts:test'], (cb) ->
   karma.start cfg.karma, cb

 gulp.task 'test:functional', ->


Corrected existing spelling mistake.

Corrected existing spelling mistake.

--- packages/rocketchat-irc/irc.server.coffee
+++ packages/rocketchat-irc/irc.server.coffee
@@ -51,7 +51,7 @@
 		@endMemberListRegex = /^.+#(\S+) :End of \/NAMES list.$/
 		@addMemberToRoomRegex = /^:(\S+)!~\S+ JOIN #(\S+)$/
 		@removeMemberFromRoomRegex = /^:(\S+)!~\S+ PART #(\S+)$/
-		@quiteMemberRegex = /^:(\S+)!~\S+ QUIT .*$/
+		@quitMemberRegex = /^:(\S+)!~\S+ QUIT .*$/

 	connect: (@loginCb) =>
 		@socket.connect @ircPort, @ircHost, @onConnect
@@ -118,9 +118,9 @@
 				@onRemoveMemberFromRoom matchResult[1], matchResult[2]
 				continue

-			matchResult = @quiteMemberRegex.exec line
-			if matchResult
-				@onQuiteMember matchResult[1]
+			matchResult = @quitMemberRegex.exec line
+			if matchResult
+				@onQuitMember matchResult[1]
 				continue

 			matchResult = @successLoginMessageRegex.exec line
@@ -271,8 +271,8 @@
 		console.log '[irc] onRemoveMemberFromRoom -> '.yellow, 'roomName:', roomName, 'member:', member
 		RocketChat.models.Rooms.removeUsernameByName roomName, member

-	onQuiteMember: (member) ->
-		console.log '[irc] onQuiteMember ->'.yellow, 'username:', member
+	onQuitMember: (member) ->
+		console.log '[irc] onQuitMember ->'.yellow, 'username:', member
 		RocketChat.models.Rooms.removeUsernameFromAll member

 		Meteor.users.update {name: member},


fixup: style about Strict.htmlInline.unwrapped

--- src/prim-strict.coffee
+++ src/prim-strict.coffee
@@ -19,7 +19,7 @@
 	samples: ({specdict}) ->
 		repeat dict list map(([k, v]) -> [k, sample v]) enumerate(specdict)
 	htmlInline: ({specdict}) ->
-		"{...}"
+		"{...}"
 	htmlBlock: ({specdict}) ->
 		head: "{"
 		body: genBlockBody('strict', 'field-name') specdict


require false isDirty

require false isDirty

--- client/lanes/models/Collection.coffee
+++ client/lanes/models/Collection.coffee
@@ -123,7 +123,9 @@

     # true if any models have unsaved data
     isDirty: ->
-        !!this.findWhere(isDirty: true)
+        !!@find( (model) ->
+            _.result(model, 'isDirty') isnt false
+        )

     url: -> @model::urlRoot()

@@ -140,8 +142,8 @@
     dataForSave: (options) ->
         unsaved = []
         for model in @models
-            if options.saveAll || model.isDirty
-                unsaved.push( model.dataForSave(options) )
+            if options.saveAll || model.isDirty isnt false
+                unsaved.push( model.dataForSave?(options) or model.serialize())
         unsaved

     mixins:[


GameObject.Effect

GameObject.Effect

--- src/game_object_effect.coffee
+++ src/game_object_effect.coffee
@@ -10,7 +10,7 @@

       # Fade out and then destroy
       player.fadeOut, 0.25, ->
-        self.destroy
+        self.destroy()

   @name fadeOut
   @methodOf GameObject#


ListItemView: DRY up the 'getItemDataId'

--- src/components/list/listitemview.coffee
+++ src/components/list/listitemview.coffee
@@ -48,7 +48,9 @@
     @unsetClass "selected"

   getItemDataId: ->
-    id = if @getData().getId?() then @getData().getId()
-    else if @getData().id?      then @getData().id
-    else if @getData()._id?     then @getData()._id
+    data = @getData()

+    id = if data.getId?() then data.getId()
+    else if data.id?      then data.id
+    else if data._id?     then data._id
+


handle if descriptions are empty

handle if descriptions are empty

--- src/components/task-step/all-steps.cjsx
+++ src/components/task-step/all-steps.cjsx
@@ -110,11 +110,13 @@
     {description, title} = TaskStore.get(taskId)
     external_url = @getUrl()

+    descriptionHTML = <Markdown text={description}/> if description? and description.length > 0
+

         {title}

-      <Markdown text={description}/>
+      {descriptionHTML}

 Spacer = React.createClass


cleanup

cleanup

--- js/src/auth/AuthenticationService.coffee
+++ js/src/auth/AuthenticationService.coffee
@@ -15,6 +15,7 @@
   CredentialProviderLoader = require 'soteria.credential-provider-loader'
   CredentialService        = require 'soteria.credential-service'
   UserUtils                = require 'soteria.user-utils'
+
   log = Logger.get('AuthenticationService')

   #


:art: Rearrange methods in LinterViews

:art: Rearrange methods in LinterViews

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -77,33 +77,16 @@
   render: ->
     @messages = @linter.messages.getAllMessages()
     @updateTabCounts()
-    @updateLineMessages()
     @updateBubble()
     @renderPanelMarkers()
+    @updateLineMessages()
     @renderPanelMessages()

-  updateTabs: ->
-    first = null
-    last = null
-
-    for key, tab of @tabs
-      tab.classList.remove('first')
-      tab.classList.remove('last')
-      tab.visibility = atom.config.get("linter.showErrorTab#{key}")
-      if tab.visibility
-        last = tab
-        first = tab unless first
-
-    first.classList.add('first') if first
-    last.classList.add('last') if last
-
-    unless @tabs[@state.scope]?.visibility
-      @state.scope = @tabPriority.filter((key) => @tabs[key].visibility)[0]
-      @state.scope ?= 'File'
-
-    @changeTab(@state.scope, false)
-
-  # consumed in editor-linter, renderPanel
+  updateTabCounts: ->
+    @tabs.File.count = @linter.messages.count.File
+    @tabs.Project.count = @linter.messages.count.Project
+    @bottomStatus.count = @linter.messages.count.Project
+
   updateBubble: (point) ->
     @removeBubble()
     return unless @showBubble
@@ -125,12 +108,61 @@
       )
       throw null

+  renderPanelMarkers: ->
+    @removeMarkers()
+    activeEditor = atom.workspace.getActiveTextEditor()
+    @messages.forEach (message) =>
+      return if @state.scope isnt 'Project' and not message.currentFile
+      if message.currentFile and message.range #Add the decorations to the current TextEditor
+        @markers.push marker = activeEditor.markBufferRange message.range, {invalidate: 'never'}
+        activeEditor.decorateMarker(
+          marker, type: 'line-number', class: "linter-highlight #{message.class}"
+        )
+        if @underlineIssues then activeEditor.decorateMarker(
+          marker, type: 'highlight', class: "linter-highlight #{message.class}"
+        )
+
   updateLineMessages: (shouldRender = false) ->
     return unless @tabs.Line.visibility
     row = atom.workspace.getActiveTextEditor()?.getCursorBufferPosition()?.row
     @lineMessages = @linter.messages.getActiveFileMessagesForRow(row)
     @tabs.Line.count = @lineMessages.length
     if shouldRender then @renderPanelMessages()
+
+  renderPanelMessages: ->
+    messages =
+      if @tabs['Line'].active
+        @lineMessages
+      else
+        @messages
+    return @setPanelWorkspaceVisibility(false) unless messages.length
+    @setPanelWorkspaceVisibility(true)
+    @panel.innerHTML = ''
+    messages.forEach (message) =>
+      return if @state.scope isnt 'Project' and not message.currentFile
+      Element = Message.fromMessage(message, addPath: @state.scope is 'Project', cloneNode: true)
+      @panel.appendChild Element
+
+  updateTabs: ->
+    first = null
+    last = null
+
+    for key, tab of @tabs
+      tab.classList.remove('first')
+      tab.classList.remove('last')
+      tab.visibility = atom.config.get("linter.showErrorTab#{key}")
+      if tab.visibility
+        last = tab
+        first = tab unless first
+
+    first.classList.add('first') if first
+    last.classList.add('last') if last
+
+    unless @tabs[@state.scope]?.visibility
+      @state.scope = @tabPriority.filter((key) => @tabs[key].visibility)[0]
+      @state.scope ?= 'File'
+
+    @changeTab(@state.scope, false)

   # This method is called when we get the status-bar service
   attachBottom: (statusBar) ->
@@ -173,44 +205,11 @@
       bubble.appendChild Message.fromMessage(trace, addPath: true)
     bubble

-  renderPanelMarkers: ->
-    @removeMarkers()
-    activeEditor = atom.workspace.getActiveTextEditor()
-    @messages.forEach (message) =>
-      return if @state.scope isnt 'Project' and not message.currentFile
-      if message.currentFile and message.range #Add the decorations to the current TextEditor
-        @markers.push marker = activeEditor.markBufferRange message.range, {invalidate: 'never'}
-        activeEditor.decorateMarker(
-          marker, type: 'line-number', class: "linter-highlight #{message.class}"
-        )
-        if @underlineIssues then activeEditor.decorateMarker(
-          marker, type: 'highlight', class: "linter-highlight #{message.class}"
-        )
-
-  renderPanelMessages: ->
-    messages =
-      if @tabs['Line'].active
-        @lineMessages
-      else
-        @messages
-    return @setPanelWorkspaceVisibility(false) unless messages.length
-    @setPanelWorkspaceVisibility(true)
-    @panel.innerHTML = ''
-    messages.forEach (message) =>
-      return if @state.scope isnt 'Project' and not message.currentFile
-      Element = Message.fromMessage(message, addPath: @state.scope is 'Project', cloneNode: true)
-      @panel.appendChild Element
-
   removeMarkers: ->
     return unless @markers.length
     for marker in @markers
       try marker.destroy()
     @markers = []
-
-  updateTabCounts: ->
-    @tabs.File.count = @linter.messages.count.File
-    @tabs.Project.count = @linter.messages.count.Project
-    @bottomStatus.count = @linter.messages.count.Project

   # this method is called on package deactivate
   destroy: ->


:art: Use ES6 Set instead of Array for LinterViews::_messages

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -6,7 +6,7 @@
   constructor: (@linter) ->
     @_showPanel = true # Altered by config observer in linter-plus
     @_showBubble = true # Altered by the config observer in linter-plus
-    @_messages = []
+    @_messages = new Set
     @_markers = []
     @_statusTiles = []

@@ -30,6 +30,9 @@
     @_bottomTabFile.active = true
     @_panel.id = 'linter-panel'

+  getMessages: ->
+    @_messages
+
   # Called in config observer of linter-plus.coffee
   setShowPanel: (showPanel) ->
     atom.config.set('linter.showErrorPanel', showPanel)
@@ -46,11 +49,10 @@
   # This message is called in editor-linter.coffee
   render: ->
     counts = {project: 0, file: 0}
-    messages = []
+    @_messages.clear()
     @linter.eachEditorLinter (editorLinter) =>
-      messages = messages.concat @_extractMessages(editorLinter.getMessages(), counts)
-    messages = messages.concat(@._extractMessages(@linter.getProjectMessages(), counts))
-    @_messages = messages
+      @_extractMessages(editorLinter.getMessages(), counts)
+    @._extractMessages(@linter.getProjectMessages(), counts)

     @_renderPanel()
     @_bottomTabFile.count = counts.file
@@ -61,13 +63,13 @@
   updateBubble: (point) ->
     @_removeBubble()
     return unless @_showBubble
-    return unless @_messages.length
+    return unless @_messages.size
     activeEditor = atom.workspace.getActiveTextEditor()
     return unless activeEditor?.getPath?()
     point = point || activeEditor.getCursorBufferPosition()
-    for message in @_messages
-      continue unless message.currentFile
-      continue unless message.range?.containsPoint? point
+    try @_messages.forEach (message)=>
+      return unless message.currentFile
+      return unless message.range?.containsPoint? point
       @_bubble = activeEditor.decorateMarker(
         activeEditor.markBufferRange(message.range, {invalidate: 'never'}),
         {
@@ -76,7 +78,7 @@
           item: @_renderBubble(message)
         }
       )
-      break
+      throw null

   # consumed in views/panel
   setPanelVisibility: (Status) ->
@@ -137,7 +139,7 @@
     @_panel.innerHTML = ''
     @_removeMarkers()
     @_removeBubble()
-    if not @_messages.length
+    if not @_messages.size
       return @setPanelVisibility(false)
     @setPanelVisibility(true)
     activeEditor = atom.workspace.getActiveTextEditor()
@@ -166,10 +168,9 @@
   _extractMessages: (Gen, counts) ->
     isProject = @_scope is 'project'
     activeFile = atom.workspace.getActiveTextEditor()?.getPath?()
-    ToReturn = []
-    Gen.forEach (Entry) ->
+    Gen.forEach (Entry) =>
       # Entry === Array<Messages>
-      Entry.forEach (message) ->
+      Entry.forEach (message) =>
         # If there's no file prop on message and the panel scope is file then count is as current
         if (not message.filePath and not isProject) or message.filePath is activeFile
           counts.file++
@@ -178,6 +179,5 @@
         else
           counts.project++
           message.currentFile = false
-        ToReturn.push message
-    ToReturn
+        @_messages.add message
 module.exports = LinterViews


scroll arrows off the top of the screen

scroll arrows off the top of the screen

--- graphics.coffee
+++ graphics.coffee
@@ -7,7 +7,7 @@
       x: 10
       y: 10
       width: @block*10
-      height: 150
+      height: @block*3
     @mapping_dims =
       x: 10
       y: @record_dims.x+@record_dims.height+@block,
@@ -88,8 +88,10 @@
     @ctx.restore()

   animateSprites: (absolute_t) ->
+    to_keep = []
     for sprite in @sprites
       do_next = true
+      remove = false
       while do_next and sprite.animations.length > 0
         do_next = false
         anim = sprite.animations[0]
@@ -112,7 +114,11 @@
           # hit the end of the animation
           sprite.leftover_t = absolute_t - (anim.start_t + anim.duration)
           sprite.animations.shift()
-          do_next = true
+
+          if anim.remove_on_finish
+            remove = true
+          else
+            do_next = true

           if anim.lerp?
             for property in anim.lerp
@@ -126,6 +132,9 @@
                 (property.v1 - property.v0) * t + property.v0

       sprite.leftover_t = 0
+      if not remove
+        to_keep.push(sprite)
+    @sprites = to_keep

   isAnimating: () ->
     for sprite in @sprites
@@ -263,25 +272,49 @@
     @next_record_sprite =
       x: @record_dims.x + .5 * @block + .5
       y: @record_dims.y + .5 * @block + .5
-      render: =>
+      render: (sprite) =>
         @ctx.strokeStyle = 'white'
+        @ctx.scale(sprite.scale, sprite.scale)
         @renderShape('square', @block*.625)
+      scale: 1
       animations: []
     @sprites.push(@next_record_sprite)
   advanceNextRecordSprite: (count) ->
-    @next_record_sprite.animations.push(
-      duration: 75
-    )
-    @next_record_sprite.animations.push(
-      duration: 50
-      lerp: [
-        name: 'x'
-        v1: @record_dims.x + (.5 + @record_sprites.length + count) * @block+.5
-      ]
-    )
+    width = @record_dims.width/@block
+    x = @record_dims.x + (.5 + @record_sprites.length%width) * @block + .5
+    y = @record_dims.y + (.5 + @record_sprites.length//width) * @block + .5
+
+    if @record_sprites.length%width == 0
+      # avoid diagonal flight
+      @next_record_sprite.animations.push(
+        duration: 75
+        lerp: [
+          {name: 'x', v1: @record_dims.x + .5*@block + @record_dims.width + .5}
+          {name: 'scale', v0: 1, v1: 0}]
+      )
+      @next_record_sprite.animations.push(
+        duration: 50
+        set: [ {name: 'y', v: y}, {name: 'scale', v: 1} ]
+        lerp: [ {name: 'x', v0: @record_dims.x - @block, v1: x} ]
+      )
+
+    else
+      @next_record_sprite.animations.push(
+        duration: 75
+      )
+      @next_record_sprite.animations.push(
+        duration: 50
+        lerp: [
+          {name: 'x', v1: x},
+          {name: 'y', v1: y}
+        ]
+      )

   addRecordSprite: (dir) ->
+    height = @record_dims.height/@block
+    width = @record_dims.width/@block
+
     sprite =
       x: @record_dims.x + (.5 + @record_sprites.length) * @block + .5
       y: @record_dims.y + .5 * @block + .5
@@ -291,11 +324,49 @@
           @ctx.scale(sprite.scale, sprite.scale)
           @ctx.strokeStyle = 'white'
           @renderArrow(dir.theta, @block)
-    sprite.animations = @animatePopIn(1, {x:sprite.x, y:sprite.y})
+      animations: []
+
+    while sprite.x > @record_dims.x + @record_dims.width
+      sprite.x -= @record_dims.width
+      sprite.y += @block
+
+    @record_sprites.push(sprite)
+
+    if @record_sprites.length == height * width
+      for old_sprite in @record_sprites[0...width]
+        old_sprite.animations = [
+          duration: 125
+          lerp: [ {name: 'scale', v0: 1, v1: 0},
+                  {name: 'y', v1: @record_dims.y-@block} ]
+          remove_on_finish: true
+        ]
+      for old_sprite,i in @record_sprites[width..]
+        desty = @record_dims.y+(.5  + i//width)*@block + .5
+        if old_sprite == sprite
+          # this does a mix of pop in and scroll up
+          sprite.animations.push(
+            duration: 100
+            lerp: [ {name: 'y', v0: sprite.y, v1: (desty-sprite.y)*(100/125)+sprite.y},
+                    {name: 'scale', v0: 0, v1: 1.25}
+                  ]
+          )
+          sprite.animations.push(
+            duration: 25
+            lerp: [ {name: 'y', v1: desty},
+                    {name: 'scale', v1: 1}
+                  ]
+          )
+        else
+          old_sprite.animations.push(
+            duration: 125
+            lerp: [ {name: 'y', v1: desty} ]
+          )
+      @record_sprites = @record_sprites[width..]
+    else
+      sprite.animations = @animatePopIn(1, {x:sprite.x, y:sprite.y})
+

     @advanceNextRecordSprite(1)
-
-    @record_sprites.push(sprite)
     @sprites.push(sprite)


fix build-style-dev task

fix build-style-dev task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -228,7 +228,10 @@
   .pipe gulp.dest './dist/styles/'

 gulp.task 'build-style-dev', ->
-  gulp.src './src/styles/**/*.less'
+  gulp.src [
+    './src/styles/**/*.less'
+    './node_modules/font-awesome/css/font-awesome.min.css'
+  ]
   .pipe ignoreError less()
   .pipe concat 'main.css'
   .pipe minifyCss()


Mostly finish J.Model documentation

Mostly finish J.Model documentation

--- models.coffee
+++ models.coffee
@@ -57,14 +57,14 @@
     @escapeDot: (key) ->
         key.replace(/\./g, '*DOT*').replace(/\$/g, '*DOLLAR*')

+
     # ## @fromJSONValue
     # - - -
-    # TODO: write documentation
-    #
+    # Return a new model instance with the fields set to a given JSON.
+    # The input is *not* of the form `{$type: ThisModelName, $value: someValue}`.
+    # It's just the `someValue` part.
     # - - -
     @fromJSONValue: (jsonValue) ->
-        # jsonValue is *not* of the form {$type: ThisModelName, $value: someValue}.
-        # It's just the someValue part.

         unless J.util.isPlainObject jsonValue
             throw new Meteor.Error 'Must override J.Model.fromJSONValue to decode non-object values'
@@ -75,10 +75,11 @@

         @fromDoc jsonValue

+
     # ## @fromDoc
     # - - -
-    # TODO: write documentation
-    #
+    # Return a new model instance with the fields set
+    # to the corresponding fields of a given document.
     # - - -
     @fromDoc: (doc) ->
         fields = EJSON.fromJSONValue @_getUnescapedSubdoc doc
@@ -123,8 +124,12 @@

     # ## @unescapeDot
     # - - -
-    # TODO: write documentation
+    # Reverse the behavior of **@escapeDot**.
+    # Specifically unescape `*DOT*` and `*DOLLAR*` in a string by
+    # replacing them with `.` and `$` respectively.
     #
+    #     J.Model.unescapeDot('func://www*DOT*example*DOT*.com/func')
+    #     # "func://www.example.com/func"
     # - - -
     @unescapeDot: (key) =>
         key.replace(/\*DOT\*/g, '.').replace(/\*DOLLAR\*/g, '$')
@@ -185,30 +190,30 @@

     # ## clone
     # - - -
-    # TODO: write documentation
+    # Clone a model instance. The clone is
+    # nonreactive because its fields are
+    # a new piece of application state.
+    # Note that clones are always detached, alive, and not read-only.
     #
-    #
+    #     foo = $$.Foo.fetchOne()
+    #     foo.bar(42)
+    #     foo.save() # Error: Cannot save attached instance.
+    #     fooClone = foo.clone()
+    #     fooClone.save()
     # - - -
     clone: ->
-        # Nonreactive because the clone's fields are
-        # their own new piece of application state.
         doc = Tracker.nonreactive => @toDoc()
-
         for fieldName in _.keys doc
             if doc[fieldName] is undefined
                 delete doc[fieldName]
-
         instance = @modelClass.fromDoc doc
         instance.collection = @collection
-
-        # Note that clones are always detached, alive, and not read-only
         instance

+
     # ## get
     # - - -
-    # TODO: write documentation
-    #
-    #
+    # Get the value of a field or reactive of a model instance.
     # - - -
     get: (fieldOrReactiveName) ->
         if not @alive
@@ -326,7 +331,8 @@
     # Insert a model document into a collection.
     #
     #     model.insert(collection, callback)
-    #     model.insert(callback)
+    #     model.insert(callback) # uses @collection
+    # - - -
     insert: (options = {}, callback) ->
         if _.isFunction(options) and arguments.length is 1
             callback = options
@@ -337,9 +343,7 @@

     # ## remove
     # - - -
-    # TODO: write documentation
-    #
-    #
+    # Remove a model document from its collection.
     # - - -
     remove: (callback) ->
         unless @alive
@@ -350,14 +354,14 @@

     # ## save
     # - - -
-    # TODO: write documentation
+    # Save a model instance into a collection.
+    # Returns `@_id`.
     #
-    #
+    #     foo.save(collection, callback)
+    #     foo.save(callback) # uses @collection
     # - - -
     save: (options = {}, callback) ->
         if _.isFunction(options) and arguments.length is 1
-            # Can call save(callback) to use @collection
-            # as the collection.
             callback = options
             options = {}

@@ -376,9 +380,13 @@

     # ## set
     # - - -
-    # TODO: write documentation
+    # Set the fields of a model instance.
+    # This method takes an dictionary of
+    # field names and values.
     #
-    #
+    #     foo.set
+    #       bar: 1
+    #       baz: 2
     # - - -
     set: (fields) ->
         unless J.util.isPlainObject fields
@@ -399,17 +407,14 @@

     # ## toDoc
     # - - -
-    # TODO: write documentation
-    #
-    #
+    # Reactive.
+    # Returns an EJSON object with all the
+    # user-defined types serialized into JSON, but
+    # not the EJSON primitives (Date and Binary).
+    # (A "compound EJSON object" can contain user-defined
+    # types in the form of J.Model instances.)
     # - - -
     toDoc: ->
-        # Reactive.
-        # Returns an EJSON object with all the
-        # user-defined types serialized into JSON, but
-        # not the EJSON primitives (Date and Binary).
-        # (A "compound EJSON object" can contain user-defined
-        # types in the form of J.Model instances.)

         unless @alive
             throw new Meteor.Error "Can't call toDoc on dead #{@modelClass.name} instance"
@@ -421,24 +426,18 @@

     # ## toJSONValue
     # - - -
-    # TODO: write documentation
-    #
-    #
-    # - - -
-    toJSONValue: ->
-        # Used by Meteor EJSON, e.g. EJSON.stringify.
-        # Note that the name is misleading because
-        # EJSON's special primitives (Date and Binary)
-        # aren't returned as JSON.
-
-        @toDoc()
+    # Alias for **@toDoc**.
+    # This is used by Meteor EJSON, e.g. EJSON.stringify.
+    # Note that the name is misleading because
+    # EJSON's special primitives (Date and Binary)
+    # aren't returned as JSON.
+    # - - -
+    toJSONValue: -> @toDoc()

     # ## toString
     # - - -
-    # TODO: write documentation
-    #
-    #
+    # Returns the string representation of a model instance.
     # - - -
     toString: ->
         if @alive
@@ -449,9 +448,8 @@

     # ## tryGet
     # - - -
-    # TODO: write documentation
-    #
-    #
+    # Try to get a field or reactive value.
+    # If the value is not ready, return `defaultValue`.
     # - - -
     tryGet: (key, defaultValue) ->
         J.tryGet(
@@ -462,29 +460,26 @@

     # ## typeName
     # - - -
-    # TODO: write documentation
+    # Used by Meteor EJSON.
+    # - - -
+    typeName: ->
+        @modelClass.name
+
+
+    # ## update
+    # - - -
+    # Update the corresponding model document in `@collection`
+    # using MongoDB operators.
     #
-    #
-    # - - -
-    typeName: ->
-        # Used by Meteor EJSON
-        @modelClass.name
-
-
-    # ## update
-    # - - -
-    # TODO: write documentation
-    #
-    #
+    # Calling something like `foo.update(bar: baz)` would replace the entire
+    # Mongo doc, which is basically always a mistake. We almost always
+    # want to call something like `foo.update($set: bar: baz)` instead.
     # - - -
     update: (args...) ->
         unless @alive
             throw new Meteor.Error "Can't call update on dead #{@modelClass.name} instance"

         unless J.util.isPlainObject(args[0]) and _.all(key[0] is '$' for key of args[0])
-            # Calling something like .update(foo: bar) would replace the entire
-            # Mongo doc, which is basically always a mistake. We almost always
-            # want to call something like .update($set: foo: bar) instead.
             throw new Meteor.Error "Must use a $ operation for #{@modelClass.name}.update"

         @collection.update.bind(@collection, @_id).apply null, args
@@ -499,6 +494,57 @@
 # must be defined before all components.
 modelDefinitionQueue = []

+
+# ## J.defineModel (J.dm)
+# - - -
+# Define a model. The definition may include
+# an `_id`, field declarations under `fields`,
+# and reactive definitions under `reactives`.
+#
+# * `@collection` is the collection that was queried
+# to obtain this instance, or the original attached
+# clone-ancestor of this instance, or just the
+# default place we're going to be inserting/saving to.
+#
+# * When a model instance is attached, it reactively receives
+# changes from its collection and is immutable
+# to the application layer.
+# Note that an attached instance always has an `_id.`
+#
+# * Attached instances die when the collection
+# they came from no longer contains their ID.
+# They never come back to life, but a new
+# attached instance with the same ID may
+# eventually replace them in the collection.
+# Detached instances dies when their creator
+# computation dies, if there is one.
+#
+#
+#     J.dm 'Foo', 'foos',
+#         _id: $$.str
+#
+#         fields:
+#             a:
+#                 type: $$.str
+#             b:
+#                 type: $$.str
+#                 include: false
+#             c:
+#                 type: $$.str
+#
+#         reactives:
+#             d:
+#                 val: ->
+#                     @a() + @c()
+#             e:
+#                 include: true
+#                 val: ->
+#                     @b() + 1
+#             f:
+#                 denorm: true
+#                 val: ->
+#                     $$.Bar.fetch({ baz: 1 }).count()
+# - - -
 J.dm = J.defineModel = (modelName, collectionName, members = {}, staticMembers = {}) ->
     modelDefinitionQueue.push
         modelName: modelName
@@ -510,25 +556,7 @@
 J._defineModel = (modelName, collectionName, members = {}, staticMembers = {}) ->
     modelConstructor = (initFields = {}, @collection = @modelClass.collection) ->
         @_id = initFields._id ? null
-
-        # @collection is the collection that was queried
-        # to obtain this instance, or the original attached
-        # clone-ancestor of this instance, or just the
-        # default place we're going to be inserting/saving to.
-
-        # If true, this instance reactively receives
-        # changes from its collection and is immutable
-        # to the application layer.
-        # Note that an attached instance always has an _id.
         @attached = false
-
-        # Attached instances die when the collection
-        # they came from no longer contains their ID.
-        # They never come back to life, but a new
-        # attached instance with the same ID may
-        # eventually replace them in the collection.
-        # Detached instances dies when their creator
-        # computation dies, if there is one.
         @alive = true
         if Tracker.active then Tracker.onInvalidate =>
             @alive = false


[Client] Add few search term oriented tests

--- NBlast.Client/tests/viewModels/searchSpec.coffee
+++ NBlast.Client/tests/viewModels/searchSpec.coffee
@@ -157,12 +157,49 @@
 				sut.page().should.be.equal(expectedPage)
 				sut.expression().should.be.equal(expectedExpression)

+			it 'Should detected terms search type correctly', ->
+				# Given
+				expectedParams = {val: true}
+				sut = new SearchViewModel(1, '*')
+				termSearchModeStub = mocker.stub(sut, 'termSearchMode', -> true)
+				getTermSearchParamsStub = mocker.stub(sut, 'getTermSearchParams')
+				searchByTermStub = mocker.stub(searchService, 'searchByTerm')
+
+				getTermSearchParamsStub.returns(expectedParams)
+				searchByTermStub.withArgs(expectedParams).returns('yeah!')
+
+				# When
+				expected = sut.requestSearch()
+
+				# Then
+				expected.should.be.eql('yeah!')
+				termSearchModeStub.calledOnce.should.be.true()
+
+			it 'Should detected simple search type correctly', ->
+				# Given
+				expectedParams = {val: true}
+				sut = new SearchViewModel(1, '*')
+				termSearchModeStub = mocker.stub(sut, 'termSearchMode', -> false)
+				getSearchParamsStub = mocker.stub(sut, 'getSearchParams')
+				searchStub = mocker.stub(searchService, 'search')
+
+				getSearchParamsStub.returns(expectedParams)
+				searchStub.withArgs(expectedParams).returns('yeah!')
+
+				# When
+				expected = sut.requestSearch()
+
+				# Then
+				expected.should.be.eql('yeah!')
+				termSearchModeStub.calledOnce.should.be.true()
+
 		describe 'When search view is bind', ->
 			it 'Should apply binding, init externals and accomplish a search request', ->
 				# Given
 				actualSearchParams = {}
 				actualSearchCallback = ->
 				sut = new SearchViewModel(10, '*')
+				termSearchModeStub = mocker.stub(sut, 'termSearchMode', -> false)
 				applyBindingsStub = mocker.stub(markupService, 'applyBindings', ->)
 				initExternalsStub = mocker.stub(sut, 'initExternals', ->)
 				searchStub = mocker.stub(searchService, 'search', (p) ->
@@ -171,12 +208,13 @@
 						actualSearchCallback = cb
 				)

-
 				# When
 				sut.bind()

 				# Then
+
 				applyBindingsStub.calledWith(sut, sinon.match.string)
+				termSearchModeStub.calledOnce.should.be.true()
 				initExternalsStub.calledOnce.should.be.true()
 				searchStub.calledOnce.should.be.true()
 				actualSearchParams.expression.should.be.equal('*')


Updated configuration for performance

Updated configuration for performance

--- udefine/config.coffee
+++ udefine/config.coffee
@@ -1,7 +1,8 @@
 # Performance shim
 do (name = 'performance') ->
-  udefine.configure (root) ->
-    udefine.inject.add name
+  unless performance?
+    udefine.configure (root) ->
+      udefine.inject.add name

 # Animationframe
 do (names = ['requestAnimationFrame', 'cancelAnimationFrame']) ->


Simplified udefine configuration

Simplified udefine configuration

--- udefine/config.coffee
+++ udefine/config.coffee
@@ -1,8 +1,6 @@
 do (names = ['check', 'checkt']) ->
   udefine.configure (root) ->
     for name in names
-      @globals[name.toLowerCase()] = root[name]
-
-      @inject[name.toLowerCase()] = {name, root}
+      @inject.add [name.toLowerCase()]

     null


use correct created property

use correct created property

--- client/views/instances/serviceView.coffee
+++ client/views/instances/serviceView.coffee
@@ -3,7 +3,7 @@
     target: (trigger) -> trigger.parentNode.nextElementSibling

 Template.serviceView.helpers
-  createdSince: -> moment(@data.dockerContainerInfo?.service?.Created).fromNow()
+  createdSince: -> moment(@data.container?.created).fromNow() if @data.container?.created
   hasSshSettings: -> @data.aux.ssh
   stateIcon: ->
     if @data.state is 'running'


Fixed a syntax issue

Fixed a syntax issue

--- src/configuration.coffee
+++ src/configuration.coffee
@@ -1,5 +1,5 @@
 express = require 'express'
-path = require('path')
+path = require 'path'

 public_dir = (filename) -> path.join __dirname, '../public', filename or ''


Use eachSeries to load snippets so we don't exhaust file descriptors

--- src/packages/snippets/lib/snippets.coffee
+++ src/packages/snippets/lib/snippets.coffee
@@ -50,7 +50,7 @@
         done()

     fs.readdir snippetsDirPath, (err, paths) ->
-      async.each(paths, loadSnippetFile, done)
+      async.eachSeries(paths, loadSnippetFile, done)

   loadTextMateSnippets: (path, done) ->
     snippetsDirPath = fsUtils.join(path, 'Snippets')
@@ -89,7 +89,7 @@
       if err
         console.warn err
         return done()
-      async.each(paths, loadSnippetFile, done)
+      async.eachSeries(paths, loadSnippetFile, done)

   translateTextmateSnippet: ({ scope, name, content, tabTrigger }) ->
     scope = TextMatePackage.cssSelectorFromScopeSelector(scope) if scope


Add missing event param

Add missing event param

--- lib/package-card.coffee
+++ lib/package-card.coffee
@@ -115,7 +115,7 @@
     else
       @on 'click', =>
         @parents('.settings-view').view()?.showPanel(@pack.name, {back: opts?.back, pack: @pack})
-      @settingsButton.on 'click', =>
+      @settingsButton.on 'click', (event) =>
         event.stopPropagation()
         @parents('.settings-view').view()?.showPanel(@pack.name, {back: opts?.back, pack: @pack})


fix "open a debugger pane" menus

fix "open a debugger pane" menus

--- lib/package/menu.coffee
+++ lib/package/menu.coffee
@@ -55,7 +55,7 @@
       {label: 'Open Outline Pane', command: 'julia-client:open-outline-pane'}
       {label: 'Open Documentation Browser', command: 'julia-client:open-documentation-browser'}
       {label: 'Open Plot Pane', command: 'julia-client:open-plot-pane'}
-      {label: 'Open Debugger Pane', command: 'julia-client:open-debugger-pane'}
+      {label: 'Open Debugger Pane', command: 'julia-debug:open-debugger-pane'}

       {type: 'separator'}


fix open startup file in Julia menu

fix open startup file in Julia menu

--- lib/package/menu.coffee
+++ lib/package/menu.coffee
@@ -44,7 +44,8 @@

       {type: 'separator'}

-      {label: 'Open Startup File', command: 'julia:open-startup-file'}
+      {label: 'Open Julia Startup File', command: 'julia:open-julia-startup-file'}
+      {label: 'Open Juno Startup File', command: 'julia:open-juno-startup-file'}
       {label: 'Open Julia Home', command: 'julia:open-julia-home'}
       {label: 'Open Package in New Window...', command: 'julia:open-package-in-new-window'}


See #69. Remove unnecessary console.log

See #69. Remove unnecessary console.log

--- lib/preview-view.coffee
+++ lib/preview-view.coffee
@@ -304,7 +304,7 @@
     @selectRendererView.attach()

   showError: (result) ->
-    console.log('showError', result)
+    # console.log('showError', result)
     failureMessage = result?.message
     @showMessage()
     @messageView.message.html $$$ ->


fixes node.structured

fixes node.structured

--- src/public/library/node.structured.coffee
+++ src/public/library/node.structured.coffee
@@ -41,7 +41,7 @@
 		_updateFoundationNodesInterval : 10000
 		_recommendParentInterval: 10000
 		_demotionTimeout: 11000
-		_tokenInfoTimeout: 3000
+		_tokenInfoTimeout: 5000

 		_pingCandidateTimeout : 1000
 		_coordinateDelta : 1
@@ -106,7 +106,7 @@
 		query: ( request, args..., callback ) =>
 			switch request
 				when 'ping'
-					callback 'pong', @position.serialize()
+					callback 'pong', @position.serialize(), @token?.serialize()
 				when 'position'
 					callback @position.serialize()
 				when 'isSuperNode'
@@ -166,9 +166,10 @@
 			if @isSuperNode
 				callback?(false)
 				return
-
+			console.log 'sending parent request to ' + peer.id
 			peer.query('peer.requestAdoption', @id, (accepted) =>
 				if accepted and not @isSuperNode
+					console.log 'parent request accepted'
 					if @_parent?
 						@removeParent()

@@ -176,6 +177,7 @@
 					@_parent = peer
 					callback?(true)
 				else
+					console.log 'parent request denied'
 					peer.emit('peer.abandonParent', @id)
 					peer.role = Peer.Role.None
 					callback?(false)
@@ -186,6 +188,7 @@
 		# @return [Peer] the parent peer
 		#
 		removeParent: ( ) ->
+			console.log 'removing parent ' + @_parent.id
 			@_parent.emit('peer.abandonParent', @id)
 			@_parent.role = Peer.Role.None
 			@_parent = null
@@ -203,7 +206,6 @@
 		#
 		addChild: ( peer ) ->
 			if peer is @_parent
-				console.log "joost is een naus"
 				@removeParent()

 			if @getSibling(peer)?
@@ -251,7 +253,6 @@
 		#
 		addSibling: ( peer, instantiate = true ) ->
 			if peer is @_parent
-				console.log "asdasd2323"
 				@removeParent()

 			if @getChild(peer)?
@@ -303,6 +304,7 @@
 					@addSibling(peer)
 				@addSibling(peer) for peer in @getPeers() when peer.isSuperNode
 				unless @token?
+					console.log "I have the first token, bitch!"
 					@token = new Token()
 					@token.nodeId = @id
 					@token.position = @position
@@ -310,11 +312,15 @@
 			else
 				for sibling in @getSiblings()
 					@removeSibling(sibling)
+				@_recommendParent(false)
 				for child in @getChildren()
 					@removeChild(child)
 				@_selectParent()
-				@broadcast('token.die', @token.serialize())
-				@token = null
+				console.log @token
+				if @token?
+					@broadcast('token.die', @token.serialize())
+					console.log "I quit. Sending token.die", @token.id
+					@token = null

 		_onPeerDisconnect: (peer) =>
 			if peer is @_parent
@@ -350,13 +356,19 @@
 				superNode = superNodes[i]
 				superNodes.splice(i, 1)

-				peer = @connect(superNode.id)
-				peer.once('channel.opened', () =>
+				if peer = @getPeer(superNode.id)
 					@setParent(peer, (accepted) =>
 						unless accepted
 							connectParent(superNodes)
 					)
-				)
+				else
+					peer = @connect(superNode.id)
+					peer.once('channel.opened', () =>
+						@setParent(peer, (accepted) =>
+							unless accepted
+								connectParent(superNodes)
+						)
+					)

 			@server.query('nodes', 'node.structured', (nodes) =>
 				superNodes = _(nodes).filter( (node) -> node.isSuperNode)
@@ -397,22 +409,25 @@
 			if @isSuperNode
 				@server.query('nodes', 'node.structured', (nodes) =>
 					superNodes = _(nodes).filter( (node) -> node.isSuperNode)
-					superNodes = _(superNodes).filter( (node) => not @getPeer(node.id)? and node.id isnt @id )

 					if superNodes.length is 0
 						return

 					for superNode in superNodes
 						( (superNode) =>
-							peer = @connect(superNode.id)
-							peer.once('channel.opened', () =>
+
+							if peer = @getPeer(superNode.id)
 								@addSibling(peer)
-							)
+							else
+								peer = @connect(superNode.id)
+								peer.once('channel.opened', () =>
+									@addSibling(peer)
+								)
 						) (superNode)
 				)

 			else unless @_parent?
-				@_enterNetwork()
+				@_selectParent()

 			else
 				current = _(@getPeers()).filter( (peer) -> peer.isSuperNode).length
@@ -437,8 +452,11 @@
 			i = 0
 			for peer in @getPeers()
 				( ( peer ) =>
-					peer.ping( ( latency, position ) =>
+					peer.ping( ( latency, position, tokenString ) =>
 						peer.position = Vector.deserialize(position)
+						if tokenString?
+							token = Token.deserialize(tokenString)
+							@addToken(token)
 						i++
 						if i is @getPeers().length
 							@_computePosition()
@@ -457,12 +475,11 @@

 				@_coordinateDelta = Math.max(0.05, @_coordinateDelta - 0.025)

-			console.log "cieapo"
 			if @token?
 				@token.position = @position
 				@_computeTokenTargetPosition()

-		_recommendParent: () =>
+		_recommendParent: (includeSelf = true) =>

 			siblings = @getSiblings()
 			children = @getChildren()
@@ -470,15 +487,15 @@
 			if not @isSuperNode or children.length is 0 or siblings.length is 0
 				return

-			for child in children
+			for child in children when child.position?
 				closestSuperNode = null
 				closestDistance = Infinity
-				for sibling in siblings
+				for sibling in siblings when sibling.position?
 					distance = child.position.getDistance(sibling.position)
 					if distance < closestDistance
 						closestDistance = distance
 						closestSuperNode = sibling
-				if closestDistance < child.position.getDistance(@position) * @_superNodeSwitchThreshold
+				if closestDistance < child.position.getDistance(@position) * @_superNodeSwitchThreshold or not includeSelf
 					child.emit('peer.recommendParent', closestSuperNode.id)

@@ -526,7 +543,7 @@
 			if @token?
 				return

-			console.log tokenString
+			console.log "received token"
 			@token = Token.deserialize(tokenString)
 			@removeToken(@token)
 			@token.nodeId = @id
@@ -547,6 +564,7 @@

 		_onTokenDied: (tokenString) =>
 			token = Token.deserialize(tokenString)
+			console.log "Received dead token", token.id
 			@removeToken(token)

@@ -556,12 +574,11 @@

 			force = Vector.createZeroVector(@position.length)
 			for token in @_tokens
-				direction = @position.subtract(token.targetPosition)		# Difference between self and other Token
-				direction.scale(2 / direction.getLength())
-				force = force.add(direction)					# Sum all token differences
-
-			console.log "asd"
-			@token.targetPosition = @position.add(force)
+				direction = @position.subtract(token.position)		# Difference between self and other Token
+				direction.scale(1 / direction.getLength())
+				force = force.add(direction)								# Sum all token differences
+
+			@token.targetPosition = @token.position.add(force)

 			magnitude = @position.getDistance(@token.targetPosition)
 			if magnitude > @_tokenMoveThreshold
@@ -584,7 +601,7 @@
 			closestChild = null
 			closestDistance = Infinity

-			for child in @getChildren()
+			for child in @getChildren() when child.position?
 				distance = child.position.getDistance(token.targetPosition)
 				if distance < closestDistance
 					closestDistance = distance
@@ -618,9 +635,12 @@

 			console.log "best candidate is #{closestCandidate}, distance is #{closestDistance}"
 			if closestCandidate? and closestCandidate isnt @id
-				@emitTo(closestCandidate, 'token.add', @token.serialize())
-				@token = null
-				@setSuperNode(false)
+				@emitTo(closestCandidate, 'token.receive', @token.serialize())
+
+				if @isSuperNode
+					@setSuperNode(false)
+				else
+					@token = null

 			else
 				@token.candidates = []


Cleaning the spaces at the top of functions.

--- lib/text-objects.coffee
+++ lib/text-objects.coffee
@@ -14,7 +14,6 @@
   constructor: (@editor, @char) ->

   findOpeningQuote: (pos) ->
-
     pos = pos.copy()
     while pos.row >= 0
       line = @editor.lineForBufferRow(pos.row)
@@ -27,15 +26,14 @@
       -- pos.row

   select: ->
-
     start = @findOpeningQuote(@editor.getCursorBufferPosition())
     return [false] unless start?
-
+
     ++ start.column  # skip the opening quote
-
+
     end = start.copy()
     escaping = false
-
+
     while end.row < @editor.getLineCount()
       endLine = @editor.lineForBufferRow(end.row)
       while end.column < endLine.length
@@ -49,14 +47,13 @@
         ++ end.column
       end.column = 0
       ++ end.row
-
+
     [false]

 class SelectInsideBrackets extends TextObject
   constructor: (@editor, @beginChar, @endChar) ->

   findOpeningBracket: (pos) ->
-
     pos = pos.copy()
     depth = 0
     while pos.row >= 0
@@ -72,12 +69,11 @@
       -- pos.row

   select: ->
-
     start = @findOpeningBracket(@editor.getCursorBufferPosition())
     return [false] unless start?

     ++ start.column  # skip the opening bracket
-
+
     end = start.copy()
     depth = 0

@@ -95,8 +91,7 @@
         ++ end.column
       end.column = 0
       ++ end.row
-
+
     [false]

 module.exports = {SelectInsideWord, SelectInsideQuotes, SelectInsideBrackets}
-


fix createVhost

fix createVhost

--- kites/sharedHosting/api.coffee
+++ kites/sharedHosting/api.coffee
@@ -225,7 +225,15 @@
   #    else
   #      log.error stderr
   #      callback stderr
-  createVhost :do->
+  createVhost : (options,callback)->
+    {username,uid,domainName} = options
+
+    domainName ?= "#{username}.#{config.defaultDomain}"
+    targetPath = "/Users/#{username}/Sites/#{domainName}"
+
+    createDirs = ['-v','-p',targetPath]
+    copyFiles  = ['-v','-r',"#{config.defaultVhostFiles}/website",targetPath]
+    changeOwner = ['-v','-R',"#{uid}:#{uid}","#{targetPath}/website"]

     spawnWrapper = (command, args , callback)->
       wrapper = spawn command,args
@@ -245,30 +253,21 @@
           log.info info = "createVhost: #{command} done!"
           callback null, info

-    createVhost = (options,callback)->
-      {username,uid,domainName} = options
-
-      domainName ?= "#{username}.#{config.defaultDomain}"
-      targetPath = "/Users/#{username}/Sites/#{domainName}"
-
-      createDirs = ['-v','-p',targetPath]
-      copyFiles  = ['-v','-r',"#{config.defaultVhostFiles}/website",targetPath]
-      changeOwner = ['-v','-R',"#{uid}:#{uid}","#{targetPath}/website"]
-
-      spawnWrapper '/bin/mkdir',createDirs, (err,res)=>
-        if err?
-          callback "[ERROR] couldn't create vhost #{err}"
-        else
-          spawnWrapper '/bin/cp',copyFiles,(err,res)=>
-            if err?
-              callback "[ERROR] Culdn't create vhost: #{err}"
-            else
-              spawnWrapper '/bin/chown',changeOwner,(err,res)->
-                if err?
-                  callback "[ERROR] Culdn't create vhost: #{err}"
-                else
-                  log.info info = "[OK] vhost #{domainName} has been created"
-                  callback null, info
+
+    spawnWrapper '/bin/mkdir',createDirs, (err,res)=>
+      if err?
+        callback "[ERROR] couldn't create vhost #{err}"
+      else
+        spawnWrapper '/bin/cp',copyFiles,(err,res)=>
+          if err?
+            callback "[ERROR] Culdn't create vhost: #{err}"
+          else
+            spawnWrapper '/bin/chown',changeOwner,(err,res)->
+              if err?
+                callback "[ERROR] Culdn't create vhost: #{err}"
+              else
+                log.info info = "[OK] vhost #{domainName} has been created"
+                callback null, info

  # suspendUser : (options,callback)->
  #   #
@@ -377,3 +376,11 @@
  #                   log.debug "[OK] func:unSuspendUser: /usr/sbin/cagefsctl -w #{userToSuspend}"
  #                   res = "[OK] user #{userToSuspend} was successfully unsuspended"
  #                   log.info res; callback? null, res
+ #
+#s = new sharedHosting
+#options =
+#  username: 'aleksey-m'
+#  uid: 5339
+#s.createVhost options,(err,res)->
+#  console.log err if err?
+#  console.log res if res?


collectionShell: bugfix: accept offset of 0

showView was ignoring all falsy offsets - bugfix to ignore only
undefined offsets

--- coffee/src/shells/collection.shell.coffee
+++ coffee/src/shells/collection.shell.coffee
@@ -371,7 +371,7 @@
     unless view.el.parentNode is @el
       @$el.append view.render().el

-    if offset
+    if offset?
       view.seek offset

     if @options.showSubshellSummary


added csv helper and wrap functions

added csv helper and wrap functions

--- modules/gui/tools/widgets.coffee
+++ modules/gui/tools/widgets.coffee
@@ -1,10 +1,39 @@

-module.exports =
+module.exports = tools =
+
+    ##
+    ##
+    ##
+    csv:
+        wrap: (ele)->
+            ele._val = ele.val ## save for reuse in values
+
+            ele.val = (val)->
+                tools.csv.values ele, val
+
+            return ele
+
+        values: (ele, values)->
+            ele._val ?= ele.val ## only if not already defined
+
+            if values is undefined  ## read
+                ele._val().replace('/ /g','').split(',')
+
+            else ## write
+                if typeof values is 'string'
+                    values = [values]
+
+                ele._val values.join ', '

     ##
     ##
     ##
     select:
+        wrap: (ele)->
+            ele.val = (values)->
+                tools.select.values ele, values
+
+            return ele

         ##
         values: (ele, values)->
@@ -32,6 +61,12 @@
     ##
     checkbox:

+        wrap: (ele)->
+            ele.val = (value)->
+                tools.checkbox.value ele, value
+
+            return ele
+
         ##
         value: (ele, value)->
             if value is undefined


More concise class name.

More concise class name.

--- RedBlackTree.coffee
+++ RedBlackTree.coffee
@@ -23,7 +23,7 @@

 In our implementation, leaves are simply undefined.
 ###
-class RBT
+class RedBlackTree
     constructor: (valuesToAdd = []) ->
         @root
         @add value for value in valuesToAdd
@@ -313,5 +313,5 @@
     if _isLeftChild node then node.parent.rightChild
     else node.parent.leftChild

-module.exports = RBT
-
+module.exports = RedBlackTree
+


format multiple exercise query properly when it comes to double quotes

--- src/flux/reference-book-exercise.coffee
+++ src/flux/reference-book-exercise.coffee
@@ -9,6 +9,8 @@
   tags = _.map(multipleUrls, (url) ->
     queryString = decodeURIComponent(url.split(QUERY_START_STRING)[1])
     [param, value] = queryString.split(':')
+    # trim individual tag of double quotes
+    value = value.replace(/^"(.+(?="$))"$/, '$1')
     params[param] ?= []
     params[param].push(value)

@@ -16,7 +18,8 @@
   )
   urlsAndTags = _.object(multipleUrls, tags)
   queryStrings = _.map(params, (values, paramKey) ->
-    "#{paramKey}:#{values.join(',')}"
+    # join values and wrap all values with a single pair of double quotes
+    "#{paramKey}:\"#{values.join(',')}\""
   )
   queryString = queryStrings.join(' ')


New memoblock does not require setting all operations as properties anymore.

--- src/createProject.coffee
+++ src/createProject.coffee
@@ -32,11 +32,11 @@
     -> @replacements = ([new RegExp("---#{n}---","g"),v] for n,v of @values)
     -> @newContents = @contents.map (contents) => @replacements.reduce replace, contents
     -> @targetDirs = _.unique @targetPaths.map getDirname
-    -> @makingPaths = each @targetDirs, mkdirp
-    -> @writingFiles = applyEach _.zip(@targetPaths,@newContents), writeFile
+    -> each @targetDirs, mkdirp
+    -> applyEach _.zip(@targetPaths,@newContents), writeFile
     -> @rPaths = filter Object.keys(renames), (path) -> fsExists "#{targetDir}/#{path}"
     -> @renamePairs = (["#{targetDir}/#{path}","#{targetDir}/#{renames[path]}"] for path in @rPaths)
-    -> @renamingFiles = applyEach @renamePairs, rename
+    -> applyEach @renamePairs, rename
   ]

 replace = (string, replacement) ->


Improve salt modification for PBKDF2

Improve salt modification for PBKDF2

--- src/crypto_pbkdf2.coffee
+++ src/crypto_pbkdf2.coffee
@@ -8,6 +8,11 @@

 # Dependencies
 Crypto = require('crypto')
+
+
+# Constants
+SALT_SUFFIX = "00000001"
+

 ###*
  * @class A simple HMAC system
@@ -48,10 +53,11 @@
     password = new Buffer(password)

   if typeof salt is 'string'
-    salt = new Buffer(salt, 'hex')
+    salt = new Buffer(salt+SALT_SUFFIX, 'hex')
+  else
+    salt = Buffer.concat [salt, new Buffer(SALT_SUFFIX, 'hex')]

   hmac = new Hmac(password, length)
-  salt = Buffer.concat [salt, new Buffer('00000001', 'hex')]
   last = xorsum = hmac.encrypt(salt)
   bit_length = 512 / 8
   i = 1


Adding support of histogram datasets

Adding support of histogram datasets

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -387,6 +387,74 @@
             )
         )

+    setupBin: (binElement, dataset, y) ->
+        binElement
+            .attr("class", "bin")
+            .attr("stroke", dataset.color)
+            .attr("x", 1)
+            .attr("width", (d) => @scales.x(d.x.getTime() + d.dx) - @scales.x(d.x) - 1)
+            .attr("transform", (d) => "translate(" + @scales.x(new Date(d.x)) + ",-" + y(d.length) + ")")
+            .attr("height", (d) -> y(d.length))
+
+        binElement
+          .on("mouseover", (bin) =>
+            @element.dispatchEvent(
+                new CustomEvent('binMouseover', {
+                    detail: {
+                        dataset: dataset.id,
+                        start: bin.x,
+                        end: new Date(bin.x.getTime() + bin.dx),
+                        bin: bin
+                    }
+                    bubbles: true,
+                    cancelable: true
+                })
+            )
+
+            if bin.length
+                names = bin.filter((r) -> r[2] && (r[2].name || r[2].id))
+                  .map((r) -> (r[2].name || r[2].id))
+
+                if names.length
+                    @tooltip.transition()
+                        .duration(200)
+                        .style("opacity", .9)
+                    @tooltip.html(names.join("<br>"))
+                        .style("left", (d3.event.pageX) + "px")
+                        .style("top", (d3.event.pageY - 28) + "px")
+          )
+          .on("mouseout", (bin) =>
+            @element.dispatchEvent(
+                new CustomEvent('binMouseout', {
+                    detail: {
+                        dataset: dataset.id,
+                        start: bin.x,
+                        end: new Date(bin.x.getTime() + bin.dx),
+                        bin: bin
+                    }
+                    bubbles: true,
+                    cancelable: true
+                })
+            )
+            @tooltip.transition()
+                .duration(500)
+                .style("opacity", 0)
+          )
+          .on('click', (bin) =>
+            @element.dispatchEvent(
+                new CustomEvent('binClicked', {
+                    detail: {
+                        dataset: dataset.id,
+                        start: bin.x,
+                        end: new Date(bin.x.getTime() + bin.dx),
+                        bin: bin
+                    }
+                    bubbles: true,
+                    cancelable: true
+                })
+            )
+          )
+
     drawRanges: (datasetElement, dataset, records) ->
         datasetElement.selectAll('rect').remove()

@@ -423,6 +491,32 @@
             .call((recordElement) => @setupRecord(recordElement, dataset))

         p.exit().remove()
+
+    drawHistogram: (datasetElement, dataset, records) ->
+        ticks = @scales.x.ticks(10)
+        dx = ticks[1] - ticks[0]
+        ticks = [new Date(ticks[0].getTime() - dx)].concat(ticks).concat([new Date(ticks[ticks.length - 1].getTime() + dx)])
+
+        bins = d3.layout.histogram()
+          .bins(ticks)
+          .range(@scales.x.domain())
+          .value((record) -> new Date(record[0] + (record[1] - record[0]) / 2))(records)
+
+        y = d3.scale.linear()
+          .domain([0, 5]) #d3.max(bins, (d) -> d.length)])
+          .range([0, @options.height - 29])
+          .clamp(true)
+
+        bars = datasetElement.selectAll(".bin")
+          .data(bins)
+
+        bars.attr("class", "bin")
+          .call((binElement) => @setupBin(binElement, dataset, y))
+
+        bars.enter().append("rect")
+          .call((binElement) => @setupBin(binElement, dataset, y))
+
+        bars.exit().remove()

     drawPaths: (datasetElement, dataset, data) ->
         @scales.y.domain(d3.extent(data, (d) -> d[1]))
@@ -500,14 +594,20 @@
         if paths and paths.length
             @drawPaths(dataset.element, dataset, paths)
         else if records and records.length
-            points = dataset.getRecords().filter((record) =>
-                (@scales.x(new Date(record[1])) - @scales.x(new Date(record[0]))) < 5
-            )
-            ranges = dataset.getRecords().filter((record) =>
-                (@scales.x(new Date(record[1])) - @scales.x(new Date(record[0]))) >= 5
-            )
-            @drawRanges(dataset.element, dataset, ranges)
-            @drawPoints(dataset.element, dataset, points)
+            data = records.map((record) =>
+                new Date(record[0] + (record[1] - record[0]) / 2)
+            )
+            if dataset.histogram
+                @drawHistogram(dataset.element, dataset, records)
+            else
+                points = records.filter((record) =>
+                    (@scales.x(new Date(record[1])) - @scales.x(new Date(record[0]))) < 5
+                )
+                ranges = records.filter((record) =>
+                    (@scales.x(new Date(record[1])) - @scales.x(new Date(record[0]))) >= 5
+                )
+                @drawRanges(dataset.element, dataset, ranges)
+                @drawPoints(dataset.element, dataset, points)

     # this function triggers the reloading of a dataset (sync)

@@ -632,7 +732,8 @@
             lineplot: lineplot,
             debounceTime: @options.debounce,
             ordinal: @ordinal,
-            element: element
+            element: element,
+            histogram: definition.histogram
         })

         @reloadDataset(id)
@@ -728,7 +829,7 @@
 # Dataset utility class for internal use only
 class Dataset
     constructor: (options) ->
-        { @id,  @color, @source, @sourceParams, @index, @records, @paths, @lineplot, @ordinal, @element } = options
+        { @id,  @color, @source, @sourceParams, @index, @records, @paths, @lineplot, @ordinal, @element, @histogram } = options
         @syncDebounced = debounce(@sync, options.debounceTime)

     getSource: ->


Disable select previous node when node removed feature for now

--- components/tree/treeviewcontroller.coffee
+++ components/tree/treeviewcontroller.coffee
@@ -321,7 +321,8 @@

     if nodeData in @indexedNodes
       index = @indexedNodes.indexOf nodeData
-      @selectNode @nodes[@getNodeId @indexedNodes[index-1]] if index-1 >= 0
+      # Disable this for now, useless for most cases, FIXME GG
+      # @selectNode @nodes[@getNodeId @indexedNodes[index-1]] if index-1 >= 0
       @indexedNodes.splice index, 1
       # todo: make decoration with events
       if @nodes[@getNodePId nodeData] and not \


[api] factor out queueNames() for individual use

--- src/redis-model.coffee
+++ src/redis-model.coffee
@@ -355,14 +355,21 @@

     callback null, jobs

+  queueNames: (callback) ->
+    ideally   = errify callback
+
+    await @scanRedis "bull:*:id", ideally defer queues
+    names = (queue[5..-4] for queue, i in queues)
+
+    callback null, names
+
   queues: (callback) ->
     ideally   = errify callback
     allCounts = []

-    await @scanRedis "bull:*:id", ideally defer queues
+    await @queueNames ideally defer queues
     for queue, i in queues
-      name = queue[5..-4]
-      await @stateCounts name, ideally defer allCounts[i]
+      await @stateCounts queue, ideally defer allCounts[i]

     callback null, allCounts