Move handling of query results to a separate class

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -5,7 +5,7 @@
         query: null
         results: null
         profile: null
-        cursor: null
+        query_result: null
         metadata: null
         cursor_timed_out: true
         view: 'tree'
@@ -19,9 +19,116 @@
             profiler: false
         history: []
         focus_on_codemirror: true
-        last_query_has_profile: true
-        #saved_data: {}
-
+
+    # This class represents the results of a query.
+    #
+    # If there is a profile, `profile` is set
+    #
+    # After a 'sync' event, one of `error`, `value` or `cursor` is always set
+    #
+    # It triggers the following events:
+    #
+    #  * sync: The first response has been received
+    #
+    #  * add: Another row has been received from a cursor
+    #
+    #  * error: An error has been received from a cursor
+    #
+    #  * end: There are no more documents to fetch
+    #
+    #  * discard: The results have been discarded
+    class @QueryResult
+        _.extend @::, Backbone.Events
+
+        constructor: (options) ->
+            @has_profile = options.profile
+            @discard_results = options.discard_results ? false
+            if options.events?
+                for own event, handler of options.events
+                    @on event, handler
+
+        # Can be used as a callback to run
+        set: (error, result) =>
+            if error?
+                @error = error
+            else if not @discard_results
+                if @has_profile
+                    @profile = result.profile
+                    value = result.value
+                else
+                    value = result
+                if typeof value._next is 'function' # if it's a cursor
+                    @results = []
+                    @truncated = false
+                    @cursor = value
+                    @is_feed = @cursor.toString() == '[object Feed]'
+                    @missing = 0
+                    @ended = false
+                    @start_index = 0
+                else
+                    @start_index = 0
+                    @value = value
+                    @ended = true
+            @trigger 'sync', @
+
+        # Remove n elements from the result set of a cursor Waits for
+        # n+1 elements, or the end before returning
+        #
+        # Note: the callbacks are not ordered. In this code, `f` is
+        # never called: `qr.shift 2 f; cb = -> qr.shift 1 cb`
+        shift: (n, k) =>
+            cb = =>
+                if @results.length > n or @ended
+                    @off 'end', cb
+                    ret = @results[0...n]
+                    @results = @results[n...]
+                    @start_index += n
+                    k @, ret
+                else
+                    @once 'add', cb
+                    @more()
+            @once 'end', cb
+            cb()
+
+        # Discard the results
+        discard: =>
+            @trigger 'discard', @
+            @off()
+            @discard_results = true
+            delete @profile
+            delete @value
+            delete @results
+            delete @truncated
+            @cursor?.close()
+            delete @cursor
+
+        # Ask for more results from the cursor
+        more: (n) =>
+            prev_missing = @missing
+            @missing = prev_missing + (n ? 1)
+            if prev_missing <= 0
+                @fetch_next()
+
+        # Gets the next result from the cursor
+        fetch_next: =>
+            if not @ended
+                @cursor.next (error, row) =>
+                    if error?
+                        if error.message is 'No more rows in the cursor.'
+                            @ended = true
+                            @trigger 'end', @
+                        else
+                            @error = error
+                            @trigger 'error', @, error
+                            @discard_results = true
+                    else
+                        if @discard_results
+                            return
+                        @results.push row
+                        @trigger 'add', @, row
+                        @missing--
+                        if @missing >= 0
+                            @fetch_next()

     class @Container extends Backbone.View
         id: 'dataexplorer'
@@ -520,7 +627,6 @@
             $(window).mousemove @handle_mousemove
             $(window).mouseup @handle_mouseup
             $(window).mousedown @handle_mousedown
-            @id_execution = 0
             @keep_suggestions_on_blur = false

             @render()
@@ -2406,30 +2512,16 @@
         # Function triggered when the user click on 'more results'
         show_more_results: (event) =>
             event.preventDefault()
-            @skip_value += @current_results.length
-            try
-                @current_results = []
-
-                # If there are more results to show, we had to buffer one row, so we add that one extra row to the current results
-                @current_results.push @extra_row
-                @extra_row = undefined
-
-                @start_time = new Date()
-
-                @id_execution++
-                get_result_callback = @generate_get_result_callback @id_execution
-                @state.cursor.next get_result_callback
-                $(window).scrollTop(@$('.results_container').offset().top)
-            catch err
-                @toggle_executing false
-                # We print the query here (the user just hit `more data`)
-                @results_view.render_error(@query, err)
+            @current_results = []
+            @start_time = new Date()
+            @state.query_result.shift @limit, @next_results_callback
+
+            # TODO: there is no indication that anything is loading
+            $(window).scrollTop(@$('.results_container').offset().top)

         abort_query: =>
-            if @state.cursor?
-                @state.cursor.close()
+            @state.query_result?.discard()
             @driver_handler.close_connection()
-            @id_execution++
             @toggle_executing false

         # Function that execute the queries in a synchronous way.
@@ -2452,8 +2544,7 @@

             # Execute the query
             try
-                if @state.cursor?
-                    @state.cursor.close()
+                @state.query_result?.discard()
                 # Separate queries
                 @non_rethinkdb_query = '' # Store the statements that don't return a rethinkdb query (like "var a = 1;")
                 @index = 0 # index of the query currently being executed
@@ -2495,7 +2586,7 @@

         # A portion is one query of the whole input.
         execute_portion: =>
-            @state.cursor = null
+            @state.query_result = null
             while @queries[@index]?
                 full_query = @non_rethinkdb_query
                 full_query += @queries[@index]
@@ -2516,22 +2607,25 @@

                 @index++
                 if rdb_query instanceof @TermBaseConstructor
-                    @skip_value = 0
                     @start_time = new Date()
                     @current_results = []

-                    @id_execution++ # Update the id_execution and use it to tag the callbacks
-                    rdb_global_callback = @generate_rdb_global_callback @id_execution
-                    # Date are displayed in their raw format for now.
-                    @state.last_query_has_profile = @state.options.profiler
+                    query_result = new DataExplorerView.QueryResult
+                        profile: @state.options.profiler
+                        events:
+                            sync: @query_result_callback
+                            error: (err) =>
+                                @toggle_executing false
+                                @results_view.render_error(@query, err)
+
                     @driver_handler.create_connection (error, connection) =>
                         if (error)
                             @error_on_connect error
                         else
                             rdb_query.private_run connection,
                                 {binaryFormat: "raw", timeFormat: "raw", profile: @state.options.profiler},
-                                rdb_global_callback # @rdb_global_callback can be fired more than once
-                    , @id_execution, @error_on_connect
+                                query_result.set
+                    , @error_on_connect

                     return true
                 else if rdb_query instanceof DataExplorerView.DriverHandler
@@ -2549,105 +2643,43 @@
                             query: @raw_query
                             broken_query: true

-
-        # Create a callback for when a query returns
-        # We tag the callback to make sure that we display the results only of the last query executed by the user
-        generate_rdb_global_callback: (id_execution) =>
-            rdb_global_callback = (error, results) =>
-                if @id_execution is id_execution # We execute the query only if it is the last one
-                    get_result_callback = @generate_get_result_callback id_execution
-
-                    if error?
-                        @toggle_executing false
-                        if @queries.length > 1
-                            @results_view.render_error(@raw_queries[@index-1], error)
-                        else
-                            @results_view.render_error(null, error)
-                        @save_query
-                            query: @raw_query
-                            broken_query: true
-                        return false
-
-                    if results?.profile? and @state.last_query_has_profile is true
-                        cursor = results.value
-                        @profile = results.profile
-                        @state.profile = @profile
-                    else
-                        cursor = results
-                        @profile = null # @profile is null if the user deactivated the profiler
-                        @state.profile = @profile
-
-
-                    if @index is @queries.length # @index was incremented in execute_portion
-                        if cursor? and typeof cursor._next is 'function' # If the result is a cursor
-                            @state.cursor = cursor
-                            @state.cursor.next get_result_callback
-                        else
-                            @toggle_executing false
-
-                            # Save the last executed query and the last displayed results
-                            @current_results = cursor
-
-                            @state.query = @query
-                            @state.results = @current_results
-                            @state.metadata =
-                                limit_value: if Object::toString.call(@results) is '[object Array]' then @current_results.length else 1 # If @current_results.length is not defined, we have a single value
-                                skip_value: @skip_value
-                                execution_time: new Date() - @start_time
-                                query: @query
-                                has_more_data: false
-
-                            @results_view.render_result
-                                results: @current_results # The first parameter is null ( = query, so we don't display it)
-                                metadata: @state.metadata
-                                profile: @profile
-
-                            # Successful query, let's save it in the history
-                            @save_query
-                                query: @raw_query
-                                broken_query: false
-                    else
-                        @execute_portion()
-
-            return rdb_global_callback
-
-        # Create a callback used in cursor.next()
-        # We tag the callback to make sure that we display the results only of the last query executed by the user
-        generate_get_result_callback: (id_execution) =>
-            get_result_callback = (error, data) =>
-                if @id_execution is id_execution
-                    if error?
-                        if error.message isnt 'No more rows in the cursor.'
-                            if @queries.length > 1
-                                @results_view.render_error(@query, error)
-                            else
-                                @results_view.render_error(null, error)
-                            return false
-
-                    if data isnt undefined
-                        if @current_results.length < @limit # We display @limit results per page, if we don't have enough, we keep requesting more
-                            @current_results.push data
-                            @extra_row = undefined # We don't know if there's an extra row yet, reset its value to undefined
-                            @state.cursor.next get_result_callback
-                        else if @current_results.length is @limit # If we've reached the limit of results we can show, we still want to know if there are more results available
-                            @extra_row = data # We requested an extra row, but won't display it on the current page, so we need to save it for later (when the user wants to show more results)
-                            get_result_callback() # Display results
-                        return true
-
+
+        # Handle the result of a query
+        query_result_callback: (result) =>
+            if result.error?
+                @toggle_executing false
+                if @queries.length > 1
+                    @results_view.render_error(@raw_queries[@index-1], result.error)
+                else
+                    @results_view.render_error(null, result.error)
+                @save_query
+                    query: @raw_query
+                    broken_query: true
+                return false
+
+            @profile = result.profile ? null
+            @state.profile = @profile
+
+            if @index is @queries.length # @index was incremented in execute_portion
+                @state.query_result = result
+                if result.cursor?
+
+                    result.shift @limit, @next_results_callback
+                else
                     @toggle_executing false
-
-                    # if data is undefined or @current_results.length is @limit
+                    @current_results = result.value
+
                     @state.query = @query
                     @state.results = @current_results
                     @state.metadata =
-                        limit_value: if @current_results?.length? then @current_results.length else 1 # If @current_results.length is not defined, we have a single value
-                        skip_value: @skip_value
+                        limit_value: if Object::toString.call(@results) is '[object Array]' then @current_results.length else 1
+                        skip_value: 0
                         execution_time: new Date() - @start_time
                         query: @query
-                        has_more_data: @extra_row isnt undefined # If we could buffer one more row, it means that there are more results available
+                        has_more_data: false

                     @results_view.render_result
-                        results: @current_results # The first parameter is null ( = query, so we don't display it)
+                        results: @current_results
                         metadata: @state.metadata
                         profile: @profile

@@ -2655,6 +2687,28 @@
                     @save_query
                         query: @raw_query
                         broken_query: false
