adjustments for homework\?  need to ask Fred some stuff

--- src/components/task-plan/reading-stats.cjsx
+++ src/components/task-plan/reading-stats.cjsx
@@ -7,7 +7,7 @@
 LoadableItem = require '../loadable-item'

 Stats = React.createClass
-  percent: (num,total) ->
+  _percent: (num,total) ->
     Math.round((num/total) * 100)

   percentDelta: (a,b) ->
@@ -22,15 +22,24 @@
       op = '-'
     op + ' ' + Math.round((change / b) * 100)

-  percentCorrect: (data) ->
+  percent: (data, correctOrIncorrect) ->
+    correctOrIncorrect ?= 'correct'
+    count = correctOrIncorrect + '_count'
+
     total_count = data.correct_count+data.incorrect_count
-    if total_count then @percent(data.correct_count, total_count) else 0
+    if total_count then @_percent(data[count], total_count) else 0

-  renderPercentCorrectBar: (data, type) ->
-    percentCorrect = @percentCorrect(data)
+  renderPercentBar: (data, type, correctOrIncorrect) ->
+    correctOrIncorrect ?= 'correct'
+    percentCorrect = @percent(data, correctOrIncorrect)
+
+    bsStyles =
+      'correct' : 'success'
+      'incorrect' : 'danger'
+
     classes = 'reading-progress-bar'
     classes += ' no-progress' unless percentCorrect
-    correct = <BS.ProgressBar className={classes} bsStyle="success" label="%(percent)s%" now={percentCorrect} key="page-progress-#{type}-#{data.page.id}" />
+    correct = <BS.ProgressBar className={classes} bsStyle={bsStyles[correctOrIncorrect]} label="%(percent)s%" now={percentCorrect} key="page-progress-#{type}-#{data.page.id}-#{correctOrIncorrect}" />

   renderProgressBar: (data, type, index, previous) ->
     studentCount = if type is 'practice' then ({data.student_count} students)
@@ -41,7 +50,8 @@

         <BS.ProgressBar className="reading-progress-group">
-          {@renderPercentCorrectBar(data, type)}
+          {@renderPercentBar(data, type, 'correct')}
+          {@renderPercentBar(data, type, 'incorrect')}
         </BS.ProgressBar>
         {previous}

@@ -87,24 +97,22 @@
     chapters = _.map(plan.stats.course.current_pages, @renderChapterBars)
     practice = _.map(plan.stats.course.spaced_pages, @renderPracticeBars)

-    if _.isEmpty(chapters)
-      chapters =
No Chapter Progress

+    unless _.isEmpty(chapters)
+      chapters =
{chapters}

+      practiceLabel = Space Practice Performance

-    if _.isEmpty(practice)
-      practice =
No Chapter Progress

-
+    unless _.isEmpty(practice)
+      practice =

+        {practiceLabel}
+        {practice}
+

     <BS.Panel className="reading-stats">

         {course}

-

-        {chapters}
-

-

-        Space Practice Performance
-        {practice}
-

+      {chapters}
+      {practice}
     </BS.Panel>

 StatsShell = React.createClass


Fix rotations >= 90 degrees

Fix rotations >= 90 degrees

--- src/coffee/obscura.coffee
+++ src/coffee/obscura.coffee
@@ -129,11 +129,21 @@
         @context.restore();
         @context.save();
         {w,h}=@dimensions
-        if angle is 90 or angle is 120
-            {cw,ch} = {h,w}
-        else if angle isnt 180 or angle isnt 360
-            cw = w*Math.cos(angle * Math.PI/180) + h*Math.sin(angle * Math.PI/180)
-            ch = h*Math.cos(angle * Math.PI/180) + w*Math.sin(angle * Math.PI/180)
+        cw = w
+        ch = h
+        if angle is 90 or angle is 270
+            cw = h
+            ch = w
+        else if angle isnt 180 and angle isnt 360
+            theta = angle % 90
+            quadrant = Math.floor(angle/90)
+
+            if quadrant is 0 or quadrant is 2
+                cw = w*Math.cos(theta * (Math.PI/180)) + h*Math.sin(theta * (Math.PI/180))
+                ch = h*Math.cos(theta * (Math.PI/180)) + w*Math.sin(theta * (Math.PI/180))
+            else
+                ch = w*Math.cos(theta * (Math.PI/180)) + h*Math.sin(theta * (Math.PI/180))
+                cw = h*Math.cos(theta * (Math.PI/180)) + w*Math.sin(theta * (Math.PI/180))

         @context.globalCompositeOperation = "copy";

@@ -164,7 +174,7 @@
         @context.rotate(angle * Math.PI/180)
         @context.clearRect(0,0,@canvas.width,@canvas.height)
         @context.drawImage(@target,0,0,w,h,-x2,-y2,w,h)
-
+        @context.translate(x,y)
         @imageDimensions = @dimensions
         @context.restore()
         @render()


Prefix entity uid with "~"

Prefix entity uid with "~"

--- src/client/entity.coffee
+++ src/client/entity.coffee
@@ -12,6 +12,6 @@
         loc.pathname,
         loc.search
       ].join('')
-    app.config.uid = util.base64.encode(uid, true)
+    app.config.uid = "~#{util.base64.encode(uid, true)}"


Fix usage of _.extend.

Fix usage of _.extend.

--- src/coffee/export.coffee
+++ src/coffee/export.coffee
@@ -31,7 +31,7 @@
     @taxService = new Taxes()

   _initMapping: (header) ->
-    _.extend @options
+    _.extend @options,
       channelService: @channelService
       categoryService: @categoryService
       typesService: @typesService


Bug fix: copied data out of store

Bug fix: copied data out of store

--- lib/memory_cursor.coffee
+++ lib/memory_cursor.coffee
@@ -52,11 +52,13 @@
         (delete find_query[key]; ins[key] = value.$in) for key, value of find_query when value?.$in
         ins_size = _.size(ins)

+        # NOTE: we clone the data out of the store since the caller could modify it
+
         # use find
         if keys.length or ins_size
           if @_cursor.$ids
             for id, model_json of @store
-              json.push(model_json) if _.contains(@_cursor.$ids, model_json.id) and _.isEqual(_.pick(model_json, keys), find_query)
+              json.push(_.clone(model_json)) if _.contains(@_cursor.$ids, model_json.id) and _.isEqual(_.pick(model_json, keys), find_query)
             callback()

           else
@@ -71,7 +73,7 @@
                   return callback(err) if err
                   return callback() unless is_match
                   if not find_keys.length or (exists and (keys.length isnt find_keys.length)) # exists only needs one result
-                    json.push(model_json)
+                    json.push(_.clone(model_json))
                     return callback()

                   # check next key
@@ -88,9 +90,9 @@
         else
           # filter by ids
           if @_cursor.$ids
-            json.push(model_json) for id, model_json of @store when _.contains(@_cursor.$ids, model_json.id)
+            json.push(_.clone(model_json)) for id, model_json of @store when _.contains(@_cursor.$ids, model_json.id)
           else
-            json = (model_json for id, model_json of @store)
+            json = (_.clone(model_json) for id, model_json of @store)
           callback()

       if not exists
@@ -111,10 +113,7 @@
             json = json.splice(0, Math.min(json.length, @_cursor.$limit))
           callback()

-        queue.defer (callback) =>
-          return callback() unless @_cursor.$include
-          json = (_.clone(model_json) for model_json in json) # ensure we don't update the underlying store
-          @fetchIncludes(json, callback)
+        queue.defer (callback) => @fetchIncludes(json, callback)

       queue.await =>
         return callback(null, (if _.isArray(json) then json.length else (if json then 1 else 0))) if @hasCursorQuery('$count')


fix(transaction): Trigger changes only after COMMIT has been executed

- Fixes #1327
- This caused the dataase query inside QuerySubscription to, on occassion,
  return stale results from the database because the database hadn't
  finished writing. This consequently caused the QuerySubscription to
  build and incorrect result set.

--- src/flux/stores/database-transaction.coffee
+++ src/flux/stores/database-transaction.coffee
@@ -27,11 +27,11 @@
       fn(@)
     .finally =>
       if @_opened
+        @_opened = false
         @_query("COMMIT")
-        @_opened = false
-      global.setImmediate =>
-        for record in @_changeRecords
-          @database.accumulateAndTrigger(record)
+        .then =>
+          for record in @_changeRecords
+            @database.accumulateAndTrigger(record)

   # Mutating the Database


simplify map event handling

simplify map event handling

--- src/components/Map.coffee
+++ src/components/Map.coffee
@@ -64,16 +64,11 @@
               when COMPANY    then VMIcons.company

     React.createElement Map,
-      ref       : 'map'
       center    : @props.center
       zoom      : @props.zoom
       className : "map",
-      onLeafletMoveend: (ev) =>
-        c = @refs.map.getLeafletElement?()?.getCenter()
-        onMoveend c
-      onLeafletZoomend: (ev) =>
-        z = @refs.map.getLeafletElement?()?.getZoom()
-        onZoomend z
+      onLeafletMoveend: (ev) -> onMoveend ev.target.getCenter()
+      onLeafletZoomend: (ev) -> onZoomend ev.target.getZoom()
       onLeafletClick: (ev) -> onClick ev.latlng
       React.createElement TileLayer,
         url: "http://{s}.tile.osm.org/{z}/{x}/{y}.png"


"Title added" notification

"Title added" notification

--- lib/notifications.coffee
+++ lib/notifications.coffee
@@ -503,8 +503,12 @@
           if changed.title or changed.published or changed.deleted or changed.private or changed.minimized or changed.format or changed.tags or changed.file
             html += "
\n"
             if changed.title
-              text += "  * Title changed from \"#{titleOrUntitled old}\"\n"
-              html += "
Title changed from \"#{formatTitleOrFilename old, true, true}\"\n"
+              if old.title
+                text += "  * Title changed from \"#{titleOrUntitled old}\"\n"
+                html += "
Title changed from \"#{formatTitleOrFilename old, true, true}\"\n"
+              else
+                text += "  * Title added\n"
+                html += "
Title added\n"
             if changed.published
               if msg.published
                 text += "  * PUBLISHED\n"


Change internal scanner default

Change internal scanner default

--- lib/babel-transpiler.coffee
+++ lib/babel-transpiler.coffee
@@ -14,7 +14,7 @@
       order: 10
     useInternalScanner:
       type: 'boolean'
-      default: true
+      default: false
       description: 'Use internal scanner for .babelrc files'
       order: 14
     stopAtProjectDirectory:


CoffeeLint

CoffeeLint

--- src/scripts/hound.coffee
+++ src/scripts/hound.coffee
@@ -28,9 +28,11 @@
   houndMessage = (mode) ->
     message = ""
     if mode is 'in'
-      message += strings.punchin[Math.floor(Math.random() * strings.punchin.length)]
+      message += strings.punchin[Math.floor(Math.random() *
+                                 strings.punchin.length)]
     else if mode is 'out'
-      message += strings.punchout[Math.floor(Math.random() * strings.punchout.length)]
+      message += strings.punchout[Math.floor(Math.random() *
+                                  strings.punchout.length)]
     if (Math.floor(Math.random() * 6) + 1) == 1
       message += strings.annoying
     return message
@@ -45,7 +47,8 @@

     user = Organization.getUserBySlackName slackuser.name
     if not user
-      Logger.debug "#{slackuser.name} couldn't be found while attempting to hound"
+      Logger.debug "#{slackuser.name} couldn't be found while attempting
+                    to hound"
       return

     if user.settings.shouldHound and user.settings.houndFrequency > 0
@@ -70,7 +73,8 @@
       lastPing = user.settings?.lastPing || now
       timeSinceStart = +Math.abs(now.diff(start, 'hours', true)).toFixed(2) || 0
       timeSinceEnd = +Math.abs(now.diff(end, 'hours', true)).toFixed(2) || 0
-      timeSinceLastPunch = now.diff(lastPunch?.times.slice(-1)[0], 'hours', true) || 0
+      timeSinceLastPunch =
+        now.diff(lastPunch?.times.slice(-1)[0], 'hours', true) || 0
       timeSinceLastMessage = user
                              .settings?.lastMessage
                              .time.diff(last.time, 'hours', true) || 0
@@ -89,40 +93,50 @@
                     houndFrequency: #{user.settings.houndFrequency}"

       if user.salary and
-         (timeSinceLastPing == 0 or timeSinceLastPing >= user.settings.houndFrequency) and
+         (timeSinceLastPing == 0 or
+          timeSinceLastPing >= user.settings.houndFrequency) and
          timeSinceLastPunch > 0.25
         if not lastPunch and not user.isInactive() and not passive
