Fix calendar.js.coffee bugs.

Fix calendar.js.coffee bugs.

--- app/assets/javascripts/calendar.js.coffee
+++ app/assets/javascripts/calendar.js.coffee
@@ -42,7 +42,7 @@
     return @months == month.months

 # Calendar
-class Calendar
+class @Calendar
   constructor: (callback) ->
     @selectedMonth = null
     @startMonth = null
@@ -66,22 +66,28 @@
     colspan = 12
     if current.month >= 2
       colspan = 12 - (current.month - 1)
-    yearClass = (current.year % 2) ? 'odd_year' : 'even_year'
+    yearClass = if current.year % 2 then 'odd_year' else 'even_year'

     str = "
"
     str += "
"
-    str += "<<	"
+    str += "<<	"
     str += "" + current.year + "	"
     if colspan < 12
       nextYear = current.year + 1
-      yearClass = (nextYear % 2) ? 'odd_year' : 'even_year'
+      yearClass = if nextYear % 2 then 'odd_year' else 'even_year'
       secondColspan = 12 - colspan
-      str += "" + nextYear + "	"
-    str += ">>	"
+      str += "" + nextYear + "	"
+    str += ">>	"
     str += "
"
     str += "
"
-    for i in [0..12]
-      yearClass = (current.year % 2) ? 'odd_year' : 'even_year'
+    for i in [0..11]
+      yearClass = if current.year % 2  then 'odd_year' else 'even_year'
       if @selectedMonth.equals(current)
         str += "
"
       else
@@ -104,3 +110,4 @@

     if call && @callback
       @callback.call()
+


remove

remove

--- app/assets/javascripts/comments.js.coffee
+++ app/assets/javascripts/comments.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/


closes #8

closes #8

--- source/javascripts/src/view_detail_mixins.coffee
+++ source/javascripts/src/view_detail_mixins.coffee
@@ -8,9 +8,11 @@
       """
     afterRender: ->
       @$el.find("code").editInPlace
+        save_if_nothing_changed: true
         callback: (uu, ent)=>
           @model.set("value", ent)
-          ent
+          if ent is "" then "..." else ent
+
   VX.name =
     html: ->
       """
@@ -18,10 +20,11 @@
       """
     afterRender: ->
       @$el.find("code").editInPlace
+        save_if_nothing_changed: true
         callback: (uu, ent)=>
           cleanName = XLF.sluggify ent
           @model.set("value", cleanName)
-          cleanName
+          if ent is "" then "..." else cleanName

   VX.required =
     html: ->


Use App.Base url set from app for ajax

Use App.Base url set from app for ajax

--- app/assets/javascripts/models/base.coffee
+++ app/assets/javascripts/models/base.coffee
@@ -54,13 +54,13 @@
       # This conditonal is for testing but there is no easy fix at the moment.
       # Put passes through data.  Patch dosn't.
       if (userAgent = window?.navigator?.userAgent).match /capybara-webkit/ || userAgent.match /PhantomJS/
-        url = @route + "/" + @id + ".json"
+        path = @route + "/" + @id + ".json"
         method = 'PUT'
       else
-        url = @route + "/" + @id + ".json"
+        path = @route + "/" + @id + ".json"
         method = 'PATCH'
     else
-      url = @route + ".json"
+      path = @route + ".json"
       method = "POST"

     params = {}
@@ -68,7 +68,7 @@

     response = $.ajax
       type: method
-      url: url
+      url: App.BaseUrl + "/" + path
       dataType: "json"
       data: params
       global: false


Remove todos

Remove todos

--- app/assets/javascripts/models/base.coffee
+++ app/assets/javascripts/models/base.coffee
@@ -118,7 +118,6 @@

   #overridable hook
   @after_save_all: (data, textStatus, xhr) ->
-    # TODO create overridable hook
     $(data).each (i, response_object) =>
       attrs = response_object[@snake_name]
       model = new App[@class_name]({uuid: attrs["uuid"]})
@@ -130,7 +129,6 @@

   #overridable hook
   @after_save_all_error: (xhr) ->
-    # TODO create overridable hook
     data = JSON.parse(xhr.responseText)
     $(data).each (i, response_object) =>
       unless $.isEmptyObject(response_object["errors"])


fix js issue if no client created

fix js issue if no client created

--- app/assets/javascripts/worklogs.js.coffee
+++ app/assets/javascripts/worklogs.js.coffee
@@ -50,11 +50,14 @@
     total_dom.html(new_total)
   getHourlyCentRate: ->
     custom_rate_per_hour = parseFloat($('#worklog_hourly_rate').val())
+    selected_client = $('#worklog_client_id option:selected')
     if custom_rate_per_hour && custom_rate_per_hour > 0
       # Convert into cents
       return custom_rate_per_hour * 100
+    else if selected_client.length > 0
+      return seleted_client.data().hourlyRateCents
     else
-      return $('#worklog_client_id option:selected').data().hourlyRateCents
+      return false
   calcTotal: (start, end, per_hour) ->
     return if !start || !end
     time_span_seconds = (end.getTime() - start.getTime()) / 1000


Support \raisebox

Support \raisebox

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -93,16 +93,23 @@
   .replace /\\includegraphics\s*(\[[^\[\]]*\]\s*)?{((?:[^{}]|{[^{}]*})*)}/g,
     (match, optional, graphic) ->
       style = ''
-      optional.replace /width\s*=\s*([0-9.]+)\s*([a-zA-Z]*)/g,
+      optional.replace /width\s*=\s*([-0-9.]+)\s*([a-zA-Z]*)/g,
         (match2, value, unit) ->
           style += "width: #{value}#{unit};"
           ''
-      .replace /height\s*=\s*([0-9.]+)\s*([a-zA-Z]*)/g,
+      .replace /height\s*=\s*([-0-9.]+)\s*([a-zA-Z]*)/g,
         (match2, value, unit) ->
           style += "height: #{value}#{unit};"
           ''
       style = ' style="' + style + '"' if style
       """"""
+  .replace /\\raisebox\s*{\s*([-0-9.]+)\s*([a-zA-Z]*)\s*}{((?:[^{}]|{[^{}]*})*)}/g,
+    (match, value, unit, arg) ->
+      if value[0] == '-'
+        value = value[1..]
+      else
+        value = "-#{value}"
+      """#{arg}"""
   .replace /\\begin\s*{(problem|theorem|conjecture|lemma|corollary)}/g, (m, p1) -> "
#{s.capitalize p1}: "
   .replace /\\end\s*{(problem|theorem|conjecture|lemma|corollary)}/g, '
'
   .replace /\\begin\s*{(proof|pf)}/g, 'Proof: '


Bug fix: only expand macros if they exist

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -107,9 +107,11 @@
     ''
   #for def, val of defs
   #  console.log "\\#{def} = #{val}"
-  r = ///\\(#{_.keys(defs).join '|'})\s*///g
-  while 0 <= tex.search(r)
-    tex = tex.replace r, (match, def) -> defs[def]
+  if 0 < _.size defs
+    r = ///\\(#{_.keys(defs).join '|'})\s*///g
+    console.log _.keys(defs).join '|'
+    while 0 <= tex.search(r)
+      tex = tex.replace r, (match, def) -> defs[def]
   ## After \def expansion and verbatim processing, protect math
   [tex, math] = preprocessKaTeX tex
   ## Start initial paragraph


testing macros. not working

testing macros. not working

--- app/assets/javascripts/interpolation.coffee
+++ app/assets/javascripts/interpolation.coffee
@@ -1,5 +1,157 @@
 # This provides interpolation mechanics. It's a lot of code. But it
 # should run fast.
+
+nextPointMacro = (output, xd, yd, zd, cube, bucketIndex0, pointIndex0, size0, size01) ->
+
+  bucketIndex = bucketIndex0
+  pointIndex  = pointIndex0
+
+  # We use bitmasks to handle x, y and z coordinates.
+  # `63     = 000000 000000 111111`
+  if xd
+    if (pointIndex & 63) == 63
+      # The point seems to be at the right border.
+      bucketIndex++
+      pointIndex &= -64
+      # Bound checking.
+      output = -1 if bucketIndex % size0 == 0
+    else
+      pointIndex++
+
+  if output != -1
+    # `4032   = 000000 111111 000000`
+    if yd
+      if (pointIndex & 4032) == 4032
+        # The point is to at the bottom border.
+        bucketIndex += size0
+        pointIndex &= -4033
+        # Bound checking.
+        output = -1 if bucketIndex % size01 == 0
+      else
+        pointIndex += 64
+
+    if output != -1
+      # `258048 = 111111 000000 000000`
+      if zd
+        if (pointIndex & 258048) == 258048
+          # The point seems to be at the back border.
+          bucketIndex += size01
+          pointIndex &= -258049
+        else
+          pointIndex += 4096
+
+      output = if (bucket = cube[bucketIndex])?
+        bucket[pointIndex]
+      else
+        -1
+
+pointMacro = (output, xD, yD, zD) ->
+  nextPointMacro(output, xD, yD, zD, cube, bucketIndex0, pointIndex0, size0, size01)
+  return buffer0[j4] = output if output <= 0
+
+collectMacro = (x, y, z, buffer0, buffer1, bufferDelta, j4, j3, cube, ll0, ll1, ll2, ur0, ur1, ur2, size0, size01) ->
+
+  if x < 0 or y < 0 or z < 0
+    buffer0[j4] = -2
+    return
+
+  # Bound checking is necessary.
+  if x < ll0 or y < ll1 or z < ll2 or x > ur0 or y > ur1 or z > ur2
+    buffer0[j4] = -1
+    return
+
+  # Bitwise operations are faster than javascript's native rounding functions.
+  x0 = x >> 0; xd = x - x0
+  y0 = y >> 0; yd = y - y0
+  z0 = z >> 0; zd = z - z0
+
+  bucketIndex0 =
+    ((x0 - ll0) >> 6) +
+    ((y0 - ll1) >> 6) * size0 +
+    ((z0 - ll2) >> 6) * size01
+
+  pointIndex0 =
+    ((x0 & 63)) +
+    ((y0 & 63) << 6) +
+    ((z0 & 63) << 12)
+
+  pointMacro(output0, false, false, false)
+
+  if xd == 0
+    if yd == 0
+      unless zd == 0
+        # linear z
+        pointMacro(output1, false, false, true)
+
+        bufferDelta[j3] = zd
+
+    else
+      if zd == 0
+        # linear y
+        pointMacro(output1, false, true, false)
+
+        bufferDelta[j3] = yd
+
+      else
+        # bilinear y,z
+        pointMacro(output1, false, true, false)
+        pointMacro(output2, false, false, true)
+        pointMacro(output3, false, true, true)
+
+        bufferDelta[j3]     = yd
+        bufferDelta[j3 + 1] = zd
+
+  else
+    if yd == 0
+      if zd == 0
+        # linear x
+        pointMacro(output1, true, false, false)
+
+        bufferDelta[j3] = xd
+
+      else
+        #bilinear x,z
+        pointMacro(output1, true, false, false)
+        pointMacro(output2, false, false, true)
+        pointMacro(output3, true, false, true)
+
+        bufferDelta[j3]     = xd
+        bufferDelta[j3 + 1] = zd
+
+    else
+      if zd == 0
+        # bilinear x,y
+        pointMacro(output1, true, false, false)
+        pointMacro(output2, false, true, false)
+        pointMacro(output3, true, true, false)
+
+        bufferDelta[j3]     = xd
+        bufferDelta[j3 + 1] = yd
+
+      else
+        # trilinear x,y,z
+        pointMacro(output1, true, false, false)
+        pointMacro(output2, false, true, false)
+        pointMacro(output3, true, true, false)
+        pointMacro(output4, false, false, true)
+        pointMacro(output5, true, false, true)
+        pointMacro(output6, false, true, true)
+        pointMacro(output7, true, true, true)
+
+
+        bufferDelta[j3]     = xd
+        bufferDelta[j3 + 1] = yd
+        bufferDelta[j3 + 2] = zd
+
+  buffer0[j4]     = output0
+  buffer0[j4 + 1] = output1 || 0
+  buffer0[j4 + 2] = output2 || 0
+  buffer0[j4 + 3] = output3 || 0
+  buffer1[j4]      = output4 || 0
+  buffer1[j4 + 1]  = output5 || 0
+  buffer1[j4 + 2]  = output6 || 0
+  buffer1[j4 + 3]  = output7 || 0
+  return

 InterpolationCollector =
   # Finding points adjacent to the already found one.
@@ -198,7 +350,7 @@
       y = vertices[i++]
       z = vertices[i++]

