Make sure FROM will be the first command

--- src/dockerfile.coffee
+++ src/dockerfile.coffee
@@ -15,8 +15,46 @@
 groupByAppearanceOrder = require('./group-by-appearance-order')
 FinalizingContext = require('./finalizing-context')
 Pack = require('./pack')
+From = require('./commands/from')

 sameCommand = (a, b) -> a.constructor == b.constructor
+
+singleMulti = (single, multi) -> {single: single, multi: multi}
+
+aggregateMap = [
+  singleMulti(Env, MultiEnv),
+  singleMulti(Expose, MultiExpose),
+  singleMulti(Label, MultiLabel),
+  singleMulti(Run, MultiRun),
+  singleMulti(Volume, MultiVolume),
+  singleMulti(ContextCopy, MultiContextCopy)
+]
+
+# Updates the list in-place
+aggregate = (list, type, ctor) ->
+  byType = list.filter((c) -> c.constructor == type)
+  newList = []
+  if byType.length > 0
+    aggregated = new ctor(byType[0])
+    aggregated.next = byType[0].next
+    newList.push(aggregated)
+    aggregateMore = (more) ->
+      newAggregated = new ctor(aggregated, more)
+      newAggregated.next = aggregated.next
+      aggregated = newAggregated
+    byType[1..].forEach(aggregateMore)
+    # sorted is ordered by type
+    start = list.indexOf(byType[0])
+    list[start...(start + byType.length)] = aggregated
+  return list
+
+aggregateOne = (single, type, ctor) ->
+  if single.constructor == type
+    aggregated = new ctor(single)
+    aggregated.next = single.next
+    return aggregated
+  else
+    return single

 class Dockerfile
   constructor: ->
@@ -47,7 +85,11 @@
     @add(command)
   build: (dockerfile = [], context = new FinalizingContext(new Pack()))->
     commands = clone(@commands)
-    # Make indepdendent commands dependent
+    from = commands.filter((c) -> c.constructor == From)[0]
+    throw new Error('Missing FROM command') unless from?
+    commands.forEach((c) -> c.next = [])
+    commands.splice(commands.indexOf(from), 1)
+    # Make independent commands dependent
     makeDependent = (c) ->
       for cmd in commands
         if sameCommand(cmd, c) and cmd.after?
@@ -55,37 +97,28 @@
           break
     commands.filter((c) -> !c.after?).forEach(makeDependent)
     # Create reverse links for simple walking
-    root = {next: [], root: true}
-    commands.filter((c) -> !c.after?).forEach((c) -> c.after = root)
-    commands.forEach((c) -> c.next = [])
+    commands.filter((c) -> !c.after?).forEach((c) -> c.after = from)
     commands.forEach((c) -> c.after.next.push(c))
+    # Walks a single layer of command dependants
     walkLayer = (layer, dockerfile, context) ->
       return unless layer.length > 0
-      sorted = groupByAppearanceOrder(layer)
-      aggregate = (list, type, ctor) ->
-        byType = list.filter((c) -> c.constructor == type)
-        if byType.length > 0
-          aggregated = new ctor(byType[0])
-          aggregated.next = byType[0].next
-          aggregateMore = (more) ->
-            newAggregated = new ctor(aggregated, more)
-            newAggregated.next = aggregated.next.concat(more.next)
-            aggregated = newAggregated
-          byType[1..].forEach(aggregateMore)
-          # sorted is ordered by type
-          start = list.indexOf(byType[0])
-          list[start...(start + byType.length)] = aggregated
-          #list.splice(list.indexOf(byType[0]), byType.length, aggregated)
-      aggregate(sorted, Env, MultiEnv)
-      aggregate(sorted, Expose, MultiExpose)
-      aggregate(sorted, Label, MultiLabel)
-      aggregate(sorted, Run, MultiRun)
-      aggregate(sorted, Volume, MultiVolume)
-      aggregate(sorted, ContextCopy, MultiContextCopy)
-      for command in sorted
+      # We only combine those without dependants
+      noDependants = layer.filter((c) -> c.next.length == 0)
+      sorted = groupByAppearanceOrder(noDependants)
+      # Combine commands without dependants
+      aggregate(sorted, map.single, map.multi) for map in aggregateMap
+      # We know that nothing depends on what we just combined
+      command.applyTo(context, dockerfile) for command in sorted
+      # After combined multi-commands we create multi-commands for those
+      # with dependants
+      dependants = layer.filter((c) -> c.next.length > 0)
+      for d, i in dependants
+        dependants[i] = aggregateOne(dependants[i], map.single, map.multi) for map in aggregateMap
+      # Walk dependants depth-first
+      for command in dependants
         command.applyTo(context, dockerfile)
         walkLayer(command.next, dockerfile, context)
-    walkLayer(root.next, dockerfile, context)
+    walkLayer([from], dockerfile, context)
     context.entry({name: '/Dockerfile'}, dockerfile.join("\n"))
     context.finalize()
     return context


use chrome.windows.onFocusChanged to listen window switching

This fix the problem that `chrome.tabs.onActivated` won't be triggered
when we switch Chrome windows.

--- background_scripts/bg_utils.coffee
+++ background_scripts/bg_utils.coffee
@@ -17,6 +17,13 @@
     chrome.tabs.onReplaced.addListener (addedTabId, removedTabId) =>
       @deregister removedTabId
       @register addedTabId
+
+    self = @
+    chrome.windows.onFocusChanged.addListener (wnd) ->
+      return if wnd == chrome.windows.WINDOW_ID_NONE
+      chrome.tabs.query {windowId: wnd, active: true}, (tabs) ->
+        self.register tabs[0].id if tabs[0]
+        chrome.runtime.lastError

   register: (tabId) ->
     currentTime = new Date()


:lipstick:

:lipstick:

--- src/chart-d3-bar.coffee
+++ src/chart-d3-bar.coffee
@@ -121,7 +121,7 @@
     @getRootElement()
       .select('.margin-convention-element')
       .append('g')
-      .attr('class', 'scale scale-x')
+      .classed(scale: true, 'scale-x': true)
       .attr('transform', "translate(0,#{chartHeight})")
       .call(D3Bar.xAxis(x0Scale))

@@ -129,19 +129,19 @@
     @getRootElement()
       .select('.margin-convention-element')
       .append('g')
-      .attr('class', 'scale scale-y')
+      .classed(scale: true, 'scale-y': true)
       .call(D3Bar.yAxis(yScale))

   renderBars: (data, x0Scale) =>
     @getRootElement()
       .select('.margin-convention-element')
       .append('g')
-      .attr('class', 'bar-chart')
+      .classed('bar-chart', true)
       .selectAll('.bars-group')
       .data(data)
       .enter()
       .append('g')
-      .attr('class', 'bars-group')
+      .classed('bars-group', true)
       .attr('transform', (d) -> "translate(#{x0Scale(d.key)},0)")

   renderBar: (chartHeight, x1Scale, yScale) =>
@@ -151,9 +151,9 @@
       .data((d, i) -> d.values)
       .enter()
       .append('g')
-      .attr('class', 'bar-group')
+      .classed('bar-group', true)
       .append('rect')
-      .attr('class', 'bar')
+      .classed('bar', true)
       .attr('x', (d, i) -> x1Scale(i))
       .attr('width', D3Bar.adjustRangeBand(x1Scale.rangeBand()))
       .attr('y', (d, i) -> yScale(d.value))
@@ -165,7 +165,7 @@
       .selectAll('.bars-group')
       .selectAll('.bar-group')
       .append('path')
-      .attr('class', 'bar-border')
+      .classed('bar-border', true)
       .attr 'd', (d, i) ->
         D3Bar.rectBorderPath(d, i, chartHeight, x1Scale, yScale, strokeWidth)
       .attr('fill', 'none')


ClampBounds docs

ClampBounds docs

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -19,7 +19,7 @@
   player.update()

   # x, y position is based on the center point so
-  # the posot
+  # the position the
   player.I.x
   # => 2.5
 </pre></code>


use jquery way to escape html

use jquery way to escape html

--- static/coffee/simulator.coffee
+++ static/coffee/simulator.coffee
@@ -1,8 +1,5 @@
 window.simulation = false
 moving_sim = false
-
-simulatorEscapeHTML = (string) ->
-  string.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>')

 window.updateSimulator = (data) ->

@@ -14,7 +11,11 @@
     msg = data.messages[i]
     direction = (if (msg.direction is "O") then "from" else "to")
     model = (if (msg.model is "msg") then "imsg" else "ilog")
-    $(".simulator-body").append "
" + simulatorEscapeHTML(msg.text) + "
"
+    $(".simulator-body").append "

"
+
+    $(".simulator-body ." + model + "." + direction + ":last").text(msg.text).html()
+
+
     i++
   $(".simulator-body").scrollTop $(".simulator-body")[0].scrollHeight
   $("#simulator textarea").val ""
@@ -203,7 +204,8 @@

   # add the progress gif
   if newMessage and newMessage.length <= 160
-    $(".simulator-body").append "
" + simulatorEscapeHTML(newMessage) + "
"
+    $(".simulator-body").append "

"
+    $(".simulator-body .imsg.to.post-message:last").text(newMessage).html()
     $("#simulator textarea").val ""
     $(".simulator-loading").css "display", "block"
     # $(".simulator-body").css "height", $(".simulator-body").height() - 25
@@ -218,7 +220,8 @@

     # add the progress gif
     if newMessage and newMessage.length <= 160
-      $(".simulator-body").append "
" + simulatorEscapeHTML(newMessage) + "
"
+      $(".simulator-body").append "

"
+      $(".simultor-body .imsg.to.post-message:last").text(newMessage).html()
       $("#simulator textarea").val ""
       $(".simulator-loading").css "display", "block"
       # $(".simulator-body").css "height", $(".simulator-body").height() - 25


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -1,5 +1,6 @@
 ClampBounds = (I={}, self) ->
-  Object.reverseMerg
+  Object.reverseMerge I,
+

   self.bind "update", ->
     I.x = I.x.clamp()


Find the timeline by controller.

Find the timeline by controller.