-          Logger.debug "Considering hounding #{user.slack} because of missing lastPunch during active period"
+          Logger.debug "Considering hounding #{user.slack} because of missing
+                        lastPunch during active period"
           if now.isAfter(start) and timeSinceStart >= 0.5
             user.hound houndMessage('in'), Logger
           else if now.isAfter(end) and timeSinceEnd >= 0.5
             user.hound houndMessage('out'), Logger
         else if lastPunch.mode is 'in' and user.isInactive()
-          Logger.debug "Considering hounding #{user.slack} because lastPunch is in and it's outside of their active period"
+          Logger.debug "Considering hounding #{user.slack} because lastPunch is
+                        in and it's outside of their active period"
           if now.isAfter(end) and timeSinceEnd >= 0.5
             user.hound houndMessage('out'), Logger
         else if lastPunch.mode is 'out' and not passive
-          Logger.debug "Considering hounding #{user.slack} because lastPunch is out during active period"
+          Logger.debug "Considering hounding #{user.slack} because lastPunch is
+                        out during active period"
           if not user.isInactive() and timeSinceStart >= 0.5
             user.hound houndMessage('in'), Logger
         else if lastPunch.mode is 'vacation' or
                 lastPunch.mode is 'sick' or
                 lastPunch.mode is 'unpaid'
-          Logger.debug "Considering hounding #{user.slack} because lastPunch is special"
-          if lastPunch.times.length > 0 and not now.isBetween(lastPunch.times[0], lastPunch.times[1]) and not passive
+          Logger.debug "Considering hounding #{user.slack} because lastPunch is
+                        special"
+          if lastPunch.times.length > 0 and
+             not now.isBetween(lastPunch.times[0], lastPunch.times[1]) and
+             not passive
             user.hound houndMessage('in'), Logger
           else if lastPunch.times.block? and not passive
-            endOfBlock = moment(lastPunch.date).add(lastPunch.times.block, 'hours')
+            endOfBlock =
+              moment(lastPunch.date).add(lastPunch.times.block, 'hours')
             if not now.isBetween(lastPunch.date, endOfBlock)
               user.hound houndMessage('in'), Logger
       else if user.salary and timeSinceLastPunch <= 0.25
         Logger.debug "#{user.slack} is safe from hounding as they punched
                       #{timeSinceLastPunch.toFixed(2)} hours ago"
       else if not user.salary and
-              (timeSinceLastPing == 0 or timeSinceLastPing >= user.settings.houndFrequency) and
+              (timeSinceLastPing == 0 or
+               timeSinceLastPing >= user.settings.houndFrequency) and
               timeSinceLastPunch > 0.25
         # Ping part-timers when their shift is longer than their houndFrequency
-        if lastPunch and lastPunch.mode is 'in' and timeSinceLastPunch > user.settings.houndFrequency
+        if lastPunch and lastPunch.mode is 'in' and
+           timeSinceLastPunch > user.settings.houndFrequency
           user.hound houndMessage('out'), Logger
       else
         Logger.debug "#{user.slack} is safe from hounding for another
@@ -130,7 +144,8 @@

   robot.adapter.client.on 'user_typing', (res) ->
     user = robot.adapter.client.rtm.dataStore.getUserById res.user
-    channel = robot.adapter.client.rtm.dataStore.getChannelGroupOrDMById res.channel
+    channel =
+      robot.adapter.client.rtm.dataStore.getChannelGroupOrDMById res.channel
     if not channel?.name
       channel = { private: true, name: 'DM' }
     hound user, channel, false
@@ -195,7 +210,8 @@
           houndFrequency: block
         }
         user.updateRow()
-        user.directMessage "Hounding frequency set to be every #{block} hours during your active time.", Logger
+        user.directMessage "Hounding frequency set to be every #{block} hours
+                            during your active time.", Logger
         Logger.addReaction 'dog2', res.message
       else if action is 'start' or action is 'on' or action is 'enable'
         user.settings.fromSettings {
@@ -213,7 +229,8 @@
           houndFrequency: -1
         }
         user.updateRow()
-        user.directMessage "Hounding is now *off*. You will not be hounded until you turn this setting back on.", Logger
+        user.directMessage "Hounding is now *off*. You will not be hounded until
+                            you turn this setting back on.", Logger
         Logger.addReaction 'dog2', res.message
       else if action is 'pause'
         if user.settings?.houndFrequency > -1 and user.settings?.shouldHound
@@ -222,10 +239,12 @@
             shouldResetHound: true
           }
           user.updateRow()
-          user.directMessage "Hounding is now *paused*. Hounding will resume tomorrow.", Logger
+          user.directMessage "Hounding is now *paused*. Hounding will resume
+                              tomorrow.", Logger
           Logger.addReaction 'dog2', res.message
         else
-          user.directMessage "Hounding is not enabled, so you cannot pause it.", Logger
+          user.directMessage "Hounding is not enabled, so you cannot pause it.",
+                             Logger
           Logger.addReaction 'x', res.message
       else if action is 'reset'
         user.settings.fromSettings {
@@ -234,17 +253,21 @@
           houndFrequency: Organization.houndFrequency
         }
         user.updateRow()
-        user.directMessage "Reset your hounding status to organization defaults *(#{Organization.houndFrequency} hours)*.", Logger
+        user.directMessage "Reset your hounding status to organization defaults
+                            *(#{Organization.houndFrequency} hours)*.", Logger
         Logger.addReaction 'dog2', res.message
       else if action is 'status' or action is 'info'
         status = if user.settings.shouldHound then 'on' else 'off'
         status = if user.settings.shouldResetHound then status else 'disabled'
         if status is 'on'
-          status += ", and is set to ping every *#{user.settings.houndFrequency} hours* while active"
+          status += ", and is set to ping every *#{user.settings.houndFrequency}
+                     hours* while active"
         user.directMessage "Hounding is #{status}.", Logger
         Logger.addReaction 'dog2', res.message
       else
-        user.directMessage "I couldn't understand you. Try something like `hound (self/org) (on/off/pause/reset/status/X hours)`", Logger
+        user.directMessage "I couldn't understand you. Try something like
+                            `hound (self/org)
+                            (on/off/pause/reset/status/X hours)`", Logger
         Logger.addReaction 'x', res.message
     else if scope is 'org'
       if not Organization.ready()
@@ -254,7 +277,9 @@
         block_str = match[0].replace('hours', '').replace('hour', '').trimRight()
         block = parseFloat block_str
         Organization.setHoundFrequency(+block.toFixed(2))
-        user.directMessage "Hounding frequency set to every #{block} hours for #{Organization.name}, time until next hound reset.", Logger
+        user.directMessage "Hounding frequency set to every #{block} hours for
+                            #{Organization.name}, time until next hound reset.",
+                            Logger
         Logger.addReaction 'dog2', res.message
       else if action is 'start' or action is 'enable' or action is 'on'
         Organization.shouldHound = true
@@ -267,29 +292,38 @@
         Organization.shouldHound = false
         Organization.shouldResetHound = false
         Organization.setShouldHound false
-        user.directMessage "Hounding is now *off* for the organization. Hounding status will not reset until it is reactivated.", Logger
+        user.directMessage "Hounding is now *off* for the organization. Hounding
+                            status will not reset until it is reactivated.",
+                            Logger
         Logger.addReaction 'dog2', res.message
       else if action is 'pause'
         Organization.shouldHound = false
         Organization.shouldResetHound = true
         Organization.setShouldHound false
-        user.directMessage "Hounding is now *paused* for the organization. Hounding will resume tomorrow.", Logger
+        user.directMessage "Hounding is now *paused* for the organization.
+                            Hounding will resume tomorrow.", Logger
         Logger.addReaction 'dog2', res.message
       else if action is 'reset'
         Organization.resetHounding()
-        user.directMessage "Reset hounding status for all #{Organization.name} employees.", Logger
+        user.directMessage "Reset hounding status for all #{Organization.name}
+                            employees.", Logger
         Logger.addReaction 'dog2', res.message
       else if action is 'status' or action is 'info'
         status = if Organization.shouldHound then 'on' else 'off'
         status = if Organization.shouldResetHound then status else 'disabled'
         if status is 'on'
-          status += ", and is set to ping every #{Organization.houndFrequency} hours while active"
+          status += ", and is set to ping every #{Organization.houndFrequency}
+                     hours while active"
         user.directMessage "Hounding is #{status}.", Logger
         Logger.addReaction 'dog2', res.message
       else
-        user.directMessage "I couldn't understand you. Try something like `hound (self/org) (on/off/pause/reset/status/X hours)`", Logger
+        user.directMessage "I couldn't understand you. Try something like
+                            `hound (self/org)
+                            (on/off/pause/reset/status/X hours)`", Logger
         Logger.addReaction 'x', res.message
     else
       Logger.debug "Hound could not parse #{command}"
-      user.directMessage "I couldn't understand you. Try something like `hound (self/org) (on/off/pause/reset/status/X hours)`", Logger
+      user.directMessage "I couldn't understand you. Try something like
+                          `hound (self/org)
+                          (on/off/pause/reset/status/X hours)`", Logger
       Logger.addReaction 'x', res.message


Connect "View Feedback" link to homework review

Now that #142 is merged we can do this

--- src/components/student-dashboard/homework-row.cjsx
+++ src/components/student-dashboard/homework-row.cjsx
@@ -14,8 +14,12 @@
     event: React.PropTypes.object.isRequired
     courseId: React.PropTypes.any.isRequired

+  contextTypes:
+    router: React.PropTypes.func
+
   viewFeedback: (e) ->
-    alert "TODO: View feedback for task ID: #{@props.event.id} in course ID: #{@props.courseId}"
+    @context.router.transitionTo 'viewTask',
+      {courseId:@props.courseId, id: @props.event.id}
     e.preventDefault() # needed to stop event from propagating the click up to the event click handler

   viewRecovery: (e) ->


Update about command

Update about command

--- scripts/about.coffee
+++ scripts/about.coffee
@@ -14,5 +14,23 @@
 #   Christopher De Cairos

 module.exports = (robot) ->
+
+  about = (msg) ->
+    msg.send """
+      Hello #{msg.user.name}
+
+      This one's designation is: mozbot
+
+      You may command mozbot to do various tasks for you - or to send you animated gifs.
+
+      Execute `mozbot help` to see a complete list of commands.
+
+      To make mozbot smarter, or to suggest features [file an issue on GitHub](https://github.com/cadecairos/mozbot/issues/new)
+      """
+
+
   robot.respond /about/i, (msg) ->
-    msg.send "Hi, I'm mozbot, I do things! You can see what I do by running `mozbot help`. If you'd like to help make me smarter, [click here](https://github.com/cadecairos/mozbot)"+    about msg
+
+  robot.respond /(who|what) are you\??/i, (msg) ->
+    about msg


デバックの為にloginfoを追加

デバックの為にloginfoを追加

--- scripts/line-beacon-listener.coffee
+++ scripts/line-beacon-listener.coffee
@@ -4,12 +4,13 @@
       res.end ""
       return
     data = if req.body.payload? then JSON.parse req.body.payload else req.body
+    robot.logger.info data
     unless data.events?
       res.end ""
       return
     unless data.events[0].beacon?
       res.end ""
       return
-    robot.logger.info data
+    robot.logger.info data.events[0].beacon
     robot.send {room: process.env.ROOM_BOX_NOTIFICATION}, "ビーコンに反応あり 反応の種類#{data.events[0].beacon.type}"
     res.end ""


May be a good idea for the submit button to have some text on it.

--- src/main/webapp/coffeescripts-hidden/load.coffee
+++ src/main/webapp/coffeescripts-hidden/load.coffee
@@ -115,6 +115,7 @@
       .append(
         $("<button />")
           .addClass('email-submission')
+          .text("Submit Email!")
           .click(submitEmail)
       )


Fix #338

Fix #338

--- files.coffee
+++ files.coffee
@@ -976,7 +976,12 @@
           chunkId: Match.Optional Number
         }

-        opts.binData = new Buffer(opts.binData, 'base64') if opts.binData
+        if opts.binData
+          if typeof Buffer.from == 'function'
+            opts.binData = Buffer.from opts.binData, 'base64'
+          else
+            opts.binData = new Buffer opts.binData, 'base64'
+
         _continueUpload = self._continueUpload opts.fileId
         unless _continueUpload
           throw new Meteor.Error 408, 'Can\'t continue upload, session expired. Start upload again.'
@@ -1916,7 +1921,7 @@
       opts =
         file:       @fileData
         fileId:     @fileId
-        chunkSize:  @config.chunkSize
+        chunkSize:  if @config.isBase64 then ((@config.chunkSize  / 4) * 3) else @config.chunkSize
         fileLength: @fileLength
       opts.FSName = @FSName if @FSName isnt @fileId


