more LPZ content updates

more LPZ content updates

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -629,7 +629,7 @@
           Cities can be hostile places for wildlife, with threats coming from habitat destruction, roads and traffic, humans, pets and large numbers of invasive species. However, with proper management, urban areas can house a number of important wildlife species, including carnivores and small mammals.

-          To assess the biodiversity of the greater Chicagoland area, Lincoln Park Zoo’s Urban Wildlife Institute has established monitoring stations within city parks, forest preserves, golf courses and cemeteries in parts of Cook, Lake, DuPage and Will Counties including downtown Chicago. Motion-triggered cameras are deployed four times per year at more than 100 sites to determine which species are present and to assess spatial and long-term patterns in wildlife communities.
+          To assess the biodiversity of the greater Chicagoland area, Lincoln Park Zoo’s Urban Wildlife Institute has established monitoring stations within city parks, forest preserves, golf courses and cemeteries in parts of Cook, Lake, DuPage and Will counties including downtown Chicago. Motion-triggered cameras are deployed four times per year at more than 100 sites to determine which species are present and to assess spatial and long-term patterns in wildlife communities.

           Knowing where Chicago’s urban wildlife is located will help us to better conserve it. This knowledge will pave the way for future studies on the behavior and ecology of specific urban species, helping stakeholders to better manage wildlife conflict.
@@ -707,7 +707,7 @@
       header: "Team"
       science:
         magle:
-          name: 'Dr. Seth Magle'
+          name: 'Dr. Seth Magle PhD'
           title: 'Director, Urban Wildlife Institute at Lincoln Park Zoo'
           image: 'http://placehold.it/100'
           description: '''


Minor text edits

Minor text edits

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -40,7 +40,7 @@
     question: "Would you like to view Curated or Uncurated data?"
     notification: "These images have been taken by cameras in public spaces"
     description: """
-      If you are using Chicago Wildlife Watch for educational purposes or with young children, you might want to use our 'Curated' dataset option.  The images in this data set are much more likely to contain an animal but still require additional classifications to confirm the animal type. For a younger audience, or for a situation where classification time is limited, this may provide a more engaging experience.
+      If you are using Chicago Wildlife Watch for educational purposes or with young children, you might want to use our 'Curated' dataset option.  The images in this data set are much more likely to contain an animal but still require additional attention to confirm the animal type. For a younger audience, or for a situation where time is limited, this may provide a more engaging experience.
     """

   siteIntro:
@@ -106,7 +106,7 @@

       welcomeHeader: 'Welcome to Chicago Wildlife Watch!'
       welcome: '''
-        This short tutorial will walk you through your first classification. Let's get started!
+        This short tutorial will walk you through your first identification. Let's get started!
       '''

       trapsHeader: 'Check out the Images'
@@ -162,41 +162,9 @@
         Then click "Identify."
       '''

-      findZebrasHeader: 'Identify another animal'
-      findZebras: '''
-        We want to try to identify all the animals in each capture.
-        Look closely and you'll notice that there are some zebras in the distance.
-
-        Let's look at another way of classifying when we already know which species we can see.
-      '''
-
-      typeZebraHeader: 'Search'
-      typeZebra: '''
-        The seach field is a quick way to highlight animals whose descriptions contain certain words.
-
-        Type "zebra" in the search field...
-      '''
-
-      clickZebraHeader: 'Search'
-      clickZebra: '''
-        ...and click "Zebra," which is the only animal highlighted.
-      '''
-
-      confirmZebraHeader: 'Just do your best!'
-      confirmZebra: '''
-        It's hard to make out the zebras in the distance, so classify as best you can.
-        Each classification is compared with the classifications of other volunteers, so even if you're not sure, just try your best!
-      '''
-
-      identifyZebraHeader: 'Count & Behavior'
-      identifyZebra: '''
-        There are two zebras in the distance, and they appear to be grazing with their heads bowed.
-        Mark "2," "Standing," and "Eating," then click "Identify."
-      '''
-
       finishHeader: 'Nice Job!'
       finish: '''
-        Now you're ready to classify some images on your own.
+        Now you're ready to identify some images on your own.

         In each image, do your best to identify all the animals you can,
         even if you can't see enough to be completely sure.
@@ -205,7 +173,7 @@
         Your observations will be combined with those of multiple volunteers,
         so even if you're not sure on something, your contribution is still very useful!

-        Don't forget: after classifying an image you can always discuss it with professional and citizen scientists or share it with friends.
+        Don't forget: after identifying an image you can always discuss it with professional and citizen scientists or share it with friends.

         Click "Finish" now to move on.
       '''
@@ -613,7 +581,7 @@
     favorites: 'Favorites'
     recents: 'Recents'
     noFavorites: 'You have no favorites!'
-    noRecents: 'You have no recent classifications!'
+    noRecents: 'You have no recent identifications!'
     showing: 'Showing'
     loadMore: 'Load more'


fix null case

fix null case

--- app/coffee/UpdatesManager.coffee
+++ app/coffee/UpdatesManager.coffee
@@ -36,7 +36,7 @@
 			MongoManager.insertCompressedUpdates project_id, doc_id, compressedUpdates, temporary,(error) ->
 				return callback(error) if error?
 				logger.log project_id: project_id, doc_id: doc_id, rawUpdatesLength: length, compressedUpdatesLength: compressedUpdates.length, "compressed doc updates"
-				if lastCompressedUpdate.inS3?
+				if lastCompressedUpdate?.inS3?
 					MongoManager.remarkDocHistoryAsArchived doc_id, callback
 				else
 					callback()


* minor changes.

* minor changes.

--- src/cache.coffee
+++ src/cache.coffee
@@ -12,9 +12,9 @@
 module.exports  = (options)->
   maxFixedCapacity = null
   fixedCapacity = 0
-  cleanInterval = null
-  maxCapacity   = null
-  maxAge        = null
+  cleanInterval = 0
+  maxCapacity   = 0
+  maxAge        = 0
   _cache        = null
   _cacheLRU     = null
   _cacheExpired = null  # {id: expired_time}
@@ -162,9 +162,9 @@
     reset: (options)->
       if options > 0
         maxCapacity   = options
-        maxAge        = null
-        maxFixedCapacity = null
-        cleanInterval = null
+        maxAge        = 0
+        maxFixedCapacity = 0
+        cleanInterval = 0
       else if options
         maxCapacity   = options.capacity
         maxAge        = options.expires
@@ -181,7 +181,7 @@
       _cacheLRU     = null
       _cacheExpired = null
       _lruQueue     = null
-      lastCleanTime = null
+      lastCleanTime = 0
     #executes a provided function once per each value in the cache object, in insertion order.
     # callback: Function to execute for each element. callback is invoked with three arguments:
     #   * the element value


refactor env var warnings

refactor env var warnings

--- src/flubr.coffee
+++ src/flubr.coffee
@@ -28,14 +28,9 @@

 module.exports = (robot) ->

-  if not process.env.HUBOT_FLUBR_URL
-    robot.logger.warning 'The HUBOT_FLUBR_URL environment variable not set'
-
-  if not process.env.HUBOT_FLUBR_PASS
-    robot.logger.warning 'The HUBOT_FLUBR_PASS environment variable not set'
-
-  if not process.env.HUBOT_FLUBR_FAIL
-    robot.logger.warning 'The HUBOT_FLUBR_FAIL environment variable not set'
+  robot.logger.warning 'HUBOT_FLUBR_URL not set' unless process.env.HUBOT_FLUBR_URL?
+  robot.logger.warning 'HUBOT_FLUBR_PASS not set' unless process.env.HUBOT_FLUBR_PASS?
+  robot.logger.warning 'HUBOT_FLUBR_FAIL not set' unless process.env.HUBOT_FLUBR_FAIL?

   robot.hear PASS, (msg) ->
     getImage msg, 'pass'


change the drop log

change the drop log

--- index.cjsx
+++ index.cjsx
@@ -35,9 +35,11 @@
       enemyShipLv: [-1, -1, -1, -1, -1, -1]
       friendShipName: ["空", "空", "空", "空", "空", "空"]
       friendShipLv: [-1, -1, -1, -1, -1, -1]
+      enemyInfo: null
+      getShip: null
     handleResponse: (e) ->
       {method, path, body, postBody} = e.detail
-      {afterFriendHp, nowFriendHp, maxFriendHp, afterEnemyHp, nowEnemyHp, maxEnemyHp, damageFriend, damageEnemy, enemyShipName, enemyShipLv, friendShipName, friendShipLv} = @state
+      {afterFriendHp, nowFriendHp, maxFriendHp, afterEnemyHp, nowEnemyHp, maxEnemyHp, damageFriend, damageEnemy, enemyShipName, enemyShipLv, friendShipName, friendShipLv, enemyInfo, getShip} = @state
       flag = false
       switch path
         when '/kcsapi/api_req_sortie/battle'
@@ -172,8 +174,13 @@
             afterEnemyHp[i] = Math.max(tmp, 0)

         when '/kcsapi/api_req_sortie/battleresult'
+          flag = true
           if body.api_get_ship?
-            setTimeout log.bind(@, "提督くん、#{body.api_enemy_info.api_deck_name}で撃破しました、#{body.api_get_ship.api_ship_type} #{body.api_get_ship.api_ship_name}ゲット　o(*≧▽≦)ツ"), 500
+            enemyInfo = body.api_enemy_info
+            getShip = body.api_get_ship
+          else
+            enemyInfo = null
+            getShip = null

       return unless flag
       @setState
@@ -189,6 +196,8 @@
         enemyShipLv: enemyShipLv
         friendShipName: friendShipName
         friendShipLv: friendShipLv
+        enemyInfo: enemyInfo
+        getShip: getShip
     componentDidMount: ->
       window.addEventListener 'game.response', @handleResponse

@@ -252,6 +261,12 @@
             }
             </tbody>
           </Table>
+          <Alert>
+          {
+            if @state.getShip? && @state.enemyInfo?
+              "提督さん、#{@state.getShip.api_ship_type}「#{@state.getShip.api_ship_name}」が戦列に加わりました"
+          }
+          </Alert>
         </div>
       else
         <div>
@@ -333,4 +348,10 @@
             }
             </tbody>
           </Table>
+          <Alert>
+          {
+            if @state.getShip? && @state.enemyInfo?
+              "提督さん、#{@state.getShip.api_ship_type}「#{@state.getShip.api_ship_name}」が戦列に加わりました"
+          }
+          </Alert>
         </div>


export notifications bar and models

export notifications bar and models