--- test/e2e/subjectDetailSpec.coffee
+++ test/e2e/subjectDetailSpec.coffee
@@ -37,8 +37,8 @@
     @findAll('.qi-modeling-chart')

   # @returns the date line chart promise
-  @property dateLine: ->
-    @find('//qi-timeline//nvd3-line-chart')
+  @property timeline: ->
+    @find('//div[@ng-controller="TimelineCtrl"]')

   # @returns the clinical profile panel promise
   @property clinicalProfile: ->
@@ -121,7 +121,7 @@
         # inserts the SVG into the DOM, and the app directive
         # then further modifies it.
         it 'should exist', ->
-          expect(page.dateLine, 'The timeline is missing').to.eventually.exist
+          expect(page.timeline, 'The timeline is missing').to.eventually.exist

       describe 'Format Button', ->
         it 'should toggle the chart/table display', ->


now the unit tests pass woot

now the unit tests pass woot

--- src/coffee/ticks.coffee
+++ src/coffee/ticks.coffee
@@ -405,28 +405,24 @@
     candidate_intervals = arr_div2(range, divisions)

     # Get magnitudes and mantissas for each candidate:
-    #magnitudes = Math.pow(10.0, Math.floor(log10(candidate_intervals)))
+
     magnitudes = candidate_intervals.map((candidate) ->
       return Math.pow(10.0, Math.floor(log10(candidate))))
-
-    #mantissas  = candidate_intervals / magnitudes
     mantissas  = arr_div3(candidate_intervals, magnitudes)
-

     # List of "pleasing" intervals between ticks on graph.
     # Only the first magnitude are listed, higher mags others are inferred:
-    magic_intervals = [5.0, 1.0, 2.0, 2.5, 10.0 ]
+    magic_intervals = [1.0, 2.0, 2.5, 5.0, 10.0 ]

     best_mantissas = []
     best_magics = []
     for mi in magic_intervals
       diff_arr = mantissas.map((x) -> Math.abs(mi - x))
-      best_magics.push(Math.min.apply(diff_arr))
+      best_magics.push(_.min(diff_arr))
     for ma in mantissas
       diff_arr = magic_intervals.map((x) -> Math.abs(ma - x))
-      best_mantissas.push(Math.min.apply(diff_arr))
-
+      best_mantissas.push(_.min(diff_arr))
     # Calculate the absolute differences between the candidates
     # (with magnitude removed) and the magic intervals:

@@ -434,9 +430,9 @@
     # Find the division and magic interval combo that produce the
     # smallest differences:
     magic_index    = argsort(best_magics )[0]
+
     mantissa_index = argsort(best_mantissas )[0]
-
-
+

     # The best interval is the magic_interval multiplied by the magnitude
     # of the best mantissa:


Creating a device for tests

Creating a device for tests


add onBackspace support to fsInput directive

--- src/fs/fsInput.coffee
+++ src/fs/fsInput.coffee
@@ -7,17 +7,18 @@
     blurElement = () -> setTimeout((-> element[0].blur()), 0)

     keyCodes =
-      Tab:      9
-      ShiftTab: 9
-      Enter:    13
-      Esc:      27
-      PgUp:     33
-      PgDown:   34
-      Left:     37
-      Up:       38
-      Right:    39
-      Down:     40
-      Space:    32
+      Tab:       9
+      ShiftTab:  9
+      Enter:     13
+      Esc:       27
+      PgUp:      33
+      PgDown:    34
+      Left:      37
+      Up:        38
+      Right:     39
+      Down:      40
+      Space:     32
+      Backspace: 8

     if attrs["fsFocusWhen"]?
       scope.$watch attrs["fsFocusWhen"], (newValue) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/clamp.coffee
+++ test/clamp.coffee
@@ -11,15 +11,15 @@
   obj = GameObject
     x: 105

-  obj.incude Clamp
+  obj.include Clamp

   obj.clamp
     x:
       min: 0
       max: 100

-  obj.update
+  obj.update()

-  equals obj.I.x, 10
+  equals obj.I.x, 100

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/game_state.coffee
+++ src/game_state.coffee
@@ -65,8 +65,6 @@

     I.updating = false

-  #TODO Include GameState modules like cameras,
-  # save states, etc.
   self.include GameState.Cameras
   self.include GameState.SaveState


Update core.coffee

Update core.coffee

Just fixed a minor typo.


Bugfixes

Bugfixes

--- src/languages/html.coffee
+++ src/languages/html.coffee
@@ -283,13 +283,29 @@
       while i < node.childNodes.length
         @cleanTree node.childNodes[i]
         if not node.childNodes[i].__location
+          if node.childNodes[i].childNodes?
+            for child in node.childNodes[i].childNodes
+              child.parentNode = node
           node.childNodes = node.childNodes[0...i].concat(node.childNodes[i].childNodes || []).concat node.childNodes[i+1...]
         else
           i++

-  fixBounds: (node, lastPosition) ->
+  fixBounds: (node) ->

     if not node
+      return
+
+    if node.childNodes?
+      for child, i in node.childNodes
+        @fixBounds child
+      newList = []
+      for child in node.childNodes
+        if !child.remove
+          newList.push child
+      node.childNodes = newList
+
+    if node.nodeName is '#document' or node.nodeName is '#document-fragment'
+      node.type = 'document'
       return

     if node.nodeName is '#text'
@@ -297,7 +313,7 @@
       if node.value.trim().length is 0
         node.remove = true
       else
-        @trimText node, lastPosition
+        @trimText node
       return

     if node.nodeName is '#comment'
@@ -310,23 +326,6 @@
         @setAttribs node, @text[node.__location.start...node.__location.end]
       return

-    if node.childNodes?
-      for child, i in node.childNodes
-        if i is node.childNodes.length - 1
-          lastPosition = node.__location?.endTag?.start ? lastPosition
-        else
-          lastPosition = node.childNodes[i+1].__location.start ? lastPosition
-        @fixBounds child, lastPosition
-      newList = []
-      for child in node.childNodes
-        if !child.remove
-          newList.push child
-      node.childNodes = newList
-
-    if node.nodeName is '#document' or node.nodeName is '#document-fragment'
-      node.type = 'document'
-      return
-
     node.type = 'blockTag'

     node.__indentLocation = {start: node.__location.startTag.end}
@@ -337,13 +336,31 @@
       node.__indentLocation.end = node.__location.startTag.end

     if not node.__location.endTag
-      node.__location.end = node.__indentLocation.end
+      # Forcing object copy because
+      # sometimes parse5 reuses location info
+      # for semi-fake nodes.
+      node.__location = {
+        start: node.__location.start
+        end: node.__indentLocation.end
+      }

     @setAttribs node, @text[node.__location.start...node.__indentLocation.start]

-  trimText: (node, lastPosition) ->
+  getEndPoint: (node) ->
+    if node.nodeName in ['#document', '#document-fragment']
+      return @text.length
+    last = null
+    parent = node.parentNode
+    ind = parent.childNodes.indexOf node
+    if ind is parent.childNodes.length - 1
+      last = if parent.__location?.endTag? then parent.__location.endTag.start else @getEndPoint parent
+    else
+      last = parent.childNodes[ind+1].__location.start
+    return last
+
+  trimText: (node) ->
     location = node.__location
-    location.end = Math.min location.end, lastPosition
+    location.end = Math.min location.end, @getEndPoint node
     text = @text[location.start...location.end].split '\n'
     i = 0
     while text[i].trim().length is 0
@@ -429,66 +446,75 @@
     if parseContext and parseContext not in ['html', 'head', 'body']
       root = htmlParser.parseFragment @text
       @cleanTree root
-      @fixBounds root, @text.length
+      @fixBounds root
     else
       root = htmlParser.parse @text
       @cleanTree root
-      @fixBounds root, @text.length
+      @fixBounds root
       if root.childNodes.length is 0
         root = htmlParser.parseFragment @text
         @cleanTree root
-        @fixBounds root, @text.length
+        @fixBounds root
+    window.root = root
+    window.parse5 = htmlParser
     @mark 0, root, 0, null

-  mark: (indentDepth, node, depth, bounds) ->
+  mark: (indentDepth, node, depth, bounds, nomark = false) ->

     switch node.type
       when 'document'
         lastChild = null
         for child in node.childNodes
-          unless lastChild and lastChild.__location.end > child.__location.start
-            @mark indentDepth, child, depth + 1, null
+          if lastChild and lastChild.__location.end > child.__location.start then nomark = true else nomark = false
+          @mark indentDepth, child, depth + 1, null, nomark
+          unless nomark
             lastChild = child

       when 'emptyTag'
-        @htmlBlock node, depth, bounds
-        for attrib in node.attributes
-          if attrib.end - attrib.start > 1
+        unless nomark
+          @htmlBlock node, depth, bounds
+          for attrib in node.attributes
+            if attrib.end - attrib.start > 1
+              @htmlSocket node, depth + 1, null, @genBounds(attrib), ATTRIBUTE_CLASSES
+
+      when 'blockTag'
+        unless nomark
+          @htmlBlock node, depth, bounds
+          for attrib in node.attributes
             @htmlSocket node, depth + 1, null, @genBounds(attrib), ATTRIBUTE_CLASSES
-      when 'blockTag'
-        @htmlBlock node, depth, bounds
-        for attrib in node.attributes
-          @htmlSocket node, depth + 1, null, @genBounds(attrib), ATTRIBUTE_CLASSES
-        indentBounds = @makeIndentBounds node
-        if indentBounds.start.line isnt indentBounds.end.line or indentBounds.start.column isnt indentBounds.end.column
-          depth++
-          prefix = @getIndentPrefix(indentBounds, indentDepth, depth)
-          indentDepth += prefix.length
-          @addIndent
-            bounds: indentBounds
-            depth: depth
-            prefix: prefix
-            classes: @getClasses node
-          lastChild = null
-          for child in node.childNodes
-            unless lastChild and lastChild.__location.end > child.__location.start
-              @mark indentDepth, child, depth + 1, null
-              lastChild = child
-        else
-          unless TAGS[node.nodeName].content is 'optional' or
-              (node.nodeName is 'script' and @hasAttribute node, 'src') or
-              node.__indentLocation.end is node.__location.end
-            @htmlSocket node, depth + 1, null, indentBounds, null, true
+          indentBounds = @makeIndentBounds node
+          if indentBounds.start.line isnt indentBounds.end.line or indentBounds.start.column isnt indentBounds.end.column
+            depth++
+            prefix = @getIndentPrefix(indentBounds, indentDepth, depth)
+            indentDepth += prefix.length
+            @addIndent
+              bounds: indentBounds
+              depth: depth
+              prefix: prefix
+              classes: @getClasses node
+            lastChild = null
+          else
+            unless TAGS[node.nodeName].content is 'optional' or
+                (node.nodeName is 'script' and @hasAttribute node, 'src') or
+                node.__indentLocation.end is node.__location.end
+              @htmlSocket node, depth + 1, null, indentBounds, null, true
+        for child in node.childNodes
+          if lastChild and lastChild.__location.end > child.__location.start then nomark = true else nomark = false
+          @mark indentDepth, child, depth + 1, null, nomark
+          unless nomark
+            lastChild = child

       when 'text'