+            else
+                @execute_portion()
+
+        # Called when there is a new batch of results to display
+        next_results_callback: (result, rows) =>
+
+            @current_results = rows
+
+            @toggle_executing false
+            @state.query = @query
+            @state.results = @current_results
+            @state.metadata =
+                limit_value: @current_results.length
+                skip_value: result.start_index
+                execution_time: new Date() - @start_time
+                query: @query
+                has_more_data: not result.ended
+
+            @results_view.render_result
+                results: @current_results
+                metadata: @state.metadata
+                profile: @profile

         # Evaluate the query
         # We cannot force eval to a local scope, but "use strict" will declare variables in the scope at least
@@ -3559,9 +3613,10 @@
                     flatten_attr: flatten_attr

         tag_record: (doc, i) =>
-            doc.record = @metadata.skip_value + i
+            doc.record = @metadata.skip_value + 1

         render_result: (args) =>
+
             if args? and args.results isnt undefined
                 @results = args.results
                 @results_array = null # if @results is not an array (possible starting from 1.4), we will transform @results_array to [@results] for the table view
@@ -3929,22 +3984,16 @@
                 driver.close @connection
                 @connection = null

-        create_connection: (cb, id_execution, connection_cb) =>
+        create_connection: (cb, connection_cb) =>
             @close_connection()

             that = @
-            @id_execution = id_execution
-
-            ((_id_execution) =>
-                driver.connect (error, connection) =>
-                    if _id_execution is @id_execution
-                        connection.removeAllListeners 'error' # See issue 1347
-                        connection.on 'error', connection_cb
-                        @connection = connection
-                        cb(error, connection)
-                    else
-                        driver.close connection
-            )(id_execution)
+
+            driver.connect (error, connection) =>
+                connection.removeAllListeners 'error' # See issue 1347
+                connection.on 'error', connection_cb
+                @connection = connection
+                cb(error, connection)

         remove: =>
             @close_connection()


Add back in missing xs check

Add back in missing xs check

--- bokehjs/src/coffee/renderer/glyph/patches.coffee
+++ bokehjs/src/coffee/renderer/glyph/patches.coffee
@@ -51,6 +51,8 @@
       for j in [0...xss[i].length]
         xs = xss[i][j]
         ys = yss[i][j]