--- index.cjsx
+++ index.cjsx
@@ -3,6 +3,7 @@
 module.exports = {
   ArbitraryHtmlAndMath:   require './src/components/html'
   AsyncButton:            require './src/components/buttons/async-button'
+  BootrapURLs:            require './src/model/urls'
   Breadcrumb:             require './src/components/breadcrumb'
   CardBody,
   ChapterSectionMixin:    require './src/components/chapter-section-mixin'
@@ -14,6 +15,8 @@
   FreeResponse:           require './src/components/exercise/free-response'
   GetPositionMixin:       require './src/components/get-position-mixin'
   KeysHelper:             require './src/helpers/keys'
+  NotificationActions:    require './src/model/notifications'
+  NotificationsBar:       require './src/components/notifications/bar'
   PinnableFooter,
   PinnedHeader,
   PinnedHeaderFooterCard: require './src/components/pinned-header-footer-card'


Use span instead of array for killing warning

--- index.cjsx
+++ index.cjsx
@@ -18,7 +18,7 @@
 module.exports =
   name: 'Akashic'
   priority: 10
-  displayName: [<FontAwesome key={0} name='book' />, ' 航海日志']
+  displayName: <FontAwesome key={0} name='book' /> 航海日志
   description: '日志'
   author: 'W.G.'
   link: 'https://github.com/JenningsWu'


rename

rename

--- examples/cli-dashboard.coffee
+++ examples/cli-dashboard.coffee
@@ -160,7 +160,7 @@
         next 1000

     r:
-      name: "round red"
+      name: "rolling red"
       program: player.createProgram (set, all, next) ->

         all BLACK
@@ -232,7 +232,7 @@

     n:
-      name: "roud blue"
+      name: "rolling blue"
       program: player.createProgram (set, all, next) ->

         all BLACK


Update ajax.js.coffee

Update ajax.js.coffee

--- vendor/assets/javascripts/auto_awesomplete/ajax.js.coffee
+++ vendor/assets/javascripts/auto_awesomplete/ajax.js.coffee
@@ -17,7 +17,13 @@
     $inputs = $(':not(.awesomplete) > input.auto-ajax-awesomplete')
     $inputs.each ->
       $input = $(this)
-      awesomplete = new Awesomplete(this)
+      awesomplete = new Awesomplete(
+        this,
+        {
+          filter: ->
+            true
+        }
+      )
       $input.on 'input', ->
         $textInput = $(this)
         path = $textInput.data('awesomplete-href')


integrate other things build by @tomsrocket

--- wm-map/source/javascripts/angular/MapController.js.coffee
+++ wm-map/source/javascripts/angular/MapController.js.coffee
@@ -5,6 +5,23 @@
   "staendeService",
   "$timeout"
   ($scope, leafletData, searchService, staendeService, $timeout) ->
+    loStyle =
+      color: "#f8f8f8"
+      weight: 2
+      opacity: 1
+      fillOpacity: 0.8
+    hiStyle =
+      color: "#ff3322"
+      weight: 3
+      opacity: 1
+    highlightFeature = (e) ->
+      #this.openPopup();
+      layer = e.target
+      layer.setStyle hiStyle
+    resetHighlight = (e) ->
+      #this.closePopup();
+      layer = e.target
+      layer.setStyle loStyle
     # set up basic stuff
     angular.extend $scope,
       muenster:
@@ -20,7 +37,16 @@
             maxZoom: 22
       search_query: ''
       geojson:
+        style: loStyle
         data: { type: "FeatureCollection", features: [] }
+        onEachFeature: (feature, layer) ->
+          layer.on
+            mouseover: highlightFeature
+            mouseout: resetHighlight
+
+          layer.bindPopup "Stand Nr. " + feature.properties.stand_nr + "
" + feature.properties.betreiber + "
" + feature.properties.warenangeb + ""
+
+          return
       updateGeoJSONFromData: (featureCollection, includeChildren, focusFeatures) ->
         # Update the scope
         if !angular.equals($scope.geojson.data, featureCollection)


Social: pluralize function names

Social: pluralize function names

--- workers/social/lib/social/models/socialapi/channel.coffee
+++ workers/social/lib/social/models/socialapi/channel.coffee
@@ -12,7 +12,7 @@
   @set
     sharedMethods :
       static      :
-        fetchActivity     :
+        fetchActivities     :
           (signature Object, Function)
         fetchChannels     :
           (signature Object, Function)
@@ -52,7 +52,7 @@

   {fetchGroup} = require "./helper"

-  @fetchActivity = secure (client, options = {}, callback)->
+  @fetchActivities = secure (client, options = {}, callback)->
     fetchGroup client, (err, group)->
       return callback err if err
       {connection:{delegate}} = client
@@ -63,8 +63,8 @@
         options.accountId = socialApiId
         options.groupName = group.slug

-        {fetchChannelActivity} = require './requests'
-        fetchChannelActivity options, callback
+        {fetchChannelActivities} = require './requests'
+        fetchChannelActivities options, callback

   @fetchPopularTopics = secure (client, options = {}, callback)->
     fetchGroup client, (err, group)->


display

display

--- events/homeEvent.coffee
+++ events/homeEvent.coffee
@@ -5,10 +5,11 @@
 ###

 debug = require('debug')('stdin/events/home')
+path = require 'path'
 module.exports.HomeEvent = (app) ->

   Feed = app.get('models').Feed
-  AnalyzeQueue = app.get('models').AnalyzeQueue
+  AnalyzeQueue = require(path.resolve('crawler','postProcessing')).AnalyzeQueue

   index: (req,res,next)->
     hatenaCrawlerQueue = app.get 'hatenaCrawlerQueue'


removed forgotted Backbone.Model dependency

--- source/javascripts/morpher/lib/triangle.js.coffee
+++ source/javascripts/morpher/lib/triangle.js.coffee
@@ -1,4 +1,4 @@
-class MorpherJS.Triangle extends Backbone.Model
+class MorpherJS.Triangle extends MorpherJS.EventDispatcher
   p1: null
   p2: null
   p3: null


Support XOR

Support XOR

--- src/formula/evaluator.coffee
+++ src/formula/evaluator.coffee
@@ -1230,6 +1230,27 @@
     args.splice(2, 0, 1)
     @["WORKDAYS.INTL"](args)

+  XOR: (args)->
+    @checkArgumentSize(args, 1, Number.MAX_VALUE)
+    true_count = 0
+    flag = false
+    for arg in args
+      if arg instanceof Range
+        for x in @expandRange(TYPE_ANY, arg)
+          if Utils.isBoolean(x) || Utils.isNumber(x)
+            true_count++ if !!x
+            flag = true
+
+      else
+        x = @getValue(arg)
+        unless Utils.isBoolean(x) || Utils.isNumber(x)
+          @error(FormulaError.VALUE)
+        true_count++ if !!x
+        flag = true
+    unless flag
+      @error(FormulaError.VALUE)
+    true_count % 2 == 1
+

   YEAR: (args)->
     @checkArgumentSize(args, 1)


game state

game state

--- src/game_state.coffee
+++ src/game_state.coffee
@@ -6,7 +6,7 @@

   queuedObjects = []

-  self = Core(I).extend
+  self = Core(I).extend {
     ###*
     The add method creates and adds an object to the game world. Two
     other events are triggered around this one: beforeAdd and afterAdd.
@@ -45,6 +45,7 @@

     objects: ->
       I.objects.copy()
+  }

   self.include Bindable


build status message in room

build status message in room

--- src/github-tfs.coffee
+++ src/github-tfs.coffee
@@ -264,8 +264,13 @@
   #######
   # A Push event has been received, we want to trigger a build
   #######
-  processPushEvent = (tfsProject, tfsCollection, tfsDefinition, repo, branch, sha, pusher, room) ->
+  processPushEvent = (tfsProject, tfsCollection, tfsDefinition, data, room) ->
     tfsURL = getTfsURL(tfsBuildQueueAPICall, tfsProject, tfsCollection)
+    repo = data.repository.name
+    branch = data.ref
+    sha = data.after
+    pusher = data.pusher.name
+
     body  = {
       "definition" : {
         "id" : tfsDefinition
@@ -285,13 +290,22 @@
     httpntlm.post tfsApiCall, (apiCallErr, apiCallRes) ->
       if apiCallErr
         robot.messageRoom room, "Encountered an error :( #{apiCallErr} while processing an event"
+        robot.logger.debug apiCallRes
+        robot.logger.debug body
         return
       else if apiCallRes.statusCode isnt 200
         robot.messageRoom room, "Request came back with a problem :( Response code is #{apiCallRes.statusCode}."
+        robot.logger.debug apiCallRes
+        robot.logger.debug body
         return
       else
         robot.messageRoom room, "@#{pusher} just pushed code on #{repo}/#{branch}. Requesting a TFS build with #{tfsCollection}/#{tfsProject}/#{tfsDefinition}"
-        robot.logger.debug apiCallRes
+        # Saving the repo and commit details so we can call the GitHub status API when build is finished.
+        response = JSON.parse apiCallRes.body
+        robot.logger.debug response
+
+        robot.brain.set response.id, data
+

   ##########################################################
   # HUBOT COMMAND
@@ -431,19 +445,29 @@

       if settings?
         robot.logger.debug "Received a push event from #{repo}"
-        processPushEvent(settings.project, settings.collection, settings.definition, repo, data.ref, data.after, data.pusher.name, room)
+        processPushEvent(settings.project, settings.collection, settings.definition, data, room)
       else
         robot.logger.debug "Received a push event from #{repo} but don't know what to do with it"
-        robot.messageRoom "A push event was received on #{repo} but I don't know what to do with it. You might want to use the 'tfs-build rem' command."
+        robot.messageRoom room, "A push event was received on #{repo} but I don't know what to do with it. You might want to use the 'tfs-build rem' command."
     else
       robot.logger.debug "Received a push event from #{repo} but don't know what to do with it"
-      robot.messageRoom "An event was received on #{repo} but I don't know what to do with it. Sorry!"
+      robot.messageRoom room, "An event was received on #{repo} but I don't know what to do with it. Sorry!"
     res.send 'OK'

   ##########################################################
   # HUBOT LISTENING END-POINT FOR BUILD RESULT
   ##########################################################
-  robot.router.post '/hubot/github-tfs/build-result', (req, res) ->
-    robot.logger.debug req.body.resource
+  robot.router.post '/hubot/github-tfs/build-result/:room', (req, res) ->
+    room   = req.params.room
+    buildResData = req.body.resource
+    #robot.logger.debug buildResData
+
+    buildReqData = robot.brain.get buildResData.id
+    #robot.logger.debug buildReqData
+
+    branch = buildReqData.ref.substring(buildReqData.ref.lastIndexOf('/')+1)
+    sha = buildReqData.after.substring(0, 7)
+
+    robot.messageRoom room, "Build ##{buildResData.id} of #{buildReqData.repository.name}/#{branch} (#{sha}) #{buildResData.status}"
     res.send 'OK'


Refactor specs.

Refactor specs.

--- spec/javascripts/application/seeds_store_spec.js.coffee
+++ spec/javascripts/application/seeds_store_spec.js.coffee
@@ -16,13 +16,16 @@
       list = [{name: 1}, {name: 2}, {name: 3}, {name: 4}]
       expect(subject.update(list))
         .eventually.notify(done)
-    it 'readsFromAll name,asc from the beginning', (done) ->
-      expect(subject.readFromAll('name', true, 0, 1)).eventually
-        .eql([{name: 1}]).notify(done)
-    it 'readsFromAll name,asc in the middle', (done) ->
-      expect(subject.readFromAll('name', true, 1, 2)).eventually
-        .eql([{name: 2}, {name: 3}]).notify(done)
-    it 'readsFromAll name,asc out of scope', (done) ->
-      expect(subject.readFromAll('name', true, 3, 2)).eventually
-        .eql([{name: 4}]).notify(done)
-        +    describe 'name', ->
+      sortBy = 'name'
+      describe 'asc', ->
+        sortOrderAsc = true
+        it 'from the beginning', (done) ->
+          expect(subject.readFromAll(sortBy, sortOrderAsc, 0, 1)).eventually
+            .eql([{name: 1}]).notify(done)
+        it 'in the middle', (done) ->
+          expect(subject.readFromAll(sortBy, sortOrderAsc, 1, 2)).eventually
+            .eql([{name: 2}, {name: 3}]).notify(done)
+        it 'beyond scope', (done) ->
+          expect(subject.readFromAll(sortBy, sortOrderAsc, 3, 2)).eventually
+            .eql([{name: 4}]).notify(done)


Navbar in teacup

Navbar in teacup

--- src/html/index.coffee
+++ src/html/index.coffee
@@ -35,4 +35,32 @@
       ]

     @body =>
-      @h1 'Hello, Teacup!'
+      @nav
+        class : "navbar navbar-default navbar-fixed-top topnav"
+        role  : "navigation"
+        =>
+          @div class: "container topnav", =>
+            # Brand and toggle get grouped for better mobile display
+            @div class: "navbar-header", =>
+              @button
+                type  : "button"
+                class : "navbar-toggle"
+                data  :
+                  toggle: "collapse"
+                  target: "#bs-example-navbar-collapse-1"
+                =>
+                  @span class: "sr-only", "Toggle navigation"
+                  @span class: "icon-bar"
+                  @span class: "icon-bar"
+                  @span class: "icon-bar"
+
+              @a class: "navbar-brand topnav", href: "#", "Responsa.eu"
+
+            @div
+              class : "collapse navbar-collapse"
+              id    : "bs-example-navbar-collapse-1"
+              =>
+                @ul class: "nav navbar-nav navbar-right", =>
+                  @li => @a href: "#about",   "About our project"
+                  @li => @a href: "#portals", "National portals"
+                  @li => @a href: "#contact", "Contact"


remove incorrect call to KDView#setTemplate

--- src/components/contextmenu/contextmenuitem.coffee
+++ src/components/contextmenu/contextmenuitem.coffee
@@ -20,7 +20,6 @@
         @setClass data.cssClass

       if data.type is "customView"
-        @setTemplate ""
         @addCustomView data

       if data.disabled


removed jaccount.likemember method

removed jaccount.likemember method

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -1386,20 +1386,6 @@
       return callback null, response[name].scope is 'repo'

-  likeMember: permit 'like members',
-    success: (client, nickname, callback)->
-      JAccount.one { 'profile.nickname' : nickname }, (err, account)=>
-        return callback new KodingError "An error occurred!" if err or not account
-
-        rel = new Relationship
-          targetId    : account.getId()
-          targetName  : 'JAccount'
-          sourceId    : @getId()
-          sourceName  : 'JAccount'
-          as          : 'like'
-
-        rel.save (err)-> callback err
-
   setLastLoginTimezoneOffset: secure (client, options, callback) ->
     {lastLoginTimezoneOffset} = options


use store methods to set/get format

use store methods to set/get format

--- src/components/exercise/question-format-type.cjsx
+++ src/components/exercise/question-format-type.cjsx
@@ -9,6 +9,7 @@
 TYPES =
   'multiple-choice' : 'Multiple Choice'
   'true-false'      : 'True/False'
+  'requires-choices': 'Requires Choices'

 # Temporarily removed as options (not needed & causes 500 on BE)
 #  'vocabulary'      : 'Vocabulary'
@@ -30,41 +31,23 @@
     sync: React.PropTypes.func.isRequired

   updateFormat: (ev) ->
-    formats =  QuestionStore.get(@props.questionId).formats or []
-    if ev.target.checked
-      formats.push(ev.target.name)
-    else
-      formats = _.without(formats, ev.target.name)
-
-    # 'true-false' and 'multiple-choice' are mutually exclusive
-    if ev.target.name is 'multiple-choice'
-      formats = _.without(formats, 'true-false')
-    if ev.target.name is 'true-false'
-      formats = _.without(formats, 'multiple-choice')
-
-    QuestionActions.setFormats(@props.questionId, formats)
+    QuestionActions.toggleFormat(@props.questionId, ev.target.name, ev.target.checked)
     @props.sync()

-  setChoiceRequired: (ev) ->
-    QuestionActions.setChoiceRequired(@props.questionId, ev.target.checked)
+  isFormatChecked: (name) ->
+    QuestionStore.hasFormat(@props.questionId, name)

   isFormatDisabled: (id, required) ->
     id is 'free-response' and required

   render: ->
     formats =  QuestionStore.get(@props.questionId).formats
-    isChoiceRequired = QuestionStore.isChoiceRequired(@props.questionId)

     <div className="format-type">
       {for id, name of TYPES
         <BS.Input key={id} name={id} type="checkbox" label={name}
-          disabled={@isFormatDisabled(id, isChoiceRequired)}
-          checked={_.contains(formats, id)}
+          checked={@isFormatChecked(id)}
           onChange={@updateFormat} />}
-
-      <BS.Input type="checkbox" label="Requires Choices"
-        onChange={@setChoiceRequired}
-        checked={isChoiceRequired} />
     </div>


Social: limit workspaces by 5 to each machine

--- workers/social/lib/social/models/sidebar.coffee
+++ workers/social/lib/social/models/sidebar.coffee
@@ -43,9 +43,17 @@
       return callback new KodingError err  if err

       machineUIds = machines.map (machine) -> machine.uid
-      JWorkspace.some { machineUId: { $in: machineUIds } }, {}, (err, workspaces) ->
-
-        return callback new KodingError err  if err
+      workspaces = []
+      queue = machineUIds.map (machineUId) ->->
+
+        options = { limit : 5 }
+        JWorkspace.some { machineUId: machineUId }, options, (err, workspaces_) ->
+
+          return callback new KodingError err  if err
+          workspaces = workspaces.concat workspaces_
+
+          queue.fin()
+      dash queue, ->

         options = { client, user, machines, workspaces, callback }
         options.addOwnFn = makeEnvironmentNodeAdderFn data.own


add info about number of changed documents to rethink query output log, with warning if big

--- salvus/rethink.coffee
+++ salvus/rethink.coffee
@@ -107,6 +107,7 @@
             warning  : 30          # display warning and stop using connection if run takes this many seconds or more
             error    : 10*60       # kill any query that takes this long (and corresponding connection)
             concurrent_warn : 500  # if number of concurrent outstanding db queries exceeds this number, put a concurrent_warn message in the log.
+            mod_warn : 2           # display MOD_WARN warning in log if any query modifies at least this many docs
             cb       : undefined
         dbg = @dbg('constructor')

@@ -115,6 +116,7 @@
         @_num_connections  = opts.pool
         @_warning_thresh   = opts.warning
         @_error_thresh     = opts.error
+        @_mod_warn         = opts.mod_warn
         @_concurrent_warn  = opts.concurrent_warn
         @_all_hosts        = opts.all_hosts

@@ -323,7 +325,12 @@
                                 @_stats ?= {sum:0, n:0}
                                 @_stats.sum += tm
                                 @_stats.n += 1
-                                winston.debug("[#{that._concurrent_queries} concurrent]   rethink: query time using (#{id}) took #{tm}ms; average=#{Math.round(@_stats.sum/@_stats.n)}ms;  -- '#{query_string}'")
+
+                                # include some extra info about the query -- if it was a write this can be useful for debugging
+                                modified = (x?.inserted ? 0) + (x?.replaced ? 0) + (x?.deleted ? 0)
+                                winston.debug("[#{that._concurrent_queries} concurrent]  [#{modified} modified]  rethink: query time using (#{id}) took #{tm}ms; average=#{Math.round(@_stats.sum/@_stats.n)}ms;  -- '#{query_string}'")
+                                if modified >= that._mod_warn
+                                    winston.debug("MOD_WARN: modified=#{modified} -- for query  '#{query_string}' ")
                             if err
                                 report_time()
                                 if err.message.indexOf('is closed') != -1


rethinkdb support: chugging away...

rethinkdb support: chugging away...

--- salvus/rethink.coffee
+++ salvus/rethink.coffee
@@ -26,7 +26,9 @@
     file_access_log :
         timestamp : []
     key_value   : false
-    projects    : {}
+    projects    :
+        last_edited : [] # so can get projects last edited recently
+        hidden_from : [{multi:true}]
     remember_me :
         expire     : []
         account_id : []
@@ -384,7 +386,7 @@
         opts = defaults opts,
             account_id : required
             cb         : required
-        if not @_validate_uuids(opts) then return
+        if not @_validate_opts(opts) then return
         @table('accounts').get(opts.account_id).delete().run(opts.cb)

     account_exists: (opts) =>
@@ -411,7 +413,7 @@
         opts = defaults opts,
             account_ids : required
             cb          : required # (err, mapping {account_id:{first_name:?, last_name:?}})
-        if not @_validate_uuids(opts) then return
+        if not @_validate_opts(opts) then return
         if opts.account_ids.length == 0 # easy special case -- don't waste time on a db query
             opts.cb(false, [])
             return
@@ -821,7 +823,7 @@
         x = {}; x[opts.path] = true
         db.table('projects').get(opts.project_id).replace(@r.row.without(public_paths:x)).run(opts.cb)

-    _validate_uuids: (opts) =>
+    _validate_opts: (opts) =>
         for k, v of opts
             if k.slice(k.length-2) == 'id'
                 if v? and not misc.is_valid_uuid_string(v)
@@ -832,9 +834,26 @@
                     if not misc.is_valid_uuid_string(w)
                         opts.cb("invalid uuid #{w} in #{k} -- #{misc.to_json(v)}")
                         return false
+            if k == 'group' and v not in misc.PROJECT_GROUPS
+                opts.cb("unknown project group '#{v}'"); return false
+            if k == 'groups'
+                for w in v
+                    if w not in misc.PROJECT_GROUPS
+                        opts.cb("unknown project group '#{w}' in groups"); return false
+
         return true

     add_user_to_project: (opts) =>
+        opts = defaults opts,
+            project_id : required
+            account_id : required
+            group      : required  # see PROJECT_GROUPS above
+            cb         : required  # cb(err)
+        if not @_validate_opts(opts) then return
+        x = {}; x[opts.group] = @r.row(opts.group).default([]).setInsert(opts.account_id)
+        @table('projects').get(opts.project_id).update(x).run(opts.cb)
+
+    remove_user_from_project: (opts) =>
         opts = defaults opts,
             project_id : required
             account_id : required
@@ -842,22 +861,8 @@
             cb         : required  # cb(err)
         if opts.group not in misc.PROJECT_GROUPS
             opts.cb("unknown project group '#{opts.group}'"); return
-        if not @_validate_uuids(opts) then return
-        x = {}
-        x[opts.group] = @r.row(opts.group).default([]).setInsert(opts.account_id)
-        @table('projects').get(opts.project_id).update(x).run(opts.cb)
-
-    remove_user_from_project: (opts) =>
-        opts = defaults opts,
-            project_id : required
-            account_id : required
-            group      : required  # see PROJECT_GROUPS above
-            cb         : required  # cb(err)
-        if opts.group not in misc.PROJECT_GROUPS
-            opts.cb("unknown project group '#{opts.group}'"); return
-        if not @_validate_uuids(opts) then return
-        x = {}
-        x[opts.group] = @r.row(opts.group).default([]).setDifference([opts.account_id])
+        if not @_validate_opts(opts) then return
+        x = {}; x[opts.group] = @r.row(opts.group).default([]).setDifference([opts.account_id])
         @table('projects').get(opts.project_id).update(x).run(opts.cb)

     get_project_users: (opts) =>
@@ -865,12 +870,12 @@
             project_id : required
             groups     : PROJECT_GROUPS
             cb         : required    # cb(err, {group:[{account_id:?,first_name:?,last_name:?}], ...})
-        if not @_validate_uuids(opts) then return
+        if not @_validate_opts(opts) then return
         groups = undefined
         names  = undefined
         async.series([
-            # get account_id's of all users
-            (cb) =>
+            (cb) =>
+                # get account_id's of all users of the project
                 @get_project_data
                     project_id : opts.project_id
                     columns    : opts.groups
@@ -883,8 +888,8 @@
                                 if not groups[i]?
                                     groups[i] = []
                             cb()
-            # get names of users
-            (cb) =>
+            (cb) =>
+                # get names of users
                 @account_ids_to_usernames
                     account_ids : _.flatten((v for k,v of groups))
                     cb          : (err, _names) =>
@@ -904,69 +909,67 @@

     # Set last_edited for this project to right now, and possibly update its size.
     # It is safe and efficient to call this function very frequently since it will
-    # actually hit the database at most once every 30 seconds (per project).  In particular,
-    # once called, it ignores subsequent calls for the same project for 30 seconds.
+    # actually hit the database at most once every minute (per project).  In particular,
+    # once called, it ignores subsequent calls for the same project for 1 minute.
     touch_project: (opts) =>
         opts = defaults opts,
             project_id : required
-            size       : undefined
             cb         : undefined
-        if not @_validate_uuids(opts) then return
+        if not @_validate_opts(opts) then return
+        if not @_touch_project_cache?
+            @_touch_project_cache = {}
+        tm = @_touch_project_cache[opts.project_id]
+        if tm? and misc.walltime(tm) < 60
+            opts.cb?()
+            return
+        @_touch_project_cache[opts.project_id] = misc.walltime()
+        now = new Date()
+        async.parallel([
+            (cb) =>
+                @db.table('projects').get(opts.project_id).update(last_edited:now).run(cb)
+            (cb) =>
+                @db.table('projects').get(opts.project_id).update(edited:@r.row('edited').default([]).setInsert(now)).run(cb)
+        ], (err) => opts.cb?(err))

     recently_modified_projects: (opts) =>
         opts = defaults opts,
             max_age_s : required
             cb        : required
+        start = new Date(new Date() - opts.max_age_s*1000)
+        @db.table('projects').between(start, new Date(), {index:'last_edited'}).pluck('id').run (err, x) =>
+            opts.cb(err, if x? then (z.id for z in x))

     undelete_project: (opts) =>
         opts = defaults opts,
             project_id  : required
-            cb          : undefined
-        if not @_validate_uuids(opts) then return
+            cb          : required
+        if not @_validate_opts(opts) then return
+        @db.table('projects').get(opts.project_id).update(deleted:false).run(opts.cb)

     delete_project: (opts) =>
         opts = defaults opts,
             project_id  : required
-            cb          : undefined
-        if not @_validate_uuids(opts) then return
+            cb          : required
+        if not @_validate_opts(opts) then return
+        @db.table('projects').get(opts.project_id).update(deleted:true).run(opts.cb)

     hide_project_from_user: (opts) =>
         opts = defaults opts,
             project_id : required
             account_id : required
-            cb         : undefined
-        if not @_validate_uuids(opts) then return
+            cb         : required
+        if not @_validate_opts(opts) then return
+        @db.table('projects').get(opts.project_id).update(
+            hidden_from:@r.row('hidden_from').default([]).setInsert(opts.account_id)).run(opts.cb)

     unhide_project_from_user: (opts) =>
         opts = defaults opts,
             project_id : required
             account_id : required
-            cb         : undefined
-        if not @_validate_uuids(opts) then return
-
-
-    # Make it so the user with given account id is listed as a(n invited) collaborator or viewer
-    # on the given project.  This modifies a set collection on the project *and* modifies a
-    # collection on that account.
-    # There is no attempt to make sure a user is in only one group at a time -- client code must do that.
-    _verify_project_user: (opts) =>
-        # We have to check that is a uuid and use strings, rather than params, due to limitations of the
-        # Helenus driver.  CQL injection...
-        if not misc.is_valid_uuid_string(opts.project_id) or not misc.is_valid_uuid_string(opts.account_id)
-            return "invalid uuid"
-        else if opts.group not in PROJECT_GROUPS
-            return "invalid group"
-        else
-            return null
-
-
-
-    # cb(err, true if project is public)
-    project_is_public: (opts) =>
-        opts = defaults opts,
-            project_id  : required
-            cb          : required  # cb(err, is_public)
-        if not @_validate_uuids(opts) then return
+            cb         : required
+        if not @_validate_opts(opts) then return
+        @db.table('projects').get(opts.project_id).update(
+            hidden_from:@r.row('hidden_from').default([]).setDifference([opts.account_id])).run(opts.cb)

     # cb(err, true if user is in one of the groups)
     user_is_in_project_group: (opts) =>
@@ -975,7 +978,15 @@
             account_id  : required
             groups      : required  # array of elts of PROJECT_GROUPS above
             cb          : required  # cb(err)
-        if not @_validate_uuids(opts) then return
+        if not @_validate_opts(opts) then return
+        # NOTE: I couldn't find a way using .or and contains to do this in one query...
+        error = undefined
+        f = (group, cb) =>
+            @db.table('projects').get(opts.project_id)(group).contains(opts.account_id).run (err, contains) =>
+                if err
+                    error = err
+                cb(contains)
+        async.detect(opts.groups, f, (contains) => opts.cb(error, contains?))

     # all id's of projects having anything to do with the given account (ignores
     # hidden projects unless opts.hidden is true).
@@ -984,13 +995,13 @@
             account_id : required
             hidden     : false
             cb         : required      # opts.cb(err, [project_id, project_id, project_id, ...])
-        if not @_validate_uuids(opts) then return
+        if not @_validate_opts(opts) then return

     get_hidden_project_ids: (opts) =>
         opts = defaults opts,
             account_id : required
             cb         : required    # cb(err, mapping with keys the project_ids and values true)
-        if not @_validate_uuids(opts) then return
+        if not @_validate_opts(opts) then return

     # gets all projects that the given account_id is a user on (owner,
     # collaborator, or viewer); gets all data about them, not just id's
@@ -1000,14 +1011,14 @@
             collabs_as_names : true       # replace all account_id's of project collabs with their user names.
             hidden           : false      # if true, get *ONLY* hidden projects; if false, don't include hidden projects
             cb               : required
-        if not @_validate_uuids(opts) then return
+        if not @_validate_opts(opts) then return

     get_projects_with_ids: (opts) =>
         opts = defaults opts,
             ids     : required   # an array of id's
             columns : PROJECT_COLUMNS
             cb      : required
-        if not @_validate_uuids(opts) then return
+        if not @_validate_opts(opts) then return

     get_project_titles: (opts) =>
         opts = defaults opts,
@@ -1015,14 +1026,14 @@
             use_cache    : true
             cache_time_s : 60*60        # one hour
             cb           : required     # cb(err, map from project_id to string (project title))
-        if not @_validate_uuids(opts) then return
+        if not @_validate_opts(opts) then return

     # cb(err, array of account_id's of accounts in non-invited-only groups)
     get_account_ids_using_project: (opts) ->
         opts = defaults opts,
             project_id : required
             cb         : required
-        if not @_validate_uuids(opts) then return
+        if not @_validate_opts(opts) then return

     ###
     # STATS


adding extra quote

adding extra quote

--- scripts/dr-mat.coffee
+++ scripts/dr-mat.coffee
@@ -28,7 +28,8 @@
   "run this in powershell `for(){PS|SAPS}`",
   "run this in powershell `while(1){ii /*}`",
   "run this in powershell `while(1){ii .}` your welcome",
-  "run this on your linux box `:(){ : | :& }; :` you are welcome."
+  "run this on your linux box `:(){ : | :& }; :` you are welcome.",
+  "sooner or later the blade will be the answer. It always is."
 ]

 module.exports = (robot) ->


Check for points first.

Check for points first.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -100,6 +100,7 @@
                 people = flatten([chat.chatters.moderators, chat.chatters.staff, chat.chatters.admins, chat.chatters.global_mods, chat.chatters.viewers])

                 for username in people
+                    points[username] ?= 0
                     points[username] += 5
                     return
     , 10000)


Inspect pointcount.

Inspect pointcount.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -16,6 +16,8 @@
 points = {}
 username = {}
 people = {}
+
+Util = require "util"

 flatten = (array) ->
     Array::concat.apply([], array)
@@ -115,8 +117,7 @@

             pointcount = robot.brain.get 'winners'
             savescore = ["#{username} has #{attrs.points}" for username, attrs of pointcount]
-            console.log winners
-            msg.send "Top points: #{savescore}"
+            msg.send "#{Util.inspect(pointcount)}"

     # robot.respond /top (\d*)$/i, (msg) ->
     #     if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])


Update useragent to latest Linux/Chrome-Dev

--- bots/libs/HexBot.coffee
+++ bots/libs/HexBot.coffee
@@ -11,7 +11,7 @@
       pageSettings:
         loadImages: false
         loadPlugins: false
-        userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1671.3 Safari/537.36'
+        userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.6 Safari/537.36'
     )
   )