-        @htmlBlock node, depth, bounds
-        @htmlSocket node, depth + 1, null
+        unless nomark
+          @htmlBlock node, depth, bounds
+          @htmlSocket node, depth + 1, null

       when 'comment'
-        @htmlBlock node, depth, bounds
-        node.__location.start += 4
-        node.__location.end -= 3
-        @htmlSocket node, depth + 1, null
+        unless nomark
+          @htmlBlock node, depth, bounds
+          node.__location.start += 4
+          node.__location.end -= 3
+          @htmlSocket node, depth + 1, null

   isComment: (text) ->
     text.match(/<!--.*-->/)


Fix PrivateMessage notifications

Fix PrivateMessage notifications

--- workers/social/lib/social/models/messages/privatemessage.coffee
+++ workers/social/lib/social/models/messages/privatemessage.coffee
@@ -20,7 +20,8 @@
         'unmark','fetchRelativeComments'
       ]
     schema        : jraphical.Message.schema
-    # TODO: copying and pasting this for now...  We need an abstract interface "commentable" or something like that)
+    # TODO: copying and pasting this for now...
+    # We need an abstract interface "commentable" or something like that)
     relationships : JPost.relationships

   reply: secure (client, comment, callback)->
@@ -29,16 +30,19 @@

   disown: secure ({connection}, callback)->
     {delegate} = connection
-    delegate.removePrivateMessage @, {as: $in: ['sender', 'recipient']}, callback
+    delegate.removePrivateMessage @, {as: $in: ['sender', 'recipient']}, \
+    callback

   collectParticipants: secure ({connection}, callback)->
     {delegate} = connection
     register = new Register # a register per message...
-    jraphical.Relationship.all targetName: 'JPrivateMessage', targetId: @getId(), sourceId: $ne: delegate.getId(), (err, rels)=>
+    jraphical.Relationship.all targetName: 'JPrivateMessage', \
+      targetId: @getId(), sourceId: $ne: delegate.getId(), (err, rels)=>
       if err
         callback err
       else
-        @participants = (rel for rel in rels when register.sign rel.sourceId) # only include unique participants.
+        # only include unique participants.
+        @participants = (rel for rel in rels when register.sign rel.sourceId)
         callback null,@

   @create = do ->
@@ -49,23 +53,15 @@
           if err
             fin err
           else
-            # FIXME GG
-            # recipient.emit 'NewPrivateMessageHasArrived',
-            #   actorType   : 'sender'
-            #   actionType  : 'newMessage'
-            #   sender      : ObjectRef(sender).data
-            #   subject     : ObjectRef(pm).data
-            # recipient.sendNotification 'NewPrivateMessageHasArrived',

             recipient.emit 'PrivateMessageSent',
               origin        : recipient
               subject       : ObjectRef(pm).data
               actorType     : 'sender'
+              actionType    : 'newMessage'
               sender        : ObjectRef(sender).data

             fin()
-            # jraphical.Channel.fetchPrivateChannelById recipient.getId(), (err, channel)->
-            #   channel.publish sender, pm
       , callback
       deliver recipient, pm for recipient in recipients

@@ -80,7 +76,8 @@

       {to, subject, body} = data
       if 'string' is typeof to
-        to = to.replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').split(' ') # accept virtaully any non-wordchar delimiters for now.
+        # accept virtaully any non-wordchar delimiters for now.
+        to = to.replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').split(' ')
       JAccount.all 'profile.nickname': $in: to, (err, recipients)->
         if err
           callback err
@@ -105,6 +102,3 @@
                       callback err
                     else
                       callback null, pm
-#                      unless delegate.profile.nickname in to
-#                        jraphical.Channel.fetchPrivateChannelById delegate.getId(), (err, channel)->
-#                          channel.publish delegate, pm


Use statSyncNoException when available

Use statSyncNoException when available

--- src/cson.coffee
+++ src/cson.coffee
@@ -113,11 +113,18 @@
   catch parseError
     callback?(parseError)

-exists = (objectPath) ->
-  try
-    fs.statSync(objectPath).isFile()
-  catch error
-    false
+if fs.statSyncNoException?
+  exists = (objectPath) ->
+    if stats = fs.statSyncNoException(objectPath)
+      stats.isFile()
+    else
+      false
+else
+  exists = (objectPath) ->
+    try
+      fs.statSync(objectPath).isFile()
+    catch error
+      false

 module.exports =
   setCacheDir: (cacheDirectory) -> csonCache = cacheDirectory


change simplification area parameter

change simplification area parameter

--- src/data.coffee
+++ src/data.coffee
@@ -255,7 +255,7 @@
             -o #{demPath}
             --id-property ID
             -p elevation
-            -s .00000000001")
+            -s 500")
     .done ->
       dem = JSON.parse(fs.readFileSync(demPath))
       dem.wgsBbox = bbox


minor bug fix

minor bug fix

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -682,7 +682,7 @@
   item     = $ '
'
   itemList = $ '
'
   loadDiv  = $ '
