make debugging code a little more managable

--- lib/scratch.coffee
+++ lib/scratch.coffee
@@ -1,8 +1,14 @@
-class JSONSpec
+EventEmitter = require('events').EventEmitter
+
+SINGLE_LINE_REGEXP = /\n/
+ISO_8601_REGEXP= /^(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(\.\d{3})?(([A-Z]+)|([+-]\d{2}\:\d{2}))$/
+URI_REGEXP = /^[a-z]+\:.+/
+
+class JSONSpec extends EventEmitter
+
   normalize:(src)=>
     dst = {}
     for k,v of src
-      console.log k
       dst[k] = @_normalize_attributes(v)
     return dst

@@ -31,10 +37,40 @@
               v.push "inclusive"
             dst[k] = v
           else
-            console.error "WARNING: Found unexpected parameter \"#{k}\"."
+            @_emit_error_message "specification-error","Found unexpected parameter \"#{k}\"."
       if dst.items? and not dst.datatype is 'array'
-        console.error "WARNING: Found 'items' for non-array datatype \"#{dst.datatype}\"."
+        @_emit_error_message "specification-error","Found 'items' for non-array datatype \"#{dst.datatype}\"."
       return dst
+
+  _emit_error_message:(type,message,tail...)=>
+    console.error type,message,tail...
+
+  _emit_message:(type,message,tail...)=>
+    # console.log type,message,tail...
+
+  _emit_validation_error:(data,spec,message_id,path,value,expected)=>
+    path = path.join '.'
+    switch message_id
+      when 'required'
+        message = "Required attribute \"#{expected}\" not found at #{path}."
+      when 'datatype'
+        message = "Expected datatype \"#{expected}\" but found #{typeof value} at #{path}."
+      when 'enum'
+        message = "Expected one of #{expected.join(', ')} but found \"#{value}\" at #{path}."
+      when 'range'
+        message = "Expected value between #{expected[0]} and #{expected[1]} (#{expected[2] ? 'inclusive'}) but found #{value} at #{path}."
+      when 'format-required'
+        message = "Expected value to match #{expected} but found #{value} at #{path}."
+      when 'format-forbidden'
+        message = "Expected value not to match #{expected} but found #{value} at #{path}."
+      else
+        message = "Validation error (#{message_id})."
+        if expected?
+          message += " Expected \"#{expected}\"."
+        if value?
+          message += " Found \"#{found}\"."
+        message += " At #{path}"
+    @_emit_error_message "validation-error",message

   validate:(data,spec,path)=>
     valid = true
@@ -47,47 +83,45 @@
           valid = false
       else
         if v?.constraints? and 'required' in v.constraints
-          console.error "WARNING: Required attribute #{k} not found at",p
+          @_emit_validation_error(data,spec,'required',p,data)
+          valid = false
     return valid

   _validate_attribute:(attr,spec,path)=>
     if Array.isArray(spec)
-      console.log "ALTERNATIVES AT",path
       for alt,i in spec
         if @_validate_attribute(attr,alt,path)
-          console.log "RETURNING true DUE TO ALT",i
           return true
-      console.log "RETURNING false DUE TO NO MATCING ALT"
       return false
     else
       valid = true
       unless attr?
         if spec?.constraints? and 'required' in spec.constraints
-          console.error "WARNING: Required attribute not found at",path
+          @_emit_validation_error(attr,spec,'required',path,attr)
           valid = false
         else
-          console.log "ok (null) at",path
+          @_emit_message "ok","(null)",path
       else
         # DATATYPE
         switch spec.datatype
           when 'string'
             unless typeof attr is 'string'
-              console.error "expected string, found ",typeof attr," at",path
-              valid = false
-            else
-              console.log 'ok (string) at',path
+              @_emit_validation_error(attr,spec,'datatype',path,attr,'string')
+              valid = false
+            else
+              @_emit_message "ok","(string)",path
           when 'number'
             unless typeof attr is 'number'
-              console.error "expected number, found ",typeof attr," at",path
-              valid = false
-            else
-              console.log 'ok (number) at',path
+              @_emit_validation_error(attr,spec,'datatype',path,attr,'number')
+              valid = false
+            else
+              @_emit_message "ok","(number)",path
           when 'array'
             unless Array.isArray(attr)
-              console.error "expected array, found ",typeof attr," at",path
-              valid = false
-            else
-              console.log 'ok (array) at',path
+              @_emit_validation_error(attr,spec,'datatype',path,attr,'array')
+              valid = false
+            else
+              @_emit_message "ok","(array)",path
               for elt,i in attr
                 p = [].concat(path)
                 p.push "[#{i}]"
@@ -95,9 +129,9 @@
                   valid = false
           when 'map'
             unless typeof attr is 'object'
-              console.error "expected map, found ",typeof attr," at",path
-            else
-              console.log 'ok (map) at',path
+              @_emit_validation_error(attr,spec,'datatype',path,attr,'object')
+            else
+              @_emit_message "ok","(map)",path
               if spec.attributes?
                 for k,v of spec.attributes
                   p = [].concat(path)
@@ -105,53 +139,54 @@
                   if not @_validate_attribute(attr[k],v,p)
                     valid = false
           else
-            console.error "datatype ",spec.datatype,"not handled"," at",path
+            @_emit_validation_error(attr,spec,'not-supported',path,spec.datatype)
             valid = false
         # ENUM
         if spec.enum?
           unless attr in spec.enum
-            console.error "expected one of ",spec.enum,"found", attr," at",path
+            @_emit_validation_error(attr,spec,'enum',path,attr,spec.enum)
             valid = false
           else
-            console.log "ok (enum) at",path
+            @_emit_message "ok","(enum)",path
         # RANGE
         if spec.range?
           if spec.range.length is 2 or spec.range[2] is 'inclusive'
             unless spec.range[0] <= attr <= spec.range[1]
-              console.error "expected a value between ",spec.range,"found", attr," at",path
-              valid = false
-            else
-              console.log "ok (range) at",path
+              @_emit_validation_error(attr,spec,"range",path,attr,spec.range)
+              valid = false
+            else
+              @_emit_message "ok","(range)",path
           else
             unless spec.range[0] < attr < spec.range[1]
-              console.error "expected a value between ",spec.range,"found", attr," at",path
-              valid = false
-            else
-              console.log "ok (range) at",path
+              @_emit_validation_error(attr,spec,"range",path,attr,spec.range)
+              valid = false
+            else
+              @_emit_message "ok","(range)",path
         # FORMAT
         if spec.format?
           for f in spec.format
             switch f
               when 'singleline'
-                if /\n/.test attr
-                  console.error "expected a single-line string at ",path
+                if SINGLE_LINE_REGEXP.test attr
+                  @_emit_validation_error(attr,spec,"format-forbidden",path,attr,SINGLE_LINE_REGEXP)
                   valid = false
                 else
-                  console.log "ok (singleline) at",path
+                  @_emit_message "ok","(singleline)",path
               when 'iso8601date'
-                unless /^(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(\.\d{3})?(([A-Z]+)|([+-]\d{2}\:\d{2}))$/.test attr
-                  console.error "expected an ISO 8601 formatted date at ",path,"found",attr
+                unless ISO_8601_REGEXP.test attr
+                  @_emit_validation_error(attr,spec,"format-required",path,attr,ISO_8601_REGEXP)
                   valid = false
                 else
-                  console.log "ok (iso8601date) at",path
+                  @_emit_message "ok","(iso8601date)",path
               when 'uri'
-                unless /^[a-z]+\:.+/.test attr
-                  console.error "expected a URI at ",path,"found",attr
+                unless URI_REGEXP.test attr
+                  @_emit_validation_error(attr,spec,"format-required",path,attr,URI_REGEXP)
                   valid = false
                 else
-                  console.log "ok (uri) at",path
-      console.log "RETURNING",valid
+                  @_emit_message "ok","(uri)",path
+      @_emit_message "returning",valid,path
       return valid
+

 ################################################################################


Fix typo in palette-dropping

Fix typo in palette-dropping

--- src/controller.coffee
+++ src/controller.coffee
@@ -880,7 +880,7 @@
       if 0 < palettePoint.x < @paletteCanvas.width and
          0 < palettePoint.y < @paletteCanvas.height or not
          (0 < renderPoint.x < @mainCanvas.width and
-         0 < renderPoint. x < @mainCanvas.height)
+         0 < renderPoint.y < @mainCanvas.height)
         @draggingBlock = null
         @draggingOffset = null
         @lastHighlight = null


Use event.shiftKey instead of @shiftKeyPressed

--- src/controller.coffee
+++ src/controller.coffee
@@ -1031,7 +1031,7 @@
           w: MAX_DROP_DISTANCE * 2
           h: MAX_DROP_DISTANCE * 2
         }, (point) =>
-          unless (point.acceptLevel is helper.DISCOURAGED) and not @shiftKeyPressed
+          unless (point.acceptLevel is helper.DISCOURAGED) and not event.shiftKey
             distance = mainPoint.from(point)
             distance.y *= 2; distance = distance.magnitude()
             if distance < min and mainPoint.from(point).magnitude() < MAX_DROP_DISTANCE and
@@ -2392,8 +2392,8 @@

     return head.container

-  hook 'key.tab', 0, ->
-    if @shiftKeyPressed
+  hook 'key.tab', 0, (state, event) ->
+    if event.shiftKey
       if @textFocus? then head = @textFocus.start
       else head = @cursor

@@ -2492,21 +2492,10 @@
   hook 'populate', 0, ->
     @handwrittenBlocks = []

-    @shiftKeyPressed = false
-
-    # Keep track of whether the shift
-    # key is pressed; it will
-    # suppress the normal "enter"
-    # handler.
-    @keyListener.register_combo
-      keys: 'shift'
-      on_keydown: => @shiftKeyPressed = true
-      on_keyup: => @shiftKeyPressd = false
-
     @keyListener.register_combo
       keys: 'enter'
-      on_keydown: =>
-        unless @textFocus? or @shiftKeyPressed
+      on_keydown: (event) =>
+        unless @textFocus? or event.shiftKey
           @setTextInputFocus null

           # Construct the block; flag the socket as handwritten
@@ -2534,7 +2523,7 @@

           @newHandwrittenSocket = newSocket

-        else if @textFocus? and not @shiftKeyPressed
+        else if @textFocus? and not event.shiftKey
           @setTextInputFocus null; @redrawMain()
         else
           return true


Remove extraneous log

Remove extraneous log

--- src/controller.coffee
+++ src/controller.coffee
@@ -1908,7 +1908,6 @@
 Editor::setPalette = (paletteGroups) ->
   @paletteHeader.innerHTML = ''
   @session.paletteGroups = paletteGroups
-  console.log 'setting palette'

   @session.currentPaletteBlocks = []
   @session.currentPaletteMetadata = []


Don't subtract the gutter width from overlay canvas heights.

--- src/controller.coffee
+++ src/controller.coffee
@@ -931,7 +931,7 @@
     @highlightCanvas.width = @dropletElement.offsetWidth - @gutter.offsetWidth
     @highlightCanvas.style.width = "#{@highlightCanvas.width}px"

-    @highlightCanvas.height = @dropletElement.offsetHeight - @gutter.offsetWidth
+    @highlightCanvas.height = @dropletElement.offsetHeight
     @highlightCanvas.style.height = "#{@highlightCanvas.height}px"

     @highlightCanvas.style.left = "#{@mainCanvas.offsetLeft}px"
@@ -2372,7 +2372,7 @@
     @lassoSelectCanvas.width = @dropletElement.offsetWidth - @gutter.offsetWidth
     @lassoSelectCanvas.style.width = "#{@lassoSelectCanvas.width}px"

-    @lassoSelectCanvas.height = @dropletElement.offsetHeight - @gutter.offsetWidth
+    @lassoSelectCanvas.height = @dropletElement.offsetHeight
     @lassoSelectCanvas.style.height = "#{@lassoSelectCanvas.height}px"

     @lassoSelectCanvas.style.left = "#{@mainCanvas.offsetLeft}px"
@@ -4108,7 +4108,7 @@
     @cursorCanvas.width = @dropletElement.offsetWidth - @gutter.offsetWidth
     @cursorCanvas.style.width = "#{@cursorCanvas.width}px"

-    @cursorCanvas.height = @dropletElement.offsetHeight - @gutter.offsetWidth
+    @cursorCanvas.height = @dropletElement.offsetHeight
     @cursorCanvas.style.height = "#{@cursorCanvas.height}px"

     @cursorCanvas.style.left = "#{@mainCanvas.offsetLeft}px"


Remove unnecessary console.log

Remove unnecessary console.log

--- src/controller.coffee
+++ src/controller.coffee
@@ -4324,7 +4324,6 @@
   # and graphics
   if @annotations[line]?
     lineDiv.className += ' droplet_' + getMostSevereAnnotationType(@annotations[line])