Replace empty path with root

Replace empty path with root

--- src/index.coffee
+++ src/index.coffee
@@ -122,7 +122,7 @@
                 ctrl::name = 'pages:' + name
             ctrl::view = ctrlPath
             if _.isString ctrl::path
-                ctrl::path = moduleInfo.url + ctrl::path
+                ctrl::path = moduleInfo.url + ctrl::path  or  '/'
             else
                 ctrl::path =
                     for p in ctrl::path


FIX Multi remote user: socket.io broadcast

--- src/index.coffee
+++ src/index.coffee
@@ -68,18 +68,18 @@

 				if sys.training?.build then 	me.dataset.buildClassifier sys.training.build,  sys.training.filter_low, sys.training.filter_high,  sys.training.structure, sys.training.epoch, sys.training.learning_rate, (code,data) ->
 					if 			code == 0
-						socket.emit 'sys', JSON.stringify {sys:{training:{build:sys.training.build, status:'done',data:data}}}
+						me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{training:{build:sys.training.build, status:'done',data:data}}}
 						me.classifier.getAvailableClassifiers (code,data) ->
 							if !code then me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{classifier:{list: data }}}
 							else console.log "WARNING Error controller: " + data
-					else if code == 1  then	socket.emit 'sys', JSON.stringify {sys:{training:{build:sys.training.build, status:'running',data:data}}}
-					else socket.emit 'sys', JSON.stringify {sys:{training:{build:sys.training.build, status:'failed',out:data}}}
+					else if code == 1  then	me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{training:{build:sys.training.build, status:'running',data:data}}}
+					else me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{training:{build:sys.training.build, status:'failed',out:data}}}

 				if sys.training?.do == 'next' then me.dataset.getNextTraining sys.training.show, sys.training.filter_low, sys.training.filter_high, (code,data) ->
