Remove hacks from dashboard as they are incompatible with mongoose, make reports and indicators display

--- server/routes/dashboard.coffee
+++ server/routes/dashboard.coffee
@@ -1,53 +1,35 @@
 _ = require('underscore')
-moment = require('moment')
+async = require('async')

 Report = require('../models/report').model
+Indicator = require('../models/indicator').model

-# TODO: not completed.
-groupReportsByDate = (reports) ->
-  obj = {
-    lastWeek: []
-    yesterday: []
-    today: []
-  }
-  yesterday = ""  #TODO
-  today = ""      #TODO
-  lastWeek = ""   #TODO
-  _.each reports, (report) ->
-    d = report.createdAt
-    if d.isAfter(today) then obj.today.push report
-    if d.isAfter(yesterday) and d.isBefore(today) then obj.today.push report
-    if d.isAfter(lastWeek) and d.isBefore(yesterday)
-      obj.lastWeek.push report
-  obj
+exports.index = (req, res) ->
+  getReports = (callback) ->
+    Report.find((err, reports) ->
+      if err?
+        callback(err)

-# Transforms a `sequelize magic` object into a plain good old JavaScript
-# object, with a formatted date.
-format = (arr) ->
-  _.map arr, (obj) ->
-    formattedObj = {}
-    for attr, val of obj.selectedValues
-      if attr == "updatedAt" or attr == "createdAt"
-        formattedObj[attr] = moment(val).format("MMM Do YYYY")
-      else
-        formattedObj[attr] = val
+      callback(null, reports)
+    )

-    percent_complete = Math.floor(Math.random()*(100-40+1)+40)
-    formattedObj['percent_complete'] = percent_complete
+  getIndicators = (callback) ->
+    Indicator.find((err, indicators) ->
+      if err?
+        callback(err)

-    if percent_complete > 60
-      formattedObj['color'] = '#5DB16B'
-    else
-      formattedObj['color'] = '#FDBC56'
+      callback(null, indicators)
+    )

-    formattedObj
-
-exports.index = (req, res) ->
-  Report.find (err, reports) ->
+  async.parallel(
+    reports: getReports
+    indicators: getIndicators
+  , (err, items) ->
     if err?
       console.error error
       return res.render(500, "Error fetching the reports")

     res.render "dashboard",
-      reports: format(reports)
-      indicators: []
+      reports: items.reports
+      indicators: items.indicators
+  )


deprecated .live to .on in admin js, fixed ajax action buttons for admin

--- app/assets/javascripts/admin/index.js.coffee
+++ app/assets/javascripts/admin/index.js.coffee
@@ -6,75 +6,80 @@
     $("td .actions", this).hide()

   # Buttons
-  $(".delete").bind("ajax:beforeSend", ->
-    $(this).parents("table").siblings(".alert").empty()
-  ).bind("ajax:success", ->
+  removeAlert = ->
+    $("#content .alert.alert-error").remove()
+
+  addAlert = ->
+    $("#content").prepend "
An Error has occured. Please try again.
"
+
+  $("#content").on("ajax:beforeSend", ".delete", (e) ->
+    removeAlert()
+  ).on("ajax:success", ".delete", (e) ->
     $(this).parents("tr").remove()
-  ).bind "ajax:error", ->
-    $(this).parents("table").siblings(".alert").append "An Error has occured. Please try again."
+  ).on "ajax:error", ".delete", (e) ->
+    addAlert()

-  $(".approve").live "click", ->
-    $(this).bind("ajax:beforeSend", ->
-      $(this).parents("table").siblings(".alert").empty()
-    ).bind("ajax:success", ->
-      link = $(this).attr("href")
-      newLink = link.replace("approve", "deny")
-      $(this).text("Deny").removeClass("approve").addClass("deny").attr("href", newLink).siblings().remove()
-    ).bind "ajax:error", ->
-      $(this).parents("table").siblings(".alert").append "An Error has occured. Please try again."
+  $("#content").on("ajax:beforeSend", ".activate", (e) ->
+    removeAlert()
+  ).on("ajax:success", ".activate", (e) ->
+    newLink = $(this).attr("href").replace("activate", "deactivate")
+    $(this).removeClass("activate").addClass("deactivate").attr("href", newLink).attr "title", "Deactivate"
+    $(this).children("i").removeClass("icon-ok-circle").addClass("icon-ban-circle")
+    $(this).parents("tr").removeClass("inactive")
+  ).on "ajax:error", ".activate", (e) ->
+    addAlert()

-  $(".deny").live "click", ->
-    $(this).bind("ajax:beforeSend", ->
-      $(this).parents("table").siblings(".alert").empty()
-    ).bind("ajax:success", ->
-      link = $(this).attr("href")
-      newLink = link.replace("deny", "approve")
-      $(this).text("Approve").removeClass("deny").addClass("approve").attr("href", newLink).siblings().remove()
-    ).bind "ajax:error", ->
-      $(this).parents("table").siblings(".alert").append "An Error has occured. Please try again."
+  $("#content").on("ajax:beforeSend", ".deactivate", (e) ->
+    removeAlert()
+  ).on("ajax:success", ".deactivate", (e) ->
+    newLink = $(this).attr("href").replace("deactivate", "activate")
+    $(this).removeClass("deactivate").addClass("activate").attr("href", newLink).attr "title", "Activate"
+    $(this).children("i").removeClass("icon-ban-circle").addClass("icon-ok-circle")
+    $(this).parents("tr").addClass "inactive"
+  ).on "ajax:error", ".deactivate", (e) ->
+    addAlert()

-  $(".go-live").live "click", ->
-    $(this).bind("ajax:beforeSend", ->
-      $(this).parents("table").siblings(".alert").empty()
-    ).bind("ajax:success", ->
-      link = $(this).attr("href")
-      newLink = link.replace("enable", "disable")
-      $(this).text("Disable").removeClass("enable").addClass("disable").attr "href", newLink
-      $(this).parents("tr").removeClass("disabled").addClass "enabled"
-    ).bind "ajax:error", ->
-      $(this).parents("table").siblings(".alert").append "An Error has occured. Please try again."
+  $("#content").on("ajax:beforeSend", ".approve", (e) ->
+    removeAlert()
+  ).on("ajax:success", ".approve", (e) ->
+    newLink = $(this).attr("href").replace("approve", "deny")
+    $(this).removeClass("approve").addClass("deny").attr("href", newLink).attr "title", "Deny"
+    $(this).children("i").removeClass("icon-ok-circle").addClass("icon-ban-circle")
+    $(this).parents("tr").removeClass("inactive")
+  ).on "ajax:error", ".approve", (e) ->
+    addAlert()

-  $(".activate").live "click", ->
-    $(this).bind("ajax:beforeSend", ->
-      $(this).parents("table").siblings(".alert").empty()
-    ).bind("ajax:success", ->
-      link = $(this).attr("href")
-      newLink = link.replace("activate", "deactivate")
-      $(this).text("Deactivate").removeClass("activate").addClass("deactivate").attr "href", newLink
-      $(this).parents("tr").removeClass("inactive").addClass "active"
-    ).bind "ajax:error", ->
-      $(this).parents("table").siblings(".alert").append "An Error has occured. Please try again."
+  $("#content").on("ajax:beforeSend", ".deny", (e) ->
+    removeAlert()
+  ).on("ajax:success", ".deny", (e) ->
+    newLink = $(this).attr("href").replace("deny", "approve")
+    $(this).removeClass("deny").addClass("approve").attr("href", newLink).attr "title", "Approve"
+    $(this).children("i").removeClass("icon-ban-circle").addClass("icon-ok-circle")
+    $(this).parents("tr").addClass "inactive"
+  ).on "ajax:error", ".deny", (e) ->
+    addAlert()

-  $(".deactivate").live "click", ->
-    $(this).bind("ajax:beforeSend", ->
-      $(this).parents("table").siblings(".alert").empty()
-    ).bind("ajax:success", ->
-      link = $(this).attr("href")
-      newLink = link.replace("deactivate", "activate")
-      $(this).text("Activate").removeClass("deactivate").addClass("activate").attr "href", newLink
-      $(this).parents("tr").removeClass("active").addClass "inactive"
-    ).bind "ajax:error", ->
-      $(this).parents("table").siblings(".alert").append "An Error has occured. Please try again."
+  # $(".go-live").live "click", ->
+  #   $(this).bind("ajax:beforeSend", ->
+  #     removeAlert()
+  #   ).bind("ajax:success", ->
+  #     newLink = $(this).attr("href").replace("enable", "disable")
+  #     $(this).text("Disable").removeClass("enable").addClass("disable").attr "href", newLink
+  #     $(this).parents("tr").removeClass("disabled").addClass "enabled"
+  #   ).bind "ajax:error", ->
+  #     addAlert()

   # Admin - Poll form
   $.template "newChoice", "
 <input id=\"poll_choices_attributes_${count}_content\" name=\"poll[choices_attributes][${count}][content]\" size=\"30\" type=\"text\">  <input id=\"poll_choices_attributes_${count}_image\" name=\"poll[choices_attributes][${count}][image]\" type=\"file\">
"

-  $("a.add").live "click", (e) ->
+  $("#content").on("click", ".add-poll-choice", (e) ->
     choice = count: $(".choice").size() + 1
     $.tmpl("newChoice", choice).appendTo "#choices .controls"
+  )

-  $("a.remove").live "click", (e) ->
+  $("#content").on("click", ".remove-poll-choice", (e) ->
     $(this).parents(".choice").remove()
+  )

   # Admin - Event form
   $(".import").click (e) ->
@@ -94,15 +99,15 @@
     )
     fp.maps.find address, callback

-  $(".location_update_form").bind("ajax:beforeSend", ->
+  $("#content").on("ajax:beforeSend", ".location_update_form", (e) ->
     $(".locate", this).html "Loading..."
-  ).bind("ajax:success", (evt, data, status, xhr) ->
+  ).on("ajax:success", ".location_update_form", (evt, data, status, xhr) ->
     event = $("#event_" + data._id)
     $(".event-latitude", event).html data.location.latitude
     $(".event-longitude", event).html data.location.longitude
-  ).bind("ajax:error", (evt, data, status, xhr) ->
+  ).on("ajax:error", ".location_update_form", (evt, data, status, xhr) ->
     alert "An error occured getting the coordinates for this event."
-  ).bind "ajax:complete", (evt, data, status, xhr) ->
+  ).on "ajax:complete", ".location_update_form", (evt, data, status, xhr) ->
     $(".locate", this).html "Locate"

   $("#submit-event").click (e) ->


UK sales defaults to 0 not a dash

UK sales defaults to 0 not a dash

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -210,7 +210,7 @@
         if sales_value
           $(sales_selector).text(sales_value)
         else
-          $(sales_selector).text("-")
+          $(sales_selector).text("0")

   updateTurnoverExportCalculation()


Fixes straggler tooltips when switching pages

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -39,6 +39,7 @@
 $(document).on 'page:restore', () ->
   $('[data-barberpole]').removeClass('af-barberpole')
   $('[title]').tooltip('hide')
+  $('.tooltip').remove()

 $(document).ready () ->
   $(document).ajaxStart () ->


add support for class-* bindings

add support for class-* bindings

--- app/assets/javascripts/lib/bb_view.js.coffee
+++ app/assets/javascripts/lib/bb_view.js.coffee
@@ -1,4 +1,5 @@
 bindingRegex = /^([\w-]+):(!?[\w.-]+)(?:\|([\w-]+))?$/
+classBindingRegex = /^class-([\w-]+)$/

 #constructs a closure to propogate Backbone changes to the view
 constructCallbackToPropagateBbChangesToView = (view, $el, elAttribute, bindingProperties, filters) ->
@@ -94,13 +95,29 @@

 updateView = ($el, elAttribute, value, previousValue) ->
   switch elAttribute
+    #text:model.someProperty
     when 'text' then $el.text(if value? then value.toString() else '')
+
+    #displayed:model.someBooleanProperty
     when 'displayed'
       if !!value then $el.show() else $el.hide()
+
+    #class:model.someProperty
     when 'class'
       $el.removeClass(previousValue) if previousValue?
       $el.addClass(value) if value?
-    else $el.attr(elAttribute, value)
+
+    else
+
+      #class-myclass:model.someProperty
+      classMatch = elAttribute.match(classBindingRegex)
+      if classMatch?
+        className = classMatch[1]
+        if !!value then $el.addClass(className) else $el.removeClass(className)
+
+      #everything else (just assume it is an attribute on the element)
+      else
+        $el.attr(elAttribute, value)

 processPropertyOfBackboneContext = (bindingChain, context, model, property) ->
   if _.isFunction(context[property])


comment out debug log

comment out debug log

--- app/assets/javascripts/meta_search.js.coffee
+++ app/assets/javascripts/meta_search.js.coffee
@@ -23,7 +23,7 @@
       @widget().menu "option", "items", "> :not(.ui-autocomplete-category)"

     _renderMenu: (ul, items) ->