-    #lineDiv.style.backgroundPosition = "2px #{treeView.distanceToBase[line].above - @view.opts.textHeight - @fontAscent}px"
     lineDiv.title = @annotations[line].map((x) -> x.text).join('\n')

   # Add breakpoint graphics
@@ -4348,9 +4347,7 @@
 }
 TYPE_FROM_SEVERITY = ['info', 'warning', 'error']
 getMostSevereAnnotationType = (arr) ->
-  result = TYPE_FROM_SEVERITY[Math.max.apply(this, arr.map((x) -> TYPE_SEVERITY[x.type]))]
-  console.log 'annotation type', result
-  return result
+  TYPE_FROM_SEVERITY[Math.max.apply(this, arr.map((x) -> TYPE_SEVERITY[x.type]))]

 Editor::findLineNumberAtCoordinate = (coord) ->
   treeView = @view.getViewNodeFor @tree


Fix map switching logic

Fix map switching logic

...when combining color blindness and visually impaired. Fixes #386

--- src/views/personalisation.coffee
+++ src/views/personalisation.coffee
@@ -132,7 +132,10 @@
                     if modeIsSet
                         newBackground = 'accessible_map'
                     else if currentBackground == 'accessible_map'
-                        newBackground = 'servicemap'
+                        if p13n.getAccessibilityMode('visually_impaired') || p13n.getAccessibilityMode('colour_blind')
+                            newBackground = 'accessible_map'
+                        else
+                            newBackground = 'servicemap'
                     if newBackground
                         p13n.setMapBackgroundLayer newBackground
             else if group == 'city'


Last working state

Last working state

--- vendor/assets/javascripts/nested-form.js.coffee
+++ vendor/assets/javascripts/nested-form.js.coffee
@@ -1,8 +1,7 @@
 class NestedForm
   constructor: (@selector) ->
     # the jquery element should be the field_wrapper
-    if ($(selector).length)
-      this.j_el = $(selector)
+    this.j_el = $(selector)
     if (this.j_el).length > 0
       this.bind_directional_buttons()
       this.set_directional_button_ability()


Server: Tests: modify helper according to new return data

--- servers/testhelper/handler/createssotokenhelper.coffee
+++ servers/testhelper/handler/createssotokenhelper.coffee
@@ -41,7 +41,9 @@
   request.post createSsoTokenRequestParams, (err, res, body) ->
     expect(err).to.not.exist
     expect(res.statusCode).to.be.equal 200
-    return callback { token : JSON.parse(body).token }
+    expect(JSON.parse(body).error).to.not.exist
+    expect(JSON.parse(body).data).to.exist
+    return callback { token : JSON.parse(body).data.token }

 createUserAndSsoToken = (apiToken, opts, callback) ->


Test categories().

Test categories().

--- spec/javascripts/application/seeds_store_spec.js.coffee
+++ spec/javascripts/application/seeds_store_spec.js.coffee
@@ -60,6 +60,16 @@
     beforeEach (done) ->
       expect(@subject.update(@listByName()))
         .eventually.notify(done)
+    beforeEach (done) ->
+      expect(@subject.updateCategories())
+        .eventually.notify(done)
+    it 'categories()', (done) ->
+      expect(@subject.categories()).eventually
+        .eql([
+          {name: 'a', podsCount: 1},
+          {name: 'b', podsCount: 1},
+          {name: 'c', podsCount: 4}
+        ]).notify(done)
     expectReadAll = () ->
       describe 'readFromAll()', ->
         it 'from the beginning', (done) ->


Show full dir to item in title bar if item not in a project folder

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -182,7 +182,7 @@
       projectPath = _.find projectPaths, (projectPath) ->
         itemPath is projectPath or itemPath?.startsWith(projectPath + path.sep)
     itemTitle ?= "untitled"
-    projectPath ?= projectPaths[0]
+    projectPath ?= if itemPath then path.dirname(itemPath) else null
     if projectPath?
       projectPath = fs.tildify(projectPath)


SocialWorker: JAccount: check if session is valid

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -1413,8 +1413,15 @@

   fetchMySessions: secure (client, callback) ->

+    { sessionToken } = client
+    return callback new KodingError 'Invalid session.'  unless sessionToken
+
     JSession = require './session'
-    username = @profile.nickname
-    JSession.some { username }, { limit : 10 }, callback
-
-
+    JSession.one { clientId : sessionToken }, (err, session) ->
+      return callback err  if err
+      return callback new KodingError 'Invalid session.'  unless session
+
+      username = session.username
+      JSession.some { username }, { limit : 10 }, callback
+
+


Sidebar: Decorate collaboration environment

--- workers/social/lib/social/models/sidebar.coffee
+++ workers/social/lib/social/models/sidebar.coffee
@@ -4,7 +4,7 @@
 JMachine   = require './computeproviders/machine'
 JWorkspace = require './workspace'

-SocialMessage = require './socialapi/message'
+SocialChannel = require './socialapi/channel'

 KodingError = require '../error'

@@ -23,6 +23,7 @@
     data =
       own: []
       shared: []
+      collaboration: []

     client.connection.delegate.fetchUser (err, user) ->

@@ -124,6 +125,15 @@
             if isMachineShared user, machine
             then successFn()
             else failureFn()
+
+          ->
+
+            return filterQueue.fin()  unless workspace.channelId
+
+            successFn = makeSuccessFn addCollaborationFn
+
+            options = {client, user, workspace, successFn, failureFn}
+            filterCollaborationWorkspace options
         ]

         dash filterQueue, -> workspaceQueue.fin()
@@ -147,3 +157,16 @@
         return yes

     return no
+
+
+  filterCollaborationWorkspace = (options = {}) ->
+
+    {client, user, workspace, successFn, failureFn} = options
+
+    SocialChannel.byId client, id: workspace.channelId, (err, channel) ->
+
+      if err
+        console.error 'Fetch workspace channel:', err
+        return failureFn err
+
+      successFn()


Better loading of suggestions

Better loading of suggestions

--- app/assets/javascripts/distribution.js.coffee
+++ app/assets/javascripts/distribution.js.coffee
@@ -1,19 +1,38 @@
 $(document).on "page:change", ->
   input = $("#add-recipients-input")
   suggestions = $("#recipient-suggestions")
+  target = $("#recipients")

+  timer = null
+  request = null
   updateSuggestions = (text) ->
