Add fancy animation to users online graph

--- resources/assets/coffee/_classes/online-chart.coffee
+++ resources/assets/coffee/_classes/online-chart.coffee
@@ -30,7 +30,12 @@
       .classed 'chart__wrapper', true

     @svgLine = @svgWrapper.append 'path'
-      .classed 'chart__line chart__line--thin chart__line--yellow', true
+      .attr 'fill', 'none'
+      .attr 'stroke', '#ffcc22'
+      .attr 'stroke-width', '1'
+      .attr 'opacity', 0
+      # .classed 'chart__line chart__line--thin chart__line--yellow', true
+

     @line = d3.line()
       .curve d3.curveMonotoneX
@@ -39,8 +44,9 @@
     _.forEach(JSON.parse($("#json-stats").text()), (e, i) -> data.push(new Object({'x': i, 'y': e.users_osu})))

     @svgEndCircle = @svgWrapper.append 'circle'
-      .classed 'chart__hover-mark chart__hover-mark--small chart__line--yellow', true
+      .classed 'chart__hover-mark chart__hover-mark--small chart__end-circle', true
       .attr 'r', 2
+      .attr 'opacity', 0

     @loadData data

@@ -55,7 +61,7 @@
     @data = data
     @svgLine.datum @data

-    @resize()
+    @reveal()

   setDimensions: =>
     areaDims = @area.node().getBoundingClientRect()
@@ -78,9 +84,7 @@
       .y (d) => @options.scales.y d.y

     @svgEndCircle
-      .transition()
-      .attr 'transform', "translate(#{@options.scales.x(@data[@data.length-1].x)}, #{@options.scales.y(@data[@data.length-1].y)})"
-
+      .attr 'transform', "translate(#{@options.scales.x(@data[@data.length-1].x)+2}, #{@options.scales.y(@data[@data.length-1].y)})"

   setSvgSize: =>
     @svg
@@ -94,10 +98,36 @@

   drawLine: =>
     @svgLine
-      .transition()
+      .attr 'stroke-dasharray', 0
       .attr 'd', @line

-  resize: =>
+  reveal: =>
+    @recalc()
+
+    @svgLine
+      .attr 'd', @line
+
+    totalLength = @svgLine.node().getTotalLength()
+
+    @svgLine
+      .attr 'stroke-dasharray', totalLength
+      .attr 'stroke-dashoffset', totalLength
+      .transition()
+        .delay 500
+        .duration 1000
+        .ease d3.easeSinOut
+        .attr 'stroke-dashoffset', 0
+        .attr 'opacity', 1
+
+    @svgEndCircle
+      .transition()
+        .delay 1400
+        .duration 300
+        .ease d3.easeSinOut
+        .attr 'opacity', 1
+
+
+  recalc: =>
     @setDimensions()

     @setScalesRange()
@@ -106,4 +136,6 @@
     @setWrapperSize()
     @setLineSize()

+  resize: =>
+    @recalc()
     @drawLine()


