Delete Geom.coffee

Delete Geom.coffee

--- Geom.coffee
+++ Geom.coffee
@@ -1,150 +0,0 @@
-class Geom
-
-#"geom" : [
-#  {
-#    "type" : "Point",
-#    "data" : "static",
-#    "properties" : {
-#      "x" : "xscale",
-#      "y" : "yscale",
-#      "fillColor" : "zscale"
-#    }
-#  }
-#]
-
-Geom::process = (obj) ->
-
-  for i in obj
-    for j in lib.GoG_JSON[i.data]
-      geomObj = {}
-      if i.type == "Point"
-        geomObj["x"] = j[i.properties.x]
-        geomObj["y"] = j[i.properties.y]
-        geomObj["color"] = if (i.properties.color is null || i.properties.color is undefined )
-          "#000000"
-        else
-          j[i.properties.color]
-        geomObj["fillColor"] = if (i.properties.fillColor is null || i.properties.fillColor is undefined )
-          "#ffffff"
-        else
-          j[i.properties.fillColor]
-        geomObj["linewidth"] = if (i.properties.linewidth is null || i.properties.linewidth is undefined )
-          1
-        else
-          j[i.properties.linewidth]
-        geomObj["radius"] = if (i.properties.radius is null || i.properties.radius is undefined )
-          7
-        else
-          j[i.properties.radius]
-        geomObj["anticlockwise"] = if (i.properties.anticlockwise is null || i.properties.anticlockwise is undefined )
-          false
-        else
-          j[i.properties.anticlockwise]
-        geomObj["startAngle"] = if (i.properties.startAngle is null || i.properties.startAngle is undefined )
-          0
-        else
-          j[i.properties.startAngle]
-        geomObj["endAngle"] = if (i.properties.endAngle is null || i.properties.endAngle is undefined )
-          360
-        else
-          j[i.properties.endAngle]
-
-        if lib.Canvas_JSON.hasOwnProperty(i.type)
-          lib.Canvas_JSON[i.type].push(geomObj)
-        else
-          lib.Canvas_JSON[i.type] = []
-          lib.Canvas_JSON[i.type].push(geomObj)
-
-      if i.type == "Picture"
-        geomObj["x"] = j[i.properties.x]
-        geomObj["y"] = j[i.properties.y]
-        geomObj["src"] = j[i.properties.src]
-
-        if lib.Canvas_JSON.hasOwnProperty(i.type)
-          lib.Canvas_JSON[i.type].push(geomObj)
-        else
-          lib.Canvas_JSON[i.type] = []
-          lib.Canvas_JSON[i.type].push(geomObj)
-
-      if i.type == "Diamond"
-        geomObj["color"] = if (i.properties.color is null || i.properties.color is undefined )
-          "#000000"
-        else
-          j[i.properties.color]
-        geomObj["x"] = j[i.properties.x]
-        geomObj["y"] = j[i.properties.y]
-
-        if lib.Canvas_JSON.hasOwnProperty(i.type)
-          lib.Canvas_JSON[i.type].push(geomObj)
-        else
-          lib.Canvas_JSON[i.type] = []
-          lib.Canvas_JSON[i.type].push(geomObj)
-
-      if i.type == "Text"
-        geomObj["x"] = j[i.properties.x]
-        geomObj["y"] = j[i.properties.y]
-        geomObj["text"] = j[i.properties.text]
-
-        if lib.Canvas_JSON.hasOwnProperty(i.type)
-          lib.Canvas_JSON[i.type].push(geomObj)
-        else
-          lib.Canvas_JSON[i.type] = []
-          lib.Canvas_JSON[i.type].push(geomObj)
-
-
-
-    for c, index in lib.GoG_JSON[i.data]
-      if index > 0
-        geomObj = {}
-        if i.type == "Line"
-          geomObj["color"] = if (i.properties.color is null || i.properties.color is undefined )
-            "#000000"
-          else
-            lib.GoG_JSON[i.data][index-1][i.properties.color]
-          geomObj["width"] = if (i.properties.width is null || i.properties.width is undefined )
-            1
-          else
-            lib.GoG_JSON[i.data][index-1][i.properties.width]
-
-          geomObj["startX"] = lib.GoG_JSON[i.data][index-1][i.properties.x]
-          geomObj["startY"] = lib.GoG_JSON[i.data][index-1][i.properties.y]
-          geomObj["endX"] = lib.GoG_JSON[i.data][index][i.properties.x]
-          geomObj["endY"] = lib.GoG_JSON[i.data][index][i.properties.y]
-
-          if lib.Canvas_JSON.hasOwnProperty(i.type)
-            lib.Canvas_JSON[i.type].push(geomObj)
-          else
-            lib.Canvas_JSON[i.type] = []
-            lib.Canvas_JSON[i.type].push(geomObj)
-
-
-    pointsArray = []
-    geomObj = {}
-    for j, c in lib.GoG_JSON[i.data]
-      if i.type == "Area"
-        arr = []
-        arr.push(j[i.properties.x])
-        arr.push(j[i.properties.y])
-        pointsArray.push(arr)
-        geomObj["points"] = pointsArray
-        geomObj["fillColor"] = if (i.properties.fillColor is null || i.properties.fillColor is undefined )
-          "rgba(200, 170, 170, 0.8)"
-        else
-          i.properties.fillColor
-        geomObj["color"] = if (i.properties.color is null || i.properties.color is undefined )
-          "#000000"
-        else
-          i.properties.color
-        geomObj["margin"] = lib.GoG_JSON["margin"]
-        geomObj["height"] = lib.GoG_JSON["height"]
-        geomObj["width"] = lib.GoG_JSON["width"]
-        if c == lib.GoG_JSON[i.data].length-1
-          if lib.Canvas_JSON.hasOwnProperty(i.type)
-            lib.Canvas_JSON[i.type].push(geomObj)
-          else
-            lib.Canvas_JSON[i.type] = []
-            lib.Canvas_JSON[i.type].push(geomObj)
-
-
-
-  return


#562 - fixes broken unit tests

#562 - fixes broken unit tests

--- dkobo/koboform/static/js/xlform/spec/skip_logic.coffee
+++ dkobo/koboform/static/js/xlform/spec/skip_logic.coffee
@@ -775,10 +775,12 @@
       _builder = sinon.stubObject(XLF.SkipLogicBuilder)
       _view_factory.create_hand_code_view.returns _view
       _view.render.returns _view
+      _view.$.withArgs('.skiplogic-handcode__cancel').returns(click: sinon.spy())
       _facade = new XLF.SkipLogicHandCodeHelper(
         'test criteria'
         _builder
         _view_factory
+        sinon.stubObject(XLF.SkipLogicHelperContext)
       )

@@ -802,7 +804,7 @@
         textarea_stub = val: sinon.stub()
         _view.$.withArgs('textarea').returns textarea_stub
         textarea_stub.val.returns 'test criteria'
-        expect(_facade.render()).toBe 'test criteria'
+        expect(_facade.serialize()).toBe 'test criteria'

     describe 'switch editing mode', () ->
       it "returns an instance of a criterion builder facade", () ->


Refactoring temporary name generation in a separate method, no functional changes.

--- scope.coffee
+++ scope.coffee
@@ -51,10 +51,14 @@
     return immediate if immediate or (options and options.immediate)
     !!(@parent and @parent.check(name))

+  # Generate a temporary variable name at the given index.
+  temporary: (type, index) ->
+    '_' + type + (if (index) > 1 then index else '')
+
   # If we need to store an intermediate result, find an available name for a
   # compiler-generated variable. `_var`, `_var2`, and so on...
   freeVariable: (type) ->
-    @tempVars[type]++ while @check temp = '_' + type + (if (@tempVars[type] or= 1) > 1 then @tempVars[type] else '')
+    @tempVars[type]++ while @check temp = @temporary type, @tempVars[type] or= 1
     @variables[temp] = 'var'
     temp


Fix README typo

Fix README typo

Replaces "the the" with "the".