-    $.ajax
-      type: 'GET'
-      url: input.data('suggestion-url')
-      data:
-        text: text
-      success: (data) ->
-        suggestions.html(data)
-        suggestions.find(".add-recipient-button").click ->
-          recipients = $(this).data("recipients")
-          $("#recipients").append(recipients)
-          false
+    if timer
+      clearTimeout(timer)
+      timer = null
+
+    if request
+      request.abort()
+      request = null
+
+    suggestions.html("
Loading...
")
+
+    timer = setTimeout ->
+      request = $.ajax
+        type: 'GET'
+        url: input.data('suggestion-url')
+        data:
+          text: text
+        success: (data) ->
+          suggestions.hide().html(data).slideDown("fast")
+          suggestions.find(".add-recipient-button").click ->
+            recipients = $(this).data("recipients")
+            target.append(recipients)
+            suggestions.html("")
+            input.val("")
+            false
+        error: ->
+          suggestions.html("An error occured.")
+    , 200

   input.on "input", ->
     updateSuggestions(input.val())


Fix DOM thrashing, makes widgets way faster

--- app/assets/javascripts/plugins/borg.js.coffee
+++ app/assets/javascripts/plugins/borg.js.coffee
@@ -9,6 +9,11 @@

   getTask: (host) ->
     @tasks[host] ||= @createTask(host)
+
+  updateTasksDOM: ->
+    for _, task of @tasks
+      task.updateDOM()
+    null

   addHeading: ->
     @$headingEl = $("

")
@@ -39,6 +44,7 @@
       @activate()

     @parse(parser)
+    @updateTasksDOM()

     if parser.findTaskEnd(@capistranoTask)
       @finish()
@@ -140,6 +146,9 @@

   update: (attrs) ->
     $.extend(this, attrs)
+    this
+
+  updateDOM: ->
     @$element.find('.task-lights-boxes').empty().append(@genBoxes())
     this


[grunt] be a bit more abstract about what we're watching for

--- tasks/grunt-contrib-watch.coffee
+++ tasks/grunt-contrib-watch.coffee
@@ -27,16 +27,16 @@
     images:
       files: ['<%= path.source.images %>/{,**/}*']
       tasks: ['copy:images']
-    nunjucks:
+    layouts:
       files: ['<%= path.source.layouts %>/{,**/}*.nj', '!<%= path.source.layouts %>{,**/}_*.nj']
       tasks: ['newer:nunjucks']
-    nunjucksPartials:
+    layoutsPartials:
       files: ['<%= path.source.layouts %>/{,**/}_*.nj']
       tasks: ['nunjucks']
     scripts:
       files: ['<%= path.source.scripts %>/{,**/}*.js']
       tasks: ['shell:jspm_build']
-    scss:
+    styles:
       files: [
         '<%= path.source.styles %>/{,**/}*.scss'
         '<%= path.temp.styles %>/{,**/}*.scss'


Stop simulation at 30s

Stop simulation at 30s

--- assets/javascripts/simulator/simulator.coffee
+++ assets/javascripts/simulator/simulator.coffee
@@ -5,7 +5,7 @@
   try
     results = scheduler.startWithTiming ->
       observableFactory(scheduler)
-    , 0, 0, 100000
+    , 0, 0, 30000
     collector.results()
   catch error
     error: error


limit query between 10km bounds

limit query between 10km bounds

--- server/app/controllers/measurement.coffee
+++ server/app/controllers/measurement.coffee
@@ -51,8 +51,12 @@
 # get measurements for wifi access points with GET /measurement/wifi
 router.get '/wifi', (req, res, next) ->
   points = []
+  if(distanceKm(req.query.nelat, req.query.nelng, req.query.swlat, req.query.swlng) > 10.0)
+    res.json(points)
+    return
   if req.query.nelat is undefined or req.query.swlat is undefined or req.query.nelng is undefined or req.query.swlng is undefined
     res.json(points)
+    return
   Measurement.find(
     location:
       $geoWithin:
@@ -72,8 +76,12 @@
 # get measurements for signal strength with GET /measurement/signal
 router.get '/signal', (req, res, next) ->
   points = []
+  if(distanceKm(req.query.nelat, req.query.nelng, req.query.swlat, req.query.swlng) > 10.0)
+    res.json(points)
+    return
   if req.query.nelat is undefined or req.query.swlat is undefined or req.query.nelng is undefined or req.query.swlng is undefined
     res.json(points)
+    return
   Measurement.find
     location:
       $geoWithin:
@@ -89,3 +97,12 @@
       points.push([point.location.coordinates[0], point.location.coordinates[1],  parseFloat(point.signalDBm) / 31.0])
     res.json(points)

+
+distanceKm = (lat1, lon1, lat2, lon2) ->
+  R = 6371
+  dLat = (lat2 - lat1) * Math.PI / 180
+  dLon = (lon2 - lon1) * Math.PI / 180
+  a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2)
+  c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
+  d = R * c
+  return d


get only compatible scales in option_value_namer.js.coffee

--- app/assets/javascripts/admin/products/services/option_value_namer.js.coffee
+++ app/assets/javascripts/admin/products/services/option_value_namer.js.coffee
@@ -53,17 +53,18 @@
       [value, unit_name]

     scale_for_unit_value: ->
-      # Find the largest available unit where unit_value comes to >= 1 when expressed in it.
-      # If there is none available where this is true, use the smallest available unit.
-      unit = ([scale, unit_name] for scale, unit_name of VariantUnitManager.unitNames[@variant.product.variant_unit] when @variant.unit_value / scale >= 1).reduce (unit, [scale, unit_name]) ->
-        if (unit && scale > unit[0]) || !unit?
-          [scale, unit_name]
-        else
-          unit
-      , null
-      if !unit?
-        unit = ([scale, unit_name] for scale, unit_name of VariantUnitManager.unitNames[@variant.product.variant_unit]).reduce (unit, [scale, unit_name]) ->
-          if scale < unit[0] then [scale, unit_name] else unit
-        , [Infinity,""]
+      # Find the largest available and compatible unit where unit_value comes
+      # to >= 1 when expressed in it.
+      # If there is none available where this is true, use the smallest
+      # available unit.
+      product = @variant.product
+      scales = VariantUnitManager.compatibleUnitScales(product.variant_unit_scale, product.variant_unit)
+      variantUnitValue = @variant.unit_value

-      unit
+      # sets largestScale = last element in filtered scales array
+      [_, ..., largestScale] = (scales.filter (s) -> variantUnitValue / s >= 1)
+
+      if (largestScale)
+        [largestScale, VariantUnitManager.getUnitName(largestScale, product.variant_unit)]
+      else
+        [scales[0], VariantUnitManager.getUnitName(scales[0], product.variant_unit)]


fix coffee lint

fix coffee lint

--- app/assets/javascripts/app/controllers/_application_controller_table.coffee
+++ app/assets/javascripts/app/controllers/_application_controller_table.coffee
@@ -45,7 +45,7 @@
           helper = tr.clone()
           helper.children().each (index) ->
             # Set helper cell sizes to match the original sizes
-            $(this).width( originals.eq(index).outerWidth() )
+            $(@).width( originals.eq(index).outerWidth() )
           return helper
         update:               @dndCallback
       @el.find('table > tbody').sortable(dndOptions)


Improved way to set notification popover height. Added immediately set counter to 0 on client side after "mark all as read" click.

--- app/assets/javascripts/app/controllers/widget/online_notification.js.coffee
+++ app/assets/javascripts/app/controllers/widget/online_notification.js.coffee
@@ -47,6 +47,7 @@
       @toggle.append('
' + count.toString() + '
')

   markAllAsRead: =>
+    @counterUpdate(0)
     @ajax(
       id:   'markAllAsRead'
       type: 'POST'
@@ -64,13 +65,19 @@
     @updateContent()

     # set heigth of notification popover
-    height = $('#app').height()
-    $('.js-notificationsContainer').css('height', "#{height-12}px")
+    heightApp            = $('#navigation').height()
+    heightPopoverHeader  = $('.js-notificationsContainer .popover-notificationsHeader').height()
+    heightPopoverContent = $('.js-notificationsContainer .popover-content').get(0).scrollHeight
+    height               = heightPopoverHeader + heightPopoverContent
+    if height > heightApp
+      height = heightApp - heightPopoverHeader - 56
+    $('.js-notificationsContainer .popover-content').css('height', "#{height}px")
+
+    # set popover arrow
     $('.js-notificationsContainer .arrow').css('top', '20px')

     # close notification list on click
     $('.js-notificationsContainer').on('click', (e) =>
-      #console.log('CL')
       @hidePopover()
     )


Made the autosave debounce faster and fix the textarea to trigger debounce on keyup

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -286,14 +286,14 @@
     #TODO: indicators, error handlers?

   triggerAutosave = (e) ->
-    window.autosave_timer ||= setTimeout( autosave, 1000 )
+    window.autosave_timer ||= setTimeout( autosave, 500 )

   $(".js-trigger-autosave").debounce "change", triggerAutosave, 50
   $("input[type='text'].js-trigger-autosave").debounce "keyup", triggerAutosave, 50
   $("input[type='number'].js-trigger-autosave").debounce "keyup", triggerAutosave, 50
   $("input[type='url'].js-trigger-autosave").debounce "keyup", triggerAutosave, 50
   $("input[type='tel'].js-trigger-autosave").debounce "keyup", triggerAutosave, 50
-  $("textarea.js-trigger-autosave").debounce "click", triggerAutosave, 50
+  $("textarea.js-trigger-autosave").debounce "keyup", triggerAutosave, 50

   updateUploadListVisiblity = (list, button, max) ->
     list_elements = list.find("li")


Restore fluent transition of bar charts. Fixes #1620 and #1631

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -143,12 +143,12 @@
       stacked_data = _.flatten @stack_method(@prepare_data())
       @svg.selectAll('rect.serie')
         .data(stacked_data, (s) -> s.id)
+        .transition()
         .attr('y', (d) => @series_height - @y(d.y0 + d.y))
         .attr('height', (d) => @y(d.y))
         .attr("data-tooltip-text", (d) =>
           Metric.autoscale_value d.y, @model.get('unit')
         )
-        .transition()

       # move the target lines
       @svg.selectAll('rect.target_line')


Remove opacity=0.8 from stacked bar chart series

Closes #1855

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -82,7 +82,6 @@
         .attr('data-tooltip-title', (s) => s.label)
         .attr('y', @series_height)
         .style('fill', (d) => d.color)
-        .style('opacity', 0.8)

       $("#{@container_selector()} rect.serie").qtip
         content:


register category change watcher once

register category change watcher once


Fixed positionPageHeaderStart at show controller.

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -85,7 +85,7 @@
     meta

   url: =>
-    '#ticket/zoom/' + @ticket_id
+    "#ticket/zoom/#{@ticket_id}"

   show: (params) =>

@@ -108,7 +108,7 @@
       @highlighed = true
       @highligher.loadHighlights()

-    if !@shown
+    if @shown

       # trigger shown to article
       App.Event.trigger('ui::ticket::shown', { ticket_id: @ticket_id } )


Set online notifications always to seen on controller show().

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -89,6 +89,11 @@

   show: (params) =>

+    @navupdate '#'
+
+    # set all notifications to seen
+    App.OnlineNotification.seen( 'Ticket', @ticket_id )
+
     # if controller is executed twice, go to latest article
     if @activeState
       @scrollToBottom()
@@ -103,8 +108,6 @@
       @highlighed = true
       @highligher.loadHighlights()

-    App.OnlineNotification.seen( 'Ticket', @ticket_id )
-    @navupdate '#'
     @positionPageHeaderStart()

   hide: =>


Hide "discard" button right after click.

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -572,8 +572,6 @@

     console.log('ticket validateion ok')

-    @formEnable(e)
-
     # validate article
     articleParams = @formParam( @$('.article-add') )
     console.log "submit article", articleParams
@@ -678,11 +676,22 @@
     @localTaskData = data
     App.TaskManager.update( @task_key, { 'state': @localTaskData })

-  taskReset: (area, data) =>
+  taskReset: =>
+    # hide reset button
+    @$('.js-reset').addClass('hide')
+
+    # reset task state
     @localTaskData =
       ticket:  {}
       article: {}
     App.TaskManager.update( @task_key, { 'state': @localTaskData })
+
+  @bind(
+    'ui::ticket::taskReset'
+    (data) =>
+      if data.ticket_id is @ticket.id
+        @taskReset()
+  )

 class TicketTitle extends App.Controller
   events:
@@ -1218,7 +1227,11 @@

   reset: (e) =>
     e.preventDefault()
-    App.TaskManager.update( @task_key, { 'state': {} })
+
+    # reset task
+    App.Event.trigger('ui::ticket::taskReset', { ticket_id: @ticket.id } )
+
+    # rerender edit area
     @render()


Fix for when a table doesn't have a datacenter.

--- admin/static/coffee/namespaces/replicas.coffee
+++ admin/static/coffee/namespaces/replicas.coffee
@@ -159,8 +159,8 @@
                 @datacenter = universe_datacenter
             else if datacenters.get(datacenter_id)?
                 @datacenter = datacenters.get datacenter_id
-
-            @datacenter.on 'all', @render
+            if @datacenter?
+                @datacenter.on 'all', @render

             @model.on 'change:primary_uuid', @render
             progress_list.on 'all', @render_progress
@@ -184,6 +184,8 @@
             @render()

         render_progress: =>
+            if not @datacenter?
+                return ''
             progress_data = DataUtils.get_backfill_progress_agg @model.get('id'), @datacenter.get('id')

             if progress_data? and _.isNaN(progress_data.percentage) is false
@@ -198,6 +200,8 @@
                 @.$('.progress_bar_full_container').slideUp 'fast'

         render: =>
+            if not @datacenter?
+                return ''
             replicas_count = DataUtils.get_replica_affinities(@model.get('id'), @datacenter.get('id'))
             if @model.get('primary_uuid') is @datacenter.get('id')
                 replicas_count++
@@ -454,7 +458,8 @@

         destroy: =>
-            @datacenter.off 'all', @render
+            if @datacenter?
+                @datacenter.off 'all', @render

             @model.off 'change:primary_uuid', @render
             progress_list.off 'all', @render_progress


ComputeController: findUidFromMachineId: helper for finding uid of given machine

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -721,3 +721,9 @@
     @fetchUserPlan (plan)=> @fetchPlans (plans)=>
       @fetchUsage { provider }, (err, usage)->
         callback err, { plan, plans, usage }
+
+
+  findUidFromMachineId: (machineId)->
+
+    for machine in @machines
+      return machine.uid  if machine._id is machineId


kdoing-apps-controller: Removed adding ace to apps extensions map. Because it was breaking fetchApps method. Still need to find a better way to do that.

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -606,13 +606,13 @@
     @extensionToApp = map = {}
     @fetchApps (err, res) =>
       # TODO: Find a better way to add Ace extensions to this map. (fatihacet)
-      res.Ace =
-        name     : "Ace"
-        fileTypes: [
-          "php", "pl", "py", "jsp", "asp", "aspx", "htm", "html", "phtml","shtml",
-          "sh", "cgi", "htaccess","fcgi","wsgi","mvc","xml","sql","rhtml", "diff",
-          "js","json", "coffee", "css","styl","sass", "scss", "less", "txt"
-        ]
+      # res.Ace =
+      #   name     : "Ace"
+      #   fileTypes: [
+      #     "php", "pl", "py", "jsp", "asp", "aspx", "htm", "html", "phtml","shtml",
+      #     "sh", "cgi", "htaccess","fcgi","wsgi","mvc","xml","sql","rhtml", "diff",
+      #     "js","json", "coffee", "css","styl","sass", "scss", "less", "txt"
+      #   ]
       for key, app of res
         fileTypes = app.fileTypes
         if fileTypes


flux/env: make sure accessLevel is group before check rev

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/flux/environment/actions.coffee
+++ client/app/lib/flux/environment/actions.coffee
@@ -64,7 +64,10 @@
   { _id: id } = stackTemplate

   stackTemplate.on 'update', ->
-    computeController.checkRevisionFromOriginalStackTemplate stackTemplate
+    debug 'stack template updated', stackTemplate
+    if stackTemplate.accessLevel is 'group'
+      computeController.checkRevisionFromOriginalStackTemplate stackTemplate
+
   stackTemplate.on 'deleteInstance', ->
     reactor.dispatch actions.REMOVE_STACK_TEMPLATE_SUCCESS, { id }


NEW: BitmapElement's soure can be an HTML Image - even if it isn't loaded yet, it'll work

--- source/Art/Engine/Elements/Shapes/BitmapElement.coffee
+++ source/Art/Engine/Elements/Shapes/BitmapElement.coffee
@@ -6,6 +6,7 @@
 {ceil, round} = Math
 {defineModule, inspect, min, max, bound, log, createWithPostCreate, isString, isNumber, BaseObject, isPlainArray, timeout} = Foundation
 {point, rect, Matrix, point0, point1} = Atomic
+{isImage} = Canvas

 defineModule module, class BitmapElement extends ShadowableElement

@@ -19,6 +20,7 @@

     # OUT: promise.then (bitmap) ->
     get: (url, initializerPromise) ->
+      return Canvas.Bitmap.get url unless isString url # non-string sources are not cached
       @_referenceCounts[url] = (@_referenceCounts[url] || 0) + 1
       out = @_cache[url] ||= initializerPromise || Canvas.Bitmap.get url
       out.then (bitmap) => @_loaded[url] = bitmap
@@ -36,7 +38,7 @@
     # returns true if the bitmap was released
     # returns false if there are still other references
     release: (url) ->
-      return unless url
+      return unless isString url
       # log "SourceToBitmapCache#release: #{url}"
       throw new Error "no references for #{url}" unless isNumber @_referenceCounts[url]
       if @_referenceCounts[url] == 0 || !isNumber @_referenceCounts[url]
@@ -97,7 +99,11 @@
     ###
     source:
       default:    null
-      validate:   (v) -> !v || isString v
+      validate:   (v) ->
+        !v ||
+        (isImage v) ||
+        (isString v)
+
       postSetter: (v) -> v && @_loadBitmapFromSource v

     ###


surface-pro-6.coffee: Updating links to getting started guide

Signed-off-by: Gareth Davies <2f9181a725196cd0a5e07c6a6e161d83fcc71157@balena.io>

--- surface-pro-6.coffee
+++ surface-pro-6.coffee
@@ -31,9 +31,9 @@
 	].concat(postProvisioningInstructions)

 	gettingStartedLink:
-		windows: 'http://docs.resin.io/surface-pro-6/nodejs/getting-started/#adding-your-first-device'
-		osx: 'http://docs.resin.io/surface-pro-6/nodejs/getting-started/#adding-your-first-device'
-		linux: 'http://docs.resin.io/surface-pro-6/nodejs/getting-started/#adding-your-first-device'
+		windows: 'https://www.balena.io/docs/learn/getting-started/intel-nuc/nodejs/'
+		osx: 'https://www.balena.io/docs/learn/getting-started/intel-nuc/nodejs/'
+		linux: 'https://www.balena.io/docs/learn/getting-started/intel-nuc/nodejs/'

 	yocto:
 		machine: 'surface-pro-6'


Added problems

Added problems

--- server/materials/data/topics/lca.coffee
+++ server/materials/data/topics/lca.coffee
@@ -9,7 +9,11 @@
     return {
         topic: topic("LCA", "Задачи на LCA", [
             label("
Теория по LCA от Алексея Упирвицкого
\n
См. еще теорию на e-maxx (там несколько разделов).
"),
-            label("TODO"),
-            problem(80)
-        ])
+            problem(111796),
+            problem(111711),
+            problem(111894)
+        ]),
+        advancedProblems: [
+            problem(724)
+        ]
     }


Seems like array comprehensions need to be surrounded with parentheses.

--- web/js/views/map_view.coffee
+++ web/js/views/map_view.coffee
@@ -30,11 +30,11 @@

     drawRoute: (route) ->
       @clearRoute()
-      shapes = leg.shape for leg in route[0].legs
+      shapes = (leg.shape for leg in route[0].legs)
       points = _.reduce shapes,
         (res, shape) -> res.concat shape,
         []
-      latLngs = new google.maps.LatLng point.y, point.x for point in points
+      latLngs = (new google.maps.LatLng point.y, point.x for point in points)

       @route = new google.maps.Polyline(


device name properly returned in get_info call

--- src/demo-model.coffee
+++ src/demo-model.coffee
@@ -82,6 +82,7 @@
  ###
 createDevice = (name) ->

+  _name = name
   _state = tangojs.tango.DevState.OFF

   get_status: -> 'ON'
@@ -90,7 +91,7 @@
   get_info: -> new tangojs.struct.DeviceInfo
     exported: true
     ior: 'IOR:123456789'
-    name: name
+    name: _name

   properties:
     scalar: new tangojs.struct.DbDatum('scalar', 1)
@@ -176,7 +177,7 @@
     goto_fault: createCommand((-> _state = tangojs.tango.DevState.FAULT),
                               'goto_fault',
                               tangojs.tango.DispLevel.OPERATOR)
-    goto_alarm: createCommand((-> _state = tangojs.tango.DevState.OFF),
+    goto_alarm: createCommand((-> _state = tangojs.tango.DevState.ALARM),
                               'goto_off',
                               tangojs.tango.DispLevel.OPERATOR)


migration script for dochistory packing

migration script for dochistory packing


allow persistent force on drag

allow persistent force on drag

--- app/scripts/alchemy/core/interactions.coffee
+++ app/scripts/alchemy/core/interactions.coffee
@@ -9,6 +9,7 @@
     d.px += d3.event.dx
     d.py += d3.event.dy
     d3.select(this).attr("transform", "translate(#{d.x}, #{d.y})")
+    alchemy.force.start() #restarts force on drag

     alchemy.edge.attr("x1", (d) -> d.source.x )
         .attr("y1", (d) -> d.source.y )


Add external group to worker environment mapping

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- deployment/grouptoenvmapping.coffee
+++ deployment/grouptoenvmapping.coffee
@@ -24,6 +24,7 @@
   'default'     : [ 'default' ]
   'bucket'      : envs
   'static'      : envs
+  'external'    : envs.concat proxies

 module.exports.isAllowed = (group, env) ->
   # if group name is not in groupToEnv


Recompile less files on each addPackageAsset call

Fixes #2589

--- packages/rocketchat-theme/server/server.coffee
+++ packages/rocketchat-theme/server/server.coffee
@@ -61,7 +61,7 @@
 		RocketChat.settings.add 'css', ''
 		RocketChat.settings.addGroup 'Layout'

-		compile = _.debounce Meteor.bindEnvironment(@compile.bind(@)), 200
+		@compileDelayed = _.debounce Meteor.bindEnvironment(@compile.bind(@)), 300

 		RocketChat.settings.onload '*', Meteor.bindEnvironment (key, value, initialLoad) =>
 			if key is 'theme-custom-css'
@@ -74,7 +74,7 @@
 			else
 				return

-			compile()
+			@compileDelayed()

 	compile: ->
 		content = [
@@ -141,6 +141,7 @@

 	addPackageAsset: (cb) ->
 		@packageCallbacks.push cb
+		@compileDelayed()

 	getCss: ->
 		return RocketChat.settings.get 'css'


Remove the most annoying memes

Remove the most annoying memes

--- scripts/memes.coffee
+++ scripts/memes.coffee
@@ -5,23 +5,15 @@
 #   babers way - the most delicious way
 #   hop on - you're gonna get some hop ons
 #   it's happening - ron paul is excited
-#   linux - RMSifies you
-#   hug|touch - no touching
 #   what do we want - for when we want brains
 #   when do we want it - for when we want brains

 module.exports = (robot) ->
-  robot.hear /\b(alarm|fire)\b/i, (msg) ->
-    msg.send 'http://media.giphy.com/media/d8695TvC8G1Co/giphy.gif'
-
   robot.hear /aribot/i, (msg) ->
     msg.send 'http://i.imgur.com/vQja8jB.jpg'

   robot.hear /\bbaber'?s\s?way\b/i, (msg) ->
     msg.send 'http://i.imgur.com/rIC8Vuc.gif'
-
-  robot.hear /\bbucks\b/i, (msg) ->
-    msg.send 'http://stuff.ebnj.net/funny/moneygun.gif'

   robot.hear /da+yum/i, (msg) ->
     msg.send 'http://i.imgur.com/vu0AC4m.gif'
@@ -47,9 +39,6 @@
   robot.hear /lunch is here/i, (msg) ->
     msg.send 'https://i.chzbgr.com/maxW500/7565889280/h1F3B738A.gif'

-  robot.hear /\slinux\b/i, (msg) ->
-    msg.send 'http://i.imgur.com/DXUgvy8.jpg'
-
   robot.hear /magic word/i, (msg) ->
     msg.send 'http://24.media.tumblr.com/8566ef54aefe210f0706b8768a62ff5c/tumblr_mldilmBIxz1r7rimro1_400.gif'


:lipstick: Fix typo in test description

:lipstick: Fix typo in test description

--- specs/units/inflector/inflector.spec.coffee
+++ specs/units/inflector/inflector.spec.coffee
@@ -40,7 +40,7 @@
       expect(inflector.singularize('')).toEqual('')
       expect(inflector.singularize('equipment')).toEqual('equipment')

-    it 'creates an singularInflection that can conjugate to the past', ->
+    it 'creates an inflector that can conjugate to the past', ->
       expect(inflector.toPast('walk')).toEqual('walked')
       expect(inflector.toPast('fight')).toEqual('fought')
       expect(inflector.toPast('buy')).toEqual('bought')


Load problems no longer redirects to mimick the behavior of the load deployment option

--- picoCTF-web/web/coffee/management-shell.coffee
+++ picoCTF-web/web/coffee/management-shell.coffee
@@ -146,7 +146,8 @@
   pushData: ->
     apiCall "POST", "/api/problems/load_problems", {competition_data: @state.publishedJSON}
     .done ((data) ->
-      apiNotify data, "/management"
+      apiNotify data
+      @clearPublishedJSON()
     ).bind this

   clearPublishedJSON: ->


update provider unit tests

update provider unit tests

--- test/Provider.coffee
+++ test/Provider.coffee
@@ -99,7 +99,7 @@
     it 'should return false if bad oauth', (done) =>
       req =
         path: '/test'
-        route: {method: 'POST'}
+        method: 'POST'
         body:
           lti_message_type: 'basic-lti-launch-request'
           lti_version: 'LTI-1p0'
@@ -127,7 +127,7 @@
     it 'should return true if good headers and oauth', (done) =>
       req =
         path: '/test'
-        route: {method: 'POST'}
+        method: 'POST'
         body:
           lti_message_type: 'basic-lti-launch-request'
           lti_version: 'LTI-1p0'
@@ -151,7 +151,7 @@
     it 'should return false if nonce already seen', (done) =>
       req =
         path: '/test'
-        route: {method: 'POST'}
+        method: 'POST'
         body:
           lti_message_type: 'basic-lti-launch-request'
           lti_version: 'LTI-1p0'
@@ -183,7 +183,7 @@

       req =
         path: '/test'
-        route: {method: 'POST'}
+        method: 'POST'
         body:
           context_id: "4"
           context_label: "PHYS 2112"


moved bootstrap only to cmsino_internal

moved bootstrap only to cmsino_internal

--- app/assets/javascripts/cmsino/cmsino_internal.js.coffee
+++ app/assets/javascripts/cmsino/cmsino_internal.js.coffee
@@ -1,5 +1,6 @@
 //= require jquery
 //= require jquery_ujs
+//= require bootstrap-sprockets
 //= require wysihtml5/wysihtml5-0.3.0.min
 //= require wysihtml5/bootstrap-wysihtml5


Add a few more tests

Add a few more tests

- Add tests for the autogenerated POST and PUT collection endpoints in
  response to Issue #26

--- test/api_tests.coffee
+++ test/api_tests.coffee
@@ -13,8 +13,8 @@
           Restivus.addRoute 'test1', {authRequired: true, roleRequired: 'admin'},
             get: ->
               1
-
-          route = Restivus.routes[0]
+          # TODO: Access routes in a less brittle way than this index that can change when new routes are added (more below)
+          route = Restivus.routes[2]
           test.equal route.path, 'test1'
           test.equal route.endpoints.get(), 1
           test.isTrue route.options.authRequired
@@ -32,7 +32,7 @@
                 action: ->
                   2

-          route = Restivus.routes[1]
+          route = Restivus.routes[3]
           test.equal route.path, 'tests'
           test.equal route.endpoints.get.action(), 2
           test.isTrue route.options.authRequired
@@ -56,13 +56,13 @@
           test.throws Restivus.configure, 'Restivus.configure() can only be called once'

         it 'should configure any previously added routes', (test) ->
-          route = Restivus.routes[0]
+          route = Restivus.routes[2]
           test.equal route.endpoints.get.action(), 1
           test.isTrue route.endpoints.get.authRequired
           test.equal route.endpoints.get.roleRequired, ['admin']

         it 'should configure any previously added collection routes', (test) ->
-          route = Restivus.routes[1]
+          route = Restivus.routes[3]
           test.equal route.endpoints.get.action(), 2
           test.isTrue route.endpoints.get.authRequired
           test.equal route.endpoints.get.roleRequired, ['admin']
@@ -90,6 +90,49 @@
           test.equal response.message, 'API endpoint not found'
           next()

+      context 'with the default autogenerated endpoints', ->
+        Restivus.addCollection new Mongo.Collection('testautogen')
+        testId = null
+
+        it 'should support a POST on api/collection', (test) ->
+          result = HTTP.post 'http://localhost:3000/api/v1/testAutogen',
+            data:
+              name: 'test name'
+              description: 'test description'
+          response = JSON.parse result.content
+          responseData = response.data
+          test.equal result.statusCode, 201
+          test.equal response.status, 'success'
+          test.equal responseData.name, 'test name'
+          test.equal responseData.description, 'test description'
+
+          # Persist the new resource id
+          testId = responseData._id
+
+
+        it 'should support a PUT on api/collection/:id', (test) ->
+          result = HTTP.put "http://localhost:3000/api/v1/testAutogen/#{testId}",
+            data:
+              name: 'update name'
+              description: 'update description'
+          response = JSON.parse result.content
+          responseData = response.data
+          test.equal result.statusCode, 200
+          test.equal response.status, 'success'
+          test.equal responseData.name, 'update name'
+          test.equal responseData.description, 'update description'
+
+          result = HTTP.put "http://localhost:3000/api/v1/testAutogen/#{testId}",
+            data:
+              name: 'update name with no description'
+          response = JSON.parse result.content
+          responseData = response.data
+          test.equal result.statusCode, 200
+          test.equal response.status, 'success'
+          test.equal responseData.name, 'update name with no description'
+          test.isUndefined responseData.description
+
+
     describe 'An endpoint', ->

       it 'should cause an error when it returns null', (test, next) ->
@@ -145,7 +188,7 @@
           get: ->
             @response.write 'Testing '
             @response.write 'chunked response.'
-#            @done()
+            @done()

         HTTP.get 'http://localhost:3000/api/v1/testChunkedResponse', (error, result) ->
           response = result.content
@@ -157,7 +200,7 @@
       it 'should respond with an error if this.done() isn\'t called after response is handled manually', (test, next) ->
         Restivus.addRoute 'testManualResponseWithoutDone',
           get: ->
-            undefined
+            @response.write 'Testing'

         HTTP.get 'http://localhost:3000/api/v1/testManualResponseWithoutDone', (error, result) ->
           test.isTrue error


Finished coffeescript conversion

Finished coffeescript conversion

--- app/assets/javascripts/store/spree_videos.js.erb.coffee
+++ app/assets/javascripts/store/spree_videos.js.erb.coffee
@@ -11,34 +11,35 @@
 		@player_holder = $(holder)

 		# check if player is given by default, pull options off of it
-		if !!(iframe = $('iframe', this.playerHolder).get(0))
+		if !!(iframe = $('iframe', @player_holder).get(0))
 			options.width = $(iframe).width()

 		@options = $.extend(@options, options)

-	switchVideo: (youtubeLink) ->
+	switchVideo: (youtube_link) ->
 		size = @calculateDimensions(true)
-		matches = @options.youtube_match.exec(youtubeLink)
-		youtubeID = if matches then matches[matches.length - 1] else youtubeLink
+		matches = @options.youtube_match.exec(youtube_link)
+		youtubeID = if matches then matches[matches.length - 1] else youtube_link
 		youtubeURL = "http://www.youtube.com/embed/" + youtubeID + "?origin=" + @options.originDomain + "&" + @options.youtube_options;

 		@player_holder.html('<iframe type="text/html" width="' + size.width + '" height="' + size.height + '" src="' + youtubeURL + '" frameborder="0"></iframe>');

-	calculateDimensions: (isYouTube) ->
+	calculateDimensions: (isYouTube = true) ->
 		# automatically size player based on HD 16:9 format
 		# can define either width / height and this will calculate the rest

-		w = @options.width == undefined ? 0 : @options.width
-		h = @options.height == undefined ? 0 : @options.width
-
+		w = if ('width' of @options) then @options.width else 0
+		h = if ('height' of @options) then @options.width else 0
+
 		# we subtract 30 from the height calculation because of the youtube menu
-		w = (h - (isYouTube ? 30 : 0)) * 16/9 if !w
-		h = Math.round(w * (9/16) + (isYouTube ? 30 : 0)) if !h
+		yt = if isYouTube is true then 30 else 0
+		w = (h - yt) * 16/9 if !w
+		h = Math.round(w * (9/16) + yt) if !h

 		{ width:w, height:h }

 $ ->
-	vm = new VideoManager("#video-player");
+	vm = new VideoManager("#video-player")

 	$('#video-thumbnails a').click (evn) ->
 		vm.switchVideo($(this).data('video-id'))


modify

modify

--- test/test-npm-module.spec.coffee
+++ test/test-npm-module.spec.coffee
@@ -10,5 +10,5 @@

 describe 'test-npm-module', ->
   it 'works', ->
-    actual = testNpmModule 'World5'
+    actual = testNpmModule 'World6'
     expect(actual).to.eql 'Hello World6'


Add ♻ as OK symbol

Add ♻ as OK symbol

--- scripts/shoot.coffee
+++ scripts/shoot.coffee
@@ -32,7 +32,8 @@
            text.indexOf("\ud83d\udd13 ") is 0, \ # OPEN LOCK
            text.indexOf("\ud83d\udd0a ") is 0, \ # SPEAKER WITH THREE SOUND WAVES
            text.indexOf("\ud83d\udce2 ") is 0, \ # PUBLIC ADDRESS LOUDSPEAKER
-           text.indexOf("\ud83d\udce3 ") is 0    # CHEERING MEGAPHONE
+           text.indexOf("\ud83d\udce3 ") is 0, \ # CHEERING MEGAPHONE
+           text.indexOf("\u267b\ufe0f ") is 0    # RECYCLING SYMBOL
         text = text.substr 3
         rate = 1


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -48,6 +48,6 @@
     ok(true)

 test "Collision.rayRectangle", ->
-  re
+  rect = Game
 module()


testing for object/builtin interactions

testing for object/builtin interactions

--- test/contracts.coffee
+++ test/contracts.coffee
@@ -399,3 +399,17 @@

 #     eq (idmod.id "foo"), "foo"
 #     throws (-> idmod.id 42)
+
+test "object contracts and builtins", ->
+  f :: ({foo: Str}) -> Str
+  f = (o) ->
+    o.foo
+
+  eq (f {foo: "bar"}), "bar", "correct object"
+  throws (-> f "string"), "string instead of an object, but should complain about missing property"
+
+test "object contracts on object-like builtins (currently failing)", ->
+  g :: ({toString: (Any) -> Str}) -> Str
+  g = (s) -> s.toString()
+
+  g "foo"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

-  equals obj.I.alpha
+  equals obj.I.alpha, 0.8

 module()


almost ready to go here

almost ready to go here

--- test/lib/config.iced
+++ test/lib/config.iced
@@ -7,7 +7,7 @@
 #====================================================================

 log = require '../../lib/log'
-{home} = require '../../lib/path'
+kbpath = require 'keybase-path'
 {make_esc} = require 'iced-error'
 {a_json_parse} = require('iced-utils').util
 path = require 'path'
@@ -97,7 +97,7 @@
   #----------------

   open_config : (cb) ->
-    file = if @file then @file else path.join(home(),@DEFAULT_FILE)
+    file = if @file then @file else path.join(kbpath.home(),@DEFAULT_FILE)
     await fs.readFile file, defer err, data
     if not err?
       await a_json_parse data, defer err, @_data


Age in seconds

Age in seconds

--- src/game_state.coffee
+++ src/game_state.coffee
@@ -51,12 +51,12 @@
   self.bind "update", (elapsedTime) ->
     I.updating = true

-    I.objects.invoke "trigger", "beforeUpdate"
+    I.objects.invoke "trigger", "beforeUpdate", elapsedTime

     [toKeep, toRemove] = I.objects.partition (object) ->
       object.update(elapsedTime)

-    I.objects.invoke "trigger", "afterUpdate"
+    I.objects.invoke "trigger", "afterUpdate",

     toRemove.invoke "trigger", "remove"


Refactor web-server unit tests a bit

Refactor web-server unit tests a bit

--- test/unit/web-server.spec.coffee
+++ test/unit/web-server.spec.coffee
@@ -25,8 +25,9 @@
     getIncludedFiles: -> includedFiles

   # async helper
-  waitForFinishingResponse = ->
+  waitForFinishingResponseAnd = (next) ->
     waitsFor (-> response._isFinished()), 'Finish response', 100
+    runs next

   mocks.fs = fsMock.create
     tcular:
@@ -76,10 +77,7 @@
     it 'should first look for testacular files', ->
       handler new httpMock.ServerRequest('/_testacular_/'), response

-      # TODO(vojta): refactor to waitForFinishingResponseAnd ->
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toEqual 'CLIENT HTML'
         expect(response._status).toBe 200
         expect(actualOptions).toEqual {}
@@ -89,43 +87,33 @@
       handler = m.createHandler fileList, staticFolderPath, adapterFolderPath, baseFolder, mockProxy,
           {'/_testacular_/': 'http://localhost:9000'}, '/_testacular_/'
       handler new httpMock.ServerRequest('/base/other.js'), response
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._status).toBe 404
         expect(response._body).toBe 'NOT FOUND'

     it 'should serve static files after proxy', ->
       handler new httpMock.ServerRequest('/base/a.js'), response
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toBe 'js-src-a'
         expect(response._status).toBe 200

     it 'should delegate to proxy after checking for testacular files', ->
       handler new httpMock.ServerRequest('/_testacular_/not_client.html'), response
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(actualOptions).toEqual {host: 'localhost', port: '9000'}

     it 'should delegate to proxy after checking for source files', ->
       handler new httpMock.ServerRequest('/base/not_client.html'), response
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(actualOptions).toEqual {host: 'localhost', port: '1000'}

     it 'should give 404 for missing files', ->
       handler new httpMock.ServerRequest('/file/non-existent.html'), response
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._status).toBe 404
         expect(response._body).toBe 'NOT FOUND'

@@ -143,9 +131,7 @@
     it 'should serve client.html', ->
       retVal = tcularSrcHandler new httpMock.ServerRequest('/_testacular_/'), response
       expect(retVal).toBeTruthy()
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toEqual 'CLIENT HTML'
         expect(response._status).toBe 200

@@ -153,9 +139,7 @@
     it 'should allow /?id=xxx', ->
       retVal = tcularSrcHandler new httpMock.ServerRequest('/_testacular_/?id=123'), response
       expect(retVal).toBeTruthy()
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toEqual 'CLIENT HTML'
         expect(response._status).toBe 200

@@ -166,9 +150,7 @@

       retVal = tcularSrcHandler new httpMock.ServerRequest('/_testacular_/context.html'), response
       expect(retVal).toBeTruthy()
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toEqual 'CONTEXT\n' +
         '<script type="text/javascript" src="/absolute/first.js?12345"></script>\n' +
         '<script type="text/javascript" src="/absolute/second.js?67890"></script>'
@@ -181,9 +163,7 @@

       retVal = tcularSrcHandler new httpMock.ServerRequest('/_testacular_/debug.html'), response
       expect(retVal).toBeTruthy()
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toEqual 'RUNNER\n' +
         '<script type="text/javascript" src="/absolute/first.js"></script>\n' +
         '<script type="text/javascript" src="/absolute/second.js"></script>'
@@ -197,9 +177,7 @@

       retVal = tcularSrcHandler new httpMock.ServerRequest('/_testacular_/context.html'), response
       expect(retVal).toBeTruthy()
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toEqual 'CONTEXT\n' +
         '<script type="text/javascript" src="/absolute/some/abs/a.js?12345"></script>\n' +
         '<script type="text/javascript" src="/base/b.js?67890"></script>\n' +
@@ -212,9 +190,7 @@

       retVal = tcularSrcHandler new httpMock.ServerRequest('/_testacular_/context.html'), response
       expect(retVal).toBeTruthy()
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toEqual 'CONTEXT\n' +
         '<script type="text/javascript" src="http://some.url.com/whatever"></script>'
         expect(response._status).toBe 200
@@ -223,9 +199,7 @@
     it 'should send non-caching headers for context.html', ->
       retVal = tcularSrcHandler new httpMock.ServerRequest('/_testacular_/context.html'), response
       expect(retVal).toBeTruthy()
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._headers['Cache-Control']).toBe 'no-cache'
         # idiotic IE8 needs more
         expect(response._headers['Pragma']).toBe 'no-cache'
@@ -241,9 +215,7 @@
         {path: '/tcular/adapter/c.js', mtime: new Date 321}]

       tcularSrcHandler new httpMock.ServerRequest('/_testacular_/context.html'), response
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toEqual 'window.__testacular__.files = {\n' +
         "  '/absolute/some/abs/a.js': '12345',\n" +
         "  '/base/b.js': '67890',\n" +
@@ -256,9 +228,7 @@
     it 'should redirect urlRoot without trailing slash', ->
       retVal = tcularSrcHandler new httpMock.ServerRequest('/_testacular_'), response
       expect(retVal).toBe true
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._status).toBe 301
         expect(response._headers['Location']).toBe '/_testacular_/'

@@ -275,9 +245,7 @@
       servedFiles = [{path: '/src/some.js', contentPath: '/src/some.js', mtime: new Date 12345}]

       srcFileHandler new httpMock.ServerRequest('/absolute/src/some.js?123345'), response
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toBe 'js-source'
         expect(response._status).toBe 200

@@ -287,9 +255,7 @@

       srcFileHandler new httpMock.ServerRequest('/base/a.js?123345'), response

-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toBe 'js-src-a'
         expect(response._status).toBe 200

@@ -298,9 +264,7 @@
       servedFiles = [{path: '/tcular/adapter/jasmine.js', contentPath: '/tcular/adapter/jasmine.js', mtime: new Date 12345}]

       srcFileHandler new httpMock.ServerRequest('/adapter/jasmine.js?123345'), response
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._body).toBe 'js-src-jasmine'
         expect(response._status).toBe 200