-      console.log '_renderMenu'
+      # console.log '_renderMenu'
       that = this
       currentCategory = ""

@@ -46,6 +46,8 @@
       # a += "
"
       $("
").append($(a)).appendTo(ul)

+
+
   $("input.search-meta").catcomplete
     source: (request, response) ->
       searchUrl = $("input#meta_search_action").val()


Fixes #9. Fix so that frontend falls back on username if first / last not avail.

--- app/assets/javascripts/models/user.js.coffee
+++ app/assets/javascripts/models/user.js.coffee
@@ -43,10 +43,15 @@
       if @get('surname')?
         name += ' ' + @get('surname')

+      name = $.trim(name)
+
       if name.length == 0
-        name = "unknown"
+        if @get('username')?
+          name = @get('username')
+        else
+          name = "unknown"

-      $.trim(name)
+      name

     emailConfirmed: (value, attr, computed) ->
       if computed.email? && computed.email_confirmation?


Remove defaults form lightboxes directives scope.

--- app/coffee/modules/backlog/lightboxes.coffee
+++ app/coffee/modules/backlog/lightboxes.coffee
@@ -21,10 +21,10 @@

 CreateEditUserstoryDirective = ($repo, $model) ->
     link = ($scope, $el, attrs) ->
-        $scope.us = {"tags": ["kaka", "pedo", "pis"]}
+        $scope.us = {}
         # TODO: defaults
         $scope.$on "usform:new", ->
-            $scope.us = {"subject": "KAKA"}
+            $scope.us = {}
             $el.removeClass("hidden")

         $scope.$on "usform:edit", (ctx, us) ->


program uses action not instruction

program uses action not instruction

--- src/layout/mixins/programs.coffee
+++ src/layout/mixins/programs.coffee
@@ -5,7 +5,7 @@
 # A Program is a sequence of Commands
 # A Command is a word in the Language
 # A Language is a dictionary of Commands and Actions
-# An Action is a method-key-value triplet (instruction, signal)
+# An Action is a method-key-value triplet (action, signal)
 #
 # Role: create and run a single program
 #
@@ -35,19 +35,17 @@

     next_index: 0
     reset_index: (world) -> world.put 'next_index', 0
-    next_instruction: (world) ->
+    next_action: (world) ->
       current_index = world.get 'next_index'
-      instructions = world.find_child('instructions').find_children()
-
-      count = instructions.length
+      actions = world.find_child('actions').find_children()
+      count = actions.length
       world.put 'next_index', get_next_index(current_index, count)
-
-      instructions[current_index]
+      actions[current_index]

     tick: (world, args) ->
       return unless world.get 'selected'
-      instruction = world.get 'next_instruction'
-      sprite.call 'prepare', instruction.get('value') if instruction
+      action = world.get 'next_action'
+      sprite.call 'prepare', action.get('value') if action

     collision: (world, args) ->
       [proposing_sprite, collision_subject, coordinates] = args
@@ -61,7 +59,7 @@
         sprite.call 'commit', coordinates

     apply: (world, args) ->
-      {target, action, instruction} = args
+      {target, action, action} = args
       console.log "programs apply: #{world}, {#{target}, #{action}}"
       console.log "edit: #{sprite.get 'editing'} -> #{world.get 'editable'}"
       return unless world.get 'editable'
@@ -69,14 +67,14 @@

     store: (world, action) ->
       console.log "programs store: #{world}, {#{action}}"
-      instructions_container = world.find_child('instructions')
-      instructions_container.add_child action
+      actions_container = world.find_child('actions')
+      actions_container.add_child action
   }

   program_row = (name, contents) ->
     program = make.columns name, [
       { _LABEL: 'program_name', name: name }
-      make.buttons("instruction", contents, my.command)
+      make.buttons("action", contents, my.command)
     ]
     my.extend program, program_behavior()


Extend with more scenery changes

Extend with more scenery changes

--- src/lazerscripts/benchmark.coffee
+++ src/lazerscripts/benchmark.coffee
@@ -15,12 +15,13 @@
       #@setShipType('PlayerControlledCube')

       @setWeapons(['lasers'])
-      @setScenery 'Bay'
+      @setScenery 'Ocean'
       @setSpeed 0
       @wait 1500
       => Crafty('DebugInfo').capture('start')
       @setSpeed 300
       @wait 15000
+      @setScenery('BayStart')
       => Crafty('DebugInfo').capture('scrolling')
       @runScript(StartOfDawn, speed: 5)
       @runScript(DayBreak, speed: 5)
@@ -33,7 +34,16 @@
       @enemies()
       => Crafty('PlayerSpaceship').shoot(false)
       => Crafty('DebugInfo').capture('swirlers shooting')
-      @wait 1000
+      @parallel(
+        @gainHeight(800, duration: 14000)
+        @setScenery('Skyline')
+      )
+      => Crafty('PlayerSpaceship').shoot(true)
+      @enemies()
+      => Crafty('PlayerSpaceship').shoot(false)
+      => Crafty('DebugInfo').capture('swirlers shooting skyline')
+      @setScenery 'Ocean'
+      @gainHeight(-800, duration: 14000)
       => Crafty('DebugInfo').capture('end')
     )


Add some comments.

Add some comments.

--- bokehjs/src/coffee/common/ticking.coffee
+++ bokehjs/src/coffee/common/ticking.coffee
@@ -1,11 +1,8 @@

 # TODO Clear out debugging code, etc.
 # TODO Organize helper functions.
-# TODO Use time-based scales only for time-based data.
-# TODO Something is breaking at the microsecond scale.
 # TODO The years scale doesn't always use the roundest numbers; it should
 # probably use a special scale.
-# TODO Add more comments.
 # TODO Add tests.
 # TODO There used to be a TODO: restore memoization.  So.... do that?
 # TODO Instead of a get_ticks() method, there used to be an auto_ticks()
@@ -18,6 +15,15 @@
   "sprintf",
 ], (_, tz, sprintf) ->

+  # ---------------------------------------------------------------------------
+  # Utility functions
+  # ---------------------------------------------------------------------------
+
+  # Similar to Python's range function.  Returns an array of values from start
+  # (inclusive) to end (exclusive), in steps of step.  If step is negative,
+  # starts at end and counts down to start.  If step is not specified, it
+  # defaults to sign(end - start).  (XXX Not sure I approve of this; Python's
+  # range is not this clever.)
   arange = (start, end=false, step=false) ->
     if not end
       end = start
@@ -72,50 +78,120 @@
       else
         return obj_as_string

+  # Returns an array containing the valid indices of the input array arr.
   indices = (arr) ->
     return _.range(arr.length)

+  # Returns the index of the minimum element of an array.
   argmin = (arr) ->
     ret = _.min(indices(arr), ((i) -> return arr[i]))
     return ret

+  # Forces a number x into a specified range [min_val, max_val].
   clamp = (x, min_val, max_val) ->
     return Math.max(min_val, Math.min(max_val, x))

+  # A log function with an optional base.
   log = (x, base=Math.E) ->
     return Math.log(x) / Math.log(base)

+  # ---------------------------------------------------------------------------
+  # Date/time utility functions
+  # ---------------------------------------------------------------------------
+
+  # Makes a copy of a date object.
+  copy_date = (date) ->
+    return new Date(date.getTime())
+
+  # Rounds a date down to the month.
+  last_month_no_later_than = (date) ->
+    date = copy_date(date)
+    date.setUTCDate(1)
+    date.setUTCHours(0)
+    date.setUTCMinutes(0)
+    date.setUTCSeconds(0)
+    date.setUTCMilliseconds(0)
+    return date
+
+
+  # Rounds a date down to the year.
+  last_year_no_later_than = (date) ->
+    date = last_month_no_later_than(date)
+    date.setUTCMonth(0)
+    return date
+
+  # Given a start and end time in millis, returns the shortest array of
+  # consecutive years (as Dates) that surrounds both times.
+  date_range_by_year = (start_time, end_time) ->
+    start_date = last_year_no_later_than(new Date(start_time))
+
+    end_date = last_year_no_later_than(new Date(end_time))
+    end_date.setUTCFullYear(end_date.getUTCFullYear() + 1)
+
+    dates = []
+    date = start_date
+    while true
+      dates.push(copy_date(date))
+
+      date.setUTCFullYear(date.getUTCFullYear() + 1)
+      if date > end_date
+        break
+
+    return dates
+
+  # Given a start and end time in millis, returns the shortest array of
+  # consecutive months (as Dates) that surrounds both times.
+  date_range_by_month = (start_time, end_time) ->
+    start_date = last_month_no_later_than(new Date(start_time))
+
+    end_date = last_month_no_later_than(new Date(end_time))
+    # XXX This is not a reliable technique in general, but it should be
+    # safe when the day of the month is 1.  (The problem case is this:
+    # Mar 31 -> Apr 31, which becomes May 1.)
+    prev_end_date = copy_date(end_date)
+    end_date.setUTCMonth(end_date.getUTCMonth() + 1)
+
+    dates = []
+    date = start_date
+    while true
+      dates.push(copy_date(date))
+
+      date.setUTCMonth(date.getUTCMonth() + 1)
+      if date > end_date
+        break
+
+    return dates
+
+  # ---------------------------------------------------------------------------
+  # Scale classes
+  # ---------------------------------------------------------------------------
+
   DEFAULT_DESIRED_N_TICKS = 6

+  # The base class for all Scale objects.  It needs to be subclassed before
+  # being used.  The simplest subclass is SingleIntervalScale.
+  #
+  # The main value of a Scale is its get_ticks() method, which takes a min and
+  # max value and (optionally) a desired number of ticks, and returns an array
+  # of approximately that many ticks, evenly spaced, with nice round values,
+  # within that range.
+  #
+  # Different Scales are suited to different types of data or different
+  # magnitudes.  To make it possible to select Scales programmatically, they
+  # also support some additional methods: get_interval(), get_min_interval(),
+  # and get_max_interval().
   class AbstractScale
+    # Creates a new AbstractScale.  The toString_properties argument is an
+    # optional list of member names which be shown when toString() is called.
     constructor: (@toString_properties=[]) ->

+    # Generates a nice series of ticks for a given range.
     get_ticks: (data_low, data_high,
                 desired_n_ticks=DEFAULT_DESIRED_N_TICKS) ->
       return @get_ticks_no_defaults(data_low, data_high, desired_n_ticks)

-    get_min_interval: () -> @min_interval
-
-    get_max_interval: () -> @max_interval
-
-    toString: () ->
-      # FIXME Should we use typeof() instead of constructor.name?
-      class_name = @constructor.name
-      props = @toString_properties
-      params_str = ("#{key}=#{repr(this[key])}" for key in props).join(", ")
-      return "#{class_name}(#{params_str})"
-
-    min_interval: undefined
-
-    max_interval: undefined
-
-    get_ideal_interval: (data_low, data_high, desired_n_ticks) ->
-      data_range = data_high - data_low
-      return data_range / desired_n_ticks
-
-    get_interval: (data_low, data_high, desired_n_ticks) ->
-      return @get_interval(data_low, data_high, desired_n_ticks)
-
+    # The version of get_ticks() that does the work (and the version that
+    # should be overridden in subclasses).
     get_ticks_no_defaults: (data_low, data_high, desired_n_ticks) ->
       interval = @get_interval(data_low, data_high, desired_n_ticks)
       start_factor = Math.floor(data_low / interval)
@@ -124,6 +200,46 @@
       ticks = (factor * interval for factor in factors)
       return ticks

+    # Given min and max values and a number of ticks, returns a tick interval
+    # that produces approximately the right number of nice ticks.  (If you just
+    # implement this method, get_ticks_no_defaults() will work.  However, if
+    # you want to return ticks that aren't evenly spaced, you'll need to
+    # override get_ticks_no_defaults() directly.  In that case, you should
+    # still implement get_interval(), because users can call it to get a sense
+    # of what the spacing will be for a given range.)
+    # FIXME Is that necessary?  Maybe users should just call get_ticks() and
+    # figure it out from that.
+    get_interval: undefined
+
+    # Returns the smallest interval that can be returned by get_interval().
+    get_min_interval: () -> @min_interval
+
+    # Returns the largest interval that can be returned by get_interval().
+    get_max_interval: () -> @max_interval
+
+    # Since min and max intervals generally don't change, subclasses can just
+    # set these in the constructor.
+    min_interval: undefined
+    max_interval: undefined
+
+    # Returns a string representation of this object.
+    toString: () ->
+      # FIXME Should we use typeof() instead of constructor.name?
+      class_name = @constructor.name
+      props = @toString_properties
+      params_str = ("#{key}=#{repr(this[key])}" for key in props).join(", ")
+      return "#{class_name}(#{params_str})"
+
+    # Returns the interval size that would produce exactly the number of
+    # desired ticks.  (In general we won't use exactly this interval, because
+    # we want the ticks to be round numbers.)
+    get_ideal_interval: (data_low, data_high, desired_n_ticks) ->
+      data_range = data_high - data_low
+      return data_range / desired_n_ticks
+
+  # The SingleIntervalScale is a Scale that always uses the same tick spacing,
+  # regardless of the input range.  It's not very useful by itself, but can
+  # be used as part of a CompositeScale below.
   class SingleIntervalScale extends AbstractScale
     constructor: (@interval) ->
       super(['interval'])