changing timout method to pass tests [#95231438]

--- review_front/app/components/copy-to-clipboard.coffee
+++ review_front/app/components/copy-to-clipboard.coffee
@@ -12,7 +12,7 @@
   actions:
     afterCopy: ->
       @justCopiedToggle()
-      Ember.run.later @, (->
+      Ember.setTimeout @, (->
         @justCopiedToggle()
       ), 1500


mainly issue 221; also noticed some instances of app instead of router

--- src/smc-hub/hub.coffee
+++ src/smc-hub/hub.coffee
@@ -407,6 +407,10 @@

 # Render a stripe invoice/receipt using pdfkit = http://pdfkit.org/
 stripe_render_invoice = (invoice_id, download, res) ->
+    if not stripe?
+        # stripe not available, configured or initaialized yet
+        res.status(404).send("stripe not available")
+        return
     invoice = undefined
     customer = undefined
     charge = undefined
@@ -438,7 +442,7 @@

     doc.pipe(res)

-    doc.image('static/favicon-128.png', 268, 15, {width: 64, align: 'center'})
+    doc.image(path_module.join(SALVUS_HOME, 'static/favicon-128.png'), 268, 15, {width: 64, align: 'center'})
     y = 100
     c1 = 100
     if invoice.paid
@@ -769,7 +773,7 @@

         passport.use(new PassportStrategy(verify))

-        app.get '/auth/local', (req, res) ->
+        router.get '/auth/local', (req, res) ->
             res.send("""<form action="/auth/local" method="post">
                             <label>Email</label>
                             <input type="text" name="username">
@@ -778,7 +782,7 @@
                             <button type="submit" value="Log In"/>Login</button>
                         </form>""")

-        app.post '/auth/local', passport.authenticate('local'), (req, res) ->
+        router.post '/auth/local', passport.authenticate('local'), (req, res) ->
             console.log("authenticated... ")
             res.json(req.user)

@@ -819,9 +823,9 @@
             # didn't work.  To figure out that this was the problem, I had to grep the source code of the passport-google-oauth
             # library and put in print statements to see what the *REAL* errors were, since that
             # library hid the errors (**WHY**!!?).
-            app.get "/auth/#{strategy}", passport.authenticate(strategy, {'scope': 'openid email profile'})
-
-            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+            router.get "/auth/#{strategy}", passport.authenticate(strategy, {'scope': 'openid email profile'})
+
+            router.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
                 profile = req.user.profile
                 passport_login
                     strategy   : strategy
@@ -858,9 +862,9 @@
                 done(undefined, {profile:profile})
             passport.use(new PassportStrategy(opts, verify))

-            app.get "/auth/#{strategy}", passport.authenticate(strategy)
-
-            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+            router.get "/auth/#{strategy}", passport.authenticate(strategy)
+
+            router.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
                 profile = req.user.profile
                 passport_login
                     strategy   : strategy
@@ -900,9 +904,9 @@
                 done(undefined, {profile:profile})
             passport.use(new PassportStrategy(opts, verify))

-            app.get "/auth/#{strategy}", passport.authenticate(strategy)
-
-            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+            router.get "/auth/#{strategy}", passport.authenticate(strategy)
+
+            router.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
                 profile = req.user.profile
                 passport_login
                     strategy   : strategy
@@ -941,9 +945,9 @@
                 done(undefined, {profile:profile})
             passport.use(new PassportStrategy(opts, verify))

-            app.get "/auth/#{strategy}", passport.authenticate("dropbox-oauth2")
-
-            app.get "/auth/#{strategy}/return", passport.authenticate("dropbox-oauth2", {failureRedirect: '/auth/local'}), (req, res) ->
+            router.get "/auth/#{strategy}", passport.authenticate("dropbox-oauth2")
+
+            router.get "/auth/#{strategy}/return", passport.authenticate("dropbox-oauth2", {failureRedirect: '/auth/local'}), (req, res) ->
                 profile = req.user.profile
                 passport_login
                     strategy   : strategy
@@ -982,9 +986,9 @@
                 done(undefined, {profile:profile})
             passport.use(new PassportStrategy(opts, verify))

-            app.get "/auth/#{strategy}", passport.authenticate(strategy)
-
-            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+            router.get "/auth/#{strategy}", passport.authenticate(strategy)
+
+            router.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
                 profile = req.user.profile
                 #winston.debug("profile=#{misc.to_json(profile)}")
                 passport_login
@@ -1025,9 +1029,9 @@
                 done(undefined, {profile:profile})
             passport.use(new PassportStrategy(opts, verify))

-            app.get "/auth/#{strategy}", passport.authenticate(strategy)
-
-            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+            router.get "/auth/#{strategy}", passport.authenticate(strategy)
+
+            router.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
                 profile = req.user.profile
                 passport_login
                     strategy   : strategy
@@ -1065,9 +1069,9 @@
                 done(undefined, {profile:profile})
             passport.use(new PassportStrategy(opts, verify))

-            app.get "/auth/#{strategy}", passport.authenticate(strategy)
-
-            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+            router.get "/auth/#{strategy}", passport.authenticate(strategy)
+
+            router.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
                 profile = req.user.profile
                 passport_login
                     strategy   : strategy


no more ef nag

no more ef nag

--- scripts/name-expander.coffee
+++ scripts/name-expander.coffee
@@ -28,13 +28,6 @@
   cliff,
   ivey,
   justin
-]
-
-ef_devs = [
-  jesse,
-  josiah,
-  ivey,
-  chong
 ]

 merlin_lax_members = [
@@ -92,9 +85,6 @@
   robot.hear /^(merlin|dat)-remote( team| dudes| guys)*:(.+)/i, (msg) ->
     msg.send merlin_remote_members.join(", ") + ": ^^"

-  robot.hear /^(glastonbury|ef|environment factory|factory)( team| dudes| guys)*:(.+)/i, (msg) ->
-    msg.send ef_devs.join(", ") + ": ^^"
-
   robot.hear /^(mashion)( team| dudes| guys)*:(.+)/i, (msg) ->
     msg.send mashion_devs.join(", ") + ": ^^"


Fix logic for falling back to preceding line's ending

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -746,8 +746,8 @@
     # Determine how to normalize the line endings of inserted text if enabled
     if normalizeLineEndings
       startRow = oldRange.start.row
-      normalizedEnding = @preferredLineEnding ?
-        @lineEndingForRow(startRow) ?
+      normalizedEnding = @preferredLineEnding or
+        @lineEndingForRow(startRow) or
         @lineEndingForRow(startRow - 1)
       if normalizedEnding
         newText = newText.replace(newlineRegex, normalizedEnding)


Add logic that uses the editor.tabType setting

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -75,7 +75,7 @@
     'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows',
     toProperty: 'languageMode'

-  constructor: ({@softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) ->
+  constructor: ({softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) ->
     super

     @emitter = new Emitter
@@ -86,7 +86,7 @@
     buffer ?= new TextBuffer
     @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode})
     @buffer = @displayBuffer.buffer
-    @softTabs = @usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true
+    @softTabs = @shouldUseSoftTabs(defaultValue: softTabs)

     for marker in @findMarkers(@getSelectionMarkerAttributes())
       marker.setProperties(preserveFolds: true)
@@ -2395,6 +2395,20 @@
     return unless @getSoftTabs()
     @scanInBufferRange /\t/g, bufferRange, ({replace}) => replace(@getTabText())

+  # Private: Computes whether or not this editor should use softTabs based on
+  # the `editor.tabType` setting.
+  #
+  # Returns a {Boolean}
+  shouldUseSoftTabs: ({defaultValue}) ->
+    tabType = atom.config.get('editor.tabType', scope: @getRootScopeDescriptor())
+    switch tabType
+      when 'auto'
+        @usesSoftTabs() ? defaultValue ? atom.config.get('editor.softTabs') ? true
+      when 'hard'
+        false
+      when 'soft'
+        true
+
   ###
   Section: Soft Wrap Behavior
   ###
@@ -2875,7 +2889,7 @@
   ###

   handleTokenization: ->
-    @softTabs = @usesSoftTabs() ? @softTabs
+    @softTabs = @shouldUseSoftTabs(defaultValue: @softTabs)

   handleGrammarChange: ->
     @unfoldAll()


Improved: proper coffee-script syntax

Improved: proper coffee-script syntax

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

     options =
         name: 'data-system'
-        port: process.env.PORT || 9101
-        host: process.env.HOST || "127.0.0.1"
+        port: process.env.PORT or 9101
+        host: process.env.HOST or "127.0.0.1"
         root: __dirname

     americano.start options, (app, server) ->


Send JSON with the Content-Type of application/json; charset=utf-8.

--- server.coffee
+++ server.coffee
@@ -31,7 +31,7 @@
       req.response.end()

 routeMatcher.get '/routes', filterAjaxOnly validation.validateGetRoutes (req) ->
-  req.response.putHeader 'Content-Type', 'application/json; charset=utf8'
+  req.response.putHeader 'Content-Type', 'application/json; charset=utf-8'
   geocodeFrom = (cb) -> geocode req.params().from, (r) -> cb(null, r)
   geocodeTo =  (cb) -> geocode req.params().to, (r) -> cb(null, r)
   async.parallel {from: geocodeFrom, to: geocodeTo}, (error, {from,to}) ->
@@ -50,16 +50,16 @@
       req.response.end JSON.stringify(from: !!from, to: !!to)

 routeMatcher.get '/address', filterAjaxOnly validation.validateGetAddress (req) ->
-  req.response.putHeader 'Content-Type', 'application/json; charset=utf8'
+  req.response.putHeader 'Content-Type', 'application/json; charset=utf-8'
   hsl.reverseGeocode req.params().coords, (address) ->
     if address
       req.response.end JSON.stringify(address)
     else
       req.response.statusCode = 400
       req.response.end()
-
+
 routeMatcher.get '/autocomplete', filterAjaxOnly validation.validateAutocomplete (req) ->
-  req.response.putHeader 'Content-Type', 'application/json; charset=utf8'
+  req.response.putHeader 'Content-Type', 'application/json; charset=utf-8'
   eb.send 'reitti.searchIndex.find', query: req.params().query, (data) ->
     req.response.end JSON.stringify(itm.name for itm in data.results)

@@ -75,7 +75,7 @@
     blankSlateActive = !/blankSlateDismissed/.test(req.headers()['cookie'])
     blankSlateHidden = !blankSlateActive or (req.path isnt '' and req.path isnt '/')

-    req.response.putHeader 'Content-Type', 'text/html; charset=utf8'
+    req.response.putHeader 'Content-Type', 'text/html; charset=utf-8'
     req.response.end indexTemplate.apply
       timezoneOffset: tzOffset
       locale: locale


fix #196: per-series options

fix #196: per-series options

--- app/graph_drawing.coffee
+++ app/graph_drawing.coffee
@@ -88,7 +88,17 @@
   legendTextColor: '#333'
   legendText: (d) -> d.name
   deselectedOpacity: 0.5
-  #lineWidth: 1
+  lineWidth: 1
+  drawAsInfinite: false
+
+seriesParams = [
+  'lineWidth'
+  'get_value'
+  'get_timestamp'
+  'drawNullAsZero'
+  'drawAsInfinite'
+  'type'
+]

 fgColorParams = ['axisLineColor', 'axisTextColor', 'crosshairLineColor', 'crosshairTextColor', 'titleTextColor', 'legendTextColor']

@@ -221,8 +231,6 @@
     externalCursorChanges = cursorModel.addSource cursorBus
     externalBrushChanges = brushModel.apply brushBus

-    type = params.type
-
     margin = top: 20, right: 80, bottom: 10, left: 80
     if params.title
       margin.top += 30
@@ -233,9 +241,6 @@

     x.range([0, width])
     y.range([height, 0])
-
-    get_value = params.get_value
-    get_timestamp = params.get_timestamp

     color.range params.d3_colors
     # ensure that colors are stable even if targets change position in the list
@@ -256,14 +261,19 @@

     hoverSelections = mouseOver.map ({index}) -> {index, selection: d3.select(container).selectAll ".target#{index}"}
     hoverSelections.onValue ({selection, index}) ->
-      path = selection.select('path')
-      path.style('stroke-width', (params.lineWidth ? 0) + 3)
-
-      lines = selection.select('.infiniteLines').selectAll('line')
-      lines.style('stroke-width', (params.lineWidth ? 0) + 3)
-
-      circles = selection.select('.circles').selectAll('circle')
-      circles.attr('r', 4)
+      selection.each (d) ->
+        targetSelection = d3.select(this)
+
+        targetSelection.select('path')
+          .style('stroke-width', (d) -> d.lineWidth + 3)
+
+        targetSelection.select('.infiniteLines').each (d) ->
+          d3.select(this).selectAll('line')
+            .style('stroke-width', d.lineWidth + 3)
+
+        targetSelection.select('.circles').selectAll('circle')
+          .attr('r', 4)
+
       highlightLegend(index)

     unhovers = hoverSelections.merge(mouseOut)
@@ -272,12 +282,19 @@

     unhovers.onValue ({selection, index}) ->
       highlightLegend(null)
-      path = selection.select('path')
-      path.style('stroke-width', params.lineWidth)
-      lines = selection.select('.infiniteLines').selectAll('line')
-      lines.style('stroke-width', params.lineWidth)
-      circles = selection.select('.circles').selectAll('circle')
-      circles.attr('r', 2)
+
+      selection.each (d) ->
+        targetSelection = d3.select(this)
+
+        targetSelection.select('path')
+          .style('stroke-width', (d) -> d.lineWidth)
+
+        targetSelection.select('.infiniteLines').each (d) ->
+          d3.select(this).selectAll('line')
+            .style('stroke-width', d.lineWidth)
+
+        targetSelection.select('.circles').selectAll('circle')
+          .attr('r', 2)

     mousePosition = mouseMoves.map (pos) ->
@@ -287,61 +304,43 @@
       x: xConstrained
       y: pos[1]

-    timeMin = null
-    timeMax = null
-    _.each data, ({datapoints}, j) ->
-      if datapoints.length > 0
-        _.each [0, datapoints.length - 1], (i) ->
-          datapoint = datapoints[i]
-          value = get_value datapoint, i, j
-          timestamp = get_timestamp datapoint, i, j
-          timeMin = Math.min timestamp, timeMin ? timestamp
-          timeMax = Math.max timestamp, timeMax
-
-    timeMin = new Date(timeMin * 1000)
-    timeMax = new Date(timeMax * 1000)
-    x.domain [params.xMin ? timeMin, params.xMax ? timeMax]
-
     stack.offset(params.areaOffset)

-    if type is 'line'
-      areaOpacity = params.areaAlpha
-      lineOpacity = 1.0
-
-      if params.interpolate?
-        line.interpolate params.interpolate
-        area.interpolate params.interpolate
+    areaOpacity = params.areaAlpha
+    lineOpacity = 1.0
+
+    if params.interpolate?
+      line.interpolate params.interpolate
+      area.interpolate params.interpolate

     if params.simplify
       simplify = _.partial simplifyPoints, params.simplify
     else
       simplify = _.identity

-    if params.drawNullAsZero
-      transformValue = (v) -> v ? 0
-      filterScatterValues = simplify
-      expandLineValues = simplify
-    else
-      # TODO this won't work well with stack
-      transformValue = (v) -> v
-      filterScatterValues = (values) ->
-        simplify _.filter values, (d) -> d.value?
-      expandLineValues = _.compose expandIsolatedValuesToLineSegments, simplify
-
     valueMin = null
     valueMax = null
+
+    timeMin = null
+    timeMax = null
+
     targets = for s, targetIndex in data
-      options = s.options ? {}
-      drawAsInfinite = options.drawAsInfinite ? false
+      options = _.extend {}, params, s.options
+      {drawAsInfinite, get_value, get_timestamp, drawNullAsZero} = options
+      if params.drawNullAsZero
+        transformValue = (v) -> v ? 0
+      else
+        transformValue = (v) -> v
       values = for datapoint, i in s.datapoints
-        value = get_value datapoint, i, targetIndex
-        timestamp = get_timestamp datapoint, i, targetIndex
+        value = transformValue(get_value(datapoint, i, targetIndex))
+        timestamp = get_timestamp(datapoint, i, targetIndex)
+        timeMin = Math.min(timestamp, timeMin ? timestamp)
+        timeMax = Math.max(timestamp, timeMax)
         time = new Date(timestamp * 1000)
-        value = transformValue value
         unless drawAsInfinite
           valueMin = Math.min value, valueMin ? value if value?
           valueMax = Math.max value, valueMax
-        {value, time: time, x: x(time), original: datapoint}
+        {value, time: time, original: datapoint}
       bisector = d3.bisector (d) -> d.time
       lineMode =
         if params.areaMode is 'all' or params.areaMode is 'stacked'
@@ -355,8 +354,13 @@
       else
         area
       name = s.target
-      targetColor = colorsByName[name]
-      {values, bisector, name, lineMode, lineFn, color: targetColor, drawAsInfinite}
+      targetColor = options.color ? colorsByName[name]
+      _.extend options, {values, bisector, name, lineMode, lineFn, color: targetColor}
+
+
+    timeMin = new Date(timeMin * 1000)
+    timeMax = new Date(timeMax * 1000)
+    x.domain [params.xMin ? timeMin, params.xMax ? timeMax]

     if params.areaMode is 'stacked'
       stack targets
@@ -374,10 +378,20 @@
     y.domain [params.yMin ? valueMin, params.yMax ? valueMax]

     _.each targets, (target) ->
-      {values} = target
+      {values, drawNullAsZero} = target
       _.each values, (v) ->
         if v.value?
           v.y = y v.value
+        v.x = x(v.time)
+
+      if drawNullAsZero
+        filterScatterValues = simplify
+        expandLineValues = simplify
+      else
+        # TODO this won't work well with stack
+        filterScatterValues = (values) ->
+          simplify _.filter values, (d) -> d.value?
+        expandLineValues = _.compose expandIsolatedValuesToLineSegments, simplify
       target.lineValues = expandLineValues values
       target.scatterValues = filterScatterValues values

@@ -492,14 +506,15 @@

     addPath = (target, hover) ->
       if hover
-        lineWidth = (params.lineWidth ? 0) + 10
-      else
-        lineWidth = params.lineWidth
+        extraWidth = 10
+        c = ''
+      else
+        extraWidth = 0

       path = target.append("path")
         .attr('class', (d) -> d.lineMode)
         .attr('stroke', (d, i) -> d.color)
-        .style('stroke-width', lineWidth)
+        .style('stroke-width', (d) -> d.lineWidth + extraWidth)
         .attr('fill', (d, i) -> if d.lineMode is 'area' then d.color)
         # TODO don't call lineFn for both visible and hover paths
         .attr('d', (d, i) -> d.lineFn(d.lineValues))
@@ -520,6 +535,7 @@
       else
         radius = 2
         opacity = 1
+
       target.append('g').attr('class', 'circles')
         .each (d) ->
           circleColor = d.color
@@ -534,12 +550,14 @@

     addInfiniteLines = (target, hover) ->
       if hover
-        lineWidth = (params.lineWidth ? 0) + 10
-      else
-        lineWidth = params.lineWidth
+        extraWidth = 10
+      else
+        extraWidth = 0
+
       target.append('g').attr('class', 'infiniteLines')
       .each (d) ->
         lineColor = d.color
+        lineWidth = d.lineWidth + extraWidth
         d3.select(this)
           .selectAll('line')
           .data((d) -> d.scatterValues)
@@ -562,7 +580,8 @@
     addTarget = (target, hover) ->
       target.each ->
         sel = d3.select(@)
-        if sel.datum().drawAsInfinite
+        {drawAsInfinite, type} = sel.datum()
+        if drawAsInfinite
           addInfiniteLines(sel, hover)
         else if type is 'line'
           addPath(sel, hover)


Fix issue with banner alignment in TileView due to the larger grid. 9x9 instead of 8x9

--- src/ui/tileView.coffee
+++ src/ui/tileView.coffee
@@ -124,9 +124,9 @@
   drawBanner : =>
     if (@banner)
       x = -Screen.HALF_UNIT
-      y = 7 * SceneView.UNIT
+      y = 8 * SceneView.UNIT
       @gurk.screen.drawIcon(Data.icons.bannerLeft, x, y)
-      for i in [0...7]
+      for i in [0...8]
         x += SceneView.UNIT
         @gurk.screen.drawIcon(Data.icons.banner, x, y)
       x += SceneView.UNIT


バスプールを表示の際に3回投稿するとかいう頭の悪いことになっていたのを修正

バスプールを表示の際に3回投稿するとかいう頭の悪いことになっていたのを修正

--- scripts/watch-buspool.coffee
+++ scripts/watch-buspool.coffee
@@ -5,13 +5,23 @@

 module.exports = (robot) ->
   robot.respond /(bus|バス)/i, (msg) ->
-    for i in [1..3]
-      url = "https://post.kyoto-su.ac.jp/upload/webcam/buspool-bc#{i}.jpg"
-      text = if i == 3 then "<https://post.kyoto-su.ac.jp/webcam/|学校ホームページURL>" else ""
-      data =
-        attachments: [
-          text : text
-          author_name: "バスプール"
-          image_url: url
-        ]
-      msg.send data+    url = ("https://post.kyoto-su.ac.jp/upload/webcam/buspool-bc#{i}.jpg" for i in [1..3])
+    data =
+      attachments: [
+        {
+          text : "<https://post.kyoto-su.ac.jp/webcam/|学校ホームページURL>"
+        },
+        {
+          author_name: "バスプール1"
+          image_url: url[0]
+        },
+        {
+          author_name: "バスプール2"
+          image_url: url[1]
+        },
+        {
+          author_name: "バスプール3"
+          image_url: url[2]
+        }
+      ]
+    msg.send data


Fix random server crash on refresh

Fix random server crash on refresh

--- server/models/mailbox.coffee
+++ server/models/mailbox.coffee
@@ -782,8 +782,9 @@

                     # next step
                     @imap_refreshStep step, (err, infoNext) ->
+                        return callback err if err
                         info.shouldNotif = shouldNotif or infoNext.shouldNotif
-                        callback err, info
+                        callback null, info

     # Public: get a message UID from its message id in IMAP


update catchup scripts

update catchup scripts

--- server/scripts/2noddy.coffee
+++ server/scripts/2noddy.coffee
@@ -55,10 +55,10 @@
       for u in newusers.data.hits.hits
         rec = u._source
         API.es.call 'POST', '/noddy/users/' + rec._id, rec, undefined, undefined, undefined, undefined, undefined, false
-      newoab = API.es.call 'GET', '/oab/request?q=createdAt:>1520248232218&size=10000', undefined, undefined, undefined, undefined, undefined, undefined, false
-      for u in newoab.data.hits.hits
-        rec = _oab_requests_sherpa u._source
-        API.es.call 'POST', '/oab/request/' + rec._id, rec, undefined, undefined, undefined, undefined, undefined, false
+      #newoab = API.es.call 'GET', '/oab/request?q=createdAt:>1520248232218&size=10000', undefined, undefined, undefined, undefined, undefined, undefined, false
+      #for u in newoab.data.hits.hits
+      #  rec = _oab_requests_sherpa u._source
+      #  API.es.call 'POST', '/oab/request/' + rec._id, rec, undefined, undefined, undefined, undefined, undefined, false
       return {users: newusers.data.hits.total, requests: newoab.data.hits.total}

 API.add 'scripts/2noddy/accounts',
@@ -92,6 +92,7 @@

       # oab_support is the same format so does not need reprocessing, but will need reindexing anyway to match new mapping
       #API.es.reindex 'oab', 'support'
+      API.es.reindex 'oab', 'availability'

       API.log {msg:'Scripting oab from oab into oab, but udpated and mapped for noddy, complete', level:'info', notify:true}
       return true


Optimize performance by reducing amount of DB requests

--- server/server_timeout.coffee
+++ server/server_timeout.coffee
@@ -15,10 +15,10 @@
     Meteor.setInterval(() ->
         timeout = Date.now()
         timeout -= Meteor.settings.inactivityTimeout || 300000
-        users = _.pluck(Sessions.find({heartbeat: {$lt: timeout}}).fetch(), 'user_id')
-        for id in users
-            Meteor.users.update(id ,{$set: {'services.resume.loginTokens': []}})
-            Sessions.remove({user_id: id})
+        user_ids = _.pluck(Sessions.find({heartbeat: {$lt: timeout}}).fetch(), 'user_id')
+        if user_ids.length
+            Meteor.users.update({_id: {$in: user_ids}} ,{$set: {'services.resume.loginTokens': []}})
+            Sessions.remove({user_id: {$in: user_ids}})

     , interval)


Add reportedAt to clienterror.

Add reportedAt to clienterror.

--- app/server/js/api.coffee
+++ app/server/js/api.coffee
@@ -47,6 +47,7 @@
             error: req.query['error']
             # Does not work for some reason, but line is specified in trace.
             # line: req.body['line']
+            reportedAt: new Date
             script: req.query['script']
             trace: req.body['trace']
             userAgent: req.headers['user-agent']


codemirror editors: only make patch when user actually does something; simplify code by deleting something suspicious; switch from debounce to throttle... based on a lot of user feedback.

--- src/smc-webapp/syncdoc.coffee
+++ src/smc-webapp/syncdoc.coffee
@@ -187,6 +187,7 @@
         @element     = @editor.element

         # window.w = @
+
         # replace undo/redo by sync-aware versions
         for cm in [@codemirror, @codemirror1]
             cm.undo = @undo
@@ -277,15 +278,15 @@
                     redux.getProjectActions(@editor.project_id).close_tab(@filename)

                 save_state = () => @_sync()
-                # We debounce instead of throttle, because we want a single "diff/commit" to correspond
-                # a burst of activity, not a bunch of little pieces of that burst.  This is more
-                # consistent with how undo stacks work.
-                @save_state_debounce = underscore.debounce(save_state, @opts.sync_interval)
+                @save_state_throttle = underscore.throttle(save_state, @opts.sync_interval, {leading:false})

                 @codemirror.on 'change', (instance, changeObj) =>
                     if @_closed
                         return
-                    #console.log("change event when live='#{@live().string()}'")
+                    if not @_setting_from_syncstring
+                        # console.log 'user_action = true'
+                        @_user_action = true
+                    # console.log("change event - origin=", changeObj.origin)
                     if changeObj.origin?
                         if changeObj.origin == 'undo'
                             @on_undo?(instance, changeObj)
@@ -293,33 +294,29 @@
                             @on_redo?(instance, changeObj)
                         if changeObj.origin != 'setValue'
                             @_last_change_time = new Date()
-                            @save_state_debounce?()
+                            @save_state_throttle?()
                             @_syncstring.exit_undo_mode()
                     update_unsaved_uncommitted_changes()

                 @emit('connect')   # successful connection
                 cb?()  # done initializing document (this is used, e.g., in the SynchronizedWorksheet derived class).

+    # Set value of the syncstring to equal current value of the codemirror editor
     _set_syncstring_to_codemirror: =>
+        if not @_user_action
+            # console.log 'not setting due to no user action'
+            # user has not explicitly done anything, so there should be no changes.
+            return
+        # console.log 'user action so setting'
+        @_user_action = false
         @_last_val = val = @codemirror.getValue()
         @_syncstring.from_str(val)

+    # Set value of the codemirror editor to equal current value of the syncstring
     _set_codemirror_to_syncstring: =>
-        val     = @_syncstring.to_str()
-        cur_val = @codemirror.getValue()
-
-        if @_last_val? and cur_val != @_last_val
-            # We *MERGE* the changes in since, we made changes between when we last set
-            # the syncstring and now.  This can perhaps sometimes happen in rare cases
-            # due to async save debouncing.  (Honestly, I don't know how this case could
-            # possibly happen.)
-            patch = syncstring.dmp.patch_make(@_last_val, cur_val)
-            val = syncstring.dmp.patch_apply(patch, val)[0]
-        else
-            patch = undefined
-        @codemirror.setValueNoJump(val)
-        if patch?
-            @_set_syncstring_to_codemirror()
+        @_setting_from_syncstring = true
+        @codemirror.setValueNoJump(@_syncstring.to_str())
+        @_setting_from_syncstring = false

     has_unsaved_changes: =>
         if not @codemirror?
@@ -363,7 +360,7 @@
             @_set_syncstring_to_codemirror()
         value = @_syncstring.undo().to_str()
         cm.setValueNoJump(value, true)
-        @save_state_debounce?()
+        @save_state_throttle?()
         @_last_change_time = new Date()

     # per-session sync-aware redo
@@ -381,7 +378,7 @@
             throw Error("doc must have a to_str method, but is doc='#{doc}', typeof(doc)='#{typeof(doc)}'")
         value = doc.to_str()
         @focused_codemirror().setValueNoJump(value, true)
-        @save_state_debounce?()
+        @save_state_throttle?()
         @_last_change_time = new Date()

     _connect: (cb) =>


fixing If/else-to-ternary with instanceof as an unparenthesized condition. JS operator precedence.

--- nodes.coffee
+++ nodes.coffee
@@ -1330,9 +1330,12 @@
   constructor: (condition, body, tags) ->
     @condition: condition
     @body:      body
+    @tags:      tags or {}
+    if @tags.invert
+      @condition: new OpNode('!', new ParentheticalNode(@condition))
+    else if @condition instanceof OpNode and @condition.operator is 'instanceof'
+      @condition: new ParentheticalNode(@condition)
     @elseBody:  null
-    @tags:      tags or {}
-    @condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert
     @isChain:   false

   bodyNode: -> @body?.unwrap()


fixing inline-loop object-in-array tests for instance variables. Issue #481

--- nodes.coffee
+++ nodes.coffee
@@ -136,9 +136,9 @@
   class:     'BaseNode'
   children: []

-  unwrap:            -> this
+  unwrap:           -> this
   isStatement:      -> no
-  isPureStatement: -> no
+  isPureStatement:  -> no
   topSensitive:     -> no

 #### Expressions
@@ -1079,7 +1079,7 @@
     [@arr1, @arr2]: @array.compileReference o, {precompile: yes}
     [i, l]: [o.scope.freeVariable(), o.scope.freeVariable()]
     prefix: if @obj1 isnt @obj2 then @obj1 + '; ' else ''
-    "!!(function(){ ${prefix}for (var $i=0, $l=${@arr1}.length; $i<$l; $i++) if (${@arr2}[$i] === $@obj2) return true; })()"
+    "!!(function(){ ${prefix}for (var $i=0, $l=${@arr1}.length; $i<$l; $i++) if (${@arr2}[$i] === $@obj2) return true; }).call(this)"

 #### TryNode


merging #1353

merging #1353

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

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

   # Compile a unary **Op**.
   compileUnary: (o) ->
@@ -1676,6 +1673,11 @@
   # force inner *else* bodies into statement form.
   compileStatement: (o) ->
     child    = del o, 'chainChild'
+    exeq     = del o, 'isExistentialEquals'
+
+    if exeq
+      return new If(@condition.invert(), @elseBodyNode(), type: 'if').compile o
+
     cond     = @condition.compile o, LEVEL_PAREN
     o.indent += TAB
     body     = @ensureBlock(@body).compile o


Wraps up #2211 -- addresses invocations within destructured params

--- nodes.coffee
+++ nodes.coffee
@@ -1282,18 +1282,21 @@
     for obj in name.objects
       # * assignments within destructured parameters `{foo:bar}`
       if obj instanceof Assign
-        names.push obj.value.base.value
+        names.push obj.value.unwrap().value
       # * splats within destructured parameters `[xs...]`
       else if obj instanceof Splat
         names.push obj.name.unwrap().value
-      # * destructured parameters within destructured parameters `[{a}]`
-      else if obj.isArray() or obj.isObject()
-        names.push @names(obj.base)...
-      # * at-params within destructured parameters `{@foo}`
-      else if obj.this
-        names.push atParam(obj)...
-      # * simple destructured parameters {foo}
-      else names.push obj.base.value
+      else if obj instanceof Value
+        # * destructured parameters within destructured parameters `[{a}]`
+        if obj.isArray() or obj.isObject()
+          names.push @names(obj.base)...
+        # * at-params within destructured parameters `{@foo}`
+        else if obj.this
+          names.push atParam(obj)...
+        # * simple destructured parameters {foo}
+        else names.push obj.base.value
+      else
+        throw SyntaxError "illegal parameter #{obj.compile()}"
     names

 #### Splat


Oauth callback URL updates

Oauth callback URL updates

--- nylas.coffee
+++ nylas.coffee
@@ -51,9 +51,9 @@
     throw new Error("urlForAuthentication() requires options.redirectURI") unless options.redirectURI?
     options.loginHint ?= ''
     options.trial ?= false
+    url = "#{@apiServer}/oauth/authorize?client_id=#{@appId}&trial=#{options.trial}&response_type=code&scope=email&login_hint=#{options.loginHint}&redirect_uri=#{options.redirectURI}"
     if options.state?
-      "#{@apiServer}/oauth/authorize?client_id=#{@appId}&trial=#{options.trial}&response_type=code&scope=email&login_hint=#{options.loginHint}&redirect_uri=#{options.redirectURI}&state=#{options.state}"
-    else
-      "#{@apiServer}/oauth/authorize?client_id=#{@appId}&trial=#{options.trial}&response_type=code&scope=email&login_hint=#{options.loginHint}&redirect_uri=#{options.redirectURI}"
-
+      url += "&state=#{options.state}"
+
+
 module.exports = Nylas


remove unused section from template

remove unused section from template

--- fav.coffee
+++ fav.coffee
@@ -93,7 +93,7 @@
       body ->
         header class: "global", ->
           a href: "/", ->
-            img class: "username", src: "/logo.png", -> ""
+            img class: "username", src: "/logo.png"
           if @session?.user?
             a class:"signout", href: "/signout", -> "Sign out"
           else


scroll when selecting previous comment

scroll when selecting previous comment

--- app/js/views/subject.coffee
+++ app/js/views/subject.coffee
@@ -51,5 +51,4 @@
     comment?.select scroll: true

   selectPrevious: ->
-    if comment = @model.comments.prev()
-      @model.comments.select comment
+    comment.select scroll: true if comment = @model.comments.prev()


Add test for handling of bad sql statement.

--- test/integration/connection-test.coffee
+++ test/integration/connection-test.coffee
@@ -89,6 +89,33 @@
     #console.log(message)
   )

+exports.execBadSql = (test) ->
+  test.expect(6)
+
+  connection = new Connection(config.server, config.userName, config.password, config.options, (err, loggedIn) ->
+    test.ok(!err)
+    test.ok(loggedIn)
+
+    connection.execSql("select bad syntax here", (err, rowCount) ->
+      test.ok(!err)
+      test.strictEqual(rowCount, undefined)
+      test.done()
+    )
+  )
+
+  connection.on('errorMessage', (error) ->
+    test.ok(error.message.indexOf('syntax'))
+    test.strictEqual(error.number, 102)
+  )
+
+  connection.on('row', (columns) ->
+    test.ok(false)
+  )
+
+  connection.on('debug', (message) ->
+    #console.log(message)
+  )
+
 exports.execSqlProc = (test) ->
   test.expect(6)


unbind event

unbind event

--- test/javascripts/turbolinks_test.coffee
+++ test/javascripts/turbolinks_test.coffee
@@ -183,5 +183,6 @@
         @server.respond()

         $(document).off 'page:before-partial-replace'
+        $(document).off 'page:load'

         $("#div1").remove()


payment: add a relation for payment methods

all payment methods will be hooked to the account from now on.  hooking some to the group made the code complicated, and also didn't make sense, since credit cards are always issued to an individual, even if they are business cards

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -94,6 +94,7 @@
         'unlinkOauth', 'changeUsername', 'fetchOldKodingDownloadLink',
         'markUserAsExempt', 'checkFlag', 'userIsExempt', 'checkGroupMembership',
         'getOdeskAuthorizeUrl', 'fetchStorage', 'fetchStorages', 'store', 'unstore'
+        'fetchPaymentMethods'
       ]
     schema                  :
       skillTags             : [String]