@@ -309,9 +273,7 @@
       servedFiles = [{path: '/src/some.js', contentPath: '/src/some.js', mtime: new Date 12345}]

       srcFileHandler new httpMock.ServerRequest('/absolute/src/some.js?12323'), response
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._headers['Cache-Control']).toEqual ['public', 'max-age=31536000']

@@ -319,9 +281,7 @@
       servedFiles = [{path: '/src/some.js', contentPath: '/src/some.js', mtime: new Date 12345}]

       srcFileHandler new httpMock.ServerRequest('/absolute/src/some.js'), response
-      waitForFinishingResponse()
-
-      runs ->
+      waitForFinishingResponseAnd ->
         expect(response._headers['Cache-Control']).toBe 'no-cache'
         # idiotic IE8 needs more
         expect(response._headers['Pragma']).toBe 'no-cache'
@@ -337,6 +297,4 @@
       servedFiles = [{path: '/first.js', mtime: new Date 12345},
         {path: '/second.js', mtime: new Date 67890}]

-      runs ->
-        expect(srcFileHandler new httpMock.ServerRequest('/base/other.js'), response).toBeFalsy()
-
+      expect(srcFileHandler new httpMock.ServerRequest('/base/other.js'), response).toBeFalsy()


Removed then.

Removed then.