-					socket.emit 'sys', JSON.stringify {sys:{training:{show:data}}}
+					me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{training:{show:data}}}

 				if sys.training?.do == 'prev' then me.dataset.getPrevTraining sys.training.show, sys.training.filter_low, sys.training.filter_high, (code,data) ->
-					socket.emit 'sys', JSON.stringify {sys:{training:{show:data}}}
+					me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{training:{show:data}}}

 				# DATASET EVENTS
@@ -89,24 +89,24 @@

 				if sys.dataset?.build then 	me.dataset.buildDataset sys.dataset?.build, (code,data) ->
 					if 			code == 0
-						socket.emit 'sys', JSON.stringify {sys:{dataset:{build:sys.dataset.build, status:'done',data:data}}}
+						me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{dataset:{build:sys.dataset.build, status:'done',data:data}}}
 						me.dataset.getTrainingSets (code,data) ->
 							if !code then me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{training:{list: data }}}
 							else console.log "WARNING Error controller: " + data

-					else if code == 1  then	socket.emit 'sys', JSON.stringify {sys:{dataset:{build:sys.dataset.build, status:'running',data:data}}}
-					else socket.emit 'sys', JSON.stringify {sys:{dataset:{build:sys.dataset.build, status:'failed',out:data}}}
+					else if code == 1  then	me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{dataset:{build:sys.dataset.build, status:'running',data:data}}}
+					else me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{dataset:{build:sys.dataset.build, status:'failed',out:data}}}

 				if sys.dataset?.delete then 	me.dataset.deleteSample sys.records?.delete, (code,data) ->
 					if !code then	me.dataset.getPrevSample sys.dataset?.delete, (code,data) ->
-							socket.emit 'sys', JSON.stringify {sys:{dataset:{show:data}}}
+							me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{dataset:{show:data}}}
 					else console.log "WARNING Delete sample: " + sys.dataset?.delete + " failed. \n("+data+")"

 				if sys.dataset?.do == 'next' then me.dataset.getNextSample sys.dataset.show, sys.dataset.filter_low, sys.dataset.filter_high, (code,data) ->
-					socket.emit 'sys', JSON.stringify {sys:{dataset:{show:data}}}
+					me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{dataset:{show:data}}}

 				if sys.dataset?.do == 'prev' then me.dataset.getPrevSample sys.dataset.show, sys.dataset.filter_low, sys.dataset.filter_high, (code,data) ->
-					socket.emit 'sys', JSON.stringify {sys:{dataset:{show:data}}}
+					me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{dataset:{show:data}}}

 				# DATABASE RECORD EVENTS
 				if sys.records?.list?	then me.dataset.getDatabases (code, data) ->
@@ -117,20 +117,20 @@
 					me.dataset.buildDatabase sys.records?.build, (code,data) ->
 						console.log data
 						if 			code == 0
-							socket.emit 'sys', JSON.stringify {sys:{records:{build:sys.records.build, status:'done',data:data}}}
+							me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{records:{build:sys.records.build, status:'done',data:data}}}
 							me.dataset.getDatasets (code,data) ->
 								if !code then me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{dataset:{list: data }}}
 								else console.log "WARNING Error controller: " + data
-						else if code == 1  then	socket.emit 'sys', JSON.stringify {sys:{records:{build:sys.records.build, status:'running',data:data}}}
-						else socket.emit 'sys', JSON.stringify {sys:{records:{build:sys.records.build, status:'failed',out:data}}}
+						else if code == 1  then	me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{records:{build:sys.records.build, status:'running',data:data}}}
+						else me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{records:{build:sys.records.build, status:'failed',out:data}}}

 				if sys.records?.delete then 	me.dataset.deleteRecord sys.records?.delete, (code,data) ->
 					if code then	console.log "WARNING Delete sample: " + sys.records?.delete + " failed. \n("+data+")"

 				if sys.records?.do == 'next' then me.dataset.getNextRecord sys.records.show, sys.records.filter_low, sys.records.filter_high, (code,data) ->
-					socket.emit 'sys', JSON.stringify {sys:{records:{show:data}}}
+					me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{records:{show:data}}}

 				if sys.records?.do == 'prev' then me.dataset.getPrevRecord sys.records.show, sys.records.filter_low, sys.records.filter_high, (code,data) ->
-					socket.emit 'sys', JSON.stringify {sys:{records:{show:data}}}
+					me.serv.io.sockets.emit 'sys', JSON.stringify {sys:{records:{show:data}}}

 new controller()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -110,4 +110,3 @@
   equals 0.5.turns, Math.TAU / 2

 module undefined
-


retire unused

retire unused

--- src/mkts-table.coffee
+++ src/mkts-table.coffee
@@ -285,25 +285,25 @@
   R.bl = "#{( EXCJSCC.n2l R.left ).toLowerCase()}#{R.bottom}"
   return R

