Set hreflang and rel="alternate" on Link.

--- src/scripts/views/link.coffee
+++ src/scripts/views/link.coffee
@@ -8,13 +8,17 @@
 module.exports = React.createClass
 	displayName: 'Link'
 	render: ->
-		props = Immutable.fromJS(@props).toJS()
-		if props.lang
-			props.params.lang = props.lang
+		props = Immutable.fromJS(@props).filterNot(
+			utils.keyIn 'lang', 'href'
+		).toJS()
+		if @props.lang
+			props.params.lang = @props.lang
+			props.hrefLang = @props.lang
 		if !props.to
-			if props.href
-				href = props.href
+			if @props.href
+				href = @props.href
 			else
 				href = URL.getPath props.params
+				props.rel = 'alternate'
 			props.to = URL.getHref href, props.params
 		React.createElement Router.Link, props


Fix install logs are not shown

Fix install logs are not shown

--- src/main/coffee/bower.coffee
+++ src/main/coffee/bower.coffee
@@ -152,7 +152,9 @@
       deferred.reject()
     .on 'end', (installed) ->
       if isSerialInstall
-        log.i "Installed: #{i.pkgMeta.name}##{i.pkgMeta.version}#{if offline then " (offline)"}" for i in installed
+        for key in Object.keys(installed)
+          i = installed[key]
+          log.i "Installed: #{i.pkgMeta.name}##{i.pkgMeta.version}#{if offline then " (offline)"}"
         hasDependencies = Object.keys(installed).length > 1
         if cached and validate and !hasDependencies
           # Installed with cache, but validation occurred
@@ -182,7 +184,9 @@
           log.w "          }"
           log.w "      }"
       else
-        log.i "Installed: #{i.pkgMeta.name}##{i.pkgMeta.version}" for i in installed
+        for key in Object.keys(installed)
+          i = installed[key]
+          log.i "Installed: #{i.pkgMeta.name}##{i.pkgMeta.version}"
       deferred.resolve()
     deferred.promise


die whitespace, die

die whitespace, die

--- src/components/course-settings/settings.cjsx
+++ src/components/course-settings/settings.cjsx
@@ -44,9 +44,9 @@
           id={@props.courseId}
           store={store}
           actions={actions}