--- src/core/javascripts/controllers/map.js.coffee
+++ src/core/javascripts/controllers/map.js.coffee
@@ -314,7 +314,7 @@
   geolocateFail = (error) ->
     switch error.code
       # if the geocode failed because the position was unavailable or the request timed out, raise an alert
-      when 2, 3 then
+      when 2, 3
         $scope.setLoaded $scope
         AlertService.danger(ErrorService.getError('GEOLOCATION_ERROR'))
       else


Ensure simple for loops don't have a block in node.test.right socket

Without this change the following JS throws a parser error when switching from text to block mode:
```
var items = [4, 5, 6];
for (var i = 0; i < items.length; i++) {
  //
}
```

--- src/languages/javascript.coffee
+++ src/languages/javascript.coffee
@@ -736,6 +736,7 @@
       node.test.operator is '<' and
       node.test.left.type is 'Identifier' and
       node.test.left.name is variableName and
+      node.test.right.type in ['Literal', 'Identifier'] and
       node.update.type is 'UpdateExpression' and
       node.update.operator is '++' and
       node.update.argument.type is 'Identifier' and


rm tabs

rm tabs

--- test/slug.test.coffee
+++ test/slug.test.coffee
@@ -1,133 +1,133 @@
 slug = require '../src/slug'

 describe 'slug', ->
