complete rewrite

complete rewrite

--- lib/index.coffee
+++ lib/index.coffee
@@ -1,137 +1,255 @@
-buildSearch = require 'stream-search-helper'
-buildChain  = require 'chain-builder'
-
-class Kevas extends (require 'stream').Transform
-
-  constructor: (options) ->
-    super options
-    @_handleExtra = @_pushString
-    @_search = buildSearch
-      delim:/(\\{1,2})?({{|}})/
-      min:4 # \\{{ is 4
-      recurse:true # get all results at once from chunk
-      groups:2 # two regex groups we want
-    @_key = ''
-
-    # if some `values` were provided
-    if options?.values?
-
-      # alias
-      values = options.values
-
-      # check if `values` has a get() function.
-      # when it does, let's consider that the way to get the values
-      if typeof values.get is 'function'
-        @on 'key', ->
-          value = values.get @key            # get the `value` from our `values`
-          if value? # when exists, add to context's `values`, as a string
-            if typeof value isnt 'string' then value = '' + value
-            @values.push value
-
-      else # otherwise, treat `values` as an object with keys
-        @on 'key', -> if values[@key]? then @values.push values[@key]
-
+OPEN1  = 123 # {
+OPEN2  = Buffer.from '{{'
+CLOSE1 = 125 # }
+CLOSE2 = Buffer.from '}}'
+SLASH1 = 92  # \
+SLASH2 = Buffer.from '\\\\'
+SLASH  = Buffer.from '\\'
+
+module.exports = (options) ->
+
+  transform = null  # transform we're building now in this function
+  buffer    = null  # will hold current buffer we're processing
+  cache     = []    # caches chunks which make up the key
+  cacheSize = 0     # number of bytes in cache for Buffer.concat()
+  index     = 0     # where are we in the current buffer
+  start     = 0     # where did the last unused content start
+  target1   = OPEN1 # at start we're looking for open braces
+  target2   = OPEN2
+  hadSlash  = false # was there an unescaped slash at tail of previous buffer?
+  hadTarget = false # was there an unescaped target1 at tail of previous buffer?
+  getValues =       # the function to get a value for a key
+    do (options) -> # either object or function in `options.values`
+      # NOTE: had to explicitly return `undefined` in noops because
+      #       coffee-coverage changed it to return a zero.
+      unless options? and options.values? then return -> undefined # noop
+
+      if typeof options.values is 'function' then options.values
+
+      else if typeof options.values is 'object'
+        values = options.values
+        (key) -> values[key]
+
+      else -> undefined # noop
+
+  # `pushOrCache` changed for different modes. OPEN :> push CLOSE :> cache
+  pushOrCache = doPush = (buffer) -> transform.push buffer
+
+  doCache = (buffer) -> # CLOSE mode caches content to combine as a key
+    cache.push buffer
+    cacheSize += buffer.length
+
+  # `switchTarget` changed for modes. OPEN :> toClose CLOSE :> toOpen
+  switchTarget = toClose = ->
+    # change what we're searching for and the mode based vars
+    target1 = CLOSE1
+    target2 = CLOSE2
+    switchTarget = toOpen
+    pushOrCache = doCache
+
+    if start < index # if there's content before {{ then push it
+      transform.push buffer.slice start, index
+
+    # move passed pair and set the start there as well.
+    start = index = index + 2
     return

-
-  _appendKey: (string) -> @_key += string  # accumulate key value
-
-  _pushString: (string) -> @push string    # push strings to the next stream
-
-  _combine: (result) ->
-    delim = result.delim
-    string = result.before
-    result = switch delim.length
-      when 2 then string             # just braces
-      when 4 then string + '\\\\'    # escaped slashes are retained
-
-    return result
-
-  _transform: (string, encoding, done) ->
-
-    string = string.toString 'utf8' # buffer becomes string, string returns itself
-    results = @_search string
-    @_parse results, done
-
-  _parse: (results, done) ->
-
-    while results.length > 0
-      result = results.shift()
-
-      # if it's a value before a found delim and it 'isn't escaped by a slash
-      if result.before? and result.g1 isnt '\\'
-        # what we do depends on the delim, more specifically, g2
-        switch result.g2 # use `g2` instead of `delim` to ignore captured slashes
-
-          when '{{'                      # string is before the start of a key
-            @_handleExtra = @_appendKey  # for later strings, append to `key`
-            string = @_combine result    # combine `.before` and delim part
-            @push string                 # push the regular string
-
-          when '}}'                        # string is before the end of a key
-            if @_handleExtra is @_pushString
-              # then the open braces were escaped, so, don't do key stuff
-              @_pushString @_combine(result) + '}}'
-            else
-              @_handleExtra = @_pushString   # for later strings, push them
-              key = @_key + @_combine result # get the accumulated key and emit it
-              @_key = ''                     # reset key to empty string for appending
-              return @_emitKey key.trim(), (error) =>
-                if error? then done error
-                else @_parse results, done
-
-      # if it's a string without a delim after it, handle one of two ways:
-      #  1. we're in non-key mode, so, it'll call _pushString
-      #  2. we're in key mode, so, it'll call _appendKey
-      else if result.string? then @_handleExtra result.string
-
-      else # it found an escaped delim, so treat it like an extra string
-        @_handleExtra result.before + result.g2
-
-    # all done with results/chunk
-    done()
-
-  _flush: (done) -> # TODO: log warning about an unclosed tag?
-    end = @_search.end()            # get anything left in the searcher
-    @push end.string if end?.string?  # push it if there was something
-    done()
-
-  on: (event, listener) ->
-    if event is 'key'
-      unless @_chain?
-        @_chain = buildChain array:[listener]
-      else
-        @_chain.add listener
-    else super event, listener
-
-  once: (event, listener) ->
-    if event is 'key'
-      fn = (control, context) =>
-        result = listener control, context
-        control.remove()
-        return result
-      @_chain.add fn
-    else super event, listener # there are no other events we use...
-
-  off: (event, listener) ->
-    if event is 'key' then @_chain.remove listener
-    else super event, listener # there are no other events we use...
-
-  _emitKey: (key, done) ->
-    context = key:key, values:[]
-    @_chain?.run context:context, done:(error) =>
-      if error? then return done error
-      @push value for value in context.values
-      process.nextTick done
-
-# Use these ways:
-#  1. buildKevas = require 'kevas'
-#     stream = buildKevas options
-#
-#  2. stream = require('kevas') (options)
-#
-#  3a. {Kevas} = require 'kevas'
-#  3b. Kevas = require('kevas').Kevas
-#      stream = new Kevas some:'options'
-module.exports = (options) -> new Kevas options
-module.exports.Kevas = Kevas
+  toOpen = ->
+    # change what we're searching for and the mode based vars
+    target1 = OPEN1
+    target2 = OPEN2
+    switchTarget = toClose
+    pushOrCache = doPush
+
+    if start < index # if there's content before }} then cache it
+      slice = buffer.slice start, index
+      cache.push slice
+      cacheSize += slice.length
+
+    # combine cache to get the "key" and reset cache vars
+    key = Buffer.concat(cache, cacheSize).toString('utf8')
+    cache.length = cacheSize = 0
+
+    # try to get a value for the key
+    value = getValues key
+
+    # if there's a replacement value then push it
+    if value? then transform.push value
+
+    else # otherwise, push the original key with the braces around it
+      transform.push OPEN2
+      transform.push key
+      transform.push CLOSE2
+
+    # move passed pair and set the start there as well.
+    start = index = index + 2
+    return
+
+  consumeSlash = -> # helper makes push/cache ops skip over a slash.
+    # backup one from where we found a pair of braces to the slash.
+    before = index - 1
+
+    if start < before # if there's content before it then push/cache it.
+      slice = buffer.slice start, before
+      pushOrCache slice
+
+    start = index # start after slash
+    index++       # index moves after matched pair's first (escaped) brace
+    return
+
+  # build the transform using the convenience constructo
+  transform = new require('stream').Transform
+
+    # we're deoding strings so chunk is always a buffer so we ignore the encoding.
+    transform: (chunk, _, done) ->
+      buffer = chunk     # move it to our outer variable.
+      index = start = 0  # reset the two indexing vars.
+
+      # if we found a single targeted brace at the tail of the previous buffer
+      # and this buffer starts with one then we've found the pair we wanted.
+      if hadTarget is true and buffer[0] is target1
+        switchTarget this, buffer, start, index
+        # the above moves index/start by +2 which is usually right, but wrong now.
+        # set to `1` to move passed the single target brace we used.
+        index = start = 1
+
+      # loop until we have no more buffer content to use
+      while index < buffer.length
+
+        # search for the next pair of braces from where we left off.
+        index = buffer.indexOf target2, index
+
+        # let's look at slash line-up to decide what to do and whether to
+        # reinstate the hadSlash slash from the previous buffer.
+        switch index
+
+          when -1 # didn't find a pair in the rest of the buffer.
+            escapePair   = false
+            escapeEscape = false
+            restoreSlash = hadSlash # maybe. if chunk length 1, target1 may be at 1
+
+          when 0 # found the pair at the start of this buffer
+            escapePair   = hadSlash
+            escapeEscape = false
+            restoreSlash = false
+
+          when 1 # found the pair at 1 fo this buffer, check before it
+            escapePair   = buffer[0] is SLASH1
+            escapeEscape = hadSlash
+            restoreSlash = false
+
+          else # found the pair at 2 or greater, check before it
+            escapePair   = buffer[index - 1] is SLASH1
+            escapeEscape = buffer[index - 2] is SLASH1
+            restoreSlash = hadSlash
+
+        # reset this because we used it above
+        hadSlash = false
+
+        if index >= 0 # if we found a pair then we handle things like this:
+
+          if restoreSlash then pushOrCache SLASH
+
+          if escapePair # there's a slash before the braces, maybe escape them.
+
+            if escapeEscape # there's a slash before the slash, so, it's escaped.
+
+              if index > 1     # when both slashes are in the current buffer,
+                consumeSlash() # then we consume the first one.
+                index--        # then we backup one to keep one of them.
+
+              switchTarget() # switch to searching for the other pair of braces
+
+            else consumeSlash() # escaped the pair of braces so consume the slash
+
+          else switchTarget() # no slashes, so, switch to searching for other braces
+
+        else # check if tail end has some info, escape slash or target1...
+
+          # reset these now to avoid a bunch of "else" statements.
+          hadTarget = hadSlash = false
+
+          # check the last bytes for a brace or slash
+          last = buffer.length - 1
+
+          switch buffer[last]
+
+            when target1 # a lone brace we're looking for
+              # TODO: if last is 0 ?
+
+              if last is 1 # special case: 1st slash would be in previous buffer
+
+                # `restoreSlash` tells if there was a slash in previous buffer
+                if buffer[0] isnt SLASH1 or restoreSlash is true
+
+                  # remember we matched a single target brace
+                  hadTarget = true
+
+                  # don't include our brace in the content handled at the bottom
+                  buffer = buffer.slice 0, 1
+
+              else # last >= 2 so we can check in buffer
+
+                if buffer[last - 1] is SLASH1 # maybe escaped
+
+                  if buffer[last - 2] is SLASH1 # escaped the slash instead
+                    hadSlash = true
+                    hadTarget = true
+                    buffer = buffer.slice start, last - 1
+
+                  else # is escaped, consume the slash, ignore the target1
+                    pushOrCache buffer.slice start, last - 1  # no slash/target1
+                    buffer = buffer.slice last, buffer.length # has target1
+
+                else # we matched a single target brace
+                  hadTarget = true
+
+                  # don't include brace in the content handled at the bottom
+                  buffer = buffer.slice start, last
+
+            when SLASH1 # a lone slash may escape brace in next buffer's start.
+
+              if buffer[last - 1] isnt SLASH1 # then it's not escaped
+
+                # remember we had an unescaped escape slash.
+                hadSlash = true
+
+                # don't include the slash in our content
+                buffer = buffer.slice start, last
+
+            else # no target1 or slash at the end.
+
+              # if there was a slash to restore then we restore it.
+              # only happens if no pair is found in entire buffer.
+              if restoreSlash then pushOrCache SLASH
+
+              if start > 0 # limit what we'll push/cache
+                buffer = buffer.slice start, buffer.length
+
+          # handles buffer for current mode.
+          pushOrCache buffer
+
+          # we're done when we reach the swith's else
+          return done()
+
+        # tail of loop
+
+      # we hit the end of the input buffer, loop stopped, we're done:
+      done()
+
+
+    flush: (done) ->      # if we were in CLOSE mode gathering a key,
+      if hadSlash then @push SLASH
+
+      if hadTarget then @push target2.slice(0, 1)
+
+      if cache.length > 0 # then output what was cached no.
+        @push OPEN2       # prepend the '{{' we found to switch mode to CLOSE.
+        @push each for each in cache
+        cache.length = cacheSize = 0  # reset the cache
+
+      done()
+
+
+  # way back up there we created `transform` which is the last thing so
+  # it is returned from the exported function.