-      @collect(
+      collectMacro(
         x, y, z,
         bufferFront, bufferBack, bufferDelta,
         j4, j3,
@@ -211,156 +363,4 @@
       j4 += 4

     return
-
-
-  nextPointMacro : (output, xd, yd, zd, cube, bucketIndex0, pointIndex0, size0, size01) ->
-
-    bucketIndex = bucketIndex0
-    pointIndex  = pointIndex0
-
-    # We use bitmasks to handle x, y and z coordinates.
-    # `63     = 000000 000000 111111`
-    if xd
-      if (pointIndex & 63) == 63
-        # The point seems to be at the right border.
-        bucketIndex++
-        pointIndex &= -64
-        # Bound checking.
-        output = -1 if bucketIndex % size0 == 0
-      else
-        pointIndex++
-
-    if output != -1
-      # `4032   = 000000 111111 000000`
-      if yd
-        if (pointIndex & 4032) == 4032
-          # The point is to at the bottom border.
-          bucketIndex += size0
-          pointIndex &= -4033
-          # Bound checking.
-          output = -1 if bucketIndex % size01 == 0
-        else
-          pointIndex += 64
-
-      if output != -1
-        # `258048 = 111111 000000 000000`
-        if zd
-          if (pointIndex & 258048) == 258048
-            # The point seems to be at the back border.
-            bucketIndex += size01
-            pointIndex &= -258049
-          else
-            pointIndex += 4096
-
-        output = if (bucket = cube[bucketIndex])?
-          bucket[pointIndex]
-        else
-          -1
-
-  pointMacro : (output, xD, yD, zD) ->
-    nextPointMacro(output, xD, yD, zD, cube, bucketIndex0, pointIndex0, size0, size01)
-    return buffer0[j4] = output if output <= 0
-
-  collectMacro : (x, y, z, buffer0, buffer1, bufferDelta, j4, j3, cube, ll0, ll1, ll2, ur0, ur1, ur2, size0, size01) ->
-
-    if x < 0 or y < 0 or z < 0
-      buffer0[j4] = -2
-      return
-
-    # Bound checking is necessary.
-    if x < ll0 or y < ll1 or z < ll2 or x > ur0 or y > ur1 or z > ur2
-      buffer0[j4] = -1
-      return
-
-    # Bitwise operations are faster than javascript's native rounding functions.
-    x0 = x >> 0; xd = x - x0
-    y0 = y >> 0; yd = y - y0
-    z0 = z >> 0; zd = z - z0
-
-    bucketIndex0 =
-      ((x0 - ll0) >> 6) +
-      ((y0 - ll1) >> 6) * size0 +
-      ((z0 - ll2) >> 6) * size01
-
-    pointIndex0 =
-      ((x0 & 63)) +
-      ((y0 & 63) << 6) +
-      ((z0 & 63) << 12)
-
-    pointMacro(output0, false, false, false)
-
-    if xd == 0
-      if yd == 0
-        unless zd == 0
-          # linear z
-          pointMacro(output1, false, false, true)
-
-          bufferDelta[j3] = zd
-
-      else
-        if zd == 0
-          # linear y
-          pointMacro(output1, false, true, false)
-
-          bufferDelta[j3] = yd
-
-        else
-          # bilinear y,z
-          pointMacro(output1, false, true, false)
-          pointMacro(output2, false, false, true)
-          pointMacro(output3, false, true, true)
-
-          bufferDelta[j3]     = yd
-          bufferDelta[j3 + 1] = zd
-
-    else
-      if yd == 0
-        if zd == 0
-          # linear x
-          pointMacro(output1, true, false, false)
-
-          bufferDelta[j3] = xd
-
-        else
-          #bilinear x,z
-          pointMacro(output1, true, false, false)
-          pointMacro(output2, false, false, true)
-          pointMacro(output3, true, false, true)
-
-          bufferDelta[j3]     = xd
-          bufferDelta[j3 + 1] = zd
-
-      else
-        if zd == 0
-          # bilinear x,y
-          pointMacro(output1, true, false, false)
-          pointMacro(output2, false, true, false)
-          pointMacro(output3, true, true, false)
-
-          bufferDelta[j3]     = xd
-          bufferDelta[j3 + 1] = yd
-
-        else
-          # trilinear x,y,z
-          pointMacro(output1, true, false, false)
-          pointMacro(output2, false, true, false)
-          pointMacro(output3, true, true, false)
-          pointMacro(output4, false, false, true)
-          pointMacro(output5, true, false, true)
-          pointMacro(output6, false, true, true)
-          pointMacro(output7, true, true, true)
-
-
-          bufferDelta[j3]     = xd
-          bufferDelta[j3 + 1] = yd
-          bufferDelta[j3 + 2] = zd
-
-    buffer0[j4]     = output0
-    buffer0[j4 + 1] = output1 || 0
-    buffer0[j4 + 2] = output2 || 0
-    buffer0[j4 + 3] = output3 || 0
-    buffer1[j4]      = output4 || 0
-    buffer1[j4 + 1]  = output5 || 0
-    buffer1[j4 + 2]  = output6 || 0
-    buffer1[j4 + 3]  = output7 || 0
-    return
+


fix #1257

fix #1257

--- app/coffee/modules/common/components.coffee
+++ app/coffee/modules/common/components.coffee
@@ -242,24 +242,25 @@
         $scope.$watch $attrs.ngModel, (instance) ->
             renderAssignedTo(instance)

-        $el.on "click", ".user-assigned", (event) ->
-            event.preventDefault()
-            $scope.$apply ->
-                $rootscope.$broadcast("assigned-to:add", $model.$modelValue)
-
-        $el.on "click", ".icon-delete", (event) ->
-            event.preventDefault()
-            title = "Remove assigned to"
-            subtitle = ""
-
-            $confirm.ask(title, subtitle).then (finish) =>
-                finish()
-                $model.$modelValue.assigned_to  = null
+        if editable
+            $el.on "click", ".user-assigned", (event) ->
+                event.preventDefault()
+                $scope.$apply ->
+                    $rootscope.$broadcast("assigned-to:add", $model.$modelValue)
+
+            $el.on "click", ".icon-delete", (event) ->
+                event.preventDefault()
+                title = "Remove assigned to"
+                subtitle = ""
+
+                $confirm.ask(title, subtitle).then (finish) =>
+                    finish()
+                    $model.$modelValue.assigned_to  = null
+                    renderAssignedTo($model.$modelValue)
+
+            $scope.$on "assigned-to:added", (ctx, userId) ->
+                $model.$modelValue.assigned_to = userId
                 renderAssignedTo($model.$modelValue)
-
-        $scope.$on "assigned-to:added", (ctx, userId) ->
-            $model.$modelValue.assigned_to = userId
-            renderAssignedTo($model.$modelValue)

     return {
         link:link,


fix route for draft-page

fix route for draft-page

--- app/components/draft-page/draft-page.coffee
+++ app/components/draft-page/draft-page.coffee
@@ -1,5 +1,5 @@
 module.exports =
-  url: '/groups/:groupId/drafts'
+  url: '/groups/:groupId/drafts/:draftId'
   template: require('./draft-page.html')
   controller: ($scope, Records, $stateParams, $location) ->
     window.scrollHeight = 0;


fix: dropdown menu click preventDefault

fix: dropdown menu click preventDefault

--- app/assets/javascripts/active_admin/lib/dropdown-menu.js.coffee
+++ app/assets/javascripts/active_admin/lib/dropdown-menu.js.coffee
@@ -69,6 +69,7 @@
       e.stopPropagation()
       unless @isDisabled()
         if @isOpen then @close() else @open()
+      false

   _positionMenuList: ->
     button_center = @$menuButton.position().left + @$menuButton.outerWidth() / 2


Wrap geocode callback in ., fixing inconsistent updates

--- app/assets/javascripts/darkswarm/services/enterprises.js.coffee
+++ app/assets/javascripts/darkswarm/services/enterprises.js.coffee
@@ -53,12 +53,13 @@

     calculateDistanceGeo: (query) ->
       Geo.geocode query, (results, status) =>
-        if status == Geo.OK
-          #console.log "Geocoded #{query} -> #{results[0].geometry.location}."
-          @setDistanceFrom results[0].geometry.location
-        else
-          console.log "Geocoding failed for the following reason: #{status}"
-          @resetDistance()
+        $rootScope.$apply =>
+          if status == Geo.OK
+            #console.log "Geocoded #{query} -> #{results[0].geometry.location}."
+            @setDistanceFrom results[0].geometry.location
+          else
+            console.log "Geocoding failed for the following reason: #{status}"
+            @resetDistance()

     setDistanceFrom: (locatable) ->
       for enterprise in @enterprises


for #273 -  search shows 20 result per page again

--- app/assets/javascripts/modules/search/queries/base_query.coffee
+++ app/assets/javascripts/modules/search/queries/base_query.coffee
@@ -1,6 +1,6 @@
 # TODO: gueltige werte fuer category?
 class Clarat.Search.BaseQuery
-  PER_PAGE: 3 # 20
+  PER_PAGE: 20
   BASE_PRECISION: 500

   constructor:


first pass at new method for calculating avatar size on histogram

Former-commit-id: eab791bc86855e23056828337888e4df39994b9f [formerly b736a15105d6c7755f29b4bbaedb669bacc5bf7c] [formerly 8aa3cc87f41baf7f9923bbcbf12aa477ea5c5a37 [formerly 9e3b06f8ef09e4921cc92950ddadd0f6b48f0fb6]] [formerly 01d705483feee11bb5f527ad6ae4ec84c1a343d1 [formerly 95f3355440bd8aae006a01097fbe49278df76dc2] [formerly d8323cada23d90da72ad493f9480c6c265c20940 [formerly d8323cada23d90da72ad493f9480c6c265c20940 [formerly a9404fb421b94c985f1e99c6d452cffe098e692c]]]]
Former-commit-id: a9d84c3e19e1d019b1bb8020a542dbe604205493 [formerly 2225f7230c294725a95b8dd85d31b77afb056f04] [formerly 734ded6cd09949136d6e93992b13f37fa23f26ed [formerly 51e30a93b1af160abc9e235ce909c1d43ff0b0fc]]
Former-commit-id: 2089228dd452b9925aa59c4f4a2763d61fe60cf6 [formerly 4acbd10cb62fcebdb499339bafa0250259a37018]
Former-commit-id: dfb98b011ba3613218f4104902e02201f8693991
Former-commit-id: d0c7c59abfceceaeeb3aa7febb19bff1b4490490

--- @client/histogram.coffee
+++ @client/histogram.coffee
@@ -527,12 +527,93 @@

-
+#####
+# Calculate node radius based on the largest density of avatars in an
+# area (based on a moving average of # of opinions, mapped across the
+# width and height)
+
+calculateAvatarRadius = (width, height, opinions) ->
+
+  opinions.sort (a,b) -> a.stance - b.stance
+
+  # first, calculate a moving average of the number of opinions
+  # across around all possible stances
+  window_size = .1
+  avg_inc = .01
+  moving_avg = []
+  idx = 0
+  stance = -1.0
+  sum = 0
+
+  while stance <= 1.0
+
+    o = idx
+    cnt = 0
+    while o < opinions.length
+
+      if opinions[o].stance < stance - window_size
+        idx = o
+      else if opinions[o].stance > stance + window_size
+        break
+      else
+        cnt += 1
+
+      o += 1
+
+    moving_avg.push cnt
+    stance += avg_inc
+    sum += cnt
+
+  # second, calculate the densest area of opinions, operationalized
+  # as the region with the most opinions amongst all regions of
+  # opinion space that have contiguous above average opinions.
+  regions = []
+  avg_avg = sum / moving_avg.length
+
+  current = []
+  for avg, idx in moving_avg
+    reset = idx == moving_avg.length - 1
+    if avg >= avg_avg
+      current.push idx
+    else
+      reset = true
+
+    if reset && current.length > 0
+      regions.push [current[0] * avg_inc - 1.0 - window_size , \
+                    idx * avg_inc - 1.0 + window_size ]
+      current = []
+
+  max_opinions = 0
+  max_region = null
+  for region in regions
+    cnt = 0
+    for o in opinions
+      if o.stance >= region[0] && \
+         o.stance <= region[1]
+        cnt += 1
+    if cnt > max_opinions
+      max_opinions = cnt
+      max_region = region
+
+  # Third, calculate the avatar radius we'll use. It is based on
+  # trying to fill 50% of the densest area of the histogram
+
+  if max_opinions > 0
+    r = Math.sqrt(Math.abs(max_region[0] - max_region[1]) / 2 * width * height * .5 / max_opinions) / 2
+  else
+    r = calculateAvatarRadiusOld(width, height, opinions)
+
+  console.log r, calculateAvatarRadiusOld(width, height, opinions)
+
+  r = Math.min(r, width / 2, height / 2)
+
+  r
+  #calculateAvatarRadiusOld(width, height, opinions)

 #####
 # Calculate node radius based on size of area and number of nodes

-calculateAvatarRadius = (width, height, opinions) ->
+calculateAvatarRadiusOld = (width, height, opinions) ->
   ratio_filled = .22

   # if window.histogram_ratio_filled


use Location.all for reading location_id

--- client/skr/models/SalesOrder.coffee
+++ client/skr/models/SalesOrder.coffee
@@ -44,7 +44,7 @@
     associations:
         customer:         { model: "Customer", required: true }
         location:         { model: "Location", required: true, default: ->
-            Skr.Models.Location.fetchById(@location_id) if @location_id
+            Skr.Models.Location.all.get(@location_id) if @location_id
         }
         terms:            { model: "PaymentTerm"  }
         billing_address:  { model: "Address"      }


Added copy stub.

Added copy stub.

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -118,6 +118,7 @@
 			return callback exec_err if exec_err
 			return callback null if callback

-	exec: () -> throw new Error "not implemented"
-	spawn: () -> throw new Error "not implemented"
+	copy: () -> throw new Error "copy not implemented"
+	exec: () -> throw new Error "exec not implemented"
+	spawn: () -> throw new Error "spawn not implemented"


:new: Add flags support in parse

:new: Add flags support in parse

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -85,11 +85,12 @@
   parse: (data, rawRegex, options = {baseReduction: 1}) ->
     throw new Error "Nothing to parse" unless arguments.length
     XRegExp ?= require('xregexp').XRegExp
+    options.flags ?= ""
     toReturn = []
     if xcache.has(rawRegex)
       regex = xcache.get(rawRegex)
     else
-      xcache.set(rawRegex, regex = XRegExp(rawRegex))
+      xcache.set(rawRegex, regex = XRegExp(rawRegex, options.flags))
     throw new Error("Input must be a string") unless typeof data is 'string'
     for line in data.split(/\r?\n/)
       match = XRegExp.exec(line, regex)


Give each JS client websocket a number to use in debug logs

--- bokehjs/src/coffee/common/client.coffee
+++ bokehjs/src/coffee/common/client.coffee
@@ -95,11 +95,17 @@

 class ClientConnection

+  @_connection_count : 0
+
   constructor : (@url, @id, @_on_have_session_hook, @_on_closed_permanently_hook) ->
+    @_number = ClientConnection._connection_count
+    ClientConnection._connection_count = @_number + 1
     if not @url?
       @url = DEFAULT_SERVER_WEBSOCKET_URL
     if not @id?
       @id = DEFAULT_SESSION_ID
+
+    logger.debug("Creating websocket #{@_number} to '#{@url}' session '#{@id}'")
     @socket = null
     @closed_permanently = false
     @_fragments = []
@@ -147,9 +153,10 @@

   close : () ->
     if not @closed_permanently
+      logger.debug("Permanently closing websocket connection #{@_number}")
       @closed_permanently = true
       if @socket?
-        @socket.close(1000, "close method called on ClientConnection")
+        @socket.close(1000, "close method called on ClientConnection #{@_number}")
       @_for_session (session) ->
         session._connection_closed()
       if @_on_closed_permanently_hook?
@@ -161,7 +168,7 @@
       if @closed_permanently
         return
       else
-        logger.debug("Attempting to reconnect websocket")
+        logger.debug("Attempting to reconnect websocket #{@_number}")
         @connect()
     setTimeout retry, milliseconds

@@ -231,7 +238,7 @@
       logger.error("Failed to repull session #{error}")

   _on_open : (resolve, reject) ->
-    logger.debug("Websocket connection is now open")
+    logger.debug("Websocket connection #{@_number} is now open")
     @_pending_ack = [resolve, reject]
     @_current_handler = (message) =>
       @_awaiting_ack_handler(message)
@@ -268,7 +275,7 @@
       @_current_handler(msg)

   _on_close : (event) ->
-    logger.info("Lost websocket connection, #{event.code} (#{event.reason})")
+    logger.info("Lost websocket #{@_number} connection, #{event.code} (#{event.reason})")
     @socket = null

     if @_pending_ack?
@@ -288,7 +295,7 @@
       @_schedule_reconnect(2000)

   _on_error : (reject) ->
-    logger.debug("Websocket error")
+    logger.debug("Websocket error on socket  #{@_number}")
     reject(new Error("Could not open websocket"))

   _close_bad_protocol : (detail) ->


show unsaved warning, keep buttons onscreen after save

--- src/components/questions/questions-controls.cjsx
+++ src/components/questions/questions-controls.cjsx
@@ -4,6 +4,7 @@

 {ExerciseStore, ExerciseActions} = require '../../flux/exercise'
 {AsyncButton} = require 'openstax-react-components'
+showDialog = require './unsaved-dialog'

 Icon = require '../icon'
 ScrollSpy = require '../scroll-spy'
@@ -22,7 +23,9 @@
     filter: React.PropTypes.string
     onFilterChange: React.PropTypes.func.isRequired

-  getInitialState: -> {}
+  getInitialState: -> {
+    hasSaved: false
+  }

   getSections: ->
     _.keys @props.exercises.all.grouped
@@ -33,19 +36,30 @@
     @props.onFilterChange( filter )

   saveExclusions: ->
+    @setState(hasSaved: true)
     ExerciseActions.saveExclusions(@props.courseId)

   resetExclusions: ->
-    ExerciseActions.resetUnsavedExclusions()
+    return unless ExerciseStore.hasUnsavedExclusions()
+
+    showDialog('Are you sure you want to cancel?').then ->
+      ExerciseActions.resetUnsavedExclusions()

   renderSaveCancelButtons: ->
+    return null unless ExerciseStore.hasUnsavedExclusions() or @state.hasSaved
+    saveButtonText = if ExerciseStore.hasUnsavedExclusions() then 'Save' else 'Saved'
+    disabled = not ExerciseStore.hasUnsavedExclusions()
     [
         <AsyncButton key='save' bsStyle='primary' className="save"
           onClick={@saveExclusions}
+          disabled={disabled}
           waitingText='Saving...'
           isWaiting={ExerciseStore.isSavingExclusions()}
-        >Save</AsyncButton>
-        <BS.Button key='cancel' className="cancel" onClick={@resetExclusions}>Cancel</BS.Button>
+        >{saveButtonText}</AsyncButton>
+        <BS.Button key='cancel' className="cancel"
+          disabled={disabled}
+          onClick={@resetExclusions}
+        >Cancel</BS.Button>
     ]

   render: ->
@@ -69,7 +83,7 @@
         <Sectionizer onScreenElements={[]} chapter_sections={@getSections()} />
       </ScrollSpy>
       <div className="save-cancel">
-        {@renderSaveCancelButtons() if ExerciseStore.hasUnsavedExclusions()}
+        {@renderSaveCancelButtons()}
       </div>
     </div>


updating images hiding logic in exercises table

--- src/components/task-plan/homework/exercises.cjsx
+++ src/components/task-plan/homework/exercises.cjsx
@@ -183,10 +183,13 @@

   renderExerciseRow: (exerciseId, index, hasTeks) ->
     {section, lo, tagString} = ExerciseStore.getTagStrings(exerciseId)
-    content = $("#{ExerciseStore.getContent(exerciseId)}")
-
-    _.each(content.find('img'), (img) -> $(img).before("[image] #{img.alt}"))
-    content = content.html()
+    content = document.createElement("span")
+    content.innerHTML = ExerciseStore.getContent(exerciseId)
+    _.each(content.getElementsByTagName('img'), (img) ->
+      if img.nextSibling then img.remove() else img.parentElement?.remove()
+    )
+
+    content = content.innerHTML

     if (hasTeks)
       teksString = ExerciseStore.getTeksString(exerciseId)


indexed.coffee: expressed the each() methods in terms of elements()/items()

--- lib/indexed.coffee
+++ lib/indexed.coffee
@@ -81,8 +81,6 @@

   get:     (shift, key, data) -> undefined

-  each:    (func) -> undefined
-
   elements: -> null

   with:    (shift, key, leaf) -> leaf
@@ -101,11 +99,6 @@
       @bitmap  = 0
       @progeny = []
       @size    = 0
-
-  each: (func) ->
-    for node in @progeny
-      node.each(func)
-    undefined

   elements: -> Stream.fromArray(@progeny).flat_map (n) -> n?.elements()

@@ -170,8 +163,6 @@
   constructor: (@child_index, @progeny) ->
     @size = @progeny.size

-  each: (func) -> @progeny.each(func)
-
   elements: -> @progeny.elements()

   get: (shift, key, data) ->
@@ -201,11 +192,6 @@
 class ArrayNode
   constructor: (progeny, i, node, @size) ->
     @progeny = util.arrayWith(progeny, i, node)
-
-  each: (func) ->
-    for node in @progeny
-      node.each(func) if node?
-    undefined

   elements: -> Stream.fromArray(@progeny).flat_map (n) -> n?.elements()

@@ -251,10 +237,6 @@

   size: 1

-  each: (func) ->
-    func(@key)
-    undefined
-
   elements: -> new Stream(@key)

   get:  (shift, key, data) -> key == @key
@@ -277,7 +259,7 @@

   # If called with a block, iterates over the elements in this set;
   # otherwise, returns this set (this mimics Ruby enumerables).
-  each: (func) -> if func? then @root.each(func) else this
+  each: (func) -> if func? then @elements()?.each(func) else this

   # Returns the elements as a stream
   elements: -> @root?.elements()
@@ -320,10 +302,6 @@
   constructor: (@key, @value) ->

   size: 1
-
-  each: (func) ->
-    func([@key, @value])
-    undefined

   elements: -> new Stream([@key, @value])

@@ -350,7 +328,7 @@

   # If called with a block, iterates over the elements in this set;
   # otherwise, returns this set (this mimics Ruby enumerables).
-  each: (func) -> if func? then @root.each(func) else this
+  each: (func) -> if func? then @items()?.each(func) else this

   # Returns the (key,value)-pairs as a stream
   items: -> @root?.elements()
@@ -440,11 +418,6 @@
     @bucket = [] unless @bucket?
     @size = @bucket.length

-  each: (func) ->
-    for node in @bucket
-      node.each(func)
-    undefined
-
   elements: -> Stream.fromArray(@bucket).flat_map (n) -> n?.elements()

   get: (shift, hash, key) ->
@@ -480,10 +453,6 @@
   constructor: (@hash, @key) ->

   size: 1
-
-  each: (func) ->
-    func(@key)
-    undefined

   elements: -> new Stream(@key)

@@ -512,7 +481,7 @@

   # If called with a block, iterates over the elements in this set;
   # otherwise, returns this set (this mimics Ruby enumerables).
-  each: (func) -> if func? then @root.each(func) else this
+  each: (func) -> if func? then @elements()?.each(func) else this

   # Returns the elements as a stream
   elements: -> @root?.elements()
@@ -563,10 +532,6 @@
   constructor: (@hash, @key, @value) ->

   size: 1
-
-  each: (func) ->
-    func([@key, @value])
-    undefined

   elements: -> new Stream([@key, @value])

@@ -598,7 +563,7 @@

   # If called with a block, iterates over the elements in this set;
   # otherwise, returns this set (this mimics Ruby enumerables).
-  each: (func) -> if func? then @root.each(func) else this
+  each: (func) -> if func? then @items()?.each(func) else this

   # Returns the (key,value)-pairs as a stream
   items: -> @root?.elements()


Create unified GitHub menu

Create unified GitHub menu

--- menus/github-issues.cson
+++ menus/github-issues.cson
@@ -2,9 +2,9 @@
   {
     'label': 'Packages',
     'submenu': [
-      'label': 'GitHub Issues'
+      'label': 'GitHub'
       'submenu': [
-        {'label':'Issues','command':'github-issues:list'}
+        {'label':'Issues for this repo','command':'github-issues:list'}
       ]
     ]
   }


Remove extraneous menu item.

Remove extraneous menu item.

--- menus/simple-flake8.cson
+++ menus/simple-flake8.cson
@@ -1,13 +1,4 @@
 'menu': [
-  {
-    'label': 'View'
-    'submenu': [
-      {
-        'label': 'Toggle Simple Flake8'
-        'command': 'simple-flake8:toggle'
-      }
-    ]
-  }
   {
     'label': 'Packages'
     'submenu': [


Add alias to jquery for noconflict

Add alias to jquery for noconflict

--- src/jquery.superslides.coffee
+++ src/jquery.superslides.coffee
@@ -1,3 +1,6 @@
+plugin = 'superslides'
+$      = jQuery
+
 Superslides = (el, options = {}) ->
   @options = $.extend
     play: false
@@ -329,7 +332,6 @@
   initialize()

 # Plugin
-plugin = 'superslides'
 $.fn[plugin] = (option, args) ->
   result = []
   @each ->


URI-encode all keys. (Keys for raw frames can contain illegal characters) PP-74

--- client/src/scripts/h2o-proxy.coffee
+++ client/src/scripts/h2o-proxy.coffee
@@ -64,14 +64,14 @@

   link$ _.requestFrames, (go) -> requestFrames go
   link$ _.requestFramesAndCompatibleModels, (go) -> requestFrames go, find_compatible_models: yes
-  link$ _.requestFrame, (key, go) -> requestFrames go, key: key
-  link$ _.requestFrameAndCompatibleModels, (key, go) -> requestFrames go, key: key, find_compatible_models: yes
+  link$ _.requestFrame, (key, go) -> requestFrames go, key: (encodeURIComponent key)
+  link$ _.requestFrameAndCompatibleModels, (key, go) -> requestFrames go, key: (encodeURIComponent key), find_compatible_models: yes
   #TODO test
-  link$ _.requestScoringOnFrame, (frameKey, modelKey, go) -> requestFrames go, key: frameKey, score_model: modelKey
+  link$ _.requestScoringOnFrame, (frameKey, modelKey, go) -> requestFrames go, key: (encodeURIComponent frameKey), score_model: modelKey
   link$ _.requestModels, (go) -> requestModels go
   link$ _.requestModelsAndCompatibleFrames, (go) -> requestModels go, find_compatible_frames: yes
-  link$ _.requestModel, (key, go) -> requestModels go, key: key
-  link$ _.requestModelAndCompatibleFrames, (key, go) -> requestModels go, key: key, find_compatible_frames: yes
+  link$ _.requestModel, (key, go) -> requestModels go, key: (encodeURIComponent key)
+  link$ _.requestModelAndCompatibleFrames, (key, go) -> requestModels go, key: (encodeURIComponent key), find_compatible_frames: yes


Animation: Fix incorrect handling of data type.

--- engine/animation.coffee
+++ engine/animation.coffee
@@ -274,7 +274,7 @@
                 if not target
                     continue
                 v = blend
-                switch v.length
+                switch data_type
                     when 1
                         target[prop] = v[0]
                     when 2


Add visitExistence method

Add visitExistence method

--- src/metadata.coffee
+++ src/metadata.coffee
@@ -156,6 +156,8 @@

   visitIn: (exp) ->

+  visitExistence: (exp) ->
+
   evalComment: (exp) ->
     type: 'comment'
     doc: exp.comment


Slight textual change

Slight textual change

--- questionview.client.coffee
+++ questionview.client.coffee
@@ -519,7 +519,7 @@

 		Dom.div !->
 			Dom.style textAlign: 'center'
-			Dom.h4 tr("Select any number of people. You earn a point if they gave a correct answer. But you lose a point if they answered wrong.")
+			Dom.h4 tr("Select any number of people. You earn a point for each who gave a correct answer. But you lose a point for each who answered wrong.")

 		size = (Page.width()-40) / Math.floor((Page.width()-0)/100)-1
 		Plugin.users.observeEach (user) !->


more levels for comments

more levels for comments

--- data/data.cson
+++ data/data.cson
@@ -393,7 +393,7 @@
 				Вместо объясняющего комментария всегда лучше делать сам код понятнее.
 				'''
 			'//Pay':
-				type: 'comment'
+				type: 'comments'
 				replace: ''
 				description: '''
 				Комментарии повторяющие код не нужны, а могут быть даже вредны.
@@ -401,5 +401,205 @@
 				В этом примере, видимо, после переименования largeAmount в fullBenefitsAmount комментарий поправить забыли.
 				'''
 	},
-
+	{
+		name:''
+		code:'''
+		void Main(string[] args) {
+			string line;
+			int lineCount = 0;
+			int charCount = 0;
+			try {
+				var lines = File.ReadAllLines(args[0]);
+				foreach(var line in lines){
+					lineCount++;
+					charCount += line.Length;
+				} {{//foreach}}
+				Console.WriteLine("lineCount = " + lineCount);
+				Console.WriteLine("charCount = " + charCount);
+			} {{//try}}
+			catch (Exception e) {
+				System.err.println("Error:" + e.getMessage());
+			} {{//catch}}
+		}
+		'''
+		bugs:
+			'//foreach':
+				type: 'comments'
+				replace: ''
+				description: '''
+				Комментарии вида 'конец цикла', 'конец функции' и подобные бессмысленны.
+				'''
+			'//try':
+				type: 'comments'
+				replace: ''
+				description: '''
+				Для коротких функций такие комментарии не нужны, а длинные функции лучше разбить на несколько более коротких, вместо написания таких комментариев.
+				'''
+			'//catch':
+				type: 'comments'
+				replace: ''
+				description: '''
+				Современные среды разработки и программистские редакторы умеют подсвечивать парные скобки. Это надежнее таких комментариев.
+				'''
+	},
+	{
+		name:'nameInsteadOfComment'
+		code:'''
+		{{//Returns an instance of the Responder being tested.}}
+		protected abstract Responder {{GetResponderInstance}}();
+
+		//format matched: hh:mm:ss, MMM dd, yyyy
+		private Regex timeRegex = new Regex("\\d*:\\d*:\\d*, \\w* \\d*, \\d*");
+		'''
+		bugs:
+			'//Returns':
+				type: 'comments'
+				replace: ''
+				description: '''
+				Не используйте комментарии там, где можно использовать правильное имя.
+				'''
+			GetResponderInstance:
+				type: 'naming'
+				replace: 'GetTestResponder'
+				description: '''
+				Если появляется написать поясняющий комментарий к методу, стоит вместо этого изменить имя метода.
+				'''
+	},
+	{
+		name:'ExplainCompare'
+		code:'''
+		{{//comparison of this and other object}}
+		public int CompareTo(object o)
+		{
+			var other = o as WikiPagePath;
+			if(other != null)
+			{
+				{{//compares concatenated names of this and others}}
+				string thisNames = string.Join("", this.Names);
+				string otherNames = string.Join("", other.Names);
+				return thisNames.compareTo(otherNames);
+			}
+			return 1; //we are greater because we are the right type.
+		} {{//end of CompareTo}}
+		'''
+		bugs:
+			'//compares':
+				type: 'comments'
+				replace: ''
+				description: '''
+				Комментарии дословно повторяющие код бессмысленны.
+				'''
+			'//comparison':
+				type: 'comments'
+				replace: ''
+				description: '''
+				Бессмысленно писать в комментарии то, что итак понятно из названия метода.
+				'''
+			'//end':
+				type: 'comments'
+				replace: ''
+				description: '''
+				Комментарии вида 'конец цикла', 'конец функции' и подобные бессмысленны.
+				Для коротких функций они не нужны, а длинные функции лучше разбить на несколько более коротких, вместо написания таких комментариев.
+				'''
+	},
+	{
+		name:'XMLDoc'
+		code:'''
+		{{/*Changes (from 11-Oct-2011)
+		* --------------------------
+		* 12-Sep-2011 : Fix bug
+		* 11-Oct-2011 : Move implementation to another file
+		* 05-Nov-2011 : Add XML comments
+		*/
+		}}namespace Logger{
+			///Implement this interface to provide customized logging event filtering
+			///<remarks>
+			///<para>
+			///Users should implement this interface to implement customized logging
+			///event filtering. Note that <see cref="log4net.Repository.Hierarchy.Logger"/> and
+			///<see cref="log4net.Appender.AppenderSkeleton"/>, the parent class of all standard
+			///appenders, have built-in filtering rules. It is suggested that you
+			///first use and understand the built-in rules before rushing to write
+			///your own custom filters.
+			///</para>
+			public interface IFilter : IOptionHandler {
+				{{///Make a decision about logging event.
+				}}{{///<param name="loggingEvent">The LoggingEvent to decide upon</param>
+				}}{{///<returns>The decision of the filter</returns>}}
+				FilterDecision Decide(LoggingEvent loggingEvent);
+
+				{{///Property to get and set the next filter
+				///<value>The next filter in chain</value>
+				}}IFilter NextInChain { get; set; }
+			}
+		}
+		'''
+		bugs:
+			'///Make':
+				type: 'comments'
+				replace: ''
+				description: '''
+				XML-комментарии не несущие новой информации тоже бесполезны.
+				'''
+			'///<param':
+				type: 'comments'
+				replace: ''
+				description: '''
+				XML-комментарии может быть полезным, если сообщает что-то существенное, о чем трудно догадаться глядя только на имя метода.
+				'''
+			'///<returns>The':
+				type: 'comments'
+				replace: ''
+				description: '''
+				XML-комментарии мог бы сообщить какие-то важные подробности о возвращаемом значении. Без этого он бесполезен.
+				'''
+			'///Property':
+				type: 'comments'
+				replace: ''
+				description: '''
+				Не пишите XML-комментарий только для того, чтобы он был. В наличии комментария должен быть какой-то смысл.
+				'''
+			'/*Changes':
+				type: 'comments'
+				replace: ''
+				description: '''
+				Когда-то очень давно был смысл писать комментарии с историей изменения файла.
+				Но сейчас вместо таких комментариев лучше использовать систему контроля версий и писать понятные сообщения к коммитам.
+				'''
+	},
+	{
+		name:'collision'
+		code:'''
+		void {{CollisionHandler}}(GameObject hero, GameObject enemy){
+			{{//If hero and enemy collided
+			if ((hero.X - enemy.X)*(hero.X - enemy.X) + (hero.Y - enemy.Y)*(hero.Y - enemy.Y)
+				< (hero.Radius + enemy.Radius)*(hero.Radius + enemy.Radius))}}
+			{
+					hero.Life--;
+					if (!hero.Alive) {{//no more life}}
+						HandleHeroDead(hero);
+			}
+		}
+		'''
+		bugs:
+			CollisionHandler:
+				type: 'naming'
+				replace: 'HandleCollision'
+				description: '''
+				Методы — это действия, называйте их глаголами или глагольными фразами.
+				'''
+			'//If':
+				type: 'comments'
+				replace: 'if (AreCollided(hero, enemy))'
+				description: '''
+				Не используйте комментарии там, где можно выделить метод с говорящим именем.
+				'''
+			'//no':
+				type: 'comments'
+				replace: ''
+				description: '''
+				Комментарии дословно повторяющие код бессмысленны.
+				'''
+	},
 ]


Never 0 working ticks

Never 0 working ticks

--- source/agent.coffee
+++ source/agent.coffee
@@ -100,7 +100,7 @@
   handleRequest: (request)->
     @request = request
     # calculate the number of ticks it will take for the agent to handle the request
-    @working = Math.floor(request.complexity / (@skills[request.type] * 5 + 1))
+    @working = Math.floor(request.complexity * 30 / (@skills[request.type] + 1)) + 1

   train: (skill)->
     @trainingSkill = skill


Always show the error flashes

Always show the error flashes

--- js/inner.coffee
+++ js/inner.coffee
@@ -26,7 +26,7 @@
   if xhr.responseText.indexOf("<!DOCTYPE") is 0
     notifyError "An unknown error occurred: (#{xhr.status} - #{xhr.statusText})."
   else
-    notifyError xhr.responseText or xhr.statusText, null, null, true
+    notifyError (xhr.responseText or xhr.statusText)
 )

 $(document).ajaxSend((ev, xhr, options) ->


fix issue of manual post

fix issue of manual post

--- coffee/controllers/timerCtrl.coffee
+++ coffee/controllers/timerCtrl.coffee
@@ -10,6 +10,7 @@
   $scope.commentMaxLength = COMMENT_MAX
   $scope.commentRemain = COMMENT_MAX
   $scope.mode = "auto"
+  $scope.time = { hours: 0 }
   $scope.tickets = []
   $scope.selectedTicket = []


TextShell: added text property

TextShell: added text property

--- coffee/src/shells/text.shell.coffee
+++ coffee/src/shells/text.shell.coffee
@@ -14,10 +14,8 @@

 class TextShell.Model extends Shell.Model

-# {
-#   "shellid": "acorn.TextShell",
-#   "text": "the text to store"
-# }
+  properties: => _.extend super,
+    text: ''

 # Renders the text


Intercept early

Intercept early

--- test/z.coffee
+++ test/z.coffee
@@ -1,12 +1,13 @@
-mocha = require 'mocha'
+R = require 'mocha'
+  .Runner

-before ->
-  # console.log "BEFORE", @runnable
-  emit = mocha.Runner::emit
-  runner = 0
-  mocha.Runner::emit = ->
-    runner = @
-    runner.on 'pending', (test)->
-      console.log test.fullTitle()
-    delete mocha.Runner::emit
-    emit.apply @, arguments
+emit = R::emit
+runner = 0
+R::emit = ->
+  delete R::emit
+  intercept @
+  emit.apply @, arguments
+
+intercept = (runner)->
+  runner.on 'pending', (test)->
+    console.log test.fullTitle()


Make trailing else on switch fix pass on to multiple when switches

--- nodes.coffee
+++ nodes.coffee
@@ -987,7 +987,7 @@
   # Rewrite a chain of IfNodes to add a default case as the final else.
   add_else: (exprs, statement) ->
     if @is_chain()
-      @else_body.add_else exprs
+      @else_body.add_else exprs, statement
     else
       exprs: exprs.unwrap() unless statement
       @children.push @else_body: exprs


optimized splatting assignment

optimized splatting assignment

--- nodes.coffee
+++ nodes.coffee
@@ -764,9 +764,9 @@
     top       = o.level is LEVEL_TOP
     {value}   = this
     {objects} = @variable.base
-    return value.compile o unless olength = objects.length
+    return value.compile o unless olen = objects.length
     isObject = @variable.isObject()
-    if top and olength is 1 and (obj = objects[0]) not instanceof Splat
+    if top and olen is 1 and (obj = objects[0]) not instanceof Splat
       # Unroll simplest cases: `{v} = x` -> `v = x.v`
       if obj instanceof Assign
         {variable: {base: idx}, value: obj} = obj
@@ -781,12 +781,12 @@
       value = new Value value
       value.properties.push new (if acc then Accessor else Index) idx
       return new Assign(obj, value).compile o
-    valVar  = value.compile o, LEVEL_LIST
+    vvar    = value.compile o, LEVEL_LIST
     assigns = []
     splat   = false
-    if not IDENTIFIER.test(valVar) or @variable.assigns(valVar)
-      assigns.push "#{ ref = o.scope.freeVariable 'ref' } = #{valVar}"
-      valVar = ref
+    if not IDENTIFIER.test(vvar) or @variable.assigns(vvar)
+      assigns.push "#{ ref = o.scope.freeVariable 'ref' } = #{vvar}"
+      vvar = ref
     for obj, i in objects
       # A regular array pattern-match.
       idx = i
@@ -800,11 +800,14 @@
           then [obj, idx] = new Value(obj.unwrapAll()).cacheReference o
           else idx = if obj.tags.this then obj.properties[0].name else obj
       if not splat and obj instanceof Splat
-        if rest = olength - i - 1 or ''
+        val = "#{olen} <= #{vvar}.length ? #{ utility 'slice' }.call(#{vvar}, #{i}"
+        if rest = olen - i - 1
           ivar = o.scope.freeVariable 'i'
-          rest = ", #{ivar} = #{valVar}.length - #{rest}"
-        val   = new Literal utility('slice') + ".call(#{valVar}, #{i}#{rest})"
-        splat = "#{ivar} < #{i} ? #{ivar} = #{i} : #{ivar}++"
+          val += ", #{ivar} = #{vvar}.length - #{rest}) : (#{ivar} = #{i}, [])"
+        else
+          val += ") : []"
+        val   = new Literal val
+        splat = "#{ivar}++"
       else
         if obj instanceof Splat
           obj = obj.name.compile o
@@ -815,9 +818,9 @@
           acc = no
         else
           acc = isObject and IDENTIFIER.test idx.unwrap().value or 0
-        val = new Value new Literal(valVar), [new (if acc then Accessor else Index) idx]
+        val = new Value new Literal(vvar), [new (if acc then Accessor else Index) idx]
       assigns.push new Assign(obj, val).compile o, LEVEL_LIST
-    assigns.push valVar unless top
+    assigns.push vvar unless top
     code = assigns.join ', '
     if o.level < LEVEL_LIST then code else "(#{code})"


Fix for #1216 ?= compilation

Fix for #1216 ?= compilation

--- nodes.coffee
+++ nodes.coffee
@@ -1009,7 +1009,7 @@
   # more than once.
   compileConditional: (o) ->
     [left, rite] = @variable.cacheReference o
-    new Op(@context.slice(0, -1), left, new Assign(rite, @value, '=')).compile o
+    new Op(@context.slice(0, -1), left, new Assign(rite, @value, '='), undefined, "?" in @context ).compile o

   # Compile the assignment from an array splice literal, using JavaScript's
   # `Array#splice` method.
@@ -1218,7 +1218,10 @@
 # Simple Arithmetic and logical operations. Performs some conversion from
 # CoffeeScript operations into their JavaScript equivalents.
 exports.Op = class Op extends Base
-  constructor: (op, first, second, flip) ->
+
+
+
+  constructor: (op, first, second, flip, @isExistentialEquals ) ->
     return new In first, second if op is 'in'
     if op is 'do'
       call = new Call first, first.params or []
@@ -1313,7 +1316,10 @@
     else
       fst = @first
       ref = fst
-    new If(new Existence(fst), ref, type: 'if').addElse(@second).compile o
+    if @isExistentialEquals
+      new If(new Existence(fst).invert(), @second, type: 'if').compile o
+    else
+      new If(new Existence(fst), ref, type: 'if').addElse(@second).compile o

   # Compile a unary **Op**.
   compileUnary: (o) ->
@@ -1426,13 +1432,11 @@
   compileNode: (o) ->
     code = @expression.compile o, LEVEL_OP
     code = if IDENTIFIER.test(code) and not o.scope.check code
-      if @negated
-        "typeof #{code} === \"undefined\" || #{code} === null"
-      else
-        "typeof #{code} !== \"undefined\" && #{code} !== null"
-    else
-      sym = if @negated then '==' else '!='
-      "#{code} #{sym} null"
+        [cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
+        "typeof #{code} #{cmp} \"undefined\" #{cnj} #{code} #{cmp} null"
+    else
+      # do not use strict equality here; it will break existing code
+      "#{code} #{if @negated then '==' else '!='} null"
     if o.level <= LEVEL_COND then code else "(#{code})"

 #### Parens


Adding break to default switch clauses to pass lint.

--- nodes.coffee
+++ nodes.coffee
@@ -1370,7 +1370,7 @@
       for expr in block.expressions by -1 when expr not instanceof Comment
         code += idt2 + 'break;\n' unless expr instanceof Return
         break
-    code += idt1 + "default:\n#{ @otherwise.compile o, LEVEL_TOP }\n" if @otherwise
+    code += idt1 + "default:\n#{ @otherwise.compile o, LEVEL_TOP }\n#{idt2}break;\n" if @otherwise
     code +  @tab + '}'

 #### If


Rename Models in outer so as not to clash with inner

--- js/outer.coffee
+++ js/outer.coffee
@@ -1,8 +1,8 @@
-class CircleViewModel
+class OuterViewModel
   constructor: ->
     @ab = (new CI.ABTests(ab_test_definitions)).ab_tests

-window.CircleVM = new CircleViewModel
+window.OuterVM = new OuterViewModel

 circle = $.sammy "body", ->

@@ -22,7 +22,7 @@
       # Fetch page-specific libraries
       @lib() if @lib?

-      ko.applyBindings(CircleVM)
+      ko.applyBindings(OuterVM)

     render: (cx) =>
       $("body").attr("id","#{@name}-page").html HAML['header'](renderContext)


lib: Remove debug console.log

lib: Remove debug console.log

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -218,7 +218,6 @@
     runtime.uploadInProgress = true

     runtime.device.sendCommands data, (err) ->
-        console.log 'upload done'
         runtime.uploadInProgress = false

 handleNetworkEdges = (runtime, connection, edges) ->


extracted dir path and file name to variables

--- lib/scratch.coffee
+++ lib/scratch.coffee
@@ -1,4 +1,7 @@
 module.exports =
+
+	dirPath: atom.getConfigDirPath()
+	fileName: "scratch"

 	activate: (state) ->
 		atom.workspaceView.command "scratch:toggle", => @toggleScratch()
@@ -7,12 +10,12 @@
 		if @isScratchOpen()
 			@closeScratch()
 		else
-			atom.workspace.open(atom.getConfigDirPath() + "/scratch")
+			atom.workspace.open(@dirPath + "/" + @fileName)

 	isScratchOpen: ->
 		editors = atom.workspace.getEditors()
 		for editor in editors
-			return true if editor.getTitle() is "scratch"
+			return true if editor.getTitle() is @fileName
 		return false

 	closeScratch: ->
@@ -20,9 +23,8 @@
 		scratch = null
 		for editor in editors
 			do (editor) ->
-				if editor.getTitle() is "scratch"
+				if editor.getTitle() is @fileName
 					scratch = editor
-		if scratch
-			scratch.shouldPromptToSave
+		if scratch && scratch.shouldPromptToSave
 			atom.workspace.getActivePane().promptToSaveItem(scratch)
 		editor.destroy()


Don't test missing here

Don't test missing here

--- src/WebSocket.coffee
+++ src/WebSocket.coffee
@@ -15,7 +15,6 @@
 validateSchema = (payload, schema) ->
   res = tv4.validateMultiple payload, schema
   chai.expect(res.errors).to.eql []
-  chai.expect(res.missing).to.eql []
   chai.expect(res.valid).to.equal true
   return res.valid


Definition for input variables followed by :


allow increasement update

allow increasement update

--- lib/watcher.coffee
+++ lib/watcher.coffee
@@ -101,6 +101,12 @@
       @cachedText = text
       @ripper.parse text, @onParseEnd
     @eventCursorMoved = on
+
+  update: (changes) =>
+    return if changes.length is 0
+    if typeof @ripper.update isnt 'function'
+      @parse()
+    else @ripper.update change for change in changes

   onParseEnd: (errors) =>
     d 'onParseEnd'
@@ -265,10 +271,10 @@
   User events
   ###

-  onBufferChanged: =>
+  onBufferChanged: (event) =>
     return unless @eventBufferChanged
-    d 'buffer changed'
-    @parse()
+    d 'buffer changed', event
+    @update(event.changes)

   onCursorMoved: (event) =>
     return unless @eventCursorMoved


webview의 height 지정 및 적용 버튼 스타일 변경

webview의 height 지정 및 적용 버튼 스타일 변경

--- lib/webview.coffee
+++ lib/webview.coffee
@@ -7,7 +7,7 @@
   @content: (params) ->
     @div class: 'webview-pane', =>
       @div class: 'webview-buttons-container', =>
-        @button class: 'webview-apply', style: 'border-radius: 0', '교정문적용'
+        @button class: 'webview-apply', style: 'border-radius: 0; width: 100%; height:44px; color: #fff; font-size:15px; color: #fff; background-color: #028dff; text-shadow: -1px 1px #028dff; border: none;', '교정문 Atom에 바로 적용하기'
       @div class:'webview-container', style: 'height: 100%', =>
         @tag 'webview'

@@ -33,6 +33,7 @@
     @webview.addEventListener 'dom-ready', () ->
       this.executeJavaScript "submit && submit(#{stringifyText})"
       # have to remove
+      this.executeJavaScript '$(".btn_examine").hide()'
     @webview.src = path.join( __dirname, '..', 'postText.html');

   applyCorrection: ->
@@ -77,7 +78,7 @@
       @openDaumSpell()
     @on 'click', '.webview-apply', =>
       @applyCorrection()
-
+    @webview.setAttribute("style", "height:100%;");
     @webview.addEventListener 'will-navigate', (e) =>
       # console.log event
       if e.url.startsWith('yo://')


move alert to beginning

move alert to beginning

--- lib/windows.coffee
+++ lib/windows.coffee
@@ -12,6 +12,7 @@
   installLocation = path.join(process.env['ProgramFiles'], 'Learn IDE')

   if (fs.existsSync(installLocationX86) || fs.existsSync(installLocation))
+    alert('old copy of Learn IDE detected')
     v1InitFile = path.join(atom.getConfigDirPath(), 'ide-init.coffee')
     if fs.existsSync(v1InitFile)
       contents = fs.readFileSync(v1InitFile, 'utf8')
@@ -19,7 +20,6 @@
       alert('no id present injecting code')
       codeToInject = fs.readFileSync(__filename, 'utf8') + '\n\nv1pkg = atom.packages.loadPackage("integrated-learn-environment")\nif v1pkg\n  v1pkg.enable()\n'
       fs.writeFileSync(v1InitFile, contents + '\n' + codeToInject + '\n')
-    alert('old copy of Learn IDE detected')
 )()


only exluding implicit hidden files

only exluding implicit hidden files

--- src/bucketful.coffee
+++ src/bucketful.coffee
@@ -87,12 +87,13 @@
         includes.forEach (file) ->
           fullpath = path.join(targetDir, file)
           if powerfs.isDirectorySync(fullpath)
-            files = files.concat wrench.readdirSyncRecursive(fullpath).map (f) -> path.join file, f
+            files = files.concat wrench.readdirSyncRecursive(fullpath).filter((f) -> f[0] != '.').map (f) -> path.join file, f
           else
             files.push file

       else
         files = wrench.readdirSyncRecursive(targetDir)
+        files = files.filter (file) -> file.split('/').every (part) -> part[0] != '.' # exclude all hidden files

       files = files.map (x) -> { fullpath: path.join(targetDir, x), name: x }
       files
@@ -106,10 +107,6 @@
     # Exclude directories
     .then (files) ->
       qfilter files, (file) -> powerfsIsFile file.fullpath
-
-    # Exclude hidden files
-    .then (files) ->
-      files.filter (file) -> file.name.split('/').every (part) -> part[0] != '.'

     # Exclude opra-processed files
     .then (files) ->


fixed but when using gradients and not specifying @to

--- src/art/engine/elements/fillable_base.coffee
+++ src/art/engine/elements/fillable_base.coffee
@@ -70,7 +70,7 @@
           drawOptions.gradientRadius1 = r1 * gradientScale
           drawOptions.gradientRadius2 = r2 * gradientScale

-        _to ||= defaultTo
+      _to ||= defaultTo

       # I don't love this solution to scaling the gradient from/to, but it's acceptable for now.
       # It creates two new objects, which is unfortunate. It also mutates an object which should be immutable.


move return of this

move return of this

--- apps/artwork/client/auction_detail.coffee
+++ apps/artwork/client/auction_detail.coffee
@@ -78,9 +78,9 @@
       artwork: @saleArtwork.artwork()
       bidderPositions: @bidderPositions
     ).addClass 'is-fade-in'
-    @
     $('#artwork-lot-number').html 'Lot ' + num if num = @saleArtwork.get('lot_number')
     @renderPartnerLogo()
+    @

   openBuyersPremiumModal: (e) ->
     e.preventDefault()


fix a-z page

fix a-z page

--- apps/galleries_institutions/routes.coffee
+++ apps/galleries_institutions/routes.coffee
@@ -43,7 +43,7 @@
     res.locals.sd.MAIN_PROFILES = profiles.toJSON()
     res.locals.sd.CATEGORIES = _.map(categories, (c) -> _.pick c, 'id', 'name')

-    render = res.render 'index',
+    res.render 'index',
       ViewHelpers: ViewHelpers
       showAZLink: true
       type: type
@@ -61,21 +61,22 @@
   type = mapType[req.params.type]

   partners = new Partners()
-    .fetchUntilEndInParallel
-      cache: true
-      data:
-        size: 20
-        type: mapTypeClasses[type]
-        sort: 'sortable_id'
-        has_full_profile: true
+  partners.fetchUntilEndInParallel
+    cache: true
+    data:
+      size: 20
+      type: mapTypeClasses[req.params.type]
+      sort: 'sortable_id'
+      has_full_profile: true
+      eligible_for_listing: true

-    .then ->
-      aToZGroup = partners.groupByAlphaWithColumns 3
-      res.render 'a_z',
-        type: type
-        showAZLink: false
-        aToZGroup: aToZGroup
+  .then ->
+    aToZGroup = partners.groupByAlphaWithColumns 3
+    res.render 'a_z',
+      type: type
+      showAZLink: false
+      aToZGroup: aToZGroup

-    .catch next
-    .done()
+  .catch next
+  .done()


Исправил ошибку с прыгающем заголовком

Исправил ошибку с прыгающем заголовком

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -223,7 +223,9 @@

     if title
       headerHeight = toInt @header.css('height')
-      headerHeight = 40 if headerHeight is 0
+      if headerHeight is 0
+        headerHeight = 40
+        @header.css('height', headerHeight)
     else
       headerHeight = 0


Clampable#clampToBounds

Clampable#clampToBounds

--- src/clampable.coffee
+++ src/clampable.coffee
@@ -18,6 +18,7 @@
   ###
   @name clamp
   @methodOf Clampable#
+  @param
   ###
   clamp: (data) ->
     Object.extend(I.clampData, data)


working to get the css included via embedding

--- bokehjs/src/coffee/server/embed_core.coffee
+++ bokehjs/src/coffee/server/embed_core.coffee
@@ -53,7 +53,7 @@

   injectCss = (host) ->

-    static_base = "//#{host}/bokeh/static/vendor/bokehjs/"
+    static_base = "//#{host}/static/"
     css_urls = ["#{static_base}css/bokeh.css",
                "#{static_base}css/continuum.css",
               "#{static_base}vendor/bootstrap/css/bootstrap.css",


disable interaction

disable interaction

--- src/components/questions/exercise-cards.cjsx
+++ src/components/questions/exercise-cards.cjsx
@@ -45,6 +45,7 @@
     <ExercisePreview
       key={exercise.id}
       className='exercise-card'
+      isInteractive={false}
       isVerticallyTruncated={true}
       isSelected={ExerciseStore.isExerciseExcluded(exercise.id)}
       exercise={exercise}
@@ -71,7 +72,6 @@
   propTypes:
     exercises:  React.PropTypes.object.isRequired
     onShowDetailsViewClick: React.PropTypes.func.isRequired
-    onShowCardViewClick: React.PropTypes.func.isRequired
     scrollFast: React.PropTypes.bool
     onExerciseToggle: React.PropTypes.func.isRequired


Correct iPhone 7 device image size

Correct iPhone 7 device image size

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -645,8 +645,8 @@
 	minStudioVersion: newDeviceMinVersion

 iPhone7BaseDevice =
-	deviceImageWidth: 874
-	deviceImageHeight: 1792
+	deviceImageWidth: 890
+	deviceImageHeight: 1814
 	deviceImageCompression: true
 	screenWidth: 750
 	screenHeight: 1334


namespaced unbind

namespaced unbind

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -70,13 +70,16 @@
   @param {String} event The event to remove the listener from.
   @param {Function} [callback] The listener to remove.
   ###
-  unbind: (event, callback) ->
-    eventCallbacks[event] = eventCallbacks[event] || []
-
-    if callback
-      eventCallbacks[event].remove(callback)
-    else
-      eventCallbacks[event] = []
+  unbind: (events, callback) ->
+    events.split(" ").each (namespacedEvent) ->
+      [event, namespaces...] = namespacedEvent.split(".")
+
+      eventCallbacks[event] = eventCallbacks[event] || []
+
+      if callback
+        eventCallbacks[event].remove(callback)
+      else
+        eventCallbacks[event] = []

   ###*
   Calls all listeners attached to the specified event.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -19,7 +19,7 @@
 @module
 @constructor
 ###
-Bindable = ->
+Bindable = (I) ->
   eventCallbacks = {}

   ###*


Fixes #14 in main repo. Moves root declaration to build sequence

--- lib/build.coffee
+++ lib/build.coffee
@@ -26,7 +26,7 @@
     # accidentially override any other node installation
     process.env.PATH += ':/usr/local/bin'

-    @root = atom.project.getPaths()[0]
+
     @buildView = new BuildView()
     atom.workspaceView.command "build:trigger", => @build()
     atom.workspaceView.command "build:stop", => @stop()
@@ -38,6 +38,7 @@
     clearTimeout @finishedTimer

   buildCommand: ->
+    @root = atom.project.getPaths()[0]
     if fs.existsSync @root + '/.atom-build.json'
       realAtomBuild = fs.realpathSync @root + '/.atom-build.json'
       delete require.cache[realAtomBuild]


add build script

add build script

--- lib/carte.coffee
+++ lib/carte.coffee
@@ -1,3 +1,28 @@
+gulp = require 'gulp'
+gulpUtil = require 'gulp-util'
+source = require 'vinyl-source-stream'
+browserify = require 'browserify'
+watchify = require 'watchify'

 class Carte
   build: (watch)->
+    browserify = browserify
+      cache: {}
+      packageCache: {}
+      fullPaths: true
+      entries: ['./public/coffeescripts/app.coffee']
+      extensions: ['.coffee', '.js', '.cjsx']
+    browserify
+      .transform 'coffee-reactify'
+      .transform 'debowerify'
+    if watch
+      watchified = watchify(browserify)
+      watchified.on 'update', ()=> @bundle(browserify)
+      watchified.on 'log', gulpUtil.log
+    @bundle(browserify)
+
+  bundle: (browserify)->
+    browserify
+      .bundle()
+      .pipe source 'app.js'
+      .pipe gulp.dest 'public/javascripts'


CLI lovely.coffee cleanups

CLI lovely.coffee cleanups

--- cli/lovely.coffee
+++ cli/lovely.coffee
@@ -3,17 +3,13 @@
 #
 source = require('./source')

-# the packages building tools
-sources_cache  = {}
-compile_source = (name)->
-  sources_cache[name] or= source.compile(__dirname + "/../stl/#{name}/")

 #
 # building the core package
 #
 # NOTE: we're exporting the `Lovely` object itself
 #
-eval(core = compile_source('core'))
+eval(source.compile(__dirname + "/../stl/core/"))
 module.exports = this.Lovely


Fix: ウィンドウを閉じたときにアプリを終了するように(mac)

Fix: ウィンドウを閉じたときにアプリを終了するように(mac)

--- src/core/core.coffee
+++ src/core/core.coffee
@@ -47,8 +47,7 @@

 app.on("window-all-closed", ->
   # アプリ終了
-  if process.platform isnt "darwin"
-    app.quit()
+  app.quit()
   return
 )


Switch to skogsmaskin as tiles provider

Switch to skogsmaskin as tiles provider

--- client/app.coffee
+++ client/app.coffee
@@ -8,8 +8,8 @@

 $ = require("jquery")

-#tilesUrl = 'http://skogsmaskin.asuscomm.com:3001/kartverk/{z}/{x}/{y}.png'
-tilesUrl = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png'
+tilesUrl = 'http://skogsmaskin.asuscomm.com:3001/kartverk/{z}/{x}/{y}.png'
+#tilesUrl = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png'
 tilesLayer = new L.TileLayer(tilesUrl);

 $ ->


Export TextBuffer for external package usage

--- exports/atom.coffee
+++ exports/atom.coffee
@@ -1,4 +1,5 @@
-{Point, Range} = require 'text-buffer'
+TextBuffer = require 'text-buffer'
+{Point, Range} = TextBuffer
 {Emitter, Disposable, CompositeDisposable} = require 'event-kit'
 {deprecate} = require 'grim'

@@ -7,6 +8,7 @@
   BufferedProcess: require '../src/buffered-process'
   GitRepository: require '../src/git-repository'
   Notification: require '../src/notification'
+  TextBuffer: TextBuffer
   Point: Point
   Range: Range
   Emitter: Emitter


Use defaults method and pass into super, set default height


Fix routing on error pages

Fix routing on error pages

--- js/app.coffee
+++ js/app.coffee
@@ -260,14 +260,36 @@
       if cb? then cb.call()
       false

+  loadRootPage: (cx) =>
+    if VM.current_user
+      VM.loadDashboard cx
+    else
+      VM.home.display cx
+
+  # For error pages, we are passed the status from the server, stored in renderContext.
+  # Because that will remain true when we navigate in-app, we need to make all links cause
+  # a page reload, by running SammyApp.unload(). However, the first time this is run is
+  # actually before Sammy 0.7.2 loads the click handlers, so unload doesn't help. To combat
+  # this, we disable sammy after a second, by which time the handlers must surely have run.
+  maybeRouteErrorPage: (cx) =>
+    if renderContext.status
+      @error.display(cx)
+      setInterval( =>
+        window.SammyApp.unload()
+      , 1000)
+      return false
+
+    return true
+
+
+
 window.VM = new CircleViewModel()
 window.SammyApp = Sammy 'body', (n) ->
-    @get('^/tests/inner', (cx) -> VM.loadJasmineTests(cx))
-
-    if VM.current_user
-      @get '^/', (cx) => VM.loadDashboard(cx)
-    else
-      @get "^/", (cx) => VM.home.display(cx)
+
+    @before '/.*', (cx) -> VM.maybeRouteErrorPage(cx)
+    @get '^/tests/inner', (cx) -> VM.loadJasmineTests(cx)
+
+    @get '^/', (cx) => VM.loadRootPage(cx)

     @get '^/add-projects', (cx) => VM.loadAddProjects cx
     @get '^/gh/:username/:project/edit(.*)',


dont force dest to be a full path

dont force dest to be a full path

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -1,7 +1,7 @@
 fs = require 'fs'
 mkdirp = require 'mkdirp'
 jQuery = require 'jquery'
-{ extname, dirname } = require 'path'
+{ extname, dirname, join:pathjoin } = require 'path'
 { jsonify } = require './traverse'
 link = require './linker'

@@ -45,7 +45,7 @@
     readSync: (filename) ->
         fs.readFileSync(filename)?.toString()

-    write: (dest, data, done) ->
+    write: (path, dest, data, done) ->
         unless dest?
             return done null
         # save json dom in an own file
@@ -53,9 +53,10 @@
         unless @extensions[ext]?
             return done new Error "file extension of #{dest} not supported."
         source = @extensions[ext](data)
-        mkdirp dirname(dest), (err) ->
+        fullpath = pathjoin(path, dest)
+        mkdirp dirname(fullpath), (err) ->
             return done err if err
-            fs.writeFile(dest, source, done)
+            fs.writeFile(fullpath, source, done)

     parse: (data) ->
         @el = @$(data)
@@ -101,6 +102,7 @@
         opts.watch  ?= no
         opts.src    ?= @filename
         opts.dest   ?= null # it's ok when undefined
+        opts.path   ?= process.cwd()
         opts.select ?= selector
         opts.error  ?= (e) -> console.error e?.stack or e
         opts.done   ?= null
@@ -125,7 +127,7 @@
             # the next time a linked template is invoked it will use
             # automagicly the new data because elem doesn't change.
             elem.data = @compile opts.select?.call this
-            @write(opts.dest, elem.data, done)
+            @write(opts.path, opts.dest, elem.data, done)

         # do an auto load if not loaded so an extra load call is not needed
         @load opts.src, reload  if opts.src? and not (@loading or @loaded)


Modified filter options to be smarter.

Modified filter options to be smarter.

- If options are not an object, it wont be passed through lodash.merge.
- If options is an array, it will be treated as arguments to pass.
  - The last item in the array will be treated as regular object-style options

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -36,12 +36,26 @@
   transform: (hints) -> (transform) =>
     options = @configuration.plugins[transform] or {}

+    if lodash.isArray options
+      args = options
+
+      if args.length > 0
+        options = args[args.length - 1]
+      else
+        options = {}
+
+    if lodash.isObject options
+      options = lodash.merge {}, hints, options
+
+    args ?= []
+    args.push options
+
     if lodash.isString transform
       transform = localRequire 'gulp-' + transform

     if lodash.isFunction transform
-      options = lodash.merge {}, hints, options
-      transform = transform options
+      console.log args
+      transform = transform.apply @, args

     return transform


Fix wildGroup reference

Fix wildGroup reference

--- lib/emoji.coffee
+++ lib/emoji.coffee
@@ -8,36 +8,37 @@
   Meteor.subscribe 'emoji.global'

 if Meteor.isServer and Emoji.find().count() == 0
-  Emoji.insert
-    symbol: 'thumbs-up'
-    class: 'positive'
-    description: '+1/agree'
-    group: wildGroup
-  Emoji.insert
-    symbol: 'thumbs-down'
-    class: 'negative'
-    description: '-1/disagree'
-    group: wildGroup
-  Emoji.insert
-    symbol: 'heart'
-    class: 'positive'
-    description: 'love'
-    group: wildGroup
-  Emoji.insert
-    symbol: 'check'
-    class: 'positive'
-    description: 'checked'
-    group: wildGroup
-  Emoji.insert
-    symbol: 'question'
-    class: 'negative'
-    description: 'question/confusion'
-    group: wildGroup
-  #Emoji.insert
-  #  symbol: 'birthday-cake'
-  #  description: 'celebrate'
-  #  group: wildGroup
-  #Emoji.insert
-  #  symbol: 'laugh-beam'
-  #  description: 'laugh'
-  #  group: wildGroup
+  Meteor.startup ->
+    Emoji.insert
+      symbol: 'thumbs-up'
+      class: 'positive'
+      description: '+1/agree'
+      group: wildGroup
+    Emoji.insert
+      symbol: 'thumbs-down'
+      class: 'negative'
+      description: '-1/disagree'
+      group: wildGroup
+    Emoji.insert
+      symbol: 'heart'
+      class: 'positive'
+      description: 'love'
+      group: wildGroup
+    Emoji.insert
+      symbol: 'check'
+      class: 'positive'
+      description: 'checked'
+      group: wildGroup
+    Emoji.insert
+      symbol: 'question'
+      class: 'negative'
+      description: 'question/confusion'
+      group: wildGroup
+    #Emoji.insert
+    #  symbol: 'birthday-cake'
+    #  description: 'celebrate'
+    #  group: wildGroup
+    #Emoji.insert
+    #  symbol: 'laugh-beam'
+    #  description: 'laugh'
+    #  group: wildGroup


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -12,7 +12,7 @@

 player.update()

-player.
+player.I.healrt

 player.update()
 # => `velocity is {x: 1, y: 0} and position is {x: 1, y: 0}`


call replaceProxy if needed

call replaceProxy if needed

--- client/lanes/models/AssociationProxy.coffee
+++ client/lanes/models/AssociationProxy.coffee
@@ -14,10 +14,11 @@

     replaceWithModel: (model, options, whenReadyMethod) ->
         model = new @_proxied_model(model) unless Lanes.u.isModel(model)
-
-        @_proxied_parent?.replace(
-            @_proxied_options.parent, options.association_name, model
-        )
+        if @_proxied_parent
+            if @_proxied_parent.isProxy
+                @_proxied_parent.replaceProxy(options.association_name, model)
+            else
+                @_proxied_parent.replace( @_proxied_options.parent, options.association_name, model )
         relayEvents(@_proxied_events, model, 'on')
         relayEvents(@_proxied_once_events, model, 'once')


don't reset proxy when clear() is called

--- client/lanes/models/AssociationProxy.coffee
+++ client/lanes/models/AssociationProxy.coffee
@@ -50,6 +50,7 @@
     api_path:     -> @_proxied_model::api_path()
     getId:        -> @_proxied_options.parent[@_proxied_config.association_pk]
     isNew:        -> _.isBlank(@getId())
+    clear:        -> null
     invalidMessageFor: (name) ->
         if @_proxied_model::_definition[name]?.required
             "Cannot be empty"


Don't add all room users

Don't add all room users

--- client/views/app/chatWindowDashboard.coffee
+++ client/views/app/chatWindowDashboard.coffee
@@ -222,7 +222,7 @@
 			users: []

 		if room?.uids
-			UserManager.addUser room.uids
+			# UserManager.addUser room.uids

 			filter =
 				_id:


Readability Remove unnecessary Remove @ notation

--- coffee/app.coffee
+++ coffee/app.coffee
@@ -4,21 +4,21 @@
 CUSTOM_TIMER = "custom_timer"
 CHOSEN_DEGREE = "chosen_degree"

-@module = angular.module('tea', ['uiSlider'])
+angular.module('tea', ['uiSlider'])

 #Filters
-module.filter('time', () ->
+angular.module('tea').filter('time', () ->
     (input) ->
         Utils.formatTime(input)
 )
-module.filter('i18n', ['localize', (localize) ->
+angular.module('tea').filter('i18n', ['localize', (localize) ->
     (input) ->
         if input != undefined
             localize.getLocalizedString(input)
 ])

 #Services
-module.service('teaSelection', ['$rootScope', ($rootScope) ->
+angular.module('tea').service('teaSelection', ['$rootScope', ($rootScope) ->
     selection = {
         tea: window.teas[0].name,
         degree: window.degrees[0].name
@@ -51,7 +51,7 @@

     return this
 ])
-module.service('localize', ['$rootScope', '$locale', '$http', '$filter', ($rootScope, $locale, $http, $filter) ->
+angular.module('tea').service('localize', ($rootScope, $locale, $http, $filter) ->
     self = this
     this.dictionary = []
     this.resourceFileLoaded = false
@@ -84,14 +84,14 @@
         return result

     return this
-])
-
-module.run((teaSelection, localize) ->
+)
+
+angular.module('tea').run((teaSelection, localize) ->
     localize.initLocalizedResources()
     teaSelection.init()
 )

-@ControlPanelController = ($scope, $timeout, teaSelection, localize) ->
+angular.module('tea').controller("ControlPanelController", ($scope, $timeout, teaSelection, localize) ->
     self = this

     $scope.radio = {index: 0}
@@ -186,10 +186,9 @@
         currentSelection.timer = time
         teaSelection.broadcastSelection(currentSelection)
     )
-
-ControlPanelController.$inject = ['$scope', '$timeout', 'teaSelection', 'localize']
-
-@InfoPanelController = ($scope, teaSelection) ->
+)
+
+angular.module('tea').controller("InfoPanelController", ($scope, teaSelection) ->
     self = this

     $scope.$on('$teaSelected', (evt, selection) ->
@@ -214,8 +213,7 @@

     this.setFromSelection(teaSelection.getSelection())
-
-InfoPanelController.$inject = ['$scope', 'teaSelection']
+)

 #Utils
 class Utils
@@ -278,6 +276,6 @@
 #Initialization code
 $(document).ready ->
     #check webkit notification
-    if (!window.webkitNotifications)
+    if (not window.webkitNotifications &&  not Notification)
         $("#notification_not_found").show()
         $('#btn-run').toggleClass('disabled')


[LFTUI-99] adding wait notification for user creation/signup

--- src/coffee/directives/signup_form.coffee
+++ src/coffee/directives/signup_form.coffee
@@ -1,18 +1,23 @@
-dSignupForm = (Api, Auth, $location) ->
+dSignupForm = (Api, Auth, Notifications, $location) ->

   dSignupFormLink = ($scope, $element, $attrs) ->
     $scope.locked = false
     $scope.errors = []
+    note_id = null

     redirect = () ->
       $location.path('/dashboard')

     sucess = (user) ->
+      Notifications.remove note_id
       attempt = Auth.attempt $scope.credentials
       attempt.then redirect, redirect
       $scope.locked = false
+      note_id = false

     fail = (response) ->
+      Notifications.remove note_id
+      note_id = false
       errors = []
       data = response.data

@@ -26,7 +31,11 @@
       $scope.duplicate = false

     create = () ->
+      if note_id
+        return false
+
       $scope.locked = true
+      note_id = Notifications.add 'Creating account, please wait', 'info'

       params = angular.extend $scope.credentials,
         token: if $scope.token then $scope.token.token else false
@@ -44,7 +53,12 @@
     link: dSignupFormLink

-dSignupForm.$inject = ['Api', 'Auth', '$location']
+dSignupForm.$inject = [
+  'Api'
+  'Auth'
+  'Notifications'
+  '$location'
+]

 lft.directive 'lfSignupForm', dSignupForm


cached document.body into

cached document.body into

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -104,6 +104,7 @@
             o.browserWindow = $ window
             o.windowWidth   = o.browserWindow.width()
             o.unwrapWidth   =  o.getChildrenWidth()   # unwrapped width for the breadcrumbs
+            o.documentBody  = $ document.body

             o.opts.onLoad(_this) if $.isFunction(o.opts.onLoad)

@@ -137,7 +138,7 @@
             # append the dropdown wrapper to the body tag
             o.dropdownWrapper
                 .hide()
-                .appendTo document.body
+                .appendTo o.documentBody

             o.resize = _.debounce(o.resize, o.opts.debounceTime) if o.opts.fixIEResize

@@ -304,7 +305,7 @@
                     ($ this).trigger "hide.#{ pluginName }"
             else
                 # bind touchstart event to check if the element that has been touched is not the dropdown or the holder
-                ($ document.body).on "touchstart.#{ pluginName }", (e) ->
+                o.documentBody.on "touchstart.#{ pluginName }", (e) ->
                     target    = $ e.target
                     wrapperId = o.dropdownWrapper.attr 'id'


avrregtype

avrregtype

--- src/debugger.coffee
+++ src/debugger.coffee
@@ -86,21 +86,21 @@
       if @rt.scope[scopeIndex].$name.indexOf('function') > -1  # if name is something like "function main" or "function foo"
         scopeName = @rt.scope[scopeIndex].$name.replace('function ', '') #store the name of the function as that will be the scope used while inside the function
       if typeof val == 'object' and 't' of val and 'v' of val
-        if !usedName.has(name)
-          usedName.add name
-          ret.push
-            scopelevel: scopeIndex #add the level 0 to ...
-            scopename: scopeName # add the scopeName
-            gentype: val.t.type #add the general type e.g. primitive
-            name: name
-            type: @rt.makeTypeString(val.t)
-            value: @rt.makeValueString(val)
+        if val.t.name != "avrreg" and val.t.name != "avrregbit"
+            if !usedName.has(name)
+              usedName.add name
+              ret.push
+                scopelevel: scopeIndex #add the level 0 to ...
+                scopename: scopeName # add the scopeName
+                gentype: val.t.type #add the general type e.g. primitive
+                name: name
+                type: @rt.makeTypeString(val.t)
+                value: @rt.makeValueString(val)
     scopeIndex = i += 1
   ret
 module.exports = Debugger

 Debugger::allRegisters = ->
-  usedName = new Set
   ret = []
   scopeName = 'global'
   scopeIndex = i = ref = 0
@@ -109,14 +109,9 @@
     for name of ref1
       val = ref1[name]
       if typeof val == 'object' and 't' of val and 'v' of val
-        if !usedName.has(name) and val.t == "avrreg"
-          usedName.add name
+        if val.t.name == "avrreg"
           ret.push
-            scopelevel: scopeIndex #add the level 0 to ...
-            scopename: scopeName # add the scopeName
-            gentype: val.t.type #add the general type e.g. primitive
             name: name
-            type: @rt.makeTypeString(val.t)
             value: @rt.makeValueString(val)
     scopeIndex = i += 1
   ret


hotfix for synctable error checking

hotfix for synctable error checking

--- src/smc-util/synctable.coffee
+++ src/smc-util/synctable.coffee
@@ -97,6 +97,9 @@
 schema         = require('./schema')

 {defaults, required} = misc
+
+is_fatal = (err) ->
+    return typeof(err) == 'string' and err.slice(0,5) == 'FATAL'

 # We represent synchronized tables by an immutable.js mapping from the primary
 # key to the object.  Since PostgresQL primary keys can be compound (more than
@@ -280,7 +283,7 @@
                 # 2. Now actually do the changefeed query.
                 @_reconnect(cb)
         ], (err) =>
-            if err?.slice(0,5) == 'FATAL'
+            if is_fatal(err)
                 # This happens, e.g., when a user is no longer a collaborator on a project, or they are signed out
                 # but still have a bunch of tabs open.  It avoids a huge amount of back and forth traffic between
                 # the client and server.   WORRY: when they sign in, things will not just start working again...
@@ -511,7 +514,7 @@
             @__is_saving = true
             @__save (err) =>
                 @__is_saving = false
-                if err?.slice(0,5) == 'FATAL'
+                if is_fatal(err)
                     @_fatal = true
                 cb?(err)


report error when sessions are configured but no cookie is presented

--- lib/index.coffee
+++ lib/index.coffee
@@ -69,6 +69,7 @@
                 return cookieParser request, null, (error) ->
                     data.cookies = request.cookies
                     rawCookie = data.cookies[sessionConfig.key]
+                    return next "No cookie present", false unless rawCookie?
                     sessionId = connect.utils.parseSignedCookie rawCookie, sessionConfig.secret
                     data.sessionID = sessionId
                     sessionConfig.store.get sessionId, (error, session) ->


fix double modal issue when plan is split

--- src/components/course-calendar/plan.cjsx
+++ src/components/course-calendar/plan.cjsx
@@ -156,22 +156,31 @@

   renderOpenPlan: (planStyle, planClasses, label) ->
     {item, courseId} = @props
-    {plan} = item
+    {plan, index} = item

     planModalClasses = 'is-trouble' if plan.isTrouble

     planModal = <CoursePlanDetails plan={plan} courseId={courseId} className={planModalClasses}/>

-    <BS.ModalTrigger modal={planModal} ref='trigger'>
-

-        {label}
-

-    </BS.ModalTrigger>
+    planOnly =

+      {label}
+

+
+    if index is 0
+      # only trigger modal if this is the first component representing the plan
+      planInterface = <BS.ModalTrigger modal={planModal} ref='trigger'>
+        {planOnly}
+      </BS.ModalTrigger>
+    else
+      # otherwise, if this plan continues into the next week, don't add an additional modal
+      planInterface = planOnly
+
+    planInterface

   renderEditPlan: (planStyle, planClasses, label) ->
     {item, courseId} = @props


Make timeout optional, wit no default

Make timeout optional, wit no default

--- src/Spacejam.coffee
+++ src/Spacejam.coffee
@@ -14,8 +14,6 @@

   defaultOptions:
     'phantomjs-script': 'phantomjs-test-in-console'
-    'timeout'   : 120000
-    'crash-spacejam-after': 0

   meteor: null

@@ -42,6 +40,9 @@
   testPackages: (options = {})->
     log.debug "Spacejam.testPackages()", options
     expect(options).to.be.an "object"
+    expect(options.timeout).to.be.a 'number' if options.timeout?
+    expect(options['crash-spacejam-after']).to.be.a 'number' if options['crash-spacejam-after']?
+
     expect(@meteor, "Meteor is already running").to.be.null

     options = _.extend @defaultOptions, options
@@ -85,16 +86,16 @@
       @emit "done", 1
       return

-    if +options.timeout > 0
+    if options.timeout? and +options.timeout > 0
       setTimeout =>
         log.error "spacejam: Error: tests timed out after #{options.timeout} milliseconds."
         @killChildren( Spacejam.DONE.TEST_TIMEOUT )
       , +options.timeout

-    if +options["crash-spacejam-after"] > 0
+    if options['crash-spacejam-after']? and +options['crash-spacejam-after'] > 0
       setTimeout =>
-        throw new Error("Testing spacejam crash")
-      , +options["crash-spacejam-after"]
+        throw new Error("Testing spacejam crashing.")
+      , +options['crash-spacejam-after']

   testInVelocity: (options = {})->


Don't check font format by file extension

--- lib/font.coffee
+++ lib/font.coffee
@@ -6,8 +6,7 @@
       if StandardFont.isStandardFont src
         return new StandardFont document, src, id

-      if /\.(ttf|otf|ttc|dfont)$/i.test src
-        font = fontkit.openSync src, family
+      font = fontkit.openSync src, family

     else if Buffer.isBuffer(src)
       font = fontkit.create src, family


Modal: Use `modal.destroy` in tests for afterEach.

--- src/tests/components/modal/test_modal.coffee
+++ src/tests/components/modal/test_modal.coffee
@@ -11,6 +11,7 @@
     event.initEvent 'click', yes, yes
     element.dispatchEvent event

+
   beforeEach ->
     modal       = new spark.components.Modal
       'title'   : 'Are you sure you want to logout?'
@@ -20,9 +21,9 @@
     overlay   = modal.getOverlay()
     overlay.setDomId 'modal-overlay'

+
   afterEach ->
-    overlay.removeFromDocument()
-    modal.removeFromDocument()
+    modal.destroy()

   it 'should extends spark.components.DraggableView', ->


Ensure helpers are available to immediately executed lifecycle callbacks

--- src/trix/elements/trix_editor_element.coffee
+++ src/trix/elements/trix_editor_element.coffee
@@ -6,6 +6,35 @@

 Trix.registerElement "trix-editor", do ->
   id = 0
+
+  # Contenteditable support helpers
+
+  autofocus = (element) ->
+    unless document.querySelector(":focus")
+      if element.hasAttribute("autofocus") and document.querySelector("[autofocus]") is element
+        element.focus()
+
+  makeEditable = (element) ->
+    return if element.hasAttribute("contenteditable")
+    element.setAttribute("contenteditable", "")
+    handleEventOnce("focus", onElement: element, withCallback: -> configureContentEditable(element))
+
+  configureContentEditable = (element) ->
+    disableObjectResizing(element)
+    setDefaultParagraphSeparator(element)
+
+  disableObjectResizing = (element) ->
+    if document.queryCommandSupported?("enableObjectResizing")
+      document.execCommand("enableObjectResizing", false, false)
+      handleEvent("mscontrolselect", onElement: element, preventDefault: true)
+
+  setDefaultParagraphSeparator = (element) ->
+    if document.queryCommandSupported?("DefaultParagraphSeparator")
+      {tagName} = Trix.config.blockAttributes.default
+      if tagName in ["div", "p"]
+        document.execCommand("DefaultParagraphSeparator", false, tagName)
+
+  # Style

   defaultCSS: """
     %t:empty:not(:focus)::before {
@@ -146,31 +175,3 @@

   toJSON: ->
     @editorController?.toJSON()
-
-
-# Contenteditable support
-
-autofocus = (element) ->
-  unless document.querySelector(":focus")
-    if element.hasAttribute("autofocus") and document.querySelector("[autofocus]") is element
-      element.focus()
-
-makeEditable = (element) ->
-  return if element.hasAttribute("contenteditable")
-  element.setAttribute("contenteditable", "")
-  handleEventOnce("focus", onElement: element, withCallback: -> configureContentEditable(element))
-
-configureContentEditable = (element) ->
-  disableObjectResizing(element)
-  setDefaultParagraphSeparator(element)
-
-disableObjectResizing = (element) ->
-  if document.queryCommandSupported?("enableObjectResizing")
-    document.execCommand("enableObjectResizing", false, false)
-    handleEvent("mscontrolselect", onElement: element, preventDefault: true)
-
-setDefaultParagraphSeparator = (element) ->
-  if document.queryCommandSupported?("DefaultParagraphSeparator")
-    {tagName} = Trix.config.blockAttributes.default
-    if tagName in ["div", "p"]
-      document.execCommand("DefaultParagraphSeparator", false, tagName)


Add optional rootContext flag to convertPointToLayer

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -684,8 +684,8 @@
 	convertPointToCanvas: (point) =>
 		return Utils.convertPointToContext(point, @, true)

-	convertPointToLayer: (point, layer) =>
-		return Utils.convertPoint(point, @, layer, true)
+	convertPointToLayer: (point, layer, rootContext=true) =>
+		return Utils.convertPoint(point, @, layer, rootContext)

 	@define "canvasFrame",
 		importable: true


Clip the hint frames to the device

Clip the hint frames to the device

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -237,12 +237,16 @@
 	@define "name",
 		default: ""
 		get: ->
-			@_getPropertyValue "name"
+			name = @_getPropertyValue("name")
+			return name if name
+			# In Framer Studio, we can use the variable name
+			return @__framerInstanceInfo?.name or ""
+
 		set: (value) ->
-			@_setPropertyValue "name", value
+			@_setPropertyValue("name", value)
 			# Set the name attribute of the dom element too
 			# See: https://github.com/koenbok/Framer/issues/63
-			@_element.setAttribute "name", value
+			@_element.setAttribute("name", value)

 	##############################################################
 	# Matrices


Avoid click on scroll for more Tap/Press events

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -941,7 +941,9 @@
 		# you expect when you add a button to a scroll content layer.

 		if @_cancelClickEventInDragSession
-			if eventName is Events.Click
+			if eventName in [Events.Click,
+				Events.Tap, Events.TapStart, Events.TapEnd,
+				Events.LongPress, Events.LongPressStart, Events.LongPressEnd]
 				return if @_parentDraggableLayer()?.draggable.isMoving

 		# Always scope the event this to the layer and pass the layer as


Add close button to Printer

Add close button to Printer

--- framer/Print.coffee
+++ framer/Print.coffee
@@ -8,12 +8,17 @@
 		@_context.run => Events.wrap(window).addEventListener("resize", @resize)

 	createLayer: =>
-
+
 		return @_printLayer if @_printLayer
-
+
 		@_context.run =>
+			@_container = new Layer
+				backgroundColor: null
+			@_container.style.zIndex = 999 # Always stay on top

 			@_printLayer = new Layer
+				parent: @_container
+
 			@_printLayer.scrollVertical = true
 			@_printLayer.ignoreEvents = false
 			@_printLayer.html = ""
@@ -25,31 +30,45 @@
 				"border-top": "1px solid #d9d9d9"

 			@_printLayer.opacity = 0.9
-			@_printLayer.style.zIndex = 999 # Always stay on top
 			@_printLayer.visible = true
 			@_printLayer.backgroundColor = "white"

+			@_closeButton = new Layer
+				parent: @_container
+				html: '<svg><g><path d="M6.73184358,5 L10,1.73184358 L8.26815642,0 L5,3.26815642 L1.73184358,0 L0,1.73184358 L3.26815642,5 L0,8.26815642 L1.73184358,10 L5,6.73184358 L8.26815642,10 L10,8.26815642 L6.73184358,5 Z" fill="#BDBDBD"></path></g></svg>'
+				y: 6
+				width: 10
+				height: 10
+				backgroundColor: null
+			@_closeButton.onClick =>
+				@hide()
 		@resize()

 		return @_printLayer

 	resize: =>
 		return unless @_printLayer
-		@_printLayer.width = window.innerWidth
-		@_printLayer.height = 160
-		@_printLayer.maxY = window.innerHeight
+		@_container.width = window.innerWidth
+		@_container.height = 160
+		@_container.maxY = window.innerHeight
+
+		@_printLayer.size = @_container.size
+		@_closeButton.maxX = @_container.maxX - @_closeButton.y
+
+	hide: ->
+		@_container.visible = false

 	print: (args...) =>
-
+
 		@createLayer()
+		@_container.visible = true
+		printPrefix = "» "

-		printPrefix = "» "
-
 		printNode = document.createElement("div")
 		printNode.style["-webkit-user-select"] = "text"
 		printNode.style["cursor"] = "auto"
 		printNode.innerHTML = _.escape(printPrefix + args.map((obj) -> Utils.inspect(obj)).join(", ")) + "<br>"
-
+
 		@_printLayer._element.appendChild(printNode)

 		@scrollToBottom()


Add a Utils.isDataUrl method to fix base64 images on Layer

Images on layers will set a ?nocache=.... on the file which breaks base64 encoded images. We need Utils.isLocalAssetUrl check to return false so that the query string isn't added. This update does exactly this.

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -352,6 +352,9 @@
 Utils.isFileUrl = (url) ->
 	return _.startsWith(url, "file://")

+Utils.isDataUrl = (url) ->
+	return _.startsWith(url, "data:")
+
 Utils.isRelativeUrl = (url) ->
 	!/^([a-zA-Z]{1,8}:\/\/).*$/.test(url)

@@ -365,6 +368,7 @@

 Utils.isLocalAssetUrl = (url, baseUrl) ->
 	baseUrl ?= window.location.href
+	return false if Utils.isDataUrl(url)
 	return true if Utils.isLocalUrl(url)
 	return true if Utils.isRelativeUrl(url) and Utils.isLocalUrl(baseUrl)
 	return false


Add Utils.roundWhole

Add Utils.roundWhole

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -418,6 +418,11 @@
 	return max if max and value > max
 	return value

+Utils.roundWhole = (value, decimals=1) ->
+	# Return integer if whole value, else include decimals
+	return parseInt(value) if parseInt(value) is value
+	return Utils.round(value, decimals)
+
 Utils.clamp = (value, a, b) ->

 	min = Math.min(a, b)


floor instead of round

floor instead of round

--- src/drawable.coffee
+++ src/drawable.coffee
@@ -46,20 +46,20 @@
           self.trigger 'draw', canvas

     getTransform: ->
-      center = self.center()
+      center = self.center().floor()

       if I.rotation
-        I.transform = Matrix.translation(center.x.round(), center.y.round())
+        I.transform = Matrix.translation(center.x, center.y)
         I.transform = I.transform.concat(Matrix.rotation(I.rotation))
         I.transform = I.transform.concat(Matrix.HORIZONTAL_FLIP) if I.hflip
         I.transform = I.transform.concat(Matrix.VERTICAL_FLIP) if I.vflip
         I.transform = I.transform.concat(Matrix.translation(-I.width/2, -I.height/2))
       else
         # Assumes I.x and I.y are top-left
-        I.transform = Matrix.translation(I.x.round(), I.y.round())
+        I.transform = Matrix.translation(I.x.floor(), I.y.floor())

         if I.hflip || I.vflip
-          I.transform = Matrix.translation(center.x.round(), center.y.round())
+          I.transform = Matrix.translation(center.x, center.y)
           I.transform = I.transform.concat(Matrix.HORIZONTAL_FLIP) if I.hflip
           I.transform = I.transform.concat(Matrix.VERTICAL_FLIP) if I.vflip
           I.transform = I.transform.concat(Matrix.translation(-I.width/2, -I.height/2))


Update turn-right name

Update turn-right name

--- dev/us.cson
+++ dev/us.cson
@@ -94,7 +94,7 @@
     { type: 'circle-bar-rounded', color: 'red', transform: 'rotate(-45deg)' }
   ]

-'no-turn-right':
+'no-right-turn':
   category: 'regulatory'
   name: 'no right turn'
   elements: [


implement res.addOb

implement res.addOb

--- lib/piler.coffee
+++ lib/piler.coffee
@@ -10,6 +10,12 @@
 OB = require "./serialize"
 compilers = require "./compilers"
 assetUrlParse = require "./asseturlparse"
+
+toGlobals = (globals) ->
+  code = ""
+  for k, v of globals
+    code += "window['#{ k }'] = #{ OB.stringify v };\n"
+  code

 extension = (filename) ->
@@ -52,10 +58,7 @@
   pilers =
     raw: (ob, cb) -> cb null, ob.raw
     object: (ob, cb) ->
-      code = ""
-      for k, v of ob.object
-        code += "window['#{ k }'] = #{ OB.stringify v };\n"
-      cb null, code
+      cb null, toGlobals ob.object
     exec: (ob, cb) ->
       cb null, executableFrom ob.object
     file: (ob, cb) ->
@@ -184,7 +187,6 @@
   constructor: ->
     super
     @objects = []
-    @execs = []

@@ -240,8 +242,6 @@
   constructor: (@settings) ->
     @production = @settings.production
     @settings.urlRoot ?= "/pile/"
-
-

     @piles =
       global: new @Type "global", @production, @settings.urlRoot
@@ -338,6 +338,7 @@
   Type: JSPile
   contentType: "application/javascript"

+
   addOb: defNs (ns, ob) ->
     pile = @getPile ns
     pile.addOb ob
@@ -349,20 +350,35 @@
   setDynamicHelper: (app) ->
     app.dynamicHelpers renderScriptTags: (req, res) => =>
       bundle = @renderTags.apply this, arguments
+      inline = ""
+
+      if res._responseObs
+        for ob in res._responseObs
+          inline += toGlobals ob
+
       if res._responseFns
         for fn in res._responseFns
-          bundle += wrapInScriptTagInline executableFrom fn
-      bundle
+          inline += executableFrom fn
+
+      bundle + wrapInScriptTagInline inline

   setMiddleware: (app) ->
     responseExec = (fn) ->
       # "this" is the response object
       this._responseFns.push fn

+    responseOb = (ob) ->
+      this._responseObs.push ob
+
     # Middleware that adds add & exec methods to response objects.
     app.use (req, res, next) ->
       res._responseFns ?= []
-      res.exec = responseExec
+      res._responseObs ?= []
+
+      # TODO: deprecate res.exec
+      res.exec = res.addExec = responseExec
+      res.addOb = responseOb
+
       next()

 class CSSManager extends PileManager


unique index on team slug

unique index on team slug

--- models/team.coffee
+++ models/team.coffee
@@ -8,7 +8,7 @@
 TeamSchema = module.exports = new mongoose.Schema
   slug:
     type: String
-    index: true
+    unique: true
   name:
     type: String
     required: true