Fix issue described #126 by @Dubbedge

Fix issue described #126 by @Dubbedge

--- files.coffee
+++ files.coffee
@@ -525,14 +525,14 @@
       if @public and not storagePath
         throw new Meteor.Error 500, "[FilesCollection.#{@collectionName}] \"storagePath\" must be set on \"public\" collections! Note: \"storagePath\" must be equal on be inside of your web/proxy-server (absolute) root."

-      storagePath ?=  "assets/app/uploads/#{@collectionName}"
+      storagePath ?= "assets#{nodePath.sep}app#{nodePath.sep}uploads#{nodePath.sep}#{@collectionName}"
       Object.defineProperty self, 'storagePath', {
         get: ->
           sp = ''
           if _.isString storagePath
             sp = storagePath
           else if _.isFunction storagePath
-            sp = storagePath.call self, "assets/app/uploads/#{self.collectionName}"
+            sp = storagePath.call self, "assets#{nodePath.sep}app#{nodePath.sep}uploads#{nodePath.sep}#{self.collectionName}"

           unless _.isString sp
             throw new Meteor.Error 400, "[FilesCollection.#{self.collectionName}] \"storagePath\" function must return a String!"
@@ -888,7 +888,7 @@
     {extension, extensionWithDot} = @_getExt fileName

     result           = opts.file
-    result.path      = "#{@storagePath}/#{opts.FSName}#{extensionWithDot}"
+    result.path      = "#{@storagePath}#{nodePath.sep}#{opts.FSName}#{extensionWithDot}"
     result.name      = fileName
     result.meta      = opts.file.meta
     result.extension = extension
@@ -1115,7 +1115,7 @@

     self       = @
     opts      ?= {}
-    opts.path  = "#{@storagePath}/#{FSName}#{extensionWithDot}"
+    opts.path  = "#{@storagePath}#{nodePath.sep}#{FSName}#{extensionWithDot}"
     opts.type  = @_getMimeType opts
     opts.meta ?= {}
     opts.size ?= buffer.length
@@ -1179,7 +1179,7 @@

     {extension, extensionWithDot} = @_getExt fileName
     opts.meta ?= {}
-    opts.path  = "#{@storagePath}/#{FSName}#{extensionWithDot}"
+    opts.path  = "#{@storagePath}#{nodePath.sep}#{FSName}#{extensionWithDot}"

     storeResult = (result, callback) ->
       result._id = fileId
@@ -1274,7 +1274,7 @@
           type:         opts.type
           size:         opts.size
           extension:    extension
-          _storagePath: path.replace "/#{fileName}", ''
+          _storagePath: path.replace "#{nodePath.sep}#{fileName}", ''

         result._id = Random.id()


Clicking flag/year should still show menu.

Previously, clicking the flag or scenario end year in the "Settings"
header item would not trigger the drop-down.

--- app/assets/javascripts/common_interface.coffee
+++ app/assets/javascripts/common_interface.coffee
@@ -51,9 +51,9 @@
   setup_menus: =>
     # Expand menus
     #
-    $("a.menu_toggler").click (e) =>
-      e.preventDefault()
-      $t = $(e.target)
+    $("a.menu_toggler").on 'click', (e) =>
+      console.log e
+      $t = $(e.currentTarget)
       is_open = $t.hasClass('menu-open')
       @close_all_menus()
       if !is_open
@@ -66,6 +66,7 @@
         offset = container.width() - 44
         arrow = container.find('.arrow')
         arrow.css('left', offset)
+      e.preventDefault()

     # Close menus when clicking outside them
     #


Add 'All' to the show options

Add 'All' to the show options

--- app/assets/javascripts/data-tables-init.coffee
+++ app/assets/javascripts/data-tables-init.coffee
@@ -37,6 +37,7 @@
     options =
       responsive: true
       order: [[0, "desc"]]
+      lengthMenu: [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]]
       pageLength: 25
       fnFooterCallback: fnFooterCallback
       fnRowCallback: fnRowCallback


Fix overlay next / prev button positiono

On tablet resolution devices, the arrows will be vertically
centered within the image.

--- app/assets/javascripts/photo_overlay.js.coffee
+++ app/assets/javascripts/photo_overlay.js.coffee
@@ -12,6 +12,8 @@
   else
     commentList.height(commentPaneHeight)
     commentList.scrollTop(commentsHeight)
+    image = overlay.find('img')
+    overlay.find('.js-overlay-image-container').height(image.height())

 $(document).on 'overlay:show', '.js-overlay', ->
   setCommentPaneHeight(this)


refactor readability of function(-calls).

--- app/assets/javascripts/plugins/graph.js.coffee
+++ app/assets/javascripts/plugins/graph.js.coffee
@@ -166,13 +166,10 @@
       .data(force.nodes())
       .enter().append("g")
       .attr("class", "node")
-      .attr("data-label", (d) ->
-        d.label)
+      .attr("data-label", (d) -> d.label)

     embedNodeInfo = (node) ->
-      info_list = $('<ul />',
-        id: 'node_info')
-      template = null
+      info_list = $('<ul />', id: 'node_info')
       payload =
         node_url: node_url
         node: node
@@ -182,9 +179,7 @@
       $("div#d3_context").html(info_list)

     embedEdgeInfo = (edge) ->
-      info_list = $('<ul />',
-        id: 'edge_info')
-      template = null
+      info_list = $('<ul />', id: 'edge_info')
       payload =
         edge_url: edge_url
         edge: edge
@@ -220,19 +215,15 @@
       addClass($(node[0][source_index]), 'highlight')
       addClass($(node[0][target_index]), 'highlight')
     )
-    $('path').on('mouseleave', (e) ->
-      removeClass('g.node', 'highlight'))
+    $('path').on('mouseleave', (e) -> removeClass('g.node', 'highlight'))

     node.append("circle").
-      attr("r", (d) ->
-        return 10 if d.is_center
-        7)
+      attr("r", (d) -> if d.is_center then 10 else 7)

     node.append("text").
       attr("x", 12).
       attr("dy", ".35em").
-      text((d) ->
-        nodeDisplayName(d))
+      text(nodeDisplayName)

     calc_offsets = (d) ->
       d.arc_offset_a = randomNumber(55, 15) if d.arc_offset_a == undefined
@@ -247,12 +238,12 @@
         d.pos_offset_b = 1 if (rand % 2) == 0

     tick = ->
-      node.attr("transform", (d) ->
+      node.attr "transform", (d) ->
         if d.is_center
           d.x = width/2
           d.y = height/2
-        "translate(#{d.x},#{d.y})")
-      path.attr("d", (d) ->
+        "translate(#{d.x},#{d.y})"
+      path.attr "d", (d) ->
         dx = d.target.x - d.source.x
         dy = d.target.y - d.source.y
         dr = Math.sqrt(dx*dx + dy*dy)*2
@@ -264,7 +255,7 @@
           "M#{d.source.x},#{d.source.y-0.6}" +
           "A-#{d.arc_offset_a},-#{d.arc_offset_b} 0 " +
           "#{d.pos_offset_a},#{d.pos_offset_b} #{d.target.x-4},#{d.target.y}"
-      )
+
     force.on('tick', tick)

   drawGraph(nodes, edgesForMode(mode))


Add log

Add log

--- src/content/parser.coffee
+++ src/content/parser.coffee
@@ -14,8 +14,8 @@
     $messages = $el.filter('.message').add($el.find('.message'))
     if $messages.length is 0
       $messages = $el.parents '.message'
+      console.log $messages.length
       return if $messages.length is 0
-      # TODO this is fake, make proper container
       root = @tokenizeMessages $messages
       root.filter $el
       return root


Fix for empty string

Fix for empty string

--- plugins/assembla/index.coffee
+++ plugins/assembla/index.coffee
@@ -42,7 +42,7 @@
           cb(new Error("Workspace not found with name '#{config.space}'"))

     validateTags = (space, cb) =>
-      if config.tags?
+      if config.tags
         tagNames = config.tags.split(",")
         @request.get("#{API_BASE_URL}/spaces/#{space.id}/tags.json")
           .timeout(4000)


[samjsharpe] Atom merge-conflicts plugin

--- modules/people/files/samjsharpe/atom/config.cson
+++ modules/people/files/samjsharpe/atom/config.cson
@@ -34,3 +34,4 @@
   "run-in-terminal": {}
   term2:
     colors: {}
+  "merge-conflicts": {}


Use ReactDOM in the course request form

Use ReactDOM in the course request form

--- gems/plugins/sfu_course_form/app/coffeescripts/bundles/course_request_form.coffee
+++ gems/plugins/sfu_course_form/app/coffeescripts/bundles/course_request_form.coffee
@@ -2,7 +2,8 @@
   'jquery'
   'underscore'
   'Backbone'
-  'react'
+  'react',
+  'react-dom'
   'sfu_course_form/compiled/models/User'
   'sfu_course_form/compiled/models/Term'
   'sfu_course_form/compiled/models/Course'
@@ -16,7 +17,7 @@
   'sfu_course_form/compiled/views/sandboxes/SandboxListView',
   'jsx/sfu_copyright_compliance_notice/SFUCopyrightComplianceNotice',
   'jqueryui/autocomplete'
-], ($, _, Backbone, React, User, Term, Course, AmaintTermList, TermList, TermListView, CourseList, CourseListView, SelectableCourseListView, SandboxList, SandboxListView, SFUCopyrightComplianceNotice) ->
+], ($, _, Backbone, React, ReactDOM, User, Term, Course, AmaintTermList, TermList, TermListView, CourseList, CourseListView, SelectableCourseListView, SandboxList, SandboxListView, SFUCopyrightComplianceNotice) ->

   user = {}
   currentUser = {}


Allows config to be accessed via sharify

--- lib/setup_sharify.coffee
+++ lib/setup_sharify.coffee
@@ -63,6 +63,7 @@
   'GALLERY_INSIGHTS_SECTION_ID'
   'ARTSY_EDITORIAL_ID'
   'CONSIGNMENTS_APP_URL'