-          renderItem={=> <TeacherRoster
-            store={store}
-            courseRoles={course.roles}
+          renderItem={=> <TeacherRoster
+            store={store}
+            courseRoles={course.roles}
             courseId={@props.courseId}/>}
         />
       </div>


Use format index as key

Use format index as key

In some cases in the editor, formats may be duplicated before save

--- src/components/question/formats-listing.cjsx
+++ src/components/question/formats-listing.cjsx
@@ -10,12 +10,11 @@
   propTypes:
     formats: React.PropTypes.arrayOf(React.PropTypes.string).isRequired

-
   render: ->

Formats:

-      {for format in @props.formats
-        {format}}
+      {for format, i in @props.formats
+        {format}}


use pure-render mixin to speed up view

use pure-render mixin to speed up view

--- src/components/questions/exercise-cards.cjsx
+++ src/components/questions/exercise-cards.cjsx
@@ -1,5 +1,7 @@
 React = require 'react'
 BS = require 'react-bootstrap'
+{addons} = require 'react/addons'
+

 {TocStore} = require '../../flux/toc'
 {ExerciseActions, ExerciseStore} = require '../../flux/exercise'
@@ -14,6 +16,8 @@
 Icon = require '../icon'

 SectionsExercises = React.createClass
+
+  mixins: [addons.PureRenderMixin]

   propTypes:
     exercises:   React.PropTypes.array.isRequired
@@ -75,7 +79,7 @@
     scrollFast: React.PropTypes.bool
     onExerciseToggle: React.PropTypes.func.isRequired

-  mixins: [ScrollTo]
+  mixins: [ScrollTo, addons.PureRenderMixin]

   componentDidMount:   ->
     @scrollToSelector('.exercise-sections', {immediate: @props.scrollFast})
@@ -83,7 +87,7 @@
   getScrollTopOffset: -> if @props.scrollFast then 110 else 150 # no idea why the difference, sorry :(

   render: ->
-    chapter_sections = _.keys(@props.exercises.grouped).sort()
+    chapter_sections = _.keys @props.exercises.grouped

     <div className="exercise-cards">
       {for cs in chapter_sections


When there are icons, reposition their tethers on load

--- js-library/app/js/jail_iframe/classes/icon_buttons.coffee
+++ js-library/app/js/jail_iframe/classes/icon_buttons.coffee
@@ -1,7 +1,19 @@
+there_are_icons = false
+
+FactlinkJailRoot.host_loaded_promise.then ->
+  FactlinkJailRoot.trigger 'reposition_icons'
+
+FactlinkJailRoot.on 'reposition_icons', ->
+  if there_are_icons
+    Tether.position()
+    console.log 'repositioned!'
+
 class IconButton
   constructor: (options) ->
     @_targetElement = options.targetElement
     @_targetOffset = options.targetOffset
+
+    there_are_icons = true
     @$el = $ """
       <factlink-icon-button>
         <factlink-icon-button-bubble>


fixed a bug in event constructor

fixed a bug in event constructor

--- lib/assets/javascripts/websocket_rails/event.js.coffee
+++ lib/assets/javascripts/websocket_rails/event.js.coffee
@@ -9,7 +9,7 @@
     attr     = data[1]
     @id      = if attr['id']? then attr['id'] else (((1+Math.random())*0x10000)|0)
     @channel = if attr.channel? then attr.channel
-    @data    = if attr.data? then attr.data else ""
+    @data    = if attr.data? then attr.data else attr
     @connection_id = data[2]
     @success_callback = success_callback
     @failure_callback = failure_callback


fixed octal

fixed octal

--- src/middleware/router.coffee
+++ src/middleware/router.coffee
@@ -274,7 +274,7 @@
 ###
 sendMLLPSocketRequest = (ctx, route, options) ->

-  endChar = String.fromCharCode(034) + '\r'
+  endChar = String.fromCharCode('034') + '\r'
   defered = Q.defer()
   requestBody = ctx.body
   client = new net.Socket()


Added some comments

Added some comments

--- app/js/jail_iframe/classes/buttons.coffee
+++ app/js/jail_iframe/classes/buttons.coffee
@@ -3,6 +3,8 @@
 class RobustHover
   constructor: (@$el, @_callbacks) ->
     @_hovered = false
+
+    # Use mousemove instead of mouseenter since it is more reliable
     @$el.on 'mousemove', @_onMouseEnter
     @$el.on 'mouseleave', @_onMouseLeave

@@ -14,15 +16,17 @@
   _onMouseEnter: =>
     return if @_hovered

+    # Mouse leaves when *external* document registers mousemove
+    $(document).on 'mousemove', @_onMouseLeave
+
     @_hovered = true
-    $(document).on 'mousemove', @_onMouseLeave
     @_callbacks.mouseenter?()

   _onMouseLeave: =>
     return unless @_hovered

+    $(document).off 'mousemove', @_onMouseLeave
     @_hovered = false
-    $(document).off 'mousemove', @_onMouseLeave
     @_callbacks.mouseleave?()

 class Button


jquery.parse returns an array, but we know there's only one element

--- app/js/jail_iframe/classes/buttons.coffee
+++ app/js/jail_iframe/classes/buttons.coffee
@@ -1,7 +1,7 @@
 class Button
   constructor: (dom_events={}) ->
     @frame = new FactlinkJailRoot.ControlIframe()
-    @frame.setContent($.parseHTML(@content))
+    @frame.setContent($.parseHTML(@content.trim())[0])
     @$el =  $(@frame.doc.body.firstChild)
     for event, callback of dom_events
       @$el.on event, callback


Break up long lines with chained function calls. DRY by drawing the series using a function seriesChart().

--- app/static/coffee/production-chart.coffee
+++ app/static/coffee/production-chart.coffee
@@ -69,77 +69,67 @@
       .attr("class", "rule")

   # Draw grid lines
-  rules.append("svg:line").attr("x1", x).attr("x2", x).attr("y1", 0).attr "y2", h - 1
-  rules.append("svg:line").attr("class", (d) ->
-    (if d then null else "axis")
-  ).data(y.ticks(10)).attr("y1", y).attr("y2", y).attr("x1", 0).attr "x2", w - 10
+  rules.append("svg:line")
+    .attr("x1", x)
+    .attr("x2", x)
+    .attr("y1", 0)
+    .attr("y2", h - 1)
+  rules.append("svg:line")
+    .attr("class", (d) -> (if d then null else "axis"))
+    .data(y.ticks(10))
+    .attr("y1", y)
+    .attr("y2", y)
+    .attr("x1", 0)
+    .attr("x2", w - 10)

   # Place axis tick labels
-  rules.append("svg:text").attr("x", x).attr("y", h + 15).attr("dy", ".71em").attr("text-anchor", "middle").text(x.tickFormat(10)).text String
-  rules.append("svg:text").data(y.ticks(12)).attr("y", y).attr("x", "8em").attr("dx", "-.35em").attr("dy", ".35em").attr("text-anchor", "end").text y.tickFormat(5)
+  rules.append("svg:text")
+    .attr("x", x)
+    .attr("y", h + 15)
+    .attr("dy", ".71em")
+    .attr("text-anchor", "middle")
+    .text(x.tickFormat(10))
+    .text(String)
+  rules.append("svg:text")
+    .data(y.ticks(12))
+    .attr("y", y)
+    .attr("x", "8em")
+    .attr("dx", "-.35em")
+    .attr("dy", ".35em")
+    .attr("text-anchor", "end")
+    .text(y.tickFormat(5))

-  # Series III
-  vis.append("svg:path").attr("class", "line").attr("fill", "none").attr("stroke", color4).attr("stroke-width", 2).attr "d", d3.svg.line().x((d) ->
-    x d.x
-  ).y((d) ->
-    y d.res
-  )
-  vis.select("circle.line").data(val_array1).enter().append("svg:circle").attr("class", "line").attr("fill", color4).attr("cx", (d) ->
-    x d.x
-  ).attr("cy", (d) ->
-    y d.res
-  ).attr "r", 1
+  seriesChart = (vis, column, color = '#000') ->
+    vis.append("svg:path")
+      .attr("class", "line")
+      .attr("fill", "none")
+      .attr("stroke", color)
+      .attr("stroke-width", 2)
+      .attr("d", d3.svg.line()
+        .x((d) -> x d.x)
+        .y((d) -> y d[column]))

-  # Series III
-  vis.append("svg:path").attr("class", "line").attr("fill", "none").attr("stroke", color3).attr("stroke-width", 2).attr "d", d3.svg.line().x((d) ->
-    x d.x
-  ).y((d) ->
-    y d.zlog
-  )
-  vis.select("circle.line").data(val_array1).enter().append("svg:circle").attr("class", "line").attr("fill", color3).attr("cx", (d) ->
-    x d.x
-  ).attr("cy", (d) ->
-    y d.zlog
-  ).attr "r", 1
-
-  # Series II
-  vis.append("svg:path").attr("class", "line").attr("fill", "none").attr("stroke", color1).attr("stroke-width", 2).attr "d", d3.svg.line().x((d) ->
-    x d.x
-  ).y((d) ->
-    y d.z
-  )
-  vis.select("circle.line").data(val_array1).enter().append("svg:circle").attr("class", "line").attr("fill", color1).attr("cx", (d) ->
-    x d.x
-  ).attr("cy", (d) ->
-    y d.z
-  ).attr "r", 1
-
-  # Series I
-  vis.append("svg:path").attr("class", "line").attr("fill", "none").attr("stroke", color2).attr("stroke-width", 2).attr "d", d3.svg.line().x((d) ->
-    x d.x
-  ).y((d) ->
-    y d.y
-  )
-  vis.selectAll("circle.line").data(val_array1).enter().append("svg:circle").attr("class", "line").attr("fill", color2).attr("cx", (d) ->
-    x d.x
-  ).attr("cy", (d) ->
-    y d.y
-  ).attr "r", 1
+  seriesChart(vis, 'res', color4)  # Reserves
+  seriesChart(vis, 'zlog', color3) # Log estimate
+  seriesChart(vis, 'z', color1)    # Estimate
+  seriesChart(vis, 'y', color2)    # World production

   # -----------------------------
   # Add Title then Legend
   # -----------------------------
   lxb = 186
-  vis.append("svg:text").attr("x", lxb - 20).attr("y", 28).attr("font-weight", "bold").text "Global bauxite production 1900-2010 (Metric tons gross weight)"
-  vis.append("svg:rect").attr("x", lxb - 20).attr("y", 150).attr("fill", color4).attr("stroke", color4).attr("height", 2).attr "width", 40
-  vis.append("svg:text").attr("x", lxb + 30).attr("y", 155).text "Reserves (USGS 2011 estimate: 28,000,000,000 tons), 100 x tons"
-  vis.append("svg:text").attr("x", lxb + 30).attr("y", 185).text "Reserve base (2010): 38,000,000,000 tons – estimate"
-  vis.append("svg:text").attr("x", lxb + 30).attr("y", 215).text "World resources (2010): 55,000,000,000 – 75,000,000,000 tons – estimate"
-  vis.append("svg:rect").attr("x", lxb - 20).attr("y", 90).attr("fill", color1).attr("stroke", color1).attr("height", 2).attr "width", 40
-  vis.append("svg:text").attr("x", lxb + 30).attr("y", 95).text "Estimated (logistic function fit)"
-  vis.append("svg:rect").attr("x", lxb - 20).attr("y", 120).attr("fill", color3).attr("stroke", color3).attr("height", 2).attr "width", 40
-  vis.append("svg:text").attr("x", lxb + 30).attr("y", 125).text "Estimated on log scale (logistic function fit)"
-  vis.append("svg:rect").attr("x", lxb - 20).attr("y", 60).attr("fill", color2).attr("stroke", color2).attr("height", 2).attr "width", 40
-  vis.append("svg:text").attr("x", lxb + 30).attr("y", 65).text "World production"
+  vis.append("svg:rect").attr("x", lxb - 20).attr("y", 125).attr("fill", color4).attr("stroke", color4).attr("height", 2).attr "width", 40
+  vis.append("svg:text").attr("x", lxb + 30).attr("y", 130).text "Reserves (USGS 2011 estimate: 28,000,000,000 tons), 100 x tons"
+  vis.append("svg:text").attr("x", lxb + 30).attr("y", 160).text "Reserve base (2010): 38,000,000,000 tons – estimate"
+  vis.append("svg:text").attr("x", lxb + 30).attr("y", 190).text "World resources (2010): 55,000,000,000 – 75,000,000,000 tons – estimate"
+
+  vis.append("svg:rect").attr("x", lxb - 20).attr("y", 65).attr("fill", color1).attr("stroke", color1).attr("height", 2).attr "width", 40
+  vis.append("svg:text").attr("x", lxb + 30).attr("y", 70).text "Estimated (logistic function fit)"
+
+  vis.append("svg:rect").attr("x", lxb - 20).attr("y", 95).attr("fill", color3).attr("stroke", color3).attr("height", 2).attr "width", 40
+  vis.append("svg:text").attr("x", lxb + 30).attr("y", 100).text "Estimated on log scale (logistic function fit)"
+
+  vis.append("svg:rect").attr("x", lxb - 20).attr("y", 40).attr("fill", color2).attr("stroke", color2).attr("height", 2).attr "width", 40
+  vis.append("svg:text").attr("x", lxb + 30).attr("y", 45).text "World production"

   return


fix a bug 设置描述输入框高度

fix a bug
设置描述输入框高度

--- lib/gitCreatePackage/gitCreatePackage-info-view.coffee
+++ lib/gitCreatePackage/gitCreatePackage-info-view.coffee
@@ -37,15 +37,17 @@

       @div class: "form-group", =>
         @label 'Describe:'
-        @subview 'describe', new EditorView(mini: true, placeholderText: 'optional' )
+        @subview 'describe', new EditorView( placeholderText: 'optional' )

   initialize: (wizardView) ->
     @editorOnDidChange @packageName, wizardView

+    @describe.attr("style","height:200px")
+
     selectPath = atom.packages.getActivePackage('tree-view').mainModule.treeView.selectedPath
     @packageName.setText _.last(selectPath.split(path.sep))
-
+
   attached: ->
     @privateSelect.hide()


testing Object#isArray

testing Object#isArray

--- src/object_extensions.coffee
+++ src/object_extensions.coffee
@@ -4,5 +4,6 @@
 * @type Object
 * @returns A boolean expressing whether the object is an instance of Array
 ###
+
 Object::isArray = ->
   return Object.prototype.toString.call(this) == '[object Array]'


working on NewAnalyzer

working on NewAnalyzer

* update tests

--- test/NewAnalyzerSpec.coffee
+++ test/NewAnalyzerSpec.coffee
@@ -200,42 +200,42 @@
         chars: [
           { value: '"'
           inString: [
-            { before: undefined, after: '"'       }
-            { before: '"',       after: undefined }
-            { before: "'",       after: "'"       }
+            { before: undefined, after: '"'      , resolved: true }
+            { before: '"',       after: undefined, resolved: true }
+            { before: "'",       after: "'"      , resolved: true }
           ] }
           { value: "'"
           inString: [
-            { before: undefined, after: "'"       }
-            { before: "'",       after: undefined }
-            { before: '"',       after: '"'       }
+            { before: undefined, after: "'"      , resolved: true }
+            { before: "'",       after: undefined, resolved: true }
+            { before: '"',       after: '"'      , resolved: true }
           ] }
           { value: "a"
           inString: [
-            { before: undefined, after: undefined }
-            { before: "'",       after: "'"       }
-            { before: '"',       after: '"'       }
+            { before: undefined, after: undefined, resolved: undefined }
+            { before: "'",       after: "'"      , resolved: undefined }
+            { before: '"',       after: '"'      , resolved: undefined }
           ] }
         ] }
         { escaped: true
         chars: [
           { value: '"'
           inString: [
-            { before: undefined, after: undefined }
-            { before: '"',       after: '"'       }
-            { before: "'",       after: "'"       }
+            { before: undefined, after: undefined, resolved: true }
+            { before: '"',       after: '"'      , resolved: true }
+            { before: "'",       after: "'"      , resolved: true }
           ] }
           { value: "'"
           inString: [
-            { before: undefined, after: undefined }
-            { before: "'",       after: "'"       }
-            { before: '"',       after: '"'       }
+            { before: undefined, after: undefined, resolved: true }
+            { before: "'",       after: "'"      , resolved: true }
+            { before: '"',       after: '"'      , resolved: true }
           ] }
           { value: "a"
           inString: [
-            { before: undefined, after: undefined }
-            { before: "'",       after: "'"       }
-            { before: '"',       after: '"'       }
+            { before: undefined, after: undefined, resolved: undefined }
+            { before: "'",       after: "'"      , resolved: undefined }
+            { before: '"',       after: '"'      , resolved: undefined }
           ] }
         ] }
       ]
@@ -255,7 +255,9 @@
                   for subject in char.inString
                     do (subject = subject)->
                       describe "and inString = #{JSON.stringify(subject.before)}", ->
-                        beforeEach -> Analyzer('inString', subject.before)
+                        beforeEach ->
+                          Analyzer('inString', subject.before)
+                          Analyzer('resolved', undefined)

                         it "inString should became #{JSON.stringify(subject.after)}", ->
                           Analyzer('stringTracker')()
@@ -291,6 +293,7 @@
         expect(spy).toHaveBeenCalledWith(
           escapeTracker
           charFilter
-          stringTracker )
-
-
+          stringTracker
+          parenthesesTracker )
+
+


Wrap mixpanel#track_forms

Wrap mixpanel#track_forms

--- lib/analytics.coffee
+++ lib/analytics.coffee
@@ -114,6 +114,9 @@
     memo
   , {})

+module.exports.trackForm = (selector, description, options = {}) ->
+  mixpanel?.track_forms? selector, description, options
+
 module.exports.modelNameAndIdToLabel = (modelName, id) ->
   throw new Error('Requires modelName and id') unless modelName? and id?
   "#{_s.capitalize(modelName)}:#{id}"


Clean up utils nomenclature; add rename and configure methods.

--- lib/api/utils.coffee
+++ lib/api/utils.coffee
@@ -2,28 +2,30 @@
 path  = require 'path'
 Base  = require '../base'
 ejs   = require 'ejs'
+_     = require 'lodash'

 class Utils extends Base

   constructor: (@sprout) -> super

-  read: (file, encoding = 'utf8') ->
-    read_path = @full_path(file)
+  read: (base_path, encoding = 'utf8') ->
+    read_path = @full_path base_path
     fs.readFileSync(read_path, encoding) if fs.existsSync(read_path)

-  write: (file, data) ->
-    fs.writeFileSync @full_path(file), ejs.render(data, @sprout.ejs_options)
+  write: (base_path, src) ->
+    fs.writeFileSync @full_path(base_path), ejs.render(src, @sprout.ejs_options)

-  remove: (file) ->
-    remove_path = @full_path(file)
+  rename: (source_path, destination_path) ->
+    fs.renameSync @full_path(source_path), @full_path(destination_path)
+
+  remove: (base_path) ->
+    remove_path = @full_path(base_path)
     fs.unlinkSync remove_path if fs.existsSync remove_path

-  write_from: (file, dest, encoding = 'utf8') ->
-    source_path = path.join @sprout.sprout_path, file
-    data = fs.readFileSync source_path, encoding
-    @write dest, data
+  configure: (config) ->
+    @sprout.config_values = _.extend @sprout.config_values, config

   full_path: (file) ->
-    path.join(@sprout.target, file)
+    path.resolve @sprout.target, file

 module.exports = Utils


nice handling of lengths of datasets

nice handling of lengths of datasets

--- static/coffee/pandas/pandas.coffee
+++ static/coffee/pandas/pandas.coffee
@@ -106,7 +106,11 @@

   pandasgroup : (e) ->
     if e.keyCode == ENTER
-     @model.save('group', @fromcsv(@$el.find(".pandasgroup").val()))
+      @model.set(
+        group : @fromcsv(@$el.find(".pandasgroup").val())
+        offset : 0
+      )
+     @model.save()

   counts : () ->
     @mget('tabledata').data._counts
@@ -125,7 +129,6 @@
       )
     else
       return null
-
   render : () ->
     group = @mget('group')
     if _.isArray(group)
@@ -137,7 +140,6 @@
     sort_ascendings = {}
     for obj in  @mget('sort')
       sort_ascendings[obj['column']] = obj['ascending']
-    length = _.values(@mget('tabledata').data)[0].length
     template_data =
       skip :
         _counts : true
@@ -149,7 +151,7 @@
       height : @mget('height')
       width : @mget('width')
       offset : @mget('offset')
-      length : length
+      length : @model.length()
       filterselected : @mget('filterselected')
       maxlength : @mget('maxlength')
       counts : @mget('tabledata').data._counts
@@ -175,6 +177,9 @@

   fetch : (options) ->
     super(options)
+
+  length : () ->
+    _.values(@get('tabledata').data)[0].length

   toggle_column_sort : (colname) =>
     sorting = @get('sort')
@@ -202,7 +207,7 @@

   go_back : () ->
     offset = @get('offset')
-    offset = offset - @get('length')
+    offset = offset - @length()
     if offset < 0
       offset = 0
     @set('offset', offset)
@@ -210,15 +215,15 @@

   go_forward : () ->
     offset = @get('offset')
-    offset = offset + @get('length')
-    maxoffset = @get('maxlength') - @get('length')
+    offset = offset + @length()
+    maxoffset = @get('maxlength') - @length()
     if offset > maxoffset
       offset = maxoffset
     @set('offset', offset)
     @save()

   go_end : () ->
-    maxoffset = @get('maxlength') - @get('length')
+    maxoffset = @get('maxlength') - @length()
     @set('offset', maxoffset)
     @save()


Minor changes

Minor changes

--- lib/broadcast.coffee
+++ lib/broadcast.coffee
@@ -8,5 +8,5 @@
     automaticallyOpenInBrowser: true

   activate: ->
-    atom.workspaceView.command "broadcast:start", => BroadcastServer.start()
-    atom.workspaceView.command "broadcast:stop", => BroadcastServer.stop()
+    atom.workspaceView.command 'broadcast:start', => BroadcastServer.start()
+    atom.workspaceView.command 'broadcast:stop', => BroadcastServer.stop()


Fix code style formatting

Fix code style formatting

--- lib/cacheable.coffee
+++ lib/cacheable.coffee
@@ -5,9 +5,9 @@
 module.exports = ->
   switch arguments.length
     when 2
-      handleBatch.apply @, arguments
+      handleBatch.apply this, arguments
     when 3
-      handleSingle.apply @, arguments
+      handleSingle.apply this, arguments
     else
       throw new Error 'Bongo#cacheable expects either 2 or 3 arguments.'

@@ -49,12 +49,14 @@
   return

-handleBatch =(batch, callback)->
-  return handleByName.call @, batch, callback  if 'string' is typeof batch
+handleBatch = (batch, callback) ->
+  return handleByName.call this, batch, callback  if 'string' is typeof batch
+
   models = []
-  queue = batch.map (single, i)=>=>
+
+  queue = batch.map (single, i) =>=>
     {name, type, constructorName, id} = single
-    handleSingle.call @, type or name or constructorName, id, (err, model)->
+    handleSingle.call this, type or name or constructorName, id, (err, model) ->
       if err then callback err
       else
         models[i] = model


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -1,6 +1,6 @@
 module "Engine"

-test "engi#find", ->
+test "#find", ->
   engine = Engine()

   engine.add


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -4,7 +4,8 @@
   engine = Engine()

   engine.add
-    x:
+    x: 0
+    y:

 test "#find", ->
   engine = Engine()


Adds spec to ensure #next is called on #initialize

--- apps/personalize/test/client/index.coffee
+++ apps/personalize/test/client/index.coffee
@@ -10,20 +10,33 @@
 describe 'PersonalizeRouter', ->
   beforeEach (done) ->
     benv.setup =>
-      benv.expose { $: benv.require 'jquery' }
+      benv.expose $: benv.require 'jquery'
       Backbone.$ = $
+      sinon.stub _, 'defer', (cb) -> cb()
       { @PersonalizeRouter, init } = mod = rewire '../../client/index'
-      mod.__set__ 'Transition', { fade: (@fadeStub = sinon.stub()) }
-      mod.__set__ 'views', { LocationView: => { render: => { $el: 'location' } } }
-      sinon.spy @PersonalizeRouter.prototype, 'navigate'
+      mod.__set__ 'Transition', fade: (@fadeStub = sinon.stub())
+      mod.__set__ 'views', LocationView: => render: => $el: 'location'
+      sinon.spy @PersonalizeRouter::, 'navigate'
       @router = new @PersonalizeRouter user: new CurrentUser
       done()

   afterEach ->
+    _.defer.restore()
     @router.navigate.restore()

   after ->
     benv.teardown()
+
+  describe '#initialize', ->
+    beforeEach ->
+      @nextSpy = sinon.spy @PersonalizeRouter::, 'next'
+
+    afterEach ->
+      @nextSpy.restore()
+
+    it 'calls #next to figure out the initial step', ->
+      router = new @PersonalizeRouter user: new CurrentUser
+      router.next.called.should.be.true

   describe '#step', ->
     it 'steps the view', ->


Maybe display errors better

Maybe display errors better

--- connector.coffee
+++ connector.coffee
@@ -10,6 +10,7 @@
     token  : config.token

   consoleError = (error) ->
+    console.error error
     console.error error.message
     console.error error.stack


chat/collabparticipants: add docs

chat/collabparticipants: add docs

--- client/activity/lib/models/collaborationchannelparticipants.coffee
+++ client/activity/lib/models/collaborationchannelparticipants.coffee
@@ -16,6 +16,15 @@
     @state = _.assign {}, @defaultState, options.state

+  ###*
+   * Computes preview participants depending on the video active state.
+   *
+   *     - Delegate to super class when video is not active.
+   *     - Return video-active participants when video is active.
+   *
+   * @param {Immutable.OrderedMap} participants - all participants
+   * @return {Immutable.List} computedPreviewParticipants
+  ###
   computePreviewParticipants: (participants) ->

     if @state.videoActive
@@ -25,6 +34,15 @@
     super

+  ###*
+   * Computes hidden participants depending on the video active state.
+   *
+   *     - Delegate to super class when video is not active.
+   *     - Return non-video-active participants when video is active.
+   *
+   * @param {Immutable.OrderedMap} participants - all participants
+   * @return {Immutable.List} computedPreviewParticipants
+  ###
   computeHiddenParticipants: (participants) ->

     if @state.videoActive
@@ -35,6 +53,12 @@
     super

+  ###*
+   * Defensively adds a video participant. Emits change afterwards if wanted.
+   *
+   * @param {string} nickname
+   * @param {boolean=} emitEvent
+  ###
   addVideoParticipant: (nickname, emitEvent = yes) ->

     index = @state.videoParticipants.indexOf nickname
@@ -44,6 +68,12 @@
       @emitChange()  if emitEvent

+  ###*
+   * Defensively removes a video participant. Emits change afterwards if wanted.
+   *
+   * @param {string} nickname
+   * @param {boolean=} emitEvent
+  ###
   removeVideoParticipant: (nickname, emitEvent = yes) ->

     index = @state.videoParticipants.indexOf nickname
@@ -53,6 +83,13 @@
       @emitChange()  if emitEvent

+  ###*
+   * Sets given username as selected participant. It's a noop if user is not
+   * online. Emits change afterwards.
+   *
+   * @param {string} nickname
+   * @param {boolean} isOnline
+  ###
   setVideoSelectedParticipant: (nickname, isOnline) ->

     return  unless isOnline
@@ -62,6 +99,11 @@
     @emitChange()

+  ###*
+   * Defensively add talking participant.
+   *
+   * @param {string} nickname
+  ###
   addTalkingParticipant: (nickname) ->

     index = @state.talkingParticipants.indexOf nickname
@@ -71,6 +113,11 @@
       @emitChange()

+  ###*
+   * Defensively remove talking participant.
+   *
+   * @param {string} nickname
+  ###
   removeTalkingParticipant: (nickname) ->

     index = @state.talkingParticipants.indexOf nickname
@@ -80,6 +127,11 @@
       @emitChange()

+  ###*
+   * Defensively add active video participant.
+   *
+   * @param {string} nickname
+  ###
   addVideoActiveParticipant: (nickname) ->

     index = @state.videoParticipants.indexOf nickname
@@ -89,6 +141,11 @@
       @emitChange()

+  ###*
+   * Defensively add active video participant.
+   *
+   * @param {string} nickname
+  ###
   removeVideoActiveParticipant: (nickname) ->

     index = @state.videoParticipants.indexOf nickname
@@ -98,9 +155,22 @@
       @emitChange()

+  ###*
+   * Difference between this and super method, is that this one emits the state
+   * with lists as well. This may be better to be moved to super class.
+  ###
   emitChange: -> @emit 'change', @getLists(), @state

+  ###*
+   * Sets video state to given state.
+   *
+   *     - if resulting state is `active` it will use second parameter as
+   *       initial participant list.
+   *
+   * @param {boolean} state
+   * @param {array.<string>} participants
+  ###
   setVideoState: (state, participants) ->

     @state.videoActive = state


fire change event when script is loaded

fire change event when script is loaded

--- server/app/assets/javascripts/datavis/DataVisClient.coffee
+++ server/app/assets/javascripts/datavis/DataVisClient.coffee
@@ -29,7 +29,7 @@
   loadFile: (context, path) ->
     cell = window.globalGraph.getCell(context)
     if cell?
-      root.jQuery.getScript(path)
+      root.jQuery.getScript(path, (a,b,c) -> cell.trigger("change:mAttributes", cell))
     else
       this.log("Discarded script file for non-existing object.")


Disable bib import if there's no value

Disable bib import if there's no value

--- app/assets/javascripts/import_button.js.coffee
+++ app/assets/javascripts/import_button.js.coffee
@@ -1,18 +1,27 @@
 # Copyright 2011-2015, The Trustees of Indiana University and Northwestern
 #   University.  Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
-#
+#
 # You may obtain a copy of the License at
-#
+#
 # http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software distributed
+#
+# Unless required by applicable law or agreed to in writing, software distributed
 #   under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
-#   CONDITIONS OF ANY KIND, either express or implied. See the License for the
+#   CONDITIONS OF ANY KIND, either express or implied. See the License for the
 #   specific language governing permissions and limitations under the License.
 # ---  END LICENSE_HEADER BLOCK  ---

 $ ->
-  import_button_html = '
<button type="submit" name="media_object[import_bib_record]" class="btn btn-success" value="yes">Import</button>
'
+  import_button_html = '
<button id="media_object_bibliographic_id_btn" type="submit" name="media_object[import_bib_record]" class="btn btn-success" value="yes">Import</button>
'
   $('div.import-button').append(import_button_html)
+  enable_bib_btn()
+  $('#media_object_bibliographic_id').keyup -> enable_bib_btn()
+
+enable_bib_btn = ->
+    if $('#media_object_bibliographic_id').val() == ""
+      $('#media_object_bibliographic_id_btn').prop('disabled', true)
+    else
+      $('#media_object_bibliographic_id_btn').prop('disabled', false)
+    return


Changed address to towndistrict

Changed address to towndistrict

--- app/assets/javascripts/users_address.js.coffee
+++ app/assets/javascripts/users_address.js.coffee
@@ -16,7 +16,7 @@
               value: item.name
               address: item.name
               post_code: item.postcode
-              neighborhood: item.district
+              neighborhood: item.towndistrict
               postal_town: item.postal_town
               east: item.east
               north: item.north


Add tests for deleting (or not) logged out ID's with alias()

--- test/casperjs/kissmetrics-anon.coffee
+++ test/casperjs/kissmetrics-anon.coffee
@@ -116,6 +116,21 @@

 	@test.assertEquals km.person, 'notevan', 'Updates person attribute'

+casper.then ->
+	data = @evaluate ->
+		km = new AnonKissmetricsClient 'abc123'
+		km.alias 'username'
+		km._storage.get()
+
+	@test.assertFalsy data, 'Logged out ID is deleted by alias'
+
+casper.then ->
+	data = @evaluate ->
+		km = new AnonKissmetricsClient 'abc123'
+		km.alias 'username', false
+		km._storage.get()
+
+	@test.assertTruthy data, 'Logged out ID is retained by alias with the `false` argument'

 # # Client API


fix menu fail for admin links

fix menu fail for admin links

refs CNVS-25916

mouseup is always accompanied by mousedown, but when it fires first
it messes up the kylemenu.  this fixes it.

TEST PLAN:
 1) groups_teacher_spec should pass webpack selenium build
Change-Id: I2978787feadd5b31ae5a7d258c5f27cc4f9536eb
Reviewed-on: https://gerrit.instructure.com/69417
Reviewed-by: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
Tested-by: Jenkins
QA-Review: August Thornton <9adc7a1161ddf32ff608de792a7e50179545f026@instructure.com>
Product-Review: Ethan Vizitei <73eeb0b2f65d05a906adf3b21ee1f9f5a2aa3c1c@instructure.com>

--- app/coffeescripts/behaviors/admin-links.coffee
+++ app/coffeescripts/behaviors/admin-links.coffee
@@ -5,7 +5,7 @@

   # this is a behaviour that will automatically set up a set of .admin-links
   # when the button is clicked, see _admin_links.scss for markup
-  $(document).on 'mousedown mouseup click keydown', '.al-trigger', (event) ->
+  $(document).on 'mousedown click keydown', '.al-trigger', (event) ->
     $trigger = $(this)
     return if $trigger.data('kyleMenu')
     opts = $.extend {noButton: true}, $trigger.data('kyleMenuOptions')
@@ -17,5 +17,4 @@
           at: $trigger.data('popup-at')
           within: $trigger.data('popup-within')
     new KyleMenu($trigger, opts)
-
     $trigger.trigger(event)


more future unit tests

more future unit tests

--- test/core/overall/parser.mocha.coffee
+++ test/core/overall/parser.mocha.coffee
@@ -6,8 +6,11 @@
 describe "parser", ->
   it "it should parse the mega definition", ->
     ex = facet()
+      #.filter('$color = "Red"')
+      #.filter('$country.is("USA")')
       .apply('parent_x', "$^x")
-      .apply('addition', "$x + 10 - $y")
+      .apply('addition1', "$x + 10 - $y")
+      #.apply('addition2', "$x.add(1)")
       .apply('multiplication', "$x * 10 / $y")
       .apply('agg_count', "$data.count()")
       .apply('agg_sum', "$data.sum($price)")
@@ -17,8 +20,11 @@

     expect(ex.toJS()).to.deep.equal(
       facet()
+      #.filter(facet('color').is("Red")')
+      #.filter(facet('country').is("USA")')
       .apply('parent_x', facet("^x"))
-      .apply('addition', facet("x").add(10, facet("y").negate()))
+      .apply('addition1', facet("x").add(10, facet("y").negate()))
+      #.apply('addition2', facet("x").add(1))
       .apply('multiplication', facet("x").multiply(10, facet("y").reciprocate()))
       .apply('agg_count', facet("data").count())
       .apply('agg_sum', facet("data").sum(facet('price')))
@@ -26,4 +32,5 @@
       .apply('agg_group_label1', facet("data").group(facet('carat')).label('Carat'))
       .apply('agg_group_label2', facet("data").group('$carat').label('Carat'))
       .toJS()
-    )+    )
+


removing console.logs

removing console.logs

--- app/controllers/InstituteShowController.coffee
+++ app/controllers/InstituteShowController.coffee
@@ -17,7 +17,6 @@

   loadInstitute:(name=null)=>
     @instituteName = name if name?
-    console.log "institute name is ", @instituteName, Institute.all()
     @currentInstitute = Institute.findBySlug(@instituteName)

@@ -34,7 +33,6 @@
     super

     @instituteName = params.id
-    console.log "institute name is ", @instituteName
     if @institutesLoadad
       @loadInstitute()
       @setTitle()


atom: install the git-blame package

atom: install the git-blame package

Also, remove the languge-lex-flex package because it no longer appears
to be valid (repo has disappeared).

--- atom/packages.cson
+++ atom/packages.cson
@@ -7,12 +7,12 @@
   "busy"
   "busy-signal"
   "file-icons"
+  "git-blame"
   "intentions"
   "language-bison"
   "language-elixir"
   "language-erlang"
   "language-graphql"
-  "language-lex-flex"
   "language-markdown"
   "language-thrift"
   "linter"


atom: deletes last file

atom: deletes last file

--- atom/projects.cson
+++ atom/projects.cson
@@ -1,25 +0,0 @@
-[
-  {
-    title: "TK Front End"
-    group: "TK"
-    paths: [
-      "/Users/mattbridges/code/bitbucket.org/trendkite/trendkite-front-end/workspaces/app"
-      "/Users/mattbridges/code/bitbucket.org/trendkite/trendkite-front-end/workspaces/tk-ui"
-    ]
-    devMode: true
-  }
-  {
-    title: "Union Web"
-    paths: [
-      "/Users/mattbridges/code/github.com/union/galley/src/union-web"
-    ]
-  }
-  {
-    title: "Tweetreach Web"
-    group: "Union Metrics"
-    paths: [
-      "/Users/mattbridges/code/github.com/union/galley/src/tweetreach-web"
-    ]
-    devMode: true
-  }
-]


Unquote keymap key names to aid syntax highlighting

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -10,106 +10,106 @@
   # Conditional blocks

   'if':
-    'prefix': 'if'
-    'body': "if (${1:condition}) {\n\t$2\n}"
+    prefix: 'if'
+    body: "if (${1:condition}) {\n\t$2\n}"

   'else if':
-    'prefix': 'elseif'
-    'body': "else if (${1:condition}) {\n\t$2\n}"
+    prefix: 'elseif'
+    body: "else if (${1:condition}) {\n\t$2\n}"

   'else':
-    'prefix': 'else'
-    'body': "else {\n\t$1\n}"
+    prefix: 'else'
+    body: "else {\n\t$1\n}"

   'for':
-    'prefix': 'for'
-    'body': "for (${1:i} = ${2:0}; ${1:i} < ${3:end}; ${1:i} += ${4:1}) {\n\t$5\n}"
+    prefix: 'for'
+    body: "for (${1:i} = ${2:0}; ${1:i} < ${3:end}; ${1:i} += ${4:1}) {\n\t$5\n}"

   'while':
-    'prefix': 'while'
-    'body': "while (${1:condition}) {\n\t$2\n}"
+    prefix: 'while'
+    body: "while (${1:condition}) {\n\t$2\n}"

   # Error handling

   'try':
-    'prefix': 'try'
-    'body': "try {\n\t$1\n}"
+    prefix: 'try'
+    body: "try {\n\t$1\n}"

   'catch':
-    'prefix': 'catch'
-    'body': "catch ($1) {\n\t$2\n}"
+    prefix: 'catch'
+    body: "catch ($1) {\n\t$2\n}"

   'finally':
-    'prefix': 'finally'
-    'body': "finally {\n\t$1\n}"
+    prefix: 'finally'
+    body: "finally {\n\t$1\n}"

 # C++

 '.source.cpp':

   'include guard':
-    'prefix': 'once'
-    'body': "#pragma once\n"
+    prefix: 'once'
+    body: "#pragma once\n"

   'include':
-    'prefix': 'inc'
-    'body': "#include $1"
+    prefix: 'inc'
+    body: "#include $1"

   'include iostream':
-    'prefix': 'incio'
-    'body': "#include <iostream>\n"
+    prefix: 'incio'
+    body: "#include <iostream>\n"

   'using namespace std':
-    'prefix': 'using'
-    'body': "using namespace std;\n"
+    prefix: 'using'
+    body: "using namespace std;\n"

   'for':
-    'prefix': 'for'
-    'body': "for (int ${1:i} = ${2:0}; ${1:i} < ${3:end}; ${1:i}${4:++}) {\n\t$5\n}"
+    prefix: 'for'
+    body: "for (int ${1:i} = ${2:0}; ${1:i} < ${3:end}; ${1:i}${4:++}) {\n\t$5\n}"

   'function':
-    'prefix': 'f'
-    'body': "${1:void} ${2:fname}($3) {\n\t$4\n}"
+    prefix: 'f'
+    body: "${1:void} ${2:fname}($3) {\n\t$4\n}"

   'function method':
-    'prefix': 'fm'
-    'body': "${1:void} ${2:Class}::${3:fname}($4) {\n\t$5\n}"
+    prefix: 'fm'
+    body: "${1:void} ${2:Class}::${3:fname}($4) {\n\t$5\n}"

   'constructor definition':
-    'prefix': 'ctor'
-    'body': "${1:Class}::${1:Class}($2) {\n\t$3\n}"
+    prefix: 'ctor'
+    body: "${1:Class}::${1:Class}($2) {\n\t$3\n}"

   'destructor definition':
-    'prefix': 'dtor'
-    'body': "${1:Class}::~${1:Class}() {\n\t$3\n}"
+    prefix: 'dtor'
+    body: "${1:Class}::~${1:Class}() {\n\t$3\n}"

   'class':
-    'prefix': 'class'
-    'body': "class ${1:Class} {\n\t$3\n};"
+    prefix: 'class'
+    body: "class ${1:Class} {\n\t$3\n};"

   'this pointer access':
-    'prefix': 'this'
-    'body': "this->"
+    prefix: 'this'
+    body: "this->"

   'cin':
-    'prefix': 'cin'
-    'body': "cin >> $1;"
+    prefix: 'cin'
+    body: "cin >> $1;"

   'cout':
-    'prefix': 'cout'
-    'body': "cout << $1;"
+    prefix: 'cout'
+    body: "cout << $1;"

   'cout endl':
-    'prefix': 'coute'
-    'body': "cout << $1 << endl;"
+    prefix: 'coute'
+    body: "cout << $1 << endl;"

   'cerr':
-    'prefix': 'cerr'
-    'body': "cerr << $1;"
+    prefix: 'cerr'
+    body: "cerr << $1;"

   'cerr endl':
-    'prefix': 'cerre'
-    'body': "cerr << $1 << endl;"
+    prefix: 'cerre'
+    body: "cerr << $1 << endl;"

 # JavaScript
@@ -119,211 +119,211 @@
   # Functions

   'Function Declaration':
-    'prefix': 'fd'
-    'body': "function ${1:name}($2) {\n\t$3\n}"
+    prefix: 'fd'
+    body: "function ${1:name}($2) {\n\t$3\n}"

   'Function Expression':
-    'prefix': 'fe'
-    'body': "function ($1) {\n\t$2\n}"
+    prefix: 'fe'
+    body: "function ($1) {\n\t$2\n}"

   # Jasmine

   'it':
-    'prefix': 'it'
-    'body': "it('$1', function ($2) {\n\t$3\n});"
+    prefix: 'it'
+    body: "it('$1', function ($2) {\n\t$3\n});"

   'describe':
-    'prefix': 'desc'
-    'body': "describe('$1', function ($2) {\n\t$3\n});"
+    prefix: 'desc'
+    body: "describe('$1', function ($2) {\n\t$3\n});"

   'expect':
-    'prefix': 'expect'
-    'body': "expect($1).${2:toBe}($3);"
+    prefix: 'expect'
+    body: "expect($1).${2:toBe}($3);"

 # Python 2 and 3

 '.source.python':

   'env (python3)':
-    'prefix': 'env3'
-    'body': "#!/usr/bin/env python3\n"
+    prefix: 'env3'
+    body: "#!/usr/bin/env python3\n"

   'name import':
-    'prefix': 'from'
-    'body': "from ${1:module} import ${2:name}"
+    prefix: 'from'
+    body: "from ${1:module} import ${2:name}"

   'docstring':
-    'prefix': 'docstr'
-    'body': "\"\"\"$1\"\"\"$2"
+    prefix: 'docstr'
+    body: "\"\"\"$1\"\"\"$2"

   'if':
-    'prefix': 'if'
-    'body': "if ${1:condition}:\n\t${2:pass}"
+    prefix: 'if'
+    body: "if ${1:condition}:\n\t${2:pass}"

   'elif':
-    'prefix': 'elif'
-    'body': "elif ${1:condition}:\n\t${2:pass}"
+    prefix: 'elif'
+    body: "elif ${1:condition}:\n\t${2:pass}"

   'else':
-    'prefix': 'else'
-    'body': "else:\n\t${1:pass}"
+    prefix: 'else'
+    body: "else:\n\t${1:pass}"

   'for':
-    'prefix': 'for'
-    'body': "for ${1:value} in ${2:iterable}:\n\t${3:pass}"
+    prefix: 'for'
+    body: "for ${1:value} in ${2:iterable}:\n\t${3:pass}"

   'while':
-    'prefix': 'while'
-    'body': "while ${1:condition}:\n\t${2:pass}"
+    prefix: 'while'
+    body: "while ${1:condition}:\n\t${2:pass}"

   'try':
-    'prefix': 'try'
-    'body': "try:\n\t${1:pass}"
+    prefix: 'try'
+    body: "try:\n\t${1:pass}"

   'except':
-    'prefix': 'except'
-    'body': "except ${1:Exception}:\n\t${2:pass}"
+    prefix: 'except'
+    body: "except ${1:Exception}:\n\t${2:pass}"

   'finally':
-    'prefix': 'finally'
-    'body': "finally:\n\t${1:pass}"
+    prefix: 'finally'
+    body: "finally:\n\t${1:pass}"

   'with':
-    'prefix': 'with'
-    'body': "with ${1:contextmanager}:\n\t${2:pass}"
+    prefix: 'with'
+    body: "with ${1:contextmanager}:\n\t${2:pass}"

   'with open()':
-    'prefix': 'witho'
-    'body': "with open(${1:path}, '${2:r}') as ${3:file}:\n\t${4:pass}"
+    prefix: 'witho'
+    body: "with open(${1:path}, '${2:r}') as ${3:file}:\n\t${4:pass}"

   'function':
-    'prefix': 'def'
-    'body': "def ${1:fname}($2):\n\t${3:pass}"
+    prefix: 'def'
+    body: "def ${1:fname}($2):\n\t${3:pass}"

   'class':
-    'prefix': 'class'
-    'body': "class ${1:ClassName}(${2:object}):\n\t${3:pass}"
+    prefix: 'class'
+    body: "class ${1:ClassName}(${2:object}):\n\t${3:pass}"

 # PHP blocks/statements (meant to be used within PHP source blocks)

 '.source.php':

   'echo':
-    'prefix': 'echo'
-    'body': "echo $1;"
+    prefix: 'echo'
+    body: "echo $1;"

   'print_r':
-    'prefix': 'pr'
-    'body': "print_r( $1 );"
+    prefix: 'pr'
+    body: "print_r( $1 );"

   'function':
-    'prefix': 'f'
-    'body': "function ${1:name}($2) {\n\t$3\n}"
+    prefix: 'f'
+    body: "function ${1:name}($2) {\n\t$3\n}"

   'if':
-    'prefix': 'if'
-    'body': "if ( ${1:condition} ) {\n\t$2\n}"
+    prefix: 'if'
+    body: "if ( ${1:condition} ) {\n\t$2\n}"

   'else if':
-    'prefix': 'elseif'
-    'body': "else if ( ${1:condition} ) {\n\t$2\n}"
+    prefix: 'elseif'
+    body: "else if ( ${1:condition} ) {\n\t$2\n}"

   'else':
-    'prefix': 'else'
-    'body': "else {\n\t$1\n}"
+    prefix: 'else'
+    body: "else {\n\t$1\n}"

   'not empty':
-    'prefix': 'nem'
-    'body': "! empty( $1 )"
+    prefix: 'nem'
+    body: "! empty( $1 )"

   'for':
-    'prefix': 'for'
-    'body': "for ( ${1:\$i} = ${2:0}; ${1:\$i} < ${3:end}; ${1:\$i} += ${4:1} ) {\n\t$5\n}"
+    prefix: 'for'
+    body: "for ( ${1:\$i} = ${2:0}; ${1:\$i} < ${3:end}; ${1:\$i} += ${4:1} ) {\n\t$5\n}"

   'foreach':
-    'prefix': 'foreach'
-    'body': "foreach ( ${1:\$object} as ${2:\$value} ) {\n\t$3\n}"
+    prefix: 'foreach'
+    body: "foreach ( ${1:\$object} as ${2:\$value} ) {\n\t$3\n}"

   'while':
-    'prefix': 'while'
-    'body': "while ( ${1:condition} ) {\n\t$2\n}"
+    prefix: 'while'
+    body: "while ( ${1:condition} ) {\n\t$2\n}"

   'array function call':
-    'prefix': 'fca'
-    'body': "${1:name}( array(\n\t$2\n)$3 );"
+    prefix: 'fca'
+    body: "${1:name}( array(\n\t$2\n)$3 );"

   'key-value pair':
-    'prefix': 'kv'
-    'body': "'${1:key}' => ${2:value}$3"
+    prefix: 'kv'
+    body: "'${1:key}' => ${2:value}$3"

   'define constant':
-    'prefix': 'def'
-    'body': "define( '$1', ${2:false} );"
+    prefix: 'def'
+    body: "define( '$1', ${2:false} );"

   'define constant conditionally':
-    'prefix': 'ifndef'
-    'body': "if ( ! defined( '$1' ) ) {\n\tdefine( '$1', ${2:false} );\n}"
+    prefix: 'ifndef'
+    body: "if ( ! defined( '$1' ) ) {\n\tdefine( '$1', ${2:false} );\n}"

   'display errors':
-    'prefix': 'derr'
-    'body': "ini_set( 'display_errors', '1' );"
+    prefix: 'derr'
+    body: "ini_set( 'display_errors', '1' );"

   'var_dump':
-    'prefix': 'vd'
-    'body': "var_dump( $1 );"
+    prefix: 'vd'
+    body: "var_dump( $1 );"

 # Inline PHP Blocks (meant to be intermixed with HTML)

 '.text.html.php':

   'PHP Block (inline)':
-    'prefix': 'php'
-    'body': "<?php $1 ?>$2"
+    prefix: 'php'
+    body: "<?php $1 ?>$2"

   'PHP Block (multiline)':
-    'prefix': 'phpblock'
-    'body': "<?php\n\t$1\n?>$2"
+    prefix: 'phpblock'
+    body: "<?php\n\t$1\n?>$2"

   'echo (inline)':
-    'prefix': 'pecho'
-    'body': "<?php echo $1; ?>$2"
+    prefix: 'pecho'
+    body: "<?php echo $1; ?>$2"

   'if (inline)':
-    'prefix': 'pif'
-    'body': "<?php if ( ${1:condition} ): ?>$2"
+    prefix: 'pif'
+    body: "<?php if ( ${1:condition} ): ?>$2"

   'else (inline)':
-    'prefix': 'pelse'
-    'body': "<?php else: ?>$2"
+    prefix: 'pelse'
+    body: "<?php else: ?>$2"

   'else if (inline)':
-    'prefix': 'pelseif'
-    'body': "<?php elseif ( ${1:condition} ): ?>$2"
+    prefix: 'pelseif'
+    body: "<?php elseif ( ${1:condition} ): ?>$2"

   'for (inline)':
-    'prefix': 'pfor'
-    'body': "<?php for ( ${1:\$i} = ${2:0}; ${1:\$i} < ${3:end}; ${1:\$i} += ${4:1} ): ?>$5"
+    prefix: 'pfor'
+    body: "<?php for ( ${1:\$i} = ${2:0}; ${1:\$i} < ${3:end}; ${1:\$i} += ${4:1} ): ?>$5"

   'foreach (inline)':
-    'prefix': 'pforeach'
-    'body': "<?php foreach ( ${1:\$object} as ${2:\$value} ): ?>$3"
+    prefix: 'pforeach'
+    body: "<?php foreach ( ${1:\$object} as ${2:\$value} ): ?>$3"

   'while (inline)':
-    'prefix': 'pwhile'
-    'body': "<?php while ( ${1:condition} ): ?>$2"
+    prefix: 'pwhile'
+    body: "<?php while ( ${1:condition} ): ?>$2"

   'endif (inline)':
-    'prefix': 'pendif'
-    'body': "<?php endif; ?>$1"
+    prefix: 'pendif'
+    body: "<?php endif; ?>$1"

   'endfor (inline)':
-    'prefix': 'pendfor'
-    'body': "<?php endfor; ?>$1"
+    prefix: 'pendfor'
+    body: "<?php endfor; ?>$1"

   'endforeach (inline)':
-    'prefix': 'pendforeach'
-    'body': "<?php endforeach; ?>$1"
+    prefix: 'pendforeach'
+    body: "<?php endforeach; ?>$1"

   'endwhile (inline)':
-    'prefix': 'pendwhile'
-    'body': "<?php endwhile; ?>$1"
+    prefix: 'pendwhile'
+    body: "<?php endwhile; ?>$1"


Refactor integration tests

Refactor integration tests

--- test/integration/dynamodb.spec.coffee
+++ test/integration/dynamodb.spec.coffee
@@ -1,8 +1,8 @@
 AWS = require('../../lib/aws')
 config = new AWS.FileSystemConfig('configuration')

-integration = (expected) ->
-  req = this.suite.parentSuite.service[this.suite.description]()
+integration = (test, callback) ->
+  req = test.suite.parentSuite.service[test.suite.description]()
   resp = null
   runs ->
     req.always (respObject) ->
@@ -10,12 +10,13 @@
   waitsFor ->
     resp != null
   runs ->
-    expect(resp.error).toBe(null)
-    expect(JSON.stringify(resp.data)).toBe(expected)
+    callback(resp)

 describe 'AWS.DynamoDB', ->
   this.service = new AWS.DynamoDB(config)

   describe 'listTables', ->
     it 'should send a request', ->
-      helpers.integration.apply(this, ['{"TableNames":[]}'])
+      integration this, (resp) ->
+        expect(resp.error).toBe(null)
+        expect(JSON.stringify(resp.data)).toBe('{"TableNames":[]}')


modals: Ember.run.later instead of setTimeout, to fix errors in ember testing

--- app/scripts/components/BsModalComponent.coffee
+++ app/scripts/components/BsModalComponent.coffee
@@ -49,18 +49,15 @@
     show: ->
         @set 'isVisible', true
         current = this
-        setTimeout (->
+        Ember.run.later(@,->
             current.set 'isVis', true
-            return
-        ), 15
-        return
+        , 15)

     hide: ->
         @set 'isVis', false
         current = this
         @$().one 'webkitTransitionEnd', (e) ->
             current.set 'isVisible', false
-            return
         false

     toggle: ->
@@ -82,7 +79,6 @@
         current = this
         @$().one 'webkitTransitionEnd', (e) ->
             if current.get('manual') then current.destroy() else current.hide()
-            return
          @trigger 'closed'


Fix tests

Fix tests

--- test/openfisca/mapping/reverse.coffee
+++ test/openfisca/mapping/reverse.coffee
@@ -27,6 +27,13 @@
     bourse_college: '2014-11': 1
     bourse_lycee: '2014-11': 1
     paris_logement_familles: '2014-11': 1
+    paris_forfait_famille: '2014-11': 1
+    paris_logement_psol: '2014-11': 1
+    paris_logement: '2014-11': 1
+    paris_logement_plfm: '2014-11': 1
+    paris_logement_aspeh: '2014-11': 1
+    paris_energie_famille: '2014-11': 1
+    paris_complement_sante: '2014-11': 1
     adpa: '2014-11': 1
     ppa: '2014-11': 1

@@ -44,21 +51,21 @@
   actual = reverseMap OPENFISCA_RESPONSE, SITUATION

   it 'should remove unused properties', ->
-    actual.should.not.have.property 'id'
+    actual.calculatedPrestations.should.not.have.property 'id'

   it 'should extract individual prestations', ->
-    actual.aah.should.equal 2
+    actual.calculatedPrestations.aah.should.equal 2

   describe 'of an amount', ->
     it 'should round', ->
-      actual.aspa.should.equal 1.2
+      actual.calculatedPrestations.aspa.should.equal 1.2

     it 'should annualize a yearly amount', ->
-      actual.acs.should.equal 1 * 12
+      actual.calculatedPrestations.acs.should.equal 1 * 12

   describe 'of a boolean', ->
     it 'should be a pass-through', ->
-      actual.cmu_c.should.be.false
+      actual.calculatedPrestations.cmu_c.should.be.false

   describe 'of an uncomputable value', ->
     REASON = 'tns'
@@ -68,5 +75,5 @@
       value: [familles: [familleWithUncomputableRSA], individus: []]

     it 'should set the value to the identifier of the uncomputability', ->
-      reverseMap(openfiscaResponseWithUncomputableRSA, SITUATION).rsa.should.equal REASON
+      reverseMap(openfiscaResponseWithUncomputableRSA, SITUATION).calculatedPrestations.rsa.should.equal REASON


removes old reference to dependance injector

--- source/module.coffee
+++ source/module.coffee
@@ -11,7 +11,7 @@

   initialize: (injector, modules) ->

-    if not injector? then throw new Error 'Space.Module::initialize needs an instance of Dependance.Injector as first argument.'
+    if not injector? then throw new Error 'Space.Module::initialize needs an instance of Space.Injector as first argument.'

     for module in @RequiredModules


Replace null with defaultfor default print style

--- src/scripts/modules/media/tabs/metadata/metadata.coffee
+++ src/scripts/modules/media/tabs/metadata/metadata.coffee
@@ -24,7 +24,7 @@
       model.url = linksHelper.getModelPath(model)
       model.printStyles =
         {
-          null: 'Default' ,
+          'default' : 'Default',
           'ccap-physics' : 'CCAP Physics',
           'ccap-sociology' : 'CCAP Sociology',
           'ccap-biology' : 'CCAP Biology',


List HTML5 date types that should use simulateSelect

--- lib/dom_utils.coffee
+++ lib/dom_utils.coffee
@@ -93,6 +93,11 @@
   #
   # Selectable means that we should use the simulateSelect method to activate the element instead of a click.
   #
+  # The html5 input types that should use simulateSelect are:
+  #   ["date", "datetime", "datetime-local", "email", "month", "number", "password", "range", "search",
+  #    "submit", "tel", "text", "time", "url", "week"]
+  # An unknown type will be treated the same as "text", in the same way that the browser does.
+  #
   isSelectable: (element) ->
     unselectableTypes = ["button", "checkbox", "color", "file", "hidden", "image", "radio", "reset"]
     (element.nodeName.toLowerCase() == "input" && unselectableTypes.indexOf(element.type) == -1) ||


Fixup 0f97b59, rough port of media-grid js.

--- src/js/jquery.hlf.media-grid.coffee
+++ src/js/jquery.hlf.media-grid.coffee
@@ -176,7 +176,7 @@
             top: 'auto', left: 'auto', bottom: 'auto', right: 'auto'
             position: $item.data @attr('initial-layout-type')

-        @layoutItems()
+        @_layoutItems()
         return
       , @_delay
       return


Added a check that ends "runs" if the number of cranes hits zero.

--- src/lib/rickshaw_strip_chart.coffee
+++ src/lib/rickshaw_strip_chart.coffee
@@ -29,7 +29,10 @@
     years = [@year...(@year+@numYears)]
     offsets = years.map (x) -> Math.round(20*(Math.random()-0.65))
     start = @numCranes
-    newVals = offsets.reduce ((l, r) -> l.concat([l[l.length-1]+r])), [start]
+    newVals = offsets.reduce ((l, r) -> l.concat([Math.max(0, l[l.length-1]+r)])), [start]
+    firstZero = newVals.indexOf(0)
+    if firstZero > -1
+      newVals = newVals[..firstZero]
     newData = newVals.map (v, i) => { x: @year+i, y: v }
     @values.push({color: "rgba(0, 0, 0, 0.1)", data: newData})


Made start year dynamic.

Made start year dynamic.

The start year of the simulation was initial hard-wired to 2015, but now actually starts at whatever the current year is.

--- src/lib/rickshaw_strip_chart.coffee
+++ src/lib/rickshaw_strip_chart.coffee
@@ -6,7 +6,7 @@

 class RickshawStripChart
   values: null
-  year: 2015
+  year: new Date().getFullYear()
   numYears: 100
   runNumber: 0
   numRuns: 50


no message

no message

--- scripts/hello.coffee
+++ scripts/hello.coffee
@@ -1,7 +1,16 @@
 module.exports = (robot) ->
+  #Ă΂ē
   robot.respond /who are you/i, (msg) ->
     msg.send "I'm hubot!"

+  #`l̔ɔ
   robot.hear /HELLO$/i, (msg) ->
     msg.send "hello!"

+  #msgIuWFNg
+  robot.respond /who am I/i, (msg) ->
+    msg.send "You are #{msg.message.user.name}"
+  #K\
+  robot.respond /who am I/i, (msg) ->
+    msg.send "You are #{msg.message.user.name}"
+


failing timeout spec with all & aborts

failing timeout spec with all & aborts

--- spec/Thenable.coffee
+++ spec/Thenable.coffee
@@ -126,27 +126,54 @@
       t.deliver 2

   describe 'with all & return values', ->
-    it 'should resolve', (done) ->
-      t = Root()
-
-      y1 = (c, data) ->
-        th = Root()
-        pr = th.then 'yep-1', ->
-          1
-        th.deliver data
-        pr
-
-      y2 = (c, data) ->
-        th = Root()
-        th.deliver data
-        .then 'yep-2', ->
-          2
-      y3 = (c, data) ->
-        th = Root()
-        th.deliver data
-        .then 'yep-3', ->
-          3
-
+
+    y1 = (c, data) ->
+      th = Root()
+      pr = th.then 'yep-1', ->
+        1
+      th.deliver data
+      pr
+
+    y2 = (c, data) ->
+      th = Root()
+      th.deliver data
+      .then 'yep-2', ->
+        2
+    y3 = (c, data) ->
+      th = Root()
+      th.deliver data
+      .then 'yep-3', ->
+        3
+
+    shouldResolve = (n1,n2,n3) ->
+      it 'should resolve', (done) ->
+        t = Root()
+        t.deliver {}
+        .all [y1, y2, y3]
+        .then 'all-yep', (choice, data) ->
+          chai.expect(data).to.eql [1,2,3]
+          data
+        .all [n1, n2, n3]
+        .then 'all-nope', (choice, data) ->
+          {}
+        .else 'all-nope-else', (choice, result) ->
+          if result.data?
+            data = result.data
+          else
+            data = result
+          chai.expect(data).to.eql
+            yeps: [1,2,3]
+            nopes: 1
+          return data
+        .finally (choice, data) ->
+          chai.expect(choice.namedPath()).to.eql ['all-yep', 'all-nope-else']
+          chai.expect(data).to.eql
+            yeps: [1,2,3]
+            nopes: 1
+          done()
+          true
+
+    describe 'with throws', ->
       n1 = (c, data) ->
         th = Root()
         th.deliver data
@@ -174,26 +201,34 @@
             yeps: data
             nopes: 3
           throw e
-      t.deliver {}
-      .all [y1, y2, y3]
-      .then 'all-yep', (choice, data) ->
-        chai.expect(data).to.eql [1,2,3]
-        data
-      .all [n1, n2, n3]
-      .then 'all-nope', (choice, data) ->
-        {}
-      .else 'all-nope-else', (choice, e) ->
-        chai.expect(e.data).to.eql
-          yeps: [1,2,3]
-          nopes: 1
-        return e.data
-      .finally (choice, data) ->
-        chai.expect(choice.namedPath()).to.eql ['all-yep', 'all-nope-else']
-        chai.expect(data).to.eql
-          yeps: [1,2,3]
-          nopes: 1
-        done()
-        true
+      shouldResolve n1, n2, n3
+
+    describe 'with aborts', ->
+      n1 = (c, data) ->
+        th = Root()
+        th.deliver data
+        .then 'nope-1', (n, data) ->
+          n.abort 'nope-1', {
+            yeps: data
+            nopes: 1
+          }
+      n2 = (c, data) ->
+        Root()
+        .deliver data
+        .then 'nope-2', (n, data) ->
+          n.abort 'nope-2', {
+            yeps: data
+            nopes: 2
+          }
+      n3 = (c, data) ->
+        Root()
+        .deliver data
+        .then 'nope-3', (n, data) ->
+          n.abort 'nope-3', {
+            yeps: data
+            nopes: 3
+          }
+      shouldResolve n1, n2, n3

   describe 'with all & branches', ->
     it 'should resolve with result per branch', (done) ->


cleanedup randomcolor method

cleanedup randomcolor method

--- app/assets/javascripts/home.coffee
+++ app/assets/javascripts/home.coffee
@@ -2,7 +2,8 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
 getRandomColor = (s, l) ->
-  color = "hsl(" + Math.random() * 360 + ", " + s + "%," + l + "%)"
+  randomdeg = Math.random() * 360
+  color = "hsl(" + randomdeg + ", " + s + "%," + l + "%)"
   return color

 jQuery(document).ready ($) ->


constructing uploader option (VI)

constructing uploader option (VI)

--- test/uploader/server.coffee
+++ test/uploader/server.coffee
@@ -33,7 +33,12 @@
       if part.fileName
         fullname = "#{__dirname}/public/uploads/#{part.fileName}"
         router.log "BUFFER:", part.fileData
-        fs.writeFileSync fullname, part.fileData
+        router.log "First char (hex):", new Buffer(part.fileData)[0]
+        if part.contentType.indexOf('text') >= 0
+          fs.writeFileSync fullname, part.fileData
+        else
+          buffer = new Buffer(part.fileData, 'binary')
+          fs.writeFileSync fullname, buffer, 'binary'
         res.write '
'
         if part.contentType.indexOf('image') >= 0
           res.write ""
@@ -47,7 +52,7 @@

   res.end """

<button onclick="history.back();">Back</button>

-	      """
+          """

   #router.log "Someone is trying to upload something"


Add space to Flag button

Add space to Flag button

--- app/components/subject-viewer.cjsx
+++ app/components/subject-viewer.cjsx
@@ -122,7 +122,9 @@
         }

           {if @props.workflow?.configuration?.enable_subject_flags
-            <FlagSubjectButton className="secret-button" classification={@props.classification} />}
+
+              <FlagSubjectButton className="secret-button" classification={@props.classification} />{' '}
+            }
           {if @props.subject?.metadata?

               <button type="button" className="secret-button" aria-label="Metadata" title="Metadata" onClick={@showMetadata}>


fallback if suggested friends is empty

fallback if suggested friends is empty

--- client/game.coffee
+++ client/game.coffee
@@ -24,12 +24,18 @@
       not noughts.appInstalled_[x.uid])
   return _.pluck(installed.concat(notInstalled), "uid")