-#-----------------------------------------------------------------------------------------------------------
-### TAINT use proper parsing tool ###
-@_parse_coordinate_with_units = ( me, coordinate ) ->
-  unless ( type = CND.type_of coordinate ) is 'text'
-    throw new Error "(MKTS/TABLE 1045) expected a text for coordinate, got a #{rpr type}"
-  unless ( match = coordinate.match /^\s*\(\s*([-0-9.]{1,8}[a-z]{0,3})\s*,\s*([-0-9.]{1,8}[a-z]{0,3})\s*\)\s*$/ )?
-    throw new Error "(MKTS/TABLE 2032) expected a coordinate with units like '( 1mm, 2.4cm )', got #{rpr coordinate}"
-  [ _, x, y, ] = match
-  return { x, y, }
-
-#-----------------------------------------------------------------------------------------------------------
-### TAINT use proper parsing tool ###
-@_parse_coordinate_without_units = ( me, coordinate ) ->
-  unless ( type = CND.type_of coordinate ) is 'text'
-    throw new Error "(MKTS/TABLE 2262) expected a text for coordinate, got a #{rpr type}"
-  unless ( match = coordinate.match /^\s*\(\s*([-0-9.]{1,8})\s*,\s*([-0-9.]{1,8})\s*\)\s*$/ )?
-    throw new Error "(MKTS/TABLE 6904) expected a unitless coordinate like '( 1, 2.4 )', got #{rpr coordinate}"
-  [ _, x, y, ] = match
-  return { x, y, }
+# #-----------------------------------------------------------------------------------------------------------
+# ### TAINT use proper parsing tool ###
+# @_parse_coordinate_with_units = ( me, coordinate ) ->
+#   unless ( type = CND.type_of coordinate ) is 'text'
+#     throw new Error "(MKTS/TABLE 1045) expected a text for coordinate, got a #{rpr type}"
+#   unless ( match = coordinate.match /^\s*\(\s*([-0-9.]{1,8}[a-z]{0,3})\s*,\s*([-0-9.]{1,8}[a-z]{0,3})\s*\)\s*$/ )?
+#     throw new Error "(MKTS/TABLE 2032) expected a coordinate with units like '( 1mm, 2.4cm )', got #{rpr coordinate}"
+#   [ _, x, y, ] = match
+#   return { x, y, }
+
+# #-----------------------------------------------------------------------------------------------------------
+# ### TAINT use proper parsing tool ###
+# @_parse_coordinate_without_units = ( me, coordinate ) ->
+#   unless ( type = CND.type_of coordinate ) is 'text'
+#     throw new Error "(MKTS/TABLE 2262) expected a text for coordinate, got a #{rpr type}"
+#   unless ( match = coordinate.match /^\s*\(\s*([-0-9.]{1,8})\s*,\s*([-0-9.]{1,8})\s*\)\s*$/ )?
+#     throw new Error "(MKTS/TABLE 6904) expected a unitless coordinate like '( 1, 2.4 )', got #{rpr coordinate}"
+#   [ _, x, y, ] = match
+#   return { x, y, }

 #-----------------------------------------------------------------------------------------------------------
 ### TAINT use proper parsing tool ###


Made Animation backwards compatible

Made Animation backwards compatible

Signed-off-by: Niels van Hoorn <9639b9eee3fe09ce53e8dfede54f918fb4bd39be@nvh.io>

--- framer/Animation.coffee
+++ framer/Animation.coffee
@@ -38,10 +38,20 @@
 	# 'properties' are the layer properties that will be animated
 	# 'options' are the animationOptions for this animation
 	# It's also possible to provide options through an 'options' key in the 'properties object'
+	# or provide the options at top level and use 'properties' to specify the properties (old API)
 	constructor: (properties={}, options={}) ->
-		_.defaults(options, properties.options)
-
-		delete properties.options
+		if properties.options?
+			_.defaults(options, properties.options)
+			delete properties.options
+		else if properties.properties?
+			#Support old properties: syntax
+			layer = properties.layer
+			props = properties.properties
+			delete properties.properties
+			delete properties.layer
+			_.defaults(options, properties)
+			properties = props
+			properties.layer = layer
 		@options = _.cloneDeep Defaults.getDefaults "Animation", options
 		super properties
 		@layer = properties.layer ? null


Make convert all -> global just do reload