UI/zoom extracting the smooth zooming functionality into a separated method

--- ui/zoom/src/zoom.coffee
+++ ui/zoom/src/zoom.coffee
@@ -45,45 +45,50 @@
       @locker.show().position(@thumb.position()).size(@thumb.size())

     @image = @image.clone().insertTo(@image, 'instead').attr('src', null)
-    @image.on('load', =>@zoom()).attr('src', link.attr('href'))
+    @image.on('load', =>@loaded()).attr('src', link.attr('href'))

     return @

 # private

   #
-  # Makes the actual zoom
+  # Handles the image-load events
+  #
+  loaded: ->
+    @dialog.style('display: inline-block; opacity: 0')
+
+    @[if @options.nolock is true then 'addClass' else 'removeClass']('lui-modal-nolock')
+
+    if @thumb && @options.fxDuration
+      @zoom()
+    else
+      @dialog.style(opacity: 1)
+
+    @locker.hide()
+
+  #
+  # Makes the smooth zoom visual effect
   #
   zoom: ()->
-    @[if @options.nolock is true then 'addClass' else 'removeClass']('lui-modal-nolock')
+    start_pos  = @thumb.position()
+    start_size = @thumb.size()

-    @dialog.style('display: inline-block; opacity: 0')
+    end_size   = @dialog.size()
+    end_pos    = @dialog.position()

-    if @thumb && @options.fxDuration
-      start_pos  = @thumb.position()
-      start_size = @thumb.size()
+    @dialog.style(position: 'absolute').size(start_size).position(start_pos)
+    @image.style width: '100%', height: '100%'

-      end_size   = @dialog.size()
-      end_pos    = @dialog.position()
+    pos_diff = @dialog.style('top,left')
+    pos_diff = x: parseInt(pos_diff.left), y: parseInt(pos_diff.top)

-      @dialog.style(position: 'absolute').size(start_size).position(start_pos)
-      @image.style width: '100%', height: '100%'
-      @locker.hide()
-
-      pos_diff = @dialog.style('top,left')
-      pos_diff = x: parseInt(pos_diff.left), y: parseInt(pos_diff.top)
-
-      @dialog.style(opacity: 1).animate({
-        top:     pos_diff.y + (end_pos.y - start_pos.y) + 'px'
-        left:    pos_diff.x + (end_pos.x - start_pos.x) + 'px'
-        width:   end_size.x + 'px'
-        height:  end_size.y + 'px'
-      }, duration: @options.fxDuration, finish: =>
-        @image.style(width: '', height: '')
-        @dialog.style(top: '', left: '', width: '', height: '')
-        @dialog.style(position: 'relative')
-      )
-    else
-      @locker.hide()
-      @dialog.style(opacity: 1)
-
+    @dialog.style(opacity: 1).animate({
+      top:     pos_diff.y + (end_pos.y - start_pos.y) + 'px'
+      left:    pos_diff.x + (end_pos.x - start_pos.x) + 'px'
+      width:   end_size.x + 'px'
+      height:  end_size.y + 'px'
+    }, duration: @options.fxDuration, finish: =>
+      @image.style(width: '', height: '')
+      @dialog.style(top: '', left: '', width: '', height: '')
+      @dialog.style(position: 'relative')
+    )


Added LogAxis, LogMapper, LogTicker, and LogFormatter

--- bokehjs/src/coffee/main.coffee
+++ bokehjs/src/coffee/main.coffee
@@ -47,6 +47,7 @@

   # mappers
   Bokeh.LinearMapper      = require("mapper/1d/linear_mapper")
+  Bokeh.LogMapper         = require("mapper/1d/log_mapper")
   Bokeh.CategoricalMapper = require("mapper/1d/categorical_mapper")
   Bokeh.GridMapper        = require("mapper/2d/grid_mapper")
   Bokeh.LinearColorMapper = require("mapper/color/linear_color_mapper")
@@ -98,6 +99,7 @@
   Bokeh.DatetimeAxis    = require("renderer/guide/datetime_axis")
   Bokeh.Grid            = require("renderer/guide/grid")
   Bokeh.LinearAxis      = require("renderer/guide/linear_axis")
+  Bokeh.LogAxis         = require("renderer/guide/log_axis")

   # overlays
   Bokeh.BoxSelection = require("renderer/overlay/box_selection")
@@ -118,6 +120,8 @@
   Bokeh.AdaptiveTicker           = require("ticking/adaptive_ticker")
   Bokeh.BasicTicker              = require("ticking/basic_ticker")
   Bokeh.BasicTickFormatter       = require("ticking/basic_tick_formatter")
+  Bokeh.LogTicker                = require("ticking/log_ticker")
+  Bokeh.LogTickFormatter         = require("ticking/log_tick_formatter")
   Bokeh.CategoricalTicker        = require("ticking/categorical_ticker")
   Bokeh.CategoricalTickFormatter = require("ticking/categorical_tick_formatter")
   Bokeh.CompositeTicker          = require("ticking/composite_ticker")


tableverse/shape: Fix minor bug with circles.

--- Oghma/tableverse/shape.coffee
+++ Oghma/tableverse/shape.coffee
@@ -25,13 +25,13 @@
   circle:
     create: -> new Kinetic.Circle()
     extra: ( attrs ) ->
-      attrs.r = attrs.width / 2
+      attrs.radius = attrs.width / 2
     b_attrs: ( attrs, B ) ->
       r = Math.max( B[0], B[1] )

       attrs.width  = 2*r
       attrs.height = 2*r
-      attrs.r      = r
+      attrs.radius = r
   line:
     create: -> new Kinetic.Line(points: [ 0, 0, 0, 0 ])
     extra: ( attrs ) ->


Fix implementation of mode so it doesn't convert values to strings

--- Src/coffeescript/cql-execution/src/elm/aggregate.coffee
+++ Src/coffeescript/cql-execution/src/elm/aggregate.coffee
@@ -163,20 +163,20 @@
     if typeIsArray(arg)
       filtered = compact(arg)
       mode = @mode(filtered)
-      if mode.length == 1 then  mode[0] else numerical_sort(mode)
+      if mode.length == 1 then mode[0] else mode

   mode: (arr) ->
-    # returns an array with the modes of arr, i.e. the
-    # elements that appear most often in arr
+    max = 0
     counts = {}
+    results = []
     for elem in arr
-      counts[elem] ||= 0
-      counts[elem] += 1
-    max = 0
-    for key, cnt of counts
-      max = cnt if cnt > max
-    (key for key, cnt of counts when cnt == max)
-
+      cnt = counts[elem] = (counts[elem] ? 0) + 1
+      if cnt is max and elem not in results
+        results.push elem
+      else if cnt > max
+        results = [elem]
+        max = cnt
+    results

   # TODO: Remove functionref when ELM does Mode natively
 module.exports.ModeFunctionRef = class ModeFunctionRef extends FunctionRef


Rename to `SearchCallbackArgument`

Rename to `SearchCallbackArgument`

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -13,7 +13,7 @@
 Patch = require './patch'
 {spliceArray, newlineRegex} = require './helpers'

-class SearchResultIteratee
+class SearchCallbackArgument
   Object.defineProperty @::, "range",
     get: ->
       return @computedRange if @computedRange?
@@ -1003,11 +1003,11 @@

     matches.reverse() if reverse
     for match in matches
-      resultIteratee = new SearchResultIteratee(this, match, lengthDelta)
-      iterator(resultIteratee)
-      lengthDelta += resultIteratee.getReplacementDelta() unless reverse
-
-      break unless global and resultIteratee.keepLooping()
+      callbackArgument = new SearchCallbackArgument(this, match, lengthDelta)
+      iterator(callbackArgument)
+      lengthDelta += callbackArgument.getReplacementDelta() unless reverse
+
+      break unless global and callbackArgument.keepLooping()
     return

   # Public: Scan regular expression matches in a given range in reverse order,


Make selectionsMarkerLayer also persistent

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -169,7 +169,7 @@
     @displayLayer ?= @buffer.addDisplayLayer()
     @displayLayer.setTextDecorationLayer(@tokenizedBuffer)
     @defaultMarkerLayer = @displayLayer.addMarkerLayer()
-    @selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true)
+    @selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true, persistent: true)

     @decorationManager = new DecorationManager(@displayLayer, @defaultMarkerLayer)


save the root schema on the generator instance

--- index_.coffee
+++ index_.coffee
@@ -7,16 +7,20 @@

 module.exports = exports = (schema, method = 'all') ->
   generator = new exports.Generator
+    root_schema: schema
     method: method

-  return generator.generate schema
+  return generator.generate()

 class exports.Generator

   constructor: (options) ->
     @method = options.method
+    @root_schema = options.root_schema

-  generate: (schema, depth = 0) ->
+  generate: (schema = null, depth = 0) ->
+
+    schema = @root_schema unless schema

     return schema  unless type(schema) is 'object'


Render FactBaseView when dealing with facts

--- core/app/backbone/views/evidence/ndp_fact_relation_or_comment_view.coffee
+++ core/app/backbone/views/evidence/ndp_fact_relation_or_comment_view.coffee
@@ -19,5 +19,4 @@
     if @model instanceof Comment
       @contentRegion.show new NDPCommentView model: @model
     else
-      @contentRegion.show new TextView model: new Backbone.Model text: 'blaat'
-
+      @contentRegion.show new FactBaseView model: @model.getFact()


removed debug logging that got left behind

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -26,7 +26,6 @@
       partitionByService = (content) ->
         serviceRegex = /^(?:(.+?):|(?:.+))\s(?:(?!^\w)(?:.*?)\s?)+/gm
         while match = serviceRegex.exec content
-          console.log 'byService', match
           data: match[0]
           name: match[1]

@@ -50,18 +49,12 @@
         (services.map (s) ->
           data = s.data
           partitions = partitionByVolume data
-          console.log 'partitions', partitions
           if partitions.length
             [all, pre, volumes, post] = partitions
-            console.log '!pre', pre
-            console.log '!volumes', volumes
-            console.log '!post', post
-            console.log '!name', s.name
-            console.log '~volumes', partitionVolumes(volumes)
+
             if s.name then partitionVolumes(volumes).forEach (vol) ->
               volumes = volumes.replace vol, (migrateVolume s.name, vol)
             data = "#{pre or ''}#{volumes or ''}#{post or ''}"
-            console.log 'aaaa!', data
           data
         ).join '\n'


fixes @param breakage

fixes @param breakage

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -148,9 +148,9 @@
         if !value? or value == ''

         else
-          segments.push currSegment
-
-          currSegment = new @Segment
+          if currSegment.code.length > 0
+            segments.push currSegment
+            currSegment = new @Segment

           #} For example, this comment should be treated as part of our code.
           if value[0] == language.ignorePrefix


trying enter and leave scripts

trying enter and leave scripts

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -57,13 +57,13 @@
   #   res.send "#{res.message.text}? That's a Paddlin'"
   #
   #
-  # enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
-  # leaveReplies = ['Are you still there?', 'Target lost', 'Searching']
+   enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
+   leaveReplies = ['Are you still there?', 'Target lost', 'Searching']
   #
-  # robot.enter (res) ->
-  #   res.send res.random enterReplies
-  # robot.leave (res) ->
-  #   res.send res.random leaveReplies
+   robot.enter (res) ->
+     res.send res.random enterReplies
+   robot.leave (res) ->
+     res.send res.random leaveReplies
   #
   # answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
   #


Corrige problemas com lastSelected

Corrige problemas com lastSelected

--- src/coffee/portal-sku-selector.coffee
+++ src/coffee/portal-sku-selector.coffee
@@ -82,7 +82,6 @@
 	update: (dimensionName, dimensionValue) =>
 		index = -1
 		lastSelected = -1
-		debugger
 		for dimension, i in @dimensions
 			if dimension.name is dimensionName
 				dimension.selected = dimensionValue
@@ -96,13 +95,15 @@
 		for dimension, i in @dimensions by -1
 			if dimension.selected isnt null && dimension.selected isnt undefined
 				lastSelected = i