-showInviteDialog = (inviteCallback) -> FB.ui
-  method: "apprequests",
-  title: "Select an opponent",
-  filters: [{name: "Friends", user_ids: getSuggestedFriends()}],
-  max_recipients: 1,
-  message: "Want to play some Noughts?", inviteCallback
+showInviteDialog = (inviteCallback) ->
+  suggestedFriends = getSuggestedFriends()
+  if suggestedFriends
+    filters = [{name: "Friends", user_ids: suggestedFriends}]
+  else
+    filters = ["app_non_users"]
+  FB.ui
+    method: "apprequests",
+    title: "Select an opponent",
+    filters: filters
+    max_recipients: 1,
+    message: "Want to play some Noughts?", inviteCallback

 displayNotice = (msg) -> $(".nNotification").text(msg)


Remove Change Listener from About Index

Remove Change Listener from About Index

--- app/pages/project/about/index.cjsx
+++ app/pages/project/about/index.cjsx
@@ -1,7 +1,6 @@
 React = require 'react'
 TitleMixin = require '../../../lib/title-mixin'
 {Markdown} = require 'markdownz'
-ChangeListener = require '../../../components/change-listener'
 PromiseRenderer = require '../../../components/promise-renderer'
 Translate = require 'react-translate-component'
 counterpart = require 'counterpart'
