Improve output of github links

Improve output of github links

--- src/github.coffee
+++ src/github.coffee
@@ -38,6 +38,11 @@
 Fuse = require 'fuse.js'

 module.exports = (robot) ->
+
+  send = (context, message, prependUsername=no) ->
+    robot.adapter.customMessage
+      channel: context.message.room
+      text: "#{if prependUsername then "<@#{context.message.user.id}> " else ""}#{message}"

   getNotifications = ->
     robot.brain.get('github-notifications') or []
@@ -122,7 +127,7 @@
   listOpenPullRequestsForRoom = (room, user) ->
     repo = repos[room]
     if not repo
-      robot.messageRoom room, "There is no github repository associated with this room. Contact your friendly <@#{robot.name}> administrator for assistance"
+      send message: room: room, "There is no github repository associated with this room. Contact your friendly <@#{robot.name}> administrator for assistance"
       return

     repo = octo.repos(githubOrg, repo)
@@ -149,8 +154,7 @@
         author = p[1]
         assignee = p[2]
         message+= """
-          *[#{pr.title}]* +#{pr.additions} -#{pr.deletions}
-          #{pr.htmlUrl}
+          *<#{pr.htmlUrl}|#{pr.title}>* +#{pr.additions} -#{pr.deletions}
           Updated: *#{moment(pr.updatedAt).fromNow()}*
           Status: #{if pr.mergeable then "Mergeable" else "Unresolved Conflicts"}
           Author: #{if author then "<@#{author.id}>" else "Unknown"}
@@ -160,13 +164,13 @@
       if message.length is 0
         message = "No matching pull requests found"

-      robot.messageRoom room, message
+      send message: room: room, message
     .catch ( error ) ->
       console.log error.stack

   robot.respond /(?:github|gh|git) delete all notifications/i, (msg) ->
     notificationsCleared = clearAllNotificationsForRoom(findRoom(msg))
-    msg.send """
+    send msg, """
       Deleted #{notificationsCleared} notification#{if notificationsCleared is 1 then "" else "s"}.
       No more notifications for you.
     """
@@ -175,34 +179,34 @@
     [__, time] = msg.match
     notificationsCleared = clearSpecificNotificationForRoom(findRoom(msg), time)
     if notificationsCleared is 0
-      msg.send "Nice try. You don't even have a notification at #{time}"
+      send msg, "Nice try. You don't even have a notification at #{time}"
     else
-      msg.send "Deleted your #{time} notification"
+      send msg, "Deleted your #{time} notification"

   robot.respond /(?:github|gh|git) notification ((?:[01]?[0-9]|2[0-4]):[0-5]?[0-9])$/i, (msg) ->
     [__, time] = msg.match
     room = findRoom(msg)
     saveNotification room, time
-    msg.send "Ok, from now on I'll remind this room about open pull requests every weekday at #{time}"
+    send msg, "Ok, from now on I'll remind this room about open pull requests every weekday at #{time}"

   robot.respond /(?:github|gh|git) list notifications$/i, (msg) ->
     notifications = getNotificationsForRoom(findRoom(msg))
     if notifications.length is 0
-      msg.send "Well this is awkward. You haven't got any github notifications set :-/"
+      send msg, "Well this is awkward. You haven't got any github notifications set :-/"
     else
-      msg.send "You have pull request notifcations at the following times: #{_.map(notifications, (notification) -> notification.time)}"
+      send msg, "You have pull request notifcations at the following times: #{_.map(notifications, (notification) -> notification.time)}"

   robot.respond /(?:github|gh|git) notifications in every room/i, (msg) ->
     notifications = getNotifications()
     if notifications.length is 0
-      msg.send "No, because there aren't any."
+      send msg, "No, because there aren't any."
     else