+        if xs.length == 0
+          continue
         pts.push([
           _.min(xs), _.min(ys),
           _.max(xs), _.max(ys),


More corrections for comments spinner

More corrections for comments spinner

--- app/assets/javascripts/artifacts.coffee
+++ app/assets/javascripts/artifacts.coffee
@@ -166,13 +166,14 @@

   # Load juvia comments
   $('#show-comments').click (e) ->
-    $('#comments').load('/comments_script.html').hide().fadeIn(500);
+
+    $('#comments').load('/comments_script.html', ->
+      $('#comments-load').hide()
+      $('html, body').animate
+        scrollTop: $("#comments").offset().top - 100
+      , 2000
+    ).hide().fadeIn(500);
+
     $('#comments-load').show()

-    $('html, body').animate
-      scrollTop: $("#comments").offset().top - 100
-    , 2000
-
-    $('#comments-load').hide()
-
     e.preventDefault()


working slider

working slider

--- app/assets/javascripts/commit.js.coffee
+++ app/assets/javascripts/commit.js.coffee
@@ -1,19 +1,18 @@
 #= require d3
+#= require underscore
 #Array.prototype.inject = (init, fn) -> this.reduce(fn, init)
-

 window.timeline_chart = do ->
   t = {}
-  t.margin =
-    top: 0
-    right: 0
-    bottom: 0
-    left: 20
+  t.recalculate = ->
+    t.width = window.innerWidth
+    t.rx = d3.scale.linear().domain([0, t.width])
+    t.x = d3.scale.linear().range([0, t.width])
   t.get_timestamp = (commit) -> commit.timestamp
-  t.get_aggregated_additions = (commit) -> commit.aggregated_additions || 0
-  t.get_aggregated_deletions = (commit) -> commit.aggregated_deletions || 0
-  t.width = (window.innerWidth) - t.margin.left - t.margin.right
-  t.height = 100 - t.margin.top - t.margin.bottom
+  t.get_aggregated_additions = (commit) -> commit.aggregated_additions or 0
+  t.get_aggregated_deletions = (commit) -> commit.aggregated_deletions or 0
+  t.width = window.innerWidth
+  t.height = 100
   t.parse_date = d3.time.format("%Y-%m-%dT%XZ").parse
   t.rx = d3.scale.linear().domain([0, t.width])
   t.x = d3.scale.linear().range([0, t.width])
@@ -30,8 +29,8 @@
   t.get_timestamp_range = (min_screen_x, max_screen_x) ->
     a = Math.floor(t.rx min_screen_x)
     b = Math.ceil(t.rx max_screen_x)
+    console.log "get_timestamp_range", a,b,[t.filtered_commits[a].timestamp, t.filtered_commits[b].timestamp]
     [t.filtered_commits[a].timestamp, t.filtered_commits[b].timestamp]
-
   # t.x_axis = d3.svg.axis().scale(t.x).orient("bottom")
   # t.y_axis = d3.svg.axis().scale(t.y).orient("left")
   t.sort_timestamp_asc = (a,b) -> a.timestamp-b.timestamp
@@ -41,20 +40,18 @@
     .append("svg")
     .attr
       class: "timeline_chart_svg"
-      width: t.width + t.margin.left + t.margin.right
-      height: t.height + t.margin.top + t.margin.bottom
-    .append("g")
-    .attr
-      transform: "translate(" + t.margin.left + "," + t.margin.top + ")"
+      width: t.width
+      height: t.height
+
   t.render_timeline_chart = (filtered_commits) ->
-    for a in [0 .. filtered_commits.length-1]
-      filtered_commits[a].pos = a if filtered_commits[a]?
+    for i in [0 .. filtered_commits.length-1]
+      filtered_commits[i].pos = i if filtered_commits[i]?
     t.filtered_commits = filtered_commits
     t.svg.selectAll("path").remove()
     t.svg.selectAll("g").remove()
     t.x.domain [0 , filtered_commits.length]
     t.y.domain [0, d3.max(filtered_commits, t.summed_additions_deletions)]
-    t.rx.range [0 , filtered_commits.length]
+    t.rx.range [0 , filtered_commits.length-1]
     a = t.svg.selectAll(".area").data(t.layer filtered_commits)

     a.enter()
@@ -92,10 +89,11 @@
 do ->
   ls = $('.left.slider')
   rs = $('.right.slider')
-  max_width = $('#timeline>svg').innerWidth() - timeline_chart.margin.right - 10
-  min_width = timeline_chart.margin.left
+  max_width = $('#timeline>svg').innerWidth()
+  min_width = 0
   ls_down = false
   rs_down = false
+  current_range = []
   down = (e)->
     ls_down = true if (e.data is ls)
     rs_down = true if (e.data is rs)
@@ -103,13 +101,22 @@
   up = (e)->
     ls_down = false
     rs_down = false
+    temp = timeline_chart.get_timestamp_range((parseInt ls.css("left")) or 0, parseInt rs.css("left") or max_width)
+    if not (_.isEqual(current_range,temp))
+      current_range = temp
+      # deegan
+      console.log Repo.commits.filter (commit) ->
+        commit.timestamp >= temp[0] and commit.timestamp <= temp[1]
+

   moved = (e) ->
-    ls.css("left", e.pageX-2.5) if ls_down and e.pageX+5 < (parseInt rs.css("left")) and e.pageX < max_width and e.pageX > min_width
-    rs.css("left", e.pageX-2.5) if rs_down and e.pageX-5 > (parseInt ls.css("left")) and e.pageX < max_width and e.pageX > min_width
+    current_rs_left = parseInt rs.css("left") or 0
+    current_ls_left = parseInt ls.css("left") or max_width
+    ls.css("left", if (e.pageX > max_width) then max_width - 2.5 else e.pageX - 2.5 ) if ls_down and e.pageX+5 < current_rs_left and current_ls_left < max_width and current_ls_left > min_width
+    rs.css("left", if (e.pageX > max_width) then max_width - 2.5 else e.pageX - 2.5 ) if rs_down and e.pageX-5 > current_ls_left and current_rs_left < max_width and current_rs_left > min_width
   ls.on 'mousedown', ls, down
   rs.on 'mousedown', rs, down
-  $(document).on('mouseup', up).on('mousemove', moved).on('mouseenter', up)
+  $(document).on('mouseup', up).on('mousemove', moved).on('mouseleave', up).on('mouseenter', up)

 settings =
@@ -136,17 +143,11 @@
     Repo.calculate_files_of commit
     Repo.timestamp_to_d3 commit
     Repo.commits = Repo.commits.concat(commit)
-
-    # clearTimeout(Repo.timer)
-    # Repo.timer = setTimeout (->
-    #   Repo.render_charts()
-    # ), 1000
     Repo.render_timeline()

     Repo.parsedFiles++
     console.log Repo.parsedFiles
     if(Repo.parsedFiles == Repo.num_commits)
-      console.log("asdf")
       Repo.render_barchart()

   render_barchart: ->
@@ -157,7 +158,7 @@
     Repo.set_labels()

   render_timeline: ->
-    timeline_chart.render_timeline_chart (c for c in Repo.commits when c.aggregated_deletions? and c.aggregated_additions? and c.timestamp?).sort(timeline_chart.sort_timestamp_asc)
+    timeline_chart.render_timeline_chart Repo.commits.sort(timeline_chart.sort_timestamp_asc)

   init: (commits) ->
     Repo.num_commits = commits.length
@@ -170,14 +171,10 @@
     Repo.render_timeline() if Object.keys(Repo.commits).length > 0

     timeline_chart.render_timeline_chart Repo.commits
-    console.log commit.id
-    if commit.files?
-      Repo.add_files commit.files
-      Repo.calculate_files_of commit
-      Repo.timestamp_to_d3 commit
-
-    else
-      console.log "Shit got sent"
+    Repo.add_files commit.files
+    Repo.calculate_files_of commit
+    Repo.timestamp_to_d3 commit
+

     Repo.parsed_commits++
     console.log("finished: " + Repo.parsed_commits)
@@ -205,6 +202,7 @@

   timestamp_to_d3: (commit) ->
+    debugger if !commit? or !commit.timestamp?
     commit.timestamp = d3.time.format("%Y-%m-%dT%XZ").parse(commit.timestamp)
   calculate_files_of: (commit) ->
     commit.aggregated_additions = d3.sum(commit.files, (file)-> file[1])
@@ -213,7 +211,7 @@
   add_files: (files) ->
     for file in files
       name = file[0]
-      Repo.files[name]   ||= [0, 0]
+      Repo.files[name]   or= [0, 0]
       Repo.files[name][0] += file[1]
       Repo.files[name][1] += file[2]

@@ -225,7 +223,6 @@
     Repo.prepared_files = Repo.formated_files

   draw: ->
-
     Repo.chart.attr
       height: Repo.formated_files.length * 23
     textscale   = d3.scale.linear().domain([0, d3.max(Repo.formated_files, (d)-> d[3] )]).range([0, 100])
@@ -259,7 +256,6 @@

   set_labels: ->
-
     labels = Repo.label_chart.selectAll("div").data(Repo.prepared_files)
       .text( (a) -> a[0])
     labels.enter()
@@ -336,7 +332,9 @@

 $.getJSON Repo.url, Repo.init
 window.t = timeline_chart
-
+$(window).resize ->
+  timeline_chart.recalculate()
+  Repo.render_timeline()
 f = $('#filter')
 filter = () ->
   a = f.val()


sankey: use nested svg elements

sankey: use nested svg elements

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -211,41 +211,39 @@
         attr("class", "link").
         style("stroke-width", (link) -> link.value()).
         style("stroke", (link, i) -> link.color()).
-        #style("stroke", (link, i) -> link_color(i)).
         style("fill", "none").
         style("opacity", 0.8).
         attr("d", (link) => @link_line link.path_points())

-      @nodes = @svg.selectAll("rect").
+      @nodes = @svg.selectAll("svg.node").
         data(@module.nodes.models, (d) -> d.get('id')).
         enter().
-        append("rect").
+        append("svg").
+        attr("class", "node").
         attr("x", (d) => @x(@module.Node.horizontal_spacing * d.get('column') + 20)).
-        attr("y", (d) => @y(d.y_offset())).
+        attr("y", (d) => @y(d.y_offset()))
+
+      @nodes.append("svg:rect").
+        attr("stroke", "gray").
+        attr("fill", (datum, i) -> colors(i)).
         attr("width", @x @module.Node.width).
-        attr("height", (d) => @y d.value()).
-        attr("stroke", "gray").
-        attr("fill", (datum, i) -> colors(i))
-
-      @labels = @svg.selectAll("text.label").
-        data(@module.nodes.models, (d) -> d.get('id')).
-        enter().
-        append("svg:text").
+        attr("height", (d) => @y d.value())
+
+      @nodes.append("svg:text").
         attr("class", "label").
-        attr("x", (d) => @x d.x_offset()).
-        attr("y", (d) => @y(d.y_center() + 5)).
-        attr("dx", 5).
+        attr("x", 5).
+        attr("y", 10).
         text((d) -> d.label())

     refresh: =>
       @nodes.data(@module.nodes.models, (d) -> d.get('id')).
         transition().duration(500).
-        attr("height", (d) => @y d.value()).
         attr("y", (d) => @y d.y_offset())

-      @labels.data(@module.nodes.models, (d) -> d.get('id')).
+      @nodes.data(@module.nodes.models, (d) -> d.get('id')).
+        selectAll("rect").
         transition().duration(500).
-        attr("y", (d) => @y(d.y_center() + 5))
+        attr("height", (d) => @y d.value())

       @links.data(@module.links.models, (d) -> d.cid).
         transition().duration(500).


Change ace editor option.

Change ace editor option.

--- app/assets/javascripts/editor.js.coffee
+++ app/assets/javascripts/editor.js.coffee
@@ -37,14 +37,18 @@
   e_obj.insertAfter(ta_obj.parent())
   tb_obj.insertAfter(ta_obj.parent())
   editor = ace.edit('editor')
+  editor.setOptions
+    maxLines: Infinity
+    showPrintMargin: false
+    wrap: true
+    showGutter: false
+    theme: "ace/theme/github"
+    mode: "ace/mode/markdown"
+    fontSize: "14px"
+  editor.setAutoScrollEditorIntoView(true)
   editor.session.setValue(ta_obj.val(), -1)
   editor.focus()
-  editor.setShowPrintMargin(false)
-  editor.session.setUseWrapMode(true)
-  editor.renderer.setShowGutter(false)
-  editor.setTheme("ace/theme/github")
-  editor.getSession().setMode("ace/mode/markdown")
-  editor.setOptions maxLines: Infinity
+
   editor.getSession().on "change", ->
     ta_obj.val editor.getSession().getValue()


autofocus after post creation

autofocus after post creation

--- app/assets/javascripts/models.js.coffee
+++ app/assets/javascripts/models.js.coffee
@@ -54,6 +54,7 @@
     @after_create: (post) ->
       new PostView post
       $('#new_post form')[0].reset()
+      $('#new_post textarea')[0].focus()

     @after_update: (post)->
       new PostEditedView post


Add \n to empty search check

Add \n to empty search check

--- app/assets/javascripts/search.js.coffee
+++ app/assets/javascripts/search.js.coffee
@@ -87,7 +87,7 @@
       params['tag'] = $("#search_tag").val()

     $.get "/videos/search", data: params, (data) ->
-      if data == '

'
+      if data == '
\n
'
         $("#search-results").html "No search results; try broadening your terms."
       else
         $("#search-results").html data


Group drawing tool radio buttons (#4137)

Group all drawing tool radio buttons as a drawing-tools input field.

--- app/classifier/tasks/drawing/index.cjsx
+++ app/classifier/tasks/drawing/index.cjsx
@@ -94,7 +94,7 @@
       count = (true for mark in @props.annotation.value when mark.tool is i).length

-          <input name={i} autoFocus={@props.autoFocus and i is 0} type="radio" className="drawing-tool-button-input" checked={i is (@props.annotation._toolIndex ? 0)} onChange={@handleChange.bind this, i} />
+          <input name="drawing-tool" autoFocus={@props.autoFocus and i is 0} type="radio" className="drawing-tool-button-input" checked={i is (@props.annotation._toolIndex ? 0)} onChange={@handleChange.bind this, i} />

               {icons[tool.type]}


Fix bug #1501: Promote to US button lost the hover style

--- app/coffee/modules/issues/detail.coffee
+++ app/coffee/modules/issues/detail.coffee
@@ -573,7 +573,7 @@

 PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm) ->
     template = _.template("""
-
+
             Promote to User Story

     """)  # TODO: i18n


Fixed infinite loop in MacroParser

Fixed infinite loop in MacroParser

--- app/coffee/modules/main/entities.coffee
+++ app/coffee/modules/main/entities.coffee
@@ -68,6 +68,10 @@
         macros = []
         parsedMacros = []

+        # TODO - remove
+        console.log 'Parsed Macro from device:'
+        console.log macroArray
+
         # Compacts the macroArray
         # QUESTION - will we ever have zeros between each key stroke?
         macroArray = _.compact(macroArray)
@@ -122,6 +126,11 @@
             macroIndex = macroIndex + 2
             continue

+          # Non-Repeated - standard procedure
+          parsedMacros.push(macro)
+          macroIndex++
+          continue
+
         # Sets the Macros on the AstrokeyConfig model
         config = @get('config')
         config.get('macros').reset(parsedMacros)


Re-fetch icons after modifying them

Re-fetch icons after modifying them

--- app/pages/lab/spotters-guide/index.cjsx
+++ app/pages/lab/spotters-guide/index.cjsx
@@ -34,19 +34,23 @@
     apiClient.type('field_guides').get project_id: project.id
       .then ([guide]) =>
         @listenTo guide
+        @fetchIcons guide
         @setState {guide}
-        guide.get 'attached_images'
-          .then (images) =>
-            icons = {}
-            images.forEach (image) ->
-              icons[image.id] = image
-            @setState {icons}

   listenTo: (guide) ->
     @_forceUpdate ?= @forceUpdate.bind this
     @_currentGuide?.stopListening @_forceUpdate
     guide?.listen @_forceUpdate
     @_currentGuide = guide
+
+  fetchIcons: (guide) ->
+    guide.uncacheLink 'attached_images'
+    guide.get 'attached_images'
+      .then (images) =>
+        icons = {}
+        images.forEach (image) ->
+          icons[image.id] = image
+        @setState {icons}

   createArticle: ->
     @props.actions.appendItem @state.guide.id
@@ -70,6 +74,8 @@
     awaitIconAction.then =>
       @props.actions.updateItem @state.guide.id, @state.editing, {title, content}
         .then =>
+          if icon?
+            @fetchIcons @state.guide
           @editArticle null

   render: ->


Color tweaks

Color tweaks

--- src/io/graphics-ascii.coffee
+++ src/io/graphics-ascii.coffee
@@ -3,8 +3,14 @@
 exports.graphics = graphics =
 	wall:
 		symbol: '#'
-		color: 'blue'
-	floor: '.'
+		# color: '#BE5F00'
+		color: '#00ff00'
+		# color: 'light green'
+	floor:
+		symbol: '.'
+		color: '#00cd00'
+		# color: 'green'
+		# color: '#603000'

 	stairsDown: '>'
 	stairsUp: '<'


active items: add signature annotations

active items: add signature annotations

--- workers/social/lib/social/models/activeitems.coffee
+++ workers/social/lib/social/models/activeitems.coffee
@@ -6,14 +6,22 @@
 JTag           = require "./tag"
 Cache          = require "../cache/main"

-{secure, daisy, Base} = Bongo
+{secure, daisy, Base, signature} = Bongo

 module.exports = class ActiveItems extends Base
   @share()

   @set
-    sharedMethods :
-      static      : ["fetchTopics", "fetchUsers"]
+    sharedMethods   :
+      static        :
+        fetchTopics : [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        fetchUsers  : [
+          (signature Function)
+          (signature Object, Function)
+        ]

   nameMapping =
     user    :
@@ -26,7 +34,8 @@
   # Returns topics in following order:
   #   * Active topics in the last day
   #   * Random topics
-  @fetchTopics = secure (client, options={}, callback)->
+  @fetchTopics = secure (client, options, callback)->
+    [callback, options] = [options, callback]  unless callback
     options.name       = "topic"
     options.fallbackFn = @fetchRandomTopics

@@ -36,7 +45,8 @@
   #   * Client's followers who are online
   #   * Active users in the last day
   #   * Random users
-  @fetchUsers = secure (client, options={}, callback)->
+  @fetchUsers = secure (client, options, callback)->
+    [callback, options] = [options, callback]  unless callback
     options.client     = client
     options.fallbackFn = @fetchRandomUsers

@@ -67,7 +77,8 @@
   #
   # Popularity is determined by number of entries in 'relationships'
   # collection that match the criteria passed to it.
-  @fetchItems = (options={}, callback) ->
+  @fetchItems = (options, callback) ->
+    [callback, options] = [options, callback]  unless callback
     {name, nin} = options
     mapping     = nameMapping[name]
     {klass, as} = mapping


Remove helper / private method object.

Remove helper / private method object.

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -778,10 +778,10 @@
     # delete options.targetOptions
     options.client                = client

-    method.call this, selector, options, (err, records = []) ->
+    method.call this, selector, options, (err, records = []) =>
       return callback err, records  if err or not records or not fetchEmail

-      helper.mergeAccountsWithEmail records, (err, accounts) ->
+      @mergeAccountsWithEmail records, (err, accounts) ->
         return callback err, accounts

@@ -1337,20 +1337,18 @@
         return callback null, channel.id

-  helper =
-
-    mergeAccountsWithEmail: (accounts, callback) ->
-
-      JUser     = require '../user'
-      usernames = accounts.map (account) -> account.profile.nickname
-
-      JUser.some { username: { $in: usernames } }, {}, (err, users) ->
-
-        return callback err, []  if err or not users
-
-        for account in accounts
-          for user in users
-            if account.profile.nickname is user.username
-              account.profile.email = user.email
-
-        return callback null, accounts
+  mergeAccountsWithEmail: (accounts, callback) ->
+
+    JUser     = require '../user'
+    usernames = accounts.map (account) -> account.profile.nickname
+
+    JUser.some { username: { $in: usernames } }, {}, (err, users) ->
+
+      return callback err, []  if err or not users
+
+      for account in accounts
+        for user in users
+          if account.profile.nickname is user.username
+            account.profile.email = user.email
+
+      return callback null, accounts


code quality

code quality

--- workers/social/lib/social/traits/slugifiable.coffee
+++ workers/social/lib/social/traits/slugifiable.coffee
@@ -1,6 +1,6 @@
-module.exports = class Sluggifiable
+module.exports = class Slugifiable

-  generateSlug =(str)->
+  slugify =(str)->
     slug = str
       .toLowerCase()                # change everything to lowercase
       .replace(/^\s+|\s+$/g, "")    # trim leading and trailing spaces
@@ -10,8 +10,6 @@
       .replace(/^-+|-+$/g, "")      # trim leading and trailing hyphens

   generateUniqueSlug =(constructor, slug, i, callback)->
-    [callback, i] = [i, callback] unless callback
-    i ?= 0
     candidate = "#{slug}#{i or ''}"
     constructor.count slug: candidate, (err, count)->
       if err then callback err
@@ -23,5 +21,5 @@
   createSlug:(callback)->
     {constructor} = this
     {slugifyFrom} = constructor
-    slug = generateSlug @[slugifyFrom]
-    generateUniqueSlug constructor, slug, callback
+    slug = slugify @[slugifyFrom]
+    generateUniqueSlug constructor, slug, 0, callback


:bug:Fix /admin/school-counts id vs. _id lean() property usage

--- app/views/admin/SchoolCountsView.coffee
+++ app/views/admin/SchoolCountsView.coffee
@@ -35,6 +35,7 @@
       url += "&options[beforeId]=#{beforeId}" if beforeId
       new Promise((resolve) -> setTimeout(resolve.bind(null, Promise.resolve($.get(url))), 100))
       .then (batchResults) =>
+        return Promise.resolve([]) if @destroyed
         results = results.concat(batchResults)
         if batchResults.length < @batchSize
           @updateLoadingState("Received #{results.length} from #{baseUrl} TOTAL")
@@ -65,7 +66,7 @@
       console.log(new Date().toISOString(), "Processing #{teachers.length} teachers...")
       @state = "Processing #{courseInstances.length} course instances..."
       for teacher in teachers
-        teacherMap[teacher.id] = teacher.geo ? {}
+        teacherMap[teacher._id] = teacher.geo ? {}

       @updateLoadingState("Processing #{classrooms.length} classrooms...")
       for classroom in classrooms
@@ -82,7 +83,7 @@
       for student in students
         continue unless studentNonHocMap[student._id]
         continue if teacherMap[student._id]
-        studentMap[student.id] = {geo: student.geo}
+        studentMap[student._id] = {geo: student.geo}

       delete studentNonHocMap[studentId] for studentId in studentNonHocMap # Don't need these anymore

@@ -99,7 +100,7 @@
         teacherID = trialRequest.applicant
         unless teacherMap[teacherID]
           # E.g. parents
-          # console.log("Skipping non-teacher #{teacherID} trial request #{trialRequest.id}")
+          # console.log("Skipping non-teacher #{teacherID} trial request #{trialRequest._id}")
           continue
         props = trialRequest.properties
         if props.nces_id and props.country and props.state


stability

stability

--- server/models/message.coffee
+++ server/models/message.coffee
@@ -423,7 +423,10 @@
                 # step 1 - open one box at random
                 (cb) -> imap.openBox boxIndex[firstboxid].path, cb
                 # step 2 - change flags to newflags
-                (cb) -> imap.setFlags firstuid, newflags, cb
+                (cb) ->
+                    # cant reproduce case, prevent crashing
+                    try imap.setFlags firstuid, newflags, cb
+                    catch err then cb err
                 # step 3 - copy the message to all addTo
                 (cb) ->
                     paths = boxOps.addTo.map (destId) ->


Base visibility on device position instead of viewport width

--- extras/Studio.framer/app.coffee
+++ extras/Studio.framer/app.coffee
@@ -41,7 +41,7 @@
 		@options =
 			padding: 20
 			width: 250
-			minWidth: 820
+			minAvailableSpace: 50
 			fixed: false

 		@render()
@@ -58,7 +58,7 @@

 		# Evaluate content and set height accordingly
 		@_updateHeight()
-		@_checkCanvasSize()
+		@_calculateAvailableSpace()

 	# Render main sheet
 	_renderSheet: ->
@@ -255,10 +255,22 @@

 		@_showPointer(@download)

-	_checkCanvasSize: ->
-		if Canvas.width < @options.minWidth then @_closeSheet() else @_openSheet()
+	_calculateAvailableSpace: ->
+
+		# Check if hand is visible
+		if Framer.Device.selectedHand isnt ""
+			availableSpace = Framer.Device.hands.screenFrame.x
+		else
+			availableSpace = Framer.Device.phone.screenFrame.x
+
+		if availableSpace < @options.minAvailableSpace
+			@_closeSheet()
+		else
+			@_openSheet()

 	_startListening: ->
+		@_calculateAvailableSpace()
+
 		# Show regular cursor on sheet
 		@sheet.onMouseOver ->
 			@style =
@@ -275,7 +287,7 @@

 		# When the window resizes evaluate if the sheet needs to be hidden
 		Canvas.onResize =>
-			@_checkCanvasSize() if !@fixed
+			@_calculateAvailableSpace() if !@fixed

 	# Show hand cursor
 	_showPointer: (layer) ->


fixed tabs over spaces

fixed tabs over spaces

--- src/application.coffee
+++ src/application.coffee
@@ -100,7 +100,7 @@
 	Promise.all([
 		knex('config').select('value').where(key: 'apiKey')
 		knex('config').select('value').where(key: 'uuid')
-        knex('config').select('value').where(key: 'version')
+		knex('config').select('value').where(key: 'version')
 		knex('app').select()
 	])
 	.then ([[apiKey], [uuid], [version], apps]) ->
@@ -108,12 +108,12 @@
 		uuid = uuid.value
 		version = uuid.value
 		###
-        # should I post 'resin-supervisor' version every 15-minutes?
-        # with something like
-        resinAPI.post(
-            device:
-                supervisor-version: version
-        )
+		# should I post 'resin-supervisor' version every 15-minutes?
+		# with something like
+		resinAPI.post(
+		    device:
+		        supervisor-version: version
+		)
 		###
 		resinAPI.get(
 			resource: 'application'


add route parameters

add route parameters

--- services/docstore/app.coffee
+++ services/docstore/app.coffee
@@ -15,6 +15,18 @@
 app = express()

 app.use Metrics.http.monitor(logger)
+
+app.param 'project_id', (req, res, next, project_id) ->
+	if project_id?.match /^[0-9a-f]{24}$/
+		next()
+	else
+		next new Error("invalid project id")
+
+app.param 'doc_id', (req, res, next, doc_id) ->
+	if doc_id?.match /^[0-9a-f]{24}$/
+		next()
+	else
+		next new Error("invalid doc id")

 app.get  '/project/:project_id/doc', HttpController.getAllDocs
 app.get  '/project/:project_id/doc/:doc_id', HttpController.getDoc


TerminalSession: session creation mode is passed as parameter to webtermConnect

--- client/Terminal/webtermview.coffee
+++ client/Terminal/webtermview.coffee
@@ -163,8 +163,8 @@
       @appStorage.setValue 'visualBell', false if not @appStorage.getValue('visualBell')?
       @appStorage.setValue 'scrollback', 1000 if not @appStorage.getValue('scrollback')?
       @updateSettings()
-
-      @webtermConnect()
+      {mode} = @getOptions()
+      @webtermConnect mode

     KD.getSingleton("kiteController").on "KiteError", (err) =>
       log "kite error:", err


fix(drag): Patch for crash on dragging threads

--- internal_packages/thread-list/lib/thread-list.cjsx
+++ internal_packages/thread-list/lib/thread-list.cjsx
@@ -151,7 +151,7 @@
     event.dataTransfer.effectAllowed = "move"
     event.dataTransfer.dragEffect = "move"

-    canvas = CanvasUtils.canvasWithThreadDragImage(dragThreadIds.length)
+    canvas = CanvasUtils.canvasWithThreadDragImage(dragData.threadIds.length)
     event.dataTransfer.setDragImage(canvas, 10, 10)
     event.dataTransfer.setData('nylas-threads-data', JSON.stringify(dragData))
     return


fixes to legacy cart updater

fixes to legacy cart updater

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -354,7 +354,8 @@
   res = []
   i = 0
   while i < ids.length
-    res.push map[ids[i]]
+    if ids[i] of map
+      res.push map[ids[i]]
     i++
   res.join '_'


Launch config

Launch config

--- app/config/settings.coffee
+++ app/config/settings.coffee
@@ -12,9 +12,9 @@
     analytics: { }

   production:
-    apiHost: 'https://dev.zooniverse.org'
-    classifyUrl: 'http://zooniverse-demo.s3-website-us-east-1.amazonaws.com/sunspots/#/classify'
-    talkHost: 'http://zooniverse-demo.s3-website-us-east-1.amazonaws.com/sunspots_talk'
+    apiHost: 'https://api.zooniverse.org'
+    classifyUrl: 'http://www.sunspotter.org/pre-launch/#/classify'
+    talkHost: 'http://talk.sunspotter.org'
     analytics: { }

 env = if window.location.port is '9295'


<tests> Measure - Correcting expected results.

--- tests/controllers/measure.coffee
+++ tests/controllers/measure.coffee
@@ -49,7 +49,7 @@
             done null

         it 'should allow requests', (done) ->
-            @client.post "sensors/#{store.rule.id}/measures", measure, done
+            @client.post "sensors/#{store.sensor.id}/measures", measure, done

         it 'should reply with the created Measure', ->
             expect(@err).to.not.exist
@@ -57,7 +57,7 @@
             expect(@body.sensorId).to.equal measure.sensorId
             expect(@body.type).to.equal measure.type
             expect(@body.value).to.equal measure.value
-            expect(@body.time).to.equal measure.time
+            expect(@body.time).to.equal measure.time.toISOString()
             expect(@body.id).to.exist
             store.measureId = @body.id

@@ -81,7 +81,7 @@
     describe 'When we get a Measure (GET /sensors/:sensorId/measures/:id) which exists', ->

         it 'should allow requests', (done) ->
-            @client.get "sensors/#{store.rule.id}/measures/#{store.measureId}", done
+            @client.get "sensors/#{store.sensor.id}/measures/#{store.measureId}", done

         it 'should reply with the corresponding measure', ->
             expect(@err).to.not.exist
@@ -89,7 +89,7 @@
             expect(@body.sensorId).to.equal store.sensor.id
             expect(@body.type).to.equal fixtureMeasure.measureMovement1.type
             expect(@body.value).to.equal fixtureMeasure.measureMovement1.value
-            expect(@body.time).to.equal fixtureMeasure.measureMovement1.time
+            expect(@body.time).to.equal fixtureMeasure.measureMovement1.time.toISOString()
             expect(@body.id).to.equal store.measureId

@@ -97,7 +97,7 @@

         it 'should allow requests', (done) ->
             id = store.measureId + 404 # since "store.measureId" is the only correct ID in DB, "store.measureId + 404" is not.
-            @client.get "sensors/#{store.rule.id}/measures/#{id}", done
+            @client.get "sensors/#{store.sensor.id}/measures/#{id}", done

         it 'should return an error', ->
             expect(@response.statusCode).to.equal 404
@@ -109,31 +109,32 @@

         before (done) ->
             measure = fixtureMeasure.measureMovement2
+            measure.sensorId = store.sensor.id
             @client.post "sensors/#{measure.sensorId}/measures", measure, done

         before (done) ->
             measure = fixtureMeasure.measureMovement3
+            measure.sensorId = store.sensor.id
             @client.post "sensors/#{measure.sensorId}/measures", measure, done

         it 'should allow requests', (done) ->
-            @client.get "sensors/#{store.rule.id}/measures?from=#{interval.minTime.toISOString()};to=#{interval.maxTime.toISOString()}", done
+            @client.get "sensors/#{store.sensor.id}/measures?from=#{interval.minTime.toISOString()};to=#{interval.maxTime.toISOString()}", done

         it 'should reply with the corresponding measures', ->
             expect(@err).to.not.exist
             expect(@response.statusCode).to.equal 200
             expect(@body).to.have.length(2)
-            expect(@body[0].type).to.equal fixtureMeasure.measureMovement1.type
-            expect(@body[0].value).to.equal fixtureMeasure.measureMovement1.value
-            expect(@body[0].time).to.equal fixtureMeasure.measureMovement1.time
-            expect(@body[0].id).to.equal store.measureId
-            expect(@body[1].type).to.equal fixtureMeasure.measureMovement1.type
-            expect(@body[1].value).to.equal fixtureMeasure.measureMovement1.value
-            expect(@body[1].time).to.equal fixtureMeasure.measureMovement1.time
+            expect(@body[0].type).to.equal fixtureMeasure.measureMovement2.type
+            expect(@body[0].value).to.equal fixtureMeasure.measureMovement2.value
+            expect(@body[0].time).to.equal fixtureMeasure.measureMovement2.time.toISOString()
+            expect(@body[1].type).to.equal fixtureMeasure.measureMovement3.type
+            expect(@body[1].value).to.equal fixtureMeasure.measureMovement3.value
+            expect(@body[1].time).to.equal fixtureMeasure.measureMovement3.time.toISOString()

     describe 'When we update a measure (PUT /sensors/:sensorId/measures/:id)', ->

         it 'should allow requests', (done) ->
-            @client.put "sensors/#{store.rule.id}/measures/#{store.measureId}", fixtureMeasure.updateMeasure, done
+            @client.put "sensors/#{store.sensor.id}/measures/#{store.measureId}", fixtureMeasure.updateMeasure, done

         it 'should return an unauthorized error', ->
             expect(@err).to.not.exist
@@ -144,7 +145,7 @@
     describe 'When we delete a Measure (DELETE /sensors/:sensorId/measures/:id)', ->

         it 'should allow requests', (done) ->
-            @client.del "sensors/#{store.rule.id}/measures/#{store.measureId}", done
+            @client.del "sensors/#{store.sensor.id}/measures/#{store.measureId}", done

         it 'should return a "success"', ->
             expect(@err).to.not.exist
@@ -152,7 +153,7 @@
             expect(@body.success).to.equal true

         it 'should allow requests to try getting the deleted measure (GET /rule/:sensorId/measures/:id), but...', (done) ->
-            @client.get "sensors/#{store.rule.id}/measures/#{store.measureId}", done
+            @client.get "sensors/#{store.sensor.id}/measures/#{store.measureId}", done

         it 'should return an error', ->
             expect(@response.statusCode).to.equal 404


allow additional triggers to be added via enironment variable

--- src/businesscat.coffee
+++ src/businesscat.coffee
@@ -5,10 +5,10 @@
 #   None
 #
 # Configuration:
-#   None
+#   HUBOT_BUSINESS_CAT_JARGON comma-separated list of additional "tiggers"
 #
 # Commands:
-#   business jargon - summons business cat
+#   Business jargon - summons business cat
 #
 # Notes:
 #   See jargon array for list of trigger phrases
@@ -18,6 +18,11 @@

 images = require './data/images.json'
 jargon = require './data/triggers.json'
+
+if process.env.HUBOT_BUSINESS_CAT_JARGON?
+  additionalJargon = (process.env.HUBOT_BUSINESS_CAT_JARGON).split(',')
+  jargon = jargon.concat(additionalJargon)
+
 regex = new RegExp jargon.join('|'), 'gi'

 module.exports = (robot) ->


video/publisher: add a possibility to change videodata

--- client/app/lib/videocollaboration/types/participant.coffee
+++ client/app/lib/videocollaboration/types/participant.coffee
@@ -68,6 +68,13 @@
     @type = 'publisher'

+  setVideoData: (videoData) ->
+
+    helper.unsubscribeFromAudioChanges @videoData
+    @videoData = videoData
+    @subscribeToAudioChanges()
+
+
 ###*
  * Transformation from OpenTok Subscriber -> Koding ChatVideoSubscriber
  *


Small updates

Small updates

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -253,7 +253,7 @@
   fetchDiskUsage:(vmName, callback = noop)->
     command = """df | grep aufs | awk '{print $2, $3}'"""
     @run { vmName, withArgs:command }, (err, res)->
-      if err or not res then [max, current] = [100, 10]
+      if err or not res then [max, current] = [0, 0]
       else [max, current] = res.trim().split " "
       warn err  if err
       callback
@@ -262,7 +262,7 @@

   fetchRamUsage:(vmName, callback = noop)->
     @info vmName, (err, vm, info)->
-      if err or info.state isnt "RUNNING" then [max, current] = [100, 3]
+      if err or info.state isnt "RUNNING" then [max, current] = [0, 0]
       else [max, current] = [info.totalMemoryLimit, info.memoryUsage]
       warn err  if err
       callback {max, current}


full reset breaks something

full reset breaks something

--- chrome/content/zotero-better-bibtex/cache.coffee
+++ chrome/content/zotero-better-bibtex/cache.coffee
@@ -15,7 +15,11 @@
     #  @cache.on('delete', (entry) -> Zotero.BetterBibTeX.debug('cache.loki delete', entry))

     @log = Zotero.BetterBibTeX.log
-    @reset()
+    @stats = {
+      hit: 0
+      miss: 0
+      clear: 0
+    }

   integer: (v) ->
     return v if typeof v == 'number'


Environments: Added extra class name for tests.

--- client/Main/providers/machinesettingsview.coffee
+++ client/Main/providers/machinesettingsview.coffee
@@ -21,6 +21,7 @@
         accessUri       :
           label         : "Assigned URL"
           itemClass     : CustomLinkView
+          cssClass      : 'assigned-url'
           title         : data.domain
           href          : accessUri
           target        : '_blank'


Link to standard objects

Link to standard objects

--- src/util/referencer.coffee
+++ src/util/referencer.coffee
@@ -484,7 +484,7 @@
             # controls external reference links
             if @verifyExternalObjReference(see.reference)
               see.label = see.reference unless see.label
-              see.reference = undefined
+              see.reference = @standardObjs[see.reference]
             else
               see.label = see.reference
               see.reference = undefined


Bind functions to the current value of this

--- flow.coffee
+++ flow.coffee
@@ -7,48 +7,48 @@
     @list = $(listSelector)
     @slide = @list.find(@options.slidesSelector).eq(0)

-  show: (slide) ->
+  show: (slide) =>
     if $.isNumeric(slide)
       @_selectSlideByIndex(slide)
     else
       @_selectSlideBySelector(slide)
     @list.find(@options.slidesSelector).hide().filter(@slide).show()

-  next: ->
+  next: =>
     slide = if @slide.next().index() is -1 then 0 else @slide.next()
     @show(slide)

-  prev: ->
+  prev: =>
     slide = if @slide.prev().index() is -1 then @_size() - 1 else @slide.prev()
     @show(slide)

-  first: ->
+  first: =>
     @show(0)

-  last: ->
+  last: =>
     @show(@_size() - 1)

-  prepend: (slideHtml) ->
+  prepend: (slideHtml) =>
     @list.prepend(slideHtml)
     @show(@slide)

-  append: (slideHtml) ->
+  append: (slideHtml) =>
     @list.append(slideHtml)
     @show(@slide)

-  insert: (position, slideHtml) ->
+  insert: (position, slideHtml) =>
     if position is 0
       @list.find(@options.slidesSelector).eq(position).before(slideHtml)
     else
       @list.find(@options.slidesSelector).eq(position - 1).after(slideHtml)
     @show(@slide)

-  replace: (position, slideHtml) ->
+  replace: (position, slideHtml) =>
     slide = if position is @slide.index() then position else @slide
     @list.find(@options.slidesSelector).eq(position).before(slideHtml).remove()
     @show(slide)

-  delete: (position) ->
+  delete: (position) =>
     if position is @slide.index()
       @next()
     @list.find(@options.slidesSelector).eq(position).remove()


Fix prev function

Fix prev function

--- flow.coffee
+++ flow.coffee
@@ -19,7 +19,7 @@
     @show(slide)

   prev: ->
-    slide = if @slide.prev().index() is -1 then @size() - 1 else @slide.prev()
+    slide = if @slide.prev().index() is -1 then @_size() - 1 else @slide.prev()
     @show(slide)

   first: ->


url: Use minimatch for the branch part of the expression

--- src/lib/resolvers/url.coffee
+++ src/lib/resolvers/url.coffee
@@ -2,8 +2,8 @@

 async = require 'async'
 glob = require 'glob'
-tilde = require 'expand-tilde'
 path = require 'path'
+minimatch = require 'minimatch'

 utils = require '../utils'
 logger = require '../logger'
@@ -13,14 +13,14 @@
     parts = expression.split ':'

     if parts.length < 3
-      secondPart = null
+      secondPart = 'master'
       firstPart = expression
     else
       secondPart = parts.pop()
       firstPart = parts.join ':'

     @url = firstPart
-    @branch = if secondPart then new RegExp secondPart else /master/
+    @branch = minimatch.makeRe secondPart

     logger.debug "URL: #{@url}, branch #{@branch.source}"


use Content-Type: application/json instead of Content-Type: application/x-www-form-urlencoded

--- client/landing/app/MainApp/account-mixins.coffee
+++ client/landing/app/MainApp/account-mixins.coffee
@@ -4,22 +4,6 @@
     {JAccount, JGuest} = api

     JGuest::fetchNonce = ->
-
-    nonces = []
-
-    fetchNonces = (callback)->
-      KD.whoami().fetchNonces (err, moreNonces)->
-        nonces = nonces.concat moreNonces
-        callback nonces
-
-    fetchNonce = (callback)->
-      nonce = nonces.shift()
-      if nonce?
-        callback nonce
-      else
-        fetchNonces -> fetchNonce callback
-
-    JAccount::fetchNonce = fetchNonce

     nonces = []

@@ -57,16 +41,18 @@
       remoteStore = new Store

       sendScrubbedCommand =(url, options)->
+        log 'SENDING:', url, options
         fetchNonce (nonce)->
           data = JSON.stringify(options)
           $.ajax
-            url     : url
-            data    :
-              data  : data
-              env   : KD.env
-              n     : nonce
-            type    : 'POST'
-            xhrFields:
+            url       : url
+            data      :
+              data    : data
+              env     : KD.env
+              n       : nonce
+            type      : 'POST'
+            success   : log
+            xhrFields :
               withCredentials: yes

       getKiteUri =(kiteName)->


Remove console.log

Remove console.log

--- app/pages/lab/project.cjsx
+++ app/pages/lab/project.cjsx
@@ -51,7 +51,6 @@
     "/lab/#{@props.project.id}#{postFix}"

   render: ->
-    console.log @props.project
     linkParams =
       projectID: @props.project.id


Add in wiring for unflag action

Add in wiring for unflag action

--- lms/static/coffee/src/open_ended/open_ended.coffee
+++ lms/static/coffee/src/open_ended/open_ended.coffee
@@ -20,7 +20,12 @@

   unflag: (event) =>
     event.preventDefault()
-    @gentle_alert "Unflag"
+    parent_tr = $(event.target).parent().parent()
+    tr_children = parent_tr.children()
+    action_type = "unflag"
+    submission_id = tr_children[4].innerText
+    student_id = tr_children[5].innerText
+    @post('take_action_on_flags', {'submission_id' : submission_id, 'student_id' : student_id, 'action_type' : action_type}, @handle_after_action)

   ban: (event) =>
     event.preventDefault()


Added error for improper command-line usage.

--- index.coffee
+++ index.coffee
@@ -190,6 +190,11 @@
     yargs.demand 1
     {argv} = yargs

+    if argv._.length is 0
+      throw new Error '''
+        A filename must be provided as the first command-line argument.
+      '''
+
     configurationFileName = _.first argv._
     winston.debug 'Loading ' + chalk.white configurationFileName


Gzip true to prevent socket hangups

Gzip true to prevent socket hangups

--- index.coffee
+++ index.coffee
@@ -8,6 +8,7 @@
 	'user-agent': '404check.io (http://404check.io)'
 agentOptions =
 	keepAlive: true
+	gzip: true
 request = request.defaults {headers, agentOptions, timeout: 60000}

 class SitemapParser


Use an environment variable instead of creating the token ourselves.

--- index.coffee
+++ index.coffee
@@ -5,8 +5,6 @@
 module.exports = (Impromptu, register, github) ->
   git = @module.require 'impromptu-git'
   rGitHubUrl = /^(?:git@github.com:|https:\/\/github.com\/)([^\/]+)\/([^\/]+)\.git/
-
-  class ImpromptuGitHubError extends Impromptu.Error

   register '_parseRemoteUrl',
     update: (done) ->
@@ -36,44 +34,8 @@
         done err, results?.repo

   register 'token',
-    cache: 'global'
-    run: (fn) ->
-      @get (err, token) =>
-        return fn err, token if token
-        @_setThenGet fn
-
     update: (done) ->
-      async.waterfall [
-        (fn) ->
-          read prompt: 'What is your GitHub username?', (err, username) ->
-            fn err, username
-
-        (username, fn) ->
-          read prompt: 'What is your GitHub password?', silent: true, (err, password) ->
-            fn err, username, password
-
-        (username, password, fn) ->
-          request.post
-            url: 'https://api.github.com/authorizations'
-            auth:
-              user: username
-              pass: password
-            body:
-              scopes: ['repo']
-              note: "Impromptu GitHub Module"
-            json: true
-          , (err, response, body) ->
-            return fn err, null if err
-            return fn err, body.token if body.token
-
-            if body.message
-              message = "An error occurred while creating your GitHub token: #{body.message}"
-            else
-              message = "An unknown error occurred while creating your GitHub token."
-
-            fn new ImpromptuGitHubError message, null
-
-      ], done
+      process.env.IMPROMPTU_GITHUB_TOKEN

   requestGitHub = (path, options, done) ->
     unless done


class refactor - corrected callback

class refactor - corrected callback

--- index.coffee
+++ index.coffee
@@ -10,41 +10,42 @@

 request = require 'superagent'

-getLink = (phrase, err, resLink) ->

+
+
+
+makeLink = (phrase) ->
+  if phrase is null then console.log 'we need phrase'
+  else
+  getPhraseLink phrase
+
+
+
+
+
+getPhraseLink = (err, phrase, phraseLink) ->
   request
   .post 'http://www.acapela-group.com/demo-tts/DemoHTML5Form_V2.php'
-  .set 'Content-Type','application/x-www-form-urlencoded'
+  .set 'Content-Type', 'application/x-www-form-urlencoded'
   .send 'MyLanguages=sonid8'
   .send 'MySelectedVoice=Graham'
   .send 'MyTextForTTS=' + "#{phrase}".split(" ").join("+")
   .send 'SendToVaaS='
   .end (res) ->
-
     if err then console.error err
     else
-      resText = res.text
+    resText = res.text
+    foobar = extractValues resText, "myPhpVar = '{fileSource}';"
+
+    resLink = foobar
+
+    phraseLink = resLink
+    console.log phraseLink
+  if err then throw console.error err
+  else
+
+   phraseLink.toJSON()

-      foobar = extractValues resText, "myPhpVar = '{fileSource}';"
-      resLink = foobar
-      console.log JSON.stringify resLink
+exports.phraseLink = makeLink

-    if err then throw console.error err
-    else
-    resLink
-
-
-
-
-class Create
-  getTTS: (phrase, phraseLink) ->
-    console.log phrase
-    phraseLinkRes = getLink(phrase)
-
-    phraseLink = JSON.stringify(phraseLinkRes.fileSource)
-
-
-module.exports = Create
-
-


try to clear save state if issues.

try to clear save state if issues.

--- index.coffee
+++ index.coffee
@@ -106,6 +106,7 @@
   play rom, extension
   .catch (e) ->
     loading.classList.add 'hidden'
+    localForage.setItem retro.md5, new Uint8Array()
     console.error e
     alert "that file couldn't be loaded"
     location.search = ""


Added route to open correct backend.js instead of bundle.js

--- index.coffee
+++ index.coffee
@@ -71,7 +71,7 @@
 		.use bodyParser.urlencoded extended: false
 		.post __sendEmailUri, sendEmail
 		.all '*', (req, res) ->
-			viewPath = require('path').resolve './web/bundle.js'
+			viewPath = require('path').resolve './web/backend.js'
 			require(viewPath)(req, res)
 			# res.sendFile(__base + paths.root.dest + 'index.html')
 		.listen Number(__port), ->


Applying dataset change to coffeescript

Applying dataset change to coffeescript

Those issues were because I only committed the actual change to the .js file before, which was getting blown away

--- index.coffee
+++ index.coffee
@@ -12,7 +12,7 @@
 	# the element
 	monitor = scrollMonitor.create el, offset binding.value
 	id = 'i' + counter++
-	el.dataset.inViewport = id
+	el.setAttribute 'data-in-viewport', id
 	monitors[id] = monitor

 	# Start listenting for changes
@@ -61,7 +61,7 @@

 # Remove scrollMonitor listeners
 removeListeners = (el) ->
-	id = el.dataset.inViewport
+	id = el.getAttribute 'data-in-viewport'
 	if monitor = monitors[id]
 		monitor.destroy()
 		delete monitors[id]


switch to search="" for linked roms.

switch to search="" for linked roms.

--- index.coffee
+++ index.coffee
@@ -96,7 +96,7 @@
     loading.classList.add 'hidden'
     console.error e
     alert "that file couldn't be loaded"
-    location.reload() # hacky but a fix
+    location.search = ""

 load = (file) ->
   return if not file instanceof Blob


FIX: Remove step 0 from portfolio preparation step

--- src/app/projects/project-portfolio-wizard/project-portfolio-wizard.coffee
+++ src/app/projects/project-portfolio-wizard/project-portfolio-wizard.coffee
@@ -23,37 +23,37 @@
         title: "Portfolio Preparation"
         icon: "fa-magic"
         subtitle: "Welcome to the portfolio preparation wizard"
-        seq: 0
+        seq: 1
       gradeStep:
         title: "Select Grade"
         icon: "fa-trophy"
         subtitle: "In preparing your portfolio, you need to undertake a self assessment. Use the unit's assessment criteria to determine the grade your portfolio should be awarded"
-        seq: 1
+        seq: 2
       summaryStep:
         title: "Learning Summary Report"
         icon: "fa-graduation-cap"
         subtitle: "Upload the Learning Summary Report, the primary porfolio document which justifies your desired grade"
-        seq: 2
+        seq: 3
       taskStep:
         title: "Align and Select Tasks"
         icon: "fa-tasks"
         subtitle: "Select tasks to include and showcase in your portfolio that demonstrates your understanding of each Indended Learning Outcome"
-        seq: 3
+        seq: 4
       otherFilesStep:
         title: "Upload Other Files"
         icon: "fa-upload"
         subtitle: "Add extra files that justify your learning to your portfolio"
-        seq: 4
+        seq: 5
       compileStep:
         title: "Compile Portfolio PDF"
         icon: "fa-file-pdf-o"
         subtitle: "Submit your portfolio for compilation"
-        seq: 5
+        seq: 6
       reviewStep:
         title: "Review Portfolio"
         icon: "fa-book"
         subtitle: "Review your portfolio submission progress"
-        seq: 6
+        seq: 7
     $scope.setActivePortfolioTab = (tab) ->
       $scope.activePortfolioTab = tab
       analyticsService.event 'Portfolio Wizard', 'Switched to Step', "#{tab.title} Step"
@@ -65,7 +65,7 @@
       $scope.activePortfolioTab = (tab for tabKey, tab of $scope.portfolioTabsData when tab.seq is newSeq)[0]
     $scope.setActivePortfolioTab $scope.portfolioTabsData.welcomeStep

-    $scope.projectHasLearningSummaryReport = () ->
+    $scope.projectHasLearningSummaryReport = ->
       _.filter($scope.project.portfolio_files, { idx: 0 }).length > 0

     $scope.portfolioSubmission = PortfolioSubmission($scope.project)


FIX: Ensure alert service singleton is returned from factory

--- src/app/common/services/alert-service.coffee
+++ src/app/common/services/alert-service.coffee
@@ -47,4 +47,6 @@
   #
   alertService.clearAll = ->
     $rootScope.alerts = []
+
+  alertService
 )


Fix quantitative color scale

Fix quantitative color scale

Also add a formatNumber function

--- app/frontend/javascripts/views/visualization-legend.js.coffee
+++ app/frontend/javascripts/views/visualization-legend.js.coffee
@@ -30,16 +30,14 @@
       lg_size = scale_size.domain()[1]
       md_size = sm_size + ((lg_size - sm_size)*0.5)

-      legend_size.find('.visualization-graph-legend-lg .visualization-graph-legend-size-amount').html( if lg_size%1 != 0 then lg_size.toFixed(1) else lg_size )
-      legend_size.find('.visualization-graph-legend-md .visualization-graph-legend-size-amount').html( if md_size%1 != 0 then md_size.toFixed(1) else md_size )
-      legend_size.find('.visualization-graph-legend-sm .visualization-graph-legend-size-amount').html( if sm_size%1 != 0 then sm_size.toFixed(1) else sm_size )
+      legend_size.find('.visualization-graph-legend-lg .visualization-graph-legend-size-amount').html( @formatNumber(lg_size) )
+      legend_size.find('.visualization-graph-legend-md .visualization-graph-legend-size-amount').html( @formatNumber(md_size) )
+      legend_size.find('.visualization-graph-legend-sm .visualization-graph-legend-size-amount').html( @formatNumber(sm_size) )
     else
       @$el.find('.visualization-graph-legend-size').hide()

     # Setup color legend
     if ( @parameters.nodesColor == 'qualitative' or @parameters.nodesColor == 'quantitative' ) and scale_color.domain().length > 1
-
-      console.log @parameters.nodesColor, scale_color.domain()

       # Avoid quantitative scales with same values
       if scale_color.domain().length == 2 and scale_color.domain()[0] == scale_color.domain()[1]
@@ -57,14 +55,14 @@

       # Insert values inside domain for quantitative scales
       if @parameters.nodesColor == 'quantitative'
-        items = items.reverse()
-        steps = (items[1] - items[0]) / 5
-        max = items.pop()
-        items.push Math.round(items[0]+steps)
-        items.push Math.round(items[1]+steps)
-        items.push Math.round(items[2]+steps)
-        items.push Math.round(items[3]+steps)
-        items.push max
+        items = items.sort (a, b) -> return b - a
+        steps = (items[0] - items[1]) / 5
+        min = items.pop()
+        items.push @formatNumber(items[0]-steps)
+        items.push @formatNumber(items[1]-steps)
+        items.push @formatNumber(items[2]-steps)
+        items.push @formatNumber(items[3]-steps)
+        items.push min

       # loop through all colors
       items.forEach (item, i) =>
@@ -80,6 +78,10 @@
       @$el.find('.visualization-graph-legend-color').hide()

+  formatNumber: (number) ->
+    number = parseFloat(number)
+    return if number%1 != 0 then number.toFixed(1) else number
+
   String.prototype.capitalize = () ->
     return this.charAt(0).toUpperCase() + this.slice(1)


Disable refunds for now..

Disable refunds for now..

--- node_modules_koding/koding-payment/lib/main.coffee
+++ node_modules_koding/koding-payment/lib/main.coffee
@@ -381,7 +381,7 @@
       callback no, subs

 terminateUserSubscription = (accountCode, data, callback) ->
-  recurlyPut "subscriptions/#{data.uuid}/terminate?refund=partial", "", (err, res) ->
+  recurlyPut "subscriptions/#{data.uuid}/terminate?refund=none", "", (err, res) ->
     if err
       callback err, {}
     else


Added logic in Setter.defaults() to prevent returning an undefined object.

--- src/Setter.coffee
+++ src/Setter.coffee
@@ -1,4 +1,5 @@
 deep = (value, other) -> lodash.merge(value, other, deep)
+lodashDefaults = lodash.partialRight(lodash.merge, deep)

 Setter =

@@ -36,7 +37,10 @@
   # @returns {Object} The object argument. If defaults is defined it is deeply merged.
   # NOTE: Relying on _.merge(A,B) returning A if it isn't an object to prevent
   # source property values overriding their defined destinations.
-  defaults: lodash.partialRight(lodash.merge, deep)
+  defaults: (obj, defaults) ->
+    # Ensure the object is defined and default to an empty object to prevent returning undefined.
+    obj ?= {}
+    lodashDefaults.call(lodash, obj, defaults)

   # @param {*} value
   # @returns Whether the given value is not an non-empty string, null, undefined or NaN.


No need to send seperate message

No need to send seperate message

--- node_modules_koding/processes/src/processes.coffee
+++ node_modules_koding/processes/src/processes.coffee
@@ -20,11 +20,25 @@
 catch e
   # do nothing, probably dir is already there.

+################################
+### HANDLE SIGNALS and KILLS ###
+################################
+
+# when doing ctrl+c
+process.on 'SIGINT', =>
+  console.log "\nkilled via ctrl+c. this should not happen"
+  process.exit(0)
+
+# guaranteed death.
+process.on 'SIGTERM', =>
+  console.log "going down"
+  process.exit(0)
+
 class Processes extends EventEmitter
   constructor:(options={})->
-    {main}        = options
-    @list         = {}
-    @child        = null
+    {main}         = options
+    @list          = {}
+    @child         = null
     @workerProcess = {}
     @childProcessesContainerDir = __dirname+"/.tmp"
     if main
@@ -36,6 +50,7 @@
           console.log "couldn't delete previous process tree at #{previouschildProcessesContainerDir}"
         #else
         #  console.log "previous process tree is deleted."
+
   fork: (options,callback) ->
     options.fork = yes
     @run options,callback, (cb) =>
@@ -54,11 +69,13 @@
       stderr : process.stderr
       onExit  : callback

+  # our main function is this. Either it is started normal, or it is wrapped
+  # via kontrold amqp process
   run: (options,callback) ->
-    {name,restartTimeout,verbose} = options
+    {name, restartTimeout, verbose} = options

     options.name            ?= Date.now()
-    options.restartTimeout ?= 1000
+    options.restartTimeout  ?= 1000
     options.verbose         ?= no

     if options.kontrol?.enabled
@@ -185,29 +202,6 @@
           workerExchange.publish "input.worker", {worker: @workerProcess[name].worker},
             appId: "#{@workerProcess[name].worker.uuid}"

-    ################################
-    ### HANDLE SIGNALS and KILLS ###
-    ################################
-
-    # When doing ctrl+c, let kontrold now we are dead
-    process.on 'SIGINT', =>
-      @workerProcess[name].worker.status = 5
-      @workerProcess[name].fetchWorkerExchange (workerExchange) =>
-        @workerProcess[name].worker.message.command = "ack"
-        workerExchange.publish "input.worker", {worker: @workerProcess[name].worker},
-          appId: "#{@workerProcess[name].worker.uuid}"
-        process.exit(0)
-
-    # When sending SIGTERM (aka kill -15, killall, ...), let kontrold know we are death
-    process.on 'SIGTERM', =>
-      console.log "going down"
-      @workerProcess[name].worker.status = 5
-      @workerProcess[name].fetchWorkerExchange (workerExchange) =>
-        @workerProcess[name].worker.message.command = "ack"
-        workerExchange.publish "input.worker", {worker: @workerProcess[name].worker},
-          appId: "#{@workerProcess[name].worker.uuid}"
-        process.exit(0)
-
   populateChildObject: (options, @child) ->
     {name,onMessage,modulePath,args,restart,restartTimeout,cmd,log,onExit,stdout,stderr,verbose,die} = options
     onExit ?= ->
@@ -238,7 +232,6 @@
         console.log "[PROCESSES][#{name}]it's time you die process:#{name}" if verbose
         @kill name
       ,die.after
-

     @child.on 'exit', ()=>
       console.log "[PROCESSES][#{name}] did exit." if verbose


Fix Mac Ctrl+P code editing behavior

Fix Mac Ctrl+P code editing behavior

--- ozaria/site/views/play/level/tome/SpellView.coffee
+++ ozaria/site/views/play/level/tome/SpellView.coffee
@@ -163,8 +163,13 @@
       bindKey: {win: 'Ctrl-S', mac: 'Command-S|Ctrl-S'}
       exec: ->  # just prevent page save call
     addCommand
+      name: 'previous-line'
+      bindKey: {mac: 'Ctrl-P'}
+      passEvent: true
+      exec: => @ace.execCommand 'golineup'  # stop trying to jump to matching paren, I want default Mac/Emacs previous line
+    addCommand
       name: 'toggle-playing'
-      bindKey: {win: 'Ctrl-P', mac: 'Command-P|Ctrl-P'}
+      bindKey: {win: 'Ctrl-P', mac: 'Command-P'}
       readOnly: true
       exec: -> Backbone.Mediator.publish 'level:toggle-playing', {}
     addCommand


JSON Schema format is redundant for now - will be re-introduced in the future.

--- lib/formats/jsonschema.coffee
+++ lib/formats/jsonschema.coffee
@@ -1,25 +0,0 @@
-
-
-module.exports =
-  representObject: (properties) ->
-    joined = properties.join ','
-    "{#{joined}}"
-
-  representObjectProperty: (name, value) ->
-    "\"#{name}\": #{value}"
-
-  representArray: (elements) ->
-    joined = elements.join ','
-    "[#{joined}]"
-
-  representString: (value) ->
-    "\"#{value}\""
-
-  representNumber: (value) ->
-    value
-
-  representBool: (value) ->
-    value.toLowerCase()
-
-  representNull: ->
-    "null"


fix(vm.set): behave if the resource set no longer exist

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -476,11 +476,15 @@
   VM = extract(params, 'VM')
   xapi = @getXapi(VM)

-  return xapi.editVm(VM._xapiId, params, (limits, vm) =>
+  return xapi.editVm(VM._xapiId, params, $coroutine (limits, vm) =>
     resourceSet = xapi.xo.getData(vm, 'resourceSet')

     if (resourceSet)
-      return @allocateLimitsInResourceSet(limits, resourceSet)
+      try
+        return yield @allocateLimitsInResourceSet(limits, resourceSet)
+      catch error
+        # if the resource set no longer exist, behave as if the VM is free
+        throw error unless noSuchObject.is(error)

     if (limits.cpuWeight && this.user.permission != 'admin')
       throw unauthorized()


fix(vm.createInterface): accept integers for position and mtu

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -1069,8 +1069,8 @@
 createInterface.params = {
   vm: { type: 'string' }
   network: { type: 'string' }
-  position: { type: 'string', optional: true }
-  mtu: { type: 'string', optional: true }
+  position: { type: ['integer', 'string'], optional: true }
+  mtu: { type: ['integer', 'string', optional: true }
   mac: { type: 'string', optional: true }
 }


fix(vm.set): works with VM templates

fix(vm.set): works with VM templates

Fixes vatesfr/xo-web#1569

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -465,7 +465,7 @@
 }

 set.resolve = {
-  VM: ['id', ['VM', 'VM-snapshot'], 'administrate']
+  VM: ['id', ['VM', 'VM-snapshot', 'VM-template'], 'administrate']
 }

 exports.set = set


change default value of  setting

change default value of  setting

--- lib/minimap-codeglance.coffee
+++ lib/minimap-codeglance.coffee
@@ -15,7 +15,7 @@
       order: 0
     numberOfLines:
       type: 'integer'
-      default: 6
+      default: 5
       minimum: 1
       order: 1
     showLineNumbers:


cleanup

cleanup

--- lib/move-selected-text.coffee
+++ lib/move-selected-text.coffee
@@ -74,6 +74,12 @@
     else
       true

+  getSelections: ->
+    selections = @editor.getSelectionsOrderedByBufferPosition()
+    if @direction is 'down'
+      selections.reverse()
+    selections
+
   execute: ->
     wise = @getWise()
     selections = @editor.getSelections()
@@ -84,14 +90,14 @@
         linewiseDisposable = switchToLinewise(@editor)

       @countTimes @getCount(), =>
-        for selection in selections
+        for selection in @getSelections()
           switch wise
             when 'linewise'
               @moveLinewise(selection) if @canMove(selection, wise)
             when 'characterwise'
               @moveCharacterwise(selection) if @canMove(selection, wise)
             else
-              console.log "NOT YET"
+              @moveCharacterwise(selection) if @canMove(selection, wise)

       linewiseDisposable?.dispose()

@@ -119,15 +125,14 @@

   moveLinewise: (selection) ->
     reversed = selection.isReversed()
-
     translation = switch @direction
-      when 'up' then {before: [[-1, 0], [0, 0]], after: [[0, 0], [-1, 0]]}
-      when 'down' then {before: [[0, 0], [1, 0]], after: [[1, 0], [0, 0]]}
-
-    rangeToMutate = selection.getBufferRange().translate(translation.before...)
+      when 'up' then [[-1, 0], [0, 0]]
+      when 'down' then [[0, 0], [1, 0]]
+
+    rangeToMutate = selection.getBufferRange().translate(translation...)
     extendLastBufferRowToRow(@editor, rangeToMutate.end.row)
     selection.setBufferRange(rangeToMutate)
-    rangeToSelect = @rotateSelectedRows(selection).translate(translation.after...)
+    rangeToSelect = @rotateSelectedRows(selection).translate(translation.reverse()...)
     selection.setBufferRange(rangeToSelect, {reversed})

   rotateSelectedRows: (selection) ->
@@ -166,15 +171,14 @@

   moveCharacterwise: (selection) ->
     reversed = selection.isReversed()
-
     translation = switch @direction
-      when 'right' then {before: [[0, 0], [0, 1]], after: [[0, 1], [0, 0]]}
-      when 'left' then {before: [[0, -1], [0, 0]], after: [[0, 0], [0, -1]]}
-
-    rangeToMutate = selection.getBufferRange().translate(translation.before...)
+      when 'right' then [[0, 0], [0, 1]]
+      when 'left' then [[0, -1], [0, 0]]
+
+    rangeToMutate = selection.getBufferRange().translate(translation...)
     insertSpacesToPoint(@editor, rangeToMutate.end)
     selection.setBufferRange(rangeToMutate)
-    rangeToSelect = @rotateChars(selection).translate(translation.after...)
+    rangeToSelect = @rotateChars(selection).translate(translation.reverse()...)
     selection.setBufferRange(rangeToSelect, {reversed})

   rotateChars: (selection) ->


Ensure package names correctly id'ed on Win

--- lib/notification-issue.coffee
+++ lib/notification-issue.coffee
@@ -69,7 +69,6 @@
   getIssueBody: ->
     new Promise (resolve, reject) =>
       return resolve(@issueBody) if @issueBody
-
       systemPromise = UserUtilities.getOSVersion()
       installedPackagesPromise = UserUtilities.getInstalledPackages()

@@ -195,11 +194,13 @@
       if match = FileURLRegExp.exec(filePath)
         filePath = match[1]

+      filePath = path.normalize(filePath)
+
       if path.isAbsolute(filePath)
         for packName, packagePath of packagePaths
           continue if filePath is 'node.js'
-          relativePath = path.relative(packagePath, filePath)
-          return packName unless /^\.\./.test(relativePath)
+          isSubfolder = filePath.indexOf(path.normalize(packagePath + path.sep)) is 0
+          return packName if isSubfolder
       @getPackageNameFromFilePath(filePath)

     if options.detail? and packageName = getPackageName(options.detail)


hide errors

hide errors

--- lib/runtime/evaluation.coffee
+++ lib/runtime/evaluation.coffee
@@ -55,7 +55,8 @@
   gotoSymbol: ->
     @withCurrentContext ({editor, mod}) =>
       words.withWord editor, (word, range) =>
-        @ink.goto.goto client.import("methods")({word: word, mod: mod})
+        client.import("methods")({word: word, mod: mod}).then (symbols) =>
+          @ink.goto.goto symbols unless symbols.error

   toggleDocs: ->
     @withCurrentContext ({editor, mod}) =>


[grunt] better order of Nunjucks' extensions, globals and filters

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -63,44 +63,9 @@
         autoescape: false
         data:  '<%= data %>'
         configureEnvironment: (env) ->
-          ###*
-           * Append string with locale name based on whether it's base locale or not
-           * Most useful for urls construction
-           * @param  {string} string                 Target string
-           * @param  {string} locale = currentLocale Locale name, for which resolving should be made
-           * @return {string} String with resolved locale in front
-          ###
-          env.addFilter 'resolveUrl', (string, locale = currentLocale) ->
-            return (if resolveLocaleDir(locale) then '/' + resolveLocaleDir(locale) else '') + string
-
-          ###*
-           * Output or not locale name based on whether it's base locale or not.
-           * @param  {string} locale Name of locale, for which should be made resolving
-           * @return {string} Resolved value
-          ###
-          env.addFilter 'resolveLocaleDir', (locale) ->
-            if resolveLocaleDir(locale) then '/' + resolveLocaleDir(locale) else ''
-
-          ###*
-          * Load string from current locale
-          * @note So far `sprintf` support only named placeholders
-          * @param  {string} string  String, which should be loaded
-          * @param  {object} ph = {} Values, which will be inserted instead of placeholders
-          * @return {string} Translated string into current locale
-          ###
-          env.addGlobal '_t', (string, ph = {}) ->
-            sprintf(i18n.dgettext(currentLocale, string), ph)
-
-          ###*
-           * Load string from specified locale
-           * @note So far `sprintf` support only named placeholders
-           * @param  {string} locale = currentLocale Locale, from which string should be loaded
-           * @param  {string} string                 String, which should be loaded
-           * @param  {object} ph     = {}            Values, which will be inserted instead of placeholders
-           * @return {string} Translated string into specified locale
-          ###
-          env.addGlobal '_dt', (locale = currentLocale, string, ph = {}) ->
-            sprintf(i18n.dgettext(locale, string), ph)
+          # ==========
+          # Extensions
+          # ==========

           ###*
            * Nunjucks extension for Markdown support
@@ -108,10 +73,36 @@
           ###
           markdown.register(env, marked)

+          # =======
+          # Globals
+          # =======
+
           ###*
            * Pass lodash inside Nunjucks
           ###
           env.addGlobal '_', _
+
+          ###*
+           * Log specified to Grunt's console for debug purposes
+           * @param {*} variable Anything we want to log to console
+           * @return {string} Logs to Grunt console
+          ###
+          env.addGlobal 'log', (variable) ->
+            console.log(variable)
+
+          ###*
+           * Get list of files or directories inside specified directory
+           * @param {string}               path    = ''             Path where to look
+           * @param {string|array[string]} pattern = '** /*'         What should be matched
+           * @param {string}               filter  = 'isFile'       Type of entity which should be matched
+           * @param {string}               cwd     = buildDir + '/' Root for lookup
+           * @return {array} Array of found files or directories
+          ###
+          env.addGlobal 'fileExpand', (path = '', pattern = '**/*', filter = 'isFile', cwd = buildDir + '/') ->
+            files   = []
+            grunt.file.expand({ cwd: cwd + path, filter: filter }, pattern).forEach (file) ->
+              files.push(file)
+            files

           ###*
            * Get information about page from specified object.
@@ -134,12 +125,29 @@
             if result then result else grunt.log.error('[getPage] can\'t find requested `' + subbedPath + '` inside specified object')

           ###*
-           * Log specified to Grunt's console for debug purposes
-           * @param {*} variable Anything we want to log to console
-           * @return {string} Logs to Grunt console
-          ###
-          env.addGlobal 'log', (variable) ->
-            console.log(variable)
+          * Load string from current locale
+          * @note So far `sprintf` support only named placeholders
+          * @param  {string} string  String, which should be loaded
+          * @param  {object} ph = {} Values, which will be inserted instead of placeholders
+          * @return {string} Translated string into current locale
+          ###
+          env.addGlobal '_t', (string, ph = {}) ->
+            sprintf(i18n.dgettext(currentLocale, string), ph)
+
+          ###*
+           * Load string from specified locale
+           * @note So far `sprintf` support only named placeholders
+           * @param  {string} locale = currentLocale Locale, from which string should be loaded
+           * @param  {string} string                 String, which should be loaded
+           * @param  {object} ph     = {}            Values, which will be inserted instead of placeholders
+           * @return {string} Translated string into specified locale
+          ###
+          env.addGlobal '_dt', (locale = currentLocale, string, ph = {}) ->
+            sprintf(i18n.dgettext(locale, string), ph)
+
+          # =======
+          # Filters
+          # =======

           ###*
            * Replaces last array element with new value
@@ -165,18 +173,22 @@
             array

           ###*
-           * Get list of files or directories inside specified directory
-           * @param {string}               path    = ''             Path where to look
-           * @param {string|array[string]} pattern = '** /*'         What should be matched
-           * @param {string}               filter  = 'isFile'       Type of entity which should be matched
-           * @param {string}               cwd     = buildDir + '/' Root for lookup
-           * @return {array} Array of found files or directories
-          ###
-          env.addGlobal 'fileExpand', (path = '', pattern = '**/*', filter = 'isFile', cwd = buildDir + '/') ->
-            files   = []
-            grunt.file.expand({ cwd: cwd + path, filter: filter }, pattern).forEach (file) ->
-              files.push(file)
-            files
+           * Append string with locale name based on whether it's base locale or not
+           * Most useful for urls construction
+           * @param  {string} string                 Target string
+           * @param  {string} locale = currentLocale Locale name, for which resolving should be made
+           * @return {string} String with resolved locale in front
+          ###
+          env.addFilter 'resolveUrl', (string, locale = currentLocale) ->
+            return (if resolveLocaleDir(locale) then '/' + resolveLocaleDir(locale) else '') + string
+
+          ###*
+           * Output or not locale name based on whether it's base locale or not.
+           * @param  {string} locale Name of locale, for which should be made resolving
+           * @return {string} Resolved value
+          ###
+          env.addFilter 'resolveLocaleDir', (locale) ->
+            if resolveLocaleDir(locale) then '/' + resolveLocaleDir(locale) else ''

           ###*
            * Replace placeholders with provided values


cooler formatting for the kernel errors

cooler formatting for the kernel errors

--- library/nucleus/kernel.coffee
+++ library/nucleus/kernel.coffee
@@ -211,14 +211,16 @@
     # errors. Depending on the instance configuration this method
     # could either crash the kernel on error or proceed operations.
     interceptExceptions: ->
+        assert u = moment().unix().toString()
         assert crash = "kernel:crashOnException"
+        assert skill = @shutdownKernel.bind this
         assert @domain = require("domain").create()
-        fatal = => @shutdownKernel "fatal kernel error"
+        fatal = => skill "Fatal kernel error at U=#{u}"
         str = (err) -> err.stack or err.message or err
-        assert bark = "kernel domain panic:\r\n%s".red
         @on "panic", (e) -> logger.error bark, str(e)
         @on "panic", (e) -> fatal() if nconf.get crash
         @domain.on "error", (err) => @emit "panic", err
+        bark = "Kernel domain panic at U=#{u}\r\n%s".red
         process.removeAllListeners "uncaughtException"
         process.on "uncaughtException", (error, arg) =>
             return no if str(error) is "socket end"


minor change

minor change

--- library/nucleus/scaled.coffee
+++ library/nucleus/scaled.coffee
@@ -166,8 +166,8 @@
     # passed to the factory, and if so - add it to the registry
     # of available services that are rotated using round-robin.
     makeRegistrar: (queue, kind) -> (service) =>
-        assert ids = @constructor.identica()
         config = Object https: kind is "https"
+        assert ids = try @constructor.identica()
         compile = (s) -> "#{s.role}@#{s.version}"
         return undefined unless service.kind is kind
         return undefined unless compile(service) is ids


Delay flushes performed after getting an op acknowledgement as well

--- services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee
+++ services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee
@@ -200,7 +200,7 @@
         callback null, oldInflightOp for callback in @inflightCallbacks

       # Send the next op.
-      @flush()
+      @delayedFlush()

     else if msg.op
       # We got a new op from the server.
@@ -247,7 +247,7 @@
   # Only one op can be in-flight at a time, so if an op is already on its way then
   # this method does nothing.
   flush: =>
-    delete @flushTimeout
+    @flushTimeout = null
     #console.log "CALLED FLUSH"

     return unless @connection.state == 'ok' and @inflightOp == null and @pendingOp != null
@@ -279,8 +279,9 @@

     @emit 'change', op

-    # A timeout is used so if the user sends multiple ops at the same time, they'll be composed
-    # & sent together.
+    @delayedFlush()
+
+  delayedFlush: () ->
     if !@flushTimeout?
         @flushTimeout = setTimeout @flush, @_flushDelay || 0


run lineman grunt spec-e2e for the next error to fix

--- lineman/tasks/spec-e2e.coffee
+++ lineman/tasks/spec-e2e.coffee
@@ -10,6 +10,6 @@

   grunt.registerTask "spec-e2e", "run specs in ci mode", (target) ->
     require('coffee-script')
-    process.argv = ["doesnt", "matter", "#{process.cwd()}/config/spec-e2e.js"]
+    process.argv = ["doesnt", "matter", "#{process.cwd()}/config/spec-e2e.coffee"]
     done = @async()
     require("#{process.cwd()}/node_modules/protractor/lib/cli")