@@ -30,40 +29,37 @@
       .reduce(((accum, page) -> accum[page.url_key] = page.title; accum), {})

   render: ->
-    <ChangeListener target={@props.project}>{=>
-      <PromiseRenderer promise={@props.project.get 'owner'}>{(owner) =>
-        [ownerName, name] = @props.project.slug.split('/')
-        projectPath = "/projects/#{ownerName}/#{name}"
+    <PromiseRenderer promise={@props.project.get 'owner'}>{(owner) =>
+      [ownerName, name] = @props.project.slug.split('/')
+      projectPath = "/projects/#{ownerName}/#{name}"

-

-          <PromiseRenderer promise={@props.project.get 'pages'}>{(pages) =>
-            pageTitles = @getPageTitles(pages)
-
-              {if pageTitles.science_case
-                <Link to="#{projectPath}/about/research" activeClassName="active"className="about-tabs">
-                  <Translate content= "nav.research" />
-                </Link>}
-              {unless @props.project.redirect
-                <Link to="#{projectPath}/about/team" activeClassName="active" className="about-tabs">
-                  <Translate content= "nav.team" />
-                </Link>}
-              {if pageTitles.results
-                <Link to="#{projectPath}/about/results" activeClassName="active"className="about-tabs">
-                  <Translate content= "nav.results" />
-                </Link>}
-              {if pageTitles.education
-                <Link to="#{projectPath}/about/education" activeClassName="active" className="about-tabs">
-                  <Translate content= "nav.education" />
-                </Link>}
-              {if pageTitles.faq
-                <Link to="#{projectPath}/about/faq" activeClassName="active" className="about-tabs">
-                  <Translate content= "nav.faq" />
-                </Link>}
-
-          }</PromiseRenderer>
+

+        <PromiseRenderer promise={@props.project.get 'pages'}>{(pages) =>
+          pageTitles = @getPageTitles(pages)
+
+            {if pageTitles.science_case
+              <Link to="#{projectPath}/about/research" activeClassName="active"className="about-tabs">
+                <Translate content= "nav.research" />
+              </Link>}
+              <Link to="#{projectPath}/about/team" activeClassName="active" className="about-tabs">
+                <Translate content= "nav.team" />
+              </Link>
+            {if pageTitles.results
+              <Link to="#{projectPath}/about/results" activeClassName="active"className="about-tabs">
+                <Translate content= "nav.results" />
+              </Link>}
+            {if pageTitles.education
+              <Link to="#{projectPath}/about/education" activeClassName="active" className="about-tabs">
+                <Translate content= "nav.education" />
+              </Link>}
+            {if pageTitles.faq
+              <Link to="#{projectPath}/about/faq" activeClassName="active" className="about-tabs">
+                <Translate content= "nav.faq" />
+              </Link>}
+
+        }</PromiseRenderer>

-          {React.cloneElement(@props.children, {owner: owner, project: @props.project, user: @props.user})}
+        {React.cloneElement(@props.children, {owner: owner, project: @props.project, user: @props.user})}

-

-      }</PromiseRenderer>
-    }</ChangeListener>
+

+    }</PromiseRenderer>