--- web/volume/spreadsheet.coffee
+++ web/volume/spreadsheet.coffee
@@ -1,8 +1,8 @@
 'use strict'

 app.directive 'spreadsheet', [
-  'constantService', 'displayService', 'messageService', 'tooltipService', 'styleService', '$compile', '$templateCache', '$timeout', '$document', '$location', '$filter', 'routerService', 'storageService', 'Segment',
-  (constants, display, messages, tooltips, styles, $compile, $templateCache, $timeout, $document, $location, $filter, router, storage, Segment) ->
+  'constantService', 'displayService', 'messageService', 'tooltipService', 'styleService', '$compile', '$templateCache', '$timeout', '$document', '$location', '$filter', '$route', 'routerService', 'storageService', 'Segment',
+  (constants, display, messages, tooltips, styles, $compile, $templateCache, $timeout, $document, $location, $filter, $route, router, storage, Segment) ->
     restrict: 'E'
     scope: true
     templateUrl: 'volume/spreadsheet.html'
@@ -494,15 +494,14 @@
             when 'top'
               v = info.metric.options[v]
             when 'summary'
-              if info.d.global
-                if Editing
-                  cell.classList.add('spreadsheet-global-record-cell')
-                  del = cell.appendChild(document.createElement('a'))
-                  del.className = 'button mini global-record white icon-text'
-                  delicon = del.appendChild(document.createElement('span'))
-                  delicon.className = 'icon trash'
-                  del.appendChild(document.createTextNode('Remove'))
-                  $(del).on 'click', $scope.$lift(clickGlobal)
+              if info.d.global && Editing
+                cell.classList.add('spreadsheet-global-record-cell')
+                del = cell.appendChild(document.createElement('a'))
+                del.className = 'button mini global-record white icon-text'
+                delicon = del.appendChild(document.createElement('span'))
+                delicon.className = 'icon trash'
+                del.appendChild(document.createTextNode('Remove'))
+                $(del).on 'click', $scope.$lift(clickGlobal)
             else
               if info.metric.type == 'void' && info.d
                 cell.className = 'clickable' if Editing && Key.id != info.c
@@ -566,8 +565,10 @@
                 td.id = ID + '-no_' + info.i + '_' + info.c
           else
             if Editing && info.c == 'slot' && Key.id != 'slot' && t == SlotCount && !info.row.partial && info.row.key
+              # TODO: style, this action is irreversable
               add = td.appendChild(document.createElement('a'))
-              add.className = 'clickable add icon'
+              add.className = 'button mini white global-record icon-text'
+              add.appendChild(document.createTextNode('Convert to whole volume'))
               $(add).on 'click', $scope.$lift(clickGlobal)
             td.appendChild(document.createTextNode(t + " " + info.category.name + "s"))
             td.className = 'more'
@@ -591,7 +592,7 @@
             info.cell = info.tr.appendChild(document.createElement('td'))
             generateText(info)
             return
-          if info.category.id == 'slot' && info.slot.global
+          if info.category.id == 'slot' && info.d.global
             generateCell(l-1) # summary
             info.cell.setAttribute("colspan", l)
             return
@@ -1235,15 +1236,16 @@
             else if info.count
               router.http(router.controllers.deleteRecordAllSlot, rec.id).then () ->
                 volume.top.addRecord(rec).then () ->
-                  # TODO
-                  rec.global = true
-                  return
+                  true
             else
               volume.top.addRecord(rec).then () ->
                 rec.global = true
                 info.row.set(info.c, info.n, populateSlotData(volume.top))
                 return
-          saveRun info.cell, act.then () ->
+          saveRun info.cell, act.then (r) ->
+            if r
+              $route.reload()
+              return
             collapse()
             generateRow(info.i)
             expand(info) if info.n


Adding failing missing tests for gravity and drag.

--- tests/src/timber_tests.coffee
+++ tests/src/timber_tests.coffee
@@ -140,5 +140,12 @@

     equals(e.position.x, 1000, "Moves along x axis")
     equals(e.position.y, 0, "Moves along x axis")
+
+test "gravity", () ->
+    ok false, "add tests for gravity"
+
+test "drag", () ->
+    ok false, "add tests for drag"
+


Translation test for Collections API was bad.

Need to figure out a better, working test.

--- tests/test_collections.coffee
+++ tests/test_collections.coffee
@@ -253,24 +253,3 @@

     .run ->
         test.done()
-
-casper.test.begin "Collections API: works through translations", (test) ->
-    common.waitForStartup('#rebel-builder')
-
-    common.selectLanguage('Deutsch')
-
-    casper.then ->
-        @evaluate ->
-            window.collection = new Collection
-                expansions:
-                    "Core": 1
-                    "TIE Interceptor Expansion Pack": 1
-
-        test.assertTruthy @evaluate ->
-            window.collection
-
-        test.assert @evaluate ->
-            window.collection.use "ship", "TIE-Abfangjäger"
-
-    .run ->
-        test.done()


Refs #68; Unit test for option changed events.

--- tests/unit/core/charts.coffee
+++ tests/unit/core/charts.coffee
@@ -194,6 +194,16 @@
         chart.option(newOptions)
         assert.deepEqual chart.option(), newOptions

+      it 'should trigger an event when an option is changed', (done) ->
+        errorCallback = ->
+          assert false, 'Setting an option did not trigger the appropriate event'
+          done()
+        setTimeout(errorCallback, 1000)
+
+        [key, value] = ['a', 20]
+        chart = new Epoch.Chart.Base()
+        chart.on 'option:a', -> done()
+        chart.option(key, value)

   describe 'SVG', ->
     [containerWidth, containerHeight] = [1000, 280]


That shouldn't have been deleted

That shouldn't have been deleted

--- src/panda-hook.coffee
+++ src/panda-hook.coffee
@@ -69,6 +69,7 @@
   # This method places a githook script into a remote repo.
   push: (options) ->
     # Generate default CoreOS post-receive githook, unless given another source.
+    options = prepare_template options

     exec "bash #{__dirname}/scripts/push #{options.hook_address} #{options.hook_port} #{options.repo_name} #{options.hook_name} #{options.hook_source}",
       {async: false},


allow self-loop for ussd ruleset test

allow self-loop for ussd ruleset test

--- karma/flows/test_services.coffee
+++ karma/flows/test_services.coffee
@@ -279,9 +279,9 @@
         ruleConnection = flowService.isConnectionAllowed(ussdMenu, ussdResponse)
         expect(ruleConnection).toBeTruthy()

-      it 'should not allow self loops', ->
+      it 'should allow self loops', ->
         ruleLoop = flowService.isConnectionAllowed(ussdMenu, ussdMenu)
-        expect(ruleLoop).toBeFalsy()
+        expect(ruleLoop).toBeTruthy()

     describe 'getFieldSelection()', ->


Fix the isDev util function to work properly on prod

--- site/lib/utils.coffee
+++ site/lib/utils.coffee
@@ -2,8 +2,8 @@

 exports.isDev = (req) ->
   # We are on DEV when Host has string like
-  # '/dev.' or '/staging.' or '.local' or '.dev' or 'localhost'
-  re = /(\/dev\.|\/staging\.|\.local$|\.dev$|localhost)/
+  # 'dev.' or 'staging.' or '.local' or '.dev' or 'localhost'
+  re = /(^dev\.|^staging\.|\.local$|\.dev$|localhost)/
   return re.test(req.headers.Host)

 exports.prettyDate = (date) ->


Added understanding of cypher create command to webadmin data browser.

--- community/server/src/main/coffeescript/neo4j/webadmin/modules/databrowser/search/CypherSearcher.coffee
+++ community/server/src/main/coffeescript/neo4j/webadmin/modules/databrowser/search/CypherSearcher.coffee
@@ -26,9 +26,7 @@
       @server = server
       @urlResolver = new ItemUrlResolver(server)
       @pattern = /// ^
-                    (start|cypher) # Start with "start" or "cypher"
-                    (.+)  # followed by anything
-                    return # Return statement is required
+                    (start|cypher|create) # Start with "start", "cypher" or "create"
                     (.+)  # followed by anything
                     $
                  ///i


adding documentation for this file

adding documentation for this file

--- src/rocketchat.coffee
+++ src/rocketchat.coffee
@@ -1,3 +1,5 @@
+# add documentation here too
+
 try
 		{Robot,Adapter,TextMessage, EnterMessage, User, Response} = require 'hubot'
 catch
@@ -146,7 +148,7 @@

 	send: (envelope, strings...) =>
 		@chatdriver.sendMessage(str, envelope.room) for str in strings
-
+
 	customMessage: (data) =>
 		@chatdriver.customMessage(data)


added a match function

added a match function

--- source/latte.coffee
+++ source/latte.coffee
@@ -1,6 +1,19 @@
-match bla,
-  obj({a:1}) fun(supafn) obj({bla: 2}) -> code_1
-  type(String) -> code_2
+# match bla,
+#   obj({a:1}) fun(supafn) obj({bla: 2}) -> code_1
+#   type(String) -> code_2
+
+match = (self, branches) ->
+  ->
+    args = Array.prototype.slice.call(arguments, 0)
+    args = args.sort()
+    allArgs = _.cloneDeep args
+    for branch in branches
+      try
+        return branch(args, allArgs)
+      catch error
+        continue
+
+    return undefined

 call = (fn, self, passed, allParams) ->
   if fn.__stamp isnt 'internal'


adding coffeescript version of helper

adding coffeescript version of helper

--- snippets/modules.cson
+++ snippets/modules.cson
@@ -247,10 +247,20 @@
     """

   'Ember Helper':
+    'prefix': 'bound-helper'
+    'body': """
+      ${1}Helper = Ember.Handlebars.makeBoundHelper ->
+        ${2:# body}
+
+      `export default ${1}Helper`
+    """
+
+  'Ember Helper':
     'prefix': 'helper'
     'body': """
-      ${1}Helper = Ember.Handlebars.makeBoundHelper ->
-        ${2:# body}
+      ${1}Helper = Ember.Helper.extend
+        compute: ->
+          ${2:# body}

       `export default ${1}Helper`
     """


Parse quotes generously from `cp`

Parse quotes generously from `cp`

--- src/steps/move.coffee
+++ src/steps/move.coffee
@@ -51,8 +51,7 @@
     ]
     processLine = (line) ->
       if line.trim() isnt ""
-        console.log line
-        pieces = /‘(.*?)’ -> ‘(.*?)’/.exec line
+        pieces = /[‘`](.*?)[’'] -> [‘`](.*?)[’']/.exec line
         console.log pieces
         fileData =
           id: Math.random()


Removed some dead code

Removed some dead code

--- examples/convexHull/hull2d.coffee
+++ examples/convexHull/hull2d.coffee
@@ -124,32 +124,15 @@

       cleanSide = (sidePoints, left) ->
         return 0 if sidePoints.length == 0
-        sidePoints.forEach (p) ->
-            p.considered = false
+
         max = sidePoints.reduce(findFurthestPoint, { distance : 0, point : null })

-        #return if !max.point
-        #return if steps >= sigstep
-
-        #if steps == sigstep - 1 and Math.random() > 0.9
-        #  console.log(dividingLine, max.point)
         max.point.used = 1
         sidePoints.splice(sidePoints.indexOf(max.point), 1)
         edgePoints.push(max.point)
         projectedMaxPoint = dividingLine.projectPoint(max.point)
         dividingEdgeA = new Edge(dividingLine.a, max.point)
         dividingEdgeB = new Edge(dividingLine.b, max.point)
-
-        self.paint.setStroke()
-        a = 15
-        a = 255 if steps == sigstep - 1
-        self.paint.setColor(255, 0, 0, a)
-        self.canvas.drawRect(self.paint, dividingLine.a.x, dividingLine.a.y, dividingLine.a.x + 5, dividingLine.a.y + 5)
-        self.canvas.drawLine(self.paint, dividingLine.a.x, dividingLine.a.y, dividingLine.b.x, dividingLine.b.y)
-        self.paint.setColor(150, 0, 150, a)
-        self.canvas.drawLine(self.paint, max.point.x, max.point.y, dividingLine.a.x, dividingLine.a.y)
-        self.paint.setColor(255, 0, 255, a)
-        self.canvas.drawLine(self.paint, max.point.x, max.point.y, dividingLine.b.x, dividingLine.b.y)

         edges.push(dividingEdgeA)
         edges.push(dividingEdgeB)


update spacing to ensure bower is configured correctly

--- src/videojs.ga.coffee
+++ src/videojs.ga.coffee
@@ -220,6 +220,7 @@
     else
       href = window.location.href
       iframe = 0
+
 		if sendbeaconOverride
       sendbeaconOverride(eventCategory, getEventName('player_load'), href, iframe, true)
     else if window.ga


suppress error when removing nonexistent file from cache

--- app/coffee/UrlCache.coffee
+++ app/coffee/UrlCache.coffee
@@ -87,7 +87,11 @@
 				callback null

 	_deleteUrlCacheFromDisk: (project_id, url, callback = (error) ->) ->
-		fs.unlink UrlCache._cacheFilePathForUrl(project_id, url), callback
+		fs.unlink UrlCache._cacheFilePathForUrl(project_id, url), (error) ->
+			if error? and error.code != 'ENOENT' # no error if the file isn't present
+				return callback(error)
+			else
+				return callback()

 	_findUrlDetails: (project_id, url, callback = (error, urlDetails) ->) ->
 		db.UrlCache.find(where: { url: url, project_id: project_id })


Fix typo

Fix typo

--- admin/admin.coffee
+++ admin/admin.coffee
@@ -221,7 +221,7 @@
     try
       hit = TurkServer.mturk "CreateHIT", params
     catch e
-      throw new Meteor.error(500, e.toString())
+      throw new Meteor.Error(500, e.toString())

     HITs.insert
       HITId: hit


Test case for #76

Test case for #76

--- spec/Network.coffee
+++ spec/Network.coffee
@@ -79,3 +79,64 @@

     chai.expect(n.initials).not.to.be.empty
     n.sendInitials()
+
+describe "Nodes are added first, then edges, then initializers (i.e. IIPs), and in order of definition order within each", ->
+  g = null
+  n = null
+  stubbed = {}
+  actual = []
+  expected = []
+
+  # Poor man's way of stubbing the Network. Investigate using
+  # [sinon-chai](https://github.com/domenic/sinon-chai) when we need stubbing
+  # for other parts of testing as well.
+  stub = ->
+    stubbed.addNode = noflo.Network::addNode
+    stubbed.addEdge = noflo.Network::addEdge
+    stubbed.addInitial = noflo.Network::addInitial
+
+    # Record the node/edge/initial and pass it along
+    noflo.Network::addNode = (node, cb) ->
+      actual.push node
+      stubbed.addNode.call this, node, cb
+    noflo.Network::addEdge = (edge, cb) ->
+      actual.push edge
+      stubbed.addEdge.call this, edge, cb
+    noflo.Network::addInitial = (initial, cb) ->
+      actual.push initial
+      stubbed.addInitial.call this, initial, cb
+
+  # Clean up after ourselves
+  restore = ->
+    noflo.Network::addNode = stubbed.addNode
+    noflo.Network::addEdge = stubbed.addEdge
+    noflo.Network::addInitial = stubbed.addInitial
+
+  before (done) ->
+    stub()
+
+    g = new noflo.Graph
+    g.baseDir = root
+    # Save the nodes/edges/initial for order testing later. The index numbers
+    # are the expected positions.
+    expected[0] = g.addNode "D", "Callback"
+    expected[10] = g.addInitial (->), "D", "callback"
+    expected[1] = g.addNode "A", "Split"
+    expected[11] = g.addInitial "Hello", "A", "in"
+    expected[2] = g.addNode "B1", "Merge"
+    expected[5] = g.addEdge "A", "out", "B1", "in"
+    expected[6] = g.addEdge "A", "out", "B2", "in"
+    expected[3] = g.addNode "B2", "Merge"
+    expected[4] = g.addNode "C", "Merge"
+    expected[7] = g.addEdge "B1", "out", "C", "in"
+    expected[12] = g.addInitial "World", "C", "in"
+    expected[8] = g.addEdge "B2", "out", "C", "in"
+    expected[9] = g.addEdge "C", "out", "D", "in"
+    noflo.createNetwork g, (nw) ->
+      n = nw
+      done()
+
+  after restore
+
+  it "should add nodes, edges, and initials, in that order", ->
+    chai.expect(actual).to.deep.equal expected


Use forwardBrackets

Use forwardBrackets

--- spec/Scoping.coffee
+++ spec/Scoping.coffee
@@ -67,8 +67,8 @@
   c.outPorts.add 'out',
     datatype: 'string'

-  c.forwardGroups =
-    in1: ['out']
+  c.forwardBrackets =
+    'in1': ['out']

   c.process (input, output) ->
     return unless input.has 'in1', 'in2', (ip) -> ip.type is 'data'
@@ -78,7 +78,7 @@
     first = input.getData 'in1'

     output.sendDone
-      out: "1#{first}#{c.nodeId}2#{second.data}#{c.nodeId}"
+      out: "1#{first}:2#{second.data}:#{c.nodeId}"

 describe 'Scope isolation', ->
   loader = null
@@ -252,7 +252,7 @@
         'CONN'
         '< 1'
         '< a'
-        'DATA bazWpPc'
+        'DATA 1bazPc1:2fooPc2:PcMerge'
         '>'
         '>'
         'DISC'


Explicilty show imported packet in battle list.

--- views/option-area.cjsx
+++ views/option-area.cjsx
@@ -5,9 +5,13 @@

 OptionArea = React.createClass
   getInitialState: ->
-    selectedTimestamp: 0
+    # selectedPacket can be 3 type
+    #   null : Last battle packet
+    #   packet in @props.battlePackets
+    #   packet not in @props.battlePackets (imported)
+    selectedPacket: null
   shouldComponentUpdate: (nextProps, nextState) ->
-    return true if @state.selectedTimestamp != nextState.selectedTimestamp
+    return true if @state.selectedPacket?.poi_timestamp != nextState.selectedPacket?.poi_timestamp
     return false if @props.battlePacketsNonce == nextProps.battlePacketsNonce
     return true

@@ -16,15 +20,13 @@
     return if index is NaN
     if index == -1
       packet = @props.battlePackets[0]
-      timestamp = 0
       @props.toggleAutoShow true
     else
       packet = @props.battlePackets[index]
-      timestamp = packet.poi_timestamp
       @props.toggleAutoShow false

     @setState
-      selectedTimestamp: timestamp
+      selectedPacket: packet
     @props.updateBattleDetail packet

   handleClickExport: ->
@@ -56,7 +58,7 @@
       packet = JSON.parse packet
       @props.updateBattleDetail packet
       @setState
-        selectedTimestamp: 0
+        selectedPacket: packet
     finally
       window.showModal
         title: __ "Paste Data"
@@ -72,15 +74,25 @@
             <Col xs={6}>
             {
               options = []
-              selectedIndex = -1
-              options.push <option key={-1} value={-1}>{__ "Last Battle"}</option>
+              selectedIndex = -1  # Default: last battle (-1)
+              selectedTimestamp = @state.selectedPacket?.poi_timestamp
+
+              # Is selectedPacket in @props.battlePackets ?
               for packet, i in @props.battlePackets
                 date = new Date(packet.poi_timestamp).toISOString()
                 date = date.slice(0, 19).replace('T', ' ')
                 comment = packet.poi_comment
-                if packet.poi_timestamp == @state.selectedTimestamp
+                if packet.poi_timestamp == selectedTimestamp
                   selectedIndex = i
                 options.push <option key={i} value={i}>{"#{date} #{comment}"}</option>
+
+              # If selectedPacket exists but not in @props.battlePackets
+              if selectedIndex < 0 and selectedTimestamp?
+                selectedIndex = -2
+                options.unshift <option key={-2} value={-2}>{__ "Selected battle"}</option>
+
+              # Default option: last battle
+              options.unshift <option key={-1} value={-1}>{__ "Last Battle"}</option>

               <Input type="select" ref="selectedIndex" value={selectedIndex} onChange={@handleSelectPacket}>
                 {options}


allow nested files

allow nested files

--- index.coffee
+++ index.coffee
@@ -44,12 +44,13 @@

     res.render tpl, {config}

-  app.put '/tastes/:filename', (req, res, next) ->
-    if req.params.filename.length > config.maxFilenameLength
+  app.put /^\/tastes\/(.+)/, (req, res, next) ->
+    filename = req.params[0]
+    if filename.length > config.maxFilenameLength
       return res.status(414).send()
-    if "/#{req.params.filename}" isnt path.resolve '/', req.params.filename
+    if "/#{filename}" isnt path.resolve '/', filename
       return res.status(400).send()
-    relPath = "tastes/#{req.params.filename}"
+    relPath = "tastes/#{filename}"
     saveFile relPath, config, req, res, (err) ->
       return next err  if err?
       res.status(204).send()


MSI might not exist on non-Windows

MSI might not exist on non-Windows

--- index.coffee
+++ index.coffee
@@ -141,7 +141,7 @@
             setupPath = path.join(outputDirectory, "#{metadata.productName}Setup.exe")
             setupMsiPath = path.join(outputDirectory, "#{metadata.productName}Setup.msi")
             fs.renameSync(path.join(outputDirectory, 'Setup.exe'), setupPath)
-            fs.renameSync(path.join(outputDirectory, 'Setup.msi'), setupMsiPath)
+            fs.renameSync(path.join(outputDirectory, 'Setup.msi'), setupMsiPath) if fs.existsSync(setupMsiPath)

           done()


Only load materials and bind keys on client

--- index.coffee
+++ index.coffee
@@ -160,12 +160,14 @@

   # load textures after all plugins loaded (since they may add their own)
   registry = plugins.get('voxel-registry')
-  game.materials.load registry.getBlockPropsAll 'texture'   # TODO: have voxel-registry do this

-  game.buttons.down.on 'pov', () -> plugins.get('voxel-player')?.toggle()
-  game.buttons.down.on 'vr', () -> plugins.toggle 'voxel-oculus'
-  game.buttons.down.on 'home', () -> plugins.get('voxel-player')?.home()
-  game.buttons.down.on 'inventory', () -> plugins.get('voxel-inventory-dialog')?.open()
+  if game.isClient
+    game.materials.load registry.getBlockPropsAll 'texture'   # TODO: have voxel-registry do this
+
+    game.buttons.down.on 'pov', () -> plugins.get('voxel-player')?.toggle()
+    game.buttons.down.on 'vr', () -> plugins.toggle 'voxel-oculus'
+    game.buttons.down.on 'home', () -> plugins.get('voxel-player')?.home()
+    game.buttons.down.on 'inventory', () -> plugins.get('voxel-inventory-dialog')?.open()

   return game


Added a piece of logging.

Added a piece of logging.

--- lib/Watchers/Asset/mirror.coffee
+++ lib/Watchers/Asset/mirror.coffee
@@ -44,6 +44,7 @@
                     for dir in root
                         try
                             path = Path.normalize "#{dir}/#{file}"
+                            @log "Looking for #{path}"
                             @pool.push GetPool path
                             @log "Found #{path}"
                             return


Always return an array from status, even when we're not in a repo

More predictable behavior

--- index.coffee
+++ index.coffee
@@ -24,7 +24,7 @@
     staged: false

 _filter_statuses_by_desc = (statuses, desc) ->
-  _.filter (statuses || []), (status) ->
+  _.filter statuses, (status) ->
     STATUS_CODE_MAP[status.code]?.desc is desc

@@ -70,7 +70,7 @@
   # This command *must* be passed through a formatter before its displayed
   @register 'status', (done) ->
     status = repo?.getStatus()
-    done null, '' unless status
+    done null, [] unless status

     statuses = []
     for path, code of status


add spec for Bacon.fromArray

add spec for Bacon.fromArray

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -933,6 +933,12 @@
       -> Bacon.once(new Bacon.Error("oop"))
       [error()])

+describe.only "Bacon.fromArray", ->
+  describe "Turns an array into an EventStream", ->
+    expectStreamEvents(
+      -> Bacon.fromArray([1, 2, 3])
+      [1, 2, 3])
+
 describe "EventStream.concat", ->
   describe "provides values from streams in given order and ends when both are exhausted", ->
     expectStreamEvents(


Added referral test

Added referral test

--- spec/ahoy_spec.coffee
+++ spec/ahoy_spec.coffee
@@ -12,6 +12,9 @@

     it "direct", ->
       checkSource "", "(direct)", "(direct)"
+
+    it "referral", ->
+      checkSource "http://www.example.org", "example.org", "referral"

     it "google organic", ->
       checkSource "http://www.google.com/?q=test", "google.com", "search", "test"


Add view helper 'match'

Add view helper 'match'

--- app/lib/view_helper.coffee
+++ app/lib/view_helper.coffee
@@ -37,3 +37,7 @@
 Handlebars.registerHelper 'with_user', (options) ->
   context = mediator.user or {}
   Handlebars.helpers.with.call(this, context, options)
+
+Handlebars.registerHelper 'match', (first, second, options) ->
+  if first is second
+    options.fn(this);


Update apm ls specs for new output

Update apm ls specs for new output

--- spec/list-spec.coffee
+++ spec/list-spec.coffee
@@ -16,15 +16,6 @@
     atomHome = temp.mkdirSync('apm-home-dir-')
     process.env.ATOM_HOME = atomHome

-  it 'lists the built-in packages', ->
-    packagesPath = path.join(resourcePath, 'src', 'packages')
-    fs.makeTreeSync(packagesPath)
-    wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures', 'test-module'), path.join(packagesPath, 'test-module'))
-
-    apm.run(['list'])
-    expect(console.log).toHaveBeenCalled()
-    expect(console.log.argsForCall[4][0]).toContain 'test-module@1.0.0'
-
   it 'lists the packages included in node_modules with an atom engine specified', ->
     packagesPath = path.join(resourcePath, 'node_modules')
     fs.makeTreeSync(packagesPath)
@@ -32,16 +23,7 @@

     apm.run(['list'])
     expect(console.log).toHaveBeenCalled()
-    expect(console.log.argsForCall[4][0]).toContain 'test-module@1.0.0'
-
-  it 'includes vendored packages', ->
-    packagesPath = path.join(resourcePath, 'vendor', 'packages')
-    fs.makeTreeSync(packagesPath)
-    wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures', 'test-module'), path.join(packagesPath, 'test-module'))
-
-    apm.run(['list'])
-    expect(console.log).toHaveBeenCalled()
-    expect(console.log.argsForCall[4][0]).toContain 'test-module@1.0.0'
+    expect(console.log.argsForCall[1][0]).toContain 'test-module@1.0.0'

   it 'lists the installed packages', ->
     packagesPath = path.join(atomHome, 'packages')
@@ -50,7 +32,7 @@

     apm.run(['list'])
     expect(console.log).toHaveBeenCalled()
-    expect(console.log.argsForCall[1][0]).toContain 'test-module@1.0.0'
+    expect(console.log.argsForCall[4][0]).toContain 'test-module@1.0.0'

   it 'labels disabled packages', ->
     packagesPath = path.join(atomHome, 'packages')
@@ -61,4 +43,4 @@

     apm.run(['list'])
     expect(console.log).toHaveBeenCalled()
-    expect(console.log.argsForCall[1][0]).toContain 'test-module@1.0.0 (disabled)'
+    expect(console.log.argsForCall[4][0]).toContain 'test-module@1.0.0 (disabled)'


Add spec

Add spec

--- spec/main-spec.coffee
+++ spec/main-spec.coffee
@@ -1,5 +1,6 @@
 CoffeeRefactor = require '../lib/main'
 { WorkspaceView } = require 'atom'
+{ inspect } = require 'util'

 # Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs.
 #
@@ -11,20 +12,24 @@

   beforeEach ->
     atom.workspaceView = new WorkspaceView
-    activationPromise = atom.packages.activatePackage('coffee-refactor')
+    activationPromise = atom.packages.activatePackage 'coffee-refactor'

-  describe "when the coffee-refactor:rename event is triggered", ->
+  describe "when the coffee-refactor:toggle-highlight event is triggered", ->
     it "attaches and then detaches the view", ->
-      # expect(atom.workspaceView.find('.coffee-refactor')).not.toExist()
+      expect(atom.workspaceView.find('.coffee-refactor')).not.toExist()

       # This is an activation event, triggering it will cause the package to be
       # activated.
-      atom.workspaceView.trigger 'coffee-refactor:rename'
+      atom.workspaceView.trigger 'coffee-refactor:toggle-highlight'

       waitsForPromise ->
         activationPromise

       runs ->
+        console.log inspect atom.workspaceView.find('workspace').length
+        $el = atom.workspaceView.find('.coffee-refactor')
+        console.log $el.length
+        expect($el).toHaveLength 1
         # expect(atom.workspaceView.find('.coffee-refactor')).toExist()
         atom.workspaceView.trigger 'coffee-refactor:rename'
         # expect(atom.workspaceView.find('.coffee-refactor')).not.toExist()


Apply small fix to prevent error of "sinceDate not found"

--- app/mixins/dateable.coffee
+++ app/mixins/dateable.coffee
@@ -3,11 +3,11 @@
 DateableMixin = Ember.Mixin.create
   # Method to check if something has happened in the last val*period
   inLast: (field, val, period) ->
-    sinceDate(field,val, period, new Date())
+    @sinceDate(field,val, period, new Date())

   # Method to check if something will happen in the next val*period
   inNext: (field, val, period) ->
-    untilDate(field,val, period, new Date())
+    @untilDate(field,val, period, new Date())

   # Checks if something happened in val*period from startDate
   sinceDate: (field, val, period, startDate) ->


`extraDesc is no longer used`

`extraDesc is no longer used`

--- app/models/analysis.coffee
+++ app/models/analysis.coffee
@@ -4,7 +4,6 @@
 Analysis = DS.Model.extend
   file: DS.belongsTo 'file', inverse: 'analyses'
   description: DS.attr 'string'
-  extraDesc: DS.attr 'string'
   analiserVersion: DS.attr 'number'
   risk: DS.attr 'number'
   status: DS.attr 'number'


Move public methods; Remove private methods

--- src/numbo.coffee
+++ src/numbo.coffee
@@ -28,38 +28,21 @@
 import zhTW from './zh-tw.coffee'
 import zhCN from './zh-cn.coffee'

+utils =
+	trimWhitespace: trimWhitespace
+	parse99: parse99
+	parseCent: parseCents #todo to be depreciated
+	parseCents: parseCents #todo to be depreciated
+	speakByDigit: speakByDigit
+	check: check
+	validate: check
+	normalize: normalize
+	normalise: normalize
+	adjustDecimal: adjustDecimal
+	splitNum: splitNum
+	splitInt: splitInt
+
 class Numbo
-
-	tools =
-		parse99: parse99
-		trimWhitespace: trimWhitespace
-		parseCents: parseCents
-		speakByDigit: speakByDigit
-		check: check
-		normalize: normalize
-		adjustDecimal: adjustDecimal
-		splitNum: splitNum
-		splitInt: splitInt
-	tools:
-		trimWhitespace: tools.trimWhitespace
-		parse99: tools.parse99
-		parseCent: tools.parseCents #todo to be depreciated
-		parseCents: tools.parseCents #todo to be depreciated
-		speakByDigit: tools.speakByDigit
-		check: tools.check
-		validate: tools.check
-		normalize: tools.normalize
-		normalise: tools.normalize
-		adjustDecimal: tools.adjustDecimal
-		splitNum: tools.splitNum
-		splitInt: tools.splitInt
-
-	convert_enUS = enUS
-	convert_zhTW = zhTW
-	convert_zhCN = zhCN
-	enUS: convert_enUS
-	zhTW: convert_zhTW
-	zhCN: convert_zhCN

 	langs = ['enUS', 'zhTW', 'zhCN']

@@ -67,7 +50,7 @@
 		# `input` can be string or number
 		# `options` can be language ('zhTW') and style ('number', 'amount' or 'cheque')
 		if options.length is 0 # not provide any option
-			convert_enUS(input)
+			@enUS(input)
 		else
 			zeroCent = false #todo: temp setup
 			parsedOptions = parseOptions(langs, options)
@@ -87,6 +70,11 @@
 					zeroCent = true
 				@[parsedOptions.plugin](input, parsedOptions.template, zeroCent) #todo: temp setup for zeroCent

+# Public methods
+Numbo::tools = utils
+Numbo::enUS = enUS
+Numbo::zhTW = zhTW
+Numbo::zhCN = zhCN

 numbo = new Numbo()


fix the accidental replacement of @usernames in status update bodies

--- client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemStatusUpdate.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemStatusUpdate.coffee
@@ -21,7 +21,7 @@

   attachTooltipAndEmbedInteractivity:=>
     @$("p.status-body a").each (i,element)=>
-      href = $(element).attr("data-original-url") or ""
+      href = $(element).attr("data-original-url") or $(element).attr("href") or ""

       twOptions = (title) ->
          title : title, placement : "above", offset : 3, delayIn : 300, html : yes, animate : yes, className : "link-expander"
@@ -30,7 +30,8 @@

       # unless $(element).text() is href.replace(/^(((ht|f)tp(s)?\:)?\/\/)/,"").replace(/\/$/,"") or\
       #        "http://"+$(element).text().replace("","") is href.replace(/\/$/,"")
-      $(element).twipsy twOptions("External Link : "+href+"")
+      unless /^(#!)/.test href
+        $(element).twipsy twOptions("External Link : "+href+"")
         # linkIsShortened = yes

       element


fix shipItem's display when horizontal

fix shipItem's display when horizontal

--- index.cjsx
+++ index.cjsx
@@ -195,7 +195,8 @@
 getCss = ->
   {doubleTabbed} = window
   if doubleTabbed then width = 800 else width = 400
-  if $('poi-app').clientWidth < width then css = 'ship-item-horizontal.css' else css = 'ship-item-vertical.css'
+  if config.get('poi.layout', 'horizontal') == 'horizontal' then area = 'poi-app' else area = 'body'
+  if $("#{area}").clientWidth < width then css = 'ship-item-horizontal.css' else css = 'ship-item-vertical.css'
   css

 module.exports =


Add in /dev/csrf endpoint for easy automated testing

--- services/web/app/coffee/router.coffee
+++ services/web/app/coffee/router.coffee
@@ -242,6 +242,8 @@
 		apiRouter.get '/status', (req,res)->
 			res.send("websharelatex is up")

+		webRouter.get '/dev/csrf', (req, res) ->
+			res.send res.locals.csrfToken

 		apiRouter.get '/health_check', HealthCheckController.check
 		apiRouter.get '/health_check/redis', HealthCheckController.checkRedis


Appropriately detect if the content is editable

--- src/scripts/modules/media/tabs/metadata/metadata.coffee
+++ src/scripts/modules/media/tabs/metadata/metadata.coffee
@@ -28,6 +28,14 @@
       model.languageName = settings.languages[model.language]
       model.subjectsList = subjects.list
       model.url = linksHelper.getModelPath(model)
+
+      if @media is 'page'
+        editable = if @model.isBook() then @model.get('currentPage')?.isEditable() else @model.isEditable()
+      else
+        editable = @model.isEditable()
+
+      model.editable = editable
+
       return model

     editable:
@@ -101,5 +109,6 @@

     initialize: () ->
       super()
+
       @listenTo @model, 'change change:currentPage', (model, options) =>
         @render() unless options?.doNotRerender


Fix Google Drive save regression

Fix Google Drive save regression

--- src/code/providers/google-drive-provider.coffee
+++ src/code/providers/google-drive-provider.coffee
@@ -241,7 +241,7 @@

     request = gapi.client.request
       path: path
-      type: method
+      method: method
       params: {uploadType: 'multipart'}
       headers: {'Content-Type': 'multipart/related; boundary="' + boundary + '"'}
       body: body


Fixed chat view scrolling behavior on the client

--- client/js/views/chat_view.coffee
+++ client/js/views/chat_view.coffee
@@ -40,7 +40,7 @@
   # Returns true if the chat is scrolled to the bottom of the screen.
   isAtBottom: =>
     $el = @$('.messages')
-    ($el[0].scrollHeight - $el.scrollTop()) == $el.outerHeight()
+    ($el.scrollTop() + $el.innerHeight()) >= $el[0].scrollHeight

   scrollToBottom: =>
     messages = @$('.messages')[0]


log tweak

log tweak

--- src/core/javascripts/controllers/base.js.coffee
+++ src/core/javascripts/controllers/base.js.coffee
@@ -1,7 +1,7 @@
 'use strict';

-angular.module('BB.Directives').directive 'bbWidget', (PathSvc, $http, $log
+angular.module('BB.Directives').directive 'bbWidget', (PathSvc, $http, $log,
     $templateCache, $compile, $q, AppConfig, $timeout, $bbug) ->

   getTemplate = (template) ->
@@ -96,7 +96,7 @@

 angular.module('BB.Controllers').controller 'BBCtrl', ($scope, $location,
     $rootScope, halClient, $window, $http, $localCache, $q, $timeout, BasketService,
-    LoginService, AlertService, $sce, $element, $compile, $sniffer, $modal,
+    LoginService, AlertService, $sce, $element, $compile, $sniffer, $modal, $log,
     BBModel, BBWidget, SSOService, ErrorService, AppConfig, QueryStringService,
     QuestionService, LocaleService, PurchaseService, $sessionStorage, $bbug, SettingsService) ->
   # dont change the cid as we use it in the app to identify this as the widget


Emit stopping event.

Emit stopping event.

--- src/59_start.coffee
+++ src/59_start.coffee
@@ -42,6 +42,7 @@

         stop: (callback = ->)->
             console.log "Stopping #{config.name}..."
+            app.emit('stopping');
             Q.all(listeners.map((_)->(Q.denodeify(_.close)())))
             .then((->callback())).catch(callback)
     }


Calling for fresh portal data

Calling for fresh portal data

Added API call extension for requesting actual portal data. So in this way any call will fetch the data the intel sees at this moment.

--- components/GetPortalState.coffee
+++ components/GetPortalState.coffee
@@ -46,7 +46,7 @@
       host: @hostname
       hostname: @hostname
       auth: "#{@login.username}:#{@login.password}"
-      path: "/api/table/portals/#{id}"
+      path: "/api/table/portals/#{id}/1"
     , (res) =>
       body = ''
       res.on 'data', (chunk) ->


Move S3 URL to private variable

Move S3 URL to private variable

--- lib/layervault/nodes/file.coffee
+++ lib/layervault/nodes/file.coffee
@@ -6,6 +6,8 @@
 Node = require '../node'

 module.exports = class File extends Node
+  S3_ENDPOINT = 'https://omnivore-scratch.s3.amazonaws.com'
+
   get: (cb) -> @api.get(@path, {}, cb.bind(@))
   create: (options, cb) ->
     {@localPath, @contentType} = options
@@ -28,7 +30,7 @@
       content_type: @contentType
       file: @localPath

-    needle.post 'https://omnivore-scratch.s3.amazonaws.com',
+    needle.post S3_ENDPOINT,
       options,
       { multipart: true },
       (err, resp, body) =>


Update Spellbook.fixOrphanWords Component

--- components/fixOrphanWords.coffee
+++ components/fixOrphanWords.coffee
@@ -33,4 +33,4 @@
 # Spellbook.fixOrphanWords({
 #   element: $('.js-orphan')
 # })
-
+#


Keep updating the preview if the editor is destroyed

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -61,7 +61,8 @@
       @editor = @editorForId(editorId)

       if @editor?
-        @emitter.emit 'did-change-title' if @editor?
+        @emitter.emit 'did-change-title'
+        @disposables.add @editor.onDidDestroy(=> @subscribeToFilePath(@getPath()))
         @handleEvents()
         @renderMarkdown()
       else


toString dates so we get the whole thing

Fixes 11

You owe me a :beer:

--- src/quest.coffee
+++ src/quest.coffee
@@ -206,9 +206,12 @@
         columns.map (column) ->
           value = val[column]
           if trimWhitespace and typeof(value) == 'string'
-            value = value.trim()
+            value.trim()
           else
-            value ?= ''
+            if value instanceof Date
+              value.toString()
+            else
+              value ? ''

       table.push.apply table, values
       console.log table.toString()


Add support for EXPRESS_BIND_ADDRESS variable

--- src/robot.coffee
+++ src/robot.coffee
@@ -270,7 +270,7 @@
     pass    = process.env.EXPRESS_PASSWORD
     stat    = process.env.EXPRESS_STATIC
     port    = process.env.EXPRESS_PORT or process.env.PORT or 8080
-    address = process.env.BIND_ADDRESS or '0.0.0.0'
+    address = process.env.EXPRESS_BIND_ADDRESS or process.env.BIND_ADDRESS or '0.0.0.0'

     express = require 'express'


don't bomb out if a name isn't set, just skip it

--- src/robot.coffee
+++ src/robot.coffee
@@ -200,7 +200,8 @@
     result = null
     lowerName = name.toLowerCase()
     for k of (@brain.data.users or { })
-      if @brain.data.users[k]['name'].toLowerCase() is lowerName
+      userName = @brain.data.users[k]['name']
+      if userName? and userName.toLowerCase() is lowerName
         result = @brain.data.users[k]
     result


Initial attempt at fixing #114 to correct formatting urls

--- src/slack.coffee
+++ src/slack.coffee
@@ -1,7 +1,7 @@
 {Robot, Adapter, EnterMessage, LeaveMessage, TopicMessage} = require 'hubot'
 {SlackTextMessage, SlackRawMessage, SlackBotMessage} = require './message'
 {SlackRawListener, SlackBotListener} = require './listener'
-{RtmClient, MemoryDataStore} = require '@slack/client'
+{RtmClient, WebClient, MemoryDataStore} = require '@slack/client'

 class SlackBot extends Adapter
   @MAX_MESSAGE_LENGTH: 4000
@@ -25,8 +25,9 @@
     return @robot.logger.error "No services token provided to Hubot" unless @options.token
     return @robot.logger.error "v2 services token provided, please follow the upgrade instructions" unless (@options.token.substring(0, 5) in ['xoxb-', 'xoxp-'])

-    # Create our slack client object
+    # Create our slack client objects
     @client = new RtmClient @options.token, @options
+    @webClient = new WebClient @options.token, @options # fallback for formatted messages

     # Setup event handlers
     # TODO: Handle eventual events at (re-)connection time for unreads and provide a config for whether we want to process them
@@ -236,7 +237,7 @@
       @robot.logger.debug "Sending to #{envelope.room}: #{msg}"

       if msg.length <= SlackBot.MAX_MESSAGE_LENGTH
-        @client.sendMessage msg, envelope.room
+        @sendFormatted msg, envelope.room

       # If message is greater than MAX_MESSAGE_LENGTH, split it into multiple messages
       else
@@ -268,7 +269,15 @@

             msg = msg.substring(breakIndex, msg.length)

-        @client.sendMessage(m, envelope.room) for m in submessages
+        @sendFormatted(m, envelope.room) for m in submessages
+
+  sendFormatted: (message, room) ->
+    # RTM doesn't support formatted Urls, so we'll use the web client instead
+    if /<.+\|.+>/.test(message)
+      @webClient.chat.postMessage(room, message)
+    else
+      @client.sendMessage(message, room)
+

   reply: (envelope, messages...) ->
     @robot.logger.debug "Sending reply"


Ignore nodegit src .cc/.h files

Ignore nodegit src .cc/.h files

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -120,6 +120,8 @@
     # Ignore *.cc and *.h files from native modules
     ignoredPaths.push "#{_.escapeRegExp(path.join('ctags', 'src') + path.sep)}.*\\.(cc|h)*"
     ignoredPaths.push "#{_.escapeRegExp(path.join('git-utils', 'src') + path.sep)}.*\\.(cc|h)*"
+    ignoredPaths.push "#{_.escapeRegExp(path.join('nodegit', 'src') + path.sep)}.*\\.(cc|h)?"
+    ignoredPaths.push "#{_.escapeRegExp(path.join('nodegit', 'generate') + path.sep)}.*\\.(cc|h)?"
     ignoredPaths.push "#{_.escapeRegExp(path.join('keytar', 'src') + path.sep)}.*\\.(cc|h)*"
     ignoredPaths.push "#{_.escapeRegExp(path.join('nslog', 'src') + path.sep)}.*\\.(cc|h)*"
     ignoredPaths.push "#{_.escapeRegExp(path.join('oniguruma', 'src') + path.sep)}.*\\.(cc|h)*"


change project name to chicago

change project name to chicago

--- app/main.coffee
+++ app/main.coffee
@@ -49,7 +49,7 @@
 #   domain: 'X?'

 app = {}
-api = new Api project: 'serengeti'
+api = new Api project: 'chicago'

 #TODO rewrite this logic for sorted seasons
 # Api.get '/projects/serengeti', (project) ->


lets limit these to at least 10 games played

--- source/javascripts/interactive.coffee
+++ source/javascripts/interactive.coffee
@@ -1,8 +1,8 @@
-# Drive and Kick      - http://localhost:4567/interactive?y=pts_crt&x=dpp
-# And One Assists     - http://localhost:4567/interactive?y=pts_crt&x=ast_ft
-# Ball Movement       - http://localhost:4567/interactive?y=pass&x=top
-# Going for the steal - http://localhost:4567/interactive?y=dist_pg&x=stl
-# Old Guys Driving    - http://localhost:4567/interactive?y=dvs&x=age
+# Drive and Kick       - http://tracking.andone.co?y=pts_crt&x=dpp
+# Passing into traffic - http://tracking.andone.co?y=pts_crt&x=ast_ft
+# Ball Movement        - http://tracking.andone.co?y=pass&x=top
+# Going for the steal  - http://tracking.andone.co?y=dist_pg&x=stl
+# Old Guys Driving     - http://tracking.andone.co?y=dvs&x=age
 render = ->
   el = d3.select '.js-scatterplot'
   margin = top: 20, right: 20, bottom: 50, left: 60
@@ -38,7 +38,7 @@

   dataLoaded = (error, data) ->
     d3.select('.js-loading').remove()
-    data = data.filter (d) -> d.team and d.min > 10
+    data = data.filter (d) -> d.team and d.min > 10 and d.gp > 10

     xag = vis.append('g')
       .attr('class', 'x axis')