--- readme-src.coffee
+++ readme-src.coffee
@@ -773,7 +773,7 @@
 doc.fn "observable.decode(mapping)", """
 decodes input using the given mapping. Is a
 bit like a switch-case or the decode function in Oracle SQL. For
-example, the following would map the value 1 into the the string "mike"
+example, the following would map the value 1 into the string "mike"
 and the value 2 into the value of the `who` property.

 ```js


Woops

Woops

--- framer/Components/PageComponent.coffee
+++ framer/Components/PageComponent.coffee
@@ -102,13 +102,9 @@
 		# If you add pages it makes sense they end up in the default place .
 		point = {x: 0, y: 0}

-		print @content.children
-
 		if @content.children.length
 			point.x = Utils.frameGetMaxX(@content.contentFrame()) if direction in ["right", "east"]
 			point.y = Utils.frameGetMaxY(@content.contentFrame()) if direction in ["down", "bottom", "south"]
-
-			print point

 		page.point = point


make auth_callback true by default

make auth_callback true by default

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -218,6 +218,8 @@
   auth_footer: false
   auth_require_pledge: false
   has_homepage: true
+
+  auth_callout: true

   homepage_list_order: []


activity scores_graph: prepare for multiple data series

We'll add the average activity as an additional data series item in
the next patch, prepare for that here.

No functional change.

--- static/js/activity_scores.coffee
+++ static/js/activity_scores.coffee
@@ -9,19 +9,25 @@
         time = new Date(end_date)
         year = time.getFullYear()
         month = time.getMonth()
-        @start_time = new Date(year - 2, month + 1, 1)
-        start_time_str = @start_time.getFullYear() + "-" +  \
-                          (@start_time.getMonth() + 1) + "-" + \
-                          @start_time.getDate()
+        start_time = new Date(year - 2, month + 1, 1)
+        start_time_str = start_time.getFullYear() + "-" +  \
+                          (start_time.getMonth() + 1) + "-" + \
+                          start_time.getDate()

         time =  new Date(new Date(year, month + 1, 1).getTime() - 1)
         year = time.getFullYear()
         month = time.getMonth()
         day = time.getDate()
-        @end_time = new Date(year, month, day)
-        end_time_str = @end_time.getFullYear() + "-" + \
-                       (@end_time.getMonth() + 1) + "-" + \
-                       @end_time.getDate()
+        end_time = new Date(year, month, day)
+        end_time_str = end_time.getFullYear() + "-" + \
+                       (end_time.getMonth() + 1) + "-" + \
+                       end_time.getDate()
+
+        @plot_series = []
+        @plot_global_options = @get_plot_global_options(start_time, end_time)
+
+        $(window).resize =>
+            @draw_plot()

         resolution = 'month'
         if activity_daily_avg
@@ -39,15 +45,16 @@
             data: params

     draw_plot: ->
-        $.plot $(@el), [ @act_histogram ], @plot_options
+        if @plot_series.length == 0
+            return

-    render: ->
-        score_list = @scores.models
+        $.plot $(@el), @plot_series, @plot_global_options

+    get_histogram: (score_list) ->
         if score_list.length == 0
-            return @
+            return []

-        @act_histogram = []
+        act_histogram = []
         if @avg_bin_score
             max_score = @avg_bin_score + 20
         else
@@ -70,13 +77,20 @@

             time = new Date(time).getTime()
             column = [ time, score ]
-            @act_histogram.push column
+            act_histogram.push column

             max_score = Math.max max_score, score

             data_idx += 1

-        colors = ["#00c0c0"]
+        return [act_histogram, max_score]
+
+    render: ->
+        [act_histogram, max_score] = @get_histogram(@scores.models)
+
+        if act_histogram.length == 0
+            return @
+
         if @avg_bin_score
             markings = [
                 yaxis:
@@ -86,14 +100,24 @@
         else
             markings = []

-        @plot_options =
-            colors: colors
-            series:
-                bars:
-                    show: true
-                    fill: 1
-                    barWidth: 16 * 24 * 60 * 60 * 1000     # milliseconds
-                    align: "center"
+        @plot_global_options['yaxis']['max'] = max_score
+        @plot_global_options['grid']['markings'] = markings
+
+        @plot_series.push
+            data: act_histogram
+            color: "#00c0c0"
+            bars:
+                show: true
+                fill: 1
+                barWidth: 16 * 24 * 60 * 60 * 1000     # milliseconds
+                align: "center"
+
+        @draw_plot()
+
+        return @
+
+    get_plot_global_options: (start_time, end_time) ->
+        return {
             xaxis:
                 mode: "time"
                 tickLength: 5
@@ -105,27 +129,19 @@
                     else
                         format_str = "MMM"
                     return moment(d).format(format_str)
-                min: @start_time.getTime() - 20 * 24 * 60 * 60 * 1000
-                max: @end_time.getTime()
+                min: start_time.getTime() - 20 * 24 * 60 * 60 * 1000
+                max: end_time.getTime()

             yaxis:
                 show: false
-                max: max_score

             grid:
-                markings: markings
                 borderWidth:
                     top: 0
                     bottom: 1
                     left: 0
                     right: 0
-
-        @draw_plot()
-
-        $(window).resize =>
-            @draw_plot()
-
-        return @
+        }

     add_all_items: (collection) =>
         @scores = collection


added tests to check rate limiter works without session

--- services/web/test/UnitTests/coffee/Security/RateLimiterMiddlewearTests.coffee
+++ services/web/test/UnitTests/coffee/Security/RateLimiterMiddlewearTests.coffee
@@ -10,7 +10,6 @@
 			"logger-sharelatex": @logger = {warn: sinon.stub()}
 		@req =
 			params: {}
-			session: {}
 		@res =
 			status: sinon.stub()
 			write: sinon.stub()
@@ -30,9 +29,30 @@
 				doc_id: @doc_id = "doc-id"
 			}

+		describe "when there is no session", ->
+			beforeEach ->
+				@RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
+				@req.ip = @ip = "1.2.3.4"
+				@rateLimiter(@req, @res, @next)
+
+			it "should call the rate limiter backend with the ip address", ->
+				@RateLimiter.addCount
+					.calledWith({
+						endpointName: "test-endpoint"
+						timeInterval: 42
+						throttle: 12
+						subjectName: "#{@project_id}:#{@doc_id}:#{@ip}"
+					})
+					.should.equal true
+
+			it "should pass on to next()", ->
+
+
 		describe "when under the rate limit with logged in user", ->
 			beforeEach ->
-				@req.session.user = { _id: @user_id = "user-id" }
+				@req.session =
+					user :
+						_id: @user_id = "user-id"
 				@RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
 				@rateLimiter(@req, @res, @next)

@@ -70,7 +90,9 @@

 		describe "when over the rate limit", ->
 			beforeEach ->
-				@req.session.user = { _id: @user_id = "user-id" }
+				@req.session  =
+					user :
+						_id: @user_id = "user-id"
 				@RateLimiter.addCount = sinon.stub().callsArgWith(1, null, false)
 				@rateLimiter(@req, @res, @next)


Allow multiple lines in substitution

Allow multiple lines in substitution

--- annotran/static/scripts/annotator/plugin/substitution.coffee
+++ annotran/static/scripts/annotator/plugin/substitution.coffee
@@ -68,10 +68,10 @@

     # we don't preserve full formatting but we can at least put some paragraph breaks in
     originalText = originalText.replace(new RegExp('\n', 'g'), '
')
-    console.log(originalText)
+    substituteText = substituteText.replace(new RegExp('\n', 'g'), '
')

     newEle = $('')
-    newEle.text(substituteText)
+    newEle.html(substituteText)

     newEle.css({
       "background-color" : "#F5F5F5"
@@ -83,7 +83,7 @@
       newEle.html(originalText)

     newEle.mouseleave (event) ->
-      newEle.text(substituteText)
+      newEle.html(substituteText)

     return newEle


Check to make sure `cb` is a function.

Check to make sure `cb` is a function.

Fixes #135.

--- lib/sync-settings.coffee
+++ lib/sync-settings.coffee
@@ -193,7 +193,7 @@

       atom.notifications.addSuccess "sync-settings: Your settings were successfully synchronized."

-      cb() unless callbackAsync
+      cb?() unless callbackAsync

   createClient: ->
     token = atom.config.get 'sync-settings.personalAccessToken'


Removed not needed debug logging.

Removed not needed debug logging.

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -365,15 +365,16 @@
       @autosaveLast = @taskGet()
     update = =>
       #console.log('AR', @formParam( @el.find('.article-add') ) )
+      currentStoreTicket = @ticket.attributes()
+      delete currentStoreTicket.article
       currentStore  =
-        ticket:  @ticket.attributes()
-        article: {
+        ticket:  currentStoreTicket
+        article:
           to:       ''
           cc:       ''
           type:     'note'
           body:     ''
           internal: ''
-        }
       currentParams =
         ticket:  @formParam( @el.find('.edit') )
         article: @formParam( @el.find('.article-add') )
@@ -390,13 +391,13 @@
       # get diff of last save
       changedBetweenLastSave = _.isEqual(currentParams, @autosaveLast )
       if !changedBetweenLastSave
-        console.log('model DIFF ', modelDiff)
+        #console.log('model DIFF ', modelDiff)

         @autosaveLast = clone(currentParams)
         @markFormDiff( modelDiff )

         @taskUpdateAll( modelDiff )
-    @interval( update, 3000, 'autosave' )
+    @interval( update, 4000, 'autosave' )

   markFormDiff: (diff = {}) =>
     ticketForm    = @$('.edit')
@@ -407,7 +408,7 @@
     params         = {}
     params.ticket  = @formParam( ticketForm )
     params.article = @formParam( articleForm )
-    console.log('markFormDiff', diff, params)
+    #console.log('markFormDiff', diff, params)

     # clear all changes
     if _.isEmpty(diff.ticket) && _.isEmpty(diff.article)


Test it with a lot of content.

Test it with a lot of content.

--- spec/buffered-process-spec.coffee
+++ spec/buffered-process-spec.coffee
@@ -114,3 +114,26 @@
     runs ->
       expect(stderr).toContain 'apm - Atom Package Manager'
       expect(stdout).toEqual ''
+
+  it "calls the specified stdout callback only with whole lines", ->
+    exitCallback = jasmine.createSpy('exit callback')
+    baseContent = "There are dozens of us! Dozens! It's as Ann as the nose on Plain's face. Can you believe that the only reason the club is going under is because it's in a terrifying neighborhood? She calls it a Mayonegg. Waiting for the Emmys. BTW did you know won 6 Emmys and was still canceled early by Fox? COME ON. I'll buy you a hundred George Michaels that you can teach to drive! Never once touched my per diem. I'd go to Craft Service, get some raw veggies, bacon, Cup-A-Soup…baby, I got a stew goin'"
+    content = (baseContent for _ in [1..200]).join('\n')
+    outputAlwaysEndsWithStew = true
+    process = new BufferedProcess
+      command: '/bin/echo'
+      args: [content]
+      options: {}
+      stdout: (lines) ->
+        endLength = 10
+        end = baseContent.substr(baseContent.length - endLength, endLength)
+        lineEndsWithStew = lines.substr(lines.length - endLength, endLength) is end
+        expect(lineEndsWithStew).toBeTrue
+
+        outputAlwaysEndsWithStew = outputAlwaysEndsWithStew && lineEndsWithStew
+      exit: exitCallback
+
+    waitsFor -> exitCallback.callCount is 1
+
+    runs ->
+      expect(outputAlwaysEndsWithStew).toBeTrue


patch loggedTimeModel instantiation to work with cherrypick of 53433b; #316

--- app/assets/javascripts/dashboard/views/dashboard_view.coffee
+++ app/assets/javascripts/dashboard/views/dashboard_view.coffee
@@ -68,6 +68,6 @@

   showTrackedTime : ->

-    view = new TrackedTimeView(model : new LoggedTimeModel())
-    @tabPane.show(view)
+    trackedTimeView = new TrackedTimeView(model : @model.get("loggedTime"))
+    @tabPane.show(trackedTimeView)


Updated product controller to use product master variants images

--- app/assets/javascripts/sprangular/controllers/product.coffee
+++ app/assets/javascripts/sprangular/controllers/product.coffee
@@ -2,7 +2,8 @@
   $scope.product = product
   $scope.adding = false
   $scope.selected =
-    image: product.variants[0].images[0]
+    image: null
+    images: []
     quantity: 1
     variant: null

@@ -12,7 +13,14 @@
   Status.pageTitle = $scope.product.name

   $scope.$watch 'selected.variant', (variant) ->
-    $scope.selected.image = variant.images[0] if variant
+    if variant && variant.images.length > 0
+      $scope.selected.images = variant.images
+    else if product.hasVariants
+      $scope.selected.images = product.variants[0].images
+    else
+      $scope.selected.images = product.images
+
+    $scope.selected.image = $scope.selected.images[0]

   $scope.changeQuantity = (val) ->
     $scope.selected.quantity = val


Set Status.meta.title when displaying product

--- app/assets/javascripts/sprangular/controllers/product.coffee
+++ app/assets/javascripts/sprangular/controllers/product.coffee
@@ -9,6 +9,7 @@
     variant: null

   Status.pageTitle        = $scope.product.name
+  Status.meta.title       = $scope.product.meta_title
   Status.meta.description = $scope.product.meta_description
   Status.meta.keywords    = $scope.product.meta_keywords


add "remove" option to billing rates

add "remove" option to billing rates

--- app/assets/javascripts/views/admin/billing_rates_item.coffee
+++ app/assets/javascripts/views/admin/billing_rates_item.coffee
@@ -7,6 +7,19 @@

   tagName: "tr",
   template: _.template("""
-    <%= project %>
-    <%= rate %>
+
+      <%= project %>
+
+
+      <%= rate %>
+
+
   """)
+
+  events:
+    "click #link_remove_item": "removeItem"
+
+
+  removeItem: ->
+
+    @model.destroy()


Fix the community select view so that it triggers model updates when fields change.

--- app/assets/javascripts/views/community_select_view.js.coffee
+++ app/assets/javascripts/views/community_select_view.js.coffee
@@ -34,6 +34,7 @@
     collectionUpdateHandler: ->
       if @collection.length > 0
         $('#community_select').removeClass('hidden')
+        $(@listSelector).change()
       else
         $('#community_select').addClass('hidden')

@@ -47,14 +48,21 @@
       for index, component of place.address_components
         if $.inArray('neighborhood', component.types) >= 0
           $('#user_neighbourhood').val(component.long_name)
+          $('#user_neighbourhood').change()
         if $.inArray('postal_code', component.types) >= 0
           $('#user_postal_code').val(component.long_name)
+          $('#user_postal_code').change()
         if $.inArray('locality', component.types) >= 0
           $('#user_city').val(component.long_name)
+          $('#user_city').change()
         if $.inArray('administrative_area_level_1', component.types) >= 0
           $('#user_province').val(component.long_name)
+          $('#user_province').change()
         if $.inArray('country', component.types) >= 0
           $('#user_country').val(component.long_name)
+          $('#user_country').change()

       $('#user_latitude').val(place.geometry.location.k)
+      $('#user_latitude').change()
       $('#user_longitude').val(place.geometry.location.B)
+      $('#user_longitude').change()


default time range to 90d

default time range to 90d

--- src/components/widgets/sales-new-vs-existing-customers/sales-new-vs-existing-customers.directive.coffee
+++ src/components/widgets/sales-new-vs-existing-customers/sales-new-vs-existing-customers.directive.coffee
@@ -18,34 +18,34 @@
     $scope.chartDeferred.promise
   ]

+  $scope.displayOptions = [
+    {label: 'Customers', value: 'customers_count'},
+    {label: 'Total Sales', value: 'total_sales'},
+    {label: 'Transactions', value: 'transactions_count'},
+  ]
+  $scope.displayType = angular.copy(_.find($scope.displayOptions, (o) ->
+    w.metadata && (o.value == w.metadata.display_type)
+  ) || $scope.displayOptions[0])
+
+  $scope.timeRangeOptions = [
+    {label: 'Last 24h', value: '-1d'},
+    {label: 'Last 5 days', value: '-5d'},
+    {label: 'Last 7 days', value: '-7d'},
+    {label: 'Last 30 days', value: '-30d'},
+    {label: 'Last 45 days', value: '-45d'},
+    {label: 'Last 60 days', value: '-60d'},
+    {label: 'Last 90 days', value: '-90d'},
+  ]
+  $scope.timeRange = angular.copy(_.find($scope.timeRangeOptions, (o) ->
+    w.metadata && (o.value == w.metadata.time_range)
+  ) || $scope.timeRangeOptions[6])

   # Widget specific methods
   # --------------------------------------
   w.initContext = ->
-    $scope.isDataFound = w.content? && w.content.summary? && w.content.summary.customers_count? &&
+    $scope.isDataFound = w.content? && w.content.summary? && w.content.summary.customers_count? &&
     w.content.summary.customers_count.total? && w.content.summary.customers_count.total > 0
-
-    $scope.displayOptions = [
-      {label: 'Customers', value: 'customers_count'},
-      {label: 'Total Sales', value: 'total_sales'},
-      {label: 'Transactions', value: 'transactions_count'},
-    ]
-    $scope.displayType = angular.copy(_.find($scope.displayOptions, (o) ->
-      w.metadata && (o.value == w.metadata.display_type)
-    ) || $scope.displayOptions[0])
-
-    $scope.timeRangeOptions = [
-      {label: 'Last 24h', value: '-1d'},
-      {label: 'Last 5 days', value: '-5d'},
-      {label: 'Last 7 days', value: '-7d'},
-      {label: 'Last 30 days', value: '-30d'},
-      {label: 'Last 45 days', value: '-45d'},
-      {label: 'Last 60 days', value: '-60d'},
-      {label: 'Last 90 days', value: '-90d'},
-    ]
-    $scope.timeRange = angular.copy(_.find($scope.timeRangeOptions, (o) ->
-      w.metadata && (o.value == w.metadata.time_range)
-    ) || $scope.timeRangeOptions[3])
+

   $scope.displayTypeOnClick = () ->
     $scope.updateSettings(false)


Update test

Update test

--- test/lib/Strategy/DBStrategy/Save/TemporaryTable/_3DCartSaveOrdersSpec.coffee
+++ test/lib/Strategy/DBStrategy/Save/TemporaryTable/_3DCartSaveOrdersSpec.coffee
@@ -53,9 +53,8 @@

   it "should save new objects @fast", ->
     knex.transaction (transaction) =>
-      strategy.transaction = transaction
       Promise.bind(@)
-      .then -> strategy.start()
+      .then -> strategy.start(transaction)
       .then -> strategy.insert(sample)
       .then -> strategy.finish()
     .then ->
@@ -76,9 +75,8 @@
         ,
           dependencies
         )
-        strategy.transaction = transaction
         Promise.bind(@)
-        .then -> strategy.start()
+        .then -> strategy.start(transaction)
         .then -> strategy.insert(sample)
         .then -> strategy.finish()
     .then ->
@@ -88,9 +86,8 @@
         ,
           dependencies
         )
-        strategy.transaction = transaction
         Promise.bind(@)
-        .then -> strategy.start()
+        .then -> strategy.start(transaction)
         .then -> strategy.insert _.defaults
           "InvoiceNumber": 344
         , sample


minutely more helpful error message

minutely more helpful error message


Fix NaiveBayesModel array-of-2dtables case.

--- src/ext/modules/routines.coffee
+++ src/ext/modules/routines.coffee
@@ -478,7 +478,7 @@
     DeepLearningModelOutput:
       fields: 'names domains help'
     NaiveBayesOutput:
-      fields: 'names domains help'
+      fields: 'names domains help pcond'
     PCAOutput:
       fields: 'names domains help'
     ModelMetricsBinomialGLM:
@@ -563,8 +563,15 @@
   extendModel = (model) ->
     inspections = {}
     inspections.parameters = inspectModelParameters model
-
-    inspectObject inspections, 'output', "getModel #{stringify model.model_id.name}", model.output
+    origin = "getModel #{stringify model.model_id.name}"
+    inspectObject inspections, 'output', origin, model.output
+
+    # Obviously, an array of 2d tables calls for a megahack.
+    if model.__meta.schema_type is 'NaiveBayesModel'
+      if isArray model.output.pcond
+        for table in model.output.pcond
+          tableName = "output - pcond - #{table.name}"
+          inspections[tableName] = inspectTwoDimTable_ origin, tableName, table

     inspect_ model, inspections
     render_ model, H2O.ModelOutput, model


Moved atom.loadPackage to a spec-only helper function

--- spec/markdown-preview-spec.coffee
+++ spec/markdown-preview-spec.coffee
@@ -5,7 +5,7 @@
 describe "MarkdownPreview", ->
   beforeEach ->
     rootView = new RootView(require.resolve('fixtures/markdown'))
-    atom.loadPackage("markdown-preview")
+    window.loadPackage("markdown-preview")
     spyOn(MarkdownPreview.prototype, 'loadHtml')

   afterEach ->


Make `project` a global and refactor startup process

--- spec/markdown-preview-spec.coffee
+++ spec/markdown-preview-spec.coffee
@@ -5,7 +5,8 @@

 describe "MarkdownPreview", ->
   beforeEach ->
-    rootView = new RootView(require.resolve('fixtures/markdown'))
+    project.setPath(project.resolve('markdown'))
+    rootView = new RootView(project.getPath())
     window.loadPackage("markdown-preview")
     spyOn(MarkdownPreview.prototype, 'loadHtml')


Workers: Tests: Remove invalid test case

--- workers/social/lib/social/models/computeproviders/computeprovider.test.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.test.coffee
@@ -202,14 +202,6 @@

       expectAccessDenied ComputeProvider, 'fetchTeamPlans', done

-    it 'should fail if user doesnt have permission', (done) ->
-
-      withConvertedUser ({ client }) ->
-
-        ComputeProvider.fetchTeamPlans client, (err, plans) ->
-          expect(err).to.exist
-          expect(err.message).to.be.equal 'Access denied'
-          done()

     it 'should be able to fetch team plans for koding admins', (done) ->


console.{log,warn} → print{Line,Warn}

console.{log,warn} → print{Line,Warn}


Dead code removal

Dead code removal

--- packages/shrub-form/client/index.coffee
+++ packages/shrub-form/client/index.coffee
@@ -125,5 +125,4 @@
 		'widget/submit'
 		'widget/select'
 		'widget/text'
-		'widget/toggle'
 	]


Run, jenkins

Run, jenkins

--- app/assets/javascripts/oxalis/controller/viewmodes/arbitrary_controller.coffee
+++ app/assets/javascripts/oxalis/controller/viewmodes/arbitrary_controller.coffee
@@ -113,7 +113,7 @@

     @input.keyboard = new Input.Keyboard(

-      #Scale plane
+      # Scale plane
       "l"             : (timeFactor) => @arbitraryView.applyScale -@model.user.get("scaleValue")
       "k"             : (timeFactor) => @arbitraryView.applyScale  @model.user.get("scaleValue")


simplified adding of methods to collection instance

--- server/collection-security.coffee
+++ server/collection-security.coffee
@@ -3,6 +3,12 @@
     @_logging = value
   @_log: (msg, level = 'debug') ->
     console[level] msg if @_logging or level is 'error'
+  @_addMethods: (parent) ->
+    createMethodCall = (method) -> ->
+      instance = CollectionSecurity._getInstance this
+      instance[method].apply instance, arguments
+
+    parent::attachSecurity = createMethodCall 'attachSecurity'
   @_utilities:
     any: (array, value) ->
       return true for element in array when element is value
@@ -84,11 +90,7 @@

+
 if Mongo?.Collection?
-  Mongo.Collection.prototype.attachSecurity = (rules) ->
-    instance = CollectionSecurity._getInstance this
-    instance.attachSecurity rules
-
-Meteor.Collection.prototype.attachSecurity = (rules) ->
-  instance = CollectionSecurity._getInstance this
-  instance.attachSecurity rules
+  CollectionSecurity._addMethods Mongo.Collection
+CollectionSecurity._addMethods Meteor.Collection


Normalise influential query, exclude retweeted tweets from query

--- web/app/models/tweets.coffee
+++ web/app/models/tweets.coffee
@@ -28,9 +28,14 @@
 module.exports.getInfluential = (skip, limit, callback) ->
 	query = """
 	MATCH (u:User)-[:tweets]->(t:Tweet)