+				break

 		originalSelection = (dim.selected for dim in @dimensions)

+		console.log lastSelected
 		for dimension, i in @dimensions
 			@resetDimension(dimension)

-			if i > lastSelected
+			if i >= lastSelected
 				for value in dimension.values
 					selection = originalSelection.slice(0)
 					selection[i] = value


create separate portal element

create separate portal element

--- resources/assets/lib/modal.coffee
+++ resources/assets/lib/modal.coffee
@@ -21,11 +21,11 @@
 import { createPortal } from 'react-dom'

 export class Modal extends PureComponent
-  portals = document.getElementsByClassName('js-react-modal')
+  roots = document.getElementsByClassName('js-react-modal')

   @inTree: (event) ->
-    portals[0] in event.composedPath()
+    roots[0] in event.composedPath()

   constructor: ->
@@ -33,7 +33,10 @@

   componentDidMount: =>
+    roots[0].appendChild @portal
     document.addEventListener 'keydown', @handleEsc
+    $(document).on 'turbolinks:before-cache.modal', () =>
+      roots[0].removeChild @portal

   componentDidUpdate: (prevProps) =>
@@ -42,6 +45,7 @@

   componentWillUnmount: =>
     document.removeEventListener 'keydown', @handleEsc
+    $(document).off '.modal'

   handleEsc: (e) =>
@@ -53,7 +57,8 @@

   render: =>
-    createPortal @renderPortalContent(), portals[0]
+    @portal ?= document.createElement('div')
+    createPortal @renderPortalContent(), @portal

   renderPortalContent: =>


WebRTC: Fix wrong on-wire protocol

WebRTC: Fix wrong on-wire protocol

Should be .command instead of .message

--- spec/webrtc.coffee
+++ spec/webrtc.coffee
@@ -35,7 +35,7 @@
       dc.onmessage = (data) =>
         msg = JSON.parse data.data
         @emit 'message', msg
-        if msg.protocol == 'runtime' && msg.message == 'getruntime'
+        if msg.protocol == 'runtime' && msg.command == 'getruntime'
           # reply so we are considered to be connected
           @send 'runtime', 'runtime',
             type: 'noflo-browser'
@@ -46,7 +46,7 @@
     return if not context.channel
     msg =
       protocol: protocol
-      message: topic
+      command: topic
       payload: payload
     m = JSON.stringify msg
     @channel.send m


change socket to localhost

change socket to localhost

--- src/app/application.coffee
+++ src/app/application.coffee
@@ -1,5 +1,5 @@
 io = require 'socket.io-client'
-socket = io.connect 'http://10.2.10.1:8000'
+socket = io.connect 'http://localhost:8000'

 socket.on 'connect', ->
   console.log 'connected!'


Remove *all* !important expressions once specificity is calculated

--- src/app/binding-set.coffee
+++ src/app/binding-set.coffee
@@ -19,7 +19,7 @@
   constructor: (selector, commandsByKeystrokes, @index, @name) ->
     BindingSet.parser ?= PEG.buildParser(fsUtils.read(require.resolve 'keystroke-pattern.pegjs'))
     @specificity = Specificity(selector)
-    @selector = selector.replace(/\s*!important\s*$/, '')
+    @selector = selector.replace(/!important/g, '')
     @commandsByKeystrokes = @normalizeCommandsByKeystrokes(commandsByKeystrokes)

   commandForEvent: (event) ->


 Merge branch 'master.activity.channel' into 'master.activity'

socialapi: client side channel management improvements

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -43,13 +43,12 @@

       brokerChannel = KD.remote.subscribe channelName, subscriptionData
       @forwardMessageEvents brokerChannel, this, ["MessageAdded", "MessageRemoved"]
-      @openedChannels[name] = brokerChannel
+      @openedChannels[channelName] = {delegate: brokerChannel, channel: this}
       @emit "ChannelRegistered-#{channelName}", this

   onChannelReady: (channel, callback) ->
     channelName = generateChannelName channel
-    channel = @openedChannels[channelName]
-    if channel instanceof KD.remote.api.SocialChannel
+    if channel = @openedChannels[channelName]?.channel
     then callback channel
     else @once "ChannelRegistered-#{channelName}", callback

@@ -187,15 +186,15 @@
           channelName: channelName
           isExclusive: yes

-        KD.remote.subscribe name, subscriptionData, (brokerChannel)->
+        KD.remote.subscribe channelName, subscriptionData, (brokerChannel)->
           {name} = brokerChannel
-          socialapi.openedChannels[name] = brokerChannel
+          socialapi.openedChannels[name] = {delegate: brokerChannel, channel: socialApiChannel}
           forwardMessageEvents brokerChannel, socialApiChannel, [
             "MessageAdded",
             "MessageRemoved"
           ]

-          socialapi.emit "ChannelRegistered-#{name}", brokerChannel
+          socialapi.emit "ChannelRegistered-#{name}", socialApiChannel

   generateChannelName = ({name, typeConstant, groupName}) ->
     return "socialapi.#{groupName}-#{typeConstant}-#{name}"


social: forward removedFromChannel events

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -333,10 +333,11 @@

   getMessageEvents = ->
     [
-      {event: "MessageAdded",   mapperFn: mapActivity, validatorFn: isFromOtherBrowser}
-      {event: "MessageRemoved", mapperFn: mapActivity, validatorFn: isFromOtherBrowser}
-      {event: "AddedToChannel", mapperFn: mapParticipant}
-      {event: "ChannelDeleted", mapperFn: mapChannel}
+      {event: "MessageAdded",       mapperFn: mapActivity, validatorFn: isFromOtherBrowser}
+      {event: "MessageRemoved",     mapperFn: mapActivity, validatorFn: isFromOtherBrowser}
+      {event: "AddedToChannel",     mapperFn: mapParticipant}
+      {event: "RemovedFromChannel", mapperFn: mapParticipant}
+      {event: "ChannelDeleted",     mapperFn: mapChannel}
     ]

   serialize = (obj) ->


SyncController: change methods to get file object as a parameter

--- client/Main/synclocalcontroller.coffee
+++ client/Main/synclocalcontroller.coffee
@@ -32,23 +32,18 @@
     @storage.setValue "saveRequestedFiles", @filesToSave
     @initializeListeners()

-  removeFromSaveArray: (fileName) ->
+  removeFromSaveArray: (file) ->
+    fileName = @getFileFullPath file
     index = @filesToSave.indexOf fileName
     if index > -1
       @filesToSave.splice index, 1
       @storage.setValue "saveRequestedFiles", @filesToSave

-  saveOpenedTabsContentToLocalStorage: ->
-    {aceViews} = KD.singletons.appManager.get("Ace").mainView
-    for filePath in Object.keys aceViews
-      content = aceViews[filePath].ace.getContents()
-      @saveToLocalStorage filePath, content
-
-  patchFileIfDiffExist: (vmName, fileName, localContent)->
-    KD.singletons.vmController.start vmName, (err, result)=>
-      return err if err
-      @fetchFileContents vmName, fileName,(err, content)=>
+  patchFileIfDiffExist: (file, localContent, cb)->
+    KD.singletons.vmController.info file.vmName, (err, vm, info)=>
       # TODO : we may need to listen vmController when state is not RUNNING
+      return cb err unless info.state is "RUNNING"
+      file.fetchContents (err, content)=>
         if content and not err
           newContent = @getPatchedContent content, localContent
           if newContent
@@ -65,33 +60,8 @@
     patchArray = dmp.patch_make originalContent, diffArray
     dmp.patch_apply patchArray, originalContent

-
-  writeToFile:(vmName, fileName, contents, callback)->
-    @emit "fs.save.started"
-    utf8Content   = KD.utils.utf8Encode contents
-    binaryContent = btoa contents
-    KD.singletons.vmController.run
-      vmName    : vmName
-      method    : 'fs.writeFile'
-      withArgs  :
-        path    : FSHelper.plainPath fileName
-        content : binaryContent
-    , (err, res)=>
-      if err then warn err
-      @emit "fs.save.finished", err, res
-      callback? err, res
-
-  fetchFileContents: (vmName, fileName, callback)->
-    KD.singletons.vmController.run
-      method    : 'fs.readFile'
-      vmName    : vmName
-      withArgs  :
-        path    : FSHelper.plainPath fileName
-    , (err, response)->
-      content = atob response.content
-      callback err, KD.utils.utf8Decode content
-
-  updateFileContentOnLocalStorage: (fileName, content)->
+  updateFileContentOnLocalStorage: (file, content)->
+    fileName = @getFileFullPath file
     @saveToLocalStorage fileName, content

   saveToLocalStorage: (fileName, contents)->


Remove unused renderLoading method

Remove unused renderLoading method

--- client/lanes/components/grid/Grid.cjsx
+++ client/lanes/components/grid/Grid.cjsx
@@ -92,11 +92,6 @@
             allowDelete: @props.allowDelete and @props.commands?.isEditing()
         }, @props.editorProps))

-    renderLoading: ->
-

-
Loading ...

-

-
     onResize: _.debounce( (size) ->
         @setState(size: {height: size.height, width: size.width})
     , 300)
@@ -141,6 +136,7 @@
                     onSortChange={@onSortChange}
                     onSelectionChange={@onRowClick}
                     idProperty='0'
+                    loading={true}
                     pagination={true}
                     onColumnResize={@onColumnResize}
                     paginationToolbarProps = { position: 'top' }


remove WHAT log

remove WHAT log

--- app/assets/javascripts/crdt/document.js.coffee
+++ app/assets/javascripts/crdt/document.js.coffee
@@ -60,7 +60,6 @@
       atom
     else if isObject value
       atom = new CRDT.Hash
-      console.log "WHAT", atom
       for key, _value of value
         atom.set key, @begetVector(_value, clock)
       atom


Removendo model usuario do recado ao salvar/excluir o modelo recado

--- app/assets/javascripts/models/recado.js.coffee
+++ app/assets/javascripts/models/recado.js.coffee
@@ -1 +1,5 @@
 class MuralDeRecados.Models.Recado extends Backbone.Model
+  sync: (method, model, options) ->
+    if method != 'read'
+      model.unset('usuario')
+    Backbone.sync(method, model, options)


Fix month name in notifications view

Fix month name in notifications view

--- app/assets/javascripts/notifications.js.coffee
+++ app/assets/javascripts/notifications.js.coffee
@@ -129,7 +129,7 @@
       minutes = "0#{minutes}"
     year = if d.getFullYear() != this_year then " #{d.getFullYear()}" else ""
     date = if today.getFullYear() == d.getFullYear() && today.getMonth() == d.getMonth() && today.getDate() == d.getDate() then ""
-    else "#{month_names[d.getMonth()-1]} #{d.getDate()}#{year} - "
+    else "#{month_names[d.getMonth()]} #{d.getDate()}#{year} - "
     "#{date}#{d.getHours()}:#{minutes}"

   createTimestamp = (n) ->


fix asynchronous graph-load code.

fix asynchronous graph-load code.

--- app/assets/javascripts/plugins/graph.js.coffee
+++ app/assets/javascripts/plugins/graph.js.coffee
@@ -15,16 +15,14 @@
 graphs_uri = window.location.href
   .replace(/\/graphs(\?[^/]+|\/)?$/, '/graphs')

-d3.select('div#graph_depth_setting ul li a').on 'click', (e) ->
+d3.selectAll('div#graph_depth_setting ul li a').on 'click', (e) ->
   d3.event.preventDefault()
   depth = parseInt(d3.select(this).html())

   d3.select('div#graph_depth_setting > a').html(depth)
     .append('span').attr('class', 'caret')

-  $.get("#{graphs_uri}?depth=#{depth}", (data) ->
-      displayGraph(data)
-    , "json")
+  $.get("#{graphs_uri}?depth=#{depth}", displayGraph, "json")

 randomNumber = (min, max) ->
   Math.random() * (max - min) + min
@@ -56,9 +54,7 @@

 jQuery ->
   d3.select("div#d3_graph").html(HandlebarsTemplates['graphs/loading']({}))
-  $.get(graphs_uri, (data) ->
-      displayGraph(data)
-    , "json")
+  $.get(graphs_uri, displayGraph, "json")

 d3NodesEdges = (data) ->
   nodes = []


Move event handlers to methods

Move event handlers to methods

--- app/assets/javascripts/right_sidebar.js.coffee
+++ app/assets/javascripts/right_sidebar.js.coffee
@@ -5,16 +5,8 @@
     @addEventListeners()

   addEventListeners: ->