improved closeFlex logic when switching tabbars

--- packages/rocketchat-ui-flextab/flex-tab/flexTabBar.coffee
+++ packages/rocketchat-ui-flextab/flex-tab/flexTabBar.coffee
@@ -2,15 +2,11 @@
 	active: ->
 		return 'active' if @template is RocketChat.TabBar.getTemplate() and RocketChat.TabBar.isFlexOpen()
 	buttons: ->
-		RocketChat.TabBar.getButtons()
 		return RocketChat.TabBar.getButtons()
 	title: ->
 		return t(@i18nTitle) or @title
 	visible: ->
 		if @groups.indexOf(RocketChat.TabBar.getVisibleGroup()) is -1
-			# if it was active, close it
-			if @template is RocketChat.TabBar.getTemplate() and RocketChat.TabBar.isFlexOpen()
-				RocketChat.TabBar.closeFlex()
 			return 'hidden'

 Template.flexTabBar.events
@@ -29,3 +25,18 @@
 			RocketChat.TabBar.setTemplate @template, ->
 				$('.flex-tab')?.find("input[type='text']:first")?.focus()
 				$('.flex-tab .content')?.scrollTop(0)
+
+Template.flexTabBar.onCreated ->
+	# close flex if the visible group changed and the opened template is not in the new visible group
+	@autorun =>
+		visibleGroup = RocketChat.TabBar.getVisibleGroup()
+
+		Tracker.nonreactive =>
+			openedTemplate = RocketChat.TabBar.getTemplate()
+			exists = false
+			RocketChat.TabBar.getButtons().forEach (button) ->
+				if button.groups.indexOf(visibleGroup) isnt -1 and openedTemplate is button.template
+					exists = true
+
+			unless exists
+				RocketChat.TabBar.closeFlex()