-	WITH u as user, SUM(t.retweet_count) as tweets_retweet_count, SUM(t.favorite_count) as tweets_favorite_count
-	RETURN user, tweets_retweet_count, tweets_favorite_count,
-		tweets_retweet_count*0.4+tweets_favorite_count*0.3+user.followers_count*0.2+user.listed_count*0.1 AS influence_factor
+	WHERE left(t.text, 4)<>'RT @'
+	WITH u, SUM(t.retweet_count)/COUNT(t) as tweets_retweet_count, SUM(t.favorite_count)/COUNT(t) as tweets_favorite_count
+	WITH MAX(tweets_retweet_count) as tweets_retweet_count_max, MAX(tweets_favorite_count) as tweets_favorite_count_max, MAX(u.followers_count) as followers_count_max, MAX(u.listed_count) as listed_count_max
+	MATCH (u:User)-[:tweets]->(t:Tweet)
+	WHERE left(t.text, 4)<>'RT @'
+	WITH u as user, SUM(t.retweet_count)/count(t) as tweets_retweet_count, SUM(t.favorite_count)/count(t) as tweets_favorite_count, tweets_retweet_count_max, tweets_favorite_count_max, followers_count_max, listed_count_max
+	RETURN user, tweets_retweet_count, tweets_favorite_count,
+	((0.4*tweets_retweet_count)/tweets_retweet_count_max + (0.3*tweets_favorite_count)/tweets_favorite_count_max + (0.2*user.followers_count)/followers_count_max + (0.1*user.listed_count)/listed_count_max) AS influence_factor
 	ORDER BY influence_factor DESC SKIP {skip} LIMIT {limit}
 	"""


Child components can trigger problem updates.

--- web/coffee/management.coffee
+++ web/coffee/management.coffee
@@ -7,16 +7,19 @@
     problems: []
     tabKey: 1

+  onProblemChange: ->
+    apiCall "GET", "/api/admin/problems"
+    .done ((api) ->
+      @setState React.addons.update @state,
+        {problems: {$set: api.data}}
+    ).bind this
+
   componentDidMount: ->
     # Formatting hack
     $("#main-content>.container").addClass("container-fluid")
     $("#main-content>.container").removeClass("container")

-    apiCall "GET", "/api/admin/problems"
-    .done ((api) ->
-      @setState React.addons.update @state,
-        {problems: {$set: api.data}}
-    ).bind this
+    @onProblemChange()

   onTabSelect: (tab) ->
     @setState React.addons.update @state,
@@ -26,7 +29,7 @@
   render: ->
       <TabbedArea activeKey={@state.tabKey} onSelect={@onTabSelect}>
         <TabPane eventKey={1} tab='Manage Problems'>
-          <ProblemTab problems={@state.problems}/>
+          <ProblemTab problems={@state.problems} onProblemChange={@onProblemChange}/>
         </TabPane>
         <TabPane eventKey={2} tab='Exceptions'>
           <ExceptionTab/>


Remove news

Remove news

--- server/materials/data/news.coffee
+++ server/materials/data/news.coffee
@@ -6,9 +6,6 @@
         newsItem("Еще два опроса", String.raw"""
             Заполните, пожалуйста, еще два опроса про алгопрог: раз, два.
         """),
-        newsItem("Занятия 19 января в лицее 40 не будет", String.raw"""
-            19 января занятия в лицее 40 не будет.
-        """),
         newsItem("Опрос про алгопрог", String.raw"""
             Ответьте, пожалуйста, на несколько вопросов. Тем, кто уже отвечал — я там добавил несколько вопросов, можете ответить еще раз.
         """),


Limit message visibility to recipients.

Limit message visibility to recipients.

--- server/publications.coffee
+++ server/publications.coffee
@@ -4,8 +4,7 @@
 Meteor.publish 'messages', (projectId) ->
   check projectId, String
   # Find all messages for the current project which have our user as sender or recipient
-  Messages.find {project: projectId}
-  # {$and: [{$or: [{recipients: this.userId}, {sender: this.userId}]}, {project: this.projectId}]}
+  Messages.find {$and: [{$or: [{recipients: {$in: [this.userId]}}, {sender: this.userId}]}, {project: projectId}]}

 Meteor.publish 'usernames', ->
   Meteor.users.find {}, {fields: {'emails': 1, '_id': 1, 'telephone_nr': 1, 'skype_id': 1, 'name': 1}}


Frame Controller gestures and timestamp

Frame Controller gestures and timestamp

--- server/src/FrameController.coffee
+++ server/src/FrameController.coffee
@@ -76,7 +76,10 @@
         if not frame.valid or frame.hands is null or frame.hands.length is 0
             console.log "Invalid frame or no hands detected"
         else
-            @model = []
+            @model =
+                hands : []
+                gestures : []
+                timestamp : frame.timestamp
             for hand in frame.hands
                 if(config.stabilize)
                     console.log "Stabilized position in use!"
@@ -85,14 +88,12 @@
                     position = hand.palmPosition
                 palmPosition = @relative3DPosition(frame, position)

-
                 pinchStrength = hand.pinchStrength
                 if pinchStrength > 0
                     pinchingFinger = @findPinchingFingerType hand
                 else
                     pinchingFinger = null

-                # Update frame model
                 handModel =
                     type : hand.type
                     extendedFingers:
@@ -105,7 +106,16 @@
                     grabStrength : hand.grabStrength
                     pinchStrength : pinchStrength
                     pinchingFinger : pinchingFinger
-                @model.push handModel
+                @model.hands.push handModel
+
+            # Gestures
+            for gesture in frame.gestures
+                gestureModel =
+                    type : gesture.type
+                    duration : gesture.duration
+                    progress: gesture.progress
+                    state : gesture.state
+                @model.gestures.push gestureModel
             @emit 'update', @model
         console.log "Processed frame: ", frame.id
         return
@@ -177,6 +187,10 @@

 consume = () ->
     frame = leapController.frame()
+
+    # Skip invalid frame processing
+    if(frame is null or frame.valid is false)
+        return
     frameController.processFrame(frame)
     console.log "Consumed frame ", frame.id


Fix root folder issue

Fix root folder issue

--- server/sign_request.coffee
+++ server/sign_request.coffee
@@ -29,7 +29,10 @@
 		expiration = new Date Date.now() + ops.expiration
 		expiration = expiration.toISOString()

-		key = "#{ops.path}/#{ops.file_name}"
+		if _.isEmpty ops.path
+			key = "#{ops.file_name}"
+		else
+			key = "#{ops.path}/#{ops.file_name}"

 		policy =
 			"expiration":expiration


Repair broken space.

Repair broken space.

--- websockets/chat_manager.coffee
+++ websockets/chat_manager.coffee
@@ -14,7 +14,7 @@
                 content: data.content

         # client tries to post something to teams chat
-        socket.on 'chat:team:post', (data)=>
+        socket.on 'chat:team:post', (data) =>
             return unless validateFormat data
             date = new Date
             ws.sendToUserTeam socket.user, 'chat:team:in',
@@ -25,7 +25,7 @@

         # client tries to post something to their side chat
         # TODO not implemented
-        socket.on 'chat:side:post', (data)=>
+        socket.on 'chat:side:post', (data) =>
             return
 #           return unless validateFormat data
 #           date = new Date
@@ -37,7 +37,7 @@

         # client tries to post something to the support chat
         # TODO not implemented
-        socket.on 'chat:support:post', (data)=>
+        socket.on 'chat:support:post', (data) =>
             return
 #           return unless validateFormat data
 #           date = new Date


fix compatibility with couchdb 1.3

fix compatibility with couchdb 1.3

--- server/models/requests.coffee
+++ server/models/requests.coffee
@@ -39,10 +39,10 @@
         byMailboxRequest:
             reduce: '_count'
             map: (doc) ->
-                if Object.keys(doc.mailboxIDs).length is 0
-                    emit ['nobox']
+                nobox = true

                 for boxid, uid of doc.mailboxIDs
+                    nobox = false
                     docDate = doc.date or (new Date()).toISOString()
                     emit ['uid', boxid, uid], doc.flags

@@ -61,6 +61,9 @@
                                                         doc.normSubject], null
                 undefined # prevent coffeescript comprehension

+                if nobox
+                    emit ['nobox']
+
         # this map is used to dedup by message-id
         dedupRequest: (doc) ->
             if doc.messageID


test_heatmap.coffee: had mychart2 twice; 2nd one should be mychart3

--- test/heatmap/test_heatmap.coffee
+++ test/heatmap/test_heatmap.coffee
@@ -15,5 +15,5 @@

 # Example: categorical scales
 d3.json "data_categorical.json", (data) ->
-    mychart2 = d3panels.heatmap({height:h, width:w})
-    mychart2(d3.select("div#chart3"), data)
+    mychart3 = d3panels.heatmap({height:h, width:w})
+    mychart3(d3.select("div#chart3"), data)


removed dead etcd sync code (server/synchronization.coffee)

--- server/synchronization.coffee
+++ server/synchronization.coffee
@@ -1,83 +0,0 @@
-currentProject = null
-
-Meteor.startup ->
-  startSync = (doc) ->
-    currentProject = doc.project
-    # sync()
-
-toApp = (node) ->
-  [ignore..., keyBase, project, appName, version] = node.key?.split('/')
-  {project: project, name: appName, key: node.key?.split('/')[0..-2].join '/'}
-
-@sync = (callback) ->
-  syncWithBaseKey = (baseKey, handler) ->
-
-    getData = ->
-      reqToken = null
-      proj = Settings.findOne().project
-      if proj is currentProject # a hack to stop prveious waits; meteor HTTP does not expose the request object, can't abort
-        recursiveUrl = "#{baseKey}/#{proj}?recursive=true"
-        waitForChange = ->
-          if reqToken and not reqToken.isAborted()
-            reqToken.abort()
-          reqToken = EtcdClient.wait recursiveUrl, (err, result) ->
-            console.error err if err
-            getData()
-        EtcdClient.discover recursiveUrl, (err, nodes) ->
-          console.error err if err
-          waitForChange()
-          handler err, nodes
-    getData()
-
-  try
-    syncWithBaseKey "apps", (err, nodes) ->
-      throw new Error err if err
-
-      apps = if nodes then (toApp node for node in nodes) else []
-      Apps.updateCollection apps
-
-      objects = []
-      if nodes
-        for n in nodes
-          [ignore..., keyBase, project, appName, version] = n.key.split('/')
-
-          objects.push
-            key: n.key
-            project: project
-            name: appName
-            version: version
-            def: n.value
-            tags: Helper.extractTags n.value
-
-      ApplicationDefs.updateCollection objects
-
-    # syncWithBaseKey "instances", (err, nodes) ->
-    #   throw new Error err if err
-    #
-    #   objects = {}
-    #   if nodes
-    #     for n in nodes
-    #       [ignore..., project, appName, instanceName, serviceName, propertyName] = n.key.split('/')
-    #       key = [project, appName, instanceName].join '/'
-    #       objects[key] = {services:{}, meta:{}} unless objects[key]
-    #       if serviceName == 'meta_'
-    #         if propertyName == 'parameters'
-    #           objects[key].parameters = EJSON.parse n.value
-    #         else
-    #           objects[key].meta[propertyName] = n.value
-    #       else
-    #         objects[key].services[serviceName] = {} unless objects[key].services[serviceName]
-    #         objects[key].services[serviceName][propertyName] = n.value
-    #
-    #       objects[key].project = project
-    #       objects[key].application = appName
-    #       objects[key].name = instanceName
-    #       objects[key].key = key
-    #
-    #   Instances.updateCollection (value for key, value of objects)
-
-    callback?()
-
-  catch
-    console.error "Error while trying to read #{baseKey}!"
-    sync() # go back to syncing


Expose error message from adduser

Expose error message from adduser

--- public/scripts/views/AddUserView.coffee
+++ public/scripts/views/AddUserView.coffee
@@ -227,7 +227,7 @@
     else if(XMLHttpRequest.status == 403)
       bootbox.alert(XMLHttpRequest.responseJSON.description)
     else
-      bootbox.alert("There was an error when adding user.\nPlease verify privileges on edit user screen.")
+      bootbox.alert("There was an error when adding user.\n\n" + XMLHttpRequest.responseText)


changed error log message

changed error log message

--- services/filestore/app.coffee
+++ services/filestore/app.coffee
@@ -43,7 +43,12 @@
 			url:req.url
 			key: req.key
 			statusCode: req.statusCode
-		err.domainEmitter.res = "to big to log"
+		err =
+			message: err.message
+			stack: err.stack
+			name: err.name
+			type: err.type
+			arguments: err.arguments
 		logger.err err:err, req:req, res:res, "uncaught exception thrown on request"
 	requestDomain.run next


trivial fix

trivial fix

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -648,7 +648,7 @@
     # Sets the active file_sort to next_column_name
     set_sorted_file_column: (column_name) =>
         current = @get_store()?.active_file_sort
-        if current.column_name == column_name
+        if current?.column_name == column_name
             is_descending = not current.is_descending
         else
             is_descending = false


tasks: link to wiki page for help

tasks: link to wiki page for help

--- src/smc-webapp/tasks/actions.coffee
+++ src/smc-webapp/tasks/actions.coffee
@@ -8,6 +8,8 @@
 {Actions}  = require('../smc-react')

 misc = require('smc-util/misc')
+
+WIKI_HELP_URL = "https://github.com/sagemathinc/cocalc/wiki/tasks"

 class exports.TaskActions extends Actions
     _init: (project_id, path, syncdb, store, client) =>
@@ -108,7 +110,6 @@
         @set_current_task(task.task_id)
         @set_editing(task.task_id)

-
     delete_task: (task_id) =>
         if not task_id?
             return
@@ -141,6 +142,7 @@
             foreground : true

     help: =>
+        window.open(WIKI_HELP_URL, "_blank").focus()

     set_editing: (task_id) =>


Zoom -> Scale

Zoom -> Scale

--- framer/DeviceView.coffee
+++ framer/DeviceView.coffee
@@ -24,8 +24,8 @@
 Device.rotateLeft()
 Device.rotateRight()

-Device.setDeviceZoom(zoom:float, animate:bool)
-Device.setContentZoom(zoom:float, animate:bool)
+Device.setDeviceScale(zoom:float, animate:bool)
+Device.setContentScale(zoom:float, animate:bool)

 Device.keyboard bool
 Device.setKeyboard(visible:bool, animate:bool)
@@ -45,8 +45,8 @@

 # _.extend Events,
 # 	DeviceTypeDidChange: "change:deviceType"
-# 	DeviceZoomDidChange: "change:deviceZoom"
-# 	DeviceContentZoomDidChange: "change:contentZoom"
+# 	DeviceScaleDidChange: "change:deviceScale"
+# 	DeviceContentScaleDidChange: "change:contentScale"
 # 	DeviceFullScreenDidChange: ""

 class exports.DeviceView extends BaseClass
@@ -165,26 +165,26 @@
 	###########################################################################
 	# DEVICE ZOOM

-	@define "deviceZoom",
-		get: -> @_deviceZoom
-		set: (deviceZoom) -> @setDeviceZoom(deviceZoom, false)
-
-	setDeviceZoom: (deviceZoom, animate=false) ->
-
-		if deviceZoom == "fit" or deviceZoom < 0
-			deviceZoom = "fit"
-		else
-			deviceZoom = parseFloat(deviceZoom)
-
-		if deviceZoom == @_deviceZoom
-			return
-
-		@_deviceZoom = deviceZoom
-
-		if deviceZoom == "fit"
+	@define "deviceScale",
+		get: -> @_deviceScale
+		set: (deviceScale) -> @setDeviceScale(deviceScale, false)
+
+	setDeviceScale: (deviceScale, animate=false) ->
+
+		if deviceScale == "fit" or deviceScale < 0
+			deviceScale = "fit"
+		else
+			deviceScale = parseFloat(deviceScale)
+
+		if deviceScale == @_deviceScale
+			return
+
+		@_deviceScale = deviceScale
+
+		if deviceScale == "fit"
 			phoneScale = @_calculatePhoneScale()
 		else
-			phoneScale = deviceZoom
+			phoneScale = deviceScale

 		@phone.animateStop()

@@ -195,15 +195,15 @@
 			@phone.scale = phoneScale
 			@phone.center()

-		@emit("change:deviceZoom")
+		@emit("change:deviceScale")

 	_calculatePhoneScale: ->

 		# Calculates a phone scale that fits the screen unless a fixed value is set

-		if @_deviceZoom and @_deviceZoom isnt "fit"
-			return @_deviceZoom
+		if @_deviceScale and @_deviceScale isnt "fit"
+			return @_deviceScale

 		[width, height] = @_getOrientationDimensions(@phone.width, @phone.height)

@@ -217,26 +217,26 @@
 	###########################################################################
 	# CONTENT ZOOM

-	@define "contentZoom",
-		get: -> @_contentZoom
-		set: (contentZoom) -> @setContentZoom(contentZoom, false)
-
-	setContentZoom: (contentZoom, animate=false) ->
-
-		contentZoom = parseFloat(contentZoom)
-
-		if contentZoom is @_contentZoom
-			return
-
-		@_contentZoom = contentZoom
+	@define "contentScale",
+		get: -> @_contentScale
+		set: (contentScale) -> @setContentScale(contentScale, false)
+
+	setContentScale: (contentScale, animate=false) ->
+
+		contentScale = parseFloat(contentScale)
+
+		if contentScale is @_contentScale
+			return
+
+		@_contentScale = contentScale

 		if animate
 			@content.animate _.extend @animationOptions,
-				properties: {scale: @_contentZoom}
-		else
-			@content.scale = @_contentZoom
-
-		@emit("change:contentZoom")
+				properties: {scale: @_contentScale}
+		else
+			@content.scale = @_contentScale
+
+		@emit("change:contentScale")

 	###########################################################################
@@ -378,6 +378,9 @@
 		@setKeyboard(!@keyboard, animate)

 	_renderKeyboard: ->
+
+		return unless @_device.keyboards
+
 		@keyboardLayer.image  = @_deviceImageUrl @_device.keyboards[@orientationName].image
 		@keyboardLayer.width  = @_device.keyboards[@orientationName].width
 		@keyboardLayer.height = @_device.keyboards[@orientationName].height
@@ -430,10 +433,10 @@
 	# keyboardHeight: 0

 AppleWatchDevice =
-	deviceImageWidth: 831
-	deviceImageHeight: 986
-	screenWidth: 360
-	screenHeight: 426
+	deviceImageWidth: 831 * 0.88
+	deviceImageHeight: 986 * 0.88
+	screenWidth: 320
+	screenHeight: 320
 	# keyboardImage: "ios-keyboard.png"
 	# keyboardWidth: 0
 	# keyboardHeight: 0


Scenario 25. added

Scenario 25. added

Scenario 25. one attribute in the child tag with space at the end

--- test/scenario-based-testing.coffee
+++ test/scenario-based-testing.coffee
@@ -264,4 +264,14 @@
     # console.log output
     expectedOutput = fs.readFileSync './test/scenarios/scenario-24-expected.html', 'utf8'
     # console.log expectedOutput
+    expect(output).to.equal(expectedOutput)
+
+  it 'Scenario 25. one attribute in the child tag with space at the end', ->
+
+    input = fs.readFileSync './test/scenarios/scenario-25-input.cohtml', 'utf8'
+    # console.log input
+    output = toHtml5 input
+    # console.log output
+    expectedOutput = fs.readFileSync './test/scenarios/scenario-25-expected.html', 'utf8'
+    # console.log expectedOutput
     expect(output).to.equal(expectedOutput)


Convenience name change for imports.

Convenience name change for imports.

--- snippets/es6_snippets.cson
+++ snippets/es6_snippets.cson
@@ -30,40 +30,41 @@
     body: "axios.get('${1}').then(res => {\n\tconsole.log(res);\n}).catch(error => {\n\tconsole.log(error);\n});"

   "ES6: Import default from module":
-    prefix: "i"
+    prefix: "i_"
     leftLabelHTML: "Ⓢ"
     rightLabelHTML: "ES6 import ${1} from '${2}';"
     body: "import ${1} from '${2}';"
-  "ES6: Import local module":
-    prefix: "ii1"
-    leftLabelHTML: "Ⓢ"
-    rightLabelHTML: "ES6 import ${1} from './${2}';"
-    body: "import ${1} from './${2}';"
-  "ES6: Import local module 1 level":
-    prefix: "ii2"
-    leftLabelHTML: "Ⓢ"
-    rightLabelHTML: "ES6 import ${1} from './../${2}';"
-    body: "import ${1} from './../${2}';"
-  "ES6: Import local module 2 levels":
-    prefix: "ii3"
-    leftLabelHTML: "Ⓢ"
-    rightLabelHTML: "ES6 import ${1} from './../../${2}';"
-    body: "import ${1} from './../../${2}';"
-  "ES6: Import local module 3 levels":
-    prefix: "ii4"
-    leftLabelHTML: "Ⓢ"
-    rightLabelHTML: "ES6 import ${1} from './../../../${2}';"
-    body: "import ${1} from './../../../${2}';"
-  "ES6: Import * as MODULE from 'module'":
-    prefix: "iias"
-    leftLabelHTML: "Ⓢ"
-    rightLabelHTML: "ES6 import * as ${1} from '${2}';"
-    body: "import * as ${1} from '${2}';"
   "ES6: Import module from":
     prefix: "iim"
     leftLabelHTML: "Ⓢ"
     rightLabelHTML: "ES6 import { ${1} } from '${2}';"
     body: "import { ${1} } from '${2}';"
+  "ES6: Import local module":
+    prefix: "iiimport"
+    leftLabelHTML: "Ⓢ"
+    rightLabelHTML: "ES6 import ${1} from './${2}';"
+    body: "import ${1} from './${2}';"
+  "ES6: Import local module 1 level":
+    prefix: "ii1"
+    leftLabelHTML: "Ⓢ"
+    rightLabelHTML: "ES6 import ${1} from './../${2}';"
+    body: "import ${1} from './../${2}';"
+  "ES6: Import local module 2 levels":
+    prefix: "ii2"
+    leftLabelHTML: "Ⓢ"
+    rightLabelHTML: "ES6 import ${1} from './../../${2}';"
+    body: "import ${1} from './../../${2}';"
+  "ES6: Import local module 3 levels":
+    prefix: "ii3"
+    leftLabelHTML: "Ⓢ"
+    rightLabelHTML: "ES6 import ${1} from './../../../${2}';"
+    body: "import ${1} from './../../../${2}';"
+  "ES6: Import * as MODULE from 'module'":
+    prefix: "ias"
+    leftLabelHTML: "Ⓢ"
+    rightLabelHTML: "ES6 import * as ${1} from '${2}';"
+    body: "import * as ${1} from '${2}';"
+

   "ES6: export default MODULE":
     prefix: "eedefault"


landing: fix a race condition while registering an account

--- client/landing/site.landing/coffee/login/registerform.coffee
+++ client/landing/site.landing/coffee/login/registerform.coffee
@@ -10,9 +10,12 @@
   ENTER = 13

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

     @emailIsAvailable = no
+    @gravatars        ?= {}
+
     @on 'EmailIsAvailable'   , => @emailIsAvailable = yes
     @on 'EmailIsNotAvailable', => @emailIsAvailable = no

@@ -175,8 +178,6 @@

     return unless isEmail

-    @gravatars ?= {}
-
     return @emit 'gravatarInfoFetched', @gravatars[email]  if @gravatars[email]

     $.ajax


minor change in condition.

minor change in condition.

--- app/assets/javascripts/api/define_behavior.js.coffee
+++ app/assets/javascripts/api/define_behavior.js.coffee
@@ -17,8 +17,8 @@
         self.authorSetting event

   $(".juvia-comment-editable-content:not(juvia-installed-behavior)").each ->
-    if self.user_logged_in and self.author_key is $(".juvia-comment-pure-content").data("key")
-      $this = $(this)
+    $this = $(this)
+    if self.user_logged_in and self.author_key is $this.data("key")
       $this.addClass "juvia-installed-behavior"
       $this.bind "dblclick", ->
         self.editComment this


Add mediaType back

Add mediaType back

--- scripts/models/content/inherits/base.coffee
+++ scripts/models/content/inherits/base.coffee
@@ -26,6 +26,12 @@

     mediaType: 'application/vnd.org.cnx.module'

+    # Modules, Books, and Folders all need to send a `mediaType`
+    toJSON: () ->
+      json = super()
+      json.mediaType = @mediaType
+      return json
+
     getTitle: (container) ->
       if @unique
         title = @get('title')


Update how and when validations are triggered.

--- src/traits/HasValidatorsView.coffee
+++ src/traits/HasValidatorsView.coffee
@@ -9,7 +9,15 @@
 parser     = require '../validator/parser'

-VALIDATION_DEBOUNCE = 300
+###*
+# Inner constant used for debouncing validation related functions.
+#
+# @const number
+# @inner
+###
+VALIDATION_DEBOUNCE = 100
+
+

 ###*
 # The `HasValidatorsView`
@@ -78,12 +86,23 @@
       doValidation()
     , VALIDATION_DEBOUNCE

-    # Perform a full validation when the field is blurred.
-    @on 'blur', => if @dirty then doValidation()
+    # Perform a full validation when the field is blurred. We have to debounce
+    # this listener most importantly because we depend on the `#dirty` property
+    # being set correctly on the first blur event. This happens syncronously
+    # but this method may be called before `#dirty` is set. Therefore, we
+    # debounce to ensure the event loop has cleared and the property is set
+    # correctly.
+    @on 'blur', debounce =>
+      if @dirty then doValidation()
+    , VALIDATION_DEBOUNCE

     @on 'focus', =>
-      val = @context[@context.validateProperty]
-      @context.validatorGroup.softValidate(val)
+      @context.validatorGroup.softValidate  @context[@context.validateProperty]

     # When the field changes, perform a live debounced full validation.
-    @context.on "change:#{@context.validateProperty}", doLiveValidation
+    @context.on "change:#{@context.validateProperty}", (e) =>
+      @context.validatorGroup.softValidate(e.data.value)
+
+    # When a view "bump" occurs, also perform a validation.
+    @on 'bump:maxlength', (e) =>
+      @context.validatorGroup.softValidate(e.data)


fix for close button

fix for close button

--- app/controllers/SernacTranscriptionController.coffee
+++ app/controllers/SernacTranscriptionController.coffee
@@ -1,5 +1,7 @@
 Spine   = require('spine')
 Subject = require('models/Subject')
+Archive = require('models/Archive')
+
 EOL     = require('models/EOL')

 class SernacTranscriptionController extends Spine.Controller
@@ -8,6 +10,8 @@

   startWorkflow:(subject)=>
     @currentSubject= subject
+
+    archive = Archive.find(@currentSubject.archive_id)
     @render()
     @delay =>

@@ -25,6 +29,8 @@
           model: transcriberModel
         })

+        $(".btn.close").attr("href", "#/archives/#{archive.slug()}")
+
         callback = ->
           transcriber.spinner.hide()
           transcriber.startTranscribing()


Server: remove daisy and dash

Server: remove daisy and dash

--- servers/lib/server/handlers/jointeam.coffee
+++ servers/lib/server/handlers/jointeam.coffee
@@ -1,7 +1,7 @@
 Bongo                                   = require 'bongo'
 koding                                  = require './../bongo'
 { uniq }                                = require 'underscore'
-{ dash, daisy }                         = Bongo
+async                                   = require 'async'

 {
   getClientId
@@ -44,7 +44,7 @@

   queue = [

-    ->
+    (next) ->
       koding.fetchClient clientId, context, (client_) ->
         client = client_

@@ -54,9 +54,9 @@
           return res.status(500).send client.message

         client.clientIP = (clientIPAddress.split ',')[0]
-        queue.next()
+        next()

-    ->
+    (next) ->
       # checking if user exists by trying to login the user
       JUser.login client.sessionToken, body, (err, result) ->
         errorMessage          = err?.message
@@ -69,9 +69,9 @@
         # setting alreadyMember to true if error is not unknownUsernameError
         # ignoring other errors here since our only concern is checking if user exists
         alreadyMember = errorMessage isnt unknownUsernameError
-        queue.next()
+        next()

-    ->
+    (next) ->
       # generating callback function to be used in both login and convert
       joinTeamKallback = generateJoinTeamKallback res, body

@@ -81,7 +81,7 @@

   ]

-  daisy queue
+  async.series queue

 generateJoinTeamKallback = (res, body) ->


提供新的事件

提供新的事件

--- src/widget/edit/multi-select.coffee
+++ src/widget/edit/multi-select.coffee
@@ -3,7 +3,8 @@
 	@tagName: "c-multiSelect"
 	@events:
 		renderItem: null
-
+		beforeRemoveItem: null
+		removeItem: null
 	_setValueContent: ()->
 		value = @_value
 		values = []
@@ -43,7 +44,9 @@
 				if val == data then continue;
 				newValues.push(val)
 			newValue = newValues.join(",")
-			@_set("value", newValue)
+			if @fire("beforeRemoveItem", @, {item: data, oldValue: value, value: newValue}) isnt false
+				@_set("value", newValue)
+			@fire("removeItem", @, {item: data, oldValue: value, value: newValue})

 	_selectData: (item) ->
 		@_inputEdited = false


fixed a resource's URL which broke https on static profile pages

--- workers/social/lib/social/models/account/render-homepage.coffee
+++ workers/social/lib/social/models/account/render-homepage.coffee
@@ -94,7 +94,7 @@

-
+

                     <button type="button" class="kdbutton kdwhitebtn profilefollowbtn w-loader" id="kd-274"><canvas width="18" height="18"></canvas><canvas style="display: none;" width="18" height="18"></canvas>

@@ -285,4 +285,4 @@
           #{firstName} #{lastName} has not written any Blog Posts yet. Click 'Activitiy' on top to see #{firstName}'s posts on Koding.

-  """+  """


Fix security issue with JPrivateMessage.create

--- workers/social/lib/social/models/messages/privatemessage.coffee
+++ workers/social/lib/social/models/messages/privatemessage.coffee
@@ -1,5 +1,7 @@
-JPost = require './post'
-JComment = require './comment'
+
+JPost       = require './post'
+JComment    = require './comment'
+KodingError = require '../../error'

 module.exports = class JPrivateMessage extends JPost

@@ -60,8 +62,14 @@

     secure (client, data, callback)->
       {delegate} = client.connection
+
+      JAccount = require '../account'
+
+      unless delegate instanceof JAccount
+        callback new KodingError 'Access denied.'
+        return no
+
       {to, subject, body} = data
-      JAccount = require '../account'
       if 'string' is typeof to
         to = to.replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').split(' ') # accept virtaully any non-wordchar delimiters for now.
       JAccount.all 'profile.nickname': $in: to, (err, recipients)->


add leftLabelHTML

add leftLabelHTML

--- snippets/core.BridleNSIS.cson
+++ snippets/core.BridleNSIS.cson
@@ -1,516 +1,673 @@
 '.source.nsis.bridle':
   'Abort()':
-    'prefix': 'Abort()'
-    'body': 'Abort("${1:user_message}")$2'
+    body: 'Abort("${1:user_message}")$2'
+    leftLabelHTML: "K"
+    prefix: 'Abort()'
   'BringToFront()':
-    'prefix': 'BringToFront()'
-    'body': 'BringToFront()$1'
+    body: 'BringToFront()$1'
+    leftLabelHTML: "K"
+    prefix: 'BringToFront()'
   'Call()':
-    'prefix': 'Call()'
-    'body': 'Call("${1:name}")$2'
+    body: 'Call("${1:name}")$2'
+    leftLabelHTML: "K"
+    prefix: 'Call()'
   'CallInstDLL()':
-    'prefix': 'CallInstDLL()'
-    'body': 'CallInstDLL("${1:dll_file}", "${2:function_name}"'
+    body: 'CallInstDLL("${1:dll_file}", "${2:function_name}"'
+    leftLabelHTML: "K"
+    prefix: 'CallInstDLL()'
   'ClearErrors()':
-    'prefix': 'ClearErrors()'
-    'body': 'ClearErrors()$1'
+    body: 'ClearErrors()$1'
+    leftLabelHTML: "K"
+    prefix: 'ClearErrors()'
   'Copy()':
-    'prefix': 'Copy()'
-    'body': 'Copy("${1:file}", "${2:target}")$3'
+    body: 'Copy("${1:file}", "${2:target}")$3'
+    leftLabelHTML: "K"
+    prefix: 'Copy()'
   'CreateDirectory()':
-    'prefix': 'CreateDirectory()'
-    'body': 'CreateDirectory("${1:path_to_create}")$3'
+    body: 'CreateDirectory("${1:path_to_create}")$3'
+    leftLabelHTML: "K"
+    prefix: 'CreateDirectory()'
   'CreateFont()':
-    'prefix': 'CreateFont()'
-    'body': 'CreateFont("${1:face_name}",${2: "${3:height}"${4:, "${5:weight}"${6:, ${7:/ITALIC}${8:, ${9:/UNDERLINE}${10:, ${11:/STRIKE}}}}}})$12'
+    body: 'CreateFont("${1:face_name}",${2: "${3:height}"${4:, "${5:weight}"${6:, ${7:/ITALIC}${8:, ${9:/UNDERLINE}${10:, ${11:/STRIKE}}}}}})$12'
+    leftLabelHTML: "K"
+    prefix: 'CreateFont()'
   'CreateShortCut()':
-    'prefix': 'CreateShortCut()'
-    'body': 'CreateShortCut("${1:link}.lnk", "${2:target_file}"${3:, "${4:parameters}"${5:, "${6:icon_file}"${7:, ${8:icon_index_number}${9:, ${10:start_options}${11:, ${12:keyboard_shortcut}${13:, "${14:description}"}}}}}}})$15'
+    body: 'CreateShortCut("${1:link}.lnk", "${2:target_file}"${3:, "${4:parameters}"${5:, "${6:icon_file}"${7:, ${8:icon_index_number}${9:, ${10:start_options}${11:, ${12:keyboard_shortcut}${13:, "${14:description}"}}}}}}})$15'
+    leftLabelHTML: "K"
+    prefix: 'CreateShortCut()'
   'Delete()':
-    'prefix': 'Delete()'
-    'body': 'Delete("${1:source}"${2:, "${3:rebootok}"})$4'
+    body: 'Delete("${1:source}"${2:, "${3:rebootok}"})$4'
+    leftLabelHTML: "K"
+    prefix: 'Delete()'
   'DeleteINISec()':
-    'prefix': 'DeleteINISec()'
-    'body': 'DeleteINISec("${1:ini_filename}", "${2:section_name}")$4'
+    body: 'DeleteINISec("${1:ini_filename}", "${2:section_name}")$4'
+    leftLabelHTML: "K"
+    prefix: 'DeleteINISec()'
   'DeleteINIStr()':
-    'prefix': 'DeleteINIStr()'
-    'body': 'DeleteINIStr("${1:ini_filename}", "${2:section_name}", "${3:str_name}")$4'
+    body: 'DeleteINIStr("${1:ini_filename}", "${2:section_name}", "${3:str_name}")$4'
+    leftLabelHTML: "K"
+    prefix: 'DeleteINIStr()'
   'DeleteRegKey()':
-    'prefix': 'DeleteRegKey()'
-    'body': 'DeleteRegKey("${1:rootkey}", "${2:subkey}"}${3:, "${4:options}"})$5'
+    body: 'DeleteRegKey("${1:rootkey}", "${2:subkey}"}${3:, "${4:options}"})$5'
+    leftLabelHTML: "K"
+    prefix: 'DeleteRegKey()'
   'DeleteRegValue()':
-    'prefix': 'DeleteRegValue()'
-    'body': 'DeleteRegValue("${1:root_key}", "${2:subkey}", "${3:key_name}")$4'
+    body: 'DeleteRegValue("${1:root_key}", "${2:subkey}", "${3:key_name}")$4'
+    leftLabelHTML: "K"
+    prefix: 'DeleteRegValue()'
   'DetailPrint()':
-    'prefix': 'DetailPrint()'
-    'body': 'DetailPrint("${1:user_message}")$2'
-  'Do Until..Loop':
-    'prefix': 'Do Until..Loop'
-    'body': """
+    body: 'DetailPrint("${1:user_message}")$2'
+    leftLabelHTML: "K"
+    prefix: 'DetailPrint()'
+  'Do Until…Loop':
+    body: """
       Do Until ${1:condition}
       \t${4:# your code here}
       Loop$5
     """