@@ -133,11 +249,16 @@
     get_interval: (data_low, data_high, n_desired_ticks) ->
       return @interval

+  # This Scale takes a collection of Scales and picks the one most appropriate
+  # for a given range.
   class CompositeScale extends AbstractScale
+    # The scales should be in order of increasing interval size; specifically,
+    # if S comes before T, then it should be the case that
+    # S.get_max_interval() < T.get_min_interval().
+    # FIXME Enforce this automatically.
     constructor: (@scales) ->
       super()

-      # FIXME Validate that the scales don't overlap.
       @min_intervals = _.invoke(@scales, 'get_min_interval')
       @max_intervals = _.invoke(@scales, 'get_max_interval')

@@ -171,7 +292,19 @@
       return best_scale.get_ticks_no_defaults(data_low, data_high,
                                               desired_n_ticks)

+  # This Scale produces nice round ticks at any magnitude.
+  # AdaptiveScale([1, 2, 5]) will choose the best tick interval from the
+  # following:
+  # ..., 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, ...
   class AdaptiveScale extends AbstractScale
+    # These arguments control the range of possible intervals.  The interval I
+    # returned by get_interval() will be the one that most closely matches the
+    # desired number of ticks, subject to the following constraints:
+    # I = (M * B^N), where
+    # M is a member of mantissas,
+    # B is base,
+    # and N is an integer;
+    # and min_interval <= I <= max_interval.
     constructor: (@mantissas, @base=10.0, @min_interval=0.0,
                   @max_interval=Infinity)->
       super(['mantissas', 'base', 'min_magnitude', 'max_magnitude'])
@@ -206,61 +339,9 @@

       return clamp(interval, @min_interval, @max_interval)

-  copy_date = (date) ->
-    return new Date(date.getTime())
-
-  last_month_no_later_than = (date) ->
-    date = copy_date(date)
-    date.setUTCDate(1)
-    date.setUTCHours(0)
-    date.setUTCMinutes(0)
-    date.setUTCSeconds(0)
-    date.setUTCMilliseconds(0)
-    return date
-
-  last_year_no_later_than = (date) ->
-    date = last_month_no_later_than(date)
-    date.setUTCMonth(0)
-    return date
-
-  date_range_by_year = (start_time, end_time) ->
-    start_date = last_year_no_later_than(new Date(start_time))
-
-    end_date = last_year_no_later_than(new Date(end_time))
-    end_date.setUTCFullYear(end_date.getUTCFullYear() + 1)
-
-    dates = []
-    date = start_date
-    while true
-      dates.push(copy_date(date))
-
-      date.setUTCFullYear(date.getUTCFullYear() + 1)
-      if date > end_date
-        break
-
-    return dates
-
-  date_range_by_month = (start_time, end_time) ->
-    start_date = last_month_no_later_than(new Date(start_time))
-
-    end_date = last_month_no_later_than(new Date(end_time))
-    # XXX This is not a reliable technique in general, but it should be
-    # safe when the day of the month is 1.  (The problem case is this:
-    # Mar 31 -> Apr 31, which becomes May 1.)
-    prev_end_date = copy_date(end_date)
-    end_date.setUTCMonth(end_date.getUTCMonth() + 1)
-
-    dates = []
-    date = start_date
-    while true
-      dates.push(copy_date(date))
-
-      date.setUTCMonth(date.getUTCMonth() + 1)
-      if date > end_date
-        break
-
-    return dates
-
+  # A MonthsScale produces ticks from a fixed subset of months of the year.
+  # E.g., MonthsScale([0, 3, 6, 9]) produces ticks of the 1st of January,
+  # April, July, and October of each year.
   class MonthsScale extends SingleIntervalScale
     constructor: (@months) ->
       @typical_interval = if @months.length > 1
@@ -289,6 +370,9 @@

       return ticks_in_range

+  # A DaysScale produces ticks from a fixed subset of calendar days.
+  # E.g., DaysScale([1, 15]) produces ticks on the 1st and 15th days of each
+  # month.
   class DaysScale extends SingleIntervalScale
     constructor: (@days) ->
       @typical_interval = if @days.length > 1
@@ -328,6 +412,7 @@

       return ticks_in_range

+  # Some time constants, in milliseconds.
   ONE_MILLI = 1.0
   ONE_SECOND = 1000.0
   ONE_MINUTE = 60.0 * ONE_SECOND
@@ -336,10 +421,16 @@
   ONE_MONTH = 30 * ONE_DAY # An approximation, obviously.
   ONE_YEAR = 365 * ONE_DAY

+  # This is a good default scale for generic numerical data.
   class BasicScale extends AdaptiveScale
     constructor: () ->
-      super([1.0, 2.0, 5.0])
-
+      super([1, 2, 5])
+
+  # This is a decent scale for time data (in milliseconds).
+  # It could certainly be improved:
+  # FIXME There should probably be a special scale for years.
+  # FIXME Some of the adaptive scales probably have too many mantissas, which
+  # leads to too-frequent tick transitions.
   class DatetimeScale extends CompositeScale
     constructor: () ->
       super([
@@ -368,6 +459,10 @@
         # Catchall for large timescales.
         new AdaptiveScale([1, 2, 5], 10, ONE_YEAR, Infinity),
       ])
+
+  # ---------------------------------------------------------------------------
+  # Formatter classes
+  # ---------------------------------------------------------------------------

   class BasicTickFormatter
     constructor: (@precision='auto', @use_scientific=true, @power_limit_high=5, @power_limit_low=-3) ->


Fix IE 10 and IE 11's broken `preventDefault`

https://github.com/turbolinks/turbolinks/issues/233
https://stackoverflow.com/questions/23349191/event-preventdefault-is-not-working-in-ie-11-for-custom-events

--- actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
+++ actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
@@ -10,6 +10,11 @@
   CustomEvent = (event, params) ->
     evt = document.createEvent('CustomEvent')
     evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
+    # IE does not set `defaultPrevented` when `preventDefault()` is called on CustomEvents
+    # http://stackoverflow.com/questions/23349191/event-preventdefault-is-not-working-in-ie-11-for-custom-events
+    evt.preventDefault = ->
+      Object.defineProperty this, 'defaultPrevented', get: ->
+        true
     evt
   CustomEvent.prototype = window.Event.prototype


renamed vismenu labels

renamed vismenu labels

Former-commit-id: 125b0d23821c79d805e220707562376bdb505177 [formerly 2fc9749f8c5633ac3b418dc8f5a0e64710bdd765]
Former-commit-id: 5f53dc12bda9c37dc00ee210c7656e029d21af3a

--- src/menu/views/VisMenu.coffee
+++ src/menu/views/VisMenu.coffee
@@ -52,8 +52,8 @@
     #vis.push name: "Sequences", id: "sequences"
     vis.push name: "Meta info", id: "metacell"
     vis.push name: "Overviewbox", id: "overviewbox"
-    vis.push name: "conserv", id: "conserv"
-    vis.push name: "LabelName", id: "labelName"
-    vis.push name: "LabelId", id: "labelId"
-    vis.push name: "LabelCheckbox", id: "labelCheckbox"
+    vis.push name: "Conserv", id: "conserv"
+    vis.push name: "Label name", id: "labelName"
+    vis.push name: "Label id", id: "labelId"
+    vis.push name: "Label checkbox", id: "labelCheckbox"
     return vis


Added support for missing catch blocks

Added support for missing catch blocks

--- source/coffee/generator.coffee
+++ source/coffee/generator.coffee
@@ -401,6 +401,8 @@
       indent()
       rv += @catch_block.js()
       rv += "}"
+    else
+      rv += "catch (k$e) {}"

   snippets =


note to self

note to self

--- src/modules/deferrable.coffee
+++ src/modules/deferrable.coffee
@@ -1,4 +1,6 @@
-# TODO THis module is going to be deprecated as it has been gradually gut over time
+# TODO
+#
+# This is horrendous code.  I need to replace it ASAP
 Luca.modules.Deferrable =
   configure_collection: (setAsDeferrable=true)->
     return unless @collection


gave up on some google chart functionality

--- demo.coffee
+++ demo.coffee
@@ -263,14 +263,11 @@
     options =
       title: titleize decamelize(title)
       width: width
-      explorer:
-        axis: 'horizontal'
-        keepInBounds: true
       height: height
       hAxis:
         title: htitle
-        viewWindow:
-          min: Math.max 1, rows.length - 400
+        viewWindow:
+          min: Math.max 1, rows.length - 500
       vAxis:
         title: vtitle
     if specs.type == 'interval'


:lipstick:

:lipstick:

--- source/javascripts/grid.coffee
+++ source/javascripts/grid.coffee
@@ -1,18 +1,15 @@
 render = ->
   width  = $(document.body).width()
   height = 280
-  gridWidth = Math.round(width / 4) - 60
-  colors = ['#111', '#333', '#555', '#777', '#999', '#bbb', '#ddd', '#fff']
+  gridWidth = Math.round(width / 4) - 41
+  colors = ['#1a1a1a', '#353535', '#555', '#757575', '#959595', '#b5b5b5', '#d5d5d5', '#f5f5f5']

   fill = d3.scale.quantile().range colors

   items = d3.select('.js-grid').selectAll('.item')
     .data(d3.entries(__mappings))
   .enter().append('div')
-
-  items.append('h3')
-    .attr('class', 'title')
-    .text((d) -> d.key)
+    .attr('class', 'item')

   projection  = d3.geo.albers().scale(1).translate [ 0, 0 ]
   path = d3.geo.path().projection(projection)
@@ -56,13 +53,6 @@
         path(blockgroup)
         context.fill()
         context.stroke()
-
-      # Draw the neighborhoods
-      # context.beginPath()
-      # path(neighborhoods)
-      # context.strokeStyle = '#fff'
-      # context.lineWidth = 0.3
-      # context.stroke()

       # Draw the bodies of water
       context.beginPath()


enable FATAL handling again

enable FATAL handling again

--- src/smc-util/synctable.coffee
+++ src/smc-util/synctable.coffee
@@ -312,7 +312,7 @@

                 if is_fatal(err)
                     console.warn('setting up changefeed', @_table, err)
-                    #@close(true)
+                    @close(true)
                     cb?(err)
                     return

@@ -574,7 +574,7 @@
                 if err
                     if is_fatal(err)
                         console.warn('FATAL doing set', @_table, err)
-                        #@close(true)
+                        @close(true)
                         cb?(err)
                         return


pass bucket name to config api

pass bucket name to config api

--- src/steroids/Providers.coffee
+++ src/steroids/Providers.coffee
@@ -320,6 +320,7 @@
       data.configurationKeys =
         bucket_id: config['bucket_id']
         steroids_api_key: config['apikey']
+        bucket_name: config['bucket']

     console.log "Updating SandboxDB data provider information..."


clean logs.

clean logs.

--- client/Dashboard/views/grouplogosettings.coffee
+++ client/Dashboard/views/grouplogosettings.coffee
@@ -16,7 +16,6 @@
         width        : 55
         height       : 55
       resized = KD.utils.proxifyUrl @group.customize.logo, proxifyOptions
-      log "resized",resized
       attributes  =
         style     : "background-image: url(#{resized});"
     else


activity: fix realtime message remove problem

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -254,9 +254,9 @@
     if item?
       item.once 'HideAnimationFinished', =>
         @listController.removeItem item
-        @listController.showNoItemWidget() if @listController.getListItems().length is 0
-
-      item.hide()
+        @listController.showNoItemWidget()  if @listController.getListItems().length is 0
+
+      item.delete()

   setScrollTops: ->


activity: latency compensation related race condition

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -67,8 +67,8 @@
     return  unless @input

     @input
+      .on 'SubmitStarted',    => @submitIsPending = yes
       .on 'SubmitStarted',    @bound 'handleEnter'
-      .on 'SubmitStarted',    => @submitIsPending = yes
       .on 'SubmitSucceeded',  @bound 'replaceFakeItemView'
       .on 'SubmitSucceeded',  => KD.utils.defer => @submitIsPending = no
       .on 'SubmitFailed',     @bound 'messageSubmitFailed'


Client: Activity: fixed indentation

Client: Activity: fixed indentation

--- client/activity/lib/flux/actions/message.coffee
+++ client/activity/lib/flux/actions/message.coffee
@@ -170,25 +170,25 @@
 ###
 editMessage = (messageId, body, payload = {}) ->