Allow Delete operator to optional remain on last character

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -92,9 +92,10 @@
   motion: null
   complete: null
   vimState: null
+  allowEOL: null
   selectOptions: null

-  constructor: (@editor, @vimState, @motion, @selectOptions={}) ->
+  constructor: (@editor, @vimState, {@motion, @allowEOL, @selectOptions}) ->
     @complete = false

   isComplete: -> @complete
@@ -113,7 +114,7 @@
         if _.last(@motion.select(1, @selectOptions))
           @editor.getSelection().delete()

-        @editor.moveCursorLeft() if cursor.isAtEndOfLine() and !@motion.isLinewise?()
+        @editor.moveCursorLeft() if !@allowEOL and cursor.isAtEndOfLine() and !@motion.isLinewise?()

       if @motion.isLinewise?()
         @editor.setCursorScreenPosition([cursor.getScreenRow(), 0])
@@ -149,11 +150,11 @@
   #
   # Returns nothing.
   execute: (count=1) ->
-    operator = new Delete(@editor, @vimState, @motion, excludeWhitespace: true)
+    operator = new Delete(@editor, @vimState,
+      motion: @motion, allowEOL: true, selectOptions: {excludeWhitespace: true})
     operator.execute(count)

     @vimState.activateInsertMode()
-    @editor.moveCursorRight() if @motion.constructor.name == 'MoveToLastCharacterOfLine'

   # Public: Marks this as complete and saves the motion.
   #


fix occurrence spec which use replace operator


Copy edit

Copy edit

--- examples/PrePostSquish.cjsx
+++ examples/PrePostSquish.cjsx
@@ -31,7 +31,7 @@
         Squish four
       </Grid>

Pre