-	it 'should replace whitespaces with replacement', ->
-		slug('foo bar baz').should.eql 'foo-bar-baz'
-		slug('foo bar baz', '_').should.eql 'foo_bar_baz'
+    it 'should replace whitespaces with replacement', ->
+        slug('foo bar baz').should.eql 'foo-bar-baz'
+        slug('foo bar baz', '_').should.eql 'foo_bar_baz'

-	it 'should remove trailing space if any', ->
-		slug(' foo bar baz ').should.eql 'foo-bar-baz'
+    it 'should remove trailing space if any', ->
+        slug(' foo bar baz ').should.eql 'foo-bar-baz'

-	it 'should remove not allowed chars', ->
-		slug('foo, bar baz').should.eql 'foo-bar-baz'
-		slug('foo- bar baz').should.eql 'foo-bar-baz'
-		slug('foo] bar baz').should.eql 'foo-bar-baz'
+    it 'should remove not allowed chars', ->
+        slug('foo, bar baz').should.eql 'foo-bar-baz'
+        slug('foo- bar baz').should.eql 'foo-bar-baz'
+        slug('foo] bar baz').should.eql 'foo-bar-baz'

-	it 'should leave allowed chars', ->
-		allowed = ['*', '+', '~', '.', '(', ')', '\'', '"', '!', ':', '@']
-		for a in allowed
-			slug("foo #{a} bar baz").should.eql "foo-#{a}-bar-baz"
+    it 'should leave allowed chars', ->
+        allowed = ['*', '+', '~', '.', '(', ')', '\'', '"', '!', ':', '@']
+        for a in allowed
+            slug("foo #{a} bar baz").should.eql "foo-#{a}-bar-baz"

-	it 'should replace latin chars', ->
-		char_map = {
-		    'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE',
-		    'Ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I',
-		    'Î': 'I', 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O',
-		    'Õ': 'O', 'Ö': 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U',
-		    'Ü': 'U', 'Ű': 'U', 'Ý': 'Y', 'Þ': 'TH', 'ß': 'ss', 'à':'a', 'á':'a',
-		    'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e',
-		    'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i',
-		    'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o',
-		    'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u', 'ű': 'u',
-		    'ý': 'y', 'þ': 'th', 'ÿ': 'y', 'ẞ': 'SS'
-		}
-		for char, replacement of char_map
-			slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"
+    it 'should replace latin chars', ->
+        char_map = {
+            'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE',
+            'Ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I',
+            'Î': 'I', 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O',
+            'Õ': 'O', 'Ö': 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U',
+            'Ü': 'U', 'Ű': 'U', 'Ý': 'Y', 'Þ': 'TH', 'ß': 'ss', 'à':'a', 'á':'a',
+            'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e',
+            'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i',
+            'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o',
+            'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u', 'ű': 'u',
+            'ý': 'y', 'þ': 'th', 'ÿ': 'y', 'ẞ': 'SS'
+        }
+        for char, replacement of char_map
+            slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"

-	it 'should replace greek chars', ->
-		char_map = {
-		    'α':'a', 'β':'b', 'γ':'g', 'δ':'d', 'ε':'e', 'ζ':'z', 'η':'h', 'θ':'8',
-		    'ι':'i', 'κ':'k', 'λ':'l', 'μ':'m', 'ν':'n', 'ξ':'3', 'ο':'o', 'π':'p',
-		    'ρ':'r', 'σ':'s', 'τ':'t', 'υ':'y', 'φ':'f', 'χ':'x', 'ψ':'ps', 'ω':'w',
-		    'ά':'a', 'έ':'e', 'ί':'i', 'ό':'o', 'ύ':'y', 'ή':'h', 'ώ':'w', 'ς':'s',
-		    'ϊ':'i', 'ΰ':'y', 'ϋ':'y', 'ΐ':'i',
-		    'Α':'A', 'Β':'B', 'Γ':'G', 'Δ':'D', 'Ε':'E', 'Ζ':'Z', 'Η':'H', 'Θ':'8',
-		    'Ι':'I', 'Κ':'K', 'Λ':'L', 'Μ':'M', 'Ν':'N', 'Ξ':'3', 'Ο':'O', 'Π':'P',
-		    'Ρ':'R', 'Σ':'S', 'Τ':'T', 'Υ':'Y', 'Φ':'F', 'Χ':'X', 'Ψ':'PS', 'Ω':'W',
-		    'Ά':'A', 'Έ':'E', 'Ί':'I', 'Ό':'O', 'Ύ':'Y', 'Ή':'H', 'Ώ':'W', 'Ϊ':'I',
-		    'Ϋ':'Y'
-		}
-		for char, replacement of char_map
-			slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"
+    it 'should replace greek chars', ->
+        char_map = {
+            'α':'a', 'β':'b', 'γ':'g', 'δ':'d', 'ε':'e', 'ζ':'z', 'η':'h', 'θ':'8',
+            'ι':'i', 'κ':'k', 'λ':'l', 'μ':'m', 'ν':'n', 'ξ':'3', 'ο':'o', 'π':'p',
+            'ρ':'r', 'σ':'s', 'τ':'t', 'υ':'y', 'φ':'f', 'χ':'x', 'ψ':'ps', 'ω':'w',
+            'ά':'a', 'έ':'e', 'ί':'i', 'ό':'o', 'ύ':'y', 'ή':'h', 'ώ':'w', 'ς':'s',
+            'ϊ':'i', 'ΰ':'y', 'ϋ':'y', 'ΐ':'i',
+            'Α':'A', 'Β':'B', 'Γ':'G', 'Δ':'D', 'Ε':'E', 'Ζ':'Z', 'Η':'H', 'Θ':'8',
+            'Ι':'I', 'Κ':'K', 'Λ':'L', 'Μ':'M', 'Ν':'N', 'Ξ':'3', 'Ο':'O', 'Π':'P',
+            'Ρ':'R', 'Σ':'S', 'Τ':'T', 'Υ':'Y', 'Φ':'F', 'Χ':'X', 'Ψ':'PS', 'Ω':'W',
+            'Ά':'A', 'Έ':'E', 'Ί':'I', 'Ό':'O', 'Ύ':'Y', 'Ή':'H', 'Ώ':'W', 'Ϊ':'I',
+            'Ϋ':'Y'
+        }
+        for char, replacement of char_map
+            slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"

-	it 'should replace turkish chars', ->
-		char_map = {
-		    'ş':'s', 'Ş':'S', 'ı':'i', 'İ':'I', 'ç':'c', 'Ç':'C', 'ü':'u', 'Ü':'U',
-		    'ö':'o', 'Ö':'O', 'ğ':'g', 'Ğ':'G'
-		}
-		for char, replacement of char_map
-			slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"
+    it 'should replace turkish chars', ->
+        char_map = {
+            'ş':'s', 'Ş':'S', 'ı':'i', 'İ':'I', 'ç':'c', 'Ç':'C', 'ü':'u', 'Ü':'U',
+            'ö':'o', 'Ö':'O', 'ğ':'g', 'Ğ':'G'
+        }
+        for char, replacement of char_map
+            slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"

-	it 'should replace cyrillic chars', ->
-		char_map = {
-		    'а':'a', 'б':'b', 'в':'v', 'г':'g', 'д':'d', 'е':'e', 'ё':'yo', 'ж':'zh',
-		    'з':'z', 'и':'i', 'й':'j', 'к':'k', 'л':'l', 'м':'m', 'н':'n', 'о':'o',
-		    'п':'p', 'р':'r', 'с':'s', 'т':'t', 'у':'u', 'ф':'f', 'х':'h', 'ц':'c',
-		    'ч':'ch', 'ш':'sh', 'щ':'sh', 'ъ':'u', 'ы':'y', 'ь':'', 'э':'e', 'ю':'yu',
-		    'я':'ya',
-		    'А':'A', 'Б':'B', 'В':'V', 'Г':'G', 'Д':'D', 'Е':'E', 'Ё':'Yo', 'Ж':'Zh',
-		    'З':'Z', 'И':'I', 'Й':'J', 'К':'K', 'Л':'L', 'М':'M', 'Н':'N', 'О':'O',
-		    'П':'P', 'Р':'R', 'С':'S', 'Т':'T', 'У':'U', 'Ф':'F', 'Х':'H', 'Ц':'C',
-		    'Ч':'Ch', 'Ш':'Sh', 'Щ':'Sh', 'Ъ':'U', 'Ы':'Y', 'Ь':'', 'Э':'E', 'Ю':'Yu',
-		    'Я':'Ya', 'Є':'Ye', 'І':'I', 'Ї':'Yi', 'Ґ':'G', 'є':'ye', 'і':'i', 'ї':'yi', 'ґ':'g'
-		}
-		for char, replacement of char_map
-			expected = "foo-#{replacement}-bar-baz"
-			expected = "foo-bar-baz" if not replacement
-			slug("foo #{char} bar baz").should.eql expected
+    it 'should replace cyrillic chars', ->
+        char_map = {
+            'а':'a', 'б':'b', 'в':'v', 'г':'g', 'д':'d', 'е':'e', 'ё':'yo', 'ж':'zh',
+            'з':'z', 'и':'i', 'й':'j', 'к':'k', 'л':'l', 'м':'m', 'н':'n', 'о':'o',
+            'п':'p', 'р':'r', 'с':'s', 'т':'t', 'у':'u', 'ф':'f', 'х':'h', 'ц':'c',
+            'ч':'ch', 'ш':'sh', 'щ':'sh', 'ъ':'u', 'ы':'y', 'ь':'', 'э':'e', 'ю':'yu',
+            'я':'ya',
+            'А':'A', 'Б':'B', 'В':'V', 'Г':'G', 'Д':'D', 'Е':'E', 'Ё':'Yo', 'Ж':'Zh',
+            'З':'Z', 'И':'I', 'Й':'J', 'К':'K', 'Л':'L', 'М':'M', 'Н':'N', 'О':'O',
+            'П':'P', 'Р':'R', 'С':'S', 'Т':'T', 'У':'U', 'Ф':'F', 'Х':'H', 'Ц':'C',
+            'Ч':'Ch', 'Ш':'Sh', 'Щ':'Sh', 'Ъ':'U', 'Ы':'Y', 'Ь':'', 'Э':'E', 'Ю':'Yu',
+            'Я':'Ya', 'Є':'Ye', 'І':'I', 'Ї':'Yi', 'Ґ':'G', 'є':'ye', 'і':'i', 'ї':'yi', 'ґ':'g'
+        }
+        for char, replacement of char_map
+            expected = "foo-#{replacement}-bar-baz"
+            expected = "foo-bar-baz" if not replacement
+            slug("foo #{char} bar baz").should.eql expected

-	it 'should replace czech chars', ->
-		char_map = {
-		    'č':'c', 'ď':'d', 'ě':'e', 'ň': 'n', 'ř':'r', 'š':'s', 'ť':'t', 'ů':'u',
-		    'ž':'z', 'Č':'C', 'Ď':'D', 'Ě':'E', 'Ň': 'N', 'Ř':'R', 'Š':'S', 'Ť':'T',
-		    'Ů':'U', 'Ž':'Z'
-		}
-		for char, replacement of char_map
-			slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"
+    it 'should replace czech chars', ->
+        char_map = {
+            'č':'c', 'ď':'d', 'ě':'e', 'ň': 'n', 'ř':'r', 'š':'s', 'ť':'t', 'ů':'u',
+            'ž':'z', 'Č':'C', 'Ď':'D', 'Ě':'E', 'Ň': 'N', 'Ř':'R', 'Š':'S', 'Ť':'T',
+            'Ů':'U', 'Ž':'Z'
+        }
+        for char, replacement of char_map
+            slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"

-	it 'should replace polish chars', ->
-		char_map = {
-		    'ą':'a', 'ć':'c', 'ę':'e', 'ł':'l', 'ń':'n', 'ó':'o', 'ś':'s', 'ź':'z',
-		    'ż':'z', 'Ą':'A', 'Ć':'C', 'Ę':'e', 'Ł':'L', 'Ń':'N', 'Ś':'S',
-		    'Ź':'Z', 'Ż':'Z'
-		}
-		for char, replacement of char_map
-			slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"
+    it 'should replace polish chars', ->
+        char_map = {
+            'ą':'a', 'ć':'c', 'ę':'e', 'ł':'l', 'ń':'n', 'ó':'o', 'ś':'s', 'ź':'z',
+            'ż':'z', 'Ą':'A', 'Ć':'C', 'Ę':'e', 'Ł':'L', 'Ń':'N', 'Ś':'S',
+            'Ź':'Z', 'Ż':'Z'
+        }
+        for char, replacement of char_map
+            slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"