-  'Do While..Loop':
-    'prefix': 'Do While..Loop'
-    'body': """
+    leftLabelHTML: "LogicLib.nsh"
+    prefix: 'Do Until…Loop'
+  'Do While…Loop':
+    body: """
       Do While ${1:condition}
       \t${4:# your code here}
       Loop$5
     """
+    leftLabelHTML: "LogicLib.nsh"
+    prefix: 'Do While…Loop'
   'EnableWindow()':
-    'prefix': 'EnableWindow()'
-    'body': 'EnableWindow(${1:hwnd}, ${2:value})$3'
+    body: 'EnableWindow(${1:hwnd}, ${2:value})$3'
+    leftLabelHTML: "K"
+    prefix: 'EnableWindow()'
   'EnumRegKey()':
-    'prefix': 'EnumRegKey()'
-    'body': 'EnumRegKey("${1:root_key}", "${2:subkey}", "${3:index}")$4'
+    body: 'EnumRegKey("${1:root_key}", "${2:subkey}", "${3:index}")$4'
+    leftLabelHTML: "K"
+    prefix: 'EnumRegKey()'
   'EnumRegValue()':
-    'prefix': 'EnumRegValue()'
-    'body': 'EnumRegValue("${1:root_key}", "${2:subkey}", "${3:index}")$4'
+    body: 'EnumRegValue("${1:root_key}", "${2:subkey}", "${3:index}")$4'
+    leftLabelHTML: "K"
+    prefix: 'EnumRegValue()'
   'Exch()':
-    'prefix': 'Exch()'
-    'body': 'Exch(${1:user_var})$2'
+    body: 'Exch(${1:user_var})$2'
+    leftLabelHTML: "K"
+    prefix: 'Exch()'
   'Exec()':
-    'prefix': 'Exec()'
-    'body': 'Exec("${1:command}")$2'
+    body: 'Exec("${1:command}")$2'
+    leftLabelHTML: "K"
+    prefix: 'Exec()'
   'ExecShell()':
-    'prefix': 'ExecShell()'
-    'body': 'ExecShell("${1:action}", "${2:command}"${3:, ${4:parameter}})$5'
+    body: 'ExecShell("${1:action}", "${2:command}"${3:, ${4:parameter}})$5'
+    leftLabelHTML: "K"
+    prefix: 'ExecShell()'
   'ExecWait()':
-    'prefix': 'ExecWait()'
-    'body': 'ExecWait("${1:command}")$2'
+    body: 'ExecWait("${1:command}")$2'
+    leftLabelHTML: "K"
+    prefix: 'ExecWait()'
   'ExpandEnvStrings()':
-    'prefix': 'ExpandEnvStrings()'
-    'body': 'ExpandEnvStrings("${1:string}")$2'
+    body: 'ExpandEnvStrings("${1:string}")$2'
+    leftLabelHTML: "K"
+    prefix: 'ExpandEnvStrings()'
   'File()':
-    'prefix': 'File()'
-    'body': 'File("${1:file}"${2:, "${3:options}"${4:, "${5:outpath}"}})$6'
+    body: 'File("${1:file}"${2:, "${3:options}"${4:, "${5:outpath}"}})$6'
+    leftLabelHTML: "K"
+    prefix: 'File()'
   'FileClose()':
-    'prefix': 'FileClose()'
-    'body': 'FileClose(${1:handle})$2'
+    body: 'FileClose(${1:handle})$2'
+    leftLabelHTML: "K"
+    prefix: 'FileClose()'
   'FileCopy()':
-    'prefix': 'FileCopy()'
-    'body': 'FileCopy("${1:file}", "${2:target}")$3'
+    body: 'FileCopy("${1:file}", "${2:target}")$3'
+    leftLabelHTML: "K"
+    prefix: 'FileCopy()'
   'FileDelete()':
-    'prefix': 'FileDelete()'
-    'body': 'FileDelete("${1:source}"${2:, "${3:rebootok}"})$4'
+    body: 'FileDelete("${1:source}"${2:, "${3:rebootok}"})$4'
+    leftLabelHTML: "K"
+    prefix: 'FileDelete()'
   'FileOpen()':
-    'prefix': 'FileOpen()'
-    'body': 'FileOpen("${1:filename}", ${2:open_mode})$3'
+    body: 'FileOpen("${1:filename}", ${2:open_mode})$3'
+    leftLabelHTML: "K"
+    prefix: 'FileOpen()'
   'FileRead()':
-    'prefix': 'FileRead()'
-    'body': 'FileRead(${1:handle}, ${2:maxlen})$3'
+    body: 'FileRead(${1:handle}, ${2:maxlen})$3'
+    leftLabelHTML: "K"
+    prefix: 'FileRead()'
   'FileReadByte()':
-    'prefix': 'FileReadByte()'
-    'body': 'FileReadByte(${1:handle}, ${2:maxlen})$3'
+    body: 'FileReadByte(${1:handle}, ${2:maxlen})$3'
+    leftLabelHTML: "K"
+    prefix: 'FileReadByte()'
   'FileReadUTF16LE()':
-    'prefix': 'FileReadUTF16LE()'
-    'body': 'FileReadUTF16LE(${1:handle}, ${2:maxlen})$3'
+    body: 'FileReadUTF16LE(${1:handle}, ${2:maxlen})$3'
+    leftLabelHTML: "K"
+    prefix: 'FileReadUTF16LE()'
   'FileReadWord()':
-    'prefix': 'FileReadWord()'
-    'body': 'FileReadWord(${1:handle}, ${2:maxlen})$3'
+    body: 'FileReadWord(${1:handle}, ${2:maxlen})$3'
+    leftLabelHTML: "K"
+    prefix: 'FileReadWord()'
   'FileRename()':
-    'prefix': 'FileRename()'
-    'body': 'FileRename("${1:source}", "${2:target}"${3:, "${4:options}"}})$5'
+    body: 'FileRename("${1:source}", "${2:target}"${3:, "${4:options}"}})$5'
+    leftLabelHTML: "K"
+    prefix: 'FileRename()'
   'FileSeek()':
-    'prefix': 'FileSeek()'
-    'body': 'FileSeek(${1:handle}, ${2:offset}${3:, ${4:mode}${5:, ${6:user_var}}})$7'
+    body: 'FileSeek(${1:handle}, ${2:offset}${3:, ${4:mode}${5:, ${6:user_var}}})$7'
+    leftLabelHTML: "K"
+    prefix: 'FileSeek()'
   'FileWrite()':
-    'prefix': 'FileWrite()'
-    'body': 'FileWrite(${1:handle})$2'
+    body: 'FileWrite(${1:handle})$2'
+    leftLabelHTML: "K"
+    prefix: 'FileWrite()'
   'FileWriteByte()':
-    'prefix': 'FileWriteByte()'
-    'body': 'FileWriteByte(${1:handle}, "${2:string}")$3'
+    body: 'FileWriteByte(${1:handle}, "${2:string}")$3'
+    leftLabelHTML: "K"
+    prefix: 'FileWriteByte()'
   'FileWriteUTF16LE()':
-    'prefix': 'FileWriteUTF16LE()'
-    'body': 'FileWriteUTF16LE(${1:handle}, "${2:string}")$3'
+    body: 'FileWriteUTF16LE(${1:handle}, "${2:string}")$3'
+    leftLabelHTML: "K"
+    prefix: 'FileWriteUTF16LE()'
   'FileWriteWord()':
-    'prefix': 'FileWriteWord()'
-    'body': 'FileWriteWord(${1:handle}, "${2:string}")$3'
+    body: 'FileWriteWord(${1:handle}, "${2:string}")$3'
+    leftLabelHTML: "K"
+    prefix: 'FileWriteWord()'
   'FindClose()':
-    'prefix': 'FindClose()'
-    'body': 'FindClose(${1:handle})$2'
+    body: 'FindClose(${1:handle})$2'
+    leftLabelHTML: "K"
+    prefix: 'FindClose()'
   'FindNext()':
-    'prefix': 'FindNext()'
-    'body': 'FindNext(${1:handle})$2'
+    body: 'FindNext(${1:handle})$2'
+    leftLabelHTML: "K"
+    prefix: 'FindNext()'
   'FindWindow()':
-    'prefix': 'FindWindow()'
-    'body': 'FindWindow("${1:windowclass}", "${2:windowtitle}"${3:, ${4:windowparent}${5:, ${6:childafter}}})$7'
+    body: 'FindWindow("${1:windowclass}", "${2:windowtitle}"${3:, ${4:windowparent}${5:, ${6:childafter}}})$7'
+    leftLabelHTML: "K"
+    prefix: 'FindWindow()'
   'FlushINI()':
-    'prefix': 'FlushINI()'
-    'body': 'FlushINI("${1:ini_filename}")$2'
+    body: 'FlushINI("${1:ini_filename}")$2'
+    leftLabelHTML: "K"
+    prefix: 'FlushINI()'
   'Function()':
-    'prefix': 'Function()'
-    'body': """
+    body: """
       Function ${1:function_name}(${2:parameters})
       \t${5:# your code here}
       FunctionEnd$6
     """
+    leftLabelHTML: "K"
+    prefix: 'Function()'
   'GetCurInstType()':
-    'prefix': 'GetCurInstType()'
-    'body': 'GetCurInstType()$1'
+    body: 'GetCurInstType()$1'
+    leftLabelHTML: "K"
+    prefix: 'GetCurInstType()'
   'GetCurrentAddress()':
-    'prefix': 'GetCurrentAddress()'
-    'body': 'GetCurrentAddress()$1'
+    body: 'GetCurrentAddress()$1'
+    leftLabelHTML: "K"
+    prefix: 'GetCurrentAddress()'
   'GetDlgItem()':
-    'prefix': 'GetDlgItem()'
-    'body': 'GetDlgItem(${1:dialog}, ${2:item_id})$3'
+    body: 'GetDlgItem(${1:dialog}, ${2:item_id})$3'
+    leftLabelHTML: "K"
+    prefix: 'GetDlgItem()'
   'GetErrorLevel()':
-    'prefix': 'GetErrorLevel()'
-    'body': 'GetErrorLevel()$1'
+    body: 'GetErrorLevel()$1'
+    leftLabelHTML: "K"
+    prefix: 'GetErrorLevel()'
   'GetFullPathName()':
-    'prefix': 'GetFullPathName()'
-    'body': 'GetFullPathName("${1:path}"${2:, "${3:options}"})$4'
+    body: 'GetFullPathName("${1:path}"${2:, "${3:options}"})$4'
+    leftLabelHTML: "K"
+    prefix: 'GetFullPathName()'
   'GetFunctionAddress()':
-    'prefix': 'GetFunctionAddress()'
-    'body': 'GetCurrentAddress("${1:function_name}")$2'
+    body: 'GetCurrentAddress("${1:function_name}")$2'
+    leftLabelHTML: "K"
+    prefix: 'GetFunctionAddress()'
   'GetInstDirError()':
-    'prefix': 'GetInstDirError()'
-    'body': 'GetInstDirError()$1'
+    body: 'GetInstDirError()$1'
+    leftLabelHTML: "K"
+    prefix: 'GetInstDirError()'
   'GetLabelAddress()':
-    'prefix': 'GetLabelAddress()'
-    'body': 'GetLabelAddress("${1:label}")$2'
+    body: 'GetLabelAddress("${1:label}")$2'
+    leftLabelHTML: "K"
+    prefix: 'GetLabelAddress()'
   'GetTempFileName()':
-    'prefix': 'GetTempFileName()'
-    'body': 'GetTempFileName(${1:\$${2:base_dir}})$3'
+    body: 'GetTempFileName(${1:\$${2:base_dir}})$3'
+    leftLabelHTML: "K"
+    prefix: 'GetTempFileName()'
   'Goto()':
-    'prefix': 'Goto()'
-    'body': 'Goto(${1:name})$2'
+    body: 'Goto(${1:name})$2'
+    leftLabelHTML: "K"
+    prefix: 'Goto()'
   'HideWindow()':
-    'prefix': 'HideWindow()'
-    'body': 'HideWindow()$1'
+    body: 'HideWindow()$1'
+    leftLabelHTML: "K"
+    prefix: 'HideWindow()'
   'If Not':
-    'prefix': 'If Not'
-    'body': """
+    body: """
       If Not ${1:condition}
       \t${4:# your code here}
       EndIf$5
     """
-  'If Not..Else':
-    'prefix': 'If Not..Else'
-    'body': """
+    leftLabelHTML: "LogicLib.nsh"
+    prefix: 'If Not'
+  'If Not…Else':
+    body: """
       If Not ${1:condition}
       \t${4:# your code here}
       Else
       \t${5:# your code here}
       EndIf$6
     """
-  'If Not..ElseIf':
-    'prefix': 'If Not..ElseIf'
-    'body': """
+    leftLabelHTML: "LogicLib.nsh"
+    prefix: 'If Not…Else'
+  'If Not…ElseIf':
+    body: """
       If Not ${1:condition}
       \t${4:# your code here}
       ElseIf
       \t${5:# your code here}
       EndIf$6
     """
+    leftLabelHTML: "LogicLib.nsh"
+    prefix: 'If Not…ElseIf'
   'If':
-    'prefix': 'If'
-    'body': """
+    body: """
       If ${1:condition}
       \t${4:# your code here}
       EndIf$5
     """
-  'If..Else':
-    'prefix': 'If..Else'
-    'body': """
+    leftLabelHTML: "LogicLib.nsh"
+    prefix: 'If'
+  'If…Else':
+    body: """
       If ${1:condition}
       \t${4:# your code here}
       Else
       \t${5:# your code here}
       EndIf$6
     """
-  'If..ElseIf':
-    'prefix': 'If..ElseIf'
-    'body': """
+    leftLabelHTML: "LogicLib.nsh"
+    prefix: 'If…Else'
+  'If…ElseIf':
+    body: """
       If ${1:condition}
       \t${4:# your code here}
       ElseIf
       \t${5:# your code here}
       EndIf$6
     """
+    leftLabelHTML: "LogicLib.nsh"
+    prefix: 'If…ElseIf'
   'InitPluginsDir()':
-    'prefix': 'InitPluginsDir()'
-    'body': 'InitPluginsDir()$1'
+    body: 'InitPluginsDir()$1'
+    leftLabelHTML: "K"
+    prefix: 'InitPluginsDir()'
   'InstTypeGetText()':
-    'prefix': 'InstTypeGetText()'
-    'body': 'InstTypeGetText(${1:inst_type_idx})$2'
+    body: 'InstTypeGetText(${1:inst_type_idx})$2'
+    leftLabelHTML: "K"
+    prefix: 'InstTypeGetText()'
   'InstTypeSetText()':
-    'prefix': 'InstTypeSetText()'
-    'body': 'InstTypeSetText(${1:inst_type_idx}, "${2:text}")$3'
+    body: 'InstTypeSetText(${1:inst_type_idx}, "${2:text}")$3'
+    leftLabelHTML: "K"
+    prefix: 'InstTypeSetText()'
   'IntCmp()':
-    'prefix': 'IntCmp()'
-    'body': 'IntCmp(${1:val1}, ${2:val2}, ${3:jump_if_equal}${4:, ${5:jump_if_val1_less}${6:, ${7:jump_if_val1_more}}})$8'
+    body: 'IntCmp(${1:val1}, ${2:val2}, ${3:jump_if_equal}${4:, ${5:jump_if_val1_less}${6:, ${7:jump_if_val1_more}}})$8'
+    leftLabelHTML: "K"
+    prefix: 'IntCmp()'
   'IntFmt()':
-    'prefix': 'IntFmt()'
-    'body': 'IntFmt(${1:format}, ${2:numberstring})$3'
+    body: 'IntFmt(${1:format}, ${2:numberstring})$3'
+    leftLabelHTML: "K"
+    prefix: 'IntFmt()'
   'IntOp()':
-    'prefix': 'IntOp()'
-    'body': 'IntOp(${1:value1}, ${2:op}, ${3:value2})$4'
+    body: 'IntOp(${1:value1}, ${2:op}, ${3:value2})$4'
+    leftLabelHTML: "K"
+    prefix: 'IntOp()'
   'LangString()':
-    'prefix': 'LangString()'
-    'body': 'LangString(${1:name}, \$\{LANG_${2:language_id}\}, "${3:string}")$4'
+    body: 'LangString(${1:name}, \$\{LANG_${2:language_id}\}, "${3:string}")$4'
+    leftLabelHTML: "K"
+    prefix: 'LangString()'
   'LicenseLangString()':
-    'prefix': 'LicenseLangString()'
-    'body': 'LicenseLangString(${1:name}, \$\{LANG_${2:language_id}\}, "${3:license_path}")$4'
+    body: 'LicenseLangString(${1:name}, \$\{LANG_${2:language_id}\}, "${3:license_path}")$4'
+    leftLabelHTML: "K"
+    prefix: 'LicenseLangString()'
   'LoadLanguageFile()':
-    'prefix': 'LoadLanguageFile()'
-    'body': 'LoadLanguageFile("${1:language_file_nlf}")$2'
+    body: 'LoadLanguageFile("${1:language_file_nlf}")$2'
+    leftLabelHTML: "K"
+    prefix: 'LoadLanguageFile()'
   'LockWindow()':
-    'prefix': 'LockWindow()'
-    'body': 'LockWindow(${1:value})$2'
+    body: 'LockWindow(${1:value})$2'
+    leftLabelHTML: "K"
+    prefix: 'LockWindow()'
   'LockWindow(off)':
-    'prefix': 'LockWindow(off)'
-    'body': 'LockWindow(off)$1'
+    body: 'LockWindow(off)$1'
+    leftLabelHTML: "K"
+    prefix: 'LockWindow(off)'
   'LockWindow(on)':
-    'prefix': 'LockWindow(on)'
-    'body': 'LockWindow(on)$1'
+    body: 'LockWindow(on)$1'
+    leftLabelHTML: "K"
+    prefix: 'LockWindow(on)'
   'LogSet()':
-    'prefix': 'LogSet()'
-    'body': 'LogSet(${1:value})$2'
+    body: 'LogSet(${1:value})$2'
+    leftLabelHTML: "K"
+    prefix: 'LogSet()'
   'LogSet(off)':
-    'prefix': 'LogSet(off)'
-    'body': 'LogSet(off)$1'
+    body: 'LogSet(off)$1'
+    leftLabelHTML: "K"
+    prefix: 'LogSet(off)'
   'LogSet(on)':
-    'prefix': 'LogSet(on)'
-    'body': 'LogSet(on)$1'
+    body: 'LogSet(on)$1'
+    leftLabelHTML: "K"
+    prefix: 'LogSet(on)'
   'LogText()':
-    'prefix': 'LogText()'
-    'body': 'LogText("${1:text}")$2'
+    body: 'LogText("${1:text}")$2'
+    leftLabelHTML: "K"
+    prefix: 'LogText()'
   'MsgBox()':
-    'prefix': 'MsgBox()'
-    'body': 'MsgBox("${1:buttons}", "${2:message}"${3:, ${4:options}${5:, ${6:sd}}})$7'
+    body: 'MsgBox("${1:buttons}", "${2:message}"${3:, ${4:options}${5:, ${6:sd}}})$7'
+    leftLabelHTML: "K"
+    prefix: 'MsgBox()'
   'MsgBox(ABORTRETRYIGNORE)':
-    'prefix': 'MsgBox(ABORTRETRYIGNORE)'
-    'body': 'MsgBox("ABORTRETRYIGNORE", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    body: 'MsgBox("ABORTRETRYIGNORE", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    leftLabelHTML: "K"
+    prefix: 'MsgBox(ABORTRETRYIGNORE)'
   'MsgBox(OK)':
-    'prefix': 'MsgBox(OK)'
-    'body': 'MsgBox("OK", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    body: 'MsgBox("OK", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    leftLabelHTML: "K"
+    prefix: 'MsgBox(OK)'
   'MsgBox(OKCANCEL)':
-    'prefix': 'MsgBox(OKCANCEL)'
-    'body': 'MsgBox("OKCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    body: 'MsgBox("OKCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    leftLabelHTML: "K"
+    prefix: 'MsgBox(OKCANCEL)'
   'MsgBox(RETRYCANCEL)':
-    'prefix': 'MsgBox(RETRYCANCEL)'
-    'body': 'MsgBox("RETRYCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    body: 'MsgBox("RETRYCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    leftLabelHTML: "K"
+    prefix: 'MsgBox(RETRYCANCEL)'
   'MsgBox(YESNO)':
-    'prefix': 'MsgBox(YESNO)'
-    'body': 'MsgBox("YESNO", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    body: 'MsgBox("YESNO", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    leftLabelHTML: "K"
+    prefix: 'MsgBox(YESNO)'
   'MsgBox(YESNOCANCEL)':
-    'prefix': 'MsgBox(YESNOCANCEL)'
-    'body': 'MsgBox("YESNOCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    body: 'MsgBox("YESNOCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
+    leftLabelHTML: "K"
+    prefix: 'MsgBox(YESNOCANCEL)'
   'Pop()':
-    'prefix': 'Pop()'
-    'body': '${1:user_var} = Pop()$2'
+    body: '${1:user_var} = Pop()$2'
+    leftLabelHTML: "K"
+    prefix: 'Pop()'
   'Push()':
-    'prefix': 'Push()'
-    'body': 'Push(${1:string})$2'
+    body: 'Push(${1:string})$2'
+    leftLabelHTML: "K"
+    prefix: 'Push()'
   'Quit()':
-    'prefix': 'Quit()'
-    'body': 'Quit()$1'
+    body: 'Quit()$1'
+    leftLabelHTML: "K"
+    prefix: 'Quit()'
   'ReadEnvStr()':
-    'prefix': 'ReadEnvStr()'
-    'body': 'ReadEnvStr(${1:name})$2'
+    body: 'ReadEnvStr(${1:name})$2'
+    leftLabelHTML: "K"
+    prefix: 'ReadEnvStr()'
   'ReadINIStr()':
-    'prefix': 'ReadINIStr()'
-    'body': 'ReadINIStr("${1:ini_filename}", "${2:section_name}", "${3:entry_name}")$4'
+    body: 'ReadINIStr("${1:ini_filename}", "${2:section_name}", "${3:entry_name}")$4'
+    leftLabelHTML: "K"
+    prefix: 'ReadINIStr()'
   'ReadRegDWORD()':
-    'prefix': 'ReadRegDWORD()'
-    'body': 'ReadRegDWORD("${1:root_key}", "${2:sub_key}", "${3:name}")$4'
+    body: 'ReadRegDWORD("${1:root_key}", "${2:sub_key}", "${3:name}")$4'
+    leftLabelHTML: "K"
+    prefix: 'ReadRegDWORD()'
   'ReadRegStr()':
-    'prefix': 'ReadRegStr()'
-    'body': 'ReadRegStr("${1:root_key}", "${2:sub_key}", "${3:name}")$4'
+    body: 'ReadRegStr("${1:root_key}", "${2:sub_key}", "${3:name}")$4'
+    leftLabelHTML: "K"
+    prefix: 'ReadRegStr()'
   'Reboot()':
-    'prefix': 'Reboot()'
-    'body': 'Reboot()$1'
+    body: 'Reboot()$1'
+    leftLabelHTML: "K"
+    prefix: 'Reboot()'
   'RegDLL()':
-    'prefix': 'RegDLL()'
-    'body': 'RegDLL("${1:file}"${2:, ${3:options}})$4'
+    body: 'RegDLL("${1:file}"${2:, ${3:options}})$4'
+    leftLabelHTML: "K"
+    prefix: 'RegDLL()'
   'Rename()':
-    'prefix': 'Rename()'
-    'body': 'Rename("${1:source}", "${2:target}"${3:, "${4:options}"})$5'
+    body: 'Rename("${1:source}", "${2:target}"${3:, "${4:options}"})$5'
+    leftLabelHTML: "K"
+    prefix: 'Rename()'
   'ReserveFile()':
-    'prefix': 'ReserveFile()'
-    'body': 'ReserveFile("${1:file}"${2:, "${3:options}"})$4'
+    body: 'ReserveFile("${1:file}"${2:, "${3:options}"})$4'
+    leftLabelHTML: "K"
+    prefix: 'ReserveFile()'
   'RMDir()':
-    'prefix': 'RMDir()'
-    'body': 'RMDir("${1:directory}"${2:, "${3:options}"})$4'
+    body: 'RMDir("${1:directory}"${2:, "${3:options}"})$4'
+    leftLabelHTML: "K"
+    prefix: 'RMDir()'
   'SearchPath()':
-    'prefix': 'SearchPath()'
-    'body': 'SearchPath("${1:filename}")$2'
+    body: 'SearchPath("${1:filename}")$2'
+    leftLabelHTML: "K"
+    prefix: 'SearchPath()'
   'SectionGetFlags()':
-    'prefix': 'SectionGetFlags()'
-    'body': 'SectionGetFlags(${1:section_index})$2'
+    body: 'SectionGetFlags(${1:section_index})$2'
+    leftLabelHTML: "K"
+    prefix: 'SectionGetFlags()'
   'SectionGetInstTypes()':
-    'prefix': 'SectionGetInstTypes()'
-    'body': 'SectionGetInstTypes(${1:section_index})$2'
+    body: 'SectionGetInstTypes(${1:section_index})$2'
+    leftLabelHTML: "K"
+    prefix: 'SectionGetInstTypes()'
   'SectionGetSize()':
-    'prefix': 'SectionGetSize()'
-    'body': 'SectionGetSize(${1:section_index})$2'
+    body: 'SectionGetSize(${1:section_index})$2'
+    leftLabelHTML: "K"
+    prefix: 'SectionGetSize()'
   'SectionSetFlags()':
-    'prefix': 'SectionSetFlags()'
-    'body': 'SectionSetFlags(${1:section_index}, ${2:inst_types})$3'
+    body: 'SectionSetFlags(${1:section_index}, ${2:inst_types})$3'
+    leftLabelHTML: "K"
+    prefix: 'SectionSetFlags()'
   'SectionSetInstTypes()':
-    'prefix': 'SectionSetInstTypes()'
-    'body': 'SectionSetInstTypes(${1:section_index}, ${2:section_flags})$3'
+    body: 'SectionSetInstTypes(${1:section_index}, ${2:section_flags})$3'
+    leftLabelHTML: "K"
+    prefix: 'SectionSetInstTypes()'
   'SectionSetSize()':
-    'prefix': 'SectionSetSize()'
-    'body': 'SectionSetSize(${1:handle}, ${2:maxlen})$3'
+    body: 'SectionSetSize(${1:handle}, ${2:maxlen})$3'
+    leftLabelHTML: "K"
+    prefix: 'SectionSetSize()'
   'SectionSetText()':
-    'prefix': 'SectionSetText()'
-    'body': 'SectionSetText(${1:section_index}, "${2:section_text}")$3'
+    body: 'SectionSetText(${1:section_index}, "${2:section_text}")$3'
+    leftLabelHTML: "K"
+    prefix: 'SectionSetText()'
   'SendMessage()':
-    'prefix': 'SendMessage()'
-    'body': 'SendMessage(${1:hwnd}, ${2:msg}, ${3:wparam}, ${4:lparam}${5:, ${6:timeout_in_ms}})$7'
+    body: 'SendMessage(${1:hwnd}, ${2:msg}, ${3:wparam}, ${4:lparam}${5:, ${6:timeout_in_ms}})$7'
+    leftLabelHTML: "K"
+    prefix: 'SendMessage()'
   'SetAutoClose()':
-    'prefix': 'SetAutoClose()'
-    'body': 'SetAutoClose(${1:value})$2'
+    body: 'SetAutoClose(${1:value})$2'
+    leftLabelHTML: "K"
+    prefix: 'SetAutoClose()'
   'SetAutoClose(false)':
-    'prefix': 'SetAutoClose(false)'
-    'body': 'SetAutoClose(false)$2'
+    body: 'SetAutoClose(false)$2'
+    leftLabelHTML: "K"
+    prefix: 'SetAutoClose(false)'
   'SetAutoClose(true)':
-    'prefix': 'SetAutoClose(true)'
-    'body': 'SetAutoClose(true)$2'
+    body: 'SetAutoClose(true)$2'
+    leftLabelHTML: "K"
+    prefix: 'SetAutoClose(true)'
   'SetCtlColors()':
-    'prefix': 'SetCtlColors()'
-    'body': 'SetCtlColors(${1:hwnd}${2:, ${3:branding}}, ${4:text_color}, ${5:bg_color})$6'
+    body: 'SetCtlColors(${1:hwnd}${2:, ${3:branding}}, ${4:text_color}, ${5:bg_color})$6'
+    leftLabelHTML: "K"
+    prefix: 'SetCtlColors()'
   'SetCurInstType()':
-    'prefix': 'SetCurInstType()'
-    'body': 'SetCurInstType(${1:inst_type_idx})$2'
+    body: 'SetCurInstType(${1:inst_type_idx})$2'
+    leftLabelHTML: "K"
+    prefix: 'SetCurInstType()'
   'SetDetailsPrint()':
-    'prefix': 'SetDetailsPrint()'
-    'body': 'SetDetailsPrint(${1:value})$2'
+    body: 'SetDetailsPrint(${1:value})$2'
+    leftLabelHTML: "K"
+    prefix: 'SetDetailsPrint()'
   'SetDetailsPrint(both)':
-    'prefix': 'SetDetailsPrint(both)'
-    'body': 'SetDetailsPrint(both)$1'
+    body: 'SetDetailsPrint(both)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetDetailsPrint(both)'
   'SetDetailsPrint(lastused)':
-    'prefix': 'SetDetailsPrint(lastused)'
-    'body': 'SetDetailsPrint(lastused)$1'
+    body: 'SetDetailsPrint(lastused)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetDetailsPrint(lastused)'
   'SetDetailsPrint(listonly)':
-    'prefix': 'SetDetailsPrint(listonly)'
-    'body': 'SetDetailsPrint(listonly)$1'
+    body: 'SetDetailsPrint(listonly)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetDetailsPrint(listonly)'
   'SetDetailsPrint(none)':
-    'prefix': 'SetDetailsPrint(none)'
-    'body': 'SetDetailsPrint(none)$1'
+    body: 'SetDetailsPrint(none)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetDetailsPrint(none)'
   'SetDetailsPrint(textonly)':
-    'prefix': 'SetDetailsPrint(textonly)'
-    'body': 'SetDetailsPrint(textonly)$1'
+    body: 'SetDetailsPrint(textonly)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetDetailsPrint(textonly)'
   'SetDetailsView()':
-    'prefix': 'SetDetailsView()'
-    'body': 'SetDetailsView(${1:value})$2'
+    body: 'SetDetailsView(${1:value})$2'
+    leftLabelHTML: "K"
+    prefix: 'SetDetailsView()'
   'SetDetailsView(hide)':
-    'prefix': 'SetDetailsView(hide)'
-    'body': 'SetDetailsView(hide)$1'
+    body: 'SetDetailsView(hide)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetDetailsView(hide)'
   'SetDetailsView(show)':
-    'prefix': 'SetDetailsView(show)'
-    'body': 'SetDetailsView(show)$1'
+    body: 'SetDetailsView(show)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetDetailsView(show)'
   'SetErrorLevel()':
-    'prefix': 'SetErrorLevel()'
-    'body': 'SetErrorLevel(${1:error_level})$2'
+    body: 'SetErrorLevel(${1:error_level})$2'
+    leftLabelHTML: "K"
+    prefix: 'SetErrorLevel()'
   'SetErrorLevel(0)':
-    'prefix': 'SetErrorLevel(0)'
-    'body': 'SetErrorLevel(0)$1'
+    body: 'SetErrorLevel(0)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetErrorLevel(0)'
   'SetErrorLevel(1)':
-    'prefix': 'SetErrorLevel(1)'
-    'body': 'SetErrorLevel(1)$1'
+    body: 'SetErrorLevel(1)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetErrorLevel(1)'
   'SetErrorLevel(2)':
-    'prefix': 'SetErrorLevel(2)'
-    'body': 'SetErrorLevel(2)$1'
+    body: 'SetErrorLevel(2)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetErrorLevel(2)'
   'SetErrors()':
-    'prefix': 'SetErrors()'
-    'body': 'SetErrors()$1'
+    body: 'SetErrors()$1'
+    leftLabelHTML: "K"
+    prefix: 'SetErrors()'
   'SetFileAttributes()':
-    'prefix': 'SetFileAttributes()'
-    'body': 'SetFileAttributes("${1:filename}", ${2:attributes})$3'
+    body: 'SetFileAttributes("${1:filename}", ${2:attributes})$3'
+    leftLabelHTML: "K"
+    prefix: 'SetFileAttributes()'
   'SetOutPath()':
-    'prefix': 'SetOutPath()'
-    'body': 'SetOutPath("${1:path}")$2'
+    body: 'SetOutPath("${1:path}")$2'
+    leftLabelHTML: "K"
+    prefix: 'SetOutPath()'
   'SetRebootFlag()':
-    'prefix': 'SetRebootFlag()'
-    'body': 'SetRebootFlag(${1:value})$2'
+    body: 'SetRebootFlag(${1:value})$2'
+    leftLabelHTML: "K"
+    prefix: 'SetRebootFlag()'
   'SetRebootFlag(false)':
-    'prefix': 'SetRebootFlag(false)'
-    'body': 'SetRebootFlag(false)$1'
+    body: 'SetRebootFlag(false)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetRebootFlag(false)'
   'SetRebootFlag(true)':
-    'prefix': 'SetRebootFlag(true)'
-    'body': 'SetRebootFlag(true)$1'
+    body: 'SetRebootFlag(true)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetRebootFlag(true)'
   'SetRegView()':
-    'prefix': 'SetRegView()'
-    'body': 'SetRegView(${1:value})$2'
+    body: 'SetRegView(${1:value})$2'
+    leftLabelHTML: "K"
+    prefix: 'SetRegView()'
   'SetRegView(32)':
-    'prefix': 'SetRegView(32)'
-    'body': 'SetRegView(32)$1'
+    body: 'SetRegView(32)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetRegView(32)'
   'SetRegView(64)':
-    'prefix': 'SetRegView(64)'
-    'body': 'SetRegView(64)$1'
+    body: 'SetRegView(64)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetRegView(64)'
   'SetRegView(lastused)':
-    'prefix': 'SetRegView(lastused)'
-    'body': 'SetRegView(lastused)$1'
+    body: 'SetRegView(lastused)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetRegView(lastused)'
   'SetShellVarContext()':
-    'prefix': 'SetShellVarContext()'
-    'body': 'SetShellVarContext(${1:value})$2'
+    body: 'SetShellVarContext(${1:value})$2'
+    leftLabelHTML: "K"
+    prefix: 'SetShellVarContext()'
   'SetShellVarContext(all)':
-    'prefix': 'SetShellVarContext(all)'
-    'body': 'SetShellVarContext(all)$1'
+    body: 'SetShellVarContext(all)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetShellVarContext(all)'
   'SetShellVarContext(current)':
-    'prefix': 'SetShellVarContext(current)'
-    'body': 'SetShellVarContext(current)$1'
+    body: 'SetShellVarContext(current)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetShellVarContext(current)'
   'SetSilent()':
-    'prefix': 'SetSilent()'
-    'body': 'SetSilent(${1:value})$2'
+    body: 'SetSilent(${1:value})$2'
+    leftLabelHTML: "K"
+    prefix: 'SetSilent()'
   'SetSilent(normal)':
-    'prefix': 'SetSilent(normal)'
-    'body': 'SetSilent(normal)$1'
+    body: 'SetSilent(normal)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetSilent(normal)'
   'SetSilent(silent)':
-    'prefix': 'SetSilent(silent)'
-    'body': 'SetSilent(silent)$1'
+    body: 'SetSilent(silent)$1'
+    leftLabelHTML: "K"
+    prefix: 'SetSilent(silent)'
   'ShowWindow()':
-    'prefix': 'ShowWindow()'
-    'body': 'ShowWindow(${1:hwnd}, ${2:value})$3'
+    body: 'ShowWindow(${1:hwnd}, ${2:value})$3'
+    leftLabelHTML: "K"
+    prefix: 'ShowWindow()'
   'Sleep()':
-    'prefix': 'Sleep()'
-    'body': 'Sleep(${1:sleeptime_in_ms})$2'
+    body: 'Sleep(${1:sleeptime_in_ms})$2'
+    leftLabelHTML: "K"
+    prefix: 'Sleep()'
   'StrCmp()':
-    'prefix': 'StrCmp()'
-    'body': 'StrCmp(${1:str1}, ${2:str2}, ${3:jump_if_equal}${4:, ${5:jump_if_val1_less}})$6'
+    body: 'StrCmp(${1:str1}, ${2:str2}, ${3:jump_if_equal}${4:, ${5:jump_if_val1_less}})$6'
+    leftLabelHTML: "K"
+    prefix: 'StrCmp()'
   'StrCpy()':
-    'prefix': 'StrCpy()'
-    'body': 'StrCpy(${1:str}${2:, ${3:maxlen}${4:, ${5:start_offset}}})$6'
+    body: 'StrCpy(${1:str}${2:, ${3:maxlen}${4:, ${5:start_offset}}})$6'
+    leftLabelHTML: "K"
+    prefix: 'StrCpy()'
   'StrLen()':
-    'prefix': 'StrLen()'
-    'body': 'StrLen(${1:str})$2'
+    body: 'StrLen(${1:str})$2'
+    leftLabelHTML: "K"
+    prefix: 'StrLen()'
   'UnRegDLL()':
-    'prefix': 'UnRegDLL()'
-    'body': 'UnRegDLL("${1:dllfile}")$2'
+    body: 'UnRegDLL("${1:dllfile}")$2'
+    leftLabelHTML: "K"
+    prefix: 'UnRegDLL()'
   'Var':
-    'prefix': 'Var'
-    'body': 'Var ${1:user_vars}$2'
+    body: 'Var ${1:user_vars}$2'
+    leftLabelHTML: "K"
+    prefix: 'Var'
   'WordFind()':
-    'prefix': 'WordFind()'
-    'body': 'WordFind("${1:string}", "${2:options}", "${3:delimiter1}"${4:, "${5:delimiter2}"${6:, "${7:center}"}})$8'
+    body: 'WordFind("${1:string}", "${2:options}", "${3:delimiter1}"${4:, "${5:delimiter2}"${6:, "${7:center}"}})$8'
+    leftLabelHTML: "WordFunc.nsh"
+    prefix: 'WordFind()'
   'WordFindS()':
-    'prefix': 'WordFindS()'
-    'body': 'WordFindS("${1:string}", "${2:options}", "${3:delimiter1}"${4:, "${5:delimiter2}"${6:, "${7:center}"}})$8'
+    body: 'WordFindS("${1:string}", "${2:options}", "${3:delimiter1}"${4:, "${5:delimiter2}"${6:, "${7:center}"}})$8'
+    leftLabelHTML: "WordFunc.nsh"
+    prefix: 'WordFindS()'
   'WordReplace()':
-    'prefix': 'WordReplace()'
-    'body': 'WordReplace("${1:string}", "${2:word1}", "${3:word2}", "${4:options}")$5'
+    leftLabelHTML: "WordFunc.nsh"
+    body: 'WordReplace("${1:string}", "${2:word1}", "${3:word2}", "${4:options}")$5'
+    prefix: 'WordReplace()'
   'WordReplaceS()':
-    'prefix': 'WordReplaceS()'
-    'body': 'WordReplaceS("${1:string}", "${2:word1}", "${3:word2}", "${4:options}")$5'
+    body: 'WordReplaceS("${1:string}", "${2:word1}", "${3:word2}", "${4:options}")$5'
+    leftLabelHTML: "WordFunc.nsh"
+    prefix: 'WordReplaceS()'
   'WriteINIStr()':
-    'prefix': 'WriteINIStr()'
-    'body': 'WriteINIStr("${1:ini_filename}", "${2:section_name}", "${3:entry_name}", "${4:value}")$5'
+    body: 'WriteINIStr("${1:ini_filename}", "${2:section_name}", "${3:entry_name}", "${4:value}")$5'
+    leftLabelHTML: "K"
+    prefix: 'WriteINIStr()'
   'WriteRegBin()':
-    'prefix': 'WriteRegBin()'
-    'body': 'WriteRegBin("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value_data}")$5'
+    body: 'WriteRegBin("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value_data}")$5'
+    leftLabelHTML: "K"
+    prefix: 'WriteRegBin()'
   'WriteRegDWORD()':
-    'prefix': 'WriteRegDWORD()'
-    'body': 'WriteRegDWORD("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$5'
+    body: 'WriteRegDWORD("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$5'
+    leftLabelHTML: "K"
+    prefix: 'WriteRegDWORD()'
   'WriteRegExpandStr()':
-    'prefix': 'WriteRegExpandStr()'
-    'body': 'WriteRegExpandStr("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$5'
+    body: 'WriteRegExpandStr("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$5'
+    leftLabelHTML: "K"
+    prefix: 'WriteRegExpandStr()'
   'WriteRegStr()':
-    'prefix': 'WriteRegStr()'
-    'body': 'WriteRegStr("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$5'
+    body: 'WriteRegStr("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$5'
+    leftLabelHTML: "K"
+    prefix: 'WriteRegStr()'
   'WriteUninstaller()':
-    'prefix': 'WriteUninstaller()'
-    'body': 'WriteUninstaller("${1:path}.exe")$2'
+    body: 'WriteUninstaller("${1:path}.exe")$2'
+    leftLabelHTML: "K"
+    prefix: 'WriteUninstaller()'


Update completions on save

Update completions on save

--- static/coffee/flows/services.coffee
+++ static/coffee/flows/services.coffee
@@ -382,7 +382,6 @@
         # only set the versions if we get back json, if we don't have permission we'll get a login page
         if headers('content-type') == 'application/json'
           _this.definition = data
-          $log.debug(data)

 ]

@@ -576,7 +575,7 @@

               # update our auto completion options
               $http.get('/flow/completion/?flow=' + Flow.flowId).success (data) ->
-                $rootScope.completions = data
+                Flow.completions = data

               Versions.updateVersions(Flow.flowId)


make rsync excludes more specific

make rsync excludes more specific

--- app/templates/_Gruntfile.coffee
+++ app/templates/_Gruntfile.coffee
@@ -154,7 +154,8 @@
 					"node_modules",
 					".sass-cache",
 					"*.scss",
-					"style.css",
+					"assets/css/style.css",
+					"assets/js/main.js",
 					"*.js.map",
 					"*.coffee",
 					"config-dev.php",
@@ -225,7 +226,8 @@
 						"config-dev.php",
 						".sass-cache",
 						"*.scss",
-						"style.css",
+						"assets/css/style.css",
+						"assets/js/main.js",
 						"*.js.map",
 						"*.coffee",
 						"assets/cache",
@@ -250,7 +252,8 @@
 						"config-dev.php",
 						".sass-cache",
 						"*.scss",
-						"style.css",
+						"assets/css/style.css",
+						"assets/js/main.js",
 						"*.js.map",
 						"*.coffee",
 						"assets/cache",


Adding some more debug msgs to demystify problems

--- tests/www/tests/utils/hooks.coffee
+++ tests/www/tests/utils/hooks.coffee
@@ -15,10 +15,14 @@
   failures = 0

   ctx.beforeAll (done)->
+    console.log '...initializing browser'
     browser.init caps, (err)->
+      console.log '...browser initialized'
       console.log err if err?
       should.not.exist err
+      console.log '...getting url', base_url
       browser.get base_url, (err)->
+        console.log '...url got!'
         console.log err if err?
         should.not.exist err
         do done
@@ -50,7 +54,9 @@

 quit = (browser, failures, notify_sauce_labs, done) ->
+  console.log '...quiting browser'
   browser.quit (err)->
+    console.log '...browser quit!'
     should.not.exist err

     if not notify_sauce_labs


Remove console logging

Remove console logging

--- app/assets/javascripts/components/overview/overview_handler.cjsx
+++ app/assets/javascripts/components/overview/overview_handler.cjsx
@@ -41,7 +41,6 @@
         />
       )

-    console.log @props.location.query.syllabus_upload
     if @props.location.query.syllabus_upload == 'true' && @props.current_user.admin
       syllabus_upload = (
         <Modal modalClass='course__syllabus-upload'>


fix atoms dirty form

fix atoms dirty form

--- app/assets/javascripts/folio/console/modules/_dirty-forms.coffee
+++ app/assets/javascripts/folio/console/modules/_dirty-forms.coffee
@@ -1,26 +1,26 @@
 handled = false

-handler = ->
+handler = ($form) ->
   return if handled
   handled = true
   beforeunload = -> 'Changes you made may not be saved.'
   $('.f-c-form-footer').addClass('f-c-form-footer--dirty')
   $(window).on 'beforeunload', beforeunload
-  $form = $(this)
-  $form = $form.closest('form') unless $form.is('form')
   $form.on 'submit', -> $(window).off('beforeunload')

 $('.simple_form')
   .filter(-> @className.match(/(new_|edit_)/))
   .not('.f-c-simple-form-with-atoms')
-  .one 'change', handler
+  .one 'change', ->
+    handler($(this))

 $('.f-c-simple-form-with-atoms__form')
-  .one 'change', handler
+  .one 'change', ->
+    handler($(this).closest('form'))

 receiveMessage = (e) ->
   return if e.origin isnt window.origin
   switch e.data.type
-    when 'setFormAsDirty' then handler()
+    when 'setFormAsDirty' then handler($('.f-c-simple-form-with-atoms'))

 window.addEventListener('message', receiveMessage, false)