-      msg.send """
+      send msg, """
         Here's the notifications for every room: #{_.map(notifications, (notification) -> "\nRoom: #{notification.room}, Time: #{notification.time}")}
       """

   robot.respond /(github|gh|git) help/i, (msg) ->
-    msg.send """
+    send msg, """
       I can remind you about open pull requests for the repo that belongs to this channel
       Use me to create a notification, and then I'll post in this room every weekday at the time you specify. Here's how:


Do not initialise ServiceTreeView in embedded mode.

--- src/views.coffee
+++ src/views.coffee
@@ -313,7 +313,7 @@
             @service_tree = new ServiceTreeView
                 collection: @service_tree_collection
                 app_view: @parent
-                el: @$el.find('#service-tree-container')
+                el: @$el.find('#service-tree-container') if isNotEmbeddedMap()

             @details_view = new DetailsView
                 el: @$el.find('#details-view-container')


Don't show line number for Suomenlinna ferry.

Would confuse natives and tourists alike.

--- src/views.coffee
+++ src/views.coffee
@@ -606,7 +606,10 @@

             legs = _.map(filtered_legs, (leg) =>
                 steps = @parse_steps leg
-
+                route = if leg.route.length < 5 then leg.route else ''
+                if leg.mode == 'FERRY'
+                    # Don't show number for ferry.
+                    route = ''
                 start_time: moment(leg.startTime).format('LT')
                 start_location: leg.from.name
                 distance: (leg.distance / 1000).toFixed(1) + 'km'
@@ -614,7 +617,7 @@
                 transit_color_class: LEG_MODES[leg.mode].color_class
                 transit_mode: LEG_MODES[leg.mode].text
                 transit_details: @get_transit_details leg
-                route: if leg.route.length < 5 then leg.route else ''
+                route: route
                 steps: steps
                 has_warnings: !!_.find(steps, (step) -> step.warning)
             )


Improve comments in xpath

Improve comments in xpath

--- src/xpath.coffee
+++ src/xpath.coffee
@@ -1,6 +1,10 @@
 $ = require('./util').$

-# A simple XPath evaluator using jQuery which can evaluate queries of
+# Get an xpath string to the node relative to the provided root
+#
+# relativeRoot - The root node for the xpath
+#
+# Returns a string
 simpleXPathJQuery = (relativeRoot) ->
   jq = this.map ->
     path = ''
@@ -18,8 +22,11 @@

   jq.get()

-# A simple XPath evaluator using only standard DOM methods which can
-# evaluate queries of the form /tag[index]/tag[index].
+# Get an xpath string to the node relative to the provided root
+#
+# relativeRoot - The root node for the xpath
+#
+# Returns a string
 simpleXPathPure = (relativeRoot) ->

   getPathSegment = (node) ->


Corrected loading of JS files.

Corrected loading of JS files.

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -16,7 +16,7 @@
 serveStatic = require 'serve-static'

 vendor_module = (module,args...) ->
-  fs.readFileSync path.join (path.dirname require.resolve module), args...
+  fs.readFileSync (path.join (path.dirname require.resolve module), args...), 'utf-8'

 socketio_key = '__session'

@@ -27,7 +27,7 @@
 jquery = -> jquery.content ?= vendor_module 'jquery', 'jquery.js'
 jquery_minified = -> jquery_minified.content ?= vendor_module 'jquery', 'jquery.min.js'
 sammy = -> sammy.content ?= vendor_module 'sammy', 'sammy.js'
-sammy_minified = -> sammy_minified.content ?= vendor 'sammy.min.js'
+sammy_minified = -> sammy_minified.content ?= vendor_module 'sammy', 'min', 'sammy-latest.min.js'
 socketjs = -> socketjs.content ?= vendor_module 'socket.io-client', 'socket.io.js'

 # CoffeeScript-generated JavaScript may contain anyone of these; when we
@@ -151,7 +151,7 @@
         e = null
         try
           # FIXME: pass parameters as per Express2
-          r = @engine views[@path], options
+          r = @engine views[@path], options, @path
         catch error
           e = "Engine .render for #{@ext} failed: #{error}"
         fn e, r
@@ -204,21 +204,21 @@
     for k, v of obj
       js = ";zappa.run(#{v});"
       js = minify(js) if app.settings['minify']
-      route verb: 'get', path: k, handler: js, contentType: 'js'
+      route verb: 'get', path: k, handler: js, type: 'js'
     return

   context.coffee = (obj) ->
     for k, v of obj
       js = ";#{coffeescript_helpers}(#{v})();"
       js = minify(js) if app.settings['minify']
-      route verb: 'get', path: k, handler: js, contentType: 'js'
+      route verb: 'get', path: k, handler: js, type: 'js'
     return

   context.js = (obj) ->
     for k, v of obj
       js = String(v)
       js = minify(js) if app.settings['minify']
-      route verb: 'get', path: k, handler: js, contentType: 'js'
+      route verb: 'get', path: k, handler: js, type: 'js'
     return

   context.css = (obj) ->
@@ -228,7 +228,7 @@
         css = coffee_css.compile v
       else
         css = String(v)
-      route verb: 'get', path: k, handler: css, contentType: 'css'
+      route verb: 'get', path: k, handler: css, type: 'css'
     return

   context.with = (obj) ->
@@ -242,7 +242,7 @@
             for k, v of obj
               module.render v, filename: k, (err, css) ->
                 throw err if err
-                route verb: 'get', path: k, handler: css, contentType: 'css'
+                route verb: 'get', path: k, handler: css, type: 'css'
             return
         return

@@ -329,7 +329,7 @@
         zappa_used = yes
         (req, res, next) ->
           send = (code) ->
-            res.contentType 'js'
+            res.type 'js'
             res.send code
           if req.method.toUpperCase() isnt 'GET' then next()
           else
@@ -373,7 +373,7 @@
     for k, v of obj
       js = ";zappa.run(#{v});"
       js = minify(js) if app.settings['minify']
-      route verb: 'get', path: k, handler: js, contentType: 'js'
+      route verb: 'get', path: k, handler: js, type: 'js'
       v.apply context
     return

@@ -439,7 +439,7 @@

     if typeof r.handler is 'string'
       app[r.verb] r.path, r.middleware, (req, res) ->
-        res.contentType r.contentType if r.contentType?
+        res.type r.type if r.type?
         res.send r.handler
         return
     else
@@ -517,7 +517,7 @@

         result = r.handler.call ctx, req, res, next

-        res.contentType(r.contentType) if r.contentType?
+        res.type(r.type) if r.type?
         if typeof result is 'string' then res.send result
         else return result


Update var name

Update var name

--- tutor/src/components/task/crumb-mixin.cjsx
+++ tutor/src/components/task/crumb-mixin.cjsx
@@ -100,14 +100,14 @@
     # insert spacer panel/crumb for reading task that have spaced practices or personalized problems
     if task.type is 'reading'

-      notCoreIndex = _.findLastIndex crumbs, (crumb) ->
+      lastCoreIndex = _.findLastIndex crumbs, (crumb) ->
         (crumb.type is 'step') and TaskStepStore.isCore(crumb.data.id)

-      notCore = if notCoreIndex is -1
+      notCore = if lastCoreIndex is -1
         _.find crumbs, (crumb) ->
           (crumb.type is 'step') and not TaskStepStore.isCore(crumb.data.id)
       else
-        crumbs[notCoreIndex]
+        crumbs[lastCoreIndex]
       if notCore?
         crumbType = 'spacer'
         spacerCrumb =


Update collision detection of bullet

Update collision detection of bullet

--- src/components/weapons/Bullet.coffee
+++ src/components/weapons/Bullet.coffee
@@ -1,6 +1,10 @@
 Crafty.c 'Bullet',
+  required: '2D, WebGL, sphere1, Collision, ColorEffects'
+  events:
+    HitOn: '_onWallHit'
+    Freeze: '_onFreeze'
+
   init: ->
-    @requires '2D, WebGL, sphere1, Collision, ColorEffects'
     @crop 4, 19, 24, 11
     @collision [
       0, 0,
@@ -8,6 +12,13 @@
       18, 7,
       0, 7
     ]
+
+  _onWallHit: ->
+    return if Game.paused
+    @trigger('BulletWall', this)
+
+  _onFreeze: ->
+    @unbind('GameLoop', @_onGameLoop)

   updateCollision: (w, h) ->
     @collision [
@@ -34,13 +45,7 @@
     @attr(
       ship: properties.ship
     )
-    @onHit 'BulletSolid', ->
-      return if Game.paused
-      @trigger('BulletWall', this)
-
-    @bind('Freeze', ->
-      @unbind('GameLoop', @_onGameLoop)
-    )
+    @checkHits('BulletSolid')
     this

   fire: (properties) ->


refactor: link styling

refactor: link styling

--- spec/inlines/link-grammar-spec.coffee
+++ spec/inlines/link-grammar-spec.coffee
@@ -19,87 +19,87 @@
   it 'is a simple url with http', ->
     {tokens} = grammar.tokenizeLine 'http://www.docbook.org/[DocBook.org]'
     expect(tokens).toHaveLength 4
-    expect(tokens[0]).toEqualJson value: 'http://www.docbook.org/', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'markup.link.asciidoc']
-    expect(tokens[1]).toEqualJson value: '[', scopes: ['source.asciidoc', 'markup.other.link.asciidoc']
-    expect(tokens[2]).toEqualJson value: 'DocBook.org', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'string.unquoted.asciidoc']
-    expect(tokens[3]).toEqualJson value: ']', scopes: ['source.asciidoc', 'markup.other.link.asciidoc']
+    expect(tokens[0]).toEqualJson value: 'http://www.docbook.org/', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'markup.link.asciidoc']
+    expect(tokens[1]).toEqualJson value: '[', scopes: ['source.asciidoc', 'markup.other.links.asciidoc']
+    expect(tokens[2]).toEqualJson value: 'DocBook.org', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'string.unquoted.asciidoc']
+    expect(tokens[3]).toEqualJson value: ']', scopes: ['source.asciidoc', 'markup.other.links.asciidoc']

   it 'is a simple url with http in phrase', ->
     {tokens} = grammar.tokenizeLine 'Go on http://foobar.com now'
     expect(tokens).toHaveLength 4
     expect(tokens[0]).toEqualJson value: 'Go on', scopes: ['source.asciidoc']
-    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'entity.name.function.asciidoc']
-    expect(tokens[2]).toEqualJson value: 'http://foobar.com', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'markup.link.asciidoc']
+    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'entity.name.function.asciidoc']
+    expect(tokens[2]).toEqualJson value: 'http://foobar.com', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'markup.link.asciidoc']
     expect(tokens[3]).toEqualJson value: ' now', scopes: ['source.asciidoc']

   it 'is a simple url with https', ->
     {tokens} = grammar.tokenizeLine 'Go on https://foobar.com now'
     expect(tokens).toHaveLength 4
     expect(tokens[0]).toEqualJson value: 'Go on', scopes: ['source.asciidoc']
-    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'entity.name.function.asciidoc']
-    expect(tokens[2]).toEqualJson value: 'https://foobar.com', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'markup.link.asciidoc']
+    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'entity.name.function.asciidoc']
+    expect(tokens[2]).toEqualJson value: 'https://foobar.com', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'markup.link.asciidoc']
     expect(tokens[3]).toEqualJson value: ' now', scopes: ['source.asciidoc']

   it 'is a simple url with file', ->
     {tokens} = grammar.tokenizeLine 'Go on file://foobar.txt now'
     expect(tokens).toHaveLength 4
     expect(tokens[0]).toEqualJson value: 'Go on', scopes: ['source.asciidoc']
-    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'entity.name.function.asciidoc']
-    expect(tokens[2]).toEqualJson value: 'file://foobar.txt', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'markup.link.asciidoc']
+    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'entity.name.function.asciidoc']
+    expect(tokens[2]).toEqualJson value: 'file://foobar.txt', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'markup.link.asciidoc']
     expect(tokens[3]).toEqualJson value: ' now', scopes: ['source.asciidoc']

   it 'is a simple url with ftp', ->
     {tokens} = grammar.tokenizeLine 'Go on ftp://foobar.txt now'
     expect(tokens).toHaveLength 4
     expect(tokens[0]).toEqualJson value: 'Go on', scopes: ['source.asciidoc']
-    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'entity.name.function.asciidoc']
-    expect(tokens[2]).toEqualJson value: 'ftp://foobar.txt', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'markup.link.asciidoc']
+    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'entity.name.function.asciidoc']
+    expect(tokens[2]).toEqualJson value: 'ftp://foobar.txt', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'markup.link.asciidoc']
     expect(tokens[3]).toEqualJson value: ' now', scopes: ['source.asciidoc']

   it 'is a simple url with irc', ->
     {tokens} = grammar.tokenizeLine 'Go on irc://foobar now'
     expect(tokens).toHaveLength 4
     expect(tokens[0]).toEqualJson value: 'Go on', scopes: ['source.asciidoc']
-    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'entity.name.function.asciidoc']
-    expect(tokens[2]).toEqualJson value: 'irc://foobar', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'markup.link.asciidoc']
+    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'entity.name.function.asciidoc']
+    expect(tokens[2]).toEqualJson value: 'irc://foobar', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'markup.link.asciidoc']
     expect(tokens[3]).toEqualJson value: ' now', scopes: ['source.asciidoc']

   it 'is a simple url with https started with "link:"', ->
     {tokens} = grammar.tokenizeLine 'Go on link:https://foobar.com now'
     expect(tokens).toHaveLength 4
     expect(tokens[0]).toEqualJson value: 'Go on ', scopes: ['source.asciidoc']
-    expect(tokens[1]).toEqualJson value: 'link:', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'entity.name.function.asciidoc']
-    expect(tokens[2]).toEqualJson value: 'https://foobar.com', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'markup.link.asciidoc']
+    expect(tokens[1]).toEqualJson value: 'link:', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'entity.name.function.asciidoc']
+    expect(tokens[2]).toEqualJson value: 'https://foobar.com', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'markup.link.asciidoc']
     expect(tokens[3]).toEqualJson value: ' now', scopes: ['source.asciidoc']

   it 'is a simple url in a bullet list', ->
     {tokens} = grammar.tokenizeLine '* https://foobar.com now'
     expect(tokens).toHaveLength 4
     expect(tokens[0]).toEqualJson value: '*', scopes: ['source.asciidoc', 'markup.list.asciidoc', 'markup.list.bullet.asciidoc']
-    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'entity.name.function.asciidoc']
-    expect(tokens[2]).toEqualJson value: 'https://foobar.com', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'markup.link.asciidoc']
+    expect(tokens[1]).toEqualJson value: ' ', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'entity.name.function.asciidoc']
+    expect(tokens[2]).toEqualJson value: 'https://foobar.com', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'markup.link.asciidoc']
     expect(tokens[3]).toEqualJson value: ' now', scopes: ['source.asciidoc']

   it 'have optional link text and attributes', ->
     {tokens} = grammar.tokenizeLine 'Go on link:url[optional link text, optional target attribute, optional role attribute] now'
     expect(tokens).toHaveLength 8
     expect(tokens[0]).toEqualJson value: 'Go on ', scopes: ['source.asciidoc']
-    expect(tokens[1]).toEqualJson value: 'link', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'entity.name.function.asciidoc']
-    expect(tokens[2]).toEqualJson value: ':', scopes: ['source.asciidoc', 'markup.other.link.asciidoc']
-    expect(tokens[3]).toEqualJson value: 'url', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'markup.link.asciidoc']
-    expect(tokens[4]).toEqualJson value: '[', scopes: ['source.asciidoc', 'markup.other.link.asciidoc']
-    expect(tokens[5]).toEqualJson value: 'optional link text, optional target attribute, optional role attribute', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'string.unquoted.asciidoc']
-    expect(tokens[6]).toEqualJson value: ']', scopes: ['source.asciidoc', 'markup.other.link.asciidoc']
+    expect(tokens[1]).toEqualJson value: 'link', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'entity.name.function.asciidoc']
+    expect(tokens[2]).toEqualJson value: ':', scopes: ['source.asciidoc', 'markup.other.links.asciidoc']
+    expect(tokens[3]).toEqualJson value: 'url', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'markup.link.asciidoc']
+    expect(tokens[4]).toEqualJson value: '[', scopes: ['source.asciidoc', 'markup.other.links.asciidoc']
+    expect(tokens[5]).toEqualJson value: 'optional link text, optional target attribute, optional role attribute', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'string.unquoted.asciidoc']
+    expect(tokens[6]).toEqualJson value: ']', scopes: ['source.asciidoc', 'markup.other.links.asciidoc']
     expect(tokens[7]).toEqualJson value: ' now', scopes: ['source.asciidoc']

   it 'is a simple mailto', ->
     {tokens} = grammar.tokenizeLine 'Go on mailto:doc.writer@example.com[] now'
     expect(tokens).toHaveLength 6
     expect(tokens[0]).toEqualJson value: 'Go on ', scopes: ['source.asciidoc']
-    expect(tokens[1]).toEqualJson value: 'mailto', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'entity.name.function.asciidoc']
-    expect(tokens[2]).toEqualJson value: ':', scopes: ['source.asciidoc', 'markup.other.link.asciidoc']
-    expect(tokens[3]).toEqualJson value: 'doc.writer@example.com', scopes: ['source.asciidoc', 'markup.other.link.asciidoc', 'markup.link.asciidoc']
-    expect(tokens[4]).toEqualJson value: '[]', scopes: ['source.asciidoc', 'markup.other.link.asciidoc']
+    expect(tokens[1]).toEqualJson value: 'mailto', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'entity.name.function.asciidoc']
+    expect(tokens[2]).toEqualJson value: ':', scopes: ['source.asciidoc', 'markup.other.links.asciidoc']
+    expect(tokens[3]).toEqualJson value: 'doc.writer@example.com', scopes: ['source.asciidoc', 'markup.other.links.asciidoc', 'markup.link.asciidoc']
+    expect(tokens[4]).toEqualJson value: '[]', scopes: ['source.asciidoc', 'markup.other.links.asciidoc']
     expect(tokens[5]).toEqualJson value: ' now', scopes: ['source.asciidoc']

   it 'is a email', ->


Move highlight methods into the appropriate stack

--- app/assets/components/togglable_group/togglable_group.coffee
+++ app/assets/components/togglable_group/togglable_group.coffee
@@ -26,20 +26,6 @@
     super(e)
     @deactivateHighlightState(e)
     @activateHighlightState(e)
-
-  # ----------------------------------------------------------------------------
-  # TODO Needs to be tested.. should this move up to Togglable?
-  activateHighlightState: (e) ->
-    activator = $(e.target).closest(@data.target)
-    parental = activator.closest('.nav')
-    if parental.length > 0 then kids = parental.children() else return null
-    if kids.length > 0 && kids.hasClass('active')
-      parental.parent('li').addClass('highlight')
-
-  # TODO Needs to be tested.. should this move up to Togglable?
-  deactivateHighlightState: (e) ->
-    @el.find('.highlight').removeClass('highlight')
-  # ----------------------------------------------------------------------------

   activateState: (e) ->
     if typeof e.target == "number"
@@ -77,5 +63,19 @@
       element.removeClass(@related_classes) unless @behavior == 'radio' && element.hasClass(@related_classes)
       return element

+  # ----------------------------------------------------------------------------
+  # TODO Needs to be tested.. should this move up to Togglable?
+  activateHighlightState: (e) ->
+    activator = $(e.target).closest(@data.target)
+    parental = activator.closest('.nav')
+    if parental.length > 0 then kids = parental.children() else return null
+    if kids.length > 0 && kids.hasClass('active')
+      parental.parent('li').addClass('highlight')
+
+  # TODO Needs to be tested.. should this move up to Togglable?
+  deactivateHighlightState: (e) ->
+    @el.find('.highlight').removeClass('highlight')
+  # ----------------------------------------------------------------------------
+
 Bindable.register('togglable-group', utensil.TogglableGroup)


better nav

better nav

--- scripts/eslogger_web.coffee
+++ scripts/eslogger_web.coffee
@@ -27,8 +27,9 @@
     room = req.params.room
     if room and '#' + room in eslogger.logRooms
       room = '#' + room
-      start = moment.utc().hour(0).minutes(0).seconds(0)
-      stop = moment.utc()
+      daynow = moment.utc()
+      start = daynow.hour(0).minutes(0).seconds(0)
+      stop = daynow
       eslogger.getLogs room, start, stop, (json_body) ->
         res.setHeader 'content-type', 'text/html'
         res.end eslogger.logContent(room, json_body, start, stop)
@@ -46,8 +47,9 @@
     else
       if room and '#' + room in eslogger.logRooms
         room = '#' + room
-        start = moment.utc().day(- day).hour(0).minutes(0).seconds(0)
-        stop = moment.utc().day(- day).hour(23).minutes(59).seconds(59)
+        daynow = moment.utc().substract(day, 'days')
+        start = daynow.hour(0).minutes(0).seconds(0)
+        stop = daynow.hour(23).minutes(59).seconds(59)
         eslogger.getLogs room, start, stop, (json_body) ->
           res.setHeader 'content-type', 'text/html'
           res.end eslogger.logContent(room, json_body, start, stop)


Treat 'closing' state as closed.

Treat 'closing' state as closed.

We are seeing cases where the websockets get stuck in the 'closing' state
after a tab has been in background for a while. So lets treat those websockets
as closed.

--- actioncable/app/assets/javascripts/action_cable/connection.coffee
+++ actioncable/app/assets/javascripts/action_cable/connection.coffee
@@ -16,11 +16,11 @@
       false

   open: =>
-    if @webSocket and not @isState("closed")
+    if @webSocket and not @isClosed()
       console.log("[cable] Attemped to open WebSocket, but existing socket is #{@getState()}", Date.now())
       throw new Error("Existing connection must be closed before opening")
     else
-      console.log("[cable] Opening WebSocket", Date.now())
+      console.log("[cable] Opening WebSocket, current state is #{@getState()}", Date.now())
       @webSocket = new WebSocket(@consumer.url)
       @installEventHandlers()
       true
@@ -30,7 +30,7 @@

   reopen: ->
     console.log("[cable] Reopening WebSocket, current state is #{@getState()}", Date.now())
-    if @isState("closed")
+    if @isClosed()
       @open()
     else
       try
@@ -43,6 +43,9 @@
     @isState("open")

   # Private
+
+  isClosed: ->
+    @isState("closing", "closed")

   isState: (states...) ->
     @getState() in states


Ignore messages with null room_id

Ignore messages with null room_id

--- services/real-time/app/coffee/WebsocketLoadBalancer.coffee
+++ services/real-time/app/coffee/WebsocketLoadBalancer.coffee
@@ -1,4 +1,5 @@
 Settings = require 'settings-sharelatex'
+logger = require 'logger-sharelatex'
 redis = require("redis-sharelatex")
 rclientPub = redis.createClient(Settings.redis.web)
 rclientSub = redis.createClient(Settings.redis.web)
@@ -8,6 +9,9 @@
 	rclientSub: rclientSub

 	emitToRoom: (room_id, message, payload...) ->
+		if !room_id?
+			logger.err {err: new Error("Empty room_id"), message, payload}, "error emitting to room"
+			return
 		@rclientPub.publish "editor-events", JSON.stringify
 			room_id: room_id
 			message: message
@@ -25,6 +29,6 @@
 		message = JSON.parse(message)
 		if message.room_id == "all"
 			io.sockets.emit(message.message, message.payload...)
-		else
+		else if message.room_id?
 			io.sockets.in(message.room_id).emit(message.message, message.payload...)


Sentence-by-sentence further work

Sentence-by-sentence further work

--- annotran/static/scripts/annotator/plugin/sentenceselection.coffee
+++ annotran/static/scripts/annotator/plugin/sentenceselection.coffee
@@ -118,21 +118,20 @@
   returnNext: (currentTarget) ->
     nextSibling = $(currentTarget).next()

-    console.log("returnNext")
-
     if nextSibling != undefined and nextSibling.length != 0 and nextSibling.prop("tagName").toLowerCase() != "figure" and nextSibling.prop("tagName").toLowerCase() != "img" and nextSibling.prop("tagName").toLowerCase() != "applet"  and nextSibling.prop("tagName").toLowerCase() != "audio"  and nextSibling.prop("tagName").toLowerCase() != "embed"  and nextSibling.prop("tagName").toLowerCase() != "object"  and nextSibling.prop("tagName").toLowerCase() != "video"
       return nextSibling
     else
       if nextSibling != undefined and nextSibling.length != 0
+        console.log("Return next")
         return nextSibling
       else
         nextSibling = $(currentTarget).parent()

-        if nextSibling != undefined and this.currentIndexes.get(nextSibling) > 0
-          console.log("parent")
-          console.log(this.currentIndexes.get(nextSibling) )
+        if nextSibling != undefined and (this.currentIndexes.get(nextSibling) != undefined or this.currentIndexes.get(nextSibling) > 0)
+          console.log("Return Parent")
           return nextSibling
         else
+          console.log("JUMP")
           return this.findNextJumpNode(currentTarget)

   scanForNextSibling: (initialTarget, currentTarget, offset_to_use, endIndex) ->
@@ -302,15 +301,27 @@
     if this.currentIndexes.get(elementToUse) == undefined
       this.currentIndexes.put(elementToUse, 0)

+    # increment the counter by the overlap of selection if in multiple nodes (this is a zero increment if only one element is selected)
+    this.currentIndexes.put(elementToUse, (currentSelection.toString().length - currentSelection.extentOffset) + this.currentIndexes.get(elementToUse))
+
+    # now increment by the offset extent within the current element
     this.currentIndexes.put(elementToUse, currentSelection.extentOffset + this.currentIndexes.get(elementToUse))

     # increment the text position counter of all parent elements here
     parent = $(elementToUse).parent()
     while this.currentIndexes.get(parent) != undefined
+      this.currentIndexes.put(parent, (currentSelection.toString().length - currentSelection.extentOffset) + this.currentIndexes.get(parent))
       this.currentIndexes.put(parent, currentSelection.extentOffset + this.currentIndexes.get(parent))
       parent = $(parent).parent()

-    if elementToUse.textContent.length <= (this.currentIndexes.get(elementToUse))
+    console.log("Textcontent length - 1:")
+    console.log(elementToUse.textContent.length - 1)
+
+    console.log("(this.currentIndexes.get(elementToUse)")
+    console.log((this.currentIndexes.get(elementToUse)))
+
+    if elementToUse.textContent.length - 1 <= this.currentIndexes.get(elementToUse)
+      console.log("INSIDE NEXT BRACKET")
       nextSibling = $(elementToUse).next()

       this.currentIndexes.put(nextSibling, 0)


:bug: Fix error raised when splitting panes due to missing minimap

--- lib/minimap-find-results-view.coffee
+++ lib/minimap-find-results-view.coffee
@@ -45,4 +45,5 @@
       super(markers)
       for k,marker of @markerViews
         marker.intersectsRenderedScreenRows = (range) ->
+          return false unless minimap?
           range.intersectsRowRange(minimap.miniEditorView.firstRenderedScreenRow, minimap.miniEditorView.lastRenderedScreenRow)


[stark] fix redirect to default route

[stark] fix redirect to default route

--- lib/assets/javascripts/stark/stark.js.coffee
+++ lib/assets/javascripts/stark/stark.js.coffee
@@ -67,7 +67,9 @@
       @load_bootstrap_data()
       if @current_user?
         @log "appear to be logged in, using default route: #{@default_route}"
-        return @router.route(@default_route)
+        # TODO we don't properly pass bootstrapped data into the default route so for now just redir
+        # return @router.route(@default_route)
+        window.location = @default_route

       @log "sending to login page: #{@login_route}"
       @router.route(@login_route)


Fix live updating for discussions

Fix live updating for discussions

--- lineman/app/services/message_channel_service.coffee
+++ lineman/app/services/message_channel_service.coffee
@@ -15,23 +15,20 @@
             @messageReceived(data, onMessageReceived)

     messageReceived: (data, onMessageReceived) ->
-      console.log 'message received: ', data
       if data.memo?
-        console.log 'new memo!', data.memo
-        memo = data.memo
-        if memo.kind == 'comment_destroyed'
-          if comment = Records.comments.get(memo.data.comment_id)
-            comment.destroy()
-        if memo.kind == 'comment_updated'
-          Records.comments.new(memo.data.comment)
-          Records.import(memo.data)
-        if memo.kind == 'comment_unliked'
-          if comment = Records.comments.get(memo.data.comment_id)
-            comment.removeLikerId(memo.data.user_id)
+        switch data.memo.kind
+          when 'comment_destroyed'
+            if comment = Records.comments.find(memo.data.comment_id)
+              comment.destroy()
+          when 'comment_updated'
+            Records.comments.initialize(memo.data.comment)
+            Records.import(memo.data)
+          when 'comment_unliked'
+            if comment = Records.comments.find(memo.data.comment_id)
+              comment.removeLikerId(memo.data.user_id)

       if data.event?
-        console.log 'new event', data.event
-        Records.events.new(data.event)
+        Records.events.initialize(data.event)

       # maybe indent this one
       Records.import(data)


fixed multiple renderer bug

fixed multiple renderer bug

--- lms/static/coffee/src/mathjax_delay_renderer.coffee
+++ lms/static/coffee/src/mathjax_delay_renderer.coffee
@@ -8,11 +8,14 @@
   elapsedTime: 0
   mathjaxDelay: 0
   mathjaxTimeout: undefined
-  bufferId: "mathjax_delay_buffer"
+  bufferId = "mathjax_delay_buffer"
+  numBuffers = 0

   constructor: (params) ->
     params = params || {}
     @maxDelay = params["maxDelay"] || @maxDelay
+    @bufferId = params["bufferId"] || (bufferId + numBuffers)
+    numBuffers += 1
     @$buffer = $("<div>").attr("id", @bufferId).css("display", "none").appendTo($("body"))

   # render: (params) ->


CollaborativeWorkspace: scroll to bottom of chat view after after it is shown

--- client/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -63,7 +63,9 @@
       @userRef.child("status").set "online"

       @loader.destroy()
-      @chatView?.show()
+      if @chatView
+        @chatView.show()
+        @utils.defer => @chatView.scrollToBottom()

       @emit "WorkspaceSyncedWithRemote"


component-lab: improve wording on trial charge info

--- client/component-lab/TrialChargeInfo/TrialChargeInfo.coffee
+++ client/component-lab/TrialChargeInfo/TrialChargeInfo.coffee
@@ -54,7 +54,7 @@
       </Col>
       <Col xs={7} className={textStyles.right}>

-          Your monthly charge will be: ${pricePerSeat} per user
+          Monthly charge after trial: ${pricePerSeat} per user

       </Col>
     </Row>


Panel: Moved getting pane class logic to a method.

--- client/landing/app/CommonViews/workspace/panel/panel.coffee
+++ client/landing/app/CommonViews/workspace/panel/panel.coffee
@@ -68,14 +68,7 @@
       @createPane paneOptions, @getPaneContainerByIndex index

   createPane: (paneOptions, targetContainer) ->
-    paneTypesToPaneClass =
-      "terminal"         : @TerminalPaneClass
-      "editor"           : @EditorPaneClass
-      "video"            : @VideoPaneClass
-      "preview"          : @PreviewPaneClass
-
-    paneType             = paneOptions.type
-    PaneClass            = paneTypesToPaneClass[paneType]
+    PaneClass            = @getPaneClass paneOptions.type
     paneOptions.delegate = @
     pane                 = new PaneClass paneOptions

@@ -86,6 +79,15 @@
   # GETTERS #
   getPaneContainerByIndex: (index) ->
     return  @panesContainer[index]
+
+  getPaneClass: (paneType) ->
+    paneTypesToPaneClass =
+      "terminal"         : @TerminalPaneClass
+      "editor"           : @EditorPaneClass
+      "video"            : @VideoPaneClass
+      "preview"          : @PreviewPaneClass
+
+    return  paneTypesToPaneClass[paneType]

   # LAYOUT CREATOR HELPERS #
   createSplitView: (type, views) ->


Updating application menu

Updating application menu

--- lib/spark-ide-status-bar-view.coffee
+++ lib/spark-ide-status-bar-view.coffee
@@ -33,15 +33,23 @@
     hasToken = !!settings.access_token
     statusElement = this.find('#spark-login-status')
     statusElement.empty()
-
+
+    menu = atom.menu.template.filter (value) ->
+      value.label == 'Spark IDE'
+
     if hasToken
       statusElement.text(settings.username)
+      menu[0].submenu[0].label = 'Log out ' + settings.username
+      menu[0].submenu[0].command = 'spark-ide:logout'
     else
       loginButton = $('<a/>').text('Click to log in to Spark Cloud...')
       statusElement.append(loginButton)
       loginButton.on 'click', =>
         atom.workspaceView.trigger 'spark-ide:login'
-
+      menu[0].submenu[0].label = 'Log in to Spark Cloud...'
+      menu[0].submenu[0].command = 'spark-ide:login'
+    atom.menu.update()
+
   setStatus: (text, type = null) ->
       el = this.find('.spark-log')
       el.text(text)


Change trigger to update filters after ProductsCtrl is loaded.

--- app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee
+++ app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee
@@ -12,6 +12,11 @@
   $scope.supplied_properties = null
   $scope.showFilterSidebar = false

+  # Update filters after initial load of shop tab
+  $timeout =>
+    $scope.update_filters()
+
+  # Update filters when order cycle changed
   $rootScope.$on "orderCycleSelected", ->
     $scope.update_filters()
     $scope.clearAll()
@@ -49,7 +54,6 @@
   $scope.$watch 'query', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
   $scope.$watchCollection 'activeTaxons', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
   $scope.$watchCollection 'activeProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
-  $scope.$watch 'Products.loading', (newValue, oldValue) -> $scope.update_filters() if newValue == false && newValue != oldValue

   $scope.loadProducts = ->
     $scope.page = 1


Add documentation for Analytics Payload Loader

--- src/loader.coffee
+++ src/loader.coffee
@@ -1,3 +1,12 @@
+###
+Analytics Payload Loader
+
+This is the `analytics.js` file that loads the actual Analytics payload. It
+utilizes the Friendly iFrames (FiF) technique in order to load the JavaScript
+library without blocking the `window.onload` event.
+
+@see http://goo.gl/VLDc3F More information on FiF technique
+###
 ((url)->
   # Section 1
   iframe = document.createElement('iframe')
@@ -10,7 +19,7 @@
   where = where[where.length - 1]
   where.parentNode.insertBefore(iframe, where)

-# Section 2
+  # Section 2
   try
     doc = iframe.contentWindow.document
   catch err


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -18,8 +18,8 @@

         #$(@node).fadeOut().fadeIn()
         @container = $(@node).parent()
-        #@instanceDataId = $(@node).attr('data-id')
-        #console.log('@instanceDataId',@instanceDataId)
+        @instanceDataId = $(@node).attr('data-id')
+        console.log('onData -> @instanceDataId: ',@instanceDataId)

         #
         # CONFIG ZONE


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -240,9 +240,7 @@
             .attr("fill", (d, i) ->
                 return color(i)
             )
-
             .attr("d", arc)
-        console.log("arc",arc)
         #path.transition()
         #    .duration(1500)
         #    #.attrTween("d", pieTween);


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -6,425 +6,6 @@
     #@buildPieStructure()
     #@render(data.data)
     @update(data.data)
-  buildPieStructure: ->
-    width = 300
-    height = 300
-    radius = Math.min(width, height) / 2
-    @color = d3.scale.category20()
-    pie = d3.layout.pie().sort(null)
-    arc = d3.svg.arc().innerRadius(radius - 100).outerRadius(radius - 50)
-    svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
-    return
-  oldPieData: ''
-  pieTween: (d, i) ->
-    `var i`
-    that = this
-    #theOldDataInPie = @oldPieData
-    # Interpolate the arcs in data space
-    #s0 = undefined
-    #e0 = undefined
-    #if theOldDataInPie[i]
-    #  s0 = theOldDataInPie[i].startAngle
-    #  e0 = theOldDataInPie[i].endAngle
-    #else if !theOldDataInPie[i] and theOldDataInPie[i - 1]
-    #  s0 = theOldDataInPie[i - 1].endAngle
-    #  e0 = theOldDataInPie[i - 1].endAngle
-    #else if !theOldDataInPie[i - 1] and theOldDataInPie.length > 0
-    #  s0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
-    #  e0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
-    #else
-    #  s0 = 0
-    #  e0 = 0
-    # TS
-    s0 = 0
-    e0 = 0
-    #
-    i = d3.interpolate({
-      startAngle: 0
-      endAngle: 0
-    },
-      startAngle: d.startAngle
-      endAngle: d.endAngle)
-    (t) ->
-      b = i(t)
-      return @arc b
-  removePieTween: (d, i) ->
-    `var i`
-    that = this
-    s0 = 2 * Math.PI
-    e0 = 2 * Math.PI
-    i = d3.interpolate({
-      startAngle: d.startAngle
-      endAngle: d.endAngle
-    },
-      startAngle: s0
-      endAngle: e0)
-    (t) ->
-      b = i(t)
-      arc b
-  updateITWORKS: (dataSet) ->
-    if !dataSet
-        dataSet = @get("data")
-    if !dataSet
-        return
-    $(@node).children("svg").remove();
-    width = 750
-    height = 450
-    radius = Math.min(width, height) / 2
-    radiuso = 135
-    radiusi = 90
-    color = d3.scale.category20()
-    pie = d3.layout.pie()#.sort(null)
-    arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
-    svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
-    console.log 'update pie', dataSet
-    that = this
-    piedata = pie(dataSet[0].value)
-    console.log 'update pie', piedata
-    #create a marker element if it doesn't already exist
-    defs = svg.select('defs')
-    if defs.empty()
-      defs = svg.append('defs')
-    marker = defs.select('marker#circ')
-    if marker.empty()
-      defs.append('marker').attr('id', 'circ').attr('markerWidth', 6).attr('markerHeight', 6).attr('refX', 3).attr('refY', 3).append('circle').attr('cx', 3).attr('cy', 3).attr 'r', 3
-    #Create/select <g> elements to hold the different types of graphics
-    #and keep them in the correct drawing order
-    pathGroup = svg.select('g.piePaths')
-    if pathGroup.empty()
-      pathGroup = svg.append('g').attr('class', 'piePaths')
-    pointerGroup = svg.select('g.pointers')
-    if pointerGroup.empty()
-      pointerGroup = svg.append('g').attr('class', 'pointers')
-    labelGroup = svg.select('g.labels')
-    if labelGroup.empty()
-      labelGroup = svg.append('g').attr('class', 'labels')
-    path = pathGroup.selectAll('path.pie').data(piedata)
-    #console.log("path",this.path.empty())
-    path.enter().append('path').attr('class', 'pie').attr 'fill', (d, i) ->
-      color i
-    path.attr('d',arc)
-        .exit()
-    #@path.transition().duration(1500).attrTween 'd', that.pieTween
-    #path.transition()
-    #    .duration(1000)
-    #    #.attrTween("d", pieTween)
-    #    .attrTween("d", (d,i) ->
-    #        #console.log("d",d)
-    #        #console.log("i",i)
-    #        i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
-    #        return (t) ->
-    #            b = i(t)
-    #            return arc(b)
-    #    )
-    #@path.exit().transition().duration(300).attrTween('d', that.removePieTween).remove()
-    #path.exit()
-    #    .transition()
-    #    .duration(300)
-    #    .attrTween("d", (d,i) ->
-    #        i = d3.interpolate({startAngle: d.startAngle,endAngle: d.endAngle},{startAngle: 2 * Math.PI,endAngle: 2 * Math.PI})
-    #        return (t) ->
-    #            b = i(t)
-    #            return arc(b)
-    #    )
-    #    .remove()
-    labels = labelGroup.selectAll('text').data(piedata.sort((p1, p2) ->
-      p1.startAngle - p2.startAngle
-    ))
-    labels.enter().append('text').attr 'text-anchor', 'middle'
-    labels.exit().remove()
-    labelLayout = d3.geom.quadtree().extent([
-      [
-        -width
-        -height
-      ]
-      [
-        width
-        height
-      ]
-    ]).x((d) ->
-      d.x
-    ).y((d) ->
-      d.y
-    )([])
-    #create an empty quadtree to hold label positions
-    maxLabelWidth = 0
-    maxLabelHeight = 0
-    labels.text((d) ->
-      # Set the text *first*, so we can query the size
-      # of the label with .getBBox()
-      d.value
-    ).each((d, i) ->
-      # Move all calculations into the each function.
-      # Position values are stored in the data object
-      # so can be accessed later when drawing the line
-      ### calculate the position of the center marker ###
-      a = (d.startAngle + d.endAngle) / 2
-      #trig functions adjusted to use the angle relative
-      #to the "12 o'clock" vector:
-      #console.log 'ts: a', a
-      d.cx = Math.sin(a) * (radius - 75)
-      d.cy = -Math.cos(a) * (radius - 75)
-      ### calculate the default position for the label,
-         so that the middle of the label is centered in the arc
-      ###
-      bbox = @getBBox()
-      #bbox.width and bbox.height will
-      #describe the size of the label text
-      labelRadius = radius - 20
-      d.x = Math.sin(a) * labelRadius
-      d.l = d.x - bbox.width / 2 - 2
-      d.r = d.x + bbox.width / 2 + 2
-      d.y = -Math.cos(a) * (radius - 20)
-      d.b = d.oy = d.y + 5
-      d.t = d.y - bbox.height - 5
-      #console.log 'ts: d', d
-      ### check whether the default position
-         overlaps any other labels
-      ###
-      conflicts = []
-      labelLayout.visit (node, x1, y1, x2, y2) ->
-        #recurse down the tree, adding any overlapping
-        #node is the node in the quadtree,
-        #node.point is the value that we added to the tree
-        #x1,y1,x2,y2 are the bounds of the rectangle that
-        #this node covers
-        if x1 > d.r + maxLabelWidth / 2 or x2 < d.l - maxLabelWidth / 2 or y1 > d.b + maxLabelHeight / 2 or y2 < d.t - maxLabelHeight / 2
-          return true
-        #don't bother visiting children or checking this node
-        p = node.point
-        v = false
-        h = false
-        if p
-          #p is defined, i.e., there is a value stored in this node
-          h = p.l > d.l and p.l <= d.r or p.r > d.l and p.r <= d.r or p.l < d.l and p.r >= d.r
-          #horizontal conflict
-          v = p.t > d.t and p.t <= d.b or p.b > d.t and p.b <= d.b or p.t < d.t and p.b >= d.b
-          #vertical conflict
-          if h and v
-            conflicts.push p
-          #add to conflict list
-        return
-      if conflicts.length
-        console.log d, ' conflicts with ', conflicts
-        rightEdge = d3.max(conflicts, (d2) ->
-          `var maxLabelHeight`
-          `var maxLabelWidth`
-          d2.r
-        )
-        d.l = rightEdge
-        d.x = d.l + bbox.width / 2 + 5
-        d.r = d.l + bbox.width + 10
-      else
-        console.log 'no conflicts for ', d
-      ### add this label to the quadtree, so it will show up as a conflict
-         for future labels.
-      ###
-      labelLayout.add d
-      maxLabelWidth = Math.max(maxLabelWidth, bbox.width + 10)
-      maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
-      return
-    )
-    #.transition()
-    .attr('x', (d) ->
-      d.x
-    ).attr 'y', (d) ->
-      d.y
-    pointers = pointerGroup.selectAll('path.pointer').data(piedata)
-    pointers.enter().append('path').attr('class', 'pointer').style('fill', 'none').style('stroke', 'black').attr 'marker-end', 'url(#circ)'
-    pointers.exit().remove()
-    #pointers.transition().attr 'd', (d) ->
-    #  console.log 'ts: d', d
-    #  if d.cx > d.l
-    #    console.log 'if d.cx > d.l','M' + d.l + 2 + ',' + d.b + 'L' + d.r - 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
-    #    'M' + d.l + 2 + ',' + d.b + 'L' + d.r - 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
-    #  else
-    #    console.log 'else', 'M' + d.r - 2 + ',' + d.b + 'L' + d.l + 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
-    #    'M' + d.r - 2 + ',' + d.b + 'L' + d.l + 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
-    return
-  updateITREALLYWORKS: (dataSet) ->
-    if !dataSet
-        dataSet = @get("data")
-    if !dataSet
-        return
-    $(@node).children("svg").remove();
-    width = 750
-    height = 450
-    radius = Math.min(width, height) / 2
-    radiuso = 135
-    radiusi = 90
-    color = d3.scale.category20()
-    pie = d3.layout.pie().value((d) -> d.value)
-    arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
-    svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
-    console.log 'update pie', dataSet
-    that = this
-    piedata = pie(dataSet)
-    console.log 'update pie', piedata
-    #create a marker element if it doesn't already exist
-    defs = svg.select('defs')
-    if defs.empty()
-      defs = svg.append('defs')
-    marker = defs.select('marker#circ')
-    if marker.empty()
-      defs.append('marker').attr('id', 'circ').attr('markerWidth', 6).attr('markerHeight', 6).attr('refX', 3).attr('refY', 3).append('circle').attr('cx', 3).attr('cy', 3).attr 'r', 3
-    #Create/select <g> elements to hold the different types of graphics
-    #and keep them in the correct drawing order
-    pathGroup = svg.select('g.piePaths')
-    if pathGroup.empty()
-      pathGroup = svg.append('g').attr('class', 'piePaths')
-    pointerGroup = svg.select('g.pointers')
-    if pointerGroup.empty()
-      pointerGroup = svg.append('g').attr('class', 'pointers')
-    labelGroup = svg.select('g.labels')
-    if labelGroup.empty()
-      labelGroup = svg.append('g').attr('class', 'labels')
-    path = pathGroup.selectAll('path.pie').data(piedata)
-    #console.log("path",this.path.empty())
-    path.enter().append('path').attr('class', 'pie').attr 'fill', (d, i) ->
-      color i
-    path.attr('d',arc)
-        .exit()
-    #@path.transition().duration(1500).attrTween 'd', that.pieTween
-    #path.transition().duration(1500).attrTween 'd', @pieTween
-    #path.transition()
-    #    .duration(1000)
-    #    #.attrTween("d", pieTween)
-    #    .attrTween("d", (d,i) ->
-    #        #console.log("d",d)
-    #        #console.log("i",i)
-    #        i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
-    #        return (t) ->
-    #            b = i(t)
-    #            return arc(b)
-    #    )
-    #@path.exit().transition().duration(300).attrTween('d', that.removePieTween).remove()
-    path.exit()
-    #    .transition()
-    #    .duration(300)
-    #    .attrTween("d", (d,i) ->
-    #        i = d3.interpolate({startAngle: d.startAngle,endAngle: d.endAngle},{startAngle: 2 * Math.PI,endAngle: 2 * Math.PI})
-    #        return (t) ->
-    #            b = i(t)
-    #            return arc(b)
-    #    )
-    #    .remove()
-    labels = labelGroup.selectAll('text').data(piedata.sort((p1, p2) ->
-      p1.startAngle - p2.startAngle
-    ))
-    labels.enter().append('text').attr 'text-anchor', 'middle'
-    labels.exit().remove()
-    labelLayout = d3.geom.quadtree().extent([
-      [
-        -width
-        -height
-      ]
-      [
-        width
-        height
-      ]
-    ]).x((d) ->
-      d.x
-    ).y((d) ->
-      d.y
-    )([])
-    #create an empty quadtree to hold label positions
-    maxLabelWidth = 0
-    maxLabelHeight = 0
-    labels.text((d) ->
-      # Set the text *first*, so we can query the size
-      # of the label with .getBBox()
-      #d.value
-      console.log 'ts: d', d
-      d.data.label
-    ).each((d, i) ->
-      # Move all calculations into the each function.
-      # Position values are stored in the data object
-      # so can be accessed later when drawing the line
-      ### calculate the position of the center marker ###
-      a = (d.startAngle + d.endAngle) / 2
-      #trig functions adjusted to use the angle relative
-      #to the "12 o'clock" vector:
-      #console.log 'ts: a', a
-      d.cx = Math.sin(a) * (radius - 75)
-      d.cy = -Math.cos(a) * (radius - 75)
-      ### calculate the default position for the label,
-         so that the middle of the label is centered in the arc
-      ###
-      bbox = @getBBox()
-      #bbox.width and bbox.height will
-      #describe the size of the label text
-      labelRadius = radius - 20
-      d.x = Math.sin(a) * labelRadius
-      d.l = d.x - bbox.width / 2 - 2
-      d.r = d.x + bbox.width / 2 + 2
-      d.y = -Math.cos(a) * (radius - 20)
-      d.b = d.oy = d.y + 5
-      d.t = d.y - bbox.height - 5
-      #console.log 'ts: d', d
-      ### check whether the default position
-         overlaps any other labels
-      ###
-      conflicts = []
-      labelLayout.visit (node, x1, y1, x2, y2) ->
-        #recurse down the tree, adding any overlapping
-        #node is the node in the quadtree,
-        #node.point is the value that we added to the tree
-        #x1,y1,x2,y2 are the bounds of the rectangle that
-        #this node covers
-        if x1 > d.r + maxLabelWidth / 2 or x2 < d.l - maxLabelWidth / 2 or y1 > d.b + maxLabelHeight / 2 or y2 < d.t - maxLabelHeight / 2
-          return true
-        #don't bother visiting children or checking this node
-        p = node.point
-        v = false
-        h = false
-        if p
-          #p is defined, i.e., there is a value stored in this node
-          h = p.l > d.l and p.l <= d.r or p.r > d.l and p.r <= d.r or p.l < d.l and p.r >= d.r
-          #horizontal conflict
-          v = p.t > d.t and p.t <= d.b or p.b > d.t and p.b <= d.b or p.t < d.t and p.b >= d.b
-          #vertical conflict
-          if h and v
-            conflicts.push p
-          #add to conflict list
-        return
-      if conflicts.length
-        console.log d, ' conflicts with ', conflicts
-        rightEdge = d3.max(conflicts, (d2) ->
-          `var maxLabelHeight`
-          `var maxLabelWidth`
-          d2.r
-        )
-        d.l = rightEdge
-        d.x = d.l + bbox.width / 2 + 5
-        d.r = d.l + bbox.width + 10
-      else
-        console.log 'no conflicts for ', d
-      ### add this label to the quadtree, so it will show up as a conflict
-         for future labels.
-      ###
-      labelLayout.add d
-      maxLabelWidth = Math.max(maxLabelWidth, bbox.width + 10)
-      maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
-      return
-    )
-    #.transition()
-    .attr('x', (d) ->
-      d.x
-    ).attr 'y', (d) ->
-      d.y
-    pointers = pointerGroup.selectAll('path.pointer').data(piedata)
-    pointers.enter().append('path').attr('class', 'pointer').style('fill', 'none').style('stroke', 'black').attr 'marker-end', 'url(#circ)'
-    pointers.exit().remove()
-    #pointers.transition().attr 'd', (d) ->
-    #  console.log 'ts: d', d
-    #  if d.cx > d.l
-    #    console.log 'if d.cx > d.l','M' + d.l + 2 + ',' + d.b + 'L' + d.r - 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
-    #    'M' + d.l + 2 + ',' + d.b + 'L' + d.r - 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
-    #  else
-    #    console.log 'else', 'M' + d.r - 2 + ',' + d.b + 'L' + d.l + 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
-    #    'M' + d.r - 2 + ',' + d.b + 'L' + d.l + 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
-    return
   update: (dataSet) ->
     if !dataSet
         dataSet = @get("data")
@@ -443,6 +24,7 @@
     console.log 'update pie', dataSet
     #that = this
     piedata = pie(dataSet)
+    # Remove zero values
     piedata = piedata.filter (pd) -> pd.value isnt 0
     console.log 'update pie', piedata
     #create a marker element if it doesn't already exist


Agent pie: working on label collisions

Agent pie: working on label collisions

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -271,7 +271,7 @@
                 return

             if conflicts.length
-                #console.log d, ' conflicts with ', conflicts
+                console.log d, ' conflicts with ', conflicts
                 rightEdge = d3.max(conflicts, (d2) ->
                     #`var maxLabelHeight`
                     #`var maxLabelWidth`
@@ -280,8 +280,8 @@
                 d.l = rightEdge
                 d.x = d.l + bbox.width / 2 + 5
                 d.r = d.l + bbox.width + 10
-            #else
-            #    console.log 'no conflicts for ', d
+            else
+                console.log 'no conflicts for ', d

             ### add this label to the quadtree, so it will show up as a conflict
                for future labels.


address code review comments

address code review comments

--- app/index.coffee
+++ app/index.coffee
@@ -114,6 +114,7 @@

     $(window).bind('beforeunload', (e) ->
         AnalyticsLogger.logEvent 'leave'
+        event.preventDefault()
     )

     app.stack.el.appendTo 'body'


Remove superflous code from evaluator test


no need for isNumber anymore

no need for isNumber anymore

--- coffee/src/humanize.coffee
+++ coffee/src/humanize.coffee
@@ -22,9 +22,6 @@
     for arrItem, index in arr
         return index if arrItem is item
     return -1
-
-isNumber = (value) ->
-    typeof value is 'number' or toString.call(value) is '[object Number]'

 isNaN = (value) ->
     value isnt value


Client: Activity: Added DropContainer and ComingSoon modal for video call functionality

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -9,6 +9,9 @@
 PublicChannelLink    = require 'activity/components/publicchannellink'
 ImmutableRenderMixin = require 'react-immutable-render-mixin'
 PublicChatPane       = require 'activity/components/publicchatpane'
+Link                 = require 'app/components/common/link'
+Modal                = require 'app/components/modal'
+showNotification     = require 'app/util/showNotification'

 module.exports = class ChannelThreadPane extends React.Component
@@ -32,6 +35,8 @@
     super props

     @state =
+      showDropTarget        : no
+      isComingSoonModalOpen : no
       channelThread         : immutable.Map()
       channelThreadMessages : immutable.List()
       messageThread         : immutable.Map()
@@ -43,6 +48,69 @@

   componentWillReceiveProps: (nextProps) -> reset nextProps, @state
+
+
+  startVideoCall: ->
+
+    @setState isComingSoonModalOpen: yes
+
+
+  onDragEnter: (event)->
+
+    kd.utils.stopDOMEvent event
+    @setState showDropTarget: yes
+
+
+  onDragOver: (event)-> kd.utils.stopDOMEvent event
+
+
+  onDragLeave: (event)->
+
+    kd.utils.stopDOMEvent event
+    @setState showDropTarget: no
+
+
+  onDrop: (event)->
+
+    kd.utils.stopDOMEvent event
+    @setState showDropTarget: no
+    showNotification 'Coming soon...', type: 'main'
+
+
+  getDropTargetClassNames: -> classnames
+    'ChannelThreadPane-dropContainer': yes
+    'hidden': not @state.showDropTarget
+
+
+  onClose: ->
+
+    @setState isComingSoonModalOpen: no
+
+
+  renderComingSoonModal: ->
+
+    if @state.isComingSoonModalOpen
+      title = 'Coming Soon'
+      <Modal className='ComingSoonModal' isOpen={yes} onClose={@bound 'onClose'}>
+

+
COLLABORATE USING VIDEO CHAT

+          Coming really soon...
+

+

+
+

+      </Modal>
+
+
+  renderDropSection: ->
+
+

+
Drop VM's here
 to start collaborating

+

   renderHeader: ->
@@ -57,11 +125,23 @@
     </ThreadHeader>

+  renderVideoCallArea: ->
+
+    <Link className='ChannelThreadPane-videoCall' onClick={@bound 'startVideoCall'}>
+      Start a Video Call
+
+    </Link>
+
+
   render: ->

-

+      {@renderComingSoonModal()}
+

+        {@renderDropSection()}

           {@renderHeader()}
+          {@renderVideoCallArea()}


Add price and cache do sku-selector

Add price and cache do sku-selector

--- src/script/portal-sku-selector.coffee
+++ src/script/portal-sku-selector.coffee
@@ -321,12 +321,12 @@
 				@showConfirmButton(selectedSku)
 			else
 				$(window).trigger 'modalHide.vtex'
-				$.get($.skuSelector.getAddUrlForSku(selectedSku.sku, selectedSku.sellerId, 1, @productData.salesChannel, false))
+				$.get($.skuSelector.getAddUrlForSku(selectedSku.sku, selectedSku.sellerId, 1, @productData.salesChannel, false, selectedSku.bestPrice, selectedSku.cacheVersionUsedToCallCheckout))
 				.done (data) =>
 						$(window).trigger 'productAddedToCart' # DEPRECATED
 						$(window).trigger 'cartProductAdded.vtex'
 				.fail (jqXHR, status) =>
-						window.location.href = $.skuSelector.getAddUrlForSku(selectedSku.sku, selectedSku.sellerId, 1, @productData.salesChannel)
+						window.location.href = $.skuSelector.getAddUrlForSku(selectedSku.sku, selectedSku.sellerId, 1, @productData.salesChannel, true, selectedSku.bestPrice, selectedSku.cacheVersionUsedToCallCheckout)
 				return false
 		else
 			@findwarning().show().text('Por favor, escolha: ' + @findUndefinedDimensions()[0].name)
@@ -370,13 +370,13 @@

 	showBuyButton: (sku) =>
 		@findbuyButton().show().parent().show()
-		@findbuyButton().attr('href', $.skuSelector.getAddUrlForSku(sku.sku, sku.sellerId, 1, @salesChannel, @options.redirect)) if sku
+		@findbuyButton().attr('href', $.skuSelector.getAddUrlForSku(sku.sku, sku.sellerId, 1, @salesChannel, @options.redirect, sku.bestPrice, sku.cacheVersionUsedToCallCheckout)) if sku

 	showConfirmButton: (sku) =>
 		dimensionsText = $.map(sku.dimensions, (k, v) -> k).join(', ')

 		@findconfirmButton()
-		.attr('href', $.skuSelector.getAddUrlForSku(sku.sku, sku.sellerId, 1, @salesChannel, @options.redirect))
+		.attr('href', $.skuSelector.getAddUrlForSku(sku.sku, sku.sellerId, 1, @salesChannel, @options.redirect, sku.bestPrice, sku.cacheVersionUsedToCallCheckout))
 		.show()
 		.find('.skuselector-confirm-dimensions').text(dimensionsText)

@@ -432,8 +432,10 @@

 # SHARED STUFF
 $.skuSelector =
-	getAddUrlForSku: (sku, seller = 1, quantity = 1, salesChannel = 1, redirect = true) ->
-		"/checkout/cart/add?qty=#{quantity}&seller=#{seller}&sku=#{sku}&sc=#{salesChannel}&redirect=#{redirect}"
+	getAddUrlForSku: (sku, seller = 1, quantity = 1, salesChannel = 1, redirect = true, price, cv) ->
+		if price and cv
+			return "/checkout/cart/add?qty=#{quantity}&seller=#{seller}&sku=#{sku}&sc=#{salesChannel}&redirect=#{redirect}&price=#{price}&cv=#{cv}"
+		return "/checkout/cart/add?qty=#{quantity}&seller=#{seller}&sku=#{sku}&sc=#{salesChannel}&redirect=#{redirect}"

 # SKU ON QUERYSTRING
 $(document).ready ->


improvements to log entries alert handling

--- src/scripts/logentries-hearsay.coffee
+++ src/scripts/logentries-hearsay.coffee
@@ -60,7 +60,7 @@
         user.room = process.env.LOGENTRIES_ROOM
       user.type = 'groupchat'

-      robot.send user, "[#{name} Logs]: #{alert}"
+      robot.send user, "[#{name} #{alert} #{data.host.name}]: #{data.event.m}"

       res.writeHead 200, {'Content-Type': 'text/plain'}
       res.end 'Thanks\n'
@@ -68,4 +68,4 @@
       robot.logger.error "Logentries: unexpected message format"
       robot.logger.info JSON.stringify(req.body)
       res.writeHead 422, {'Content-Type': 'text/plain'}
-      res.end 'Expected payload JSON string to exist\n'+      res.end 'Expected payload JSON string to exist\n'


throttle synctable update

throttle synctable update

--- src/smc-hub/postgres-synctable.coffee
+++ src/smc-hub/postgres-synctable.coffee
@@ -10,6 +10,7 @@

 immutable    = require('immutable')
 async        = require('async')
+underscore   = require('underscore')

 {defaults} = misc = require('smc-util/misc')
 required = defaults.required
@@ -661,6 +662,8 @@
             @_select_columns = misc.keys(SCHEMA[@_table].fields)

         @_select_query = "SELECT #{(quote_field(x) for x in @_select_columns)} FROM #{@_table}"
+
+        @_update = underscore.throttle(@_update, 500)

         @_init (err) => cb(err, @)


Stepped okSubmits back

Stepped okSubmits back

--- server/api/dashboard.coffee
+++ server/api/dashboard.coffee
@@ -56,7 +56,7 @@
 export default dashboard = () ->
     queries =
         # remember that months start from 0
-        ok: {ok: 1, lastSubmitTime: {$gt: new Date(2017, 9, 1)}},
+        ok: {ok: 1, lastSubmitTime: {$gt: new Date(2017, 9, 10)}},
         wa: {solved: 0, ok: 0, ignored: 0, attempts: {$gt: 0}},
         ig: {ignored: 1},
         ac: {solved: 1}


More spell check!

More spell check!

--- .atom/config.cson
+++ .atom/config.cson
@@ -205,14 +205,17 @@
       "source.makefile"
       "source.plist"
       "source.python"
+      "source.rst"
       "source.shell"
       "source.sql"
       "source.yaml"
       "text.git-commit"
       "text.html.basic"
       "text.html.erb"
+      "text.md"
       "text.plain"
       "text.plain.null-grammar"
+      "text.restructuredtext"
       "text.todo"
     ]
   "tab-control":


Adjusts atom config file.

Adjusts atom config file.

--- .atom/config.cson
+++ .atom/config.cson
@@ -96,7 +96,6 @@
     ]
   "linter-ui-default":
     panelHeight: 235
-    showPanel: true
   minimap:
     plugins:
       cursorline: true


fixup! Generalize current selection on timeline

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -187,7 +187,7 @@

     $scope.fileAdded = (file) ->
       return unless editing
-      $scope.track?.upload(file)
+      $scope.current?.upload(file)

     $scope.fileSuccess = Store.fileSuccess
     $scope.fileProgress = Store.fileProgress
@@ -200,8 +200,8 @@
       ratechange: ->
         $scope.playing = video[0].playbackRate
       timeupdate: ->
-        if $scope.track?.asset
-          $scope.position = $scope.track.asset.segment.base + 1000*video[0].currentTime
+        if $scope.current?.asset
+          $scope.position = $scope.current.asset.segment.base + 1000*video[0].currentTime
       ended: ->
         $scope.playing = 0
         # look for something else to play?


added missing originalFsPath

added missing originalFsPath

--- app/coffee/FileHandler.coffee
+++ app/coffee/FileHandler.coffee
@@ -47,10 +47,12 @@

 	_getConvertedFileAndCache: (bucket, key, convertedKey, opts, callback)->
 		convertedFsPath = ""
+		originalFsPath = ""
 		async.series [
 			(cb) =>
 				@_convertFile bucket, key, opts, (err, fileSystemPath, originalFsPath) ->
 					convertedFsPath = fileSystemPath
+					originalFsPath = originalFsPath
 					cb err
 			(cb)->
 				ImageOptimiser.compressPng convertedFsPath, cb


fix rubocop path

fix rubocop path

--- .atom/config.cson
+++ .atom/config.cson
@@ -33,7 +33,7 @@
     statusIconScope: "File"
   "linter-alex": {}
   "linter-rubocop":
-    command: "/usr/local/opt/rbenv/versions/2.3.0/bin/rubocop"
+    command: "/usr/local/opt/rbenv/shims/rubocop"
   "linter-ruby": {}
   "linter-write-good": {}
   "markdown-preview":


atom settings

atom settings

--- .atom/config.cson
+++ .atom/config.cson
@@ -29,6 +29,7 @@
     showErrorTabFile: false
     showErrorTabProject: false
     statusIconScope: "File"
+  "linter-alex": {}
   "linter-rubocop":
     command: "/usr/local/opt/rbenv/versions/2.3.0/bin/rubocop"
   "linter-ruby": {}


remove watching shortcut

remove watching shortcut

--- app/js/views/shortcuts.coffee
+++ app/js/views/shortcuts.coffee
@@ -21,10 +21,6 @@
     'Go to Mentioned':
       key: 'g m'
       action: -> Backbone.history.navigate 'mentioned', trigger: true
-
-    'Go to Watching':
-      key: 'g w'
-      action: -> Backbone.history.navigate 'watching', trigger: true

     'Open help for keyboard shortcuts':
       key: '?'


fix typos and indentation

fix typos and indentation

--- templates/dev-node-coffee/gulpfile.coffee
+++ templates/dev-node-coffee/gulpfile.coffee
@@ -11,10 +11,10 @@

 ### Config ###
 config = require('./package.json')['gulp-config'] or {}
-  source = config.source or './source'
-  build = config.build or './build'
-  dist = config.dist or './dist'
-  test = config.test or './test'
+source = config.source or './source'
+build = config.build or './build'
+dist = config.dist or './dist'
+test = config.test or './test'

 ### Clean ###

@@ -86,7 +86,7 @@
     .pipe jshint.reporter 'default'

 gulp.task 'jshint-test', ->
-  gulp.src ["#{build}/#{test}/**/*.js"]
+  gulp.src [ "#{build}/#{test}/**/*.js" ]
     .pipe jshint '.jshintrc'
     .pipe jshint.reporter 'default'

@@ -96,7 +96,7 @@
 ### Build ###
 gulp.task 'build-source', ->
   gulp
-    .src ["#{source}/**/*.coffee", "!#{source}/#{test}/**/*" ]
+    .src [ "#{source}/**/*.coffee", "!#{source}/#{test}/**/*" ]
     .pipe coffee(bare:true).on 'error', util.log
     .pipe gulp.dest build

@@ -125,7 +125,7 @@
     .pipe gulp.dest "./#{test}"

 gulp.task 'dist', (cb) ->
-  runSequence 'dist-build', 'dist-test', 'copy-dist'], 'concat-dist', cb
+  runSequence [ 'dist-build', 'dist-test', 'copy-dist' ], 'concat-dist', cb

 ### Test ###
 gulp.task 'mocha', ->


FIX: Remove clipping of x axis value for burndown.

No values should be < 0

--- src/app/visualisations/progress-burndown-chart.coffee
+++ src/app/visualisations/progress-burndown-chart.coffee
@@ -45,24 +45,10 @@
         '#E01B5D'

     #
-    # Clips x values to be at the y = 0 intercept if y < 0
+    # No need to clip x axis
     #
     xAxisClipNegBurndown = (d) ->
-      if d[1] < 0.0
-        # find the x intercept at y = 0
-        # know originX is the origin date of the graph (i.e. burnoff is still 100%)
-        originX = $scope.project.burndown_chart_data[0].values[0][0]
-        # work off the 100% point and this point
-        [pt1x, pt1y] = [originX, 1]
-        [pt2x, pt2y] = [d[0], d[1]]
-        # find gradient
-        m    = (pt2y - pt1y) / (pt2x - pt1x)
-        # get actual y intercept
-        c    = pt1y - m * pt1x
-        # solve x intercept via 0 = mx+c
-        -c/m
-      else
-        d[0]
+      d[0]

     #
     # Clips y to 0 if y < 0


Update recent-posts.coffee

Update recent-posts.coffee

--- scripts/recent-posts.coffee
+++ scripts/recent-posts.coffee
@@ -3,7 +3,7 @@
 document.addEventListener "DOMContentLoaded", (event) ->
   blogListElement = document.getElementById('latest-blog')
   x = new XMLHttpRequest()
-  x.open("GET", "http://phonegap.com/blog/recent-posts.xml", true)
+  x.open("GET", "/blog/recent-posts.xml", true)

   posts = []
   months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']


aggregate transactions by date before running a cumnulative balance. requires convertng to a hash and then back to a list again, so also affects ordering of when we sort by and format dates.

--- app/models/group-model.coffee
+++ app/models/group-model.coffee
@@ -22,26 +22,43 @@
       @getActiveBuckets('funded', 'fundedAt')

     balanceOverTime: ->
+      # balance over time combines money in (allocations) and money out (paid
+      # buckets).
       paidBuckets = _.filter @buckets(), (bucket) ->
         bucket.isPaid()

       paymentsByDate = _.map paidBuckets, (bucket) ->
         [
-          parseInt(moment(bucket.paidAt).format('x')), bucket.totalContributions * -1
+          bucket.paidAt, bucket.totalContributions * -1
         ]

       allocationsByDate = _.map @allocations(), (allocation) ->
         [
-          parseInt(moment(allocation.createdAt).format('x')), allocation.amount
+          allocation.createdAt, allocation.amount
         ]

-      allTransactions = paymentsByDate.concat allocationsByDate
+      transactionsByDate = paymentsByDate.concat allocationsByDate

-      transactionsByDate = _.sortBy allTransactions, (tx) ->
+      # aggregate all txs for a given date
+      aggregateByDate = {}
+      for item in transactionsByDate
+        d = moment(item[0]).format('l')
+        aggregateByDate[d] = if not aggregateByDate[d] then 0 else aggregateByDate[d]
+        aggregateByDate[d] += item[1]
+
+      # turn it back into a list, and convert date back to unix epoch
+      aggregateByDateList = []
+      for _date, _amt of aggregateByDate
+        unixDate = moment(_date, "MM/DD/YYYY").format('x')
+        aggregateByDateList.push [unixDate, _amt]
+
+      # sort it again...
+      aggregateByDateSorted = _.sortBy aggregateByDateList, (tx) ->
         tx[0]

+      # *then* convert it to balance
       balance = 0
-      balanceByDate = _.map transactionsByDate, (item) ->
+      balanceByDate = _.map aggregateByDateSorted, (item) ->
         balance += item[1]
         [item[0], parseInt(balance)]


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -61,5 +61,5 @@

   equals p1.y, 0

-
+  equals
 module()


Change wording.

Change wording.

--- app/pages/lab/visibility.cjsx
+++ app/pages/lab/visibility.cjsx
@@ -100,7 +100,8 @@

-        <Link to="/lab/#{@props.project.id}/workflows">Workflows Settings has moved.</Link>
+
Temporary notice

+
The workflow settings have moved from the visibility page to the <Link to="/lab/#{@props.project.id}/workflows">workflows page</Link>. For improved discoverability, we have moved these settings so all relevant information and configuration of workflows is in one place.


Remove the message link

Remove the message link

--- app/panoptes/account-bar.cjsx
+++ app/panoptes/account-bar.cjsx
@@ -18,7 +18,6 @@
           {@props.user.display_name}
         </button>
         <Avatar user={@props.user} />
-

         Profile


Filter retired subjects from the subject queue

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -185,7 +185,7 @@
               setTimeout fetchSubjectsAgain, 2000
         .then (subjects) ->
           nonLoadedSubjects = (newSubject for newSubject in subjects when newSubject isnt subjectToLoad)
-          filteredSubjects = nonLoadedSubjects.filter((subject) => subject if !subject.already_seen)
+          filteredSubjects = nonLoadedSubjects.filter((subject) => !subject.already_seen && !subject.retired)
           subjectsToLoad = if filteredSubjects.length > 0 then filteredSubjects else nonLoadedSubjects
           upcomingSubjects.forWorkflow[workflow.id].push subjectsToLoad...


Blur layer

Blur layer

--- app/scripts/scenery/City.coffee
+++ app/scripts/scenery/City.coffee
@@ -130,7 +130,7 @@
   #@addBackground(0, @level.visibleHeight - 140, Crafty.e('2D, WebGL, Color').color(col3).attr(z: -400, w: 297, h: 83), .37)

   bg = Crafty.e('2D, WebGL, cityLayer2, Collision, SunBlock, Horizon')
-    .attr(z: -505)
+    .attr(z: -505, blur: 1.2)
     .collision([4, 29, 72, 29, 72, 118, 4, 118])
     .colorDesaturation(Game.backgroundColor)
     .saturationGradient(.6, .6)
@@ -151,7 +151,7 @@

 generator.defineElement 'city-bridge', ->
   bg = Crafty.e('2D, WebGL, cityLayer2, Collision, SunBlock, Horizon')
-    .attr(z: -505)
+    .attr(z: -505, blur: 1.2)
     .collision([4, 29, 72, 29, 72, 118, 4, 118])
     .colorDesaturation(Game.backgroundColor)
     .saturationGradient(.6, .6)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/expirable.coffee
+++ test/expirable.coffee
@@ -27,6 +27,6 @@
   5.times ->
     obj.update()

-  e
+  equals obj.I.

 module()


Add root route to formatted links.

Add root route to formatted links.

--- core/htdocs_source/src/pods/oxivalue-format/component.coffee
+++ core/htdocs_source/src/pods/oxivalue-format/component.coffee
@@ -11,12 +11,12 @@
             evt.preventDefault()
             @container.lookup("route:openxpki").sendAjax
                 data:
-                    page:target.href.split("#")[1]
+                    page:target.href.split("#")[1].replace /\/openxpki\//, ""
                     target:target.target

     types:
         certstatus: (v) -> "#{v.label}"
-        link: (v) -> "#{v.label}"
+        link: (v) -> "#{v.label}"
         timestamp: (v) ->
           if v > 0
             moment.unix(v).utc().format("YYYY-MM-DD HH:mm:ss UTC")


Switch moves: Don't request a switch if battle is over

--- server/bw/data/moves.coffee
+++ server/bw/data/moves.coffee
@@ -1870,10 +1870,6 @@
       else
         40

-extendMove 'U-turn', ->
-  @afterSuccessfulHit = (battle, user, target) ->
-    battle.forceSwitch(user)
-
 extendMove 'Venoshock', ->
   @basePower = (battle, user, target) ->
     if target.has(Status.Toxic) || target.has(Status.Poison)
@@ -1881,9 +1877,13 @@
     else
       @power

-extendMove 'Volt Switch', ->
-  @afterSuccessfulHit = (battle, user, target) ->
-    battle.forceSwitch(user)
+makeSwitchMove = (moveName) ->
+  extendMove moveName, ->
+    @afterSuccessfulHit = (battle, user, target) ->
+      battle.forceSwitch(user) unless battle.isOver()
+
+makeSwitchMove 'Volt Switch'
+makeSwitchMove 'U-turn'

 makeStatusCureAttackMove 'Wake-Up Slap', Status.Sleep


Update Snore

Update Snore

--- server/bw/data/moves.coffee
+++ server/bw/data/moves.coffee
@@ -1597,8 +1597,13 @@

 extendMove 'Snore', ->
   @usableWhileAsleep = true
-  @use = (battle, user, target) ->
-    @fail(battle)  if !user.has(Status.Sleep)
+  oldUse = @use
+  @use = (battle, user, target) ->
+    if !user.has(Status.Sleep)
+      @fail(battle)
+      return false
+    else
+      oldUse.call(this, battle, user, target)

 extendMove 'Soak', ->
   @afterSuccessfulHit = (battle, user, target) ->


Updated to be the latest version of jquery-ui-rails

--- app/assets/javascripts/active_admin/sortable.js.coffee
+++ app/assets/javascripts/active_admin/sortable.js.coffee
@@ -1,4 +1,4 @@
-#= require jquery.ui.sortable
+#= require jquery-ui/sortable
 #= require jquery.mjs.nestedSortable

 window.ActiveAdminSortableEvent = do ->


fix image format

fix image format

--- snippets/downloads-svg.cson
+++ snippets/downloads-svg.cson
@@ -1,7 +1,7 @@
 '.source.gfm':
   'apmsource.gfm':
     'prefix': 'svg-apm-dl'
-    'body': '[![apm](https://img.shields.io/apm/dm/${1:package}.png${2:?style=flat${3:-square}})](${4:https://atom.io/${5:packages}/${1:package}})'
+    'body': '[![apm](https://img.shields.io/apm/dm/${1:package}.svg${2:?style=flat${3:-square}})](${4:https://atom.io/${5:packages}/${1:package}})'
   'Chocolateysource.gfm':
     'prefix': 'svg-chocolatey-dl'
     'body': '[![Chocolatey](https://img.shields.io/chocolatey/dt/${1:package}.svg${2:?style=flat${3:-square}})](${4:https://chocolatey.org/packages/${1:package}})'


fps to 20

fps to 20

--- app/assets/javascripts/libs/gl_engine/gl_engine.coffee
+++ app/assets/javascripts/libs/gl_engine/gl_engine.coffee
@@ -14,7 +14,7 @@

 	# for throttling renderLoop
 	lastLoopTime = null
-	maximumframerate = 30
+	maximumframerate = 20

 	#to stop the animationLoop
 	stopAnimation = 1


Added ignoreLoadingIndicator to cart service

--- app/assets/javascripts/sprangular/services/cart.coffee
+++ app/assets/javascripts/sprangular/services/cart.coffee
@@ -70,7 +70,7 @@
       else
         params = $.param(variant_id: variant.id, quantity: quantity)

-        $http.post '/api/cart/add_variant', params
+        $http.post '/api/cart/add_variant', params, ignoreLoadingIndicator: true
           .success (response) ->
             service.load(response)

@@ -87,7 +87,7 @@
     updateItemQuantity: (id, quantity) ->
       params = $.param(variant_id: id, quantity: quantity)

-      $http.put '/api/cart/update_variant', params
+      $http.put '/api/cart/update_variant', params, ignoreLoadingIndicator: true
         .success(@load)

     changeVariant: (oldVariant, newVariant) ->


do not store isReady

do not store isReady

--- app/assets/javascripts/uploadcare/files/base.js.coffee
+++ app/assets/javascripts/uploadcare/files/base.js.coffee
@@ -20,7 +20,6 @@
       @cdnUrlModifiers = null
       @isImage = null
       @imageInfo = null
-      @isReady = null

       @__uploadDf = $.Deferred()
       @__infoDf = $.Deferred()
@@ -53,13 +52,12 @@
       @isImage = data.is_image
       @imageInfo = data.image_info
       @isStored = data.is_stored
-      @isReady = data.is_ready

       if @settings.imagesOnly && !@isImage
         @__infoDf.reject('image', this)
         return

-      if @isReady
+      if data.is_ready
         @__infoDf.resolve(this)

     __updateInfo: =>


remove console.log calls

remove console.log calls

--- app/assets/javascripts/views/paper_edit_view.js.coffee
+++ app/assets/javascripts/views/paper_edit_view.js.coffee
@@ -69,7 +69,6 @@
     @setupAutosave()

   timeoutSave: ->
-    console.log 'timeouts cleared'
     @saveVisualEditorChanges()
     @get('controller').send('savePaper')
     Ember.run.cancel(@short)
@@ -85,10 +84,8 @@
   setupAutosave: ->
     Ember.$(document).on 'keyup', '.ve-ui-surface, #paper-title', =>
       @get('controller.model').set('saved', false)
-      console.log 'short set'
       @short = Ember.run.debounce(@, @timeoutSave, 1000 * 10)
       unless @long
-        console.log 'long set'
         @long = Ember.run.later(@, @timeoutSave, 1000 * 60)
       @keyCount++
       if @keyCount > 200


Fix so it's not as broken. ~ Zee

Fix so it's not as broken. ~ Zee

--- app/assets/javascripts/web/routes/gaze_route.js.coffee
+++ app/assets/javascripts/web/routes/gaze_route.js.coffee
@@ -20,11 +20,6 @@
       if @get('nextParams')
         @store.find('spotlight', collectionFilter)
         @clearMeta()
-
-  model: (params) ->
-    @clearMeta()
-    @set('category', params.category)
-    return @store.filter('spotlight', @defaultQuery, @defaultFilter)

   setupController: (controller,spotlights) ->
     controller = @controllerFor('gaze')
@@ -76,3 +71,10 @@

     return @store.filter('spotlight', query, filter)

+Cloudsdale.GazeIndexRoute = Cloudsdale.GazeRoute.extend
+
+  model: (params) ->
+    @clearMeta()
+    @set('category', params.category)
+    return @store.filter('spotlight', @defaultQuery, @defaultFilter)
+


ComputeProvider: setGroupStack: allow team admins to set stack over api

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -61,6 +61,8 @@
           (signature Function)
         updateTeamCounters:
           (signature String, Function)
+        setGroupStack     :
+          (signature Object, Function)

   @providers      = PROVIDERS
@@ -793,6 +795,74 @@
       async.series queue, (err) -> callback err

+  @setGroupStack = (client, options, callback) ->
+
+    { user, group } = client.r
+    { templateId, shareCredential = no } = options
+
+    unless templateId
+      return callback new KodingError 'Template Id is required'
+
+    if group.getAt('slug') in ['guests', 'koding']
+      return callback new KodingError 'Access denied'
+
+    credential    = null
+    stackTemplate = null
+
+    async.series [
+
+      # Fetch stacktemplate with requester session
+      (next) ->
+        JStackTemplate = require './stacktemplate'
+        JStackTemplate.one$ client, { _id: templateId }, (err, template) ->
+          return next err  if err
+          return next new KodingError 'No such template found'  unless template
+          stackTemplate = template
+          do next
+
+      # Fetch provider credential with requester session
+      (next) ->
+        stackTemplate.fetchProviderCredential client, (err, _credential) ->
+          return next err  if err
+          credential = _credential
+          do next
+
+      # Set accessLevel of template to 'group'
+      (next) ->
+        stackTemplate.setAccess client, 'group', next
+
+      # Set template to group
+      (next) ->
+        group.modify client, {
+          stackTemplates: [ stackTemplate.getId() ]
+        }, next
+
+      # Share credential with team
+      (next) ->
+        if shareCredential
+          credential.shareWith$ client, { target: group.getAt 'slug' }, next
+        else
+          do next
+
+      # Notify members about the stack template change
+      (next) ->
+        group.sendNotification \
+          'StackTemplateChanged', stackTemplate.getId(), next
+
+    ], (err) ->
+      callback err
+
+
+  @setGroupStack$ = permit
+
+    advanced: [
+      { permission: 'sudoer' }
+      { permission: 'sudoer', superadmin: yes }
+    ]
+
+    success: revive { shouldReviveProvider: no }, @setGroupStack
+
+
   do ->

     JGroup   = require '../group'


Graph: changed member node name with the hardcoded value

--- workers/social/lib/social/models/graph/decorators/newmemberbucket.coffee
+++ workers/social/lib/social/models/graph/decorators/newmemberbucket.coffee
@@ -46,7 +46,7 @@
   generateSnapshot:(member)->
     snapshot = {}
     snapshot._id         = member.id
-    snapshot.sourceName  = member.name
+    snapshot.sourceName  = "JAccount"

     bongo = {constructorName : @bucketName}
     snapshot.bongo_      = bongo
@@ -55,7 +55,7 @@

     anchor =
       bongo_ : { constructorName : "ObjectRef" }
-      constructorName : member.name
+      constructorName : "JAccount"
       id : member.id

     snapshot.anchor      = anchor


Add a debug message when constructing a map

--- src/mapgen.coffee
+++ src/mapgen.coffee
@@ -32,6 +32,7 @@
 generateMap = mapParameters.sampledBy(genMapCommand.delay(100))

 generateMap.onValue (params) ->
+  console.log "Generating a map!"
   grid = ((2 for w in [0..params.width-1]) for h in [0..params.height-1])
   waterSource = new SimplexNoise
   biomeSource1 = new SimplexNoise


Pass the model to the popover's template when rendering

--- src/scripts/modules/media/header/popovers/book/book.coffee
+++ src/scripts/modules/media/header/popovers/book/book.coffee
@@ -8,4 +8,5 @@
       options:
         html: true
         placement: 'bottom'
-        content: template()
+        content: template
+          model: @model


Allow deprecated 'invalidation' marker param

--- src/marker.coffee
+++ src/marker.coffee
@@ -36,6 +36,10 @@
     if params.persist?
       params.persistent = params.persist
       delete params.persist
+
+    if params.invalidation
+      params.invalidate = params.invalidation
+      delete params.invalidation

   @delegatesMethods 'containsPoint', 'containsRange', 'intersectsRow', toProperty: 'range'
   @delegatesMethods 'clipPosition', 'clipRange', toProperty: 'manager'


Testing

Testing

--- src/markov.coffee
+++ src/markov.coffee
@@ -93,9 +93,10 @@
     # Chance to randomly respond un-prompted
     if pct > 0 and Math.random() < pct
       words = msg.message.text.match /\w+/g
-      randword = Math.floor(Math.random() * words.length)
+      console.log(words[0])
+      randword = Math.floor(Math.random() * words.length + 1)
       seed = words[randword]
-      model.generate seed[0] or '', max, (text) =>
+      model.generate seed or '', max, (text) =>
         msg.send text

   # Generate markov chains on demand, optionally seeded by some initial state.