-	it 'should replace latvian chars', ->
-		char_map = {
-		    'ā':'a', 'č':'c', 'ē':'e', 'ģ':'g', 'ī':'i', 'ķ':'k', 'ļ':'l', 'ņ':'n',
-		    'š':'s', 'ū':'u', 'ž':'z', 'Ā':'A', 'Č':'C', 'Ē':'E', 'Ģ':'G', 'Ī':'i',
-		    'Ķ':'k', 'Ļ':'L', 'Ņ':'N', 'Š':'S', 'Ū':'u', 'Ž':'Z'
-		}
-		for char, replacement of char_map
-			slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"
+    it 'should replace latvian chars', ->
+        char_map = {
+            'ā':'a', 'č':'c', 'ē':'e', 'ģ':'g', 'ī':'i', 'ķ':'k', 'ļ':'l', 'ņ':'n',
+            'š':'s', 'ū':'u', 'ž':'z', 'Ā':'A', 'Č':'C', 'Ē':'E', 'Ģ':'G', 'Ī':'i',
+            'Ķ':'k', 'Ļ':'L', 'Ņ':'N', 'Š':'S', 'Ū':'u', 'Ž':'Z'
+        }
+        for char, replacement of char_map
+            slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"

-	it 'should replace currencies', ->
-		char_map = {
-		    '€': 'euro', '₢': 'cruzeiro', '₣': 'french franc', '£': 'pound',
-		    '₤': 'lira', '₥': 'mill', '₦': 'naira', '₧': 'peseta', '₨': 'rupee',
-		    '₩': 'won', '₪': 'new shequel', '₫': 'dong', '₭': 'kip', '₮': 'tugrik',
-		    '₯': 'drachma', '₰': 'penny', '₱': 'peso', '₲': 'guarani', '₳': 'austral',
-		    '₴': 'hryvnia', '₵': 'cedi', '¢': 'cent', '¥': 'yen', '元': 'yuan',
-		    '円': 'yen', '﷼': 'rial', '₠': 'ecu', '¤': 'currency', '฿': 'baht',
-		    "$": 'dollar'
-		}
-		for char, replacement of char_map
-			replacement = replacement.replace ' ', '-'
-			slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"
+    it 'should replace currencies', ->
+        char_map = {
+            '€': 'euro', '₢': 'cruzeiro', '₣': 'french franc', '£': 'pound',
+            '₤': 'lira', '₥': 'mill', '₦': 'naira', '₧': 'peseta', '₨': 'rupee',
+            '₩': 'won', '₪': 'new shequel', '₫': 'dong', '₭': 'kip', '₮': 'tugrik',
+            '₯': 'drachma', '₰': 'penny', '₱': 'peso', '₲': 'guarani', '₳': 'austral',
+            '₴': 'hryvnia', '₵': 'cedi', '¢': 'cent', '¥': 'yen', '元': 'yuan',
+            '円': 'yen', '﷼': 'rial', '₠': 'ecu', '¤': 'currency', '฿': 'baht',
+            "$": 'dollar'
+        }
+        for char, replacement of char_map
+            replacement = replacement.replace ' ', '-'
+            slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"

-	it 'should replace symbols', ->
-		char_map = {
-		    '©':'(c)', 'œ': 'oe', 'Œ': 'OE', '∑': 'sum', '®': '(r)', '†': '+',
-		    '“': '"', '”': '"', '‘': "'", '’': "'", '∂': 'd', 'ƒ': 'f', '™': 'tm',
-		    '℠': 'sm', '…': '...', '˚': 'o', 'º': 'o', 'ª': 'a', '•': '*',
-		    '∆': 'delta', '∞': 'infinity', '♥': 'love', '&': 'and', '|': 'or',
-		    '<': 'less', '>': 'greater'
-		}
-		for char, replacement of char_map
-			slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"
+    it 'should replace symbols', ->
+        char_map = {
+            '©':'(c)', 'œ': 'oe', 'Œ': 'OE', '∑': 'sum', '®': '(r)', '†': '+',
+            '“': '"', '”': '"', '‘': "'", '’': "'", '∂': 'd', 'ƒ': 'f', '™': 'tm',
+            '℠': 'sm', '…': '...', '˚': 'o', 'º': 'o', 'ª': 'a', '•': '*',
+            '∆': 'delta', '∞': 'infinity', '♥': 'love', '&': 'and', '|': 'or',
+            '<': 'less', '>': 'greater'
+        }
+        for char, replacement of char_map
+            slug("foo #{char} bar baz").should.eql "foo-#{replacement}-bar-baz"

-	it 'should allow forcing lowercase slugs', ->
-		slug('FOO Bar baZ', true).should.eql 'foo-bar-baz'
-		slug('FOO Bar baZ', '_', true).should.eql 'foo_bar_baz'
+    it 'should allow forcing lowercase slugs', ->
+        slug('FOO Bar baZ', true).should.eql 'foo-bar-baz'
+        slug('FOO Bar baZ', replacement:'_', lowerCase:true).should.eql 'foo_bar_baz'


model_postgres: setSubscription(): coearce isSet to boolean

--- src/local/model_postgres.coffee
+++ src/local/model_postgres.coffee
@@ -186,7 +186,7 @@
         , (cb2) ->
             db.query "SELECT subscription FROM subscriptions WHERE node=$1 AND \"user\"=$2", [ node, user ], cb2
         , (res, cb2) ->
-            isSet = res?.rows?[0]
+            isSet = res?.rows?[0]?
             console.log "setSubscription #{node} #{user} isSet=#{isSet} toDelete=#{toDelete}"
             if isSet and not toDelete
                 if listener


Cleanup: remove some dead code

Cleanup: remove some dead code

--- src/local/model_postgres.coffee
+++ src/local/model_postgres.coffee
@@ -98,25 +98,6 @@
     withNextDb (db) ->
         new Transaction(db, cb)

-# TODO: currently unused, should re-check to delete local node after an unsubscribe
-exports.isListeningToNode = (node, listenerJids, cb) ->
-    i = 1
-    conditions = listenerJids.map((listenerJid) ->
-        i++
-        "listener = $#{i}"
-    ).join(" OR ")
-    unless conditions
-        # Short-cut
-        return cb null, false
-
-    withNextDb (db) ->
-        db.query "SELECT listener FROM subscriptions WHERE node = $1 AND (#{conditions}) LIMIT 1"
-        , [node, listenerJids...]
-        , (err, res) ->
-            process.nextTick ->
-                dbIsAvailable(db)
-            cb err, (res?.rows?[0]?)
-
 exports.nodeExists = (node, cb) ->
     withNextDb (db) ->
         db.query "SELECT node FROM nodes WHERE node=$1", [ node ], (err, res) ->
@@ -153,11 +134,6 @@
             nodes = res?.rows?.map (row) ->
                 row.node
             cb null, nodes
-
-exports.getListenerNodes = (listener, cb) ->
-    db.query "SELECT DISTINCT node FROM subscriptions WHERE listener=$1", [listener], (err, res) ->
-        cb err, res?.rows?.map (row) -> row.node
-

 LOST_TRANSACTION_TIMEOUT = 60 * 1000

@@ -517,16 +493,6 @@
             )
         ], cb

-    getOwners: (node, cb) ->
-        db = @db
-        async.waterfall [(cb2) ->
-            db.query "SELECT \"user\" FROM affiliations WHERE node=$1 AND affiliation='owner' ORDER BY updated DESC", [ node ], cb2
-        , (res, cb2) ->
-            cb2 null, res.rows.map((row) ->
-                row.user
-            )
-        ], cb
-
     getOwnersByNodePrefix: (nodePrefix, cb) ->
         db = @db
         async.waterfall [(cb2) ->
@@ -596,33 +562,6 @@
             else
                 cb2 new errors.NotFound("No such item")
         ], cb
-
-    ##
-    # @param itemCb {Function} itemCb({ node: String, id: String, item: Element })
-    getUpdatesByTime: (subscriber, timeStart, timeEnd, itemCb, cb) ->
-        conditions = [ "node IN (SELECT node FROM subscriptions WHERE \"user\"=$1 AND subscription='subscribed')" ]
-        params = [ subscriber ]
-        i = 1
-        if timeStart
-            conditions.push "updated >= $" + (++i) + "::timestamp"
-            params.push timeStart
-        if timeEnd
-            conditions.push "updated <= $" + (++i) + "::timestamp"
-            params.push timeEnd
-        q = @db.query("SELECT id, node, xml FROM items WHERE " + conditions.join(" AND ") + " ORDER BY updated ASC", params)
-        q.on "row", (row) ->
-            if item
-                itemCb
-                    node: row.node
-                    id: row.id
-                    item: row.xml
-
-
-        q.on "error", (err_) ->
-            err = err_
-
-        q.on "end", ->
-            cb err

     getRecentPosts: (subscriber, timeStart, maxItems, cb) ->
         db = @db


adding tests module for toaster

adding tests module for toaster

--- toaster.coffee
+++ toaster.coffee
@@ -1,4 +1,12 @@
-modules =
-	name: 'Coffee Toaster'
-	src: 'src'
-	release: 'lib/toaster.js'
+modules = [
+	{
+		name: 'Coffee Toaster'
+		src: 'src'
+		release: 'lib/toaster.js'
+	},
+	{
+		name: 'Coffee Toaster - Tests'
+		src: 'tests/src'
+		release: 'tests/all.js'
+	}
+]


fix tests for class name change

fix tests for class name change

--- tests/admin.go.coffee
+++ tests/admin.go.coffee
@@ -186,8 +186,8 @@
 casper.then ->
   @click "#createEntity"

-casper.waitForSelector "form.form-horizontal", ->
-  @fill "form.form-horizontal",
+casper.waitForSelector "form.form", ->
+  @fill "form.form",
     displaypage: true
     title: "title999"
     url: "url999"
@@ -200,16 +200,16 @@
   @test.assertEqual tempArr.length isnt 0, true, "check table list"
   @mouseEvent "click", "tr td:nth-child(3) a:first-child"

-casper.waitForSelector "form.form-horizontal", ->
-  @fill "form.form-horizontal",
+casper.waitForSelector "form.form", ->
+  @fill "form.form",
     displaypage: false
   , true

 casper.waitForSelector "tr td:first-child", ->
   @mouseEvent "click", "tr td:nth-child(3) a:first-child"

-casper.waitForSelector "form.form-horizontal", ->
-  @test.assertEqual false, @getFormValues("form.form-horizontal").displaypage, "check data update"
+casper.waitForSelector "form.form", ->
+  @test.assertEqual false, @getFormValues("form.form").displaypage, "check data update"

 i = 0
 casper.repeat 30, ->
@@ -372,8 +372,8 @@
 casper.then ->
   @click "#createEntity"

-casper.waitForSelector "form.form-horizontal", ->
-  @fill "form.form-horizontal",
+casper.waitForSelector "form.form", ->
+  @fill "form.form",
     displaypage: true
     title: "title999"
     url: "url999"


output correctly align and context

output correctly align and context

--- lib/Default.coffee
+++ lib/Default.coffee
@@ -39,29 +39,22 @@
         type = then @types[type].symbol
       else
         type = @keyword
-
-    if @align
-      if CONST.ENV is 'production'
-        align = ' '
-      else if @keyword
-        align = ' '
-      else
-        align = @align
-    else
-      align = ' '
-
     type = type.toUpperCase() if @upperCase
     type

+  outputAlign: ->
+    return ' ' if CONST.ENV is 'production' or not @align or @keyword
+    @align
+
   outputCounter: ->
+    return '' unless @timestamp
     now = new Date
     diff = now - @timestamp
     ++@counter if (diff > 1000)
     @timestamp = new Date()
     "[#{@decorateCounter(@counter)}]"

-  outputContext: ->
-    @context
+  outputContext: -> @context

   transport: console.log

@@ -91,7 +84,9 @@
     messageContext = @outputContext()
     messageContext = @colorize CONST.LINE_COLOR, messageContext

-    output = "#{messageType} #{messageCounter} #{messageContext} #{@align}#{message}"
+    align = @outputAlign()
+
+    output = "#{messageType} #{messageCounter} #{messageContext}#{align}#{message}"
     output += @colorize colorType, diff if diff
     output

@@ -136,12 +131,12 @@
     formatUtil.apply null, messages

   keyword: null
+  context: null
   diff: false
   align: "\t\t"
   color: true
   counter: 0
   cli: false
-  context: 'test'

   timestamp: new Date()


Fixes for Mattermost 3.5.0

Fixes for Mattermost 3.5.0