'
-  $.prototype.append.apply loadDiv [
+  $.prototype.append.apply loadDiv, [
     $ ''
     $ '
', text: utils.i18n 'shortening'
   ]


test(utils): add test for `min` function.

--- test/utils.coffee
+++ test/utils.coffee
@@ -8,4 +8,14 @@
     utils.min([-1, 0, 1]).should.be.equal(-1)
     utils.min([1, 0, -1]).should.be.equal(-1)
     utils.min([5, 0, -5, 10, -10]).should.be.equal(-10)
+    utils.min([1, undefined]).should.be.equal(1)
+    utils.min([undefined, 1]).should.be.equal(1)
+    utils.min([1, 'a']).should.be.equal(1)
+    utils.min(['a', 1]).should.be.equal(1)
+    utils.min([1, 1]).should.be.equal(1)
     done()
+
+describe 'min', ->
+  it 'should work', (done) ->
+    utils.min([1, 10, 100]).should.be.equal(1)
+    done()


 Merge branch 'master.kite.keys' into 'master'

Master.Kite.Keys

--- client/Account/views/kodingkeys.coffee
+++ client/Account/views/kodingkeys.coffee
@@ -4,10 +4,17 @@

     options.noItemFoundText = """

EXPERIMENTAL

-      You have no Koding keys. Koding keys are used to authenticate external
-      kites (Koding applications running on other machines). To get your keys listed here
-      you need to download & install the KD Tool (Mac OS X only):
+

+        You have no Koding keys. Koding keys are used to authenticate external
+        kites (Koding applications running on other machines). To get your keys listed here you need to download and install kd tool:
+

+
+      For OS X run the following command in Terminal:
       $ brew install "https://kd-tool.s3.amazonaws.com/kd.rb"
+
+      For Ubuntu/Debian install the following package:
+      https://kd-tool.s3.amazonaws.com/kd-latest-linux.deb
+
       and then register your machine with
       $ kd register


Fix missing ReactDOM call

Fix missing ReactDOM call

--- gems/plugins/sfu_course_form/app/coffeescripts/bundles/course_request_form.coffee
+++ gems/plugins/sfu_course_form/app/coffeescripts/bundles/course_request_form.coffee
@@ -259,7 +259,7 @@
   $(document).ready ->

-    React.render(
+    ReactDOM.render(
       React.createElement(
         SFUCopyrightComplianceNotice,
         { className: 'sfu-ic-wizard-box__message-text__copyright_compliance' }


kite: rename callback to cb

kite: rename callback to cb

--- client/app/MainApp/kite/newkite.coffee
+++ client/app/MainApp/kite/newkite.coffee
@@ -21,8 +21,8 @@
     @initBackoff options  if @autoReconnect

     @handlers =
-      log   : (options, callback)-> log options.withArgs
-      alert : (options, callback)-> alert options.withArgs
+      log   : (options, cb)-> log options.withArgs
+      alert : (options, cb)-> alert options.withArgs

     @proto = proto @handlers
     @proto.on 'request', (req)=>
@@ -48,19 +48,19 @@
     @ws.close()

   # Call a method on the connected Kite.
-  tell: (method, args, callback) ->
+  tell: (method, args, cb) ->
     options =
       authentication : @authentication
       withArgs       : args

     # Normally the request is made with the following statement:
-    #   @proto.request method, [options, callback]
+    #   @proto.request method, [options, cb]
     # However, we are making a single request/response call and
     # there is no point to hold the callback function because it
     # will never be called again. That's why we are deleting to
     # free the memory with the code below.

-    scrub = @proto.scrubber.scrub [options, callback]
+    scrub = @proto.scrubber.scrub [options, cb]

     @proto.emit 'request',
         method    : method
@@ -70,7 +70,7 @@

     # Remove the handler from proto when callback is called.
     # This is required to prevent unused callbacks to consume memory.
-    if callback
+    if cb
       # id of the last callback function
       id = Number(Object.keys(scrub.callbacks).last)
       # original handler
@@ -125,6 +125,6 @@
       else
         @emit "connectionFailed"

-  ready: (callback)->
-    return KD.utils.defer callback  if @readyState
-    @once 'ready', callback
+  ready: (cb)->
+    return KD.utils.defer cb  if @readyState
+    @once 'ready', cb


Fixed: sub list reordering

Fixed: sub list reordering

--- client/app/views/task_list_view.coffee
+++ client/app/views/task_list_view.coffee
@@ -181,9 +181,12 @@
     onMoveUp: (cid, toFocus = null) ->
         return if @isReindexing

+        # we want to move up to the previous model in the base collection
+        # so we need to know where is it then make get the new order
         currentModel = @views.findByModelCid(cid).model
-        previousIndex = @baseCollection.indexOf(currentModel) - 1
-        previous = @baseCollection.at previousIndex
+        previousIndexInSubCollection = @collection.indexOf(currentModel) - 1
+        previous = @collection.at previousIndexInSubCollection
+        previousIndex = @baseCollection.indexOf previous
         previousOfPrevious = @baseCollection.at(previousIndex - 1) or null

         if previousIndex >= 0
@@ -200,14 +203,17 @@
     onMoveDown: (cid) ->
         return if @isReindexing

+        # we want to move up to the next model in the base collection
+        # so we need to know where is it then make get the new order
         currentModel = @views.findByModelCid(cid).model
-        nextIndex = @baseCollection.indexOf(currentModel) + 1
-        nextModel = @baseCollection.at nextIndex
+        nextIndexInSubCollection = @collection.indexOf(currentModel) + 1
+        next = @collection.at nextIndexInSubCollection
+        nextIndex = @baseCollection.indexOf next
         nextOfNextModel = @baseCollection.at(nextIndex + 1) or null

         # if not last item of the collection
         if nextIndex isnt @baseCollection.length
-            newOrder = @baseCollection.getNewOrder nextModel, nextOfNextModel
+            newOrder = @baseCollection.getNewOrder next, nextOfNextModel
             {order, step} = newOrder
             currentModel.set 'order', order
             currentModel.save()


test: Better tests on cursor.limit

test: Better tests on cursor.limit

--- test/index.coffee
+++ test/index.coffee
@@ -652,9 +652,25 @@
       throw err if err
       MyDoc.insert _id: 'cursor', (err, doc) ->
         throw err if err
-        done()
-
-  it "should allow deeply chained cursor", ->
+        MyDoc.insert _id: 'cursor2', (err, doc) ->
+          throw err if err
+          done()
+
+  it "should work with a limit", (done) ->
+    cursor = MyDoc.find {}
+    cursor = cursor.limit 1
+    # .next() just returns the next document, not all docs
+    cursor.next (err, doc) ->
+      return done err if err
+      should.exist doc
+      doc.should.have.property '__schema'
+      doc.should.eql _id: 'cursor'
+      cursor.next (err, doc) ->
+        return done err if err
+        should.not.exist doc
+        done()
+
+  it "should allow deeply chained cursor", (done) ->
     cursor = MyDoc.find {}
     cursor.should.have.property 'document'
     cursor.should.have.property 'cursor'
@@ -669,8 +685,9 @@
     cursor.should.have.property 'cursor'
     cursor.next (err, doc) ->
       throw err if err
-      doc.should.not.be.null
+      should.exist doc
       doc.should.have.property '__schema'
+      done()

   it "should work with forEach", ->
     count = 0


Group match & replace tests

Group match & replace tests

--- test/write.coffee
+++ test/write.coffee
@@ -139,82 +139,86 @@
         return next err if err
         content.should.eql 'my friend\n# to\nyou coquin'
         next()
-
-  they 'with match as a string', (ssh, next) ->
-    mecano.write
-      ssh: ssh
-      match: 'lets try to replace that one'
-      content: 'here we are\nlets try to replace that one\nyou coquin'
-      replace: 'my friend'
-      destination: "#{scratch}/fromto.md"
-    , (err, written) ->
-      return next err if err
-      written.should.eql 1
-      misc.file.readFile ssh, "#{scratch}/fromto.md", 'utf8', (err, content) ->
-        return next err if err
-        content.should.eql 'here we are\nmy friend\nyou coquin'
-        next()
-
-  they 'with match as a regular expression', (ssh, next) ->
-    mecano.write
-      ssh: ssh
-      content: 'email=david(at)adaltas(dot)com\nusername=root'
-      match: /(username)=(.*)/
-      replace: '$1=david (was $2)'
-      destination: "#{scratch}/replace"
-    , (err, written) ->
-      return next err if err
-      written.should.eql 1
-      misc.file.readFile ssh, "#{scratch}/replace", 'utf8', (err, content) ->
-        return next err if err
-        content.should.eql 'email=david(at)adaltas(dot)com\nusername=david (was root)'
-        next()
-
-  they 'with match as a regular expression and multiple content', (ssh, next) ->
-    mecano.write
-      ssh: ssh
-      match: /(.*try) (.*)/
-      content: 'here we are\nlets try to replace that one\nyou coquin'
-      replace: ['my friend, $1']
-      destination: "#{scratch}/replace"
-    , (err, written) ->
-      return next err if err
-      written.should.eql 1
-      misc.file.readFile ssh, "#{scratch}/replace", 'utf8', (err, content) ->
-        return next err if err
-        content.should.eql 'here we are\nmy friend, lets try\nyou coquin'
-        next()
-
-  they 'with match with global and multilines', (ssh, next) ->
-    mecano.write
-      ssh: ssh
-      match: /^property=.*$/mg
-      content: '#A config file\n#property=30\nproperty=10\nproperty=20\n#End of Config'
-      replace: 'property=50'
-      destination: "#{scratch}/replace"
-    , (err, written) ->
-      return next err if err
-      written.should.eql 1
-      misc.file.readFile ssh, "#{scratch}/replace", 'utf8', (err, content) ->
-        return next err if err
-        content.should.eql '#A config file\n#property=30\nproperty=50\nproperty=50\n#End of Config'
-        next()
-
-  they 'will replace destination if source or content does not exists', (ssh, next) ->
-    mecano.write
-      ssh: ssh
-      content: 'This is\nsome content\nfor testing'
-      destination: "#{scratch}/a_file"
-    , (err, written) ->
-      return next err if err
-      mecano.write
-        ssh: ssh
-        match: /(.*content)/
-        replace: 'a text'
+
+  describe 'match & replace', ->
+
+    they 'with match as a string', (ssh, next) ->
+      mecano.write
+        ssh: ssh
+        match: 'lets try to replace that one'
+        content: 'here we are\nlets try to replace that one\nyou coquin'
+        replace: 'my friend'
+        destination: "#{scratch}/fromto.md"
+      , (err, written) ->
+        return next err if err
+        written.should.eql 1
+        misc.file.readFile ssh, "#{scratch}/fromto.md", 'utf8', (err, content) ->
+          return next err if err
+          content.should.eql 'here we are\nmy friend\nyou coquin'
+          next()
+
+    they 'with match as a regular expression', (ssh, next) ->
+      # With a match
+      mecano.write
+        ssh: ssh
+        content: 'email=david(at)adaltas(dot)com\nusername=root'
+        match: /(username)=(.*)/
+        replace: '$1=david (was $2)'
+        destination: "#{scratch}/replace"
+      , (err, written) ->
+        return next err if err
+        written.should.eql 1
+        # Without a match
+        mecano.write
+          ssh: ssh
+          match: /this wont work/
+          replace: '$1=david (was $2)'
+          destination: "#{scratch}/replace"
+        , (err, written) ->
+          return next err if err
+          written.should.eql 0
+          misc.file.readFile ssh, "#{scratch}/replace", 'utf8', (err, content) ->
+            return next err if err
+            content.should.eql 'email=david(at)adaltas(dot)com\nusername=david (was root)'
+            next()
+
+    they 'with match as a regular expression and multiple content', (ssh, next) ->
+      mecano.write
+        ssh: ssh
+        match: /(.*try) (.*)/
+        content: 'here we are\nlets try to replace that one\nyou coquin'
+        replace: ['my friend, $1']
+        destination: "#{scratch}/replace"
+      , (err, written) ->
+        return next err if err
+        written.should.eql 1
+        misc.file.readFile ssh, "#{scratch}/replace", 'utf8', (err, content) ->
+          return next err if err
+          content.should.eql 'here we are\nmy friend, lets try\nyou coquin'
+          next()
+
+    they 'with match with global and multilines', (ssh, next) ->
+      mecano.write
+        ssh: ssh
+        match: /^property=.*$/mg
+        content: '#A config file\n#property=30\nproperty=10\nproperty=20\n#End of Config'
+        replace: 'property=50'
+        destination: "#{scratch}/replace"
+      , (err, written) ->
+        return next err if err
+        written.should.eql 1
+        misc.file.readFile ssh, "#{scratch}/replace", 'utf8', (err, content) ->
+          return next err if err
+          content.should.eql '#A config file\n#property=30\nproperty=50\nproperty=50\n#End of Config'
+          next()
+
+    they 'will replace destination if source or content does not exists', (ssh, next) ->
+      mecano.write
+        ssh: ssh
+        content: 'This is\nsome content\nfor testing'
         destination: "#{scratch}/a_file"
       , (err, written) ->
         return next err if err
-        written.should.eql 1
         mecano.write
           ssh: ssh
           match: /(.*content)/
@@ -222,21 +226,19 @@
           destination: "#{scratch}/a_file"
         , (err, written) ->
           return next err if err
-          written.should.eql 0
-          misc.file.readFile ssh, "#{scratch}/a_file", 'utf8', (err, content) ->
-            return next err if err
-            content.should.eql 'This is\na text\nfor testing'
-            next()
-
-  they 'can not defined source and content', (ssh, next) ->
-    mecano.write
-      ssh: ssh
-      source: 'abc'
-      content: 'abc'
-      destination: 'abc'
-    , (err) ->
-      err.message.should.eql 'Define either source or content'
-      next()
+          written.should.eql 1
+          mecano.write
+            ssh: ssh
+            match: /(.*content)/
+            replace: 'a text'
+            destination: "#{scratch}/a_file"
+          , (err, written) ->
+            return next err if err
+            written.should.eql 0
+            misc.file.readFile ssh, "#{scratch}/a_file", 'utf8', (err, content) ->
+              return next err if err
+              content.should.eql 'This is\na text\nfor testing'
+              next()

   describe 'append', ->

@@ -576,6 +578,16 @@

   describe 'error', ->

+    they 'can not defined source and content', (ssh, next) ->
+      mecano.write
+        ssh: ssh
+        source: 'abc'
+        content: 'abc'
+        destination: 'abc'
+      , (err) ->
+        err.message.should.eql 'Define either source or content'
+        next()
+
     they 'if source doesn\'t exists', (ssh, next) ->
       mecano.write
         ssh: ssh


Fixed test for release write lock

Fixed test for release write lock

--- test/index.coffee
+++ test/index.coffee
@@ -273,7 +273,7 @@
             assert ld?
             assert id.equals ld.files_id
             assert ld.expires instanceof Date
-            assert ld.expires > lock1.timeCreated
+            assert ld.expires.getTime() is lock1.timeCreated.getTime()
             assert.equal ld.read_locks, 0
             assert.equal ld.write_lock, false
             assert.equal ld.write_req, false


reformat selector spec

reformat selector spec

--- test/tourney/selector.spec.coffee
+++ test/tourney/selector.spec.coffee
@@ -11,15 +11,19 @@
   describe 'given number of teams less than 9', ->
     it 'returns object with type "round robin"', ->
       expect(selector(2).type).to.eq 'round robin'
+
     it 'returns object containing number of games', ->
       expect(selector(2).games).to.eq 1
+
     it 'returns object containing a schedule', ->
       expect(selector(2).schedule).to.eql [[1,2]]

   describe 'given number of teams less than 9', ->
     it 'returns object with type "round robin"', ->
       expect(selector(9).type).to.eq 'pods'
+
     it 'returns object containing number of games', ->
       expect(selector(9).games).to.eq 24
+
     it 'returns object containing a schedule', ->
       expect(selector(9).schedule).to.be.ok


Reload grid if query has changed

Reload grid if query has changed

--- client/lanes/components/grid/Grid.cjsx
+++ client/lanes/components/grid/Grid.cjsx
@@ -17,6 +17,9 @@

     setDataState: (newstate) ->
         @refs.grid?.reload() if newstate.query
+
+    componentWillReceiveProps: (nextProps) ->
+        @refs.grid.reload() if nextProps.query

     propTypes:
         query:  React.PropTypes.instanceOf(Lanes.Models.Query).isRequired


If we have multiple results take the first

--- src/ical-topic.coffee
+++ src/ical-topic.coffee
@@ -45,7 +45,7 @@
       name: user.real_name || user.name

     results = fuzzy.filter cleanName.trim(), users, extract: (user) -> user.name
-    if results?.length is 1
+    if results?.length >= 1
       return "@#{results[0].original.username}"
     else
       return name


bringing back half assignments, but just for 'a = or b' and 'a = and b'

--- lexer.coffee
+++ lexer.coffee
@@ -88,6 +88,8 @@
     tag = id.toUpperCase() if include(JS_KEYWORDS, id) or (not forcedIdentifier and include(COFFEE_KEYWORDS, id))
     tag = 'LEADING_WHEN'   if tag is 'WHEN' and include LINE_BREAK, @tag()
     tag = 'ALL'            if id is 'all' and @tag() is 'FOR'
+    if @tag() is '=' and tag in ['AND', 'OR']
+      return @tag 1, CONVERSIONS[id] + '='
     if include(JS_FORBIDDEN, id)
       if forcedIdentifier
         tag = 'STRING'


Only fire open description event once

Only fire open description event once

--- framer/Components/ShareComponent.coffee
+++ framer/Components/ShareComponent.coffee
@@ -323,6 +323,8 @@
 			@_calculateAvailableSpace()
 			@_enableUserSelect(@description)

+
+
 		# Truncate if description is too long
 		if @shareInfo.description.length > @options.maxDescriptionLength

@@ -340,7 +342,7 @@
 			@description.height = @descriptionTruncatedSize.height
 			@description.html = parseDescription(@options.shortDescription)

-			@description.onTapEnd -> showFullDescription()
+			@description.once(Events.TapEnd, showFullDescription)
 			@_showPointer(@description)

 		else


bubbles: anim stops when clicking on a year or a slider

--- frontend/bubbles/bubbles-aiddata.coffee
+++ frontend/bubbles/bubbles-aiddata.coffee
@@ -40,8 +40,9 @@
       attrs: [1947..2011]
       explain: 'In #attr# there were #magnitude# ... from #origin# in #dest#'

+
 state = null
-
+stopAnimation = null

@@ -130,6 +131,7 @@
     .show()

   $("g.x.axis text").click ->
+    stopAnimation()
     $("#yearSlider").slider('value', state.magnAttrs().indexOf( + $(this).text()))

   $("#play").hover(
@@ -294,7 +296,7 @@

     state = initFlowData(conf)
     state.selMagnAttrGrp = "aid"
-    state.selAttrIndex =  state.magnAttrs().length - 3  # state.magnAttrs().length - 1
+    state.selAttrIndex =  state.magnAttrs().length - 7  # state.magnAttrs().length - 1
     state.totalsMax = calcMaxTotalMagnitudes(data, conf)

@@ -374,16 +376,17 @@
     updateNodeSizes()

     placeNodesWithoutCoords = (nodes) ->
+      squeezeFactor = 0.75
       totalw = 0
       for n in nodes
-        if not n.x? or not n.y? then totalw += 2 * n.maxr/2
+        if not n.x? or not n.y? then totalw += 2 * n.maxr * squeezeFactor
       x = 0
       for n in nodes
         if not n.x? or not n.y?
-          n.x = x + n.maxr/2 + (bubblesChartWidth - totalw)/2
+          n.x = x + n.maxr * squeezeFactor + (bubblesChartWidth - totalw)/2
           n.y = bubblesChartHeight - nodesWithoutCoordsMarginBottom
           n.gravity = {x: n.x, y: n.y}
-          x += 2 * n.maxr/2
+          x += 2 * n.maxr * squeezeFactor

     placeNodesWithoutCoords(nodes)

@@ -504,7 +507,7 @@
           recipient = d.target.data[conf.nodeLabelAttr]
           donor = d.source.data[conf.nodeLabelAttr]

-          flowLabel = shortenLabel(donor, 25) + " -> " + shortenLabel(recipient, 25)
+          flowLabel = shortenLabel(donor, 20) + " -> " + shortenLabel(recipient, 20)
           createTimeSeries(d3.select(tseries), data, flowLabel)

@@ -553,7 +556,7 @@
       if not TimeSeries.exists("node", i)
         tseries = TimeSeries.create("node", i)
         nodeLabel = d3.select(node).data()[0][conf.nodeLabelAttr]
-        createTimeSeries(d3.select(tseries), data, shortenLabel(nodeLabel, 50))
+        createTimeSeries(d3.select(tseries), data, shortenLabel(nodeLabel, 40))

     bubble = svg.selectAll("g.bubble")
@@ -682,8 +685,9 @@
         min: 0
         max: state.magnAttrs().length - 1
         value: state.selAttrIndex
-        slide: (e, ui) -> updateYear(e, ui, false)
+        slide: (e, ui) -> stopAnimation(); updateYear(e, ui, false)
         change: (e, ui) -> updateYear(e, ui, false)
+
     $("#yearSlider").focus()

     #$("#playButton").button()
@@ -700,7 +704,7 @@

     timer = undefined

-    stop = ->
+    stopAnimation = ->
       clearInterval(timer)
       timer = undefined
       $("#play span")
@@ -710,7 +714,7 @@

     $("#play").click ->
       if timer
-        stop()
+        stopAnimation()
       else
         $("#play span")
           .removeClass("ui-icon-play")
@@ -726,7 +730,7 @@

         timer = setInterval(->
           if (state.selAttrIndex >= state.magnAttrs().length - 1)
-            stop()
+            stopAnimation()
           else
             state.selAttrIndex++
             $("#yearSlider").slider('value', state.selAttrIndex)


remove a console.log

remove a console.log

--- client/Main/kite/kodingkite.coffee
+++ client/Main/kite/kodingkite.coffee
@@ -12,7 +12,6 @@
     @emit 'ready'

   tell: (rpcMethod, params, callback) ->
-    console.log rpcMethod, params
     @ready().then => @transport.tell rpcMethod, [params], callback

   @createMethod = (ctx, { method, rpcMethod }) ->


Make sure reactRoot is set

Make sure reactRoot is set

--- client/lanes/react/Viewport.coffee
+++ client/lanes/react/Viewport.coffee
@@ -63,4 +63,4 @@
         root = React.createElement(Lanes.React.Root, {viewport: @},
             React.createElement(component)
         )
-        Lanes.Vendor.ReactDOM.render(root, @lanes)
+        @reactRoot = Lanes.Vendor.ReactDOM.render(root, @lanes)


testrunner: call routehandler in index file

--- client/testrunner/lib/index.coffee
+++ client/testrunner/lib/index.coffee
@@ -1,2 +1 @@
-# empty file for bant to build.
-module.exports = class TestRunnerController
+require('./routehandler')()


Fixes #4309 - TypeError: params[(attribute.name + "_is_display_warning")] is not a function

--- app/assets/javascripts/app/controllers/_ui_element/select.coffee
+++ app/assets/javascripts/app/controllers/_ui_element/select.coffee
@@ -61,7 +61,7 @@
     return if !selectedVal
     return if !params

-    params[attribute.name + '_is_display_warning'](selectedVal)
+    params[attribute.name + '_is_display_warning']?(selectedVal)

   @toggleDisplayWarn: (warn_visible, attribute, item) ->
     if !warn_visible


When query changed (typing or autocomplete), update URL. When page loads, perform query search.

--- app/assets/javascripts/darkswarm/directives/map_search.js.coffee
+++ app/assets/javascripts/darkswarm/directives/map_search.js.coffee
@@ -1,23 +1,33 @@
-Darkswarm.directive 'mapSearch', ($timeout) ->
+Darkswarm.directive 'mapSearch', ($timeout, Search) ->
   # Install a basic search field in a map
   restrict: 'E'
-  require: '^googleMap'
+  require: ['^googleMap', 'ngModel']
   replace: true
-  template: '<input id="pac-input" placeholder="' + t('location_placeholder') + '"></input>'
+  template: '<input id="pac-input" ng-model="query" placeholder="' + t('location_placeholder') + '"></input>'
   scope: {}
-  link: (scope, elem, attrs, ctrl) ->
+
+  controller: ($scope) ->
+    $scope.query = Search.search()
+
+    $scope.$watch 'query', (query) ->
+      Search.search query
+
+
+  link: (scope, elem, attrs, ctrls) ->
+    [ctrl, model] = ctrls
+    scope.input = document.getElementById("pac-input")
+
     $timeout =>
       map = ctrl.getMap()

       searchBox = scope.createSearchBox map
       scope.bindSearchResponse map, searchBox
       scope.biasResults map, searchBox
-
+      scope.performUrlSearch map

     scope.createSearchBox = (map) ->
-      input = document.getElementById("pac-input")
-      map.controls[google.maps.ControlPosition.TOP_LEFT].push input
-      return new google.maps.places.SearchBox(input)
+      map.controls[google.maps.ControlPosition.TOP_LEFT].push scope.input
+      return new google.maps.places.SearchBox(scope.input)

     scope.bindSearchResponse = (map, searchBox) ->
       google.maps.event.addListener searchBox, "places_changed", =>
@@ -30,6 +40,12 @@
         scope.$apply ->
           model.$setViewValue elem.val()

+    # When the map loads, and we have a search from ?query, perform that search
+    scope.performUrlSearch = (map) ->
+      google.maps.event.addListener map, "tilesloaded", =>
+        google.maps.event.trigger(scope.input, 'focus');
+        google.maps.event.trigger(scope.input, 'keydown', {keyCode: 13});
+
     # Bias the SearchBox results towards places that are within the bounds of the
     # current map's viewport.
     scope.biasResults = (map, searchBox) ->


[improvement] Milestones: rounded complexity up rather than showing an arbitrary number of decimal places (1m)

--- app/assets/javascripts/houston-scheduler/models/milestone.coffee
+++ app/assets/javascripts/houston-scheduler/models/milestone.coffee
@@ -15,6 +15,7 @@
       else
         indeterminate = true

+    complexity = Math.ceil(complexity)
     complexity += '+' if indeterminate
     complexity


no message

no message

--- app/assets/javascripts/sprangular/controllers/productList.coffee
+++ app/assets/javascripts/sprangular/controllers/productList.coffee
@@ -1,6 +1,7 @@
 Sprangular.controller 'ProductListCtrl', ($scope, $routeParams, Status, taxon, products, Catalog, Cart) ->
   if taxon
     Status.pageTitle = taxon.pretty_name
+    $scope.taxon = taxon
   else
     Status.setPageTitle('nav.products')


Override theme's highlight line; Don't multilines disappearing

[Closes #18]

--- lib/highlight-line-view.coffee
+++ lib/highlight-line-view.coffee
@@ -71,10 +71,7 @@
       underlineColor: "255, 165, 0"}
     @defaultOpacity = 50

-    @subscribe @editorView, 'cursor:moved', @updateSelectedLine
-    @subscribe @editorView, 'selection:changed', @updateSelectedLine
-    @subscribe @editorView.getPane(), 'pane:active-item-changed',
-      @updateSelectedLine
+    @subscribe @editorView, 'editor:display-updated', @updateSelectedLine
     atom.workspaceView.on 'pane:item-removed', @destroy

     @updateUnderlineStyle()
@@ -114,11 +111,14 @@
     @showHighlight()

   resetBackground: ->
-    $('.line').css('background-color', '')
-              .css('border-top','')
+    $('.line').css('border-top','')
               .css('border-bottom','')
               .css('margin-bottom','')
               .css('margin-top','')
+    $('.line').each (index, line) ->
+      if $(line).attr('style')?.indexOf('background') isnt -1
+        top = $(line).css('top')
+        $(line).attr 'style', "position: absolute; top: #{top};" if top?

   makeLineStyleAttr: ->
     styleAttr = ''
@@ -130,7 +130,7 @@
       if show
         bgColor = @wantedColor('backgroundRgbColor')
         bgRgba = "rgba(#{bgColor}, #{@wantedOpacity()})"
-        styleAttr += "background-color: #{bgRgba};"
+        styleAttr += "background: #{bgRgba} !important;"
     if atom.config.get('highlight-line.enableUnderline') and underlineStyleInUse
       ulColor = @wantedColor('underlineRgbColor')
       ulRgba = "rgba(#{ulColor},1)"
@@ -163,9 +163,8 @@
         if selection = @editorView.editor.getSelection()
           if selection.isSingleScreenLine()
             if @editorView.constructor.name is "ReactEditorView"
-              pos = $(lineElement).css("position")
               topPX = $(lineElement).css("top")
-              styleAttr += "position: #{pos}; top: #{topPX}; width: 100%"
+              styleAttr += "position: absolute; top: #{topPX}; width: 100%;" if topPX?

             $(lineElement).attr 'style', styleAttr
           else if atom.config.get('highlight-line.enableSelectionBorder')
@@ -180,12 +179,10 @@
               startLine = @findLineElementForRow(@editorView, start)
               endLine = @findLineElementForRow(@editorView, end)
               if @editorView.constructor.name is "ReactEditorView"
-                pos = $(startLine).css("position")
                 topPX = $(startLine).css("top")
-                selectionStyleAttrs[0] += "position: #{pos}; top: #{topPX}; width: 100%"
-                pos = $(endLine).css("position")
+                selectionStyleAttrs[0] += "position: absolute; top: #{topPX}; width: 100%;" if topPX?
                 topPX = $(endLine).css("top")
-                selectionStyleAttrs[1] += "position: #{pos}; top: #{topPX}; width: 100%"
+                selectionStyleAttrs[1] += "position: absolute; top: #{topPX}; width: 100%;" if topPX?

               $(startLine).attr 'style', selectionStyleAttrs[0]
               $(endLine).attr 'style', selectionStyleAttrs[1]


update tests

update tests

--- components/contact/test/inquiry.coffee
+++ components/contact/test/inquiry.coffee
@@ -27,6 +27,7 @@
       sinon.stub @analytics.track, 'funnel'
       sinon.stub Inquiry.prototype, 'open'
       sinon.stub Inquiry.prototype, 'updatePosition'
+      sinon.stub Inquiry.prototype, 'isLoaded'
       sinon.stub(Inquiry.prototype, 'displayAfterInquiryFlow').returns false
       @view = new Inquiry artwork: @artwork, partner: @partner, el: $('body')
       @view.renderTemplates()
@@ -39,8 +40,7 @@
   describe '#renderTemplates', ->
     it 'has the correct header', ->
       html = @view.$el.html()
-      html.should.containEql 'Foo Bar, an Artsy Specialist, is available'
-      html.should.containEql 'img alt="Foo Bar"'
+      html.should.containEql 'Ask a Specialist'

   describe '#submit', ->
     describe 'Logged out', ->


Disabled tab sorting action when user started drag from close icon.

--- components/tabs/KDTabHandleView.coffee
+++ components/tabs/KDTabHandleView.coffee
@@ -1,5 +1,7 @@
 class KDTabHandleView extends KDView
-  constructor:(options = {})->
+
+  constructor: (options = {}, data) ->
+
     options.hidden   ?= no        # yes or no
     options.title    ?= "Title"   # a String
     options.pane     ?= null      # a KDTabPaneView instance
@@ -11,12 +13,14 @@
       options.draggable  = axis: "x"
       @dragStartPosX = null

-    super options
+    super options, data

     @on "DragStarted", (event, dragState) =>
+      @startedDragFromCloseElement = $(event.target).hasClass "close-tab"
       @handleDragStart event, dragState

     @on "DragInAction", (x, y) =>
+      @dragIsAllowed = no if @startedDragFromCloseElement
       @handleDragInAction x, y

     @on "DragFinished", (event) =>


Verbs are now lowercase

Verbs are now lowercase

--- lib/rest/rest_interface.coffee
+++ lib/rest/rest_interface.coffee
@@ -9,7 +9,7 @@
       verbItem = RestItemBuilder(null, null, verb: verb, config: config[verb] ? {})
       verbItem._attributes.ajaxAdapter = config.ajaxAdapter
       verbItem._attributes.url = config.url
-      @[verb] = verbItem
+      @[verb.toLowerCase()] = verbItem
       if config[verb]?.aliases?
         for alias in config[verb].aliases
           @[alias] = verbItem


Fix PickerOptionView

Fix PickerOptionView

--- assets/javascripts/views/002_permissions_fields_picker.js.coffee
+++ assets/javascripts/views/002_permissions_fields_picker.js.coffee
@@ -139,10 +139,10 @@
     @getOption()?.value

   getText: =>
-    @getOption().name || TentStatus.Helpers.minimalEntity(@getOption().value)
+    @getOption()?.name || TentStatus.Helpers.minimalEntity(@getOption().value)

   isGroup: =>
-    !!@getOption().group
+    !!@getOption()?.group

   destroy: =>
     Marbles.DOM.removeNode(@el) if @el


GameObject.Effect

GameObject.Effect

--- src/game_object_effect.coffee
+++ src/game_object_effect.coffee
@@ -9,7 +9,7 @@
       player.fadeOut 2

       # Fade out and then destroy
-      player.fadeOut, 0.25, "destroy"
+      player.fadeOut, 0.25,

   @name fadeOut
   @methodOf GameObject#


Fixed: only one test was run

Fixed: only one test was run

--- tests/main.coffee
+++ tests/main.coffee
@@ -23,7 +23,7 @@

     describe "Reset Database", ->

-        describe.only "When resetDatabase is called with no option", ->
+        describe "When resetDatabase is called with no option", ->

             before (done) ->
                 @timeout 5000


Updated docpad for bal-util modules change

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -359,11 +359,11 @@

 		# Node Path
 		# The location of our node executable
-		nodePath: if /node$/.test(process.execPath) then process.execPath else 'node'
+		nodePath: null

 		# Git Path
 		# The location of our git executable
-		gitPath: if /^win/.test(process.platform) then 'git.cmd' else 'git'
+		gitPath: null

 		# Template Data
 		# What data would you like to expose to your templates
@@ -384,6 +384,7 @@
 		# Events
 		# A hash of event handlers
 		events: null  # {}
+

 	# =================================
 	# Initialization Functions
@@ -665,6 +666,18 @@
 		# Chain
 		@

+	# Init Git Repo
+	# next(err,results)
+	initGitRepo: (opts) ->
+		# Prepare
+		opts.gitPath ?= @config.gitPath
+
+		# Forward
+		balUtil.initGitRepo(opts)
+
+		# Chain
+		@
+
 	# Init Node Modules
 	# next(err,results)
 	initNodeModules: (opts={}) ->
@@ -1285,7 +1298,6 @@

 		# Configure
 		repoConfig =
-			gitPath: docpad.config.gitPath
 			path: destinationPath
 			url: skeletonModel.get('repo')
 			branch: skeletonModel.get('branch')
@@ -1313,7 +1325,7 @@
 			return docpad.error(err)  if err

 			# Initalize the git repository
-			balUtil.initGitRepo(repoConfig)
+			docpad.initGitRepo(repoConfig)

 		# Chain
 		@
@@ -1609,7 +1621,7 @@
 		websiteConfig = {}

 		# Async
-		tasks = new balUtil.Group (err) =>
+		preTasks = new balUtil.Group (err) =>
 			return next(err)  if err

 			# Merge Configuration (not deep!)
@@ -1663,16 +1675,20 @@
 			# Async
 			postTasks = new balUtil.Group (err) =>
 				return next(err)
-			postTasks.total = 2

 			# Load collections
-			@loadCollections(postTasks.completer())
+			postTasks.push (complete) ->
+				docpad.loadCollections(complete)

 			# Initialize
-			@loadPlugins(postTasks.completer())
+			postTasks.push (complete) ->
+				docpad.loadPlugins(complete)
+
+			# Fire post tasks
+			postTasks.async()

 		# Load DocPad Configuration
-		tasks.push (complete) =>
+		preTasks.push (complete) =>
 			@loadConfigPath @packagePath, (err,data) ->
 				return complete(err)  if err
 				data or= {}
@@ -1685,7 +1701,7 @@
 				complete()

 		# Load Website Package Configuration
-		tasks.push (complete) =>
+		preTasks.push (complete) =>
 			rootPath = instanceConfig.rootPath or @config.rootPath
 			websitePackagePath = pathUtil.resolve(rootPath, instanceConfig.packagePath or @config.packagePath)
 			@loadConfigPath websitePackagePath, (err,data) ->
@@ -1699,7 +1715,7 @@
 				complete()

 		# Load Website Configuration
-		tasks.push (complete) =>
+		preTasks.push (complete) =>
 			rootPath = instanceConfig.rootPath or websitePackageConfig.rootPath or @config.rootPath
 			configPaths = instanceConfig.configPaths or websitePackageConfig.configPaths or @config.configPaths
 			for configPath, index in configPaths
@@ -1714,8 +1730,24 @@
 				# Compelte the loading
 				complete()

+		# Get the git path
+		unless @config.gitPath?
+			preTasks.push (complete) ->
+				balUtil.getGitPath (err,gitPath) ->
+					return docpad.error(err)  if err
+					docpad.config.gitPath = gitPath
+					complete()
+
+		# Get the node path
+		unless @config.nodePath?
+			preTasks.push (complete) ->
+				balUtil.getNodePath (err,nodePath) ->
+					return docpad.error(err)  if err
+					docpad.config.nodePath = nodePath
+					complete()
+
 		# Run the load tasks synchronously
-		tasks.sync()
+		preTasks.sync()

 		# Chain
 		@


home/billing: render container as a section

--- client/home/lib/billing/hometeambillingpaymentinformation.coffee
+++ client/home/lib/billing/hometeambillingpaymentinformation.coffee
@@ -3,7 +3,7 @@
 ReactView = require 'app/react/reactview'
 PaymentInformation = require './components/paymentinformation'

-module.exports = class HomeTeamBillingForm extends ReactView
+module.exports = class HomeTeamBillingPaymentInformation extends ReactView

   constructor: (options = {}, data) ->

@@ -12,5 +12,5 @@
     super options, data

-  renderReact: -> <PaymentInformation />
+  renderReact: -> <PaymentInformation.Container />


landing: teams: add solo login link below team selector

--- client/landing/site.landing/coffee/teams/teamselectorview.coffee
+++ client/landing/site.landing/coffee/teams/teamselectorview.coffee
@@ -88,6 +88,7 @@

Trying to create a team? Click here to get started.

       {{> @previousTeams}}
+
Looking for Koding Solo?

       Acceptable user policyCopyright/DMCA guidelinesTerms of servicePrivacy policy


Improve label regex and supported channels message

--- src/jira.coffee
+++ src/jira.coffee
@@ -135,7 +135,7 @@
     .then (user) ->
       reporter = user[0] if user and user.length is 1
       quoteRegex = /`{1,3}([^]*?)`{1,3}/
-      labelsRegex = /\s+#\S+/g
+      labelsRegex = /(?:\s+|^)#\S+/g
       priorityRegex = eval "/\\s+!(#{(priorities.map (priority) -> priority.name).join '|'})\\b/i" if priorities
       mentionRegex = eval "/(?:@([\\w._]*))/i"

@@ -361,12 +361,12 @@
     project = projects[room]
     type = types[command]

-    if not project
+    unless project
       channels = []
       for team, key of projects
         channel = robot.adapter.client.getChannelGroupOrDMByName team
         channels.push " <\##{channel.id}|#{channel.name}>" if channel
-      return msg.reply "#{type} must be submitted in one of the following project channels:" + channels
+      return msg.reply "#{type} must be submitted in one of the following project channels: #{channels}"

     report project, type, msg


update

update

--- keymaps/path-to-module.cson
+++ keymaps/path-to-module.cson
@@ -7,6 +7,6 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/advanced/keymaps
-'.workspace':
+'.atom-workspace':
   'ctrl-alt-p': 'path-to-module:file2module'
   'ctrl-alt-m': 'path-to-module:module2file'


add test for kdu pct: size

add test for kdu pct: size

--- tests/extractor-jp2-kakadu.test.coffee
+++ tests/extractor-jp2-kakadu.test.coffee
@@ -17,7 +17,7 @@
 Tests start here:
 ###

-test 'extract image with full region and w, size', (assert) ->
+test 'KDU: extract image with full region and w, size', (assert) ->
   # assert.plan(2)
   data = fixtures()
   tester = (output_image) ->
@@ -25,7 +25,7 @@
   extractor = new Extractor data.options, tester
   extractor.extract()

-test 'extract image with xywh region', (assert) ->
+test 'KDU: extract image with xywh region', (assert) ->
   # assert.plan(2)
   data = fixtures()
   data.options.params['region'] = data.region_xywh
@@ -34,7 +34,7 @@
   extractor = new Extractor data.options, tester
   extractor.extract()

-test 'extract image with small region and full size', (assert) ->
+test 'KDU: extract image with small region and full size', (assert) ->
   data = fixtures()
   data.options.params['region'] = data.region_xywh
   data.options.params['size'] = 'full'
@@ -43,7 +43,16 @@
   extractor = new Extractor data.options, tester
   extractor.extract()

-test 'extract image with small region and w,h size', (assert) ->
+test 'KDU: extract image with pct: size', (assert) ->
+  data = fixtures()
+  data.options.params['region'] = data.region_xywh
+  data.options.params['size'] = {pct: 50}
+  tester = (output_image) ->
+    test_assertions_and_cleanup(assert, output_image, data.params.format)
+  extractor = new Extractor data.options, tester
+  extractor.extract()
+
+test 'KDU: extract image with small region and w,h size', (assert) ->
   data = fixtures()
   data.options.params['region'] = data.region_xywh
   data.options.params['size'] = {w: 200, h: 200}
@@ -52,7 +61,7 @@
   extractor = new Extractor data.options, tester
   extractor.extract()

-test 'extract image with small region and ,h size', (assert) ->
+test 'KDU: extract image with small region and ,h size', (assert) ->
   data = fixtures()
   data.options.params['region'] = data.region_xywh
   data.options.params['size'] = {w: undefined, h: 200}
@@ -61,7 +70,7 @@
   extractor = new Extractor data.options, tester
   extractor.extract()

-test 'extract full image with small size but rotated 90 degrees', (assert) ->
+test 'KDU: extract full image with small size but rotated 90 degrees', (assert) ->
   data = fixtures()
   data.options.params['rotation'] = {degrees: 90, mirror: false}
   tester = (output_image) ->
@@ -69,7 +78,7 @@
   extractor = new Extractor data.options, tester
   extractor.extract()

-test 'extract and convert to png', (assert) ->
+test 'KDU: extract and convert to png', (assert) ->
   data = fixtures()
   data.options.params['format'] = 'png'
   tester = (output_image) ->


removed the dependency on filesystem

removed the dependency on filesystem

--- gulp-espruino/src/gulp-espruino.coffee
+++ gulp-espruino/src/gulp-espruino.coffee
@@ -1,5 +1,4 @@
 SerialPort = require('serialport').SerialPort
-fs = require 'fs'
 through = require 'through2'
 extend = require 'extend'
 Q = require 'q'


Return constructed glyph renderer from glyph functions

--- bokehjs/src/coffee/api/plotting.coffee
+++ bokehjs/src/coffee/api/plotting.coffee
@@ -231,8 +231,10 @@
       @_update_legend(legend, glyph_renderer)

     @add_renderers(glyph_renderer)
-
-  _marker: (cls, args) -> @_glyph(cls, "x,y", args)
+    return glyph_renderer
+
+  _marker: (cls, args) ->
+    return @_glyph(cls, "x,y", args)

   _get_range: (range) ->
     if not range?


Add use rules to the js-compiler

Add use rules to the js-compiler

--- src/js/compilers/js/main.coffee
+++ src/js/compilers/js/main.coffee
@@ -294,8 +294,8 @@
 		if rule.outTerrainItemName?
 			push "setTerrainItem(position, #{resolveItem rule.outTerrainItemName})"

-		if rule.outInventoryItems?
-			rule.outInventoryItems.forEach ({ itemName, quantity }) ->
+		if rule.give?
+			rule.give.forEach ({ itemName, quantity }) ->
 				push "addInventoryItem(#{resolveItem itemName}, #{quantity})"
 				return

@@ -322,6 +322,67 @@
 		ruleLines = makeList()
 		rules.forEach (rule) ->
 			ruleLines.push compileEnterRule rule
+			return
+		push ruleLines.join ' else '
+
+		push '}'
+
+		join '\n'
+
+
+	compileUseRule = (rule) ->
+		{ push, join } = makeList()
+
+		terrainItemCondition = if isSetReference rule.inTerrainItemName
+			"sets['#{rule.inTerrainItemName}'].has(terrainItem)"
+		else
+			"terrainItem === '#{rule.inTerrainItemName}'"
+
+		inventoryItemCondition = if isSetReference rule.inInventoryItemName
+			"sets['#{rule.inInventoryItemName}'].has(inventoryItem.name)"
+		else
+			"inventoryItem.name === '#{rule.inInventoryItemName}'"
+
+		push "if (#{terrainItemCondition} && #{inventoryItemCondition} && inventoryItem.count > 0) {"
+
+		if rule.consume
+			push "consumeInventoryItem()"
+
+		if rule.outTerrainItemName?
+			push "setTerrainItem(forwardPosition, #{resolveItem rule.outTerrainItemName})"
+
+		if rule.give?
+			rule.give.forEach ({ itemName, quantity }) ->
+				push "addInventoryItem(#{resolveItem itemName}, #{quantity})"
+				return
+
+		if rule.healthDelta? and rule.healthDelta != 0
+			push "state.player.health += #{rule.healthDelta}"
+
+		if rule.teleport?
+			push "state.player.position.level = '#{rule.teleport.levelName}'"
+			push "state.player.position.x = #{rule.teleport.x}"
+			push "state.player.position.y = #{rule.teleport.y}"
+
+		push "}"
+
+		join '\n'
+
+
+	compileUseRules = (rules) ->
+		{ push, join } = makeList()
+
+		push 'function applyUseRules (position) {'
+
+		push 'const forwardPosition = stepForward(position)'
+
+		push 'const terrainItem = getTerrainItem(forwardPosition)'
+
+		push 'const inventoryItem = getInventoryItem()'
+
+		ruleLines = makeList()
+		rules.forEach (rule) ->
+			ruleLines.push compileUseRule rule
 			return
 		push ruleLines.join ' else '

@@ -361,6 +422,7 @@
 						x: #{params['start_location'][0]},
 						y: #{params['start_location'][1]},
 						level: "#{params['start_location'][2]}",
+						direction: 2,
 					},
 					inventory: {
 						index: -1,
@@ -377,6 +439,20 @@
 					position.y >= 0 && position.y < levelSize.y
 			}

+			function stepForward (position) {
+				const increment = [
+					{ x:  0, y: -1 },
+					{ x: -1, y:  0 },
+					{ x:  0, y:  1 },
+					{ x:  1, y:  0 },
+				][position.direction]
+
+				return {
+					x: position.x + increment.x,
+					y: position.y + increment.y,
+				}
+			}
+
 			function getTerrainItem (position) {
 				const { player, levels } = state
 				const level = state.levels[player.position.level]
@@ -411,6 +487,18 @@
 					entry.count += count
 				} else {
 					items.push({ name, count })
+				}
+
+				if (index === -1) {
+					state.player.inventory.index = 0
+				}
+			}
+
+			function consumeInventoryItem () {
+				const { items, index } = state.player.inventory
+
+				if (items[index] != null && items[index].count > 0) {
+					items[index].count--
 				}
 			}

@@ -439,6 +527,8 @@
 			#{compileLeaveRules spec.leaveRules}

 			#{compileEnterRules spec.enterRules}
+
+			#{compileUseRules spec.useRules}
 		"""

 	{


Grab back focus prevents focusing embeds."

Partially addresses #2303.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -63,7 +63,7 @@
           @exit()

   grabBackFocus: (element) ->
-    return @continueBubbling unless DomUtils.isEditable element
+    return @continueBubbling unless DomUtils.isFocusable element
     element.blur()
     @suppressEvent


gradual upgrade of the access system (part II)

--- library/exposure/access.coffee
+++ library/exposure/access.coffee
@@ -64,36 +64,61 @@
     # counterparts in the destination, once the composition process
     # takes place. See the `Archetype::composition` hook definition
     # for more information. Keys are names, values can be anything.
-    @COMPOSITION_EXPORTS = ressurections: 1, hibernations: 1
+    @COMPOSITION_EXPORTS = resurrections: 1, hibernations: 1

     # Add the supplied implementation function to the internal stack
-    # of function that will be invoked each the time the access system
-    # is ressurecting the entity out of the session. These functions
+    # of functions that will be invoked every time the access system
+    # is resurrecting the entity out of the session. These functions
     # will be called asynchronously, with an ability to abruptly end
     # the execution. Please consult with the `async` package and the
     # source code of this method for more details on the mechanics.
-    @ressurection: (xoptions, ximplement) ->
+    @resurrection: (xoptions, ximplement) ->
         noOptions = "the options must be an object"
         noImplement = "must have implementation body"
         noSignature = "function has invalid signature"
-        message = "Install session ressurector in %s"
+        message = "Install session resurrector in %s"
         assert identify = this.identify().underline
         implement = _.find arguments, _.isFunction
         options = _.find arguments, _.isPlainObject
-        assert previous = @ressurections or Array()
+        assert previous = @resurrections or Array()
         return previous unless arguments.length > 0
         assert _.isObject(options or {}), noOptions
         assert _.isFunction(implement), noImplement
         assert (implement.length) > 1, noSignature
         logger.debug message.yellow, identify.bold
         fn = (arbitraryVector) -> return implement
-        return fn @ressurections = previous.concat
+        return fn @resurrections = previous.concat
             implement: implement or _.noop
             options: options or Object()

+    # Add the supplied implementation function to the internal stack
+    # of functions that will be invoked every time the access system
+    # is hibernating the entity inside the session. These functions
+    # will be called asynchronously, with an ability to abruptly end
+    # the execution. Please consult with the `async` package and the
+    # source code of this method for more details on the mechanics.
+    @hibernation: (xoptions, ximplement) ->
+        noOptions = "the options must be an object"
+        noImplement = "must have implementation body"
+        noSignature = "function has invalid signature"
+        message = "Install session hibernator in %s"
+        assert identify = this.identify().underline
+        implement = _.find arguments, _.isFunction
+        options = _.find arguments, _.isPlainObject
+        assert previous = @hibernations or Array()
+        return previous unless arguments.length > 0
+        assert _.isObject(options or {}), noOptions
+        assert _.isFunction(implement), noImplement
+        assert (implement.length) > 1, noSignature
+        logger.debug message.yellow, identify.bold
+        fn = (arbitraryVector) -> return implement
+        return fn @hibernations = previous.concat
+            implement: implement or _.noop
+            options: options or Object()
+
     # Dereference the potentially existent entity from the session
     # into the supplied container, where the session is residing. It
-    # basically retrieves the hibernated entity, ressurects it and
+    # basically retrieves the hibernated entity, resurrects it and
     # defined the appropriate getter property on supplied container.
     # If the entity or session does not exist, nothing gets defined.
     # Please refer to source code, as it contains important details.
@@ -106,13 +131,13 @@
         return callback() unless session = container.session
         return callback() unless content = session[sid]
         delete container[key] if _.has container, key
-        surrogate = _.unique @constructor.ressurection()
+        surrogate = _.unique @constructor.resurrection()
         functions = (o.implement.bind @ for o in surrogate)
         boxd = (obj) -> (apply fn, obj for fn in functions)
         fasn = (o, c) -> series boxd(o), (e, r) -> c(e, o)
-        @ressurectEntity ?= (c, fn) -> fasn _.clone(c), fn
-        @ressurectEntity content, (error, entity) =>
-            format = (m) -> "ressurection error: #{m}"
+        @resurrectEntity ?= (c, fn) -> fasn _.clone(c), fn
+        @resurrectEntity content, (error, entity) =>
+            format = (m) -> "resurrection error: #{m}"
             masked = format error.message if error
             return callback Error masked if error
             s = enumerable: no, configurable: yes
@@ -120,7 +145,7 @@
             p.get = s.get = -> entity or undefined
             Object.defineProperty container, key, p
             Object.defineProperty session, key, p
-            @emit "ressurect", container, entity
+            @emit "resurrect", container, entity
             assert container[key]?; callback()

     # Authenticate supplied entity as the authorized entity against
@@ -128,7 +153,9 @@
     # container could be either a request or duplex socket or other
     # member that has the session storage installed under `session`.
     # The method also saves the session once it has authenticated.
+    # Please refer to source code, as it contains important details.
     authenticate: (container, entity, rme, callback) ->
+        {series, apply} = async or require "async"
         noSave = "the session has not save function"
         noSession = "container has no session object"
         message = "Persisted %s entity against session"
@@ -137,7 +164,11 @@
         assert symbol = @constructor.ACCESS_ENTITY_SYMBOL
         assert session = container?.session, noSession
         assert _.isObject(entity), "malformed entity"
-        @hibernateEntity ?= (xe, xn) -> xn null, xe
+        surrogate = _.unique @constructor.hibernation()
+        functions = (o.implement.bind @ for o in surrogate)
+        boxd = (obj) -> (apply fn, obj for fn in functions)
+        fasn = (o, c) -> series boxd(o), (e, r) -> c(e, o)
+        @hibernateEntity ?= (c, fn) -> fasn _.clone(c), fn
         @hibernateEntity entity, (error, content) =>
             return callback error, undefined if error
             assert not _.isEmpty(content), "no content"


Functional corrections in the service

Functional corrections in the service

--- library/gearing/service.coffee
+++ library/gearing/service.coffee
@@ -91,7 +91,7 @@
     # are not specified, the service will be published everywhere.
     @publish: (scopes...) ->
         registry = scoping.Scope.REGISTRY or {}
-        scopes = undefined if scopes is [undefined]
+        scopes = undefined if _.isEqual scopes, [undefined]
         exists = (scope) => this in (scope.services or [])
         p = (scope) => (scope.services ?= []).push this
         n = (scope) => logger.debug(notification, @name, scope.tag)