-    {socialapi} = kd.singletons
-    { EDIT_MESSAGE_BEGIN
-      EDIT_MESSAGE_SUCCESS
-      EDIT_MESSAGE_FAIL
-    } = actionTypes
-
-    dispatch EDIT_MESSAGE_BEGIN, { messageId, body, payload }
-
-    socialapi.message.edit {id: messageId, body, payload}, (err, message) ->
-      if err
-        dispatch EDIT_MESSAGE_FAIL, { err, messageId }
-        return
-
-      kd.utils.wait 673, ->
-        socialapi.message.byId {id: messageId}, (err, message) ->
-          message.body = body
-          dispatch EDIT_MESSAGE_SUCCESS, {
-            message, messageId
-          }
+  {socialapi} = kd.singletons
+  { EDIT_MESSAGE_BEGIN
+    EDIT_MESSAGE_SUCCESS
+    EDIT_MESSAGE_FAIL
+  } = actionTypes
+
+  dispatch EDIT_MESSAGE_BEGIN, { messageId, body, payload }
+
+  socialapi.message.edit {id: messageId, body, payload}, (err, message) ->
+    if err
+      dispatch EDIT_MESSAGE_FAIL, { err, messageId }
+      return
+
+    kd.utils.wait 673, ->
+      socialapi.message.byId {id: messageId}, (err, message) ->
+        message.body = body
+        dispatch EDIT_MESSAGE_SUCCESS, {
+          message, messageId
+        }

 ###*


activity: render NewChannel modal as child route

--- client/activity/lib/routes/SingleChannel.coffee
+++ client/activity/lib/routes/SingleChannel.coffee
@@ -3,6 +3,8 @@
 ActivityFlux      = require 'activity/flux'
 getGroup          = require 'app/util/getGroup'
 changeToChannel   = require 'activity/util/changeToChannel'
+
+NewChannelRoute = require './NewChannel'

 {
   thread  : threadActions,
@@ -18,6 +20,7 @@

     @path = ':channelName(:/postId)'
     @childRoutes = [
+      new NewChannelRoute
     ]


Re-Remove Useless Method

Re-Remove Useless Method

--- client/app/actions/layout_action_creator.coffee
+++ client/app/actions/layout_action_creator.coffee
@@ -49,10 +49,6 @@

         AppDispatcher.handleViewAction
             type: type
-
-    minimizePreview: ->
-        AppDispatcher.handleViewAction
-            type: ActionTypes.MINIMIZE_PREVIEW_PANE

     refresh: ->
         AppDispatcher.handleViewAction


Add fi TODOs

Add fi TODOs

--- dev/fi.cson
+++ dev/fi.cson
@@ -1,4 +1,4 @@
-information_border_fi:
+'information_border_fi':
   category: 'information'
   name: 'European border crossing into Finland'
   elements: [
@@ -8,3 +8,10 @@
     { type: 'content-5', color: 'white', content: 'FINLAND', transform: 'translate(0,0)' }
     { type: 'content-5', color: 'white', content: 'SUOPMA', transform: 'translate(0,100%)' }
   ]
+
+'cyclists-crossing-ahead':
+  category: 'danger'
+  name: 'cyclists crossing ahead'
+  elements: [
+    { type: 'circle-bg', color: 'black' }
+  ]


NodeType tag applied uniformly

NodeType tag applied uniformly

--- app/scripts/alchemy/drawing/drawNodes.coffee
+++ app/scripts/alchemy/drawing/drawNodes.coffee
@@ -23,8 +23,8 @@
                         .data d3Nodes, (n) -> n.id
         node.enter().append "g"
                 .attr "class", (d) ->
-                    node_data = alchemy._nodes[d.id].getProperties()
-                    if d.nodeType? then "node #{d.nodeType} active" else "node active"
+                    nodeType = alchemy._nodes[d.id]._nodeType
+                    "node #{nodeType} active"
                 .attr 'id', (d) -> "node-#{d.id}"
                 .classed 'root', (d) -> d.root


Fix multiple file chunk handling.

Fix multiple file chunk handling.

--- app/assets/javascripts/azure_direct_upload.coffee
+++ app/assets/javascripts/azure_direct_upload.coffee
@@ -17,7 +17,6 @@
     return this

   form = this
-  chunksent = false

   settings =
     path: ''
@@ -62,6 +61,7 @@
           complete:   ( xhr, status )         -> form.trigger( 'ajax:complete', [xhr, status] )
           success:    ( response_data, status, xhr )   ->
             data.files[0].blockno = 0
+            data.files[0].chunksent = false
             data.files[0].base_url = response_data.uri
             data.url = data.files[0].base_url.replace("BLOCK_ID", btoa(pad(data.files[0].blockno, 5)))
             form.trigger( 'ajax:success', [data, status, xhr] )
@@ -105,11 +105,11 @@
         settings.callbacks.submit(data) if settings.callbacks.submit

       chunksend: (e, data) =>
-        if chunksent
+        if data.files[0].chunksent
           data.files[0].blockno++
           data.url = data.files[0].base_url.replace("BLOCK_ID", btoa(pad(data.files[0].blockno, 5)))

-        chunksent = true
+        data.files[0].chunksent = true
         settings.callbacks.chunksend(data) if settings.callbacks.chunksend
       chunkdone: (e, data) =>
         settings.callbacks.chunkdone(data) if settings.callbacks.chunkdone


F atom with model select

F atom with model select

--- app/assets/javascripts/folio/console/nodes.coffee
+++ app/assets/javascripts/folio/console/nodes.coffee
@@ -22,15 +22,17 @@
     form = $t.find(':selected').data('form')
     $fields = $t.closest('.nested-fields')

-    $selects = $fields.find(".atom-model-select:not(.disabled[data-class='#{klass}'])")
-    $selects.addClass('disabled').prop('disabled', true)
-    $selects.parent().addClass('disabled')
-    $selects.parent().hide()
-
-    $selects = $fields.find(".atom-model-select.disabled[data-class='#{klass}']")
-    $selects.removeClass('disabled').prop('disabled', false)
-    $selects.parent().removeClass('disabled')
-    $selects.parent().show()
+    $modelSelects = $fields
+      .find(".atom-model-select:not(.disabled[data-class='#{klass}'])")
+    $modelSelects.addClass('disabled').prop('disabled', true)
+    $modelSelects.parent().addClass('disabled')
+    $modelSelects.parent().hide()
+
+    $modelSelects = $fields
+      .find(".atom-model-select.disabled[data-class='#{klass}']")
+    $modelSelects.removeClass('disabled').prop('disabled', false)
+    $modelSelects.parent().removeClass('disabled')
+    $modelSelects.parent().show()

     $content = $fields.find('.atom-content')
     switch form
@@ -47,7 +49,10 @@
           window.folioConsoleDestroyRedactor($textarea[0])
       when 'none'
         $content.hide()
-
+
+    if $modelSelects.length > 0
+      $modelSelects.trigger('change')
+
   $(document).on 'cocoon:after-insert', (e, insertedItem) ->
     selectRightForm($(insertedItem).find('select.atom-type-select'))

@@ -59,16 +64,18 @@
       .find('input, select').prop('disabled', false)
     $fields.find("fieldset:not([data-type='#{type}'])")
       .hide()
-      .find('input, select').prop('disabled', 'disabled');
+      .find('input, select').prop('disabled', 'disabled')

   $(document).on 'change', '.atom-type-select', ->
     selectRightForm(this)

   $(document).on 'change', '.atom-model-select', ->
     $t = $(this)
-    $textarea = $t.closest('.nested-fields').find('textarea')
+    $textarea = $t.closest('.nested-fields')
+                  .find('.node_atoms_content textarea')
     content = $t.find(':selected').data('content')
-    window.folioConsoleRedactorSetContent($textarea[0], content)
+    if content
+      window.folioConsoleRedactorSetContent($textarea[0], content)

   $('#paginate-images a').on 'ajax:success', (e, data, status, json) ->
     # pagination


add option to only manage a single file upload

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -22,6 +22,7 @@
     remove_failed_progress_bar: false
     progress_bar_target: null
     click_submit_target: null
+    allow_multiple_files: true

   $.extend settings, options

@@ -42,11 +43,13 @@
         file.unique_id = Math.random().toString(36).substr(2,16)

         unless settings.before_add and not settings.before_add(file)
-          current_files.push data
           data.context = $($.trim(tmpl("template-upload", file))) if $('#template-upload').length > 0
           $(data.context).appendTo(settings.progress_bar_target || $uploadForm)
           if settings.click_submit_target
-           forms_for_submit.push data
+            if settings.allow_multiple_files
+              forms_for_submit.push data
+            else
+              forms_for_submit = [data]
           else
             data.submit()


Fix broken base query

Fix broken base query

--- app/assets/javascripts/search/queries/base.coffee
+++ app/assets/javascripts/search/queries/base.coffee
@@ -4,8 +4,7 @@
   VALUES_PER_FACET: 300 # must be >= Category.Count to avoid missing categories

   constructor:
-    (@query = '', @category = null, @facetFilters = [], @page = null,
-      @sort_order = 'nearby') ->
+    (@query = '', @facetFilters = [], @page = null, @sort_order = 'nearby') ->
       # Algolia seems to want this string in an array
       @categoryArray = [] # NOTE use this later for tags?!


changed to Math.ceil

changed to Math.ceil

--- bokehjs/src/coffee/renderer/tile/dynamic_image_renderer.coffee
+++ bokehjs/src/coffee/renderer/tile/dynamic_image_renderer.coffee
@@ -48,7 +48,7 @@
       bounds : bounds
       cache_key : bounds.join(':')

-    image.src = @mget('image_source').get_image_url(bounds[0], bounds[1], bounds[2], bounds[3], Math.round(@map_frame.get('height')), Math.round(@map_frame.get('width')))
+    image.src = @mget('image_source').get_image_url(bounds[0], bounds[1], bounds[2], bounds[3], Math.ceil(@map_frame.get('height')), Math.ceil(@map_frame.get('width')))
     return image

   render: (ctx, indices, args) ->


Basic previews working

Basic previews working

--- src/views/level-select.coffee
+++ src/views/level-select.coffee
@@ -21,6 +21,7 @@
         'click .play': 'play'

   current: 0
+  page: 0
   difficulty: 'easy'
   stats: {}

@@ -28,20 +29,6 @@
     @elem = $(template())
     @render()

-    # Spike: draw previews on all the <canvas> objects
-    i = 0
-    while (i < 9)
-      canvas = @$('.preview canvas').eq(i)
-      context = canvas[0].getContext('2d')
-      level = levels[@difficulty][i]
-      for clue, index in level.clues
-          if clue is 1
-            x = index % 10
-            y = Math.floor(index / 10)
-            context.fillRect(x, y, 1, 1)
-      i += 1
-
-
   previous: (e) ->
     e.preventDefault()

@@ -54,7 +41,9 @@

       @trigger 'sfx:play', 'button'

-      @showPreview levels[@difficulty][@current]
+      # @showPreview levels[@difficulty][@current]
+      @page -= 1
+      @drawPreviews()

   next: (e) ->
     e.preventDefault()
@@ -67,8 +56,9 @@
       if @current == 1 then @$('.previous').removeClass 'disabled'

       @trigger 'sfx:play', 'button'
-
-      @showPreview levels[@difficulty][@current]
+      @page += 1
+      @drawPreviews()
+      # @showPreview levels[@difficulty][@current]

   play: (e) ->
     e.preventDefault()
@@ -114,16 +104,16 @@

       # Show a preview of the completed level
       # This will show the previously completed random puzzle
-      if level.clues.length > 0
-        @$('.preview .complete').show()
-        @$('.preview .incomplete').hide()
-
-        # Clear out previous preview
-        @$('.preview .complete div').removeClass('filled')
-
-        for clue, index in level.clues
-          if clue is 1
-            @$('.preview .complete div').eq(index).addClass('filled')
+      # if level.clues.length > 0
+      #   @$('.preview .complete').show()
+      #   @$('.preview .incomplete').hide()
+
+      #   # Clear out previous preview
+      #   @$('.preview .complete div').removeClass('filled')
+
+      #   for clue, index in level.clues
+      #     if clue is 1
+      #       @$('.preview .complete div').eq(index).addClass('filled')

     else
       @$('.level-number').html "#{@difficulty.charAt(0).toUpperCase() + @difficulty.slice(1)} ##{@current + 1}: ????"
@@ -140,6 +130,21 @@
     #   'left': 0
     # , 500, 'cubic-bezier(0.5, -0.5, 0.5, 1.5)'

+  drawPreviews: ->
+    i = 0
+    while (i < 9)
+      canvas = @$('.preview canvas').eq(i)
+      pixelSize = Math.floor(canvas.width() / 10)
+      context = canvas[0].getContext('2d')
+      context.clearRect(0, 0, canvas.width(), canvas.height())
+
+      level = levels[@difficulty][@page * 9 + i]
+      for clue, index in level.clues
+          if clue is 1
+            x = index % 10
+            y = Math.floor(index / 10)
+            context.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize)
+      i += 1

   resize: (width, height, orientation) ->
     preview = @$('.preview')