+  'SAILTHRU_MASTER_LIST'
 ), {
   JS_EXT: if config.NODE_ENV in ["production", "staging"] then \
     ".min.js.cgz" else ".js"


MenuManager is extended

MenuManager is extended

--- src/menu-manager.coffee
+++ src/menu-manager.coffee
@@ -5,7 +5,7 @@
 CSON = require 'season'
 fs = require 'fs-plus'

-# Public: Provides a registry for menu items that you'd like to appear in the
+# Extended: Provides a registry for menu items that you'd like to appear in the
 # application menu.
 #
 # An instance of this class is always available as the `atom.menu` global.


Continue emitting events when setting scroll top in the model

--- src/display-buffer.coffee
+++ src/display-buffer.coffee
@@ -1158,11 +1158,19 @@

   getScrollTop: -> @scrollTop

-  setScrollTop: (@scrollTop) ->
+  setScrollTop: (scrollTop) ->
+    unless scrollTop is @scrollTop
+      @scrollTop = scrollTop
+      @emitter.emit 'did-change-scroll-top', @scrollTop
+    @scrollTop

   getScrollLeft: -> @scrollLeft

-  setScrollLeft: (@scrollLeft) ->
+  setScrollLeft: (scrollLeft) ->
+    unless scrollLeft is @scrollLeft
+      @scrollLeft = scrollLeft
+      @emitter.emit 'did-change-scroll-left', @scrollLeft
+    @scrollLeft

   decorateFold: (fold) ->
     @decorateMarker(fold.marker, type: 'line-number', class: 'folded')


Better length detection

Better length detection

--- app/js/jail_iframe/classes/icon_buttons.coffee
+++ app/js/jail_iframe/classes/icon_buttons.coffee
@@ -131,7 +131,7 @@
     $clonedEl = $(el).clone()
     $clonedEl.find('a').remove() # Strip links

-    textLength = $clonedEl.text().length
+    textLength = $clonedEl.text().replace(/\s+/g, ' ').trim().length
     $clonedEl.remove()

     textLength >= 50


Don't need to map unless the primary key is set

--- src/model/associations/has_many_association.coffee
+++ src/model/associations/has_many_association.coffee
@@ -57,10 +57,10 @@
         else
           if newChildren.length > 0
             record = newChildren.shift()
-            recordsToMap.push(record)
+            recordsToMap.push(record) if id?
           else
             record = new relatedModel
-            recordsToMap.push(record)
+            recordsToMap.push(record) if id?
             recordsToAdd.push(record)

         record._withoutDirtyTracking -> @fromJSON jsonObject


Returned code smell to existential operators in Punch

--- src/models/punch.coffee
+++ src/models/punch.coffee
@@ -27,7 +27,7 @@
     [times, command] = _parseTime command, start, end
     [dates, command] = _parseDate command

-    tz = timezone ? user.timetable.timezone.name
+    tz = timezone || user.timetable.timezone.name
     datetimes = []
     if dates.length is 0 and times.length is 0
       datetimes.push(moment.tz(tz))
@@ -191,9 +191,9 @@
     headers = HEADERS.rawdata
     today = moment.tz(constants.TIMEZONE)
     row = @row ? {}
-    row[headers.id] = row[headers.id] ? uuid.v1()
-    row[headers.today] = row[headers.today] ? @date.format('MM/DD/YYYY')
-    row[headers.name] = row[headers.name] ? name
+    row[headers.id] = row[headers.id] || uuid.v1()
+    row[headers.today] = row[headers.today] || @date.format('MM/DD/YYYY')
+    row[headers.name] = row[headers.name] || name
     if @times.block?
       block = @times.block
       hours = Math.floor block
@@ -310,8 +310,8 @@
 _parseMode = (command) ->
   comps = command.split ' '
   [mode, command] = [comps.shift(), comps.join ' ']
-  mode = (mode ? '').trim()
-  command = (command ? '').trim()
+  mode = (mode || '').trim()
+  command = (command || '').trim()
   if mode in MODES
     [mode, command]
   else
@@ -319,7 +319,7 @@

 _parseTime = (command, activeStart, activeEnd) ->
   # parse time component
-  command = command.trimLeft() ? ''
+  command = command.trimLeft() || ''
   activeTime = (activeEnd.diff(activeStart, 'hours', true).toFixed(2))
   time = []
   if match = command.match REGEX.rel_time
@@ -356,7 +356,7 @@
   [time, command]

 _parseDate = (command) ->
-  command = command.trimLeft() ? ''
+  command = command.trimLeft() || ''
   date = []
   if match = command.match /today/i
     date.push moment()
@@ -433,7 +433,7 @@

 _parseProjects = (command) ->
   projects = []
-  command = command.trimLeft() ? ''
+  command = command.trimLeft() || ''
   command_copy = command.split(' ').slice()

   for word in command_copy


Ensure ContextPanel is visible when updating offsets

--- src/coffee/cilantro/ui/workflows/results.coffee
+++ src/coffee/cilantro/ui/workflows/results.coffee
@@ -99,6 +99,9 @@
             @updateContextPanelOffsets()

         updateContextPanelOffsets: =>
+            if not @isClosed? or @isClosed
+                return
+
             # Find the bounds of the results workflow to properly fix the
             # position of the context/filter panel.
             @workflowTopOffset = @$el.position().top


Check that the results workspace is open before trying to hide overlay

--- src/coffee/cilantro/ui/workflows/results.coffee
+++ src/coffee/cilantro/ui/workflows/results.coffee
@@ -135,7 +135,8 @@
                 @ui.loadingOverlay.show()

         hideLoadingOverlay: =>
-            @ui.loadingOverlay.hide()
+            if @isClosed? and not @isClosed
+                @ui.loadingOverlay.hide()

         onWindowResize: =>
             @updateContextPanelOffsets()


Fix JS parsing for StudentSolutionsView

Fix JS parsing for StudentSolutionsView

Also, show 40 one-off solutions, for those cases where there are few matches

--- app/views/artisans/StudentSolutionsView.coffee
+++ app/views/artisans/StudentSolutionsView.coffee
@@ -11,6 +11,7 @@
 utils = require 'core/utils'
 Aether = require 'aether'
 require 'vendor/aether-python'
+require 'esper'

 unless typeof esper is 'undefined'
   realm = new esper().realm
@@ -71,8 +72,10 @@
         @solutions[hash] ?= []
         @solutions[hash].push session

+      oneOffs = 0
       tallyFn = (result, value, key) =>
-        return result if value is 1
+        return result if value is 1 and oneOffs > 40
+        oneOffs += 1 if value is 1
         result[value] ?= []
         result[value].push key
         result


Fix /artisans/student-solutions broken Aether import

--- app/views/artisans/StudentSolutionsView.coffee
+++ app/views/artisans/StudentSolutionsView.coffee
@@ -10,9 +10,7 @@
 LevelSessions = require 'collections/LevelSessions'
 ace = require('lib/aceContainer')
 aceUtils = require 'core/aceUtils'
-# Aether = require 'aether'
-# require 'aether/parsers/python'
-# require 'esper.js'
+{createAetherOptions} = require 'lib/aether_utils'

 unless typeof esper is 'undefined'
   realm = new esper().realm


:art: Render message when attached, not created

--- lib/views/message.coffee
+++ lib/views/message.coffee
@@ -1,38 +1,38 @@
 # This file is imported in views/panel

 class Message extends HTMLElement
-  initialize: (message, addPath) ->
-    @message = message
+  initialize: (@message, @addPath)->

+  attachedCallback: ->
     # The ribbon
     ribbon = document.createElement 'span'
     ribbon.classList.add 'badge'
     ribbon.classList.add 'badge-flexible'
-    ribbon.classList.add 'badge-' + message.type.toLowerCase()
-    ribbon.textContent = message.type
+    ribbon.classList.add "badge-#{@message.type.toLowerCase()}"
+    ribbon.textContent = @message.type

     # The message
     theMessage = document.createElement('span')
-    if message.html and message.html.length
+    if @message.html and @message.html.length
       theMessage.innerHTML = message.html
     else
-      theMessage.textContent = message.message
+      theMessage.textContent = @message.message

     # The link
-    if message.file
-      message.displayFile = message.file
+    if @message.file
+      @message.displayFile = @message.file
       try
-        atom.project.getPaths().forEach (path) ->
-          return unless message.file.indexOf(path) is 0
-          message.displayFile = message.file.substr( path.length + 1 ) # Remove the trailing slash as well
+        atom.project.getPaths().forEach (path) =>
+          return unless @message.file.indexOf(path) is 0
+          @message.displayFile = @message.file.substr( path.length + 1 ) # Remove the trailing slash as well
           throw null
       file = document.createElement 'a'
-      file.addEventListener 'click', @onClick.bind(this, message.file, message.position)
-      if message.position
+      file.addEventListener 'click', @onClick.bind(this, @message.file, @message.position)
+      if @message.position
         file.textContent =
-          'at line ' + message.position[0][0] + ' col ' + message.position[0][1] + ' '
-      if addPath
-        file.textContent += 'in ' + message.displayFile
+          'at line ' + @message.position[0][0] + ' col ' + @message.position[0][1] + ' '
+      if @addPath
+        file.textContent += 'in ' + @message.displayFile
     else
       file = null
     @appendChild ribbon


fix the bug from #1132 so that new assignments dont trip up the get original

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -48,13 +48,13 @@
   _server_copy: {}

   _loaded: (obj, planId) ->
-    @_server_copy[planId] = JSON.stringify(obj)
+    @_server_copy[planId] = JSON.stringify(obj or {})
     obj

   # Somewhere, the local copy gets taken apart and rebuilt.
   # Keep a copy of what was served.
   _getOriginal: (planId) ->
-    JSON.parse(@_server_copy[planId])
+    JSON.parse(@_server_copy[planId] or "{}")

   _getPlan: (planId) ->
     @_local[planId] ?= {}


Set 'changed' property on models modified by aloha

--- src/scripts/helpers/backbone/views/editable.coffee
+++ src/scripts/helpers/backbone/views/editable.coffee
@@ -89,6 +89,7 @@
                     editableBody = editableBody.trim() # Trim for idempotence
                     # Change the contents but do not update the Aloha editable area
                     @model.set(value, editableBody) # TODO: Should we add a flag to not re-render the editable?
+                    setChanged(options.onEdit)

             # Setup Select2
             when 'select2'


Clean up code comments

Clean up code comments

--- src/scripts/models/accountSwitcherViewModel.coffee
+++ src/scripts/models/accountSwitcherViewModel.coffee
@@ -1,19 +1,3 @@
-# TODO:
-# - match API shape
-# - will disabled accounts be passed to us?
-# - when searching select the first one so you can type alias and press enter
-
-# DONE:
-# - rename to account switcher :(
-# - animation and the scroll-area are off
-# - arrow keys with search
-# - iphone layout
-# - keydown events on mobile
-# - textbox on mobile causes a zoom font-sizes greater that 16 dont cause this :)
-# - remove monkey class
-# - textbox style
-# - logic in scrollTo for detecting if it is offScreen is wrong
-
 class Account
   constructor: (options) ->
     @rawAcct = options.rawAcct


Use a way cooler pattern

Use a way cooler pattern

--- src/streak-podium.coffee
+++ src/streak-podium.coffee
@@ -21,7 +21,7 @@
 _ = require("underscore")

 module.exports = (robot) ->
-  robot.hear /ladder/i, (res) ->
+  robot.hear /#streaklife|commit streak/i, (res) ->
     orgLogin = process.env.HUBOT_ORG_LOGIN
     access_token = process.env.HUBOT_ORG_ACCESS_TOKEN
     unless access_token && orgLogin?


change keycap for pair any to indicating "S'urround.

--- keymaps/vim-mode.cson
+++ keymaps/vim-mode.cson
@@ -197,7 +197,7 @@
 'atom-text-editor.vim-mode.operator-pending-mode, atom-text-editor.vim-mode.visual-mode':
   'i w':  'vim-mode:inner-word'
   'i W':  'vim-mode:inner-whole-word'
-  'i ;':  'vim-mode:inner-pair-any'
+  'i s':  'vim-mode:inner-pair-any'
   'i "':  'vim-mode:inner-double-quotes'
   'i \'': 'vim-mode:inner-single-quotes'
   'i `':  'vim-mode:inner-back-ticks'
@@ -219,7 +219,7 @@
   'i e':  'vim-mode:inner-entire'
   'a w':  'vim-mode:a-word'
   'a W':  'vim-mode:a-whole-word'
-  'a ;':  'vim-mode:a-pair-any'
+  'a s':  'vim-mode:a-pair-any'
   'a "':  'vim-mode:a-double-quotes'
   'a \'': 'vim-mode:a-single-quotes'
   'a `':  'vim-mode:a-back-ticks'


Fix returning items.

Fix returning items.

--- kirppu/static_src/js/checkout/vendorcheckoutmode.coffee
+++ kirppu/static_src/js/checkout/vendorcheckoutmode.coffee
@@ -149,7 +149,7 @@

     # Move just returned item to "Last returned item" part from "remaining items" list.
     returnable_item = $('#' + item.code, @remainingItems.body)
-    if returnable_item.size() == 0
+    if returnable_item.length == 0
       # Item was not in "remaining" list, but it was still returned. (Transition from state AD.)
       console.warn("Item not found in list of remaining items: " + item.code)
     else


Add a unit test for the `preDoPassportLogin` module hook.

--- services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee
+++ services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee
@@ -28,7 +28,7 @@
 				trackSession: sinon.stub()
 				untrackSession: sinon.stub()
 				revokeAllUserSessions: sinon.stub().callsArgWith(1, null)
-			"../../infrastructure/Modules": {hooks: {fire: sinon.stub().callsArgWith(2, null, [])}}
+			"../../infrastructure/Modules": @Modules = {hooks: {fire: sinon.stub().callsArgWith(2, null, [])}}
 		@user =
 			_id: ObjectId()
 			email: @email = "USER@example.com"
@@ -214,6 +214,7 @@
 		beforeEach ->
 			@AuthenticationController._recordFailedLogin = sinon.stub()
 			@AuthenticationController._recordSuccessfulLogin = sinon.stub()
+			@Modules.hooks.fire = sinon.stub().callsArgWith(2, null, [])
 			# @AuthenticationController.establishUserSession = sinon.stub().callsArg(2)
 			@req.body =
 				email: @email
@@ -221,6 +222,17 @@
 				session:
 					postLoginRedirect: "/path/to/redir/to"
 			@cb = sinon.stub()
+
+		describe "when the preDoPassportLogin hooks produce an info object", ->
+			beforeEach ->
+				@Modules.hooks.fire = sinon.stub().callsArgWith(2, null, [null, {redir: '/somewhere'}, null])
+
+			it "should stop early and call done with this info object", (done) ->
+				@AuthenticationController.doPassportLogin(@req, @req.body.email, @req.body.password, @cb)
+				@cb.callCount.should.equal 1
+				@cb.calledWith(null, false, {redir: '/somewhere'}).should.equal true
+				@LoginRateLimiter.processLoginRequest.callCount.should.equal 0
+				done()

 		describe "when the users rate limit", ->


Add license override for cheerio@0.15.0

Add license override for cheerio@0.15.0

Newer versions of the library have the license added, but this is easier
than upgrading for now.

--- build/tasks/license-overrides.coffee
+++ build/tasks/license-overrides.coffee
@@ -35,6 +35,10 @@

       MIT/X11
     """
+  'cheerio@0.15.0':
+    repository: "https://github.com/cheeriojs/cheerio"
+    license: 'MIT'
+    source: 'https://github.com/cheeriojs/cheerio/blob/master/package.json'
   'specificity@0.1.3':
     repository: 'https://github.com/keeganstreet/specificity'
     license: 'MIT'


Add HTTPS capability by changing route matcher from "http" to "https?"

--- knockout.model.coffee
+++ knockout.model.coffee
@@ -63,7 +63,7 @@

     # creates an action with HTTP POST
     doPost: (routeName,params = {},callback = null,type = "json") ->
-        if routeName.match(/^http:\/\//) is null
+        if routeName.match(/^https?:\/\//) is null
             url = @__urls[routeName]
         else
             url = routeName
@@ -71,7 +71,7 @@

     # creates an action with HTTP GET
     doGet: (routeName,params = {},callback = null,type = "json") ->
-        if routeName.match(/^http:\/\//) is null
+        if routeName.match(/^https?:\/\//) is null
             url = @__urls[routeName]
         else
             url = routeName
@@ -79,7 +79,7 @@

     # (static) creates an action with HTTP POST
     @doPost: (routeName,params = {},callback = null,type = "json") ->
-        if routeName.match(/^http:\/\//) is null
+        if routeName.match(/^https?:\/\//) is null
             url = @__parse_url(@__urls[routeName],params)
         else
             url = @__parse_url(routeName,params)
@@ -93,7 +93,7 @@

     # (static) creates an action with HTTP GET
     @doGet: (routeName,params = {},callback = null,type = "json") ->
-        if routeName.match(/^http:\/\//) is null
+        if routeName.match(/^https?:\/\//) is null
             url = @__parse_url(@__urls[routeName],params)
         else
             url = @__parse_url(routeName,params)


Add getter for loaded themes

Add getter for loaded themes

--- src/theme-manager.coffee
+++ src/theme-manager.coffee
@@ -28,6 +28,10 @@
   # Internal-only:
   getActiveThemes: ->
     _.clone(@activeThemes)
+
+  # Internal-only:
+  getLoadedThemes: ->
+    _.clone(@loadedThemes)

   # Internal-only:
   unload: ->


Only update view when path changes

Only update view when path changes

--- lib/archive-view.coffee
+++ lib/archive-view.coffee
@@ -17,9 +17,11 @@

     @setModel(editSession)

-  setPath: (@path) ->
-    return unless @path?
+  setPath: (path) ->
+    return unless path?
+    return if @path is path

+    @path = path
     @tree.hide()
     @loadingMessage.show()
     archive.list @path, tree: true, (error, entries) =>


Onboarding: Passed deferPositioning flag for onboaring item context menu.

--- client/Main/onboarding/onboardingitemview.coffee
+++ client/Main/onboarding/onboardingitemview.coffee
@@ -32,17 +32,18 @@
       console.warn "Path parse error for onboarding item", { appName, itemName, e }

   createContextMenu: ->
-    @contextMenu   = new JContextMenu
-      cssClass     : "onboarding-wrapper"
-      sticky       : yes
-      arrow        :
-        placement  : "top"
-      menuMaxWidth : 500
-      menuWidth    : 500
-      delegate     : @parentElement
-      x            : @parentElement.getX() - 20
-      y            : @parentElement.getY() + 40
-    , customView   : @createContentView()
+    @contextMenu       = new JContextMenu
+      cssClass         : "onboarding-wrapper"
+      sticky           : yes
+      arrow            :
+        placement      : "top"
+      menuMaxWidth     : 500
+      menuWidth        : 500
+      deferPositioning : yes
+      delegate         : @parentElement
+      x                : @parentElement.getX() - 20
+      y                : @parentElement.getY() + 40
+    , customView       : @createContentView()

     @contextMenu.on "viewAppended", =>
       KD.utils.defer =>


preparing to deploy the router; remove a log

--- client/landing/app/CommonViews/Navigation.coffee
+++ client/landing/app/CommonViews/Navigation.coffee
@@ -4,7 +4,6 @@
     previousSelection = @selectedItems.slice()
     @removeAllItems()
     @instantiateListItems @getData().items
-    console.log previousSelection
     @selectItemByName name  for {name} in previousSelection

   selectItemByName:(name)->


Fix DoccoFileProcessor lint

Fix DoccoFileProcessor lint

--- src/commands/docco.cmd/docco_file_processor.coffee
+++ src/commands/docco.cmd/docco_file_processor.coffee
@@ -4,6 +4,7 @@
 Neat = require '../../neat'
 DoccoPreProcessor = require './docco_pre_processor'
 DoccoTitleProcessor = require './docco_title_processor'
+SHOWDOWN = "#{Neat.neatRoot}/node_modules/docco/vendor/showdown"

 {puts, error, missing} = Neat.require 'utils/logs'
 {render} = Neat.require 'utils/templates'
@@ -12,7 +13,7 @@

 try
   {parse} = require 'docco'
-  {Showdown:showdown} = require "#{Neat.neatRoot}/node_modules/docco/vendor/showdown"
+  {Showdown:showdown} = require SHOWDOWN
 catch e
   return error _('neat.commands.docco.missing_module',
                   missing: missing 'docco')


Storable mixin minor refactoring

Storable mixin minor refactoring

--- javascripts/coffeescripts/mixins/storable.js.coffee
+++ javascripts/coffeescripts/mixins/storable.js.coffee
@@ -6,9 +6,9 @@

     if cssData = localStorage.getItem(@id)
       $el.css JSON.parse(cssData)
-    else
-      $el.css css if css?
-
+    else if css?
+      $el.css css
+
   saveCss: (el = @el) ->
     return unless @id

@@ -16,8 +16,8 @@

     @savableCss = ['top', 'left', 'display', 'opacity'] unless @savableCss
     cssData = {}
-
+
     for cssProperty in @savableCss
       cssData[cssProperty] = $o(el).css cssProperty
-
+
     localStorage.setItem @id, JSON.stringify(cssData)


fixing tests

fixing tests

--- javascripts/lib/math/expression_manipulation.coffee
+++ javascripts/lib/math/expression_manipulation.coffee
@@ -158,7 +158,6 @@

         result_exp = _ExpressionManipulator.build(expression_position.expression()).clone().
           withComponent(expression_position, (component)=>
-
             _ImplicitMultiplication.build(@comps).invokeD(component)
             component.appendD(@comps.build_exponentiation(base: base, power: power))
           ).value()
@@ -174,15 +173,18 @@
         @position = @pos.build(new_exp.last().id())
         new_exp

-      perform: (expression, position)->
-        if expression.isEmpty()
-          expression: expression
-          position: position
-        else
-          expr = _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-            performOrDefault((expr)=> @appendAction(expr))
-          expression: expr
-          position: @position
+      perform: (expression_position)->
+        expr = expression_position.expression()
+        return expression_position if expr.isEmpty()
+
+        result_exp = _ExpressionManipulator.build(expr).clone().
+          withComponent(expression_position, (component)=>
+            _OverrideIfOperatorOrAppend.build(@comps, component).withD(
+              @comps.build_multiplication()
+            )
+          ).value()
+
+        expression_position.clone(expression: result_exp)

     class_mixer(AppendMultiplication)

@@ -398,18 +400,30 @@

     class _OverrideIfOperatorOrAppend
       initialize: (@comps, @expression)->
-      with: (operator)->
+      with: (operator, append_method="append", replace_method="replaceLast")->
         last = @expression.last()
-        if last && last.isOperator()
-          if last instanceof @comps.classes.exponentiation
-            if last.power().isBlank()
-              @expression.replaceLast(operator)
+
+        action =
+          if last
+            if last.isOperator()
+              if last instanceof @comps.classes.exponentiation
+                "append"
+              else
+                "replace"
             else
-              @expression.append(operator)
+              "append"
           else
-            @expression.replaceLast(operator)
-        else
-          @expression.append(operator)
+            "append"
+
+        if action == "append"
+          @expression[append_method](operator)
+        else if action == "replace"
+          @expression[replace_method](operator)
+
+      # @destructive
+      withD: (operator)->
+        @with(operator, "appendD", "replaceLastD")
+
     class_mixer(_OverrideIfOperatorOrAppend)


Some Refactoring

Some Refactoring

Hopefully fixes #10

--- lib/caniuse-view.coffee
+++ lib/caniuse-view.coffee
@@ -97,41 +97,25 @@
         .forEach (key) =>
           agent = @data.agents[key]
           version = agent.versions[agent.versions.length - i] or ''
-
           support = String(item.stats[key][version])
-
           td = $('<td>').text(version)

-          # console.log(support, item.notes_by_num)
           hasNote = support.match(/\s#(\d+)$/)
           if hasNote
             td.append $('<span>').text(hasNote[1])
             support = support.replace /\s#(\d+)$/g, ''
             needNotes.push hasNote[1]

-          # hasInfo = support.match(/\s([a-z]$)$/)
-          # if hasInfo
-          #   support = support.replace /\s[a-z]$/g, ''
-          #   info = infos[hasInfo[1]]
-          #   console.log hasInfo[1], info
-
-          # support = support.replace /\s.*/g, ''
-
           supportKeys = support.split(/\s/g)
-
-          if version
-            td.addClass('is-unsupported')

           supportKeys
             .forEach (supportKey) ->
               # y - (Y)es, supported by default
               if supportKey is 'y'
-                td.removeClass('is-unsupported')
                 td.addClass('is-supported')

               # a - (A)lmost supported (aka Partial support)
               else if supportKey is 'a'
-                td.removeClass('is-unsupported')
                 td.addClass('is-almost-supported')

               else if supportKey is 'x'
@@ -142,6 +126,9 @@
               # p - No support, but has (P)olyfill
               # n - (N)o support, or disabled by default
               # d - (D)isabled by default (need to enable flag or something)
+
+          if version and not td.attr('class')
+            td.addClass('is-unsupported')

           tr.append(td)

@@ -171,13 +158,9 @@

   populate: ->
     if @loadData()
-      rows = Object.keys(@data.data)
-        .map (key) =>
-          row = @data.data[key]
-          row.key = key
-          return row
-
-      @setItems rows
+      @setItems(Object.keys(@data.data)
+        .map (key) => $.extend({ key }, @data.data[key])
+        .sort (a, b) -> a.title.localeCompare(b.title))

   show: ->
     @populate()


updated cc-dash spec

updated cc-dash spec

--- test/components/cc-dashboard/dashboard.spec.coffee
+++ test/components/cc-dashboard/dashboard.spec.coffee
@@ -100,7 +100,7 @@
             completed_percentage: 1.10

       Testing.renderComponent(SectionProgress, options).then ({dom}) ->
-        expect(dom.querySelector('.reading-progress-bar').attributes.label.value).to.equal('100% completed')
+        expect(dom.querySelector('.reading-progress-bar span span').innerHTML).to.equal('100% completed')

     it 'hides complete progress bar when 0% complete', ->
       options =
@@ -120,7 +120,7 @@
           performance: 1.0

       Testing.renderComponent(SectionPerformance, options).then ({dom}) ->
-        expect(dom.querySelector('.progress-bar-correct').attributes.label.value).to.be.equal('100% correct')
+        expect(dom.querySelector('.progress-bar-correct span span').innerHTML).to.be.equal('100% correct')
         expect(dom.querySelector('.progress-bar-incorrect')).to.be.null

     it 'hides correct progress bar when all incorrect', ->
@@ -129,7 +129,7 @@
           performance: 0.0

       Testing.renderComponent(SectionPerformance, options).then ({dom}) ->
-        expect(dom.querySelector('.progress-bar-incorrect').attributes.label.value).to.be.equal('100% incorrect')
+        expect(dom.querySelector('.progress-bar-incorrect span span').innerHTML).to.be.equal('100% incorrect')
         expect(dom.querySelector('.progress-bar-correct')).to.be.null


Fixes for JSFX

Fixes for JSFX

--- client/src/app/io.coffee
+++ client/src/app/io.coffee
@@ -567,6 +567,7 @@

           for channelName, channelData of circuitData
             channelMeta = _.findWhere(options.channels, { circuitId: circuitId, name: channelName })
+            if not channelMeta? then throw new Error("Cannot find channel '#{channelName}' for circuit '#{circuitId}'")

             switch channelMeta.type
               when "clip"


Player updating working

Player updating working

--- client/src/app/io.coffee
+++ client/src/app/io.coffee
@@ -679,6 +679,7 @@
     eventQueue = []
     timerId = null
     playerId = null
+    playerInfo = {} # Current state of player

     sendResults = ->
       if eventQueue.length is 0 then return
@@ -704,9 +705,6 @@

     io =
       enterPlaySequence: ->
-        # If metrics session was not created then return
-        if not playerId then return
-
         # Create player
         jqXhr = $.ajax
           url: options.metrics.baseUrl + "/v1/player/"
@@ -735,17 +733,34 @@
       provideData: ->
         global:
           events: []
+          player: playerInfo

       establishData: (ioData) ->
         # Only send events in play sequence
         if not playerId then return

-        # Expecting a format like { events: [ type: "", section: [], coordinates: [], customData: }, ... ] }
-
-        # Collate all data into the events queue (disregard individual circuits)
+        # Contains updated playerInfo if necessary
+        newPlayerInfo = null
+
+        # Expecting a format like { player: {}, events: [ type: "", section: [], coordinates: [], customData: }, ... ] }
         for circuitId in _.pluck(options.circuitMetas, "id")
+          # Collate all data into the events queue (disregard individual circuits)
           eventQueue.push(ioData[circuitId].events...)

+          # Update player info
+          if not _.isEqual(ioData[circuitId].player, playerInfo)
+            newPlayerInfo = ioData[circuitId].player
+
+        # Update player info if necessary
+        if newPlayerInfo
+          jqXhr = $.ajax
+            url: options.metrics.baseUrl + "/v1/player/" + playerId
+            type: "PUT"
+            data: JSON.stringify(newPlayerInfo)
+            processData: false
+            contentType: "application/json"
+          playerInfo = newPlayerInfo
+
         return null # avoid accumulating results

       destroy: -> # NOP


Better Drag/Drop conventions.

Better Drag/Drop conventions.

--- examples/dragdrop.coffee
+++ examples/dragdrop.coffee
@@ -10,7 +10,6 @@
     src: (src) -> src != ''

 ImgView = Draggable View
-  # If you des
   model: Img
   # Cheap to completely mutate on change, no need to describe bindings, just
   # re-render entire view on any model changes.
@@ -34,7 +33,6 @@
     # Various state changes
     'drops:add': (d) ->
       @el.appendChild d.el
-
     'drops:remove': (d) ->
       @el.removeChild d.el

@@ -42,13 +40,14 @@
   # to render/bindings.
   events:
     drop: (d) ->
-      @model.drops.push d
+      @addDrop d
+    dragstart: (d) ->
+      @removeDrop d

   # You should never mutate the DOM manually, just update the model and let
   # render/bindings do the work!
   removeDrop: (idx) ->
     @model.drops.pop idx
-
   addDrop: (d) ->
     @model.drops.push d


cleanup

cleanup

--- src/jquery.kerning.coffee
+++ src/jquery.kerning.coffee
@@ -34,7 +34,6 @@

   $(document).on 'ready', ->
     $(document).find('[data-kerning]').each ->
-      console.log('onREady...')
       # JSON.parseだけだと厳密すぎるのでevalでも評価を試す
       parseJSON = (text)->
         obj = null


Account: Send confirmation email, when user clicks verify now link in account settings

--- client/Account/views/username.coffee
+++ client/Account/views/username.coffee
@@ -256,14 +256,12 @@
         testPath     : "account-email-edit"
         click        : =>
           KD.whoami().fetchFromUser "email", (err, email)=>
-            if err
-              notify err.message
-            else
-              KD.remote.api.JPasswordRecovery.recoverPassword email, (err)=>
-                message = "Email confirmation mail is sent"
-                if err then message = err.message else @verifyEmail.hide()
-
-                notify message
+            return notify err.message if err
+
+            KD.remote.api.JPasswordRecovery.resendVerification nickname, (err)=>
+              @verifyEmail.hide()
+              return KD.showError err if err
+              notify "We've sent you a confirmation mail."

     @addSubView @verifyEmail = new KDCustomHTMLView o


app: fix Reactor import in MainController

--- client/app/lib/maincontroller.coffee
+++ client/app/lib/maincontroller.coffee
@@ -47,7 +47,7 @@
 ShortcutsController      = require './shortcutscontroller'
 MarketingController      = require './marketing/marketingcontroller'
 MachineShareManager      = require './machinesharemanager'
-KodingFluxReactor        = require './flux/reactor'
+KodingFluxReactor        = require './flux/base/reactor'

 module.exports           = class MainController extends KDController


Proxifier: move isInProduction and getProtocol into its separate getters

with this one we will be able to test this method separately

--- client/app/lib/util/proxifier.coffee
+++ client/app/lib/util/proxifier.coffee
@@ -18,10 +18,10 @@
     return callback url  if parser.hostname in ['127.0.0.1', 'dev.kodi.ng']

     # check if running under production environment
-    isInProduction = globals.config.environment is 'production'
+    isInProduction = @isInProduction()

     # get the current protocol
-    { protocol } = global.document.location
+    protocol = @getProtocol()

     # build our new url, example:
     # old: http://54.164.174.218:3000/kite
@@ -77,3 +77,12 @@
     timeout  = 2000

     doXhrRequest { endPoint, type, timeout }, callback
+
+
+  @getProtocol = ->
+    { protocol } = global.document.location
+    return protocol
+
+
+  @isInProduction = ->
+    return globals.config.environment is 'production'


reduce grade

reduce grade

--- atmospheric/002-accumulated-heat/viewer.coffee
+++ atmospheric/002-accumulated-heat/viewer.coffee
@@ -79,7 +79,7 @@
         points
     contour = (positioning, heatdata) ->
         for idx in [1...256]
-            value = 2 * sc / 256 * idx
+            value = sc / 256 * idx
             for row in [0...256]
                 points = find(heatdata[row], value)
                 for col in points


Stringify response body before sending; check for entityId in request.body, not in top-level task

--- lib/service/dataLink.coffee
+++ lib/service/dataLink.coffee
@@ -83,8 +83,6 @@
     return registeredCollections[collectionName]

   initCompletionHandler = (task, callback) ->
-    unless task.originalRequest?
-      task.originalRequest = task.request

     environmentId = task.appMetadata._id

@@ -164,9 +162,12 @@
         unless result.statusCode?
           result.statusCode = 200
         console.log "About to parse entity(s)"
-        if result.statusCode < 400 and entityParser.isKinveyEntity(entity) is false
-          if entity.constructor isnt Array
-            entity = entityParser.entity entity
+        result.body = JSON.stringify entity
+        #if result.statusCode < 400 and entityParser.isKinveyEntity(entity) is false
+
+          #if entity.constructor isnt Array
+          #  entity = entityParser.entity entity
+

         result.continue = true

@@ -207,6 +208,10 @@
     console.log "Generating completion handler"
     completionHandler = initCompletionHandler task, callback

+    try
+      task.request.body = JSON.parse task.request.body
+    catch e
+
     console.log "Checking dataop"
     if task.method is 'POST'
       console.log "DataOp is insert"
@@ -216,7 +221,7 @@
       dataOp = 'onUpdate'
     else if task.method is 'GET' and task.endpoint isnt '_count'
       console.log "DataOp is GET"
-      if task.entityId?
+      if task.request?.entityId?
         console.log "GetById"
         dataOp = 'onGetById'
       else if task.query?


make RouteCommands of Router an empty array by default

--- source/infrastructure/router.coffee
+++ source/infrastructure/router.coffee
@@ -13,12 +13,14 @@

   Aggregate: null
   InitializingCommand: null
+  RouteCommands: null

   constructor: ->
     if not @Aggregate?
       throw new Error Router.ERRORS.aggregateNotSpecified
     if not @InitializingCommand?
       throw new Error Router.ERRORS.missingInitializingCommand
+    @RouteCommands ?= []
     super

   onDependenciesReady: ->


Shorten the enabling and disabling of bindings

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -640,19 +640,11 @@
     @cancelPendingState()
     @clearQueuedKeystrokes()

-    enableBindings = (bindings) ->
-      if bindings?
-        binding.enabled = true for binding in bindings
-
-    disableBindings = (bindings) ->
-      if bindings?
-        binding.enabled = false for binding in bindings
-
-    disableBindings(bindingsToDisable)
+    binding.enabled = false for binding in bindingsToDisable if bindingsToDisable?
     for event in eventsToReplay
       @handleKeyboardEvent(event, true)
       if bindingsToDisable? and not @pendingPartialMatches?
-        enableBindings(bindingsToDisable)
+        binding.enabled = true for binding in bindingsToDisable if bindingsToDisable?
         bindingsToDisable = null

     atom?.assert(not bindingsToDisable?, "Invalid keymap state")


multiline comment in Agda

multiline comment in Agda

--- lib/text-buffer/pure.coffee
+++ lib/text-buffer/pure.coffee
@@ -2,11 +2,11 @@

 #
 #   "Pure" in the sense of not having any interaction with the text buffer
-#   just text transformations only
 #

 # regular expressions
-commentRegex = /(--[^\r\n]*[\r\n])/
+
+commentRegex = /(--[^\r\n]*[\r\n])|(\{-([^-]|[\r\n]|(-+([^-\}]|[\r\n])))*-+\})/
 goalBracketRegex = /(\{![^!\}]*!\})/
 goalQuestionMarkGroupRegex =
     /// (
@@ -67,6 +67,10 @@
         .lex goalQuestionMarkRegex, 'goal ?s', 'goal ?'
         .result

+    test = new Lexer text
+        .lex commentRegex, 'raw', 'comment'
+    console.log test
+
     # tag original positions
     pos = 0
     tokens
@@ -105,7 +109,7 @@

         # reorganize the contents inside the brackets
         obj.modifiedContent = obj.modifiedContent.replace(/\{!.*!\}/, "{! #{content + paddingSpaces} !}").substr(prefix.length)
-
+
         i += 1
         return obj


Rename

Rename

--- lib/unsubscribe-button.cjsx
+++ lib/unsubscribe-button.cjsx
@@ -1,7 +1,7 @@
 {Utils, React, MessageStore} = require 'nylas-exports'
 {RetinaImg} = require 'nylas-component-kit'

-class MyComposerButton extends React.Component
+class UnsubscribeButton extends React.Component

   @displayName: 'UnsubscribeButton'

@@ -47,4 +47,4 @@
     return doc

-module.exports = MyComposerButton
+module.exports = UnsubscribeButton


Add todos for collection names

Add todos for collection names

--- coffee/collection.coffee
+++ coffee/collection.coffee
@@ -5,6 +5,8 @@
 collections = {}

 exports.param = (req, res, next, collection) ->
+  # @TODO url-decode collection
+  # @TODO restricted collections
   if collections[collection]
     req.col = collections[collection]
     return next()


Update default crash report submission URL.

--- common/api/lib/crash-reporter.coffee
+++ common/api/lib/crash-reporter.coffee
@@ -4,9 +4,9 @@
   start: (options={}) ->
     {productName, companyName, submitUrl, autoSubmit, ignoreSystemCrashHandler, extra} = options

-    productName ?= 'Atom-Shell'
+    productName ?= 'atom-shell'
     companyName ?= 'GitHub, Inc'
-    submitUrl ?= 'http://54.249.141.25'
+    submitUrl ?= 'http://54.249.141.25:1127/post'
     autoSubmit ?= true
     ignoreSystemCrashHandler ?= false
     extra ?= {}


Update table_view.coffee

Update table_view.coffee

--- lib/views/table_view.coffee
+++ lib/views/table_view.coffee
@@ -64,7 +64,7 @@
         if not @collection.origModels and @collection.whereAll?
             @collection.pager?()

-        @$el.empty().append("

</table")
+        @$el.empty().append("

")
         @table = @$('table').addClass(@options.table_class)
         @body = @$('tbody')


attempting to improve protected page detection work better

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -368,7 +368,8 @@
 isSpecialPage = (tab) ->
   log.trace()

-  not tab.url.indexOf 'chrome'
+  return yes for protocol in ['chrome', 'view-source'] when not tab.url.indexOf protocol
+  no

 # Determine whether or not `tab` is currently displaying a page on the Chrome Web Store.
 isWebStore = (tab) ->


Finalize wording and UI

Finalize wording and UI

--- src/smc-webapp/r_account.cjsx
+++ src/smc-webapp/r_account.cjsx
@@ -530,8 +530,9 @@
     render : ->
         <Well style={marginTop: '15px', textAlign:'center'}>
             Are you sure you want to do this?

-            You will lose access to all of your projects.

-            To proceed type delete this account below.
+            You will immediately lose access to all of your projects.

+

+            To proceed, type delete this account below.
             <Input
                 autoFocus
                 value       = {@state.confirmation_text}


fixed selectOrCreateTab function

fixed selectOrCreateTab function

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -79,7 +79,7 @@
     footerF.find('button:first-child').attr(
       disabled: 'disabled'
       title:    utils.i18n 'refreshing_title'
-    ).html(utils.i18n 'refreshing_text')
+    ).html utils.i18n 'refreshing_text'
   # Add the clear button if badges are visibile; they can be distracting.
   if updates
     footerF.append(
@@ -117,7 +117,7 @@
     ).appendTo tbody
     footer.find('#refreshLink').remove()
   # Otherwise; let's create a row for each order.
-  for order, i in ext.orders
+  for order in ext.orders
     tbody.append(
       $.prototype.append.apply $('<tr/>'), [
         $.prototype.append.apply $('<td/>'), [
@@ -131,7 +131,7 @@
             text:                order.number
             title:               utils.i18n 'order_title'
         ],
-        $('<td/>').append $ '<span/>', text: getStatusText i
+        $('<td/>').append $ '<span/>', text: getStatusText order
       ]
     )
     # Order had an error; I suppose I should tell the user.
@@ -196,14 +196,14 @@
   encode = encodeURIComponent
   return "#{ORDER_URL}#{encode order.number}/#{encode order.code}"

-# Attempt to derive the status text to be displayed for the indexed order.
+# Attempt to derive the status text to be displayed for the order.
 # The status text will either be that of the latest update or a single
 # whitespace character if no status updates have been detected yet for that
 # order.
-getStatusText = (index) ->
-  length = ext.orders[index].updates.length
-  return ' ' if length is 0
-  return ext.orders[index].updates[length - 1].status
+getStatusText = (order) ->
+  length = order.updates?.length
+  return ' ' unless length
+  return order.updates[length - 1].status

 # Return the total number of detected status updates for all existing orders
 # since the last time badges were cleared.
@@ -301,15 +301,15 @@
 # If no existing tab exists a new one is simply created.
 selectOrCreateTab = (url, callback) ->
   chrome.windows.getCurrent (win) ->
-    chrome.tabs.query
-      active:   yes
-      windowId: win.id
-    , (tabs) ->
+    chrome.tabs.query windowId: win.id, (tabs) ->
       # Try to find an existing tab.
-      break for tab in tabs when tab.url.indexOf(url) is 0
-      if tab
+      for tab in tabs
+        if tab.url.indexOf(url) is 0
+          existingTab = tab
+          break
+      if existingTab
         # Found one! Now to select it.
-        chrome.tabs.update tab.id, selected: yes
+        chrome.tabs.update existingTab.id, selected: yes
         callback? no
       else
         # Ach well, let's just create a new one.


fix(perf): Avoid blocking writes to stdout in progress

This uses `process.stdout.write()` directly to avoid blocking I/O
when displaying progress bars in processes that are heavy on I/O,
namely the Etcher CLI.

Change-Type: patch

--- lib/widgets/progress.coffee
+++ lib/widgets/progress.coffee
@@ -105,7 +105,7 @@
 			return

 		eraser = _.str.repeat(' ', @_lastLine.length)
-		console.log(CARRIAGE_RETURN + eraser)
+		process.stdout.write(CARRIAGE_RETURN + eraser + '\n')

 	###*
 	# @summary Update the progress bar
@@ -124,4 +124,4 @@
 	###
 	update: (state) ->
 		@_eraseLastLine()
-		console.log(CARRIAGE_RETURN + @_tick(state))
+		process.stdout.write(CARRIAGE_RETURN + @_tick(state) + '\n')


webapp: webrtc subdir for side chat; some whitespace fixes

--- src/smc-webapp/syncdoc.coffee
+++ src/smc-webapp/syncdoc.coffee
@@ -292,18 +292,18 @@
           (date.getTime() - last_date.getTime()) > 60000

             header.find(".salvus-chat-header-name")
-              .text(mesg.name)
-              .css(color: "#" + mesg.color)
+                .text(mesg.name)
+                .css(color: "#" + mesg.color)
             header.find(".salvus-chat-header-date")
-              .attr("title", date.toISOString())
-              .timeago()
+                .attr("title", date.toISOString())
+                .timeago()

         else
             header.hide()

         entry.find(".salvus-chat-entry-content")
-          .html(markdown.markdown_to_html(mesg.mesg.content).s)
-          .mathjax()
+            .html(markdown.markdown_to_html(mesg.mesg.content).s)
+            .mathjax()

         chat_output = @element.find(".salvus-editor-codemirror-chat-output")
         chat_output.append(entry)
@@ -326,17 +326,17 @@
           (date.getTime() - last_date.getTime()) > 60000

             header.find(".salvus-chat-header-name")
-              .text(sender_name)
-              .css(color: "#" + message_color)
+                .text(sender_name)
+                .css(color: "#" + message_color)
             header.find(".salvus-chat-header-date")
-              .attr("title", date.toISOString())
-              .timeago()
+                .attr("title", date.toISOString())
+                .timeago()
         else
             header.hide()

         entry.find(".salvus-chat-entry-content")
-          .html(markdown.markdown_to_html(mesg.payload.content).s)
-          .mathjax()
+            .html(markdown.markdown_to_html(mesg.payload.content).s)
+            .mathjax()

         chat_output = @element.find(".salvus-editor-codemirror-chat-output")
         chat_output.append(entry)
@@ -361,11 +361,11 @@
           (date.getTime() - last_date.getTime()) > 60000

             header.find(".salvus-chat-header-name")
-              .text(sender_name)
-              .css(color: "#" + message_color)
+                .text(sender_name)
+                .css(color: "#" + message_color)
             header.find(".salvus-chat-header-date")
-              .attr("title", date.toISOString())
-              .timeago()
+                .attr("title", date.toISOString())
+                .timeago()
         else
             header.hide()

@@ -400,11 +400,11 @@
           (date.getTime() - last_date.getTime()) > 60000

             header.find(".salvus-chat-header-name")
-              .text(sender_name)
-              .css(color: "#" + message_color)
+                .text(sender_name)
+                .css(color: "#" + message_color)
             header.find(".salvus-chat-header-date")
-              .attr("title", date.toISOString())
-              .timeago()
+                .attr("title", date.toISOString())
+                .timeago()
         else
             header.hide()

@@ -429,7 +429,7 @@
         # webpacking this here doesn't, because it needs a parameter and webpack only has js file as targets (if rendered to a file)
         # maybe https://github.com/webpack/webpack/issues/536 has some answer some day in the future …
         # TODO make this video chat properly part of the website or get rid of it
-        group_chat_url = window.smc_base_url + "/static/group_chat_side.html"
+        group_chat_url = window.smc_base_url + "/static/webrtc/group_chat_side.html"
         video_container.html("<iframe id='#{room_id}' src='#{group_chat_url}?#{room_id}' height='#{video_height}'></iframe>")

         # Update heights of chat and video windows


fixing URL shorteners for #64, changing implementation of OAuth for #20, and added case-sensitivity change for #66

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -89,6 +89,8 @@
   substring: 'Linux'
   title:     'Linux'
 ]
+# Regular expression used to detect upper case characters.
+R_UPPER_CASE      = /[A-Z]+/
 # Regular expression used to perform simple URL validation.
 R_VALID_URL       = /^https?:\/\/\S+\.\S+/i
 # Extension ID of the production version of Template.
@@ -118,7 +120,7 @@
   name: 'bitly'
   output: (resp) ->
     JSON.parse(resp).data.url
-  title: 'bit.ly'
+  title: i18n.get 'shortener_bitly'
   url: ->
     'http://api.bitly.com/v3/shorten'
 ,
@@ -132,8 +134,6 @@
     JSON.stringify longUrl: url
   isEnabled: ->
     store.get 'googl.enabled'
-  isOAuthEnabled: ->
-    store.get 'googl.oauth'
   method: 'POST'
   name: 'googl'
   oauth: ChromeExOAuth.initBackgroundPage
@@ -144,13 +144,9 @@
     consumer_secret: 'anonymous'
     request_url:     'https://www.google.com/accounts/OAuthGetRequestToken'
     scope:           'https://www.googleapis.com/auth/urlshortener'
-  oauthKeys: [
-    'oauth_tokenhttps://www.googleapis.com/auth/urlshortener'
-    'oauth_token_secrethttps://www.googleapis.com/auth/urlshortener'
-  ]
   output: (resp) ->
     JSON.parse(resp).id
-  title: 'goo.gl'
+  title: i18n.get 'shortener_googl'
   url: ->
     'https://www.googleapis.com/urlshortener/v1/url'
 ,
@@ -178,7 +174,7 @@
   name: 'yourls'
   output: (resp) ->
     JSON.parse(resp).shorturl
-  title: 'YOURLS'
+  title: i18n.get 'shortener_yourls'
   url: ->
     store.get 'yourls.url'
 ]
@@ -422,6 +418,8 @@
         success: no
   runner.pushPacked null, addAdditionalData, ->
     [tab, data, ->
+      # Ensure all properties of `data` are lower case.
+      transformData data
       # To complete the data, simply extend it using `template`.
       $.extend data, template: template
       if template.content
@@ -711,14 +709,17 @@
         decodeURIComponent(render text) ? ''
     depth:                 screen.colorDepth
     # Deprecated since 1.0.0, use `anchorTarget` instead.
-    doanchortarget:        anchor.target
+    doanchortarget:        ->
+      @anchortarget
     # Deprecated since 1.0.0, use `anchorTitle` instead.
-    doanchortitle:         anchor.title
+    doanchortitle:         ->
+      @anchortitle
     encode:                ->
       (text, render) ->
         encodeURIComponent(render text) ? ''
     # Deprecated since 0.1.0.2, use `encode` instead.
-    encoded:               encodeURIComponent url.attr 'source'
+    encoded:               ->
+      encodeURIComponent @url
     favicon:               tab.favIconUrl
     fparam:                ->
       (text, render) ->
@@ -729,13 +730,20 @@
         url.fsegment(parseInt render(text), 10) ? ''
     fsegments:             url.fsegment()
     googl:                 googl.enabled
-    googloauth:            googl.oauth
+    googlaccount:          ->
+      shortener = ext.queryUrlShortener (shortener) ->
+        shortener.name is 'googl'
+      shortener.oauth.hasToken()
+    # Deprecated since 1.0.0, use `googlAccount` instead.
+    googloauth:            ->
+      @googlaccount()
     java:                  navigator.javaEnabled()
     notifications:         notifications.enabled
     notificationduration:  notifications.duration * .001
     offline:               not navigator.onLine
     # Deprecated since 0.1.0.2, use `originalUrl` instead.
-    originalsource:        tab.url
+    originalsource:        ->
+      @originalurl
     originaltitle:         tab.title or url.attr 'source'
     originalurl:           tab.url
     os:                    operatingSystem
@@ -763,18 +771,21 @@
     segments:              url.segment()
     # Deprecated since 1.0.0, use `shorten` instead.
     short:                 ->
-      shortCallback
+      @shorten()
     shortcuts:             store.get 'shortcuts'
     shorten:               ->
       shortCallback
     title:                 title or url.attr 'source'
     toolbarclose:          toolbar.close
     # Deprecated since 1.0.0, use the inverse of `toolbarPopup` instead.
-    toolbarfeature:        not toolbar.popup
+    toolbarfeature:        ->
+      not @toolbarpopup
     # Deprecated since 1.0.0, use `toolbarStyle` instead.
-    toolbarfeaturedetails: toolbar.style
+    toolbarfeaturedetails: ->
+      @toolbarstyle
     # Deprecated since 1.0.0, use `toolbarKey` instead.
-    toolbarfeaturename:    toolbar.key
+    toolbarfeaturename:    ->
+      @toolbarkey
     toolbarkey:            toolbar.key
     toolbaroptions:        toolbar.options
     toolbarpopup:          toolbar.popup
@@ -787,6 +798,13 @@
     yourlsurl:             yourls.url
     yourlsusername:        yourls.username
   data
+
+# Ensure there is a lower case variant of all properties of `data`, optionally
+# removing the original non-lower-case property.
+transformData = (data, deleteOld) ->
+  for own prop, value of data when R_UPPER_CASE.test prop
+    data[prop.toLowerCase()] = value
+    delete data[prop] if deleteOld

 # HTML building functions
 # -----------------------
@@ -1053,7 +1071,6 @@
     bitly.username ?= ''
   store.modify 'googl', (googl) ->
     googl.enabled ?= yes
-    googl.oauth   ?= yes
     googl.usage   ?= 0
   store.modify 'yourls', (yourls) ->
     yourls.enabled   ?= no
@@ -1084,7 +1101,6 @@
     store.remove 'bitlyApiKey', 'bitlyUsername'
     store.set 'googl',
       enabled: store.get('googl') ? yes
-      oauth:   store.get('googlOAuth') ? yes
     store.remove 'googlOAuth'
     yourls = store.get 'yourls'
     store.set 'yourls',
@@ -1105,8 +1121,8 @@
 # or an error is encountered.
 callUrlShortener = (map, callback) ->
   service  = getActiveUrlShortener()
+  endpoint = service.url()
   title    = service.title
-  endpoint = service.url()
   # Ensure the service URL exists in case it is user-defined (e.g. YOURLS).
   unless endpoint
     return callback
@@ -1115,21 +1131,17 @@
       success: no
   # Create a runner to control the dependencies in the asynchronous processes.
   runner = new utils.Runner()
-  if service.oauth? and service.isOAuthEnabled()
-    runner.push service.oauth, 'authorize', ->
-      runner.next()
   for own placeholder, url of map
     do (placeholder, url) ->
       runner.push null, ->
-        oauth  = no
+        oauth  = !!service.oauth?.hasToken()
         params = service.getParameters(url) or {}
         # Build the HTTP request for the URL shortener service.
         xhr = new XMLHttpRequest()
         xhr.open service.method, "#{endpoint}?#{$.param params}", yes
         xhr.setRequestHeader 'Content-Type', service.contentType
         # Setup OAuth for the request when required.
-        if service.oauth and service.isOAuthEnabled()
-          oauth = yes
+        if oauth
           xhr.setRequestHeader 'Authorization',
             service.oauth.getAuthorizationHeader(endpoint,
               service.method, params)
@@ -1145,7 +1157,7 @@
             else
               # Something went wrong so let's tell the user.
               runner.finish
-                message: i18n.get 'shortener_detailed_error', [title, url]
+                message: i18n.get('shortener_detailed_error', [title, url])
                 success: no
         # Finally, send the HTTP request.
         xhr.send service.input url
@@ -1317,13 +1329,13 @@
     result

   # Retrieve the first template that passes the specified `filter`.
-  queryTemplate: (filter) ->
-    utils.query @templates, yes, filter
+  queryTemplate: (filter, singular = yes) ->
+    utils.query @templates, singular, filter

   # Retrieve the first URL shortener service that passes the specified
   # `filter`.
-  queryUrlShortener: (filter) ->
-    utils.query SHORTENERS, yes, filter
+  queryUrlShortener: (filter, singular = yes) ->
+    utils.query SHORTENERS, singular, filter

   # Reset the message and status associated with the current copy request.
   # This should be called when a copy request is completed regardless of its


Added restoreOriginalContent function to core

--- hallo.coffee
+++ hallo.coffee
@@ -196,6 +196,10 @@
         setUnmodified: ->
            @originalContent = @getContents()

+        # Restore the content original
+        restoreOriginalContent: ->
+            @element.html(@originalContent)
+
         # Execute a contentEditable command
         execute: (command, value) ->
             if document.execCommand command, false, value


Remove resolve methods.

Remove resolve methods.

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -120,31 +120,6 @@
     $rootScope.constants.type = {}
     $rootScope.constants.users = {}

-    #TODO: deprecate and remove this methods.
-    $rootScope.resolvePoints = (id) ->
-        point = $rootScope.constants.points[id]
-        return if point? then point.name else undefined
-
-    $rootScope.resolveStatus = (id) ->
-        status = $rootScope.constants.status[id]
-        return if status? then status.name else undefined
-
-    $rootScope.resolvePriority = (id) ->
-        priority = $rootScope.constants.priority[id]
-        return if priority? then priority.name else undefined
-
-    $rootScope.resolveSeverity = (id) ->
-        severity = $rootScope.constants.severity[id]
-        return if severity? then severity.name else undefined
-
-    $rootScope.resolveType = (id) ->
-        type = $rootScope.constants.type[id]
-        return if type? then type.name else undefined
-
-    $rootScope.resolveUser = (id) ->
-        user = $rootScope.constants.users[id]
-        return if user? then user.username else "Unassigned"
-
     $rootScope.baseUrls =
         projects: "/"
         backlog: "/project/%s/backlog"


Minor refactor on init method on main module.

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -182,11 +182,8 @@

     $gmUrlsProvider.setUrls("api", apiUrls)

-
-init = ($rootScope, $location, $gmStorage, $gmAuth, $gmUrls, $i18next, $gmConfig, localconfig, $data, $log, $favico) ->
+assignInitialConstants = ($rootScope) ->
     $rootScope.pageTitle = ""
-    $rootScope.auth = $gmAuth.getUser()
-
     $rootScope.constants = {}

     $rootScope.constants.usStatuses = {}
@@ -214,20 +211,12 @@

     $rootScope.constants.users = {}

-    # Initialize configuration
-    $gmConfig.initialize(localconfig)
-
-    # Configure on init a default host and scheme for api urls.
-    $gmUrls.setHost("api", $gmConfig.get("host"), $gmConfig.get("scheme"))
-
-
-    $data.loadSiteInfo($rootScope).then (sitedata) ->
-        $log.debug "Site data:", sitedata
-
-    if not $gmConfig.get("debug")
-        $log.debug = ->
-
-    $rootScope.baseUrls =
+assignNavegationUrls = ($rootScope) ->
+    conditionalUrl = (url, raw) ->
+        return url if raw
+        return "/##{url}"
+
+    $rootScope.baseUrls = {
         projects: "/"
         backlog: "/project/%s/backlog"
         kanban: "/project/%s/kanban"
@@ -244,12 +233,8 @@
         invitation: "/invitation/%s"
         admin: "/project/%s/admin/%s"
         attachment: "/media/attachment-files/%s/%s/%s"
-
-    conditionalUrl = (url, raw) ->
-        return url if raw
-        return "/##{url}"
-
-    # TODO: refactor this.
+    }
+
     $rootScope.urls =
         projectsUrl: ->
             return '/#/'
@@ -355,6 +340,35 @@

             return baseUrl + url

+
+
+init = ($rootScope, $location, $gmStorage, $gmAuth, $gmUrls, $i18next, $gmConfig, localconfig, $data, $log, $favico) ->
+    $rootScope.auth = $gmAuth.getUser()
+
+    # Assing to root scope intial empty constants
+    assignInitialConstants($rootScope)
+
+    # Assign navegration urls
+    assignNavegationUrls($rootScope)
+
+    # Initialize configuration
+    $gmConfig.initialize(localconfig)
+
+    # Configure on init a default host and scheme for api urls.
+    $gmUrls.setHost("api", $gmConfig.get("host"), $gmConfig.get("scheme"))
+
+    # Initialize i18next service
+    $i18next.initialize($gmConfig.get("defaultLanguage"))
+
+    # Load site data.
+    $data.loadSiteInfo($rootScope).then (sitedata) ->
+        $log.debug "Site data:", sitedata
+
+    # Overwrite debug logging function if loging is disabled
+    # Is seems hacky but is the unque way at this momment.
+    if not $gmConfig.get("debug")
+        $log.debug = ->
+
     $rootScope.momentFormat = (input, format) ->
         return moment(input).format(format)

@@ -362,7 +376,8 @@
         $gmStorage.clear()
         $location.url("/login")

-    $i18next.initialize($gmConfig.get("defaultLanguage"))
+    # FIXME: the following next two event assignations
+    # related to i18n seems need a refactor ;)

     $rootScope.$on "i18n:change", (event, lang) ->
         if lang


Signature pad styling

Signature pad styling

--- components/default-components.coffee
+++ components/default-components.coffee
@@ -1004,7 +1004,7 @@

                         {{label}}

-

+

                                 <signature-pad></signature-pad>


FRAMEWORK: kdtabpaneview now slugifies/lowercases the name option to a css class

--- components/tabs/KDTabPaneView.coffee
+++ components/tabs/KDTabPaneView.coffee
@@ -1,12 +1,12 @@
 class KDTabPaneView extends KDView
   constructor:(options = {},data)->
-
+
     options.hiddenHandle ?= no      # yes or no
     options.name        or= ""      # a String
-    options.cssClass      = KD.utils.curryCssClass "kdtabpaneview kdhiddentab clearfix #{options.name}"
+    options.cssClass      = KD.utils.curryCssClass "kdtabpaneview kdhiddentab clearfix #{KD.utils.slugify(options.name.toLowerCase())}"

     super options, data
-
+
     @name = options.name
     @setClass "clearfix"
     @setHeight @$().parent().height()


Code cleaning.

Code cleaning.

--- server/manage.coffee
+++ server/manage.coffee
@@ -132,7 +132,6 @@

       console.log "Done"

-  Meteor.methods
     'process-pdfs': ->
       console.log "Processing pending PDFs"

@@ -144,7 +143,6 @@

       console.log "Done"

-  Meteor.methods
     'refresh-pdfs-cache': ->
       console.log "Refreshing PDFs cache"

@@ -161,7 +159,6 @@

       Meteor.call 'process-pdfs' if count > 0

-  Meteor.methods
     'refresh-arxhiv-meta': ->
       console.log "Refreshing arXiv metadata"


fix for the last node

fix for the last node

--- app/elements/aozora-story/aozora-story.coffee
+++ app/elements/aozora-story/aozora-story.coffee
@@ -57,7 +57,7 @@
     @node = node

   jumpToNextNode: ->
-    return unless @node?
+    return unless @node? and @node.next?

     # TODO IMPORTANT very bad performance, integrate IndexedDB or just use array index
     getNodeById = (id) =>


update crazy test

update crazy test

--- test/integration/crazy-subscriptions-spec.coffee
+++ test/integration/crazy-subscriptions-spec.coffee
@@ -25,28 +25,6 @@
     @testDispatcherWorker.getHydrant (error, @hydrant) =>
       done error

-  beforeEach 'define whitelists', () ->
-    @meshbluWhitelists =
-      version: "2.0.0"
-      whitelists:
-        broadcast:
-          as: [uuid:"*"]
-          received: [uuid:"*"]
-          sent: [uuid:"*"]
-        discover:
-          as: [uuid:"*"]
-          view: [uuid:"*"]
-        configure:
-          as: [uuid:"*"]
-          received: [uuid:"*"]
-          sent: [uuid:"*"]
-          update: [uuid:"*"]
-        message:
-          as: [uuid:"*"]
-          from: [uuid:"*"]
-          received: [uuid:"*"]
-          sent: [uuid:"*"]
-
   beforeEach 'create update device', (done) ->
     @authUpdate =
       uuid: 'update-uuid'
@@ -56,17 +34,12 @@
       uuid: 'update-uuid'
       type: 'device:updater'
       token: bcrypt.hashSync @authUpdate.token, 8
-      meshblu: @meshbluWhitelists

-        # version: '2.0.0'
-        # whitelists:
-        #   configure:
-        #     received: [{uuid: 'emitter-uuid'}]
-
-      # configureWhitelist: ['*']
-      # discoverWhitelist: ['*']
-      # receiveWhitelist: ['*']
-      # sendWhitelist: ['*']
+      meshblu:
+        version: '2.0.0'
+        whitelists:
+          configure:
+            sent: [{uuid: 'emitter-uuid'}]

     @devices.insert @updateDevice, done

@@ -79,21 +52,12 @@
       uuid: 'emitter-uuid'
       type: 'device:sender'
       token: bcrypt.hashSync @authEmitter.token, 8
-      meshblu: @meshbluWhitelists

-      #   version: '2.0.0'
-      #   whitelists:
-      #     configure:
-      #       update: [{uuid: 'update-uuid'}]
-      #       as: [{uuid: 'update-uuid'}]
-      #       received: [{uuid: 'update-uuid'}]
-      #       sent: [{uuid: 'update-uuid'}]
-
-      # configureWhitelist: ['*']
-      # discoverWhitelist: ['*']
-      # receiveWhitelist: ['*']
-      # sendWhitelist: ['*']
-
+      meshblu:
+        version: '2.0.0'
+        whitelists:
+          configure:
+            received: [{uuid: 'spy-uuid'}]

     @devices.insert @senderDevice, done

@@ -101,18 +65,6 @@
     @spyDevice =
       uuid: 'spy-uuid'
       type: 'device:spy'
-      meshblu: @meshbluWhitelists
-
-        # version: '2.0.0'
-        # whitelists:
-        #   configure:
-        #     received: [{uuid: 'emitter-uuid'}]
-
-      # configureWhitelist: ['*']
-      # discoverWhitelist: ['*']
-      # receiveWhitelist: ['*']
-      # sendWhitelist: ['*']
-

     @devices.insert @spyDevice, done

@@ -134,14 +86,6 @@
           subscriberUuid: 'emitter-uuid'

         @subscriptions.insert subscription, done
-      #
-      # beforeEach 'create configure received subscription', (done) ->
-      #   subscription =
-      #     type: 'configure.sent'
-      #     emitterUuid: 'emitter-uuid'
-      #     subscriberUuid: 'spy-uuid'
-      #
-      #   @subscriptions.insert subscription, done

       beforeEach 'create configure received subscription', (done) ->
         subscription =