@@ -223,6 +224,10 @@
         as          : 'owner'
         targetType  : 'JInvitationRequest'

+      paymentMethod :
+        as          : 'payment method'
+        targetType  : 'JPaymentPaymentMethod'
+
   constructor:->
     super
     @notifyOriginWhen 'PrivateMessageSent', 'FollowHappened'
@@ -1055,7 +1060,7 @@
         """

       options['resultsKey'] = 'followers'
-      Graph          = require "./graph/graph"
+      Graph = require "./graph/graph"
       graph = new Graph(config:KONFIG['neo4j'])
       graph.fetchFromNeo4j(query, options, callback)

@@ -1199,3 +1204,19 @@
   # we are using this in sorting members list..
   updateMetaModifiedAt: (callback)->
     @update $set: 'meta.modifiedAt': new Date, callback
+
+  fetchPaymentMethods$: secure (client, callback) ->
+    {delegate} = client.connection
+    if delegate is this or delegate.can 'administer accounts'
+      @fetchPaymentMethods (err, paymentMethods) ->
+        return callback err  if err
+
+        paymentData = []
+
+        queue = paymentMethods.map (paymentMethod, i) -> ->
+          paymentMethod.fetchAssociatedPaymentData (err, associatedData) ->
+            return callback err  if err
+            paymentData[i] = associatedData
+            queue.fin()
+
+        dash queue, -> callback null, paymentData


SocialWorker: JAccount: compare account instance with client.connection.delegate

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -1413,6 +1413,10 @@

   fetchMySessions: secure (client, options, callback) ->

+    # check if requester is the owner of the account
+    unless @equals client.connection.delegate
+      return callback new KodingError 'Access denied.'
+
     { sessionToken } = client
     return callback new KodingError 'Invalid session.'  unless sessionToken

@@ -1426,10 +1430,6 @@
       return callback err  if err
       return callback new KodingError 'Invalid session.'  unless session

-      # check if requester is the owner of the account
-      unless @profile.nickname is session.username
-        return callback new KodingError 'Access denied.'
-
       selector = { username : session.username }
       options  = { limit, skip, sort }
       JSession.some selector, options, callback


JSession: update foreignAuth schema to add GitLab

--- workers/social/lib/social/models/session.coffee
+++ workers/social/lib/social/models/session.coffee
@@ -31,6 +31,7 @@
         type            : Date
         default         : -> new Date
       foreignAuth       :
+        gitlab          : Object
         github          : Object
         facebook        : Object
         linkedin        : Object


Making sure transaction.request.body is used only if really present.

--- src/transaction-runner.coffee
+++ src/transaction-runner.coffee
@@ -514,7 +514,7 @@
         @configuration.emitter.emit 'test error', error, test, () ->
         return callback()

-      req.write transaction.request['body'] if transaction.request['body'] != ''
+      req.write(transaction.request.body) if transaction.request.body
       req.end()
     catch error
       test.title = transaction.id


Add capture for comment definition punctuation.

--- grammars/powershell.cson
+++ grammars/powershell.cson
@@ -18,7 +18,7 @@
     'captures':
       '1':
         'name': 'punctuation.definition.comment.powershell'
-    'match': '\\s*#.*$'
+    'match': '\\s*(#).*$'
     'name': 'comment.line.number-sign.powershell'
   }
   {


Show all candidates on empty prefix

Show all candidates on empty prefix

Fixes atom-haskell/language-haskell-scoped#2

--- lib/completion-backend/completion-backend.coffee
+++ lib/completion-backend/completion-backend.coffee
@@ -110,6 +110,8 @@
         Promise.resolve {bufferInfo, rootDir, moduleMap, moduleInfo}

   filter: (candidates, prefix, keys) ->
+    unless prefix
+      return candidates
     candidates
     .map (c) ->
       c1 = _.clone(c)


Using elapsed time in delayed events

Using elapsed time in delayed events

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -19,8 +19,11 @@
         self.sendOrApply(fn)
         event.lastFired += period

-    for event in I.delayEvents
-
+    [I.delayedEvents, firingEvents] = delayedEvents.partition (event) ->
+      (event.delay -= elapsedTime) >= 0
+
+    firingEvents.each (event) ->
+      event.callback()

   ###*
   Execute fn every n frames.


levels and default game states

levels and default game states

--- src/title_screen.coffee
+++ src/title_screen.coffee
@@ -7,6 +7,6 @@
   self.bind "overlay", (canvas) ->
     self.centerText canvas, App.name

-    self.centerText canvas
+    self.centerText canvas, ""

   return self


levels and default game states

levels and default game states

--- src/title_screen.coffee
+++ src/title_screen.coffee
@@ -7,6 +7,6 @@
   self.bind "overlay", (canvas) ->
     self.centerText canvas, App.name

-    self.centerText canvas, ""
+    self.centerText canvas, "Press "

   return self


:fire: unused

:fire: unused

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -28,7 +28,7 @@

     @narrowEditorElement = @narrowEditor.element
     @narrowEditorElement.classList.add('narrow')
-    @narrowEditorElement.classList.add(params.class) if params.class
+    # @narrowEditorElement.classList.add(params.class) if params.class

     @narrowEditor.getTitle = => @provider?.getTitle()
     @narrowEditor.isModified = -> false


Operators: Arithmetic operator precedence tested

--- src/test/parser/TextParserSpec.coffee
+++ src/test/parser/TextParserSpec.coffee
@@ -12,6 +12,7 @@
   aString = new Literal('"a string"', 'a string')
   aNumber = new Literal('10.5', 10.5)
   aNumber22 = new Literal('22', 22)
+  aFunctionCall = new FunctionCall('a', 'a', [])

   describe 'expressions', ->

@@ -77,6 +78,23 @@
         expressionFor('10.5>= 22 ').should.eql new InfixExpression('10.5>= 22', '>=', [aNumber, aNumber22])

+    describe 'precedence', ->
+
+      it 'multiplication higher than addition', ->
+        expressionFor('10.5 + a * 22').should.eql new InfixExpression('10.5 + a * 22', '+', [
+                                                   aNumber,
+                                                   new InfixExpression('a * 22', '*', [aFunctionCall, aNumber22])
+        ])
+
+      it 'multiplication and division both higher than addition and subtraction', ->
+        expressionFor('22 / 10.5 + a * 22 - 10.5').should.eql new InfixExpression('22 / 10.5 + a * 22 - 10.5', '+', [
+                                                   new InfixExpression('22 / 10.5', '/', [aNumber22, aNumber]),
+                                                   new InfixExpression('a * 22 - 10.5', '-', [
+                                                     new InfixExpression('a * 22', '*', [aFunctionCall, aNumber22]),
+                                                     aNumber
+                                                   ])
+        ])
+
   describe 'function definition', ->

     it 'defining a constant', ->


making method works with sync and async (interactive mode) calls

--- src/toaster/generators/project.coffee
+++ src/toaster/generators/project.coffee
@@ -1,47 +1,54 @@
 #<< Config

-class Project extends Question
+exports.Project = class Project extends Question

 	constructor:(@basepath)->

-	create:()->
-		if !(arg_path = argv._[0])
-			console.log "You need to inform a target path!".red
+	create:(folderpath, name, src, release)->
+		if !folderpath
+			console.log "#{'Error'.bold.red} You need to inform" +
+						"a target path!".red
 			return console.log "\ttoaster new myawesomeapp".green

-		if arg_path.substr 0, 1 != "/"
-			target = "#{@basepath}/#{arg_path}"
+		if folderpath.substr 0, 1 != "/"
+			target = "#{@basepath}/#{folderpath}"
 		else
-			target = arg_path
+			target = folderpath
+
+		return @scaffold target, name, src, release if name? && src? && release?

 		console.log ". #{'Wonderful!'.rainbow}",
 			"#{'Let\'s toast something fresh! :)'.grey.bold}"
 		console.log ". With this as your basepath: #{target.cyan}"
 		console.log ". Please, tell me:"
-
 		question1 = "\tWhat's your app name? (none)"
 		question2 = "\tWhere's its src folder? (src)"
 		question3 = "\tWhere do you want your release file? (release/app.js)"

-		@ask question1, /.+/, (name)=>
-			@ask question2, /.*/, (src)=>
-				@ask question3, /.*/, (release)=>
-
-					srcdir = "#{target}/" + ( src || "src" )
-					releasefile = "#{target}/" + ( release || "release/app.js" )
-					releasedir = releasefile.split("/").slice(0, -1).join "/"
-
-					fs.mkdirSync target, 0755
-					console.log "#{'Created'.green.bold} #{target}"
-					fs.mkdirSync srcdir, 0755
-					console.log "#{'Created'.green.bold} #{srcdir}"
-					fs.mkdirSync releasedir, 0755
-					console.log "#{'Created'.green.bold} #{releasedir}"
-
-					name = name
-					srcdir = srcdir.replace( target, "" ).substr 1
-					releasefile = releasefile.replace( target, "" ).substr 1
-
-					new Config( target ).write name, srcdir, releasefile
-
-					process.exit()+		@ask question1, /.+/, name, (name)=>
+			@ask question2, /.*/, src, (src)=>
+				@ask question3, /.*/, release, (release)=>
+					@scaffold target, name, src, release
+					process.exit()
+
+	scaffold:(target, name, src, release)->
+		srcdir = "#{target}/" + ( src || "src" )
+		releasefile = "#{target}/" + ( release || "release/app.js" )
+		releasedir = releasefile.split("/").slice(0, -1).join "/"
+
+		if path.existsSync target
+			console.log "#{'Error'.bold.red} Folder exists! #{target}".red
+			return
+
+		fs.mkdirSync target, 0755
+		console.log "#{'Created'.green.bold} #{target}"
+		fs.mkdirSync srcdir, 0755
+		console.log "#{'Created'.green.bold} #{srcdir}"
+		fs.mkdirSync releasedir, 0755
+		console.log "#{'Created'.green.bold} #{releasedir}"
+
+		name = name
+		srcdir = srcdir.replace( target, "" ).substr 1
+		releasefile = releasefile.replace( target, "" ).substr 1
+
+		new Config( target ).write name, srcdir, releasefile


fix removing membership

fix removing membership

--- vue/src/shared/models/membership_model.coffee
+++ vue/src/shared/models/membership_model.coffee
@@ -58,4 +58,4 @@
     @volume == 'mute'

   beforeRemove: ->
-    invokeMap(@recordStore.events.find('eventable.type': 'membership', 'eventable.id': @id), 'remove')
+    invokeMap(@recordStore.events.find('eventableType': 'membership', 'eventableId': @id), 'remove')


extra check for Plumb directive for maximum safety

--- static/coffee/flows/directives.coffee
+++ static/coffee/flows/directives.coffee
@@ -109,7 +109,6 @@
 # manage connections when actionset changes
 app.directive "actionset", [ "$timeout", "$log", "Plumb", "Flow", ($timeout, $log, Plumb, Flow) ->

-
   link = (scope, element, attrs) ->

     Plumb.updateConnection(scope.actionset)
@@ -274,9 +273,6 @@
     scope.$watch (->Flow.language), ->
       scope.updateTranslationStatus(scope.ruleset, Flow.flow.base_language, Flow.language)

-
-
-
   return {
     restrict: "A"
     link: link
@@ -291,6 +287,7 @@
     scope.$watch (->scope.ngModel), ->
       opConfig = Flow.getOperatorConfig(scope.ngModel.type)
       scope.verbose_name = opConfig.verbose_name
+
   return {
     template: '<span>[[verbose_name]]</span>'
     restrict: "C"
@@ -304,6 +301,8 @@
 # turn an element into a jsplumb source
 app.directive "source", [ 'Plumb', '$log', (Plumb, $log) ->
   link = (scope, element, attrs) ->
+    if not attrs.id or not attrs.dropScope then return
+
     if window.mutable
       Plumb.makeSource(attrs.id, attrs.dropScope)


[grunt] remove no longer used variables from Nunjucks task

--- tasks/templates.coffee
+++ tasks/templates.coffee
@@ -36,15 +36,10 @@
       baseLocaleAsRoot  : true
       gettext           : grunt.config('i18n.gettext')

-  gettext    = options.i18n.gettext
-  baseLocale = options.i18n.baseLocale
+  { getLocalesNames, getLocaleProps, getLocaleDir, getLangcode, getRegioncode, isoLocale } = new i18nTools(options.i18n.locales, options.i18n.baseLocale, options.i18n.baseLocaleAsRoot)

-  { getLocalesNames, getLocaleProps, getLocaleDir, getLangcode, getRegioncode, isoLocale } = new i18nTools(options.i18n.locales, baseLocale, options.i18n.baseLocaleAsRoot)
-
-  locales      = getLocalesNames()
-
-  buildDir     = options.files.dest
-  templatesDir = options.files.cwd
+  gettext = options.i18n.gettext
+  locales = getLocalesNames()

   # =======================
   # Config l10n of Nunjucks
@@ -99,7 +94,7 @@
             getLangcode(locale)

         preprocessData: (data) ->
-          pagepath     = humanReadableUrl(@src[0].replace(templatesDir, ''), options.humanReadableUrls.exclude)
+          pagepath     = humanReadableUrl(@src[0].replace(options.files.cwd, ''), options.humanReadableUrls.exclude)
           breadcrumb   = crumble(pagepath)
           matterData   = grunt.file.readJSON(options.files.matter)
           pageProps    = (get(matterData, breadcrumb) or {}).props


Created filters for image files in client-assets script

--- gulp/clientAssets.coffee
+++ gulp/clientAssets.coffee
@@ -9,22 +9,23 @@
 module.exports = (gulp, config) ->
   gulp.task 'client-assets', ->
     path = require 'path'
-    imagemin = require('gulp-imagemin');
-    pngquant = require('imagemin-pngquant');
+    imagemin = require 'gulp-imagemin'
+    pngquant = require 'imagemin-pngquant'
+    gulpFilter = require 'gulp-filter'
+    svgFilter = gulpFilter 'assets/*.svg'
+    pngFilter = gulpFilter 'assets/*.png'
+    jpgFilter = gulpFilter 'assets/*.jpg'
     gulp.src['assets/**'], base: './assets'
-      `.pipe(imagemin({
-         progressive: true,
-         interlaced: true,
-         multipass: true,
-         svgoPlugins: [{removeViewBox: false}],
-         use: [pngquant()]
-       }))`
+      .pipe svgFilter
+      .pipe pngFilter
+      .pipe jpgFilter
+      .pipe imagemin
+        progressive: true
+        interlaced: true
+        multipass: true
+        svgoPlugins: [removeViewBox: false]
+        use: [pngquant()]
+      .pipe svgFilter.restore()
+      .pipe pngFilter.restore()
+      .pipe jpgFilter.restore()
       .pipe gulp.dest path.join config.paths.dest, 'app/assets'
-
- # Attempt to translate javascript into coffeescript
-#      .pipe imagemin
-#        progressive: true
-#        interlaced: true
-#        multipass: true
-#        svgoPlugins: [removeViewBox: false]
-#        use: [pngquant ]


Do not attach scroll listeners when `zoomable` is not set

--- src/utils/events.coffee
+++ src/utils/events.coffee
@@ -1,5 +1,5 @@
       getEventDispatcher: () ->
-
+
         events = [
           'focus',
           'hover',
@@ -13,61 +13,40 @@
         zoom.scale(1)
         zoom.translate([0, 0])

-        if options.axes.x.zoomable?
-          svg.selectAll('.x.axis').call(axes.xAxis)
+        this.getZoomHandler(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch, false)()

-        if options.axes.y.zoomable?
-          svg.selectAll('.y.axis').call(axes.yAxis)
+      getZoomHandler: (svg, dimensions, axes, data, columnWidth, options, handlers, dispatch, zoom) ->
+        self = this

-        if options.axes.y2?.zoomable?
-          svg.selectAll('.y2.axis').call(axes.y2Axis)
+        return ->
+          zoomed = false

-        if data.length
-          columnWidth = this.getBestColumnWidth(axes, dimensions, data, options)
-          this.drawData(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch)
+          [ 'x', 'y', 'y2' ].forEach (axis) ->
+            if options.axes[axis]?.zoomable?
+              svg.selectAll(".#{axis}.axis").call(axes["#{axis}Axis"])
+              zoomed = true
+
+          if data.length
+            columnWidth = self.getBestColumnWidth(axes, dimensions, data, options)
+            self.drawData(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch)
+
+          if zoom and zoomed
+            self.createZoomResetIcon(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch, zoom)

       setZoom: (svg, dimensions, axes, data, columnWidth, options, handlers, dispatch) ->
-        self = this
-
-        zoomHandler = () ->
-            zoomed = false
+        zoom = this.getZoomListener(axes, options)

-            if options.axes.x.zoomable?
-              svg.selectAll('.x.axis').call(axes.xAxis)
-              zoomed = true
+        if zoom
+          zoom.on("zoom", this.getZoomHandler(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch, zoom))
+          svg.call(zoom)

-            if options.axes.y.zoomable?
-              svg.selectAll('.y.axis').call(axes.yAxis)
-              zoomed = true
+      getZoomListener: (axes, options) ->
+        zoomable = false
+        zoom = d3.behavior.zoom()

-            if options.axes.y2?.zoomable?
-              svg.selectAll('.y2.axis').call(axes.y2Axis)
-              zoomed = true
+        [ 'x', 'y', 'y2' ].forEach (axis) ->
+          if options.axes[axis]?.zoomable?
+            zoom[axis](axes["#{axis}Scale"])
+            zoomable = true

-            if data.length
-              columnWidth = self.getBestColumnWidth(axes, dimensions, data, options)
-              self.drawData(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch)
-
-            if zoomed
-              self.createZoomResetIcon(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch, zoom)
-
-        zoom = this.getZoomListener(axes, options, zoomHandler)
-        svg.call(zoom)
-
-      getZoomListener: (axes, options, zoomHandler) ->
-
-        zoomListener = d3.behavior.zoom()
-
-        if zoomHandler?
-          zoomListener.on("zoom", zoomHandler)
-
-        if options.axes.x?.zoomable?
-          zoomListener.x(axes.xScale)
-
-        if options.axes.y?.zoomable?
-          zoomListener.y(axes.yScale)
-
-        if options.axes.y2?.zoomable?
-          zoomListener.y(axes.y2Scale)
-
-        return zoomListener+        return if zoomable then zoom else false


Less lies, and no console.log

Less lies, and no console.log

--- assets/js/vote.js.coffee
+++ assets/js/vote.js.coffee
@@ -2,14 +2,12 @@
   $('.vote').on 'click', (event) ->
     do event.preventDefault
     $this = $(this)
+    $counter = $('.number-of-votes') # TODO: Scope the search to $this
+
     return unless (comicID = $this.data 'id')
-    $counter = $('.number-of-votes')
     count = parseInt $counter.text()
     count = 0 if isNaN(count)

-    console.log "Vote on: #{comicID} with #{count} votes so far"
-
-    # TODO: Make a call, then on success
     $.ajax "/vote/#{comicID}/",
       type: 'POST'
       headers: {'Content-Type': 'application/json'}


Atom config

Atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,4 +1,10 @@
 "*":
+  "atom-beautify":
+    general:
+      analytics: false
+    js:
+      beautify_on_save: true
+      indent_size: 2
   autosave:
     enabled: true
   core:
@@ -44,5 +50,6 @@
     position: "Left"
   "tree-view":
     hideIgnoredNames: true
+    hideVcsIgnoredFiles: true
   welcome:
     showOnStartup: false


120 line length

120 line length

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -3,8 +3,9 @@
     showIndentGuide: true
     fontFamily: "Menlo"
     invisibles: {}
+    softWrapAtPreferredLineLength: true
     fontSize: 15
-    softWrapAtPreferredLineLength: true
+    preferredLineLength: 120
   core:
     ignoredNames: [
       ".bundle"


use github style in markdown

use github style in markdown

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -30,6 +30,8 @@
     userId: "c48ac152-7102-ae12-c9d6-2aafd0240b51"
   "linter-ui-default":
     panelHeight: 81
+  "markdown-preview":
+    useGitHubStyle: true
   mprettier: {}
   "reason-refmt":
     useLinter: false


Add some atom config

Add some atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -23,6 +23,7 @@
       "background-tips"
       "exception-reporting"
       "metrics"
+      "linter-shellcheck"
     ]
   "spell-check":
     grammars: [
@@ -34,3 +35,5 @@
     showOnStartup: false
   "release-notes":
     viewedVersion: "0.89.0"
+  tabs:
+    usePreviewTabs: true


Move docs libraries into their own method

--- js/outer/docs.coffee
+++ js/outer/docs.coffee
@@ -90,6 +90,10 @@
   title: (cx) =>
     "Documentation"

+  lib: =>
+    $("#query").typeahead
+      'source': window.VM.suggestArticles
+
   render: (cx) =>
     rewrite = @rewrite_old_name cx.params.splat[0]
     if rewrite != false
@@ -105,6 +109,3 @@
       .append(HAML['categories']({categories: @categories(), page: name}))
       .append(HAML[name]({find_articles_by_tag: @find_articles_by_tag})) ## XXX: merge w/renderContext?
     $("#main").append(HAML['footer'](renderContext))
-    $("#query").typeahead {
-      'source': window.VM.suggestArticles
-    }


Fix: Don't abort response when last retry fails

--- src/marbles/http.js.coffee
+++ src/marbles/http.js.coffee
@@ -120,9 +120,9 @@
       abort = false

       retryFn = =>
+        return if @retry_count >= @MAX_NUM_RETRIES
+
         abort = true
-
-        return if @retry_count >= @MAX_NUM_RETRIES

         @retry_count += 1
         @retry()


More logging

More logging

--- client/preview_page.coffee
+++ client/preview_page.coffee
@@ -40,6 +40,7 @@

 	$( $('.progress p')[item] ).show()
 	progress = ->
+		console.log "Progress tick"
 	  $('.progress p').hide()
 	  if(item<length)
 	    $( $('.progress p')[item] ).show()
@@ -63,4 +64,5 @@
 	      setTimeout(f, 300)
 	      clearInterval(interval)

+	console.log "Starting progress ticker"
 	interval = setInterval(progress, 800)


Couple performance tweaks.

Couple performance tweaks.

Removing unnecessary hasOwnProperty call (since it's on a hash) and checking to make sure the constructor isn't Object.

--- src/Hydrate.coffee
+++ src/Hydrate.coffee
@@ -142,7 +142,7 @@
         else
           obj[k] = v
     else if typeof obj == "object"
-      if obj.__hydrate_cons?
+      if obj.__hydrate_cons? && obj.__hydrate_cons != "Object"
         proto = @resolvePrototype obj.__hydrate_cons
         if proto?
           if Util.supportsProto
@@ -152,8 +152,7 @@
             tmp.prototype = proto
             t = new tmp
             for k, v of obj
-              if obj.hasOwnProperty k
-                t[k] = v
+              t[k] = v
             obj = t
         else
           @errorHandler new PrototypeNotFoundError(obj, obj.__hydrate_cons)


Restructuring Hydrate a little to be compatible with more environments.  Also removing compatibility shims -- they can be re-added in other ways.

--- src/Hydrate.coffee
+++ src/Hydrate.coffee
@@ -1,326 +1,325 @@
-`
-/*
-From http://www.tutorialspoint.com/javascript/array_indexof.htm
-*/
-if (!Array.prototype.indexOf)
-{
-  Array.prototype.indexOf = function(elt /*, from*/)
-  {
-    var len = this.length;
-
-    var from = Number(arguments[1]) || 0;
-    from = (from < 0)
-         ? Math.ceil(from)
-         : Math.floor(from);
-    if (from < 0)
-      from += len;
-
-    for (; from < len; from++)
-    {
-      if (from in this &&
-          this[from] === elt)
-        return from;
-    }
-    return -1;
-  };
-}
-if(typeof JSON === "undefined"){
-  var dir = "../lib";
-  if(typeof JSON2JSDirectory !== "undefined") dir = JSON2JSDirectory;
-  document.write('\x3Cscript type="text/javascript" src="' + dir + '/json2.js">\x3C/script>');
-}
-`
-
-class Hydrate
-  class Util
-    @d2h = (d) ->
+((definition) ->
+  # This file will function properly as a <script> tag, or a module
+  # using CommonJS and NodeJS or RequireJS module formats.  In
+  # Common/Node/RequireJS, the module exports the Q API and when
+  # executed as a simple <script>, it creates a Q global instead.
+
+  # Montage Require
+  if typeof bootstrap == "function"
+    bootstrap("hydrate", definition);
+
+  # CommonJS
+  else if typeof exports == "object"
+    module.exports = definition();
+
+  # RequireJS
+  else if typeof define == "function" && define.amd
+    define(definition);
+
+  # SES (Secure EcmaScript)
+  else if typeof ses != "undefined"
+    if !ses.ok()
+      return;
+    else
+      ses.makeHydrate = definition;
+
+  # <script>
+  else
+    window.Hydrate = definition();
+
+)(->
+  Util =
+    d2h: (d) ->
       d.toString 16
-    @h2d = (h) ->
+    h2d: (h) ->
       parseInt h, 16
-
+
     # Checks to see if the __proto__ property is accessible
-    @supportsProto = ({}).__proto__?
+    supportsProto: ({}).__proto__?
     # Checks to see if the name property of functions is supportedcomment
-    @supportsFunctionNames = typeof (->).name == "string"
-
+    supportsFunctionNames: typeof (->).name == "string"
+
     # Extract the function name from the given function.
     # This doesn't work with anonymous functions, for obvious reasons.
-    @functionName = (func) ->
+    functionName: (func) ->
       if Util.supportsFunctionNames
         # func.name isn't writable, so this is reliable
         func.name
       else
         # for IE
         func.toString().match(/function ([^(]*)/)?[1]
-  @Util = Util
-
-  # If you try to serialize an object you've attached a method to,
-  # the serialization won't be complete and may throw this error.
-  class Hydrate.NonPrototypeFunctionError extends Error
-    constructor: (@object, @name) ->
-      @message = "Couldn't serialize object; had non-prototype function '#{@name}'"
-  # If you try to deserialize an object, but its constructor can't be found,
-  # this error may be thrown.
-  class Hydrate.PrototypeNotFoundError extends Error
-    constructor: (@object, @cons_id) ->
-      @message = "Prototype not found for object; looked for #{@cons_id}"
-  class Hydrate.AnonymousConstructorError extends Error
-    constructor: (@object) ->
-      @message = "Couldn't resolve constructor name; seems it has an anonymous constructor and object's prototype has no #constructor_name property to provide hints"
-  class Hydrate.VersionInstancePropertyError extends Error
-    constructor: (@object) ->
-      @message = "Objects can't have versions on the instances; can only be on the prototype"
-
-  # Pass in a list of Resolvers that work with deserialization.
-  # See Resolvers below for more information.
+
+
+  class Hydrate
+    # If you try to serialize an object you've attached a method to,
+    # the serialization won't be complete and may throw this error.
+    class Hydrate.NonPrototypeFunctionError extends Error
+      constructor: (@object, @name) ->
+        @message = "Couldn't serialize object; had non-prototype function '#{@name}'"
+    # If you try to deserialize an object, but its constructor can't be found,
+    # this error may be thrown.
+    class Hydrate.PrototypeNotFoundError extends Error
+      constructor: (@object, @cons_id) ->
+        @message = "Prototype not found for object; looked for #{@cons_id}"
+    class Hydrate.AnonymousConstructorError extends Error
+      constructor: (@object) ->
+        @message = "Couldn't resolve constructor name; seems it has an anonymous constructor and object's prototype has no #constructor_name property to provide hints"
+    class Hydrate.VersionInstancePropertyError extends Error
+      constructor: (@object) ->
+        @message = "Objects can't have versions on the instances; can only be on the prototype"
+    # Pass in a list of Resolvers that work with deserialization.
+    # See Resolvers below for more information.
+    # Public.
+    constructor: (@resolver=null) ->
+      if !@resolver?
+        @resolver = new ContextResolver(window)
+      # can be overwritten by #setErrorHandler
+      @errorHandler = (e) ->
+        throw e
+
+      @migrations = {}
+
+    # Serialize an input into a string.  Functions can't be serialized, nor can special objects (like window).
+    # Public.
+    stringify: (input) ->
+      @processed_inputs = []
+      @counter = 0
+      result = switch typeof input
+        when "number", "string" then JSON.stringify(input)
+        when "function" then throw new Error("can't serialize functions")
+        else
+          if input instanceof Array
+            arr = []
+            arr.push @analyze i for i in input
+
+            JSON.stringify(arr)
+          else
+            # is an object...
+            JSON.stringify @analyze input
+      @cleanAfterStringify()
+      result
+
+    # Stringifying an object modifies it.  This is called afterwards in order to remove those modifications.
+    # Private.
+    cleanAfterStringify: ->
+      for input in @processed_inputs
+        if input
+          delete input.__hydrate_id
+          delete input.version
+      true
+
+    # Do a deeper analysis of a value to convert it to a string (and return a better candidate for serialization).  Recurses.
+    # Private.
+    analyze: (input, name) ->
+      switch typeof input
+        when "number", "string" then input
+        when "function"
+          # skip, should probably check to see if the function is attached to the prototype
+          @errorHandler new Hydrate.NonPrototypeFunctionError(input, name)
+        when "undefined"
+          "__hydrate_undef"
+        else
+          if input == null
+            null
+          else if input instanceof Array
+            output = []
+            for v, i in input
+              output[i] = @analyze v, i
+            output
+          else
+            if(input.__hydrate_id)
+              "__hydrate_ref_#{input.__hydrate_id}"
+            else
+              input.__hydrate_id = Util.d2h(@counter++)
+              @processed_inputs.push input
+              # is an object...
+              output = new Object
+              for k, v of input
+                if input.hasOwnProperty k
+                  output[k] = @analyze v, k
+              cons = Util.functionName(input.constructor)
+              if cons == "" && !input.hasOwnProperty("constructor_name")
+                cons = input.constructor_name
+              if !cons?
+                @errorHandler AnonymousConstructorError(input)
+              unless cons == "Object"
+                output.__hydrate_cons = cons
+
+              # copy version property to the instance
+              if input.hasOwnProperty("version")
+                @errorHandler new Hydrate.VersionInstancePropertyError(input)
+              if input.version?
+                output.version = input.version
+
+              output
+
+    # Set an error handler function for when errors do arise.  It receives one argument: an exception.
+    # Public.
+    setErrorHandler: (@errorHandler) ->
+
+    # How Hydrate tracks references to other objects (this is a key).
+    @_refMatcher = /__hydrate_ref_(.*)/
+
+    # Convert a string previously serialized with Hydrate back into a proper object.
+    # Uses the list of resolvers passed in at construction time to map the declared prototype back
+    # to a real function.
+    # Also, invokes migrations as appropriate.
+    # Public.
+    parse: (input) ->
+      @identified_objects = []
+      @references_to_resolve = []
+      o = JSON.parse(input)
+      o = @fixTree o
+      if o instanceof Array || (o? && typeof o == "object")
+        l = o.length
+        if o?
+          for reference in @references_to_resolve
+            [obj, obj_key, ref_id] = reference
+            obj[obj_key] = @identified_objects[ref_id]
+          @clean o
+      o
+
+    # Fixes the object "tree" resulting from basic object deserialization.  This includes things like
+    # fixing prototypes and resolving references.  Recurses.
+    # Public.
+    fixTree: (obj) ->
+      # TODO reduce duplication between following two branches
+
+      if obj instanceof Array
+        for v, k in obj
+          v = @fixTree v
+          if v == "__hydrate_undef"
+            obj[k] = undefined;
+          else if typeof v == "string" && m = v.match Hydrate._refMatcher
+            k2 = Util.h2d(m[1])
+            @references_to_resolve.push([obj, k, k2])
+          else
+            obj[k] = v
+      else if obj == "__hydrate_undef"
+        obj = undefined
+      else if obj? && typeof obj == "object"
+        # Object has a constructor; find its prototype and switch our object over to it
+        if obj && obj.__hydrate_cons?
+          proto = @resolvePrototype obj.__hydrate_cons
+          if proto?
+            if Util.supportsProto
+              obj.__proto__ = proto
+            else
+              tmp = (->)
+              tmp.prototype = proto
+              t = new tmp
+              for k, v of obj
+                t[k] = v
+              obj = t
+          else
+            @errorHandler new Hydrate.PrototypeNotFoundError(obj, obj.__hydrate_cons)
+
+        for k, v of obj
+          v = @fixTree v
+          if k == "__hydrate_id"
+            v2 = Util.h2d(v)
+            @identified_objects[v2] = obj
+          else if v == "__hydrate_undef"
+            obj[k] = undefined
+          else if typeof v == "string" && m = v.match Hydrate._refMatcher
+            k2 = Util.h2d(m[1])
+            @references_to_resolve.push([obj, k, k2])
+          else
+            obj[k] = v
+      obj
+
+    # Converts a string representing a constructor to an actual function.  Traverses all
+    # resolvers in order looking for one that can accomplish this.
+    # Private.
+    resolvePrototype: (cons_id) ->
+      if !@resolver?
+        throw new Error("No Hydrate resolver found -- you should specify one in the Hydrate constructor!")
+      @resolver.resolve cons_id
+
+    # Clean up the object tree after it's been mostly deserialized.  This is necessary
+    # because some properties get added during the serialization process to permit deserialization.
+    # Also, runs migrations.
+    # Private.
+    clean: (o, cleaned=[]) ->
+      # migrate
+      migrations = @migrations[o.__hydrate_cons]
+      if(o.version? &&
+         migrations? &&
+         o.version < migrations.length)
+        for num in [o.version..migrations.length - 1]
+          migrations[num].call(o)
+        delete o.version
+
+      cleaned.push o
+      if typeof o == "object" && !(o instanceof Array)
+        for k, v of o
+          if k == "__hydrate_id" || k == "__hydrate_cons"
+            delete o[k]
+          else if typeof v == "object" && v && !(o instanceof Array) && cleaned.indexOf(v) < 0
+            @clean(v, cleaned)
+      true
+
+    # Declare a migration for an object -- this will automatically run callbacks
+    # on old objects to get them in sync with current Javascript classes.
+    # Three arguments:
+    #  klass: function or function name for which migration applies, i.e. Person or "Person"
+    #  index: version number of the model, i.e. 2 will cause the provided callback to be execute on deserializing Person instances with a version of 1.
+    #  callback: the function to be called on the object being deserialized.  The "this" of the function will be the object.  There are no arguments.
+    #
+    # NOTE: The version of the object is NOT (and should NOT) be set by this framework or by you in the callback.  Ther version number should be on the prototype, not the instance.  This ensures you don't get into weird situations where the instance thinks it's version 1 when the prototype it's currently associated with thinks it's version 2.
+    migration: (klass, index, callback) ->
+      switch typeof klass
+        when "function"
+          klass = klass.name
+          if klass == ""
+            @errorHandler new AnonymousConstructorError(klass)
+        when "string"
+          null
+        else
+          throw new Error("invalid class passed in; pass a function or a string")
+      all_versions = @migrations[klass]
+      if !all_versions?
+        all_versions = @migrations[klass] = []
+      all_versions[index-1] = callback
+      #if !@highest_migration[klass]? || @highest_migration[klass] < index
+      #  @highest_migration[klass] = index
+      true
+
+  # A Resolver is simple: it maps from a string representing a constructor to a prototype that's used by that constructor.
   # Public.
-  constructor: (@resolver=null) ->
-    if !@resolver?
-      @resolver = new ContextResolver(window)
-    # can be overwritten by #setErrorHandler
-    @errorHandler = (e) ->
-      throw e
-
-    @migrations = {}
-
-  # Serialize an input into a string.  Functions can't be serialized, nor can special objects (like window).
+  class Resolver
+    resolve: (cons_id) ->
+      # return null if resolver didn't find anything
+      throw new Error("abstract")
+
+  # The context resolver is also fairly simple: it takes a list of objects as "contexts", then when a constructor string is provided, it iterates through them in order, looking for the first context that contains that constructor string as a property, and returns the prototype for it.
+  # If you want another way to map from constructor names to prototypes, make something similar to ContextResolver and pass it into the main Hydrate constructor.
   # Public.
-  stringify: (input) ->
-    @processed_inputs = []
-    @counter = 0
-    result = switch typeof input
-      when "number", "string" then JSON.stringify(input)
-      when "function" then throw new Error("can't serialize functions")
+  class ContextResolver extends Resolver
+    constructor: (@context) ->
+    resolve: (cons_id) ->
+      v = @context[cons_id]
+      if v?
+        v.prototype
       else
-        if input instanceof Array
-          arr = []
-          arr.push @analyze i for i in input
-
-          JSON.stringify(arr)
-        else
-          # is an object...
-          JSON.stringify @analyze input
-    @cleanAfterStringify()
-    result
-
-  # Stringifying an object modifies it.  This is called afterwards in order to remove those modifications.
-  # Private.
-  cleanAfterStringify: ->
-    for input in @processed_inputs
-      if input
-        delete input.__hydrate_id
-        delete input.version
-    true
-
-  # Do a deeper analysis of a value to convert it to a string (and return a better candidate for serialization).  Recurses.
-  # Private.
-  analyze: (input, name) ->
-    switch typeof input
-      when "number", "string" then input
-      when "function"
-        # skip, should probably check to see if the function is attached to the prototype
-        @errorHandler new Hydrate.NonPrototypeFunctionError(input, name)
-      when "undefined"
-        "__hydrate_undef"
-      else
-        if input == null
-          null
-        else if input instanceof Array
-          output = []
-          for v, i in input
-            output[i] = @analyze v, i
-          output
-        else
-          if(input.__hydrate_id)
-            "__hydrate_ref_#{input.__hydrate_id}"
-          else
-            input.__hydrate_id = Util.d2h(@counter++)
-            @processed_inputs.push input
-            # is an object...
-            output = new Object
-            for k, v of input
-              if input.hasOwnProperty k
-                output[k] = @analyze v, k
-            cons = Util.functionName(input.constructor)
-            if cons == "" && !input.hasOwnProperty("constructor_name")
-              cons = input.constructor_name
-            if !cons?
-              @errorHandler AnonymousConstructorError(input)
-            unless cons == "Object"
-              output.__hydrate_cons = cons
-
-            # copy version property to the instance
-            if input.hasOwnProperty("version")
-              @errorHandler new Hydrate.VersionInstancePropertyError(input)
-            if input.version?
-              output.version = input.version
-
-            output
-
-  # Set an error handler function for when errors do arise.  It receives one argument: an exception.
-  # Public.
-  setErrorHandler: (@errorHandler) ->
-
-  # How Hydrate tracks references to other objects (this is a key).
-  @_refMatcher = /__hydrate_ref_(.*)/
-
-  # Convert a string previously serialized with Hydrate back into a proper object.
-  # Uses the list of resolvers passed in at construction time to map the declared prototype back
-  # to a real function.
-  # Also, invokes migrations as appropriate.
-  # Public.
-  parse: (input) ->
-    @identified_objects = []
-    @references_to_resolve = []
-    o = JSON.parse(input)
-    o = @fixTree o
-    if o instanceof Array || (o? && typeof o == "object")
-      l = o.length
-      if o?
-        for reference in @references_to_resolve
-          [obj, obj_key, ref_id] = reference
-          obj[obj_key] = @identified_objects[ref_id]
-        @clean o
-    o
-
-  # Fixes the object "tree" resulting from basic object deserialization.  This includes things like
-  # fixing prototypes and resolving references.  Recurses.
-  # Public.
-  fixTree: (obj) ->
-    # TODO reduce duplication between following two branches
-
-    if obj instanceof Array
-      for v, k in obj
-        v = @fixTree v
-        if v == "__hydrate_undef"
-          obj[k] = undefined;
-        else if typeof v == "string" && m = v.match Hydrate._refMatcher
-          k2 = Util.h2d(m[1])
-          @references_to_resolve.push([obj, k, k2])
-        else
-          obj[k] = v
-    else if obj == "__hydrate_undef"
-      obj = undefined
-    else if obj? && typeof obj == "object"
-      # Object has a constructor; find its prototype and switch our object over to it
-      if obj && obj.__hydrate_cons?
-        proto = @resolvePrototype obj.__hydrate_cons
-        if proto?
-          if Util.supportsProto
-            obj.__proto__ = proto
-          else
-            tmp = (->)
-            tmp.prototype = proto
-            t = new tmp
-            for k, v of obj
-              t[k] = v
-            obj = t
-        else
-          @errorHandler new Hydrate.PrototypeNotFoundError(obj, obj.__hydrate_cons)
-
-      for k, v of obj
-        v = @fixTree v
-        if k == "__hydrate_id"
-          v2 = Util.h2d(v)
-          @identified_objects[v2] = obj
-        else if v == "__hydrate_undef"
-          obj[k] = undefined
-        else if typeof v == "string" && m = v.match Hydrate._refMatcher
-          k2 = Util.h2d(m[1])
-          @references_to_resolve.push([obj, k, k2])
-        else
-          obj[k] = v
-    obj
-
-  # Converts a string representing a constructor to an actual function.  Traverses all
-  # resolvers in order looking for one that can accomplish this.
-  # Private.
-  resolvePrototype: (cons_id) ->
-    if !@resolver?
-      throw new Error("No Hydrate resolver found -- you should specify one in the Hydrate constructor!")
-    @resolver.resolve cons_id
-
-  # Clean up the object tree after it's been mostly deserialized.  This is necessary
-  # because some properties get added during the serialization process to permit deserialization.
-  # Also, runs migrations.
-  # Private.
-  clean: (o, cleaned=[]) ->
-    # migrate
-    migrations = @migrations[o.__hydrate_cons]
-    if(o.version? &&
-       migrations? &&
-       o.version < migrations.length)
-      for num in [o.version..migrations.length - 1]
-        migrations[num].call(o)
-      delete o.version
-
-    cleaned.push o
-    if typeof o == "object" && !(o instanceof Array)
-      for k, v of o
-        if k == "__hydrate_id" || k == "__hydrate_cons"
-          delete o[k]
-        else if typeof v == "object" && v && !(o instanceof Array) && cleaned.indexOf(v) < 0
-          @clean(v, cleaned)
-    true
-
-  # Declare a migration for an object -- this will automatically run callbacks
-  # on old objects to get them in sync with current Javascript classes.
-  # Three arguments:
-  #  klass: function or function name for which migration applies, i.e. Person or "Person"
-  #  index: version number of the model, i.e. 2 will cause the provided callback to be execute on deserializing Person instances with a version of 1.
-  #  callback: the function to be called on the object being deserialized.  The "this" of the function will be the object.  There are no arguments.
-  #
-  # NOTE: The version of the object is NOT (and should NOT) be set by this framework or by you in the callback.  Ther version number should be on the prototype, not the instance.  This ensures you don't get into weird situations where the instance thinks it's version 1 when the prototype it's currently associated with thinks it's version 2.
-  migration: (klass, index, callback) ->
-    switch typeof klass
-      when "function"
-        klass = klass.name
-        if klass == ""
-          @errorHandler new AnonymousConstructorError(klass)
-      when "string"
         null
-      else
-        throw new Error("invalid class passed in; pass a function or a string")
-    all_versions = @migrations[klass]
-    if !all_versions?
-      all_versions = @migrations[klass] = []
-    all_versions[index-1] = callback
-    #if !@highest_migration[klass]? || @highest_migration[klass] < index
-    #  @highest_migration[klass] = index
-    true
-
-# A Resolver is simple: it maps from a string representing a constructor to a prototype that's used by that constructor.
-# Public.
-class Resolver
-  resolve: (cons_id) ->
-    # return null if resolver didn't find anything
-    throw new Error("abstract")
-
-# The context resolver is also fairly simple: it takes a list of objects as "contexts", then when a constructor string is provided, it iterates through them in order, looking for the first context that contains that constructor string as a property, and returns the prototype for it.
-# If you want another way to map from constructor names to prototypes, make something similar to ContextResolver and pass it into the main Hydrate constructor.
-# Public.
-class ContextResolver extends Resolver
-  constructor: (@context) ->
-  resolve: (cons_id) ->
-    v = @context[cons_id]
-    if v?
-      v.prototype
-    else
+
+  # For simplicity's sake one can only pass a single resolver into Hydrate.  If you need more, use the MultiResolver.
+  # You can pass any number of resolvers into the MultiResolver constructor, which will be resolved in order.
+  class MultiResolver extends Resolver
+    constructor: (@resolvers=[]) ->
+    resolve: (cons_id) ->
+      for res in @resolvers
+        proto = res.resolve(cons_id)
+        return proto if proto?
       null

-# For simplicity's sake one can only pass a single resolver into Hydrate.  If you need more, use the MultiResolver.
-# You can pass any number of resolvers into the MultiResolver constructor, which will be resolved in order.
-class MultiResolver extends Resolver
-  constructor: (@resolvers=[]) ->
-  resolve: (cons_id) ->
-    for res in @resolvers
-      proto = res.resolve(cons_id)
-      return proto if proto?
-    null
-
-
-Hydrate.Resolver = Resolver;
-Hydrate.ContextResolver = ContextResolver;
-Hydrate.MultiResolver = MultiResolver;
-
-this.Hydrate = Hydrate;
+  Hydrate.Util = Util;
+
+  Hydrate.Hydrate = Hydrate;
+  Hydrate.Resolver = Resolver;
+  Hydrate.ContextResolver = ContextResolver;
+  Hydrate.MultiResolver = MultiResolver;
+
+  return Hydrate
+)


Fix a crash.

Fix a crash.

On a matchless query, Mongo retuns docs as undefined rather than an
empty array, causing an index error (docs.length).

I don't know what is causing it to return undefined docs, so let's
treat the symptoms rather than the disease.

--- src/models/torrents.coffee
+++ src/models/torrents.coffee
@@ -34,24 +34,25 @@
   q.select { title : 1, size : 1, dateUploaded : 1, category : 1, permalink : 1, snatches : 1, infohash : 1 }, { _id : 0 }
   q.sort 'dateUploaded', -1
   q.exec (err, docs) ->
-    multi = redis.multi()
-    t = Date.now()
-    t_ago = t - ANNOUNCE_INTERVAL * DROP_COUNT * 1000
-    for doc in docs
-      key_seed = 'torrent:' + doc.infohash + ':seeds'
-      key_peer = 'torrent:' + doc.infohash + ':peers'
-      multi.ZREMRANGEBYSCORE key_peer, 0, t_ago
-      multi.ZREMRANGEBYSCORE key_seed, 0, t_ago
-      multi.ZCARD key_peer
-      multi.ZCARD key_seed
+    if docs isnt undefined
+      t_ago = t - ANNOUNCE_INTERVAL * DROP_COUNT * 1000
+      multi = redis.multi()
+      t = Date.now()
+      for doc in docs
+        key_seed = 'torrent:' + doc.infohash + ':seeds'
+        key_peer = 'torrent:' + doc.infohash + ':peers'
+        multi.ZREMRANGEBYSCORE key_peer, 0, t_ago
+        multi.ZREMRANGEBYSCORE key_seed, 0, t_ago
+        multi.ZCARD key_peer
+        multi.ZCARD key_seed

-    multi.exec (err, replies) ->
-      for doc in docs
-        replies.shift()
-        replies.shift()
-        doc.peers = replies.shift()
-        doc.seeds = replies.shift()
-      callback(docs)
+      multi.exec (err, replies) ->
+        for doc in docs
+          replies.shift()
+          replies.shift()
+          doc.peers = replies.shift()
+          doc.seeds = replies.shift()
+        callback(docs)

 Torrent.method 'generatePermalink', (callback) ->
   baseurl = @title.substring(0, 75).replace(`/ /g`, '_')


debug travis: forgot global leak

debug travis: forgot global leak

--- test/alarm_test.coffee
+++ test/alarm_test.coffee
@@ -148,7 +148,7 @@

     describe "PUT alarms/:id", ->

-        before (done) =>
+        before (done) ->
             helpers.cleanDb =>
                 done()

@@ -157,7 +157,7 @@

         describe "Update alarm with same timezone", ->

-            it "When I create an alarm", (done) =>
+            it "When I create an alarm", (done) ->
                 @alarm =
                     action: 'DISPLAY'
                     trigg: "Tue Apr 23 2013 14:25:00"
@@ -168,7 +168,7 @@
                     done()

-            it "Then should return the alarm with the updated value", (done) =>
+            it "Then should return the alarm with the updated value", (done) ->

                 @alarm.action = 'EMAIL'
                 @alarm.trigg = "Tue Apr 23 2013 14:30:00"
@@ -186,7 +186,7 @@
                     body.should.have.property 'description', @alarm.description
                     done()

-            it "And should have persisted the alarm into database", (done) =>
+            it "And should have persisted the alarm into database", (done) ->

                 helpers.getAlarmByID @alarm.id, (err, alarm) =>
                     should.not.exist err
@@ -201,7 +201,7 @@

         describe "Update an alarm with an other timezone", ->

-            it "When I create an alarm", (done) =>
+            it "When I create an alarm", (done) ->
                 @alarm =
                     action: 'DISPLAY'
                     trigg: "Tue Apr 23 2013 14:25:00"
@@ -212,7 +212,7 @@
                     done()

-            it "Then should return the alarm with the updated value", (done) =>
+            it "Then should return the alarm with the updated value", (done) ->

                 @alarm.action = 'EMAIL'
                 @alarm.trigg = "Tue Apr 23 2013 14:30:00"
@@ -231,7 +231,7 @@
                     body.should.have.property 'timezone', @alarm.timezone
                     done()

-            it "And should have persisted the alarm into database", (done) =>
+            it "And should have persisted the alarm into database", (done) ->

                 helpers.getAlarmByID @alarm.id, (err, alarm) =>
                     should.not.exist err


Third test

Third test

--- test/js/totodoo.coffee
+++ test/js/totodoo.coffee
@@ -35,3 +35,12 @@
 				expect(text).to.eventually.contain
 				'Todos'
 			@timeout 4000, done()
+
+		it 'is the length of the default list items', (done) ->
+			drvr = @driver
+			# wait for page to be loaded
+			@driver.wait((()-> drvr.executeScript('return document.readyState')), 10000).then ()->
+				lis = element.all(drvr.findElement(id: 'todo-list').findElements(tagName: 'li'))
+				expect(lis.count()).to.eventually.equal
+				2
+			@timeout 4000, done()


return lis

return lis

--- test/js/totodoo.coffee
+++ test/js/totodoo.coffee
@@ -32,8 +32,8 @@
 			@timeout 4000, done()

 		it 'is the lenght of the default list items = 2', (done) ->
-			len = @driver.findElement(js: 'return document.getElementById("todo-list").getElementsByTagName("li").length')
-			expect(len).to.eventually.equal 2
+			lis = @driver.findElement(js: 'return document.getElementById("todo-list").getElementsByTagName("li")')
+			expect(lis.length).to.eventually.equal 2
 			@timeout 4000, done()

 	describe 'List actions', ->


tab/space consistency

tab/space consistency

--- irc.coffee
+++ irc.coffee
@@ -48,27 +48,27 @@

 # Many thanks to Dennis for his StackOverflow answer: http://goo.gl/UDanx
 string2ArrayBuffer = (string, callback) ->
-  bb = new WebKitBlobBuilder()
-  bb.append(string)
-  f = new FileReader()
-  f.onload = (e) ->
-    callback(e.target.result)
-  f.readAsArrayBuffer(bb.getBlob())
+	bb = new WebKitBlobBuilder()
+	bb.append(string)
+	f = new FileReader()
+	f.onload = (e) ->
+		callback(e.target.result)
+	f.readAsArrayBuffer(bb.getBlob())

 arrayBuffer2String = (buf, callback) ->
-  bb = new WebKitBlobBuilder()
-  bb.append(buf)
-  f = new FileReader()
-  f.onload = (e) ->
-    callback(e.target.result)
-  f.readAsText(bb.getBlob())
+	bb = new WebKitBlobBuilder()
+	bb.append(buf)
+	f = new FileReader()
+	f.onload = (e) ->
+		callback(e.target.result)
+	f.readAsText(bb.getBlob())

 arrayBufferToArrayOfLongs = (arrayBuffer) ->
-  longs = []
-  arrayBufferView = new Uint8Array(arrayBuffer)
-  for i in [0...arrayBufferView.length]
-    longs[i] = arrayBufferView[i]
-  longs
+	longs = []
+	arrayBufferView = new Uint8Array(arrayBuffer)
+	for i in [0...arrayBufferView.length]
+		longs[i] = arrayBufferView[i]
+	longs

 arrayOfLongsToArrayBuffer = (longs) ->
 	ab = new ArrayBuffer longs.length
@@ -122,8 +122,8 @@
 		@socket.setTimeout 60000, @onTimeout

 	onTimeout: =>
-    @send 'PING', +new Date
-    @socket.setTimeout 60000, @onTimeout
+		@send 'PING', +new Date
+		@socket.setTimeout 60000, @onTimeout

 	onData: (pdata) ->
 		@data = @data.concat pdata
@@ -148,10 +148,10 @@
 				break

 	onError: (err) ->
-	  console.log "error", err
-	  @socket.setTimeout 0, @onTimeout
-	  @socket.end()
-	  @emit 'disconnect'
+		console.log "error", err
+		@socket.setTimeout 0, @onTimeout
+		@socket.end()
+		@emit 'disconnect'

 	onClose: ->
 		@socket.setTimeout 0, @onTimeout


Social: fix possible type errors

Social: fix possible type errors

--- workers/social/lib/social/models/computeproviders/computeutils.coffee
+++ workers/social/lib/social/models/computeproviders/computeutils.coffee
@@ -55,6 +55,9 @@
   return callback null  unless revive

   { connection: { delegate:account }, context: { group } } = client
+
+  return callback new KodingError 'Account not set'  unless account
+  return callback new KodingError 'group not set'  unless group

   JGroup = require '../group'
   JGroup.one { slug: group }, (err, groupObj) ->


Word submissions are now sent as part of the score event

--- models/game.coffee
+++ models/game.coffee
@@ -18,6 +18,7 @@
     @gameId = null
     @_sendScoreInterval = null
     @snapHistory = []
+    @wordSubmissionHistory = []

   setEndConfig: (endConfig) ->
     @endConfig = endConfig
@@ -108,6 +109,10 @@

     #Add the submission to db
     @DbHelper.addWordSubmission @gameId, player.uuid, word, (word_index)=>
+      @wordSubmissionHistory.push
+        player: player.identifier()
+        word: word
+
       snapped = []
       for p in @players
         if p.hasGuessed(word)
@@ -190,8 +195,10 @@
     @io.sockets.emit "scores",
       scores: @scores()
       snaps: @snapHistory
+      wordSubmissions: @wordSubmissionHistory

     @snapHistory = []
+    @wordSubmissionHistory = []

   gameOver: () ->
     console.log "Ending game"


Reject duplicate words from the same player

--- models/game.coffee
+++ models/game.coffee
@@ -6,11 +6,17 @@
     @players.push(newPlayer)

   addWord: (player, word) ->
+
+    //Reject word if already guessed by player
+    if word in player.words
+      return
+
     snapped = false
     for p in @players
       if word in p.words
         p.sendSnap(word, 1)
         snapped = true
+
     if snapped
       player.sendSnap(word, 1)
     player.addWord(word)


rename contents to getContents

rename contents to getContents

--- octokit.coffee
+++ octokit.coffee
@@ -985,7 +985,7 @@

           # Get contents
           # --------
-          @contents = (branch, path) ->
+          @getContents = (branch, path) ->
             _request 'GET', "#{@repoPath}/contents?ref=#{branch}", {path: path}


id.equals(null) is bad

id.equals(null) is bad

--- models/team.coffee
+++ models/team.coffee
@@ -23,7 +23,7 @@
 TeamSchema.plugin require('mongoose-types').useTimestamps

 TeamSchema.method 'includes', (person, code) ->
-  @code == code or _.any @people_ids, (id) -> id.equals(person?.id)
+  @code == code or person and _.any @people_ids, (id) -> id.equals(person.id)

 TeamSchema.method 'people', (callback) ->
   Person.find _id: { '$in': @people_ids }, callback


Workspace::getActiveEitor -> Workspace::getActiveTextEditor

--- spec/github-file-spec.coffee
+++ spec/github-file-spec.coffee
@@ -29,7 +29,7 @@
          atom.workspace.open(filePathRelativeToWorkingDir)

       runs ->
-        editor = atom.workspace.getActiveEditor()
+        editor = atom.workspace.getActiveTextEditor()
         githubFile = GitHubFile.fromPath(editor.getPath())

     teardownWorkingDirAndRestoreFixture = (fixtureName) ->
@@ -84,7 +84,7 @@
               atom.workspace.open('a/b#/test#hash.md')

             runs ->
-              editor = atom.workspace.getActiveEditor()
+              editor = atom.workspace.getActiveTextEditor()
               githubFile = GitHubFile.fromPath(editor.getPath())
               spyOn(githubFile, 'openUrlInBrowser')
               githubFile.open()