@@ -154,6 +159,13 @@
       preview.width(Math.round(width / 10) * 10)
       preview.height(preview.width())

+    @$('canvas').each (index, object) ->
+      canvas = $(object)
+      canvas.attr('width', Math.floor(canvas.width() / 10) * 10)
+      canvas.attr('height', Math.floor(canvas.height() / 10) * 10)
+
+    @drawPreviews()
+
   hide: (duration = 500, callback) ->
     super duration, callback


SocialWorker: move _queue outside of oldStorages.forEach

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -1000,17 +1000,17 @@
           queue.next()

       ->
+        _queue = []
         # removing old storages
         oldStorages.forEach (oldStorage) ->
-          _queue = []
           _queue.push ->
             oldStorage.remove (err) ->
               return _queue.fin err  if err
               _queue.fin()

-          dash _queue, (err) ->
-            return callback err  if err
-            queue.next()
+        dash _queue, (err) ->
+          return callback err  if err
+          queue.next()

       ->
         callback null, newStorage


"Travis CI (Part 41)"

"Travis CI (Part 41)"

--- spec/linter-opencl-spec.coffee
+++ spec/linter-opencl-spec.coffee
@@ -2,7 +2,7 @@

 describe 'The OpenCL build provider for Atom Linter',  ->
   linter = require('../lib/linter-opencl').provideLinter().lint
-  dirPath = '~/build/BenSolus/linter-opencl/spec//files'
+  dirPath = '~/build/BenSolus/linter-opencl/spec/files'

   beforeEach ->
     waitsForPromise ->


Ending animation and rocket trails

Ending animation and rocket trails

--- app/scripts/lazerscripts/stage1/stage1boss.coffee
+++ app/scripts/lazerscripts/stage1/stage1boss.coffee
@@ -27,8 +27,8 @@
       @parallel(
         @moveTo(x: .85, y: .41)
         @sequence(
-          @say('Large Drone', 'We have control now! You will suffer!')
-          @say('Large Drone', 'Earths defences are in our hands!')
+          @say('Drone Commander', 'We have control now! You will suffer!')
+          @say('Drone Commander', 'Earths defences are in our hands!')
           @wait 500
         )
       )
@@ -206,10 +206,54 @@
     @sequence(
       @drop(location: @location(), item: 'diagonals')
       @while(
-        @moveTo(x: 1.15, y: .21, speed: 50)
+        @moveTo(x: .6, y: .91, speed: 150)
         @sequence(
           @explosion(@location())
           @wait 700
+        )
+      )
+      @moveTo(y: 1.0, speed: 100)
+      @moveTo(y: .7, speed: 150)
+      @sendToBackground(0.9, -100)
+      @parallel(
+        @while(
+          @moveTo(x: -.15, speed: 300)
+          @sequence(
+            @explosion(@location(),
+              radius: 10
+              duration: 480
+              z: -100
+              alpha: .8
+              eachFrame:
+                rotation: (r) -> r + 1
+                alpha: (a) -> Math.max(0, a - .003)
+                lightness: (l) -> Math.max(.2, l - .05)
+                y: (y) -> y - (Math.random() * 2)
+            )
+            @wait -> 40 + (Math.random() * 50)
+          )
+        )
+
+        @scale(0.8, duration: 3000)
+      )
+      => @entity.flip('X')
+      @sendToBackground(0.7, -150)
+      @while(
+        @moveTo(x: 1.1, speed: 200)
+        @sequence(
+          @explosion(@location(),
+            radius: 10
+            duration: 480
+            z: -150
+            alpha: .8
+            lightness: 1.0
+            eachFrame:
+              rotation: (r) -> r + 1
+              alpha: (a) -> Math.max(0, a - .003)
+              lightness: (l) -> Math.max(.2, l - .05)
+              y: (y) -> y - (Math.random() * 2)
+          )
+          @wait -> 40 + (Math.random() * 50)
         )
       )
     )
@@ -284,11 +328,13 @@
           radius: 5
           duration: 180
           z: 1
+          alpha: .8
+          lightness: 1.0
           eachFrame:
-            rotation: 1
-            alpha: [.8, -.01, 0]
-            lightness: [1.0, -.05, .2]
-            y: -0.5
+            rotation: (r) -> r + 1
+            alpha: (a) -> Math.max(0, a - .01)
+            lightness: (l) -> Math.max(.2, l - .05)
+            y: (y) -> y - .2 - (Math.random() * .7)
         )
         @wait 20
       )
@@ -336,10 +382,10 @@
             duration: 180
             z: 1
             eachFrame:
-              rotation: 1
-              alpha: [.8, -.02, 0]
-              lightness: [1.0, -.05, .2]
-              y: -1
+              rotation: (r) -> r + 1
+              alpha: (a) -> Math.max(0, a - .01)
+              lightness: (l) -> Math.max(.2, l - .05)
+              y: (y) -> y - 0.5
           )
           @wait 20
         )


unfocus

unfocus

--- spec/motion-scroll-spec.coffee
+++ spec/motion-scroll-spec.coffee
@@ -2,7 +2,7 @@
 settings = require '../lib/settings'
 globalState = require '../lib/global-state'

-fdescribe "Motion Scroll", ->
+describe "Motion Scroll", ->
   [set, ensure, keystroke, editor, editorElement, vimState] = []
   text = new TextData([0...100].join("\n"))


Update spec to check that focus is restored

--- spec/notifications-spec.coffee
+++ spec/notifications-spec.coffee
@@ -92,18 +92,27 @@
         expect(notificationContainer.childNodes.length).toBe 0

       it "is removed when the close icon is clicked", ->
-        notification = atom.notifications.addSuccess('A message', dismissable: true)
-        notificationElement = notificationContainer.querySelector('atom-notification.success')
-
-        expect(notificationContainer.childNodes.length).toBe 1
-
-        notificationElement.querySelector('.close.icon').click()
-
-        advanceClock(NotificationElement::visibilityDuration)
-        expect(notificationElement).toHaveClass 'remove'
-
-        advanceClock(NotificationElement::animationDuration)
-        expect(notificationContainer.childNodes.length).toBe 0
+        jasmine.attachToDOM(workspaceElement)
+
+        waitsForPromise ->
+          atom.workspace.open()
+
+        runs ->
+          notification = atom.notifications.addSuccess('A message', dismissable: true)
+          notificationElement = notificationContainer.querySelector('atom-notification.success')
+
+          expect(notificationContainer.childNodes.length).toBe 1
+
+          notificationElement.focus()
+          notificationElement.querySelector('.close.icon').click()
+
+          expect(atom.views.getView(atom.workspace.getActiveTextEditor())).toHaveFocus()
+
+          advanceClock(NotificationElement::visibilityDuration)
+          expect(notificationElement).toHaveClass 'remove'
+
+          advanceClock(NotificationElement::animationDuration)
+          expect(notificationContainer.childNodes.length).toBe 0

     describe "when an autoclose notification is added", ->
       it "closes and removes the message after a given amount of time", ->


updated spec

updated spec

--- spec/sync-settings-spec.coffee
+++ spec/sync-settings-spec.coffee
@@ -125,7 +125,7 @@
             expect(SyncSettings.fileContent(atom.keymap.getUserKeymapPath())).toEqual original
             fs.writeFileSync atom.keymap.getUserKeymapPath(), original

-      it "downloads the files defined in config.extraFiles", ->
+      it "downloads all other files in the gist as well", ->
         atom.config.set 'sync-settings.extraFiles', ['test.tmp', 'test2.tmp']
         run (cb) ->
           SyncSettings.upload cb


:white_check_mark: fixed specs to activate the plugin before running

--- spec/sync-settings-spec.coffee
+++ spec/sync-settings-spec.coffee
@@ -35,6 +35,7 @@

     token = null
     gistId = null
+    SyncSettings.activate()

     beforeEach ->
       @token = process.env.GITHUB_TOKEN || atom.config.get(TOKEN_CONFIG)


:art: Fix tense of tests description

:art: Fix tense of tests description

--- spec/table-element-spec.coffee
+++ spec/table-element-spec.coffee
@@ -727,12 +727,12 @@

         describe 'core:confirm', ->
           describe 'when the editor is still on the dynamic name', ->
-            it 'does not change the cell value', ->
+            it 'does not changes the cell value', ->
               atom.commands.dispatch(editorElement, 'core:confirm')
               expect(tableEditor.getScreenColumn(0).name).toEqual(undefined)

           describe 'when the editor is empty', ->
-            it 'does not change the cell value', ->
+            it 'does not changes the cell value', ->
               confirmHeaderCellEdit()
               expect(tableEditor.getScreenColumn(0).name).toEqual(undefined)

@@ -1459,7 +1459,7 @@

   describe 'tablr:align-left', ->
     describe 'when there is no target column', ->
-      it 'change the alignment of the active column', ->
+      it 'changes the alignment of the active column', ->
         tableEditor.getScreenColumn(tableEditor.getCursorPosition().column).align = 'right'

         atom.commands.dispatch(tableElement, 'tablr:align-left')
@@ -1467,7 +1467,7 @@
         expect(tableEditor.getScreenColumn(tableEditor.getCursorPosition().column).align).toEqual('left')

     describe 'when there is a target column', ->
-      it 'change the alignment of the target column', ->
+      it 'changes the alignment of the target column', ->
         tableElement.contextMenuColumn = 2
         tableEditor.getScreenColumn(2).align = 'right'

@@ -1477,13 +1477,13 @@

   describe 'tablr:align-center', ->
     describe 'when there is no target column', ->
-      it 'change the alignment of the active column', ->
+      it 'changes the alignment of the active column', ->
         atom.commands.dispatch(tableElement, 'tablr:align-center')

         expect(tableEditor.getScreenColumn(tableEditor.getCursorPosition().column).align).toEqual('center')

     describe 'when there is a target column', ->
-      it 'change the alignment of the target column', ->
+      it 'changes the alignment of the target column', ->
         tableElement.contextMenuColumn = 2

         atom.commands.dispatch(tableElement, 'tablr:align-center')
@@ -1492,13 +1492,13 @@

   describe 'tablr:align-right', ->
     describe 'when there is no target column', ->
-      it 'change the alignment of the active column', ->
+      it 'changes the alignment of the active column', ->
         atom.commands.dispatch(tableElement, 'tablr:align-right')

         expect(tableEditor.getScreenColumn(tableEditor.getCursorPosition().column).align).toEqual('right')

     describe 'when there is a target column', ->
-      it 'change the alignment of the target column', ->
+      it 'changes the alignment of the target column', ->
         tableElement.contextMenuColumn = 2

         atom.commands.dispatch(tableElement, 'tablr:align-right')


emits event when state changes occur

emits event when state changes occur

--- components/inputs/KDInputViewWithPreview.coffee
+++ components/inputs/KDInputViewWithPreview.coffee
@@ -37,10 +37,12 @@
           @$("div.preview_content").removeClass "hidden"
           @$("div.preview_switch").removeClass "content-hidden"
           @$().removeClass "content-hidden"
+          @emit "PreviewShown"
         else
           @$("div.preview_content").addClass "hidden"
           @$("div.preview_switch").addClass "content-hidden"
           @$().addClass "content-hidden"
+          @emit "PreviewHidden"

     @previewOnOffContainer = new KDView
       cssClass : "preview_switch"


manually set HTTP_FORWARDED_COUNT so that meteor.com hosting grabs the right IP address

--- demo/demo.coffee
+++ demo/demo.coffee
@@ -56,6 +56,10 @@
       @stop()

 if Meteor.isServer