--- src/matteruser.coffee
+++ src/matteruser.coffee
@@ -118,23 +118,22 @@

     message: (msg) =>
         @robot.logger.debug msg
-        return if msg.user_id == @self.id # Ignore our own output
-        @robot.logger.debug 'From: ' + msg.user_id + ', To: ' + @self.id
+        mmPost = JSON.parse msg.data.post
+        mmUser = @client.getUserByID mmPost.user_id
+        return if mmPost.user_id == @self.id # Ignore our own output
+        @robot.logger.debug 'From: ' + mmPost.user_id + ', To: ' + @self.id

-        mmChannel = @client.getChannelByID msg.channel_id if msg.channel_id
-        mmUser = @client.getUserByID msg.user_id
-        mmPost = JSON.parse msg.data.post
-
-        @robot.logger.debug 'Received message from '+mmUser.username+': ' + mmPost.message
-        user = @robot.brain.userForId msg.user_id
-        user.room = msg.channel_id
+        user = @robot.brain.userForId mmPost.user_id
+        user.room = mmPost.channel_id

         text = mmPost.message
-        if msg.data.channel_type == 'D' and !///^#{@robot.name} ///i.test(text) # Direct message
-          text = "#{@robot.name} #{text}"
-          user.mm.dm_channel_id = msg.channel_id
+        if msg.data.channel_type == 'D'
+          if !///^#{@robot.name} ///i.test(text) # Direct message
+            text = "#{@robot.name} #{text}"
+          user.mm.dm_channel_id = mmPost.channel_id
+        @robot.logger.debug 'Text: ' + text

-        @receive new TextMessage user, text, msg.id
+        @receive new TextMessage user, text, mmPost.id
         @robot.logger.debug "Message sent to hubot brain."
         return true


fix naming

fix naming

--- src/mkts-table.coffee
+++ src/mkts-table.coffee
@@ -258,26 +258,26 @@
   #.........................................................................................................
   ### TAINT code duplication ###
   ### TAINT this will have to be changed to allow for named fields ###
-  fieldhints = new Set ( _.trim() for _ in fieldhints.split ',' )
-  if fieldhints.has '*'
-    fields = Object.keys me.fieldcells
+  fieldhint_set = new Set ( _.trim() for _ in fieldhints.split ',' )
+  if fieldhint_set.has '*'
+    fieldnames = Object.keys me.fieldcells
   else
     ### TAINT as it stands, `fieldborder'table:bottom,right:red'` will style all bottom and right borders
     of all fields that have real estate along the bottom and right borders of the table. An improved version
     should probably only affect the bottom borders of table-bottom fields and the right borders of
     table-right fields. Use two statements `fieldborder'table:bottom:red'`, `fieldborder'table:right:red'` to
     express that meaning FTTB. ###
-    if fieldhints.has 'table'
-      fieldhints.delete 'table'
+    if fieldhint_set.has 'table'
+      fieldhint_set.delete 'table'
       for edge in edges
-        fieldhints.add d.cellkey for d from IG.GRID.walk_edge_cellrefs me.grid, edge
+        fieldhint_set.add d.cellkey for d from IG.GRID.walk_edge_cellrefs me.grid, edge
     cellkeys    = ( fieldhint for fieldhint from fieldhints )
     fields      = @_fieldnames_from_cellkeys me, cellkeys
   #.........................................................................................................
   style = style.trim()
   style = null if style in [ 'none', '', ]
   #.........................................................................................................
-  return { fields, edges, style, }
+  return { fieldnames, edges, style, }

 #===========================================================================================================


cruft

cruft

--- lib/ink.coffee
+++ lib/ink.coffee
@@ -19,7 +19,6 @@
     Console.activate()
     PlotPane.activate()
     Workspace.activate()
-    window.Stepper = Stepper

   deactivate: ->
     Result.deactivate()


few more polish commits on the shell

few more polish commits on the shell

--- lib/lt3.coffee
+++ lib/lt3.coffee
@@ -57,13 +57,15 @@
   exit/quit                       exit shell

   HELPER_COMMANDS
+  get:site <id>                   get site by id
+  get:object <id>                 get object by id
   get:sites [<slug>]              get sites with shell
   get:allsites                    returns a list of id_slug mappings for all sites
   get:sitemap <slug>              get contents of site
   get:site_ids <slug>..           returns a id to slug lookup
   help/docs                       this document
-  shell:site <id>                  this opens site in context
-  shell:object_or_page <id>        this opens object in context
+  shell:site <id>                 this opens site in context
+  shell:object <id>               this opens object in context

   DB_COMMANDS                     Query the database with results
   db.sites.find
@@ -116,27 +118,6 @@
     else
       rl.prompt()

- ###
-      May be added later still tbd on implementation
-      when 'get:objects_c'
-        getDB (db) ->
-          db.get('objects').find {collection: l_args[0]}, (err, objects) ->
-            for object in objects
-              log "------Object------"
-              log JSON.stringify(object.val(), null, 4)
-              log "------End Object------"
-            rl.prompt();
-
-      when 'get:objects_t'
-        getDB (db) ->
-         db.get('objects').find {type: l_args[0]}, (err, objects) ->
-          for object in objects
-            log "------Object------"
-            log JSON.stringify(object.val(), null, 4)
-            log "------End Object------"
-          rl.prompt();
-    ###
-
 executeCommand = (l_token,l_command,l_args) =>
   if @shell_context
     executeContextCommand l_token,l_command,l_args
@@ -147,6 +128,22 @@
       exit();

     when 'get:sites'
+      getDB (db) ->
+       db.get('sites').findOne {_id: l_args[0]}, (err, site) ->
+          log "------Site------"
+          log JSON.stringify(site.val(), null, 4)
+          log "------End Site------"
+          rl.prompt();
+
+    when 'get:object'
+      getDB (db) ->
+       db.get('objects').findOne {_id: l_args[0]}, (err, object) ->
+         log "------Object------"
+         log JSON.stringify(object.val(), null, 4)
+         log "------End Object------"
+         rl.prompt();
+
+    when 'get:site'
       getDB (db) ->
        db.get('sites').find {slug: l_args[0]}, (err, sites) ->
         for site in sites
@@ -154,7 +151,6 @@
           log JSON.stringify(site.val(), null, 4)
           log "------End Site------"
         rl.prompt();
-
     when 'get:allsites'
       getDB (db) ->
        db.get('sites').find {}, (err, sites) ->
@@ -193,6 +189,7 @@
           prompt="#{sites[0].data.slug}-#{id}"
           rl.setPrompt "#{prompt}>"
           rl.prompt();
+
     when 'shell:object'
       getDB (db) =>
         db.get('objects').findOne {_id: l_args[0]}, (err, object) =>
@@ -822,7 +819,7 @@
             throw err if err
             log "page added to #{site_slug}"
             exit()
-s
+
   when 'add:object'

     # parse arguments


text/html changes

text/html changes

--- client/landing/site.hackathon/coffee/home/AppViewFull.coffee
+++ client/landing/site.hackathon/coffee/home/AppViewFull.coffee
@@ -372,8 +372,8 @@

How does it work?

First, sign up using the simple form above. Every day (up until we stop accepting
-        applications on Dec 3rd, 2014 12 noon PST) between 4-5pm PST, we will send all new
-        applicants a short questionnaire that will help us learn a little bit more about
+        applications on Dec 3rd, 2014 12 noon PST) between 4-5pm PST, we will send all new
+        applicants a short questionnaire that will help us learn a little bit more about
         you and your team’s background. We promise to keep it short and sweet.

@@ -390,10 +390,11 @@
         developer looking for a backend database team member. Once you get a response, jump into
         private chats on Koding itself to discuss your ideas and start recruiting!

-
+

         Prizes (described below) are in three different categories so it does not matter if you
         are an expert, a student or a beginner at programming. Everyone can compete and win!
+

How do I get accepted?

@@ -410,52 +411,52 @@

         The following themes have been selected:

-
Problems facing our planet, explained using interactive data visualization.
-        	(e.g. climate change, earthquakes, food/water waste, etc.)

-
Introducing software development to a beginner (games!)

-
No one reads the fine print (ie TOS, EULA, legal documents) anymore yet every
-        	site has them. Devise a creative/interactive solution.

-
HTML5 games that are educational and learning oriented. (multiplayer preferred)

-
Challenges associated with real time communication and translation (Star
-        	Trek universal translation anyone?)

+
Problems facing our planet, explained using interactive data visualization.
+          (e.g. climate change, earthquakes, food/water waste, etc.)

+
Introducing software development to a beginner (games!)

+
No one reads the fine print (ie TOS, EULA, legal documents) anymore yet every
+          site has them. Devise a creative/interactive solution.

+
HTML5 games that are educational and learning oriented. (multiplayer preferred)

+
Challenges associated with real time communication and translation (Star
+          Trek universal translation anyone?)

-        Your task is to use publicly available resources (APIs, data sets, graphics, etc.) and
+
Your task is to use publicly available resources (APIs, data sets, graphics, etc.) and
         your imagination to create a project that addresses one of the themes of the event.

What are the prizes?

Total prizes over $#{prize.toLocaleString()}! Here are the details.

-
Prizes will be awarded in three categories:

-		Top Hacker:
-

-
First prize:  <s>$5,000</s> $8,000

-
Second prize: <s>#3,000</s> $4,000

-
Third prize:  <s>$2,000</s> $2,000

-

-		Student Hacker:
-

-
First prize: $1,500

-
Second prize: $1,000

-
Third prize: $500

-

-		Apprentice Hacker:
-

-
First prize: $1,200

-
Second prize: $800

-
Third prize: $500

-

-

+
Prizes will be awarded in three categories:

+
Top Hacker:

+

+
First prize:  <s>$5,000</s> $8,000

+
Second prize: <s>$3,000</s> $4,000

+
Third prize: $2,000

+

+
Student Hacker (High school or less):

+

+
First prize: $1,500

+
Second prize: $1,000

+
Third prize: $500

+

+
Apprentice Hacker:

+

+
First prize: $1,200

+
Second prize: $800

+
Third prize: $500

+

+

Additional Prizes

-
These will be presented to the #1 winning team in the "Top Hacker" prize category

+
These will be presented to the #1 winning team in the "Top Hacker" prize category.

Count.ly will award an extra $1,000 to the winning team if they have a Countly account so register today!

-

+

@@ -466,7 +467,7 @@

If the winning team has an oDesk.com or Elance.com account, they will receive an extra $2,500. Register today if you don't have an account with them.
-

+

@@ -475,24 +476,25 @@

The top winning team will receive $5,000 in account credit from Digital Ocean. and a one year service account credit from Tutum.

-

-

-

-
These prizes can be won by any team, even if you don't win the top 3 spots in the "Top Hacker" prize
-		category.

-

+

+

+

+
These prizes can be won by any team

+
Even if you don't win the top 3 spots in the "Top Hacker" prize
+    category, you can still apply to win these prizes.

+

Intuit will award an extra $2,500 to the team that presents the best use of the QuickBooks API.

-

+

Count.ly will award an extra $1,000 to the winning team if they have a Countly account.

-

+

@@ -503,7 +505,7 @@

3Scale will offer an additional $1,500 to the team that makes the best usage of any API on thier site.

-

+

DATADOG will award $1,000 to the team that makes the best use of their service and their API.

@@ -523,14 +525,14 @@

APITools will offer $1,500 to the team that uses APItools in the creation of thier project.

-

+

What is the schedule?

Now - Registration open.

-
#{closeDay}, 1200 PST - Applications are closed. Final notifications are sent to teams/individuals
+
#{closeDay}, 1200 PST - Applications are closed. Final notifications are sent to teams/individuals
           who are accepted

#{firstDay} - Day 1 Let the hacking begin!

#{secondDay} - Day 2 of the Hackathon
@@ -569,16 +571,16 @@

 What are the rules?

-

-
You (or someone from your team) must be approved by Koding and should have our acceptance email.

-
Go to this repo on GitHub and
-			follow the instructions presented there.

-
All team members must have a Koding account.

-
All project developed must be directly aligned with one of the themes of the event.

-
Your project MUST BE a web app that is viewable on your Koding VMs web URL. Mobile
-			apps are not allowed. (Don't worry if you don't have an always-on Koding VM, we will take
-			care of that for you when we review your work.)

-

+

+
You (or someone from your team) must be approved by Koding and should have our acceptance email.

+
Go to this repo on GitHub and
+          follow the instructions presented there.

+
All team members must have a Koding account.

+
All project developed must be directly aligned with one of the themes of the event.

+
Your project MUST BE a web app that is viewable on your Koding VMs web URL. Mobile
+          apps are not allowed. (Don't worry if you don't have an always-on Koding VM, we will take
+          care of that for you when we review your work.)

+

Our goal is to ensure that all teams have a level playing field; therefore it is
         imperative that all code, design, assets, etc… must be created during the duration
         of the event. No exceptions. You can brainstorm ideas prior to the event, however