-    _this = @
-    @sidebar.on 'click', '.sidebar-collapsed-icon', (e) ->
-      e.preventDefault()
-      $block = $(@).closest('.block')
-      _this.openDropdown($block);
-
-    $('.dropdown').on 'hidden.gl.dropdown', (e) ->
-      e.preventDefault()
-      $block = $(@).closest('.block')
-      _this.sidebarDropdownHidden($block)
+    @sidebar.on('click', '.sidebar-collapsed-icon', @, @sidebarCollapseClicked)
+    $('.dropdown').on('hidden.gl.dropdown', @, @onSidebarDropdownHidden)
     $('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
     $('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)

@@ -40,6 +32,12 @@
     else
       i.show()

+  sidebarCollapseClicked: (e) ->
+    sidebar = e.data
+    e.preventDefault()
+    $block = $(@).closest('.block')
+    sidebar.openDropdown($block);
+
   openDropdown: (blockOrName) ->
     $block = if _.isString(blockOrName) then @getBlock(blockOrName) else blockOrName

@@ -52,6 +50,12 @@
   setCollapseAfterUpdate: ($block) ->
     $block.addClass('collapse-after-update')
     $('.page-with-sidebar').addClass('with-overlay')
+
+  onSidebarDropdownHidden: (e) ->
+    sidebar = e.data
+    e.preventDefault()
+    $block = $(@).closest('.block')
+    sidebar.sidebarDropdownHidden($block)

   sidebarDropdownHidden: ($block) ->
     if $block.hasClass('collapse-after-update')


Prevent status/time resetting back to '-' when not null. PP-8

--- client/src/scripts/scoring-view.coffee
+++ client/src/scripts/scoring-view.coffee
@@ -7,8 +7,8 @@
     algorithm: score.model.model_algorithm
     category: score.model.model_category
     responseColumn: score.model.response_column_name
-    status: node$ score.status or '-'
-    time: node$ score.time or '-'
+    status: node$ if isNull score.status then '-' else score.status
+    time: node$ if isNull score.time then '-' else score.time
     result: node$ score.result

   initialize = (scoring) ->


Adds a test

Adds a test

--- client/test/models/artwork.test.coffee
+++ client/test/models/artwork.test.coffee
@@ -8,16 +8,38 @@

   beforeEach ->
     sinon.stub Backbone, 'sync'
-    @artwork = new Artwork fabricate 'artwork'
+    @artwork = new Artwork _.extend {}, fabricate 'artwork',
+      title: 'Foo'
+      date: '1/1/2017'
+      id: 'foo'
+      artists: [
+        name: 'Andy Warhol'
+        id: 'andy-warhol'
+      ]
+      partner: {
+        name: 'MOMA'
+        default_profile_public: true
+        default_profile_id: 'moma'
+      }

   afterEach ->
     Backbone.sync.restore()

-  describe '#truncatedLabel', ->
+  it 'creates a nice short label for the artwork', ->
+    @artwork.truncatedLabel().should.equal 'A.W. Foo, 1/1/2017'

-    it 'creates a nice short label for the artwork', ->
-      @artwork.set
-        title: 'Foo'
-        date: 'Monday'
-        artist: name: 'Andy Warhol'
-      @artwork.truncatedLabel().should.equal 'A.W. Foo, Monday'
+  it 'denormalizes artworks', ->
+    denormalized = @artwork.denormalized()
+    denormalized.type.should.equal 'artwork'
+    denormalized.id.should.equal '564be09ab202a319e90000e2'
+    denormalized.slug.should.equal 'foo'
+    denormalized.date.should.equal '1/1/2017'
+    denormalized.title.should.equal 'Foo'
+    denormalized.image.should.equal '/local/additional_images/4e7cb83e1c80dd00010038e2/1/larger.jpg'
+    denormalized.partner.name.should.equal 'MOMA'
+    denormalized.partner.slug.should.equal 'moma'
+    denormalized.artists[0].name.should.equal 'Andy Warhol'
+    denormalized.artists[0].slug.should.equal 'andy-warhol'
+    denormalized.width.should.equal 1000
+    denormalized.height.should.equal 585
+    denormalized.credit.should.equal 'Sourced from ARS'


Changed it so weather doesn't process location

--- scripts/weather.coffee
+++ scripts/weather.coffee
@@ -20,7 +20,7 @@
 #   kyleslattery
 module.exports = (robot) ->
   robot.hear /weather ?(.+)?/i, (msg) ->
-    location = msg.match[1] || process.env.HUBOT_DARK_SKY_DEFAULT_LOCATION
+    location = process.env.HUBOT_DARK_SKY_DEFAULT_LOCATION
     return if not location

     googleurl = "http://maps.googleapis.com/maps/api/geocode/json"


fix the task publish transition

fix the task publish transition

--- src/components/task-plan/reading.cjsx
+++ src/components/task-plan/reading.cjsx
@@ -7,6 +7,9 @@
 {TaskPlanStore, TaskPlanActions} = require '../../flux/task-plan'
 {TocStore, TocActions} = require '../../flux/toc'
 LoadableMixin = require '../loadable-mixin'
+
+# Transitions need to be delayed so react has a chance to finish rendering so delay them
+delay = (fn) -> setTimeout(fn, 1)

 SectionTopic = React.createClass
   render: ->
@@ -159,8 +162,10 @@

   publishPlan: ->
     id = @getId()
+    {courseId} = @getParams()
     TaskPlanActions.publish(id)
-    @transitionTo('editReading', {id})
+    delay =>
+      @transitionTo('editReading', {courseId, id})

   deletePlan: () ->
     id = @getId()


disabling save button if fields aren't changed

--- src/components/task-plan/reading.cjsx
+++ src/components/task-plan/reading.cjsx
@@ -183,8 +183,10 @@
     id = @getId()
     plan = TaskPlanStore.get(id)

-    saveable = plan?.title and plan?.due_at and plan?.settings?.page_ids?.length > 0
-    publishable = saveable and not TaskPlanStore.isChanged(id)
+    valid = plan?.title and plan?.due_at and plan?.settings?.page_ids?.length > 0
+    publishable = valid and not TaskPlanStore.isChanged(id)
+    saveable = valid and TaskPlanStore.isChanged(id)
+
     deleteable = not TaskPlanStore.isNew(id)
     headerText = if TaskPlanStore.isNew(id) then 'Add Reading' else 'Edit Reading'
     topics = TaskPlanStore.getTopics(id)


code reshaping

code reshaping

--- src/client/api.coffee
+++ src/client/api.coffee
@@ -1,108 +1,21 @@
 define ['lib/version', 'lib/hullbase'], (version, base) ->

   (app) ->
+
+    models = {}
+    collections = {}
+
     rpc = false
     rawFetch = null
-    require:
-      paths:
-        easyXDM: 'components/easyXDM/easyXDM'
-        backbone: 'components/backbone/backbone'
-        cookie: 'components/jquery.cookie/jquery.cookie'
-      shim:
-        easyXDM: { exports: 'easyXDM' }
-        backbone: { exports: 'Backbone', deps: ['underscore', 'jquery'] }
-
-    initialize: (app)->
-      core    = app.core
-      sandbox = app.sandbox
-
-      _         = require('underscore')
-      Backbone  = require('backbone')
-      easyXDM   = require('easyXDM')
-
-      slice = Array.prototype.slice
-
-      # Stolen from fb sdk
-      # https://github.com/facebook/facebook-js-sdk/blob/deprecated/src/core/api.js#L188
-      extractApiArgs = (args)->
-        callback = errback = null
-        path = args.shift()
-        next = args.shift()
-        params = {}
-
-        if (typeof path != 'string')
-          throw new TypeError("Invalid path passed to Hull.api() : " + JSON.stringify(path));
-
-        while (next)
-          type = typeof next
-          if type == 'string' && !method
-            method = next.toLowerCase()
-          else if (type == 'function' && (!callback || !errback))
-            if !callback
-              callback = next
-            else if (!errback)
-              errback = next
-          else if (type == 'object')
-            params = _.extend(params, next)
-          else
-            throw new TypeError("Invalid argument passed to Hull.api(): " + next)
-
-          next = args.shift()
-
-        method ?= 'get'
-
-        callback ?= ->
-        errback  ?= (err, data)-> console.error('The request has failed: ', err, data)
-
-        ret = [{ path: path, method: method, params: params }, callback, errback]
-
-        ret
-
-      app.core.setCurrentUser = setCurrentUser = (headers={})->
-        return unless app.config.appId
-        cookieName = "hull_#{app.config.appId}"
-        currentUserId = app.core.currentUser?.id
-        if headers && headers['Hull-User-Id'] && headers['Hull-User-Sig']
-          val = btoa(JSON.stringify(headers))
-          $.cookie(cookieName, val, path: "/")
-          if currentUserId != headers['Hull-User-Id']
-            app.core.currentUser = {
-              id: headers['Hull-User-Id'],
-              sig: headers['Hull-User-Sig']
-            }
-            app.core.mediator.emit('hull.currentUser', app.core.currentUser)
-        else
-          $.removeCookie(cookieName, path: "/")
-          app.core.currentUser = false
-          app.core.mediator.emit('hull.currentUser', app.core.currentUser)
-
-        app.sandbox.config ?= {}
-        app.sandbox.config.curentUser = app.core.currentUser
-
-      ###
-      # Sends the message described by @params to easyXDM
-      # @param {Object} contains the provider, uri and parameters for the message
-      # @param {Function} optional a success callback
-      # @param {Function} optional an error callback
-      # @return {Promise}
-      ###
-      message = (params, callback, errback)->
-        console.error("Api not initialized yet") unless rpc
-        promise = core.data.deferred()
-
-        onSuccess = (res)->
-          if res.provider == 'hull' && res.headers
-            setCurrentUser(res.headers)
-          callback(res.response)
-          promise.resolve(res.response)
-
-        onError = (err)->
-          errback(err)
-          promise.reject(err)
-
-        rpc.message params, onSuccess, onError
-
-        promise
+    module =
+      require:
+        paths:
+          easyXDM: 'components/easyXDM/easyXDM'
+          backbone: 'components/backbone/backbone'
+          cookie: 'components/jquery.cookie/jquery.cookie'
+        shim:
+          easyXDM: { exports: 'easyXDM' }
+          backbone: { exports: 'Backbone', deps: ['underscore', 'jquery'] }

       ###
       # Normalizes the parameters defining a message. At this point, they can have two forms
@@ -115,7 +28,7 @@
       # @param {Array} the parameters for the API calls
       # @return {Array} The normalized form of parameters
       ###
-      normalizeAPIArguments = (argsArray)->
+      normalizeArguments: (argsArray)->
         defaultProvider = 'hull'
         description = argsArray.shift()
         params = {}
@@ -129,233 +42,309 @@

         path        = path.substring(1) if path[0] == "/"
         path        = [provider, path].join("/")
-        ret         = [path]
+
+        ret         = []
         ret.push(params) if params?
-        ret.concat(argsArray)
-
-      exec = (m)->
-        method = m
-        ()->
-          normalizedParams = normalizeAPIArguments (slice.call(arguments))
-          args        = extractApiArgs(normalizedParams)
-          req         = args[0]
-          req.method  = method
-          message.apply(api, args)
-
-      batch = ->
-        args      = slice.call(arguments)
-        # next      = args.shift()
-        promises  = []
-        requests  = []
-        responses = []
-        callback  = errback = null
-
-        # # Parsing the arguments...
-        while (next)
+        ret = ret.concat(argsArray)
+
+        callback = errback = null
+        params = {}
+
+        while (next = ret.shift())
           type = typeof next
-          if (type == 'function')
+          if type == 'string' && !method
+            method = next.toLowerCase()
+          else if (type == 'function' && (!callback || !errback))
             if !callback
               callback = next
-            else if !errback
+            else if (!errback)
               errback = next
+          else if (type == 'object')
+            params = _.extend(params, next)
+          else
+            throw new TypeError("Invalid argument passed to Hull.api(): " + next)
+
+        method ?= 'get'
+        callback ?= ->
+        errback  ?= (err, data)-> console.error('The request has failed: ', err, data)
+
+        [{ path: path, method: method, params: params }, callback, errback]
+
+      # Builds the URL used by easyXDM
+      buildRemoteUrl: (config)->
+        remoteUrl = "#{config.orgUrl}/api/v1/#{config.appId}/remote.html?v=#{version}"
+        remoteUrl += "&js=#{config.jsUrl}"  if config.jsUrl
+        remoteUrl += "&uid=#{config.uid}"   if config.uid
+
+      initialize: (app)->
+        core    = app.core
+        sandbox = app.sandbox
+
+        _         = require('underscore')
+        Backbone  = require('backbone')
+        easyXDM   = require('easyXDM')
+
+        slice = Array.prototype.slice
+
+        app.core.setCurrentUser = setCurrentUser = (headers={})->
+          return unless app.config.appId
+          cookieName = "hull_#{app.config.appId}"
+          currentUserId = app.core.currentUser?.id
+          if headers && headers['Hull-User-Id'] && headers['Hull-User-Sig']
+            val = btoa(JSON.stringify(headers))
+            $.cookie(cookieName, val, path: "/")
+            if currentUserId != headers['Hull-User-Id']
+              app.core.currentUser = {
+                id: headers['Hull-User-Id'],
+                sig: headers['Hull-User-Sig']
+              }
+              app.core.mediator.emit('hull.currentUser', app.core.currentUser)
+          else
+            $.removeCookie(cookieName, path: "/")
+            app.core.currentUser = false
+            app.core.mediator.emit('hull.currentUser', app.core.currentUser)
+
+          app.sandbox.config ?= {}
+          app.sandbox.config.curentUser = app.core.currentUser
+
+        ###
+        # Sends the message described by @params to easyXDM
+        # @param {Object} contains the provider, uri and parameters for the message
+        # @param {Function} optional a success callback
+        # @param {Function} optional an error callback
+        # @return {Promise}
+        ###
+        message = (params, callback, errback)->
+          console.error("Api not initialized yet") unless rpc
+          promise = core.data.deferred()
+
+          onSuccess = (res)->
+            if res.provider == 'hull' && res.headers
+              setCurrentUser(res.headers)
+            callback(res.response)
+            promise.resolve(res.response)
+
+          onError = (err)->
+            errback(err)
+            promise.reject(err)
+
+          rpc.message params, onSuccess, onError
+
+          promise
+
+
+
+        api = -> message.apply(api, module.normalizeArguments(slice.call(arguments)))
+
+        methodMap =
+          'create': 'post'
+          'update': 'put'
+          'delete': 'delete'
+          'read':   'get'
+
+        sync = (method, model, options={})->
+          url   = if _.isFunction(model.url) then model.url() else model.url
+          verb  = methodMap[method]
+
+          data = options.data
+          if !data? && model && (method == 'create' || method == 'update' || method == 'patch')
+            data = options.attrs || model.toJSON(options)
+
+          dfd = api(url, verb, data)
+          dfd.then(options.success)
+          dfd.fail(options.error)
+          dfd
+
+        setupModel = (attrs, raw)->
+          model = generateModel(attrs, raw)
+          model.on 'change', ->
+            args = slice.call(arguments)
+            eventName = ("model.hull." + model._id + '.' + 'change')
+            core.mediator.emit(eventName, { eventName: eventName, model: model, changes: args[1]?.changes })
+          dfd   = model.deferred = core.data.deferred()
+          model._id = attrs._id
+          models[attrs._id] = model #caching
+          if model.id
+            model._fetched = true
+            dfd.resolve(model)
+          else
+            model._fetched = false
+            model.fetch
+              success: ->
+                model._fetched = true
+                dfd.resolve(model)
+              error:   ->
+                dfd.fail(model)
+          model
+
+        api.model = (attrs)->
+          getFromCacheOrCreate(attrs, false)
+
+        rawFetch = getFromCacheOrCreate = (attrs, raw)->
+          attrs = { _id: attrs } if _.isString(attrs)
+          attrs._id = attrs.path unless attrs._id
+          throw new Error('A model must have an identifier...') unless attrs?._id?
+          models[attrs._id] || setupModel(attrs, raw || false)
+
+        # Clears the cache
+        app.core.mediator.on 'hull.currentUser', (hasUser)->
+          if (!hasUser)
+            models = _.pick(models, 'me', 'app', 'org')
+            collections = {}
+
+        generateModel = (attrs, raw) ->
+          _Model = if raw then RawModel else Model
+          if attrs.id
+            model = new _Model(attrs)
+          else
+            model = new _Model()
+
+        BaseHullModel = Backbone.Model.extend
+          sync: sync
+
+        RawModel = BaseHullModel.extend
+          url: ->
+            @_id || @id
+
+        Model = BaseHullModel.extend
+          url: ->
+            if (@id || @_id)
+              url = @_id || @id
             else
-              throw new Error('Incorrect arguments passed to Hull.batch(). Only callback & errback can be defined.')
-          else
-            requests.push(next)
-
-          next = args.shift()
-
-        _.map(requests, (request)->
-          if _.isString(request)
-            req = [{ path: request, method: 'get' }]
-          else
-            req = [request]
-          promises.push(message.apply(api, req))
-        )
-
-        # Actual request
-        res = core.data.when.apply($, promises)
-        res.then(callback, errback)
-        res
-
-      api = -> message.apply(api, extractApiArgs(normalizeAPIArguments(slice.call(arguments))))
-
-      methodMap =
-        'create': 'post'
-        'update': 'put'
-        'delete': 'delete'
-        'read':   'get'
-
-      sync = (method, model, options={})->
-        url   = if _.isFunction(model.url) then model.url() else model.url
-        verb  = methodMap[method]
-
-        data = options.data
-        if !data? && model && (method == 'create' || method == 'update' || method == 'patch')
-          data = options.attrs || model.toJSON(options)
-
-        dfd = api(url, verb, data)
-        dfd.then(options.success)
-        dfd.fail(options.error)
-        dfd
-
-      models = {}
-      collections = {}
-
-      setupModel = (attrs, raw)->
-        model = generateModel(attrs, raw)
-        model.on 'change', ->
-          args = slice.call(arguments)
-          eventName = ("model.hull." + model._id + '.' + 'change')
-          core.mediator.emit(eventName, { eventName: eventName, model: model, changes: args[1]?.changes })
-        dfd   = model.deferred = core.data.deferred()
-        model._id = attrs._id
-        models[attrs._id] = model #caching
-        if model.id
-          model._fetched = true
-          dfd.resolve(model)
-        else
-          model._fetched = false
-          model.fetch
-            success: ->
-              model._fetched = true
-              dfd.resolve(model)
-            error:   ->
-              dfd.fail(model)
-        model
-
-      api.model = (attrs)->
-        getFromCacheOrCreate(attrs, false)
-
-      rawFetch = getFromCacheOrCreate = (attrs, raw)->
-        attrs = { _id: attrs } if _.isString(attrs)
-        attrs._id = attrs.path unless attrs._id
-        throw new Error('A model must have an identifier...') unless attrs?._id?
-        models[attrs._id] || setupModel(attrs, raw || false)
-
-      # Clears the cache
-      app.core.mediator.on 'hull.currentUser', (hasUser)->
-        if (!hasUser)
-          models = _.pick(models, 'me', 'app', 'org')
-          collections = {}
-
-      generateModel = (attrs, raw) ->
-        _Model = if raw then RawModel else Model
-        if attrs.id
-          model = new _Model(attrs)
-        else
-          model = new _Model()
-
-      BaseHullModel = Backbone.Model.extend
-        sync: sync
-
-      RawModel = BaseHullModel.extend
-        url: ->
-          @_id || @id
-
-      Model = BaseHullModel.extend
-        url: ->
-          if (@id || @_id)
-            url = @_id || @id
-          else
-            url = @collection?.url
-          url
-
-      Collection = Backbone.Collection.extend
-        model: Model
-        sync: sync
-
-
-      setupCollection = (path)->
-        collection      = new Collection
-        collectionURI   = path
-        collection.url  = path
-        collection.on 'all', ->
-          args = slice.call(arguments)
-          eventName = ("collection." + collectionURI.replace(/\//g, ".") + '.' + args[0])
-          core.mediator.emit(eventName, { eventName: eventName, collection: collection, changes: args[1]?.changes })
-        dfd   = collection.deferred = core.data.deferred()
-        if collection.models.length > 0
-          collection._fetched = true
-          dfd.resolve(collection)
-        else
-          collection._fetched = false
-          collection.fetch
-            success: ->
-              collection._fetched = true
-              dfd.resolve(collection)
-            error:   ->
-              dfd.fail(collection)
-        collection
-
-      api.collection = (path)->
-        throw new Error('A model must have an path...') unless path?
-        throw new Error('You must specify the provider...') if (path.path && !path.provider)
-        col = setupCollection.apply(api, slice.call arguments)
-        path = normalizeAPIArguments(slice.call arguments)[0]
-        collections[path] ?= col
-        collections[path]
-
-      api.get     = exec('get')
-      api.post    = exec('post')
-      api.put     = exec('put')
-      api.delete  = exec('delete')
-      api.batch   = batch
-
-      core.data.api = api
-      core.track = (eventName, params)->
-        core.data.api({provider:"track", path: eventName}, 'post', params)
-
-      sandbox.track = (eventName, params)->
-        core.track(eventName, params)
-
-      initialized = core.data.deferred()
-
-      onRemoteMessage = -> console.warn("RPC Message", arguments)
-
-      timeout = setTimeout(
-        ()->
-          initialized.reject('Remote loading has failed. Please check "orgUrl" and "appId" in your configuration. This may also be about connectivity.')
-        , 30000)
-
-      onRemoteReady = (remoteConfig)->
-        window.clearTimeout(timeout)
-        data = remoteConfig.data
-        app.config.assetsUrl            = remoteConfig.assetsUrl
-        app.config.services             = remoteConfig.services
-        app.config.widgets.sources.hull = remoteConfig.baseUrl + '/widgets'
-        app.sandbox.config ?= {}
-        app.sandbox.config.debug        = app.config.debug
-        app.sandbox.config.assetsUrl    = remoteConfig.assetsUrl
-        app.sandbox.config.appId        = app.config.appId
-        app.sandbox.config.orgUrl       = app.config.orgUrl
-        app.sandbox.config.services     = remoteConfig.services
-        app.sandbox.config.entity_id    = data.entity?.id
-        for m in ['me', 'app', 'org', 'entity']
-          attrs = data[m]
-          if attrs
-            attrs._id = m
-            getFromCacheOrCreate(attrs, true)
-
-        initialized.resolve(data)
-
-      initialized.reject(new TypeError 'no organizationURL provided. Can\'t proceed') unless app.config.orgUrl
-      initialized.reject(new TypeError 'no applicationID provided. Can\'t proceed') unless app.config.appId
-      remoteUrl = "#{app.config.orgUrl}/api/v1/#{app.config.appId}/remote.html?v=#{version}"
-      remoteUrl += "&js=#{app.config.jsUrl}"  if app.config.jsUrl
-      remoteUrl += "&uid=#{app.config.uid}"   if app.config.uid
-
-      rpc = new easyXDM.Rpc({
-        remote: remoteUrl
-      }, {
-        remote: { message: {}, ready: {} }
-        local:  { message: onRemoteMessage, ready: onRemoteReady }
-      })
-
-      initialized
-
-    afterAppStart: (app)->
-      base.me     = rawFetch('me', true);
-      base.app    = rawFetch('app', true);
-      base.org    = rawFetch('org', true);
-      app.core.mediator.emit('hull.currentUser', app.core.currentUser)
+              url = @collection?.url
+            url
+
+        Collection = Backbone.Collection.extend
+          model: Model
+          sync: sync
+
+
+        setupCollection = (path)->
+          collection      = new Collection
+          collectionURI   = path
+          collection.url  = path
+          collection.on 'all', ->
+            args = slice.call(arguments)
+            eventName = ("collection." + collectionURI.replace(/\//g, ".") + '.' + args[0])
+            core.mediator.emit(eventName, { eventName: eventName, collection: collection, changes: args[1]?.changes })
+          dfd   = collection.deferred = core.data.deferred()
+          if collection.models.length > 0
+            collection._fetched = true
+            dfd.resolve(collection)
+          else
+            collection._fetched = false
+            collection.fetch
+              success: ->
+                collection._fetched = true
+                dfd.resolve(collection)
+              error:   ->
+                dfd.fail(collection)
+          collection
+
+        api.collection = (path)->
+          throw new Error('A model must have an path...') unless path?
+          throw new Error('You must specify the provider...') if (path.path && !path.provider)
+          col = setupCollection.apply(api, slice.call arguments)
+          path = module.normalizeArguments([path])[0].path
+          collections[path] ?= col
+          collections[path]
+
+        api.batch = ->
+          args      = slice.call(arguments)
+          # next      = args.shift()
+          promises  = []
+          requests  = []
+          responses = []
+          callback  = errback = null
+
+          # # Parsing the arguments...
+          while (next)
+            type = typeof next
+            if (type == 'function')
+              if !callback
+                callback = next
+              else if !errback
+                errback = next
+              else
+                throw new Error('Incorrect arguments passed to Hull.batch(). Only callback & errback can be defined.')
+            else
+              requests.push(next)
+
+            next = args.shift()
+
+          _.map(requests, (request)->
+            if _.isString(request)
+              req = [{ path: request, method: 'get' }]
+            else
+              req = [request]
+            promises.push(message.apply(api, req))
+          )
+
+          # Actual request
+          res = core.data.when.apply($, promises)
+          res.then(callback, errback)
+          res
+
+
+        _.each ['get', 'post', 'put', 'delete'], (method)->
+          api[method] = ()->
+            args = module.normalizeArguments (slice.call(arguments))
+            req         = args[0]
+            req.method  = method
+            message.apply(api, args)
+
+        core.data.api = api
+        core.track = (eventName, params)->
+          core.data.api({provider:"track", path: eventName}, 'post', params)
+
+        sandbox.track = (eventName, params)->
+          core.track(eventName, params)
+
+        initialized = core.data.deferred()
+
+        onRemoteMessage = -> console.warn("RPC Message", arguments)
+
+        timeout = setTimeout(
+          ()->
+            initialized.reject('Remote loading has failed. Please check "orgUrl" and "appId" in your configuration. This may also be about connectivity.')
+          , 30000)
+
+        onRemoteReady = (remoteConfig)->
+          window.clearTimeout(timeout)
+          data = remoteConfig.data
+          app.config.assetsUrl            = remoteConfig.assetsUrl
+          app.config.services             = remoteConfig.services
+          app.config.widgets.sources.hull = remoteConfig.baseUrl + '/widgets'
+          app.sandbox.config ?= {}
+          app.sandbox.config.debug        = app.config.debug
+          app.sandbox.config.assetsUrl    = remoteConfig.assetsUrl
+          app.sandbox.config.appId        = app.config.appId
+          app.sandbox.config.orgUrl       = app.config.orgUrl
+          app.sandbox.config.services     = remoteConfig.services
+          app.sandbox.config.entity_id    = data.entity?.id
+          for m in ['me', 'app', 'org', 'entity']
+            attrs = data[m]
+            if attrs
+              attrs._id = m
+              getFromCacheOrCreate(attrs, true)
+
+          initialized.resolve(data)
+
+        initialized.reject(new TypeError 'no organizationURL provided. Can\'t proceed') unless app.config.orgUrl
+        initialized.reject(new TypeError 'no applicationID provided. Can\'t proceed') unless app.config.appId
+
+        rpc = new easyXDM.Rpc({
+          remote: module.buildRemoteUrl(app.config)
+        }, {
+          remote: { message: {}, ready: {} }
+          local:  { message: onRemoteMessage, ready: onRemoteReady }
+        })
+
+        initialized
+
+      afterAppStart: (app)->
+        base.me     = rawFetch('me', true);
+        base.app    = rawFetch('app', true);
+        base.org    = rawFetch('org', true);
+        app.core.mediator.emit('hull.currentUser', app.core.currentUser)
+    module


Enable app to run behind a proxy

Enable app to run behind a proxy

--- src/coffee/app.coffee
+++ src/coffee/app.coffee
@@ -50,6 +50,7 @@
 ###
 app.configure ->
   app.set 'port', port
+  app.enable 'trust proxy'
   app.use (req, res, next) ->
     requestDomain = domain.create()
     requestDomain.add(req)


Remove useless func

Remove useless func

--- src/coffee/app.coffee
+++ src/coffee/app.coffee
@@ -78,15 +78,4 @@

 ## Usefull functions
 delay = (ms, func) ->
-  setTimeout func, ms
-
-jsonp = (url, data) ->
-  script = document.createElement('script');
-  script.src = url + '?data=' + 'test';
-  document.body.appendChild(script);
-
-__parseJSONPResponse = (data) ->
-  # now you have access to your data
-  console.log 'parsed'
-
-
+  setTimeout func, ms


(feature) add defaultState run-time option for use by product importing.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -125,6 +125,7 @@
       .option '--publish', 'When given, all changes will be published immediately'
       .option '--updatesOnly', "Won't create any new products, only updates existing"
       .option '--dryRun', 'Will list all action that would be triggered, but will not POST them to SPHERE.IO'
+      .option '--defaultState', "When given, specifies which state to assign imported products to, if they don't have one"
       .option '-m, --matchBy [value]', 'Product attribute name which will be used to match products. Possible values: id, slug, sku, <custom_attribute_name>. Default: id. Localized attribute types are not supported for <custom_attribute_name> option', 'id'
       .option '-e, --encoding [encoding]', 'Encoding used when reading data from input file | default: utf8', 'utf8'
       .usage '--projectKey <project-key> --clientId <client-id> --clientSecret <client-secret> --csv <file>'


api protocol can be configured

api protocol can be configured

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -15,6 +15,7 @@
   .describe('clientSecret', 'your OAuth client secret for the SPHERE.IO API')
   .describe('accessToken', 'an OAuth access token for the SPHERE.IO API')
   .describe('sphereHost', 'SPHERE.IO API host to connect to')
+  .describe('sphereProtocol', 'SPHERE.IO API protocol to connecto to')
   .option('excludeEmptyStocks', 'whether to skip empty stocks or not')
   .describe('channelKey', 'when you want to filter stock entries for a special channel key')
   .describe('targetDir', 'the folder where exported files are saved')
@@ -67,6 +68,7 @@
   options = _.extend credentials,
     user_agent: "#{package_json.name} - #{package_json.version}"
   options.host = argv.sphereHost if argv.sphereHost
+  options.protocol = argv.sphereProtocol if argv.sphereProtocol

   createDir = new CreateDir logger, argv.targetDir, argv.useExportTmpDir
   fetchStocks = new FetchStocks logger, options, argv.channelKey


Don't allow dropping on the empty main tree if it's not allowed.

--- src/controller.coffee
+++ src/controller.coffee
@@ -1628,8 +1628,9 @@
       head = head.next

     if head is @session.tree.end and @session.floatingBlocks.length is 0 and
-        @mainCanvas.width + @session.scrollOffsets.main.x > mainPoint.x > @session.scrollOffsets.main.x - @gutter.offsetWidth and
-        @mainCanvas.height + @session.scrollOffsets.main.y > mainPoint.y > @session.scrollOffsets.main.y
+        (@mainCanvas.width + @session.scrollOffsets.main.x > mainPoint.x > @session.scrollOffsets.main.x - @gutter.offsetWidth and
+        @mainCanvas.height + @session.scrollOffsets.main.y > mainPoint.y > @session.scrollOffsets.main.y) and
+        @getAcceptLevel(@draggingBlock, @session.tree) is helper.ENCOURAGE
       @session.view.getViewNodeFor(@session.tree).highlightArea.draw @highlightCtx
       @lastHighlight = @session.tree


CUI.Checkbox

CUI.Checkbox

- proxy setText and getText from Button

--- src/elements/Checkbox/Checkbox.coffee
+++ src/elements/Checkbox/Checkbox.coffee
@@ -110,6 +110,8 @@
 			"isActive"
 			"getGroup"
 			"setGroup"
+			"setText"
+			"getText"
 		])

 		@replace(@__checkbox)


Add reverse lookup to dependency graph

Add reverse lookup to dependency graph

--- src/core/graph.coffee
+++ src/core/graph.coffee
@@ -11,7 +11,8 @@
     @edges = []

   addEdge: (node) ->
-    @edges.push node
+    if node not in @edges
+      @edges.push node

 class Graph

@@ -32,11 +33,16 @@
     node.addEdge @nodeFor dependency

   dependenciesFor: (item) ->
-    ### return an array with all depdenencies for *item* ###
+    ### return a list of items that *item* depends on ###
     node = @nodeFor item
     resolved = @resolveNode node
     resolved.splice resolved.indexOf(node), 1
     return resolved.map (node) -> node.item
+
+  dependsOn: (item) ->
+    ### return a list of items depending on *item* ###
+    target = @nodeFor item
+    return @reverseLookup(target).map (node) -> node.item

   ### private ###

@@ -50,6 +56,14 @@
         @resolveNode edge, resolved, seen
     resolved.push node
     return resolved
+
+  reverseLookup: (target) ->
+    rv = []
+    for id, node of @nodes
+      continue if node is target
+      if target in @resolveNode node
+        rv.push node
+    return rv

 class GraphHandler

@@ -133,7 +147,7 @@
   proxy = GraphHandler.proxy contents, (dep) ->
     graph.addDependency current, dep

-  locals.contents = proxy
+  locals.contents = proxy # TODO: don't modify locals

   async.eachSeries items, (item, callback) ->
     current = item
@@ -142,4 +156,6 @@
   , (error) ->
     callback error, graph

-exports.buildGraph = buildGraph
+### Exports ###
+
+module.exports = {Graph, GraphHandler, buildGraph}


use the correct prototype

use the correct prototype

Former-commit-id: d64aa0a7ee4b853f94ccccedd9ec6f9ee0d8c974 [formerly 38dbfb6719b06ee7c15221b08b0481f9b623075c]
Former-commit-id: 7baeeaf351a4953ecdd1d86612bb18d8c17ea15a

--- src/bone/pluginator.coffee
+++ src/bone/pluginator.coffee
@@ -12,13 +12,13 @@
         0
     return plugsSort

-view = require("../bone/view")
+viewType = require("../bone/view")

 # pluginator mixin
 # handles + order plugins
 # gives a view a hierarchical / nested layout
 # a view must have a `render` method
-pluginator = view.extend
+pluginator = viewType.extend

   # mix & shake
   #mixin: (view) ->
@@ -79,7 +79,7 @@
   # TODO: check for memory leaks
   removeViews: ->
     views = @_views()
-    for key,el of @.views
+    for key,el of views
       el.undelegateEvents()
       el.unbind()
       el.removeViews() if el.removeViews?
@@ -102,8 +102,7 @@
   # The region is no longer usable after being removed.
   remove: ->
     @removeViews()
-    view.remove.apply @
-    #ViewPrototype.remove.apply @
+    viewType::remove.apply @

   _views : ->
     @.views = {} unless @.views?


Check if the paragraph is visible

Check if the paragraph is visible

--- app/js/jail_iframe/classes/icon_buttons.coffee
+++ app/js/jail_iframe/classes/icon_buttons.coffee
@@ -101,7 +101,7 @@
       @destroy()

   _valid: =>
-    @$paragraph.find('.factlink').length <= 0
+    @$paragraph.find('.factlink').length <= 0 && @$paragraph.is(':visible')

   _updatePosition: ->
     contentBox = FactlinkJailRoot.contentBox(@$paragraph[0])


Update hidden value count when items are added to selections

--- src/ext/components/model-input.coffee
+++ src/ext/components/model-input.coffee
@@ -115,6 +115,7 @@

   react _availableSearchTerm, throttle _searchAvailable, 500
   react _selectedSearchTerm, throttle _searchSelected, 500
+  react _selectedValues, throttle _searchSelected, 500

   control = createControl 'list', parameter
   control.values = _values


Nicer default values.

Nicer default values.

--- src/extras/testing/xhr_mocking.coffee
+++ src/extras/testing/xhr_mocking.coffee
@@ -17,14 +17,15 @@
   _assertXHR: (method, url, params, callback) ->
     confirmExpectation = sinon.mock()

-    _params = [200, { "Content-Type": "application/json" }, "{}"]
-    _params[0] = params["status"]   if params["status"]?
-    _params[1] = params["type"]     if params["type"]?
-    _params[2] = params["response"] if params["response"]?
+    paramsArray = [
+      params.status || 200,
+      params.type || {'Content-Type': 'application/json'},
+      params.response || "{}"
+    ]

     @server.respondWith method, url, (request) =>
       confirmExpectation()
-      request.respond.apply(request, _params)
+      request.respond.apply(request, paramsArray)

     callback?()
     @server.respond()


Add new line

Add new line

--- client/home/lib/flux/index.coffee
+++ client/home/lib/flux/index.coffee
@@ -8,4 +8,4 @@
     VirtualMachinesSearchFlux = require '../virtualmachines/flux/search'
     VirtualMachinesSearchFlux.register reactor
     VirtualMachinesSelectedMachineFlux = require '../virtualmachines/flux/selectedmachine'
-    VirtualMachinesSelectedMachineFlux.register reactor+    VirtualMachinesSelectedMachineFlux.register reactor


fix safari-11 webcam capture depracation issue

fixes COMMS-567

test plan:
- test in Safari 10 and 11
- enable user avatars
- go to /profile/settings and click the edit button on the user avatar
- select the "Take a Picture" tab
- note that webcam picture capture works

Change-Id: I54a117a9629e11225944288cfaffa90c119187cf
Reviewed-on: https://gerrit.instructure.com/134362
Tested-by: Jenkins
Reviewed-by: Venk Natarajan <f9eab59b292314637a3c917fea4742741e36a19b@instructure.com>
QA-Review: KC Naegle <9fbecd016338f9910821a8a5d5d0ae4fc9abfaa5@instructure.com>
Product-Review: Felix Milea-Ciobanu <9384aa226ac9d3237ed0bc52e18ffcb251ee0bf7@instructure.com>

--- app/coffeescripts/views/profiles/TakePictureView.coffee
+++ app/coffeescripts/views/profiles/TakePictureView.coffee
@@ -61,7 +61,8 @@

     displayMedia: (@stream) =>
       @$video.removeClass('pending')
-      @$video.attr('src', window.URL.createObjectURL(@stream))
+      try @$video.get(0).srcObject = @stream
+      catch err then @$video.attr('src', window.URL.createObjectURL(@stream))
       @$video.on('onloadedmetadata loadedmetadata', _.once(@onMediaMetadata).bind(this))

     onMediaMetadata: (e) ->


fork updates location and displayed meta info

--- client/src/app/model/games.coffee
+++ client/src/app/model/games.coffee
@@ -26,13 +26,16 @@
     return keys

-.factory 'games', ($http, $q, loggedUser, currentGame, gameConverter, gameHistory, gameTime) ->
+.factory 'games', ($http, $q, $location, loggedUser, currentGame, gameConverter, gameHistory, gameTime) ->

   saveInfo = ->
     $http.post('/games', currentGame.info)
       .then (savedGame) ->
         currentGame.info = savedGame.data
         currentGame.version.gameId = currentGame.info.id
+        $http.get("/users?id=#{currentGame.info.ownerId}")
+      .then (creator) ->
+        currentGame.creator = creator.data.username

   updateInfo = ->
     $http.put('/games', currentGame.info)
@@ -42,22 +45,21 @@
     $http.post('/game-versions', gameConverter.convertGameVersionToEmbeddedJson(currentGame.version))
       .then (savedGameVersion) -> currentGame.version = gameConverter.convertGameVersionFromEmbeddedJson(savedGameVersion.data)

-  # TODO: shouldn't this just define an object rather than returning an object? What's the role of the names?
   saveActions:
     none:
       name: 'No Action'
       execute: -> console.log 'games.saveActions.none executed'
-    createFromScratch:
-      name: 'Create'
-      execute: -> saveInfo().then(saveVersion)
     saveNewVersion:
       name: 'Save'
       execute: -> updateInfo().then(saveVersion)
     fork:
       name: 'Fork'
       execute: ->
+        # Removing the game ID will make the server provide a new one
         delete currentGame.info.id
-        saveInfo().then(saveVersion)
+        saveInfo().then ->
+          $location.path("/game/#{currentGame.version.gameId}/edit")
+          saveVersion()

   loadJson: (gameName) ->
     deferred = $q.defer()
@@ -65,7 +67,6 @@
     propertyNames = @propertyNames
     $http.get("/assets/games/#{gameName}.json")
       .error((error) ->
-          # TODO Where does this line go ? showMessage(MessageType.Error, "Cannot load game files")
           deferred.reject error
         )
       .success((game) ->
@@ -85,10 +86,8 @@
       return @saveActions.none
     if currentGame.info.id and currentGame.info.ownerId is loggedUser.profile.id
       return @saveActions.saveNewVersion
-    else if currentGame.info.ownerId
+    else
       return @saveActions.fork
-    else
-      return @saveActions.createFromScratch

   loadAll: ->
     allGames = []


fix api error json

fix api error json

--- fav.coffee
+++ fav.coffee
@@ -15,7 +15,7 @@
     if @session?.twitter?
       callback()
     else
-      "{errors:[{message:'login first to use api.'}]}"
+      '{"error":"login first to use api."}'

   @get '/api/lookup/:screen_name': ->
     path = "/users/lookup.json?screen_name=#{encodeURIComponent @params.screen_name}&include_entities=true&suppress_response_codes=true"


Fix typo: `toogle` -> `toggle`

Fix typo: `toogle` -> `toggle`

--- app/assets/javascripts/map.js.coffee
+++ app/assets/javascripts/map.js.coffee
@@ -9,7 +9,7 @@

     $('.js-toggle-map').on
         click: ->
-          App.Map.toogleMap()
+          App.Map.toggleMap()

   initializeMap: (element) ->
     App.Map.cleanInvestmentCoordinates(element)
@@ -106,7 +106,7 @@
           add_marker=createMarker(i.lat , i.long)
           add_marker.bindPopup(contentPopup(i.investment_title, i.investment_id, i.budget_id))

-  toogleMap: ->
+  toggleMap: ->
       $('.map').toggle()
       $('.js-location-map-remove-marker').toggle()


fixed preventDefault in navbar

fixed preventDefault in navbar

--- app/assets/javascripts/navbar.coffee
+++ app/assets/javascripts/navbar.coffee
@@ -8,8 +8,6 @@
   $navbar = $(".navbar-nav")
   $navbar.on "click", "a", (evt)->

-    evt.preventDefault()
-
     $navbar.find(".active").removeClass("active")
     $(evt.target).parent().addClass("active")


Add method to handle adding ship to name with button

--- app/assets/javascripts/orders.coffee
+++ app/assets/javascripts/orders.coffee
@@ -139,7 +139,10 @@
   event.preventDefault()
   addTrackingRow()

-$(document).on "click", "#suggested-addresses button", ->
+$(document).on "click", "button.suggested-name", ->
+  $("#order_ship_to_name").val $(@).text()
+
+$(document).on "click", "button.suggested-address", ->
   $("#order_ship_to_address").val $(@).text()

 $(document).on "change", ".order-row .category", ->


Fixes regression if merged and then optimized

--- app/assets/javascripts/upload.coffee
+++ app/assets/javascripts/upload.coffee
@@ -397,7 +397,7 @@

   $('#userManuscript-uploadButton-call').on 'click', (e) ->
     e.preventDefault()
-    if merged != undefined
+    if merged != undefined && result == undefined
       result = merged
     if result== undefined
       result =document.getElementById('upload-userManuscript').files[0]


Update accounts template fields

Update accounts template fields

--- app/client/bootstrap/accounts.coffee
+++ app/client/bootstrap/accounts.coffee
@@ -1,3 +1,10 @@
+AccountsTemplates.removeField 'password'
+AccountsTemplates.addField
+  _id: 'password'
+  type: 'password'
+  required: true
+  minlenght: 6
+
 AccountsTemplates.addField
   _id: 'username'
   type: 'text'
@@ -5,10 +12,15 @@
   min: 3
   max: 32
   re: /^[\w]{3,32}$/
+  errStr: 'invalid username'

 AccountsTemplates.addField
   _id: 'fullname'
   type: 'text'
+  min: 6
+  max: 140
   display: 'Fullname'
   required: true
   trim: true
+  re: /^[\w\s]{3,140}$/
+  errStr: 'invalid fullname'


add comment about background flush limit

--- app/coffee/DeleteQueueManager.coffee
+++ app/coffee/DeleteQueueManager.coffee
@@ -70,6 +70,10 @@
             if Settings.shuttingDown
                 logger.warn "discontinuing background flush due to shutdown"
                 return
-            DeleteQueueManager.flushAndDeleteOldProjects {timeout:1000,min_delete_age:3*60*1000,limit:1000}, (err, flushed) ->
+            DeleteQueueManager.flushAndDeleteOldProjects {
+                timeout:1000,
+                min_delete_age:3*60*1000,
+                limit:1000 # high value, to ensure we always flush enough projects
+            }, (err, flushed) ->
                 setTimeout doFlush, (if flushed > 10 then SHORT_DELAY else LONG_DELAY)
         doFlush()


Remove console.log

Remove console.log

--- test/cabin-spec.coffee
+++ test/cabin-spec.coffee
@@ -30,7 +30,6 @@
   it 'should redirect unauthenticated users to /login', (done) ->
     server.inject method: 'GET', url: '/liste', (res) ->
       assert.equal res.raw.res.statusCode, 401
-      console.log res.raw.res._headers.location
       done()

   it 'should display a list of cabins', (done) ->


Added type checking

Added type checking

--- scripts/steam.coffee
+++ scripts/steam.coffee
@@ -103,6 +103,9 @@
                 params.account_id = id
                 history params, 'profile URL'
         else
+            if typeof value isnt 'number'
+                msg.reply "Invalid Steam ID entered. Please ensure you have included the -u flag if you are trying to search by profile URL."
+                return
             history params

     robot.respond /dota match (\d+)( \d*)?/i, (msg) ->


tidy and unpend tests

tidy and unpend tests

--- test/error_test.coffee
+++ test/error_test.coffee
@@ -1,7 +1,4 @@
 objective 'Error handling', ->
-
-    # no longer callign the error handler,
-    # let the promise reject if no ee in args

     it 'injects the error if (ee)',


Correction problème historique

Correction problème historique

--- server/api/me.coffee
+++ server/api/me.coffee
@@ -11,7 +11,7 @@
 .get access.logged, (req, res) ->
   res.send req.session.user

-app.route '/consommation/:lieu'
+app.route '/consommation'
 .get access.logged, (req, res) ->
   utils.requestAndSend(
     req,
@@ -36,6 +36,8 @@
     [(req.query.limit or 10), (req.query.skip or 0)],
     "GET consommations"
   )
+
+app.route '/consommation/:lieu'
 .post(
   access.logged,
   middles.getLieu,


Enabled technical works

Enabled technical works

--- server/server.coffee
+++ server/server.coffee
@@ -54,13 +54,11 @@

 configurePassport(app, db)

-###
 app.use (req, res, next) ->
     if not req.user?.admin
-        res.status(503).send("На сервере проводятся технические работы. Ожидаемое время восстановления — вторая половина для 9 мая.")
+        res.status(503).send("На сервере проводятся технические работы. Ожидаемая продолжительность — до часа.")
         return
     next()
-###

 setupApi(app)


use i18n to show hero name in hero-select-view

--- app/views/core/HeroSelectView.coffee
+++ app/views/core/HeroSelectView.coffee
@@ -28,7 +28,7 @@
     # @heroes = new ThangTypes({}, { project: ['original', 'name', 'heroClass, 'slug''] })
     # @supermodel.trackRequest @heroes.fetchHeroes()

-    api.thangTypes.getHeroes({ project: ['original', 'name', 'shortName', 'heroClass', 'slug', 'ozaria'] }).then (heroes) =>
+    api.thangTypes.getHeroes({ project: ['original', 'name', 'shortName', 'i18n', 'heroClass', 'slug', 'ozaria'] }).then (heroes) =>
       @heroes = heroes.filter((h) => !h.ozaria)
       @debouncedRender()


Fix tests

Fix tests

--- test/usage_test.coffee
+++ test/usage_test.coffee
@@ -10,7 +10,7 @@
   fldata = fs.readFileSync 'test/data/sample_trivial.md', 'utf-8'

   it 'should be a brunch plugin', ->
-    expect(cb.brunchPlugin).to.be.an 'object'
+    expect(cb).to.be.an 'object'
   it 'should be typed as "template"', ->
     expect(cb.type).to.equal 'template'


added pr changes, 'isEmpty' should probably throw errors in the future

--- testRequirement.coffee
+++ testRequirement.coffee
@@ -82,7 +82,7 @@
       else
         re = RegExp substring, "m" # multiline matching
       if objectValueStr
-        match = objectValueStr.match re
+        match = re.test objectValueStr

       # if substring in object value str
       return true if match
@@ -94,14 +94,26 @@

 # Check if specific field value is empty
 isEmpty = (objectKey, emptyTest, object) ->
+  if objectKey not of object
+    # this field does not exist so return true that it's "empty"
+    # TODO: throw an error
+    return true
+
   objectValue = object[objectKey]
+  objectValueIsCorrectType = (objectValue isnt null) and (typeof objectValue isnt 'boolean')
   shouldBeEmpty = emptyTest['empty']

-  if shouldBeEmpty #return true if objectValue is empty
+  if shouldBeEmpty and objectValueIsCorrectType #return true if objectValue is empty
     return objectValue.length is 0

-  else if not shouldBeEmpty #return true if objectValue is not empty
+  else if not shouldBeEmpty and objectValueIsCorrectType #return true if objectValue is not empty
     return objectValue.length isnt 0
+
+  else #objectValue is not correct type, so makes no sense to check if it's empty
+    #TODO: throw an error
+    return true
+
+

 # Check if the test is a pubdate test
 isPubdateTest = (testValues) ->
@@ -110,7 +122,7 @@
 # Extract specific time from the document pubdate field and compare it with the test
 matchesPubdate = (objectKey, pubdateTest, object) ->
   objectValue = object[objectKey]
-  year = new Date(objectValue * 1000).getFullYear() # convert to milliseconds
+  year = new Date(objectValue).getFullYear() # convert to milliseconds

   if 'year' of pubdateTest # compare years
     testValue = pubdateTest['year']
@@ -129,8 +141,8 @@
   objectValue = object[objectKey]
   objectValue = JSON.stringify(objectValue) # match in stringified arrays and objects too
   re = RegExp testValue, "im"
-  match = objectValue.match re
-  return if match then true else false
+  match = re.test objectValue
+  return match

 # "And" the results of multiple tests
 andTest = (object, tests) ->


linux: it is now safe to use clipboard in renderer process

--- atom/common/api/lib/clipboard.coffee
+++ atom/common/api/lib/clipboard.coffee
@@ -1,14 +1,9 @@
-if process.platform is 'linux' and process.type is 'renderer'
-  # On Linux we could not access clipboard in renderer process.
-  module.exports = require('remote').require 'clipboard'
-else
-  binding = process.atomBinding 'clipboard'
-
-  module.exports =
-    has: (format, type='standard') -> binding._has format, type
-    read: (format, type='standard') -> binding._read format, type
-    readText: (type='standard') -> binding._readText type
-    writeText: (text, type='standard') -> binding._writeText text, type
-    readImage: (type='standard') -> binding._readImage type
-    writeImage: (image, type='standard') -> binding._writeImage image, type
-    clear: (type='standard') -> binding._clear type
+binding = process.atomBinding 'clipboard'
+module.exports =
+  has: (format, type='standard') -> binding._has format, type
+  read: (format, type='standard') -> binding._read format, type
+  readText: (type='standard') -> binding._readText type
+  writeText: (text, type='standard') -> binding._writeText text, type
+  readImage: (type='standard') -> binding._readImage type
+  writeImage: (image, type='standard') -> binding._writeImage image, type
+  clear: (type='standard') -> binding._clear type


Delay recognising query as search-engine query.

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -324,7 +324,7 @@
   searchEngines: {}

   filter: (queryTerms, onComplete) ->
-    {url: url, description: description} = this.getSearchEngineMatches(queryTerms[0])
+    {url: url, description: description} = @getSearchEngineMatches queryTerms
     suggestions = []
     if url
       url = url.replace(/%s/g, Utils.createSearchQuery queryTerms[1..])
@@ -343,8 +343,8 @@
   refresh: ->
     this.searchEngines = root.Settings.getSearchEngines()

-  getSearchEngineMatches: (queryTerm) ->
-    this.searchEngines[queryTerm] || {}
+  getSearchEngineMatches: (queryTerms) ->
+    if 1 < queryTerms.length and engine = @searchEngines[queryTerms[0]] then engine else {}

 # A completer which calls filter() on many completers, aggregates the results, ranks them, and returns the top
 # 10. Queries from the vomnibar frontend script come through a multi completer.


For javascript URLs, do not match javascript content.

We only match the text "javascript:...".

The affects only the bookmark completer.

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -66,14 +66,10 @@
            #{@highlightQueryTerms Utils.escapeHtml @title}

-          #{insertTextIndicator}#{@highlightUrlTerms Utils.escapeHtml @simplifyJavascriptUrls @shortenUrl()}
+          #{insertTextIndicator}#{@highlightUrlTerms Utils.escapeHtml @shortenUrl()}
           #{relevancyHtml}

         """
-
-  # Simplify "javascript:" URLs; show them as "javascript:..."; see #961.
-  simplifyJavascriptUrls: (url) ->
-    if Utils.hasJavascriptPrefix(url) then "javascript:..." else url

   # Use neat trick to snatch a domain (http://stackoverflow.com/a/8498668).
   getUrlRoot: (url) ->
@@ -150,6 +146,8 @@

   # Simplify a suggestion's URL (by removing those parts which aren't useful for display or comparison).
   shortenUrl: () ->
+    # If we already have display URL, then use it.
+    @shortUrl ?= @displayUrl
     return @shortUrl if @shortUrl?
     # We get easier-to-read shortened URLs if we URI-decode them.
     url = (Utils.decodeURIByParts(@url) || @url).toLowerCase()
@@ -208,7 +206,10 @@
       if @currentSearch.queryTerms.length > 0
         @bookmarks.filter (bookmark) =>
           suggestionTitle = if usePathAndTitle then bookmark.pathAndTitle else bookmark.title
-          RankingUtils.matches(@currentSearch.queryTerms, bookmark.url, suggestionTitle)
+          bookmark.hasJavascriptPrefix ?= Utils.hasJavascriptPrefix bookmark.url
+          bookmark.displayUrl ?= "javascript:..." if bookmark.hasJavascriptPrefix
+          suggestionUrl = bookmark.displayUrl ? bookmark.url
+          RankingUtils.matches(@currentSearch.queryTerms, suggestionUrl, suggestionTitle)
       else
         []
     suggestions = results.map (bookmark) =>
@@ -216,6 +217,7 @@
         queryTerms: @currentSearch.queryTerms
         type: "bookmark"
         url: bookmark.url
+        displayUrl: bookmark.displayUrl
         title: if usePathAndTitle then bookmark.pathAndTitle else bookmark.title
         relevancyFunction: @computeRelevancy
     onComplete = @currentSearch.onComplete
@@ -252,7 +254,7 @@
     bookmark.children.forEach((child) => @traverseBookmarksRecursive child, results, bookmark) if bookmark.children

   computeRelevancy: (suggestion) ->
-    RankingUtils.wordRelevancy(suggestion.queryTerms, suggestion.url, suggestion.title)
+    RankingUtils.wordRelevancy(suggestion.queryTerms, suggestion.displayUrl ? suggestion.url, suggestion.title)

 class HistoryCompleter
   filter: ({ queryTerms, seenTabToOpenCompletionList }, onComplete) ->


Removed getOptimalWidth method from KDTabHandleView.

--- components/tabs/KDTabHandleView.coffee
+++ components/tabs/KDTabHandleView.coffee
@@ -39,10 +39,4 @@
     @getOptions().hidden

   getWidth: ->
-    @$().outerWidth(no) or 0
-
-  getOptimalWidth: ->
-    titleWidth   = @$().find('b').outerWidth no
-    paddingLeft  = parseInt @$().css('paddingLeft'), 10
-    paddingRight = parseInt @$().css('paddingRight'), 10
-    titleWidth + paddingLeft + paddingRight+    @$().outerWidth(no) or 0


[#18] ネストの原型みたいの出来た

[#18] ネストの原型みたいの出来た

--- src/coffee/kazitori.coffee
+++ src/coffee/kazitori.coffee
@@ -142,6 +142,8 @@
     @isIE = win.navigator.userAgent.toLowerCase().indexOf('msie') != -1
     @isOldIE = @isIE and (!docMode||docMode < 7)
     @_dispatcher = new EventDispatcher()
+    @.handlers = []
+    @.beforeHandlers = []
     @_bindBefores()
     @_bindRules()
     @_bindNotFound()
@@ -362,7 +364,6 @@
       if isBefore
         callback = @_bindFunctions(name)
       else if name instanceof Kazitori
-        console.log "nest"
         @_bindChild(rule, name)
         return @
       else if typeof name is "function"
@@ -372,6 +373,7 @@
         if name.hasOwnProperty('__super__')
           try
             child = new name({'isAutoStart':false})
+            console.log "hi"
             @_bindChild(rule, child)
             return @
           catch e
@@ -382,7 +384,6 @@
         callback = @[name]

     target = if isBefore then @.beforeHandlers else @.handlers
-    console.log "before rule instance::", rule
     target.unshift new Rule(rule, (fragment)->
       args = @.router.extractParams(@, fragment)
       callback && callback.apply(@.router, args)
@@ -390,9 +391,17 @@
     return @

   _bindChild:(rule, child)->
-    for childRule in child.handlers
+    childHandlers = child.handlers.concat()
+    for childRule in childHandlers
       childRule.update(rule)
-    @.handlers = child.handlers.concat @.handlers
+    #うーん
+    # @._groups.push new InternalGroup(rule, child.handlers)
+    @.handlers = childHandlers.concat @.handlers
+
+    childBefores = child.beforeHandlers.concat()
+    for childBefore in childBefores
+      childBefore.update(rule)
+    @.beforeHandlers = childBefores.concat @.beforeHandlers

   #ルールを後から追加する
@@ -585,7 +594,6 @@
       callback = notFoundFuncName
     else
       callback = @[notFoundFuncName]
-    console.log notFoundFragment, @.notFound
     @._notFound = new Rule(notFoundFragment, (fragment)->
       args = @.router.extractParams(@, fragment)
       callback && callback.apply(@.router, args)
@@ -902,17 +910,6 @@
     @callback = callback
     @router = router
     @update(rule)
-    # @rule = string
-    # @_regexp = @_ruleToRegExp(string)
-    # @types = []
-
-    # re = new RegExp(namedParam)
-    # matched = string.match(re)
-    # if matched isnt null
-    #   @isVariable = true
-    #   for m in matched
-    #     t = m.match(genericParam)||null
-    #     @types.push if t isnt null then t[1] else null

   #マッチするかどうかテスト
   # **args**
@@ -929,7 +926,6 @@
   update:(path)=>
     @.rule = path + @.rule
     @._regexp = @_ruleToRegExp(@.rule)
-
     re = new RegExp(namedParam)
     matched = path.match(re)
     if matched isnt null


Remove unused require.

Remove unused require.

--- src/coffee/services.coffee
+++ src/coffee/services.coffee
@@ -1,4 +1,3 @@
-querystring = require "querystring"
 builder     = require "xmlbuilder"

 debug = (msg)-> console.log "DEBUG: #{msg}"


make selector a little bit more picky

make selector a little bit more picky

--- src/flux/media.coffee
+++ src/flux/media.coffee
@@ -3,7 +3,7 @@
 {makeSimpleStore} = require './helpers'

 LINKS_BEGIN = ['#']
-LINKS_CONTAIN = ['/contents/', '.cnx.org']
+LINKS_CONTAIN = ['cnx.org/contents/']

 MEDIA_LINK_EXCLUDES = [
   '.nav'


Removed duplicate code.

Removed duplicate code.

--- lib.coffee
+++ lib.coffee
@@ -21,6 +21,11 @@
     templateInstance = getTemplateInstance templateInstance.view.parentView

   null
+
+getTemplateInstanceFunction = (view) ->
+  templateInstance = getTemplateInstance view
+  ->
+    templateInstance

 class ComponentsNamespaceReference
   constructor: (@namespace, @templateInstance) ->
@@ -140,18 +145,6 @@
     Blaze.Template._withTemplateInstanceFunc templateFunc, ->
       Blaze._wrapCatchingExceptions(f, 'template helper').apply self, args

-viewToTemplateInstance = (view) ->
-  # We skip contentBlock views which are injected by Meteor when using
-  # block helpers (in addition to block helper view). This matches more
-  # the visual structure of templates and not the internal implementation.
-  while view and (not view.template or view.name is '(contentBlock)' or view.name is '(elseBlock)')
-    view = view.originalParentView or view.parentView
-
-  # Body view has template field, but not templateInstance. We return null in that case.
-  return null unless view?.templateInstance
-
-  _.bind view.templateInstance, view
-
 addEvents = (view, component) ->
   eventsList = component.events()

@@ -166,7 +159,7 @@
           event = args[0]

           currentView = Blaze.getView event.currentTarget
-          templateInstance = viewToTemplateInstance currentView
+          templateInstance = getTemplateInstanceFunction currentView

           if Template._withTemplateInstanceFunc
             withTemplateInstanceFunc = Template._withTemplateInstanceFunc