+  # Try setting this so it works on meteor.com
+  # (https://github.com/oortcloud/unofficial-meteor-faq)
+  process.env.HTTP_FORWARDED_COUNT = 1
+
   Meteor.publish null, ->
     [
       Meteor.users.find {},


[devtools] Display more info when err occurs

--- dev_tools.coffee
+++ dev_tools.coffee
@@ -37,9 +37,13 @@
         log.info "Changing fields for #{konnectorName} with #{fields}."
         connector.change konnectorName, fields, (err) ->
             if err
+                log.error "Failed to change fields."
                 log.error err
             else
-                log.info "Fields were successfully changed for #{konnectorName}"
+                log.info """
+Fields were successfully changed for #{konnectorName}
+Note: only fields described in the connector file are stored.
+"""

     else
         log.error "Field values are required (see --fields option)."
@@ -51,6 +55,7 @@

     connector.run konnectorName, (err) ->
         if err
+            log.error "An error occured while trying to run the import."
             log.error err.message
         else
             log.info "Import is finished."
@@ -67,8 +72,11 @@

     if program.delete
         modelName = program.delete
-        data.delete modelName, ->
-            log.info 'All models were destroyed.'
+        data.delete modelName, (err) ->
+            if err
+                log.info 'Destroying models failed.'
+            else
+                log.info 'All models were destroyed.'

     else if program.show
         modelName = program.show


Reading assignments now also show blue pies for past due dates

--- src/components/scores/reading-cell.cjsx
+++ src/components/scores/reading-cell.cjsx
@@ -63,7 +63,7 @@
             isConceptCoach={isConceptCoach}
             size={20}
             value={TH.getCompletedPercent(task)}
-            isLate={TH.isLate(task)} />
+            isLate={TH.isDue(task)} />
           </span>
         </BS.OverlayTrigger>
       </div>


reverting routes to something working

reverting routes to something working

--- client/landing/app/MainApp/routes.coffee
+++ client/landing/app/MainApp/routes.coffee
@@ -80,9 +80,9 @@
           else if account
             appManager.tell "Members", "createContentDisplay", account

-    '/discussion/:staticLink':({staticLink})->
+    '/discussion/:title': ({title})->

-        bongo.api.JDiscussion.one "title": staticLink, (err, discussion)->
+        bongo.api.JDiscussion.one "title": title, (err, discussion)->
           if err then warn err
           else if discussion
             appManager.tell "Activity", "createContentDisplay", discussion


Fix comments in prefixes updater

Fix comments in prefixes updater

--- updaters/prefixes.coffee
+++ updaters/prefixes.coffee
@@ -163,13 +163,14 @@
     prefix 'tab-size',
             browsers: browsers

-  # Intrinsic & Extrinsic Sizing
+  # Intrinsic & extrinsic sizing
   @feature 'intrinsic-width', (browsers) =>
     prefix 'max-content', 'min-content', 'fit-content', 'fill-available',
             props:  ['width',  'min-width',  'max-width',
                      'height', 'min-height', 'max-height']
             browsers: browsers

+  # Zoom and grub cursos
   @feature 'css3-cursors-newer', (browsers) =>
     prefix 'zoom-in', 'zoom-out', 'grab', 'grabbing',
             props:  ['cursor']


Fix typo.

Fix typo.

Change assinging to assigning.

--- src/parser/ruby-parser.coffee
+++ src/parser/ruby-parser.coffee
@@ -169,7 +169,7 @@
   asignDefaultValue: (line, convention, commitUrl) ->
     convention = {lang: this.lang} unless convention
     (convention.asignDefaultValue =
-      title: "How to write assinging default values to method parameters"
+      title: "How to write assigning default values to method parameters"
       column: [
         {
           key: "space", display: "Use spaces",


custom: add option to draw pads on bottom layer

footprints for edge-mounted parts (mostly connectors) have pads on
top and bottom copper layer.
previously pad were only drawn on the top copper layer.
this change add the housing option "padBottom".
when "padBottom: true" is added to the pad group, the pads are
placed on the bottom layer.

--- src/pattern/default/custom.coffee
+++ src/pattern/default/custom.coffee
@@ -69,7 +69,7 @@
       width: housing['padWidth' + suffix]
       height: housing['padHeight' + suffix]
       shape: if padDiameter? then 'circle' else 'rectangle'
-      layer: ['topCopper', 'topMask', 'topPaste']
+      layer: if housing['padBottom' + suffix] then ['bottomCopper', 'bottomMask', 'bottomPaste'] else ['topCopper', 'topMask', 'topPaste']

   if housing['padPosition' + suffix]?
     hasPads = true


add config-save

add config-save

--- views/akashic-records-log.cjsx
+++ views/akashic-records-log.cjsx
@@ -147,6 +147,7 @@
     if configChecked[2] is true
       configChecked[0] = false
       configChecked[1] = false
+    config.set "plugin.Akashic.#{@props.contentType}.configChecked", JSON.stringify configChecked
     @setState
       configChecked: configChecked

@@ -154,6 +155,8 @@
     {activePage} = @state
     @rowChooseChecked = JSON.parse config.get "plugin.Akashic.#{@props.contentType}.checkbox", JSON.stringify @state.rowChooseChecked
     rowChooseChecked = JSON.parse JSON.stringify @rowChooseChecked
+    @configChecked = JSON.parse config.get "plugin.Akashic.#{@props.contentType}.configChecked", JSON.stringify @state.configChecked
+    configChecked = JSON.parse JSON.stringify @configChecked
     showAmount = config.get "plugin.Akashic.#{@props.contentType}.showAmount", @state.showAmount
     dataAfterFilter = @_filter @props.data, @state.filterKeys
     dataAfterFilterLength = dataAfterFilter.length
@@ -165,6 +168,7 @@
       dataShow = []
     @setState
       rowChooseChecked: rowChooseChecked
+      configChecked: configChecked
       showAmount: showAmount
       dataAfterFilter: dataAfterFilter
       dataAfterFilterLength: dataAfterFilterLength


Don't create bootstrap-tags inputs until they are needed

--- app/assets/javascripts/rglossa/react/components/centre/search_inputs/cwb_multiword_term.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/centre/search_inputs/cwb_multiword_term.jsx.coffee
@@ -19,13 +19,14 @@
     if @props.termIndex is 0
       @refs.searchfield.getDOMNode().focus()

-    tagsInput = $(@refs.taglist.getDOMNode()).tags(
-        promptText: ' '
-        afterDeletingTag: @afterDeletingTag)
-
-    term = @props.term
-    tagsInput.addTag(term.pos) if term.pos
-    tagsInput.addTag(feature.value) for feature in term.features
+    if @props.term.pos or @props.term.features.length
+      # If the term already contains a POS or grammatical features when the
+      # multiword term view is mounted, we need to create the bootstrap-tags input
+      # and fill it in
+      tagsInput = @getTagsInput()
+      term = @props.term
+      tagsInput.addTag(term.pos) if term.pos
+      tagsInput.addTag(feature.value) for feature in term.features

   componentWillUnmount: ->
@@ -40,8 +41,7 @@
     # search expression in the regex view, in which case the multiword view
     # will be mounted anew when we switch to it and the tags will be set up
     # by componentDidMount.
-
-    tagsInput = $(@refs.taglist.getDOMNode()).tags()
+    tagsInput = @getTagsInput()

     if nextProps.term.pos isnt @props.term.pos
       if @props.term.pos
@@ -54,6 +54,19 @@
       not currentFeatures.some (cf) -> cf.attr is f.attr and cf.value is f.value

     tagsInput.addTag(feature.value) for feature in newFeatures
+
+
+  getTagsInput: ->
+    tagListNode = $(@refs.taglist.getDOMNode())
+
+    if $(@getDOMNode()).find('.tags-input').length
+      # the bootstrap-tags input has already been created, so just return that
+      tagListNode.tags()
+    else
+      # create a new one
+      tagListNode.tags(
+        promptText: ' '
+        afterDeletingTag: @afterDeletingTag)

   changeTerm: (attribute, value) ->


Set available domains to domain list when backend returns a result, if the requested domain is not available make a second request to get suggestions from backend. And the build the UI based on that.

--- client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
@@ -52,9 +52,25 @@
     {createButton}        = @newDomainEntryForm.buttons
     {domains, domainName} = @newDomainEntryForm.inputs
     domainName            = "#{domainName.getValue()}.#{domains.getValue()}"
-    KD.remote.api.JDomain.getDomainInfo domainName, (err, status)->
-      createButton.hideLoader()
-      return warn err if err
+    {getDomainInfo, getDomainSuggestions} = KD.remote.api.JDomain
+
+    getDomainInfo domainName, (err, status)=>
+
+      if err
+        createButton.hideLoader()
+        return warn err
+
+      if status.available
+        @newDomainEntryForm.setAvailableDomainsData \
+          [{domain:domainName, price:status.price}]
+        createButton.hideLoader()
+      else
+        @newDomainEntryForm.domainList?.destroy?()
+        getDomainSuggestions domainName, (err, suggestions)=>
+          createButton.hideLoader()
+          return warn err if err
+          @newDomainEntryForm.setAvailableDomainsData suggestions
+
       new KDNotificationView
         title: if status.available then 'Yay its available' else 'Sorry dude.'


Workspace: Added random! load balancing with 5 instances.

--- client/app/CommonViews/workspace/collaborativeworkspace.coffee
+++ client/app/CommonViews/workspace/collaborativeworkspace.coffee
@@ -4,10 +4,14 @@

     super options, data

-    @sessionData       = []
-    @firepadRef        = new Firebase "https://hulogggg.firebaseIO.com/"
-    @sessionKey        = options.sessionKey or @createSessionKey()
-    @workspaceRef      = @firepadRef.child @sessionKey
+    @sessionData  = []
+    instances     = ["instance1", "instance2", "instance3", "instance4", "instance5"]
+    myInstance    = instances[KD.utils.getRandomNumber(5) - 1] # should use config
+    @firepadRef   = new Firebase "https://#{myInstance}.firebaseIO.com/"
+    @sessionKey   = options.sessionKey or @createSessionKey()
+    @workspaceRef = @firepadRef.child @sessionKey
+
+    log "joining to", myInstance

     @createUserListContainer()
     @createLoader()


Bootstrapper: Updated sample code.

Bootstrapper: Updated sample code.

--- src/coffee/Bootstrapper.coffee
+++ src/coffee/Bootstrapper.coffee
@@ -13,6 +13,13 @@
   constructor: ->
     o = new spark.core.Object
     v = new spark.core.View
+      tagName   : 'header'
+      cssClass  : 'main'
+      domId     : 'big-header'
+      template  : '
HELLO SPARK
'
+
+    goog.exportSymbol 'obj', o
+    goog.exportSymbol 'vvv', v

 window.addEventListener "load", (->


Remove warnings.

Remove warnings.

--- src/coffee/csv/exporter.coffee
+++ src/coffee/csv/exporter.coffee
@@ -24,13 +24,13 @@
           throw { errors } if _.size errors
           @write [rawHeader] # pass array of array to ensure newline in CSV
           rawHeader
-      parser.on 'error', (error) =>
+      parser.on 'error', (error) ->
         reject error
-      parser.on 'finish', =>
+      parser.on 'finish', ->
         resolve 'Header loaded.'

-      stream = fs.createReadStream(fileName)
-      stream.on 'error', (error) =>
+      stream = fs.createReadStream fileName
+      stream.on 'error', (error) ->
         reject error

       stream.pipe(parser)
@@ -38,15 +38,15 @@
   export: (templateFileName, outPutFileName) ->
     new Promise (resolve, reject) =>
       @stream = fs.createWriteStream outPutFileName
-      @stream.on 'finish', =>
-        resolve 'OK'
+      @stream.on 'finish', ->
+        resolve 'Export done.'
       @stream.on 'error', (error) ->
         reject error

       @loadTemplate templateFileName
-      .catch (error) =>
+      .catch (error) ->
         reject error
-      .then () =>
+      .then =>

         processChunk = (payload) =>
           @logger.info "Processing #{_.size payload.body.results} categories"


Also disable @#$%@# bootstraps idea of reliable transitions.

--- core/app/assets/javascripts/test_counter_cookie_storage.coffee
+++ core/app/assets/javascripts/test_counter_cookie_storage.coffee
@@ -1,5 +1,6 @@
 if window.test_counter &&
     /^(::1|127\.0\.0\.1|localhost)$/.test(window.location.hostname)
   window.$.fx.off = true
+  $(() -> window.$.support.transition = undefined)
   window.addEventListener 'unload', -> #phantomjs doesn't support beforeunload
     document.cookie = "test_counter=#{window.test_counter};path=/"


Fix highlighting comment when adding as evidence.

--- core/app/backbone/views/fact_relations/add_comment_view.coffee
+++ core/app/backbone/views/fact_relations/add_comment_view.coffee
@@ -1,7 +1,7 @@
 class window.AddCommentView extends Backbone.Marionette.ItemView
   className: 'add-comment'
   events:
-    'click .js-post': 'addDefaultModel'
+    'click .js-post': 'addWithHighlight'
     'click .js-switch': 'switchCheckboxClicked'
     'blur  .js-content': 'updateModel'
     'keydown .js-content': 'parseKeyDown'
@@ -14,7 +14,10 @@
   parseKeyDown: (e) =>
     code = e.keyCode || e.which
     @updateModel()
-    @addDefaultModel() if code is 13
+    @addWithHighlight if code is 13
+
+  addWithHighlight: ->
+    @addDefaultModel({highlight: true })

   initializeModel: ->
     @model = new Comment(content: '', created_by: currentUser)


Fix: not save config

Fix: not save config

--- index.cjsx
+++ index.cjsx
@@ -91,8 +91,11 @@
     handleShipChange: (e) ->
       ship_id = parseInt(e.target.value)
       return unless ship_id != NaN
+      # Save secretary config
       @setState
         notifySecretary: ship_id
+      config.set('plugin.secretary.ship', ship_id)
+      # Update secretary voice
       if ship_id
         @updateNotifyConfig(ship_id)
       else


Bump to 1.1.0

Bump to 1.1.0

--- index.cjsx
+++ index.cjsx
@@ -34,7 +34,7 @@
   displayName: <FontAwesome name='info-circle' />{' ' + __('Battle Detail')}
   author: 'Dazzy Ding'
   link: 'https://github.com/yukixz'
-  version: '1.0.0'
+  version: '1.1.0'
   description: __ "Show battle detail"
   handleClick: ->
     window.battleDetailWindow.show()


Fix nowHp

Fix nowHp

--- index.cjsx
+++ index.cjsx
@@ -243,9 +243,10 @@
             damageHp[i] = 0
           getShip = null
           shipLv[i] = -1 for i in [6..11]
+          nowHp = Object.clone afterHp
           if body.api_enemy?
             if enemyInformation[body.api_enemy.api_enemy_id]?
-              [shipName, shipLv, maxHp, nowHp, enemyFormation, enemyTyku] = getMapEnemy shipName, shipLv, maxHp, afterHp, enemyFormation, enemyTyku, enemyInformation[body.api_enemy.api_enemy_id]
+              [shipName, shipLv, maxHp, nowHp, enemyFormation, enemyTyku] = getMapEnemy shipName, shipLv, maxHp, nowHp, enemyFormation, enemyTyku, enemyInformation[body.api_enemy.api_enemy_id]
               afterHp = Object.clone afterHp
             else
               jsonId = body.api_enemy.api_enemy_id


Add more error checking, and more descriptive reasons for failure

--- src/scripts/meme_generator.coffee
+++ src/scripts/meme_generator.coffee
@@ -35,6 +35,9 @@
 # Author:
 #   skalnik

+
+inspect = require('util').inspect
+
 module.exports = (robot) ->
   unless robot.brain.data.memes?
     robot.brain.data.memes = [
@@ -196,19 +199,33 @@
       text0: text0,
       text1: text1
     .get() (err, res, body) ->
-      result = JSON.parse(body)['result']
-      if result? and result['instanceUrl']? and result['instanceImageUrl']? and result['instanceID']?
-        instanceID = result['instanceID']
-        instanceURL = result['instanceUrl']
-        img = result['instanceImageUrl']
-        msg.http(instanceURL).get() (err, res, body) ->
-          # Need to hit instanceURL so that image gets generated
-          if preferredDimensions?
-            callback "http://images.memegenerator.net/instances/#{preferredDimensions}/#{instanceID}.jpg"
-          else
-            callback "http://images.memegenerator.net/instances/#{instanceID}.jpg"
-      else
-        msg.reply "Sorry, I couldn't generate that image."
+      if err
+        msg.reply "Ugh, I got an exception trying to contact memegenerator.net:", inspect(err)
+        return
+
+      jsonBody = JSON.parse(body)
+      success = jsonBody.success
+      errorMessage = jsonBody.errorMessage
+      result = jsonBody.result
+
+      if not success
+        msg.reply "Ugh, stupid request to memegenerator.net failed: \"#{errorMessage}.\" What does that even mean?"
+        return
+
+      instanceID = result?.instanceID
+      instanceURL = result?.instanceUrl
+      img = result?.instanceImageUrl
+
+      unless instanceID and instanceURL and img
+        msg.reply "Ugh, I got back weird results from memegenerator.net. Expected an image URL, but couldn't find it in the result. Here's what I got:", inspect(jsonBody)
+        return
+
+      msg.http(instanceURL).get() (err, res, body) ->
+        # Need to hit instanceURL so that image gets generated
+        if preferredDimensions?
+          callback "http://images.memegenerator.net/instances/#{preferredDimensions}/#{instanceID}.jpg"
+        else
+          callback "http://images.memegenerator.net/instances/#{instanceID}.jpg"

 khanify = (msg) ->
   msg = msg.toUpperCase()


Set active flag even if page is loaded outside the context of a book

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -36,6 +36,8 @@
           if @isBook()
             if @get('contents').length
               @setPage(options.page or 1) # Default to page 1
+          else
+            @set('active', true)

         .fail (model, response, options) =>
           @set('error', response?.status or model?.status or 9000)


[data] add some destructuring magic

[data] add some destructuring magic

--- source/data/index.coffee
+++ source/data/index.coffee
@@ -1,17 +1,17 @@
 { merge } = require('lodash')

-module.exports = (grunt) ->
-  pkg = grunt.file.readJSON 'package.json'
-  sitename = grunt.config('env.sitename')
-  buildRoot = grunt.config('path.build.root') + '/'
+module.exports = ({ config, file: { readJSON } }) ->
+  pkg = readJSON('package.json')
+  sitename = config('env.sitename')
+  buildRoot = config('path.build.root') + '/'

   data =
     path:
-      fonts: grunt.config('path.build.fonts').replace(buildRoot, '')
-      images: grunt.config('path.build.images').replace(buildRoot, '')
-      scripts: grunt.config('path.build.scripts').replace(buildRoot, '')
-      styles: grunt.config('path.build.styles').replace(buildRoot, '')
-      sprites: grunt.config('path.build.sprites').replace(buildRoot, '')
+      fonts: config('path.build.fonts').replace(buildRoot, '')
+      images: config('path.build.images').replace(buildRoot, '')
+      scripts: config('path.build.scripts').replace(buildRoot, '')
+      styles: config('path.build.styles').replace(buildRoot, '')
+      sprites: config('path.build.sprites').replace(buildRoot, '')
       source: '<%= path.source %>'
       build: '<%= path.build %>'
     site:
@@ -21,8 +21,8 @@
       homepage: if sitename then "https://#{sitename}" else pkg.homepage
       twitter: pkg.twitter
       version: pkg.version
-      locales: grunt.config('locales')
-      baseLocale: grunt.config('baseLocale')
+      locales: config('locales')
+      baseLocale: config('baseLocale')
       googleAnalyticsId: false # 'UA-XXXXX-X'
       yandexMetrikaId: false # 'XXXXXX'
     env:


Remove deprecated sendfile()

Remove deprecated sendfile()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/bounded.coffee
+++ test/bounded.coffee
@@ -11,6 +11,19 @@
   equals obj.bounds().y, 10
   equals obj.bounds().width, 50
   equals obj.bounds().height, 100
+
+test "Bounds returns correct centered x, y, width, height", ->
+  obj = GameObject
+    x: -5
+    y: 20
+    width: 100,
+    height: 200
+
+  obj.include(Bounded)
+  bounds = obj.centeredBounds()
+
+  equals bounds.x, 0
+  equals bounds.y, 30

 test "Bounds returns correct x, y when called with offset", ->
   obj = GameObject


OX-142 Fix Buttons

OX-142 Fix Buttons

--- app/assets/javascripts/oxalis/controller/camera_controller.coffee
+++ app/assets/javascripts/oxalis/controller/camera_controller.coffee
@@ -57,34 +57,35 @@

     camera = @cameras[VIEW_3D]
     b = @model.scaleInfo.voxelToNm(@model.binary.cube.upperBoundary)
+    pos = @model.scaleInfo.voxelToNm(@model.flycam.getPosition())
     time = 800
     notify = => @trigger("cameraPositionChanged")
     @tween = new TWEEN.Tween({ notify: notify, upX: camera.up.x, upY: camera.up.y, upZ: camera.up.z, camera: camera, flycam: @flycam,sv : @skeletonView,x: camera.position.x,y: camera.position.y,z: camera.position.z,l: camera.left,r: camera.right,t: camera.top,b: camera.bottom })
     switch id
       when VIEW_3D
-        scale = Math.sqrt(b[0]*b[0]+b[1]*b[1])/1.8
+        diagonal = Math.sqrt(b[0]*b[0]+b[1]*b[1])
+        scale = diagonal/1.8
         # Calulate the x coordinate so that the vector from the camera to the cube's middle point is
         # perpendicular to the vector going from (0, b[1], 0) to (b[0], 0, 0).
-        x = b[1] * b[1] / (2 * b[0]) + b[0] / 2
-        @tween.to({  x: x, y: b[1], z: b[2] * 0.1, upX: 0, upY: 0, upZ: -1, l: -scale, r: scale, t: scale-scale*0.1, b: -scale-scale*0.1}, time)
+        @tween.to({  x: pos[0] + b[1] / diagonal, y: pos[1] + b[0] / diagonal, z: pos[2] - 1/2, upX: 0, upY: 0, upZ: -1, l: -scale, r: scale, t: scale, b: -scale }, time)
         .onUpdate(@updateCameraPrev)
         .start()
         #rotation: (-36.25, 30.6, 20.47) -> (-36.25, 30.6, 20.47)
       when PLANE_XY
         scale = (Math.max b[0], b[1] * 1.12)/1.75
-        @tween.to({  x: b[0]/2, y: b[1]/2, z: 0, upX: 0, upY: -1, upZ: 0, l: -scale, r: scale, t: scale+scale*0.12, b: -scale+scale*0.12}, time)
+        @tween.to({  x: pos[0], y: pos[1], z: pos[2] - 1, upX: 0, upY: -1, upZ: 0, l: -scale, r: scale, t: scale+scale*0.12, b: -scale+scale*0.12}, time)
         .onUpdate(@updateCameraPrev)
         .start()
         #rotation: (-90, 0, 90) -> (-90, 0, 0)
       when PLANE_YZ
         scale = (Math.max b[1] * 1.12, b[2])/1.75
-        @tween.to({  x: b[0], y: b[1]/2, z: b[2]/2, upX: 0, upY: -1, upZ: 0, l: -scale, r: scale, t: scale+scale*0.12, b: -scale+scale*0.12}, time)
+        @tween.to({  x: pos[0] + 1, y: pos[1], z: pos[2], upX: 0, upY: -1, upZ: 0, l: -scale, r: scale, t: scale+scale*0.12, b: -scale+scale*0.12}, time)
         .onUpdate(@updateCameraPrev)
         .start()
         #rotation: (0, 90, 0) -> (-90, 90, 0)
       when PLANE_XZ
         scale = (Math.max b[0], b[2] * 1.12)/1.75
-        @tween.to({  x: b[0]/2, y: b[1], z: b[2]/2, upX: 0, upY: 0, upZ: -1, l: -scale, r: scale, t: scale+scale*0.12, b: -scale+scale*0.12}, time)
+        @tween.to({  x: pos[0], y: pos[1] + 1, z: pos[2], upX: 0, upY: 0, upZ: -1, l: -scale, r: scale, t: scale+scale*0.12, b: -scale+scale*0.12}, time)
         .onUpdate(@updateCameraPrev)
         .start()
         #rotation: (0, 0, 0) -> (0, 0, 0)


enable compression for smc-project raw server for speedup.

--- src/smc-project/raw_server.coffee
+++ src/smc-project/raw_server.coffee
@@ -34,6 +34,10 @@

     raw_port_file  = misc_node.abspath("#{data_path}/raw.port")
     raw_server     = express()
+
+    # suggested by http://expressjs.com/en/advanced/best-practice-performance.html#use-gzip-compression
+    compression = require('compression')
+    raw_server.use(compression())

     # Needed for POST file to custom path.
     raw_server.use(body_parser.urlencoded({ extended: true }))


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -23,6 +23,6 @@

   obj.include Metered

-
+  obj.meter

 module()


movable velocity and acceleration getters

--- test/movable.coffee
+++ test/movable.coffee
@@ -40,7 +40,7 @@
   60.times ->
     particle.update(1/60)

-  equals particle.I.velocity.x, 11
-  equals particle.I.velocity.y,
+  equals particle.I.velocity.x, 0
+  equals particle.I.velocity.y, 30

 module()


isWin is false for game-in-progress

isWin is false for game-in-progress


only fire impression on experiment

only fire impression on experiment

--- src/mobile/apps/home/client/view.coffee
+++ src/mobile/apps/home/client/view.coffee
@@ -21,12 +21,13 @@

   initialize: ->
     @collection = new ShowsFeed
-    analytics.track("Impression", {
-      context_page: "Home",
-      context_module: "HubEntrypoint",
-      subject: "Featured Categories",
-    })
-    splitTest("homepage_collection_hub_entrypoints_test_qa").view()
+    if sd.HOMEPAGE_COLLECTION_HUB_ENTRYPOINTS_TEST == "experiment"
+      analytics.track("Impression", {
+        context_page: "Home",
+        context_module: "HubEntrypoint",
+        subject: "Featured Categories",
+      })
+    splitTest("homepage_collection_hub_entrypoints_test").view()

     @slideshow = new Flickity '#carousel-track',
       cellAlign: 'left'


Fix typo

Fix typo

--- test/service.coffee
+++ test/service.coffee
@@ -203,7 +203,7 @@
         , (err, started) ->
           return next err if err
           started.should.eql 0
-          # Detect already stoped
+          # Detect already stopped
           mecano.service
             ssh: ssh
             name: 'ntp'


Fix show services sr-only button on NVDA

--- src/views/service-tree.coffee
+++ src/views/service-tree.coffee
@@ -27,6 +27,7 @@
             'mouseenter .service-node .show-services-button': 'showTooltip'
             'mouseleave .service-node .show-services-button': 'removeTooltip'
             'keydown .service-tree .show-services ': @keyboardHandler @showServices, ['enter', 'space']
+            'click .service-tree .show-services ': @showServices
         type: 'service-tree'

         hideContents: ->


pubsub_client: fix <actor/> placement

pubsub_client: fix <actor/> placement

--- src/xmpp/pubsub_client.coffee
+++ src/xmpp/pubsub_client.coffee
@@ -91,13 +91,13 @@
 class PubsubRequest extends Request
     xmlns: NS.PUBSUB
     requestIq: ->
-        pubsubEl = @pubsubChild().root()
+        pubsubEl = new xmpp.Element('iq', type: @iqType()).
+            c('pubsub', xmlns: @xmlns)
+        pubsubEl.cnode(@pubsubChild().root())
         if @opts.actor
             pubsubEl.c('actor', xmlns: NS.BUDDYCLOUD_V1).
                 t(@opts.actor)
-        new xmpp.Element('iq', type: @iqType()).
-            c('pubsub', xmlns: @xmlns).
-            cnode(pubsubEl)
+        pubsubEl.up()

     iqType: ->
         throw new TypeError("Unimplemented request")


payment: show the dock after user is logged out when workflow is completed

--- client/Main/payment/paymentcontroller.coffee
+++ client/Main/payment/paymentcontroller.coffee
@@ -153,8 +153,8 @@
           { cardFirstName: firstName, cardLastName: lastName } = billing
           { JUser } = KD.remote.api
           JUser.convert { firstName, lastName, email }, (err) ->
-            JUser.logout()
-            KD.singletons.dock.getView().show()
+            JUser.logout ->
+              KD.singletons.dock.getView().show()
       .enter()

     workflow


ComputeProvider::fetchMachines: fetchStacks first then concat machines from them

--- client/Main/providers/computeprovider.coffee
+++ client/Main/providers/computeprovider.coffee
@@ -12,6 +12,19 @@
     KD.remote.api.JStack.some {}, (err, stacks = [])->
       return callback err  if err?
       callback null, ComputeProvider.stacks = stacks
+
+
+  @fetchMachines = (callback)->
+
+    @fetchStacks (err, stacks)->
+      return callback err  if err?
+
+      machines = []
+      stacks.forEach (stack)->
+        stack.machines.forEach (machine)->
+          machines.push new Machine { machine }
+
+      callback null, machines

   @credentialsFor = (provider, callback)->


init gold master test, which is kinda dumb. oops.

![oops](http://stream1.gifsoup.com/view5/3997062/rick-perry-oops-o.gif)

--- test/get_value_gm_test.coffee
+++ test/get_value_gm_test.coffee
@@ -1,3 +1,15 @@
+# This is a "Gold master" test. If you need to update these constants, you can do so
+# by manually clicking through the kitchen sink form in the Chrome dev console and running:
+#
+#   copy(JSON.stringify($('[data-formrenderer]').data('form-renderer').getValue()))
+#
+# ...which will copy the stringified JSON to your clipboard. You should then manually verify
+# the output, and if you're satisfied, you can finally update the constant.
+
+EXPECTED_BLANK_VALUES = '{"37":{"0":false,"1":false},"44":{"am_pm":"AM"},"46":"","48":{"country":"US"},"49":{"0":["",""],"1":["",""]}}'
+
+EXPECTED_PRESENT_VALUES = '{"35":"foo","36":"bar","37":{"0":"on","1":false},"39":"Choice #2","40":"Choice #2","41":{"dollars":"12","cents":"99"},"42":"123","43":{"month":"12","day":"30","year":"2014"},"44":{"am_pm":"PM","hours":"6","minutes":"01","seconds":"30"},"45":"http://www.google.com","46":"","47":"foo@bar.com","48":{"country":"GB","street":"123 main st"},"49":{"0":["hey",""],"1":["","nay"]}}'
+
 before ->
   $('body').html('<div data-formrenderer />')

@@ -5,5 +17,37 @@
   before ->
     @fr = new FormRenderer Fixtures.FormRendererOptions.KITCHEN_SINK()

+    @fieldCid
+
   it 'renders ok', ->
     expect(@fr).to.be.ok
+
+  it 'serializes the blank values', ->
+    expect(JSON.stringify(@fr.getValue())).to.equal(EXPECTED_BLANK_VALUES)
+
+  it 'serializes present values', ->
+    $('.response_field_text input').val('foo').trigger('input')
+    $('.response_field_paragraph textarea').val('bar').trigger('input')
+    $('.response_field_checkboxes input').first().click().trigger('change')
+    $('.response_field_radio input').last().click().trigger('change')
+    $('.response_field_dropdown select').val('Choice #2').trigger('change')
+    $('.response_field_price input').first().val('12').trigger('input')
+    $('.response_field_price input').last().val('99').trigger('input')
+    $('.response_field_number input').last().val('123').trigger('input')
+    $('.response_field_date input').eq(0).val('12').trigger('input')
+    $('.response_field_date input').eq(1).val('30').trigger('input')
+    $('.response_field_date input').eq(2).val('2014').trigger('input')
+    $('.response_field_time input').eq(0).val('6').trigger('input')
+    $('.response_field_time input').eq(1).val('01').trigger('input')
+    $('.response_field_time input').eq(2).val('30').trigger('input')
+    $('.response_field_time select').val('PM').trigger('change')
+    $('.response_field_website input').val('http://www.google.com').trigger('input')
+    $('.response_field_email input').val('foo@bar.com').trigger('input')
+    $('.response_field_address input').eq(0).val('123 main st').trigger('input')
+    $('.response_field_address select').val('GB').trigger('change')
+    $('.response_field_table textarea').eq(0).val('hey').trigger('input')
+    $('.response_field_table textarea').eq(3).val('nay').trigger('input')
+
+    # @todo file, mapmarker
+
+    expect(JSON.stringify(@fr.getValue())).to.equal(EXPECTED_PRESENT_VALUES)


Modify the speakers page to include only accepted speakers

--- client/controller/speakers-controller.coffee
+++ client/controller/speakers-controller.coffee
@@ -1,6 +1,6 @@
 class @SpeakersController extends RouteController

-  waitOn: -> Meteor.subscribe('speakers')
+  waitOn: -> Meteor.subscribe('proposals')

   after: -> document.title = "Speakers | Reversim Summit 2014"


cc: add setStackTemplateAccessLevel listener

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -28,6 +28,7 @@
 runMiddlewares       = require 'app/util/runMiddlewares'
 TestMachineMiddleware = require './middlewares/testmachine'

+whoami = require 'app/util/whoami'

 { actions : HomeActions } = require 'home/flux'
 require './config'
@@ -71,6 +72,7 @@

       groupsController.on 'StackTemplateChanged', @bound 'checkGroupStacks'
       groupsController.on 'StackAdminMessageCreated', @bound 'handleStackAdminMessageCreated'
+      groupsController.on 'SetStackTemplateAccessLevel', @bound 'setStackTemplateAccessLevel'

       @fetchStacks =>

@@ -1000,6 +1002,32 @@
   sharingStackTemplate: (_id, type) ->
     remote.api.JStackTemplate.one { _id }, (err, template) ->
       template.setAccess type
+
+
+  setStackTemplateAccessLevel: (params) ->
+
+    { reactor } = kd.singletons
+    { contents: { id: _id, change: { $set: { accessLevel } } } } = params
+
+    if accessLevel is 'group'
+      new kd.NotificationView title : 'Stack Template is Shared With Group'
+    else
+      new kd.NotificationView title : 'Stack Template is Unshared With Group'
+    remote.api.JStackTemplate.one { _id }, (err, stackTemplate) =>
+
+      if stackTemplate
+        reactor.dispatch 'REMOVE_STACK_TEMPLATE_SUCCESS', { id: stackTemplate._id }
+        reactor.dispatch 'UPDATE_STACK_TEMPLATE_SUCCESS', { stackTemplate }
+      else
+        reactor.dispatch 'REMOVE_STACK_TEMPLATE_SUCCESS', { id: _id }
+
+      remote.api.JComputeStack.one { baseStackId: _id }, (err, stack) =>
+        if stack
+          @checkStackRevisions()  if accessLevel is 'group'
+          if accessLevel is 'private' and not stackTemplate
+            new kd.NotificationView { title: "You have no longer access to #{stack.title}" }
+
+
   checkGroupStacks: ->

     @checkStackRevisions()


UserEnvironmentDataProvider: Fixed revive for loop.

:facepalm: detected.

--- client/app/lib/userenvironmentdataprovider.coffee
+++ client/app/lib/userenvironmentdataprovider.coffee
@@ -35,7 +35,7 @@

     return no  unless @hasData()

-    for section in globals.userEnvironmentData
+    for key, section of globals.userEnvironmentData
       for obj in section
         obj.machine = remote.revive obj.machine
         for ws, i in obj.workspaces


One more fix regarding previous commit.

One more fix regarding previous commit.

--- client/coffeescript/templates/post_wrapper.coffee
+++ client/coffeescript/templates/post_wrapper.coffee
@@ -9,7 +9,7 @@
     return Session.get("group_#{@parent_id}")

   title_current_group: ->
-    if @cur.name?
+    if @cur.name? and @cur.name isnt 'all'
       return "Posts tagged as '#{@cur.name}'"
     else
       return "All Posts"


Make it work for now.

Make it work for now.

--- client/landing/app/MainApp/login/loginview.coffee
+++ client/landing/app/MainApp/login/loginview.coffee
@@ -190,6 +190,11 @@
           duration  : 2000
         @loginForm.reset()

+        @hideView()
+
+        if KD.config.profileEntryPoint? or KD.config.groupEntryPoint?
+          $('#group-landing').css 'opacity', 0
+
   doRequest:(formData)->

     KD.remote.api.JInvitationRequest.create formData, (err, result)=>


Fixed issue where subscription data not immediately available to populate room fields on relabel

--- client/views/app/sideNav/privateGroupsFlex.coffee
+++ client/views/app/sideNav/privateGroupsFlex.coffee
@@ -102,14 +102,6 @@
 		else
 			Template.instance().error.set(err)

-Template.privateGroupsFlex.onRendered ->
-	relabelRoom = this.data.relabelRoom
-	if relabelRoom?
-		Meteor.subscribe 'room', relabelRoom
-		roomData = ChatRoom.findOne relabelRoom
-		console.log roomData
-		if roomData
-			this.find('#pvt-group-name').value = roomData.name

 Template.privateGroupsFlex.onCreated ->
@@ -125,12 +117,25 @@
 		instance.find('#pvt-group-members').value = ''
 		instance.roomData = undefined

-	instance.roomData = ChatRoom.findOne instance.data.relabelRoom, { fields: { usernames: 1, t: 1, name: 1 } }
-	if instance.roomData?.usernames
-		users = instance.roomData.usernames
-		for user in users
-			instance.selectedUserNames[user] = user
-			instance.selectedUsers.set instance.selectedUsers.get().concat user
+
+
+	# Tracker.autorun function that gets re-executed on changes to the (reactive)
+	# subscription to the 'room' publication. Once it is flagged as 'ready', stop
+	# re-executing the function and populate the room name, members, labels data.
+	# Function automatically stops if template is closed.
+	instance.autorun (c) ->
+		sub = Meteor.subscribe('room', instance.data.relabelRoom)
+		if sub.ready()
+			c.stop()
+			roomData = ChatRoom.findOne instance.data.relabelRoom
+			if roomData
+				instance.find('#pvt-group-name').value = roomData.name
+				users = roomData.usernames
+				for user in users
+					instance.selectedUserNames[user] = user
+					instance.selectedUsers.set instance.selectedUsers.get().concat user
+
+

 	# other conversation members
 	instance.otherMembers = _.without(instance.data.members, Meteor.userId())


Added SynchronizedTime to Game class

Added SynchronizedTime to Game class

--- game.coffee
+++ game.coffee
@@ -19,6 +19,8 @@

     @players = [@player0, @player1, @player2, @player3]

+    @currtime = SynchronizedTime.getTime()
+

   key_down: (player, key, coord) ->


ComponentLoader: Don't fail on missing getIcon function

This can happen when loaded component is using an older version of NoFlo
which does not define the getIcon method.

--- src/lib/ComponentLoader.coffee
+++ src/lib/ComponentLoader.coffee
@@ -121,7 +121,7 @@

   setIcon: (name, instance) ->
     # See if component has an icon
-    return if instance.getIcon()
+    return if not instance.getIcon or instance.getIcon()

     # See if library has an icon
     [library, componentName] = name.split '/'