-      {"x is number of columns of white space to
+      {"where x is number of columns of white space to
         add to the beginning of the span."}

@@ -43,7 +43,7 @@
         Pre four
       </Grid>

Post

-      {"x is number of columns of white space to
+      {"where x is number of columns of white space to
         add at the end of the span."}


fix(lint): Fix linter issues on account-sidebar-section.cjsx

--- internal_packages/account-sidebar/lib/account-sidebar-section.cjsx
+++ internal_packages/account-sidebar/lib/account-sidebar-section.cjsx
@@ -96,7 +96,7 @@
     @setState(showCreateInput: not @state.showCreateInput)

   _onInputBlur: =>
-   @setState(showCreateInput: false) if not @_clickingCreateButton
+    @setState(showCreateInput: false) if not @_clickingCreateButton

   _onInputKeyDown: (event, section) =>
     if event.key is 'Escape'


Use ::setInputEnabled to disable input when available

The approach of preempting textinput events to prevent handling of input
outside of insert mode doesn't mesh well with the new React editor
because we don't use jQuery to process input events. I added an explicit
API for disabling input in atom/atom@64870c733, which is used here if
available. We retain the old interception approach for the time being
for compatibility with the existing editor.

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -30,6 +30,7 @@
     params.id = 0;

     @setupCommandMode()
+    @editorView.setInputEnabled?(false)
     @registerInsertIntercept()
     @registerInsertTransactionResets()
     if atom.config.get 'vim-mode.startInInsertMode'
@@ -364,6 +365,7 @@
   # Returns nothing.
   activateInsertMode: (transactionStarted = false)->
     @mode = 'insert'
+    @editorView.setInputEnabled?(true)
     @editor.beginTransaction() unless transactionStarted
     @submode = null
     @changeModeClass('insert-mode')
@@ -371,6 +373,7 @@

   deactivateInsertMode: ->
     return unless @mode == 'insert'
+    @editorView.setInputEnabled?(false)
     @editor.commitTransaction()
     transaction = _.last(@editor.buffer.history.undoStack)
     item = @inputOperator(@history[0])


Use buffer row data attribute to find gutter lines

--- src/react-editor-view.coffee
+++ src/react-editor-view.coffee
@@ -54,7 +54,7 @@
       @gutter.find('.line-number').removeClass(klass)

     @gutter.addClassToLine = (bufferRow, klass) =>
-      lines = @gutter.find(".line-number-#{bufferRow}")
+      lines = @gutter.find("[data-buffer-row='#{bufferRow}']");
       lines.addClass(klass)
       lines.length > 0


Fix message file linking when switching to/from history mode


add fade in functionality to composite model view


A fix for the scrolling up bug

A fix for the scrolling up bug

--- src/scripts/directive.coffee
+++ src/scripts/directive.coffee
@@ -278,7 +278,6 @@
                           return
                         for item in result.reverse()
                           lastScope = insert first--, item, true
-                        first -= result.length
                         console.log "prepended #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
                         finalize()
                         lastScope.$watch 'adjustBuffer', ->


pager who's on call for <search>

pager who's on call for <search>

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -95,10 +95,9 @@
         msg.finish
         return

-      withScheduleMatching msg, msg.match[2], (schedule) ->
-
-        scheduleId = schedule.id
-        return unless scheduleId
+      withScheduleMatching msg, msg.match[2], (matchingSchedule) ->
+
+        return unless matchingSchedule.id

         start     = moment().format()
         minutes   = parseInt msg.match[3]
@@ -108,9 +107,9 @@
           'end':       end,
           'user_id':   userId
         }
-        withCurrentOncall msg, scheduleId, (old_username) ->
+        withCurrentOncall msg, matchingSchedule, (old_username, schedule) ->
           data = { 'override': override }
-          pagerDutyPost msg, "/schedules/#{scheduleId}/overrides", data, (json) ->
+          pagerDutyPost msg, "/schedules/#{schedule.id}/overrides", data, (json) ->
             if json.override
               start = moment(json.override.start)
               end = moment(json.override.end)
@@ -234,13 +233,12 @@
     pagerDutyGet msg, "/schedules", query, (json) ->
       buffer = ''
       schedules = json.schedules
-      if schedules
+      if schedules.length > 0
         for schedule in schedules
           buffer += "* #{schedule.name} - https://#{pagerDutySubdomain}.pagerduty.com/schedules##{schedule.id}\n"
         msg.send buffer
       else
         msg.send 'No schedules found!'
-

   robot.respond /(pager|major)( me)? (schedule|overrides)( (.+))?$$/i, (msg) ->
     query = {
@@ -324,9 +322,18 @@
         msg.send "Something went weird."

   # who is on call?
-  robot.respond /who('s|s| is)? (on call|oncall)/i, (msg) ->
-    withCurrentOncall msg, (username) ->
-      msg.reply "#{username} is on call"
+  robot.respond /who('s|s| is)? (on call|oncall)( for (.+))?/i, (msg) ->
+    query = {}
+    if msg.match[4]
+      query['query'] = msg.match[4]
+    pagerDutyGet msg, "/schedules", query, (json) ->
+      schedules = json.schedules
+      if schedules.length > 0
+        for s in schedules
+          withCurrentOncall msg, s, (username, schedule) ->
+            msg.send "* #{username} is on call for #{schedule.name} - https://#{pagerDutySubdomain}.pagerduty.com/schedules##{schedule.id}\n"
+      else
+        msg.send 'No schedules found!'

   parseIncidentNumbers = (match) ->
     match.split(/[ ,]+/).map (incidentNumber) ->
@@ -481,7 +488,7 @@
         msg.send "#{q} matched #{json.schedules.length} schedules. Can you be more specific?"
         return

-  withCurrentOncall = (msg, scheduleId, cb) ->
+  withCurrentOncall = (msg, schedule, cb) ->
     oneHour = moment().add('hours', 1).format()
     now = moment().format()

@@ -490,9 +497,9 @@
       until: oneHour,
       overflow: 'true'
     }
-    pagerDutyGet msg, "/schedules/#{scheduleId}/entries", query, (json) ->
+    pagerDutyGet msg, "/schedules/#{schedule.id}/entries", query, (json) ->
       if json.entries and json.entries.length > 0
-        cb(json.entries[0].user.name)
+        cb(json.entries[0].user.name, schedule)

   pagerDutyIncident = (msg, incident, cb) ->
     pagerDutyGet msg, "/incidents/#{encodeURIComponent incident}", {}, (json) ->


Add test fragments support for visual test helpers.

--- tests/js/base-visual.coffee
+++ tests/js/base-visual.coffee
@@ -23,13 +23,18 @@
       config.vars ?= {}
       # - Build test template vars.
       vars = _.pick config, 'label', 'className', 'footerHtml'
-      vars.footerHtml ?= ''
+      vars.footerHtml ?= '' unless config.asFragments
       vars.html = _.template config.template, config.vars
-      # - Render test to get container element.
-      $context = $('#main').renderVisualTest vars
+      # - Render test to get test context (or fragments).
+      $container = if config.asFragments then $('body') else $('#main')
+      opts = { template: '<%= html %>' } if config.asFragments
+      $test = $container.renderVisualTest vars, opts
+      if config.asFragments
+        $test = $test.addClass 'visual-test-fragment'
+                     .filter '.visual-test-fragment'
       # - Run tests.
-      config.beforeTest $context if config.beforeTest?
-      config.test $context
+      config.beforeTest $test if config.beforeTest?
+      config.test $test

   # Helpers for additional setup.
   _.extend $.visualTest,


Remove dead code.

Remove dead code.

--- tests/scripts/fuzzer.coffee
+++ tests/scripts/fuzzer.coffee
@@ -16,15 +16,6 @@
           op.value = op.value.replace(NBSP_FILTER, " ")
       return delta

-    get: (ref) ->
-      window.Fuzzer[ref]
-
-    getEditorDelta: ->
-      window.editor.getDelta()
-
-    set: (ref, val) ->
-      window.Fuzzer[ref] = val
-
     setDeltaReplay: (docDelta, deltaRef) ->
       d = JSON.parse(docDelta)
       window.Fuzzer[deltaRef] =


Graph: Don't expect name: NoFlo runtime

Graph: Don't expect name: NoFlo runtime

This is a NoFlo specific default

--- src/WebSocket.coffee
+++ src/WebSocket.coffee
@@ -111,7 +111,6 @@
                 baseDir: path.resolve __dirname, '../'
                 id: 'foo'
                 main: true
-                name: 'NoFlo runtime'
             ,
               protocol: 'graph'
               command: 'addnode'


Fixed adding attributes when there isn't an attribute object

--- src/api/utils.coffee
+++ src/api/utils.coffee
@@ -140,7 +140,7 @@
     return undefined

   if !elem.attribs
-    return undefined
+    elem.attribs = {}

   # Return the entire attribs object if no attribute specified
   if !name


Allow plain text files in subject uploads (#6202)

Add `text/plain` to the list of file types accepted by the uploader in the Project Builder's subject set page. Move the list of accepted types into a `VALID_FILE_TYPES` constant so it isn't buried in the middle of the file and hard to find.

--- app/pages/lab/subject-set.cjsx
+++ app/pages/lab/subject-set.cjsx
@@ -17,6 +17,15 @@
 NOOP = Function.prototype

 VALID_SUBJECT_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.mp3', '.m4a', '.mpeg', '.txt', '.json']
+VALID_FILE_TYPES = [
+  'text/csv',
+  'text/plain',
+  'text/tab-separated-values',
+  'image/*',
+  'video/*',
+  'audio/*',
+  'application/json'
+]
 INVALID_FILENAME_CHARS = ['/', '\\', ':', ',']
 MAX_FILE_SIZE = 1000 * 1024

@@ -212,7 +221,7 @@

You've reached your subject upload limit. Please  contact us to request changes to your allowance.

       else

-          <UploadDropTarget accept={"text/csv, text/tab-separated-values, image/*, video/*, audio/*, text/*, application/json"} multiple onSelect={@handleFileSelection}>
+          <UploadDropTarget accept={VALID_FILE_TYPES.join(',')} multiple onSelect={@handleFileSelection}>
             Drag-and-drop or click to upload manifests and subject images here (you must select the media files as well as the manifest)

             Manifests must be .csv or .tsv. The first row should define metadata headers. All other rows should include at least one reference to an image filename in the same directory as the manifest.

             Headers that begin with "#" or "//" denote private fields that will not be visible to classifiers in the main classification interface or in the Talk discussion tool.


14 update

14 update

--- test/scenario-based-testing.coffee
+++ test/scenario-based-testing.coffee
@@ -158,10 +158,10 @@

   it 'Scenario 14. Attribute with two space at the first and last of the first attribute and one space on the first of the second attribute ', ->

-    input = fs.readFileSync './test/scenarios/scenario-13-input.cohtml', 'utf8'
+    input = fs.readFileSync './test/scenarios/scenario-14-input.cohtml', 'utf8'
     # console.log input
     output = toHtml5 input
     # console.log output
-    expectedOutput = fs.readFileSync './test/scenarios/scenario-13-expected.html', 'utf8'
+    expectedOutput = fs.readFileSync './test/scenarios/scenario-14-expected.html', 'utf8'
     # console.log expectedOutput
     expect(output).to.equal(expectedOutput)


Try out 60 FPS

Try out 60 FPS

--- app/scripts/scenes/Game.coffee
+++ app/scripts/scenes/Game.coffee
@@ -3,6 +3,7 @@
   #
   # import from globals
   Game = window.Game
+  Crafty.timer.FPS(60)

   scriptName = data?.script ? 'Stage1'
   script = Game.Scripts[scriptName]


find motif: fixes #52

find motif: fixes #52

Former-commit-id: f4f1979f42c38abef8544f6df387dfb7889bff51 [formerly c1ae1ab309174259c215dcd824eaa43efcbc8f8d]
Former-commit-id: 7bbc2cf9dc6e45f6e362712e24eab46da2f2a47f

--- src/menu/views/SelectionMenu.coffee
+++ src/menu/views/SelectionMenu.coffee
@@ -10,7 +10,7 @@

   render: ->
     menu = new MenuBuilder("Selection")
-    menu.addNode "Find all (supports RegEx)", =>
+    menu.addNode "Find Motif (supports RegEx)", =>
       search = prompt "your search", "D"
       # marks all hits
       search = new RegExp search, "gi"


More reply box logic cleanup and hide page scroll on mobile

--- resources/assets/coffee/_classes/forum-topic-reply.coffee
+++ resources/assets/coffee/_classes/forum-topic-reply.coffee
@@ -66,10 +66,8 @@
     localStorage.setItem "forum-topic-reply--#{document.location.pathname}--#{key}", value

-  activate: (e) =>
+  activate: =>
     @setState 'active', '1'
-    @box[0].dataset.state = 'active'
-    button.classList.add 'js-activated' for button in @toggleButtons

     @stickyFooter.markerEnable @marker()
     $.publish 'stickyFooter:check'
@@ -89,18 +87,13 @@
     @inputChange()
     $input[0].selectionStart = data.length

-    @activate(e)
+    @activate()

-  deactivate: (e) =>
-    e.preventDefault() if e
-
+  deactivate: =>
     @stickyFooter.markerDisable @marker()
     @setState 'active', '0'
-    delete @box[0].dataset.state if @box[0].dataset.state?
-    button.classList.remove 'js-activated' for button in @toggleButtons
     $.publish 'stickyFooter:check'
-    @stickOrUnstick() if osu.isMobile()
     @disableFlash()

@@ -139,10 +132,10 @@

   stickOrUnstick: (_e, target) =>
     stick =
-      if osu.isMobile()
-        @box[0]?.dataset.state == 'active'
+      if osu.isDesktop()
+        target == 'forum-topic-reply'
       else
-        target == 'forum-topic-reply'
+        @getState('active') == '1'

     @toggleStick(stick)

@@ -152,10 +145,10 @@
       @deactivate()
     else
       @activate()
-      @$input().focus()

   toggleDeactivate: (e) =>
+    # prevent reactivation if the button is located inside the form
     e.stopPropagation()
     @deactivate()

@@ -163,18 +156,32 @@
   toggleStick: (stick) =>
     isSticking = @getState('sticking') == '1'

+    document.body.style.overflow =
+      if !stick || osu.isDesktop()
+        ''
+      else
+        'hidden'
+
     return if stick == isSticking
+
+    box = @box[0]

     if stick
       @setState 'sticking', '1'
+      box.dataset.state = 'active'
       target = @fixedBar[0]
+      buttonAction = 'add'
     else
       @deleteState 'sticking'
+      delete box.dataset.state if box.dataset.state?
       target = @container[0]
+      buttonAction = 'remove'
+
+    button.classList[buttonAction] 'js-activated' for button in @toggleButtons

     $input = @$input()
     inputFocused = $input.is(':focus')

-    target.insertBefore(@box[0], target.firstChild)
+    target.insertBefore(box, target.firstChild)

     $input.focus() if inputFocused


Fix failtime chart when the data is all zero

--- resources/assets/coffee/_classes/stacked-bar-chart.coffee
+++ resources/assets/coffee/_classes/stacked-bar-chart.coffee
@@ -38,6 +38,7 @@
           height: if previousData? then previousData.value + previousData.height else 0

     @max = d3.max _.map @data, (m) -> _.sumBy m, 'value'
+    @max = 1 if @max == 0

     @resize()


Remove console logging

Remove console logging

--- resources/assets/coffee/react/beatmapset-page/main.coffee
+++ resources/assets/coffee/react/beatmapset-page/main.coffee
@@ -28,7 +28,6 @@

     @state = JSON.parse(@props.container.dataset.state ? 'null')
     @restoredState = @state?
-    console.log 'restored state?', @restoredState

     if !@restoredState
       optionsHash = BeatmapsetPageHash.parse location.hash


100% test coverage

100% test coverage

--- test/tourney-time.spec.coffee
+++ test/tourney-time.spec.coffee
@@ -12,7 +12,7 @@
 describe 'tourney-time', ->
   describe 'given one team', ->
     it 'throws an error', ->
-      expect(tourneyTime).to.throw
+      expect(()-> tourneyTime teams:0).to.throw "You must have at least two teams to continue"

   describe 'with two playing areas, 30 min games, and 10 min rest', ->
     defaultTourney = {areas: 2, time: 30, rest: 10}


Fix specs

Fix specs

--- src/mongodb_event_store.spec.coffee
+++ src/mongodb_event_store.spec.coffee
@@ -6,13 +6,12 @@
   saveErr = null
   domainEvent =
     name: 'doSomething'
-    aggregate:
-      name: 'OurAggregate'
+    context: 'whatever'

   beforeEach (done) ->
     mongoDBEventStore = require './mongodb_event_store'
     mongoDBEventStore.initialize (err) ->
-      mongoDBEventStore.db.collection 'OurAggregate', (err, collection) ->
+      mongoDBEventStore.db.collection 'whatever', (err, collection) ->
         collection.remove {}, ->
           mongoDBEventStore.save domainEvent, (err) ->
             saveErr = err
@@ -30,7 +29,7 @@

   describe '#find', ->
     it 'should find the stored DomainEvent', (done) ->
-      mongoDBEventStore.find 'OurAggregate', {}, (err, docs) ->
+      mongoDBEventStore.find 'whatever', {}, (err, docs) ->
         expect(docs).to.deep.equal [
           domainEvent
         ]


Fix lint

Fix lint

--- test/unit/remote/index.coffee
+++ test/unit/remote/index.coffee
@@ -370,12 +370,13 @@
                         should.exist doc.remote.binary
                         @couch.get created.id, (err, file) ->
                             should.not.exist err
+                            lastModified = doc.lastModification.toISOString()
                             file.should.have.properties
                                 path: '/backup'
                                 name: 'cat3.jpg'
                                 docType: 'file'
                                 creationDate: doc.creationDate.toISOString()
-                                lastModification: doc.lastModification.toISOString()
+                                lastModification: lastModified
                                 size: 36901
                             done()


[docs] Added test comment

[docs] Added test comment

--- public/angular_app/app/scripts/app.coffee
+++ public/angular_app/app/scripts/app.coffee
@@ -1,5 +1,6 @@
 'use strict'

+//added test comment
 ebenezerApp = angular.module('ebenezerApp', [
   'ngResource', 'ngSanitize',
   'ngAnimate', 'fx.animations','ngRoute',


Add failing test for list prefix requirement

--- test/unit/test_updates.coffee
+++ test/unit/test_updates.coffee
@@ -379,3 +379,64 @@

   @orderedCompleteCheckbox.click()

+asyncTest "update ignores items that look like Task List items but lack list prefix", ->
+  expect 3
+
+  $('#qunit-fixture').empty()
+
+  container = $ '<div>', class: 'js-task-list-container'
+
+  list = $ '<ul>', class: 'task-list'
+
+  item1 = $ '<li>', class: 'task-list-item'
+  item1Checkbox = $ '<input>',
+    type: 'checkbox'
+    class: 'task-list-item-checkbox'
+    disabled: true
+    checked: false
+
+  item2 = $ '<li>', class: 'task-list-item'
+  item2Checkbox = $ '<input>',
+    type: 'checkbox'
+    class: 'task-list-item-checkbox'
+    disabled: true
+    checked: false
+
+  field = $ '<textarea>', class: 'js-task-list-field', text: """
+    [ ] one
+    [ ] two
+    - [ ] three
+    - [ ] four
+  """
+
+  changes = """
+    [ ] one
+    [ ] two
+    - [ ] three
+    - [x] four
+  """
+
+  item1.append item1Checkbox
+  list.append item1
+  item1.expectedIndex = 1
+
+  item2.append item2Checkbox
+  list.append item2
+  item2.expectedIndex = 2
+
+  container.append list
+  container.append field
+
+  $('#qunit-fixture').append(container)
+  container.taskList()
+
+  field.on 'tasklist:changed', (event, index, checked) =>
+    ok checked
+    equal index, item2.expectedIndex
+    equal field.val(), changes
+
+  setTimeout ->
+    start()
+  , 20
+
+  item2Checkbox.click()


Components: Sidebar: Fix "Using Maps" error

--- client/app/lib/components/sidebar/index.coffee
+++ client/app/lib/components/sidebar/index.coffee
@@ -55,7 +55,7 @@

     isRendered = no

-    (@state.sharedMachines.concat @state.collaborationMachines).map (machine) =>
+    (@state.sharedMachines.concat @state.collaborationMachines).toList().map (machine) =>

       if not isRendered and @popoverNeeded machine
         isRendered = yes


video: model remove participant element fix

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -181,21 +181,7 @@

   ###*
-   * This is a convinient method.
-   * This is probably wrong but the way openTok sessions
-   * are working makes all subscriber videos invisible.
-   * This method's pure existence is to support it.
-   * p.s.: 173 is weird.
    *
-   * @param {OT.Subscriber|OT.Publisher} participant
   ###
-  fixParticipantVideo: (participant) ->

-    { video } = participant

-    KD.utils.wait 173, ->
-      vElement = video.videoElement()
-      vElement.style.left   = '-14px'
-      vElement.style.height = '265px'
-      vElement.style.width  = '355px'
-


Improve invalid location handling, fix max subinterval computation

--- server/router.coffee
+++ server/router.coffee
@@ -326,6 +326,8 @@
   @response.statusCode = 200
   @response.end(JSON.stringify({
     events: events.map (event) =>
+      if not event
+        return null
       baseIncidents = []
       constrainingIncidents = []
       event.incidents.map (incident) ->
@@ -365,7 +367,14 @@
         groupedLocSubIntervals = _.groupBy(locSubIntervals, 'start')
         maxSubintervals = []
         for group, subIntervalGroup of groupedLocSubIntervals
-          maxSubintervals.push(_.max(subIntervalGroup, (x) -> x.value))
+          subIntervalGroupTree = LocationTree.from(subIntervalGroup.map (x) -> x.location)
+          subIntervalGroupTree.children.forEach (locationNode) ->
+            maxSubintervals.push(_.max(subIntervalGroup, (subInterval) ->
+              if locationNode.value.id == subInterval.location.id
+                subInterval.value
+              else
+                0
+            ))
         if location in topLocations
           maxSubintervalsPerTopLocation = maxSubintervalsPerTopLocation.concat(maxSubintervals)
         maxSubintervals = _.sortBy(maxSubintervals, (x) -> x.start)


Optimizing imports.

Optimizing imports.

--- server/routes.coffee
+++ server/routes.coffee
@@ -9,7 +9,6 @@

 # Date formatting.
 dateFormat = require 'dateformat'
-now = new Date()

 # Sitemap.
 sitemap = (require 'sitemap').createSitemap ({
@@ -20,7 +19,7 @@
       url: '/'
       changefreq: 'weekly'
       priority: 1
-      lastmodISO: dateFormat(now, "yyyy-MM-dd")
+      lastmodISO: dateFormat(new Date(), "yyyy-MM-dd")
     }
   ]
 });


implemented get for individual jobs, just keep job IDs in the queue

--- server/server.coffee
+++ server/server.coffee
@@ -8,6 +8,7 @@

 CONTROLLER_ADDRESS = "tcp://0.0.0.0:5555"
+Q_PREFIX = '__q'

 redisC = null # the connection to redis

@@ -40,7 +41,7 @@

         addRoute "\/queue\/(.+?)\/?$", {
             GET: (match, response) ->
-                queue = match[1]
+                queue = Q_PREFIX + match[1]
                 redisC.lrange queue, 0, -1, (err, messages) ->
                     respond response, {
                         success: true,
@@ -48,20 +49,20 @@
                     }

             POST: (match, response, bag) ->
-                queue = match[1]
+                queue = Q_PREFIX + match[1]
                 id = uuid.generate()
                 job = JSON.stringify {
                     uuid: id,
                     message: bag
                 }

-                redisC.lpush queue, job, (err, status) ->
-                    respond response, { success: status, uuid: id }
-                    # Actually send this task out to be worked on
-                    pusher.send job
+                redisC.transaction ->
+                    redisC.set id, job
+                    redisC.lpush queue, id, (err, status) ->
+                        respond response, { success: status, uuid: id }

             DELETE: (match, response) ->
-                queue = match[1]
+                queue = Q_PREFIX + match[1]
                 redisC.del queue, (err, status) ->
                     respond response, {
                         success: status,
@@ -70,9 +71,8 @@

         addRoute "\/job\/(.+?)\/?$", {
             GET: (match, response) ->
-                respond response, {
-                    success: true
-                }
+                redisC.get match[1], (err, job) ->
+                    respond response, job
         }

         listener = http.createServer (request, response) ->
@@ -80,7 +80,7 @@
             message = ""
             request.on 'data', (data) ->
                 message += data
-            request.on 'end', () ->
+            request.on 'end', ->
                 if message
                     try
                         bag = JSON.parse(message)
@@ -99,6 +99,7 @@
                         found = true
                         methods[request.method](match, response, message)
                         _.breakLoop()
+
                 if not found
                     return404(response)


fix binding issue

fix binding issue

--- server/server.coffee
+++ server/server.coffee
@@ -105,7 +105,7 @@
         port = config.port or 7000
         host = config.host or "localhost"

-        listener.listen port, host, (err) ->
+        listener.listen port, host, (err) =>
             if err
                 throw err
             @emit('started', host, port)


Use openshift ip

Use openshift ip

--- server/server.coffee
+++ server/server.coffee
@@ -71,5 +71,6 @@
 app.use renderOnServer

 port = (process.env.OPENSHIFT_NODEJS_PORT || 3000)
+ip = (process.env.OPENSHIFT_NODEJS_IP || '0.0.0.0')
 app.listen port, () ->
   logger.info 'Example app listening on port ', port


change wording of opened state

change wording of opened state

--- src/smc-util/compute-states.coffee
+++ src/smc-util/compute-states.coffee
@@ -90,7 +90,7 @@
         commands : ['status', 'mintime']

     opened:
-        desc     : 'Project is available and ready to start running.'
+        desc     : 'Project is available and ready to try to run.'
         icon     : 'stop'
         display  : 'Stopped'
         stable   : true


Fixed activeTime tests

Fixed activeTime tests

--- tests/models/test_user.coffee
+++ tests/models/test_user.coffee
@@ -102,16 +102,15 @@
       expect(elapsed).to.equal 8
   describe '#isInactive()', ->
     it 'should be true when it is earlier than the start time', ->
-      start = @user.timetable.start
+      [start, end] = @user.activeHours()
       time = moment(start).subtract(2, 'hours')
       expect(@user.isInactive(time)).to.be.true
     it 'should be true when it is later than the end time', ->
-      end = @user.timetable.end
+      [start, end] = @user.activeHours()
       time = moment(end).add(2, 'hours')
       expect(@user.isInactive(time)).to.be.true
     it 'should be false when it is in between the start and end time', ->
-      start = @user.timetable.start
-      end = @user.timetable.end
+      [start, end] = @user.activeHours()
       time = moment(start).add(end.diff(start, 'hours') / 2, 'hours')
       expect(@user.isInactive(time)).to.be.false
   describe '#undoPunch()', ->


[skip] Stopped sorting collections by default (10m)

--- vendor/assets/javascripts/neat/collection_editor.coffee
+++ vendor/assets/javascripts/neat/collection_editor.coffee
@@ -15,7 +15,6 @@
 #   viewPath - by default this is set to resource
 #
 class window.Neat.CollectionEditor extends Backbone.View
-  sortedBy: 'name'
   sortOrder: 'asc'
   sortAliases: {}
   templateOptions: {}
@@ -93,11 +92,14 @@

   rerenderPage: (page)->
     page = @paginator.getCurrentPage() unless _.isNumber(page)
-    sortField = @sortField(@sortedBy)
-    items = @collection.sortBy (model)->
-      val = model.get(sortField) || ''
-      if _.isString(val) then val.toLowerCase() else val
-    items.reverse() if @sortOrder == 'desc'
+    if @sortedBy
+      sortField = @sortField(@sortedBy)
+      items = @collection.sortBy (model)->
+        val = model.get(sortField) || ''
+        if _.isString(val) then val.toLowerCase() else val
+      items.reverse() if @sortOrder == 'desc'
+    else
+      items = @collection.toArray()
     @paginator.init items, page


fix variable mismatch in error handler

fix variable mismatch in error handler

--- services/spelling/app/coffee/SpellingAPIController.coffee
+++ services/spelling/app/coffee/SpellingAPIController.coffee
@@ -7,8 +7,8 @@
 		metrics.inc "spelling-check", 0.1
 		logger.log token: req?.params?.user_id, word_count: req?.body?.words?.length, "running check"
 		SpellingAPIManager.runRequest req.params.user_id, req.body, (error, result) ->
-			if err?
-				logger.err err:err, user_id:req?.params?.user_id, word_count: req?.body?.words?.length, "error processing spelling request"
+			if error?
+				logger.err err:error, user_id:req?.params?.user_id, word_count: req?.body?.words?.length, "error processing spelling request"
 				return res.send(500)
 			res.send(result)


add autoprefixer map functionality

add autoprefixer map functionality

general housekeeping too

fix: livereload images path

--- app/templates/Gruntfile.coffee
+++ app/templates/Gruntfile.coffee
@@ -22,7 +22,7 @@
         files: [ 'Gruntfile.coffee' ]<% if ( stylesLang === 'sass') { %>

       styles:
-        files: '<%%= config.app %>/styles/{,*/}*.scss'
+        files: '<%%= config.app %>/styles/{,*/}*.{scss,sass}'
         tasks: [
           'sass'
           'autoprefixer'
@@ -46,7 +46,7 @@
         files: [ '<%%= config.app %>/scripts/{,*/}*.coffee' ]
         tasks: [
           'coffeelint'
-          'coffee:jitter'
+          'coffee'
           'replace:scripts'
         ]<% } %><% if ( scriptsLang === 'javascript') { %>

@@ -70,7 +70,7 @@
         files: [
           '<%%= config.temp %>/styles/{,*/}*.css'
           '<%%= config.temp %>/scripts/{,*/}*.js'
-          '<%%= config.app %>/img/{,*/}*'
+          '<%%= config.app %>/images/{,*/}*'
         ]

     clean:
@@ -127,6 +127,7 @@
     autoprefixer:
       options:
         browsers: [ 'last 2 version' ]
+        map: true
       post:
         files: [
           expand: true


return true threw warning

return true threw warning

--- pub/work/src/js/components/ItemComponent.coffee
+++ pub/work/src/js/components/ItemComponent.coffee
@@ -37,9 +37,7 @@
       e.preventDefault()
       return

-  onFocus: (e) ->
-    @props._focus e,@
-    return true
+  onFocus: (e) -> @props._focus e,@

   _markDone: (e) ->
     WorkActions.setItem @props.item,'done',(not (@props.item.done is true))