Changes description and adds an author

Changes description and adds an author

--- src/ghe.coffee
+++ src/ghe.coffee
@@ -1,5 +1,5 @@
 # Description:
-#   Tells the status of GHE
+#   Access your GitHub Enterprise instance through Hubot
 #
 # Dependencies:
 #   None
@@ -13,7 +13,7 @@
 #   hubot ghe (status|info) <info> - gives some information about large repositories
 #
 # Authors:
-#   pnsk
+#   pnsk, mgriffin

 module.exports = (robot) ->
   robot.respond /ghe (status|info)? ?(.*)$/i, (msg) ->


remove debugging console.log

remove debugging console.log

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -31,7 +31,6 @@

   update_keyHandler = () ->
     $("#input_answer").keydown((e) =>
-      console.log this.curren
       if e.which == 13
         attempt = $("#input_answer").val()
         if attempt.toLowerCase() == @current.toLowerCase()


remove creamer file

remove creamer file


nuh-uh

nuh-uh

--- src/startup.coffee
+++ src/startup.coffee
@@ -25,7 +25,6 @@
 atom.keybinder = new KeyBinder
 atom.settings = new Settings

-# Ignore extensions for now
 atom.extensions = {}
 atom.extensionManager = new ExtensionManager


Move jquery ready call (main hook) to after ClientSideValidation object creation in order to prevent a ReferenceError when the DOM is already ready (ie. the client side validations javascript is evaluated after the page has finished loading).

--- coffeescript/rails.validations.coffee
+++ coffeescript/rails.validations.coffee
@@ -94,11 +94,6 @@
     passElement()

   afterValidate()
-
-# Main hook
-# If new forms are dynamically introduced into the DOM the .validate() method
-# must be invoked on that form
-$(-> $(ClientSideValidations.selectors.forms).validate())

 if window.ClientSideValidations == undefined
   window.ClientSideValidations = {}
@@ -441,3 +436,8 @@
       before: (form, eventData) ->
       fail:   (form, eventData) ->
       pass:   (form, eventData) ->
+
+# Main hook
+# If new forms are dynamically introduced into the DOM the .validate() method
+# must be invoked on that form
+$(-> $(ClientSideValidations.selectors.forms).validate())


Add Emacs Lisp package to projects list

Add Emacs Lisp package to projects list

--- user/projects.cson
+++ user/projects.cson
@@ -127,3 +127,10 @@
 	paths: [
 		"/Users/johngardner/.atom/packages/language-restructuredtext"
 	]
+"language-emacs-lisp":
+	title: "language-emacs-lisp"
+	devMode: true
+	paths: [
+		"/Users/johngardner/Labs/language-emacs-lisp"
+		"/Users/johngardner/Documents/Code-related/emacs-lisp"
+	]


Update project-listing

Update project-listing

--- user/projects.cson
+++ user/projects.cson
@@ -85,17 +85,10 @@
 		devMode: true
 	}
 	{
-		title: "FI-V2"
-		paths: [
-			"/Users/johngardner/Labs/FI-V2"
-			"/Users/johngardner/Labs/file-icons"
-		]
-		devMode: true
-	}
-	{
 		title: "File"
 		paths: [
 			"/Users/johngardner/Mirrors/File"
 		]
+		devMode: true
 	}
 ]


db amount limit for mongo (max 5) KodingError class for mongo

--- kites/databases/mongodbApi.coffee
+++ kites/databases/mongodbApi.coffee
@@ -6,6 +6,16 @@
 #log     = log4js.addAppender log4js.fileAppender(logFile), "[MongoDBApi]"
 log     = log4js.getLogger('[MongoDBApi]')

+class AccessError extends Error
+  constructor:(@message)->
+
+class KodingError extends Error
+  constructor:(message)->
+    return new KodingError(message) unless @ instanceof KodingError
+    Error.call @
+    @message = message
+    @name = 'KodingError'
+
 class MongoDB

   appendKodingUsername = (username, str)->
@@ -26,7 +36,7 @@
     @mongoHost = @config.databases.mongodb[0].host
     @mongoUser = @config.databases.mongodb[0].user
     @mongoPass = @config.databases.mongodb[0].password
-    @server = new mongo.Server @mongoHost, 27017
+    @server = new mongo.Server @mongoHost, 27017, socketOptions:keepAlive:5

   uniqueId = (length=8) ->

@@ -47,6 +57,7 @@
             log.error err if err
             collection.find({},{fields:user:1}).toArray (err,items)->
               log.error err if err
+              db.close() # temporary solotion - close connection after each query
               usernames = []
               for user in items
                 usernames.push user.user
@@ -124,7 +135,7 @@
     dbConf = {dbName,dbUser,dbPass}

     dbConf.dbHost = @mongoHost
-    dbConf.dbType = "mongo"/
+    dbConf.dbType = "mongo"

     dbConf.dbPass = uniqueId()
     dbConf.dbUser = dbConf.dbName
@@ -133,27 +144,39 @@

     db = new mongo.Db dbConf.dbName, @server

-    db.open (err,db)=>
-      if err?
-        log.error "[ERROR] can't open database #{dbConf.dbName}: #{err}"
-        callback? "[ERROR] can't open database #{dbConf.dbName}: #{err}"
-      else
-        db.admin().authenticate @mongoUser, @mongoPass,(err,result)=>
-          if err?
-            log.error "[ERROR] can't authenticate with admin credentials: #{err}"
-            callback?  "[ERROR] can't authenticate with admin credentials: #{err}"
-          else
-            db.addUser dbConf.dbUser,dbConf.dbPass,(err,res)->
-              log.debug err,res
-              db.authenticate dbConf.dbUser,dbConf.dbPass,(err,res)->
+    dbCount = (username,callback) =>
+      @fetchDatabaseList username:username,(err,dbsArray)->
+        if err then callback err
+        else
+          callback null,dbsArray.length
+
+    dbCount username,(err,dbNr)=>
+      unless err
+        if dbNr > 20 # 0..4
+          log.error e = "You exceeded your quota, please delete one before adding a new one."
+          callback new KodingError e
+        else
+          db.open (err,db)=>
+            if err?
+              log.error "[ERROR] can't open database #{dbConf.dbName}: #{err}"
+              callback? "[ERROR] can't open database #{dbConf.dbName}: #{err}"
+            else
+              db.admin().authenticate @mongoUser, @mongoPass,(err,result)=>
                 if err?
-                  log.error "[ERROR] can't create user #{dbConf.dbUser} and database #{dbConf.dbName}: #{err}"
-                  callback? "[ERROR] can't create user #{dbConf.dbUser} and database #{dbConf.dbName}: #{err}"
+                  log.error "[ERROR] can't authenticate with admin credentials: #{err}"
+                  callback?  "[ERROR] can't authenticate with admin credentials: #{err}"
                 else
-                  log.debug res
-                  log.info "[OK] user #{dbConf.dbUser} and database #{dbConf.dbName} has been created"
-                  log.info dbConf
-                  callback? null,dbConf
+                  db.addUser dbConf.dbUser,dbConf.dbPass,(err,res)->
+                    log.debug err,res
+                    db.authenticate dbConf.dbUser,dbConf.dbPass,(err,res)->
+                      if err?
+                        log.error "[ERROR] can't create user #{dbConf.dbUser} and database #{dbConf.dbName}: #{err}"
+                        callback? "[ERROR] can't create user #{dbConf.dbUser} and database #{dbConf.dbName}: #{err}"
+                      else
+                        log.debug res
+                        log.info "[OK] user #{dbConf.dbUser} and database #{dbConf.dbName} has been created"
+                        log.info dbConf
+                        callback? null,dbConf

   changePassword : (options,callback)->

@@ -221,15 +244,15 @@
 mongoDB = new MongoDB config
 module.exports = mongoDB

-# options =
-#    username : "gokment"
-#    dbName    : "dlskdlskdlsk"
+#options =
+#   username : "gokment"
+#   dbName    : "dlskdlskdlskdlsjdlsjdlkdkddkdkd"

 #mongoDB.createDatabase options,(err,res)->
 #  console.log err,res

-# mongoDB.fetchDatabaseList options,(res)->
-#   console.log res
+#mongoDB.fetchDatabaseList options,(err,res)->
+#  console.log res
 #dbs =  ['gokment_c219e4c7','gokment_df539bb7']
 #for db in dbs
 #  do (db)->


Restore react-gravatar className + remove isRequired from most propTypes

--- src/index.cjsx
+++ src/index.cjsx
@@ -7,19 +7,19 @@
   displayName: 'Gravatar'

   propTypes:
-    size: React.PropTypes.number.isRequired
-    rating: React.PropTypes.string.isRequired
-    https: React.PropTypes.bool.isRequired
-    default: React.PropTypes.string.isRequired
     email: React.PropTypes.string.isRequired
-    className: React.PropTypes.className
+    size: React.PropTypes.number
+    rating: React.PropTypes.string
+    https: React.PropTypes.bool
+    default: React.PropTypes.string
+    className: React.PropTypes.string

   getDefaultProps: ->
     size: 50
     rating: 'g'
     https: false
     default: "retro"
-    className: "react-gravatar"
+    className: ""

   render: ->
     base = if @props.https
@@ -36,5 +36,5 @@
     src = base + md5.digest_s(@props.email) + "?" + query

     return(
-
+
     )


Making sure the component is rendered in tests.

--- tests/tests.coffee
+++ tests/tests.coffee
@@ -688,40 +688,45 @@
     ,
       /Template 'TemplateWhichDoesNotExist' cannot be found/

-  testEvents: =>
-    MainComponent.calls = []
-    SubComponent.calls = []
-
-    renderedComponent = Blaze.render Template.eventsTestTemplate, $('body').get(0)
-
-    $('.eventsTestTemplate button').each (i, button) =>
-      $(button).click()
-
-    @assertEqual MainComponent.calls, [
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'MainComponent']
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '1', b: '2'}, 'MainComponent']
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'MainComponent']
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '3', b: '4'}, 'MainComponent']
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'FooComponent']
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '5', b: '6'}, 'FooComponent']
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'SubComponent']
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '1', b: '2'}, 'SubComponent']
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'SubComponent']
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '3', b: '4'}, 'SubComponent']
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'FooComponent']
-      ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '5', b: '6'}, 'FooComponent']
-    ]
-
-    @assertEqual SubComponent.calls, [
-      ['SubComponent', 'SubComponent.onClick', {top: '42'}, {top: '42'}, 'SubComponent']
-      ['SubComponent', 'SubComponent.onClick', {top: '42'}, {a: '1', b: '2'}, 'SubComponent']
-      ['SubComponent', 'SubComponent.onClick', {top: '42'}, {top: '42'}, 'SubComponent']
-      ['SubComponent', 'SubComponent.onClick', {top: '42'}, {a: '3', b: '4'}, 'SubComponent']
-      ['SubComponent', 'SubComponent.onClick', {top: '42'}, {top: '42'}, 'FooComponent']
-      ['SubComponent', 'SubComponent.onClick', {top: '42'}, {a: '5', b: '6'}, 'FooComponent']
-    ]
-
-    Blaze.remove renderedComponent
+  testEvents: [
+    ->
+      MainComponent.calls = []
+      SubComponent.calls = []
+
+      @renderedComponent = Blaze.render Template.eventsTestTemplate, $('body').get(0)
+
+      Tracker.afterFlush @expect()
+  ,
+    ->
+      $('.eventsTestTemplate button').each (i, button) =>
+        $(button).click()
+
+      @assertEqual MainComponent.calls, [
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'MainComponent']
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '1', b: '2'}, 'MainComponent']
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'MainComponent']
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '3', b: '4'}, 'MainComponent']
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'FooComponent']
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '5', b: '6'}, 'FooComponent']
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'SubComponent']
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '1', b: '2'}, 'SubComponent']
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'SubComponent']
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '3', b: '4'}, 'SubComponent']
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {top: '42'}, 'FooComponent']
+        ['MainComponent', 'MainComponent.onClick', {top: '42'}, {a: '5', b: '6'}, 'FooComponent']
+      ]
+
+      @assertEqual SubComponent.calls, [
+        ['SubComponent', 'SubComponent.onClick', {top: '42'}, {top: '42'}, 'SubComponent']
+        ['SubComponent', 'SubComponent.onClick', {top: '42'}, {a: '1', b: '2'}, 'SubComponent']
+        ['SubComponent', 'SubComponent.onClick', {top: '42'}, {top: '42'}, 'SubComponent']
+        ['SubComponent', 'SubComponent.onClick', {top: '42'}, {a: '3', b: '4'}, 'SubComponent']
+        ['SubComponent', 'SubComponent.onClick', {top: '42'}, {top: '42'}, 'FooComponent']
+        ['SubComponent', 'SubComponent.onClick', {top: '42'}, {a: '5', b: '6'}, 'FooComponent']
+      ]
+
+      Blaze.remove @renderedComponent
+  ]

   testAnimation: [
     ->


Start fixing tests.

Start fixing tests.

--- tests/utils.coffee
+++ tests/utils.coffee
@@ -7,5 +7,15 @@

 if Meteor.isServer
   # Set up a dummy batch
-  unless Batches.findOne(active: true)
-    Batches.insert(active: true)
+  unless Batches.find().count()
+    Batches.insert(name: 'test')
+
+  # Set up a dummy HIT type and HIT
+  unless HITTypes.find().count()
+    batch = Batches.findOne()
+    hitTypeId = HITTypes.insert
+      batchId: batch._id
+    hitId = "authHitId"
+    HITs.insert
+      HITId: hitId
+      HitTypeId: hitTypeId


don't require token

don't require token

--- _source/lib/routes/server/_vars.coffee
+++ _source/lib/routes/server/_vars.coffee
@@ -12,14 +12,12 @@
   # transactionDetail: "#{baseURL}transactionDetails/"
   # account: "#{baseURL}accounts/"

+
+if not Meteor.settings.api?.tokenName or not Meteor.settings.api?.token
+  return
+
 Apollos.api.tokenName = Meteor.settings.api.tokenName
 Apollos.api.token = Meteor.settings.api.token
-
-
-if not Apollos.api.tokenName or not Apollos.api.token
-  return
-
-

 Apollos.api.register = (collections, platform) ->


fixed missing definition

fixed missing definition

--- addon/components/autox-show-for.coffee
+++ addon/components/autox-show-for.coffee
@@ -2,7 +2,7 @@
 `import layout from '../templates/components/autox-show-for'`
 `import {_computed} from '../utils/xdash'`

-{computed, inject} = Ember
+{computed, inject, isPresent} = Ember
 {apply} = _computed
 {alias} = computed


VASTTracker.click called to VASTUtil.resolve and not VASTUtil.resolveURLTemplates

--- src/tracker.coffee
+++ src/tracker.coffee
@@ -96,7 +96,7 @@
         if @clickThroughURLTemplate?
             if @linear
                 variables = CONTENTPLAYHEAD: @progressFormated()
-            clickThroughURL = VASTUtil.resolve([@clickThroughURLTemplate], variables)[0]
+            clickThroughURL = VASTUtil.resolveURLTemplates([@clickThroughURLTemplate], variables)[0]

             @emit "clickthrough", clickThroughURL


[IMPAC-598] Disable threshold when widget hist_params in past

--- src/components/widgets-common/chart-threshold/chart-threshold.component.coffee
+++ src/components/widgets-common/chart-threshold/chart-threshold.component.coffee
@@ -92,6 +92,7 @@

     onChartNotify = (chart)->
       ctrl.chart = chart
+      validateHistParameters()
       Highcharts.addEvent(chart.container, 'click', onChartClick)
       thresholdSeries = _.select(chart.series, (s)-> s.name.toLowerCase().includes('threshold'))
       _.each(thresholdSeries, (t)->
@@ -136,6 +137,13 @@
       ctrl.chart.setSize(null, ctrl.chart.chartHeight + ctrl.chartShrinkSize, false)
       ctrl.chart.container.parentElement.style.height = "#{ctrl.chart.chartHeight}px"

+    # Disable threshold when selected time period is strictly in the past
+    validateHistParameters = ->
+      widgetHistParams = ctrl.widget.metadata && ctrl.widget.metadata.hist_parameters
+      # Widget histParams are YTD by default (when undefined on metadata),
+      # therefore in the past by default
+      ctrl.disabled = _.isEmpty(widgetHistParams) || moment(widgetHistParams.to) <= moment().startOf('day')
+      return

     return ctrl
 })


#850 - remove buttons from editable fields

--- jsapp/xlform_model_view/view.utils.coffee
+++ jsapp/xlform_model_view/view.utils.coffee
@@ -19,7 +19,7 @@

           newValue: ent
         , that
-      showButtons: false
+      showbuttons: false

     editableOpts = _.extend(opts, options)


switch log file writing to use `w` instead of `w+`

--- src/log.coffee
+++ src/log.coffee
@@ -5,7 +5,7 @@
 module.exports = (cwd, logPath, callback) ->
   filename = path.resolve cwd, logPath
   dirname = path.dirname filename
-  flags = 'w+'
+  flags = 'w'

   mkdirp dirname, (error) ->
     return callback(error) if error?


:lipstick:

:lipstick:

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -185,7 +185,6 @@
   modifiers = new Set

   for key, i in keys
-
     if AtomModifiers.has(key)
       modifiers.add(key)
     else


Use much more sophisticated pluralization.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -5,9 +5,65 @@
 sysPath = require 'path'
 logger = require './logger'

-# A simple function that will return pluralized form of word.
+# Pluralization rules.
+# Original author: TJ Holowaychuk.
+pluralRules = [
+  [/(m)an$/gi, '$1en'],
+  [/(pe)rson$/gi, '$1ople'],
+  [/(child)$/gi, '$1ren'],
+  [/^(ox)$/gi, '$1en'],
+  [/(ax|test)is$/gi, '$1es'],
+  [/(octop|vir)us$/gi, '$1i'],
+  [/(alias|status)$/gi, '$1es'],
+  [/(bu)s$/gi, '$1ses'],
+  [/(buffal|tomat|potat)o$/gi, '$1oes'],
+  [/([ti])um$/gi, '$1a'],
+  [/sis$/gi, 'ses'],
+  [/(?:([^f])fe|([lr])f)$/gi, '$1$2ves'],
+  [/(hive)$/gi, '$1s'],
+  [/([^aeiouy]|qu)y$/gi, '$1ies'],
+  [/(x|ch|ss|sh)$/gi, '$1es'],
+  [/(matr|vert|ind)ix|ex$/gi, '$1ices'],
+  [/([m|l])ouse$/gi, '$1ice'],
+  [/(quiz)$/gi, '$1zes'],
+  [/s$/gi, 's'],
+  [/$/gi, 's']
+]
+
+uncountables = [
+  'advice',
+  'energy',
+  'excretion',
+  'digestion',
+  'cooperation',
+  'health',
+  'justice',
+  'labour',
+  'machinery',
+  'equipment',
+  'information',
+  'pollution',
+  'sewage',
+  'paper',
+  'money',
+  'species',
+  'series',
+  'rain',
+  'rice',
+  'fish',
+  'sheep',
+  'moose',
+  'deer',
+  'news'
+]
+
+# Function that will return pluralized form of word.
 exports.pluralize = (word) ->
-  word + 's'
+  rule = pluralRules.filter((rule) -> word.match rule[0])[0]
+  if word not in uncountables and rule
+    word.replace rule[0], rule[1]
+  else
+    word

 exports.startsWith = (string, substring) ->
   string.indexOf(substring) is 0


Eliminate code duplication in choosing notifier.

Related: #134.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -159,18 +159,24 @@
   "jasmine" of global

-hasGrowl = no
-exec "which growlnotify", (error) ->
-  hasGrowl = yes unless error?
-
-hasNotifySend = no
-exec "which notify-send", (error) ->
-  hasNotifySend = yes unless error?
-
-
-exports.growl = (title, text) ->
-  if hasGrowl then spawn "growlnotify", [title, "-m", text]
-  else if hasNotifySend then spawn "notify-send", [title, text]
+
+exports.notify = (title, text) -> null
+
+
+# Map of possible system notifiers in format
+# Key - "name of system command that would be executed".
+# Value - args, with which the command would be spawned.
+# E.g. spawn growlnotify, [title, "-m", text]
+exports.notifiers = notifiers =
+  growlnotify: (title, text) -> [title, "-m", text]
+  "notify-send": (title, text) -> [title, text]
+
+
+# Try to determine right system notifier.
+for name, transform of notifiers
+  do (name, transform) ->
+    exec "which #{name}", (error) ->
+      exports.notify = ((args...) -> spawn name, transform args...) unless error?

 exports.log = (text, color = "green", isError = no) ->
@@ -178,7 +184,7 @@
   # TODO: log stdout on testing output end.
   output = "#{formatDate(color)} #{text}\n"
   stream.write output, "utf8" unless exports.isTesting()
-  exports.growl "Brunch error", text if isError
+  exports.notify "Brunch error", text if isError

 exports.logError = (text) -> exports.log text, "red", yes


Adding contenteditable support

Adding contenteditable support

--- src/hotkeys.coffee
+++ src/hotkeys.coffee
@@ -107,6 +107,7 @@
             # (return if $el is not radio, is not checkbox, is not a button, but it is still an input or editable element)
             return if $(document.activeElement).not(":radio").not(":checkbox").not(":button").is(":input,[contenteditable]")

+
             context = @currentContext ? @registeredViews[namespace].tree

             @walkContext context, event.which if context?


update AP responses

update AP responses

- respond to levels 9-16
- include badge requirements


email: wait till client is ready to queue

--- workers/social/lib/social/models/newemail.coffee
+++ workers/social/lib/social/models/newemail.coffee
@@ -14,7 +14,8 @@

   {forcedRecipient, defaultFromMail} = KONFIG.email

-  VERSION_1 = ' v1'
+  VERSION_1  = ' v1'
+  EVENT_TYPE = 'api.mail_send'

   @types =
     WELCOME          : 'welcome'          + VERSION_1
@@ -35,12 +36,15 @@
     unless mqClient
       return callback new KodingError 'RabbitMQ client not found in Email'

-    mqClient.once "ready", =>
+    sendMessage =->
       mqClient.exchange "#{exchangeName}", exchangeOpts, (exchange) =>
         unless exchange
           return callback new KodingError "Exchange not found!: #{exchangeName}"

-        exchange.publish "", type : 'api.mail_send', message: mail
+        exchange.publish "", mail, type:EVENT_TYPE
         exchange.close()

-        callback null
+    if mqClient.readyEmitted then sendMessage()
+    else mqClient.on "ready", -> sendMessage()
+
+    callback null


config: add heatlhcheck endpoint

config: add heatlhcheck endpoint

--- config/workers.coffee
+++ config/workers.coffee
@@ -110,6 +110,9 @@
       nginx             :
         locations       : [
           {
+            location    : "/-/healthCheck"
+          }
+          {
             location    : "~*(^(\/(Pricing|About|Legal|Features|Blog|Docs)))"
             extraParams : [ "if ($host !~* ^(dev|default|sandbox|latest|www)) { return 301 /; }" ]
           }
@@ -119,6 +122,8 @@
           }
           {
             location    : "/"
+            cors        : on
+            auth        : if options.environment is 'sandbox' then yes else no
           }
         ]


After each test, immediately abort all pending requests

--- spec_app/spec/javascripts/helpers/reset_up.js.coffee
+++ spec_app/spec/javascripts/helpers/reset_up.js.coffee
@@ -13,10 +13,13 @@
   # the timeout below will actually happen.
   jasmine.clock().uninstall()

+  # Abort all requests so any cancel handlers can run and do async things.
+  up.network.abort()
+
   # Most pending promises will wait for an animation to finish.
-  promise = up.motion.finish()
+  whenMotionsDone = up.motion.finish()

-  u.always promise, ->
+  u.always whenMotionsDone, ->

     # Wait one more frame so pending callbacks have a chance to run.
     # Pending callbacks might change the URL or cause errors that bleed into


add --profile to usage, make it prettier

--- console/runner.coffee
+++ console/runner.coffee
@@ -20,7 +20,12 @@
   optimist = require 'optimist'
   {argv} = optimist

-  optimist.usage('Usage: $0 /path/to/classfile --java=[args for JVM] [--log=[0-10]|debug|error]')
+  optimist.usage '''
+  Usage: $0 /path/to/classfile
+    --java=[args for JVM]
+    [--log=[0-10]|debug|error]
+    [--profile]
+  '''

   return optimist.showHelp() if argv.help


Added Static Dir for Assets

Added Static Dir for Assets

--- .metalsmith/gulpfile.coffee
+++ .metalsmith/gulpfile.coffee
@@ -28,6 +28,7 @@
     '../**'
   ]
   sass: './sass/**/*.scss'
+  static: './static/**/*'

 # ## metalsmith
@@ -56,6 +57,14 @@
   metal()

+# ## static
+#
+# Copy static assets to build dir.
+gulp.task 'static', ->
+  gulp.src paths.static
+    .pipe gulp.dest 'build'
+
+
 # ## watch:md
 #
 # Run a web server exposing the files in the build directory, and recompile
@@ -78,5 +87,5 @@

 gulp.task 'watch', ['watch:md']
 gulp.task 'watch:all', ['watch:md', 'watch:code', 'watch:sass']
-gulp.task 'build', ['metalsmith', 'sass']
+gulp.task 'build', ['metalsmith', 'sass', 'static']
 gulp.task 'default', ['build']


Refactor Git-based package installation

Refactor Git-based package installation

Signed-off-by: Katrina Uychaco <ef0b2ab5bd1ac851826b76acd653f3c4825374fb@github.com>

--- src/install.coffee
+++ src/install.coffee
@@ -1,6 +1,7 @@
 path = require 'path'

 async = require 'async'
+CSON = require 'season'
 _ = require 'underscore-plus'
 yargs = require 'yargs'
 CSON = require 'season'
@@ -389,6 +390,7 @@
   createAtomDirectories: ->
     fs.makeTreeSync(@atomDirectory)
     fs.makeTreeSync(@atomPackagesDirectory)
+    fs.makeTreeSync(@atomGitPackagesDirectory)
     fs.makeTreeSync(@atomNodeDirectory)

   # Compile a sample native module to see if a useable native build toolchain
@@ -488,22 +490,44 @@
   getHostedGitInfo: (name) ->
     hostedGitInfo.fromUrl(name)

-  installGitPackage: (packageInfo, targetDir, callback) ->
+  installGitPackage: (gitPackageInfo, options, callback) ->
+    cloneDir = temp.mkdirSync("atom-git-package-clone-")
+    tasks = []
+
+    tasks.push (cb) => @cloneGitPackage gitPackageInfo, cloneDir, cb
+    tasks.push (cb) => @installGitPackageDependencies(cloneDir, options, cb)
+    tasks.push ({name}, cb) =>
+      targetDir = path.join(@atomGitPackagesDirectory, name)
+      fs.cp cloneDir, targetDir, cb
+
+    async.waterfall tasks, callback
+
+  installGitPackageDependencies: (directory, options, callback) =>
+    tasks = []
+
+    options.cwd = directory
+    tasks.push (cb) => @installDependencies(options, cb)
+    tasks.push (cb) -> CSON.readFile(CSON.resolve(path.join(directory, 'package')), cb)
+
+    async.waterfall tasks, (err, metadata) ->
+      callback(err, metadata)
+
+  cloneGitPackage: (packageInfo, cloneDir, callback) ->
     Develop = require './develop'
     develop = new Develop()

     if packageInfo.default is 'sshurl'
       url = packageInfo.toString()
-      develop.cloneRepository url, targetDir, {}, callback
+      develop.cloneRepository url, cloneDir, {}, callback
     else if packageInfo.default is 'https'
-      develop.cloneRepository name, targetDir, {}, callback
+      develop.cloneRepository name, cloneDir, {}, callback
     else if packageInfo.default is 'shortcut'
       url = packageInfo.https().replace(/^git\+https:/, "https:")
-      develop.cloneRepository url, targetDir, {}, (error) ->
+      develop.cloneRepository url, cloneDir, {}, (error) ->
         if error
           # more error checking to make sure failure is because of protocol
           url = packageInfo.sshurl()
-          develop.cloneRepository url, targetDir, {}, callback
+          develop.cloneRepository url, cloneDir, {}, callback
         else
           callback()

@@ -529,21 +553,7 @@
       gitPackageInfo = @getHostedGitInfo(name)

       if gitPackageInfo
-        targetDir = path.join(@atomGitPackagesDirectory, gitPackageInfo.project)
-        @installGitPackage gitPackageInfo, targetDir, (error) =>
-          return callback(error) if error
-          pack =
-            name: gitPackageInfo.project
-          installDirectory = temp.mkdirSync('apm-install-dir-')
-
-          tasks = []
-          tasks.push (cb) -> fs.cp(targetDir, installDirectory, cb)
-          tasks.push (cb) =>
-            options.cwd = installDirectory
-            @installDependencies(options, cb)
-          tasks.push (cb) -> fs.cp(installDirectory, targetDir, cb)
-          async.waterfall tasks, options.callback
-
+        @installGitPackage gitPackageInfo, options, options.callback
       else if name is '.'
         @installDependencies(options, callback)
       else # is registered package


Ensure project.paths is non-empty

Ensure project.paths is non-empty

--- lib/util.coffee
+++ lib/util.coffee
@@ -57,8 +57,8 @@
     rows = _.filter rows, (row) ->
       # Is `.project` non-null
       row.project? and
-      # Is `.project.paths` non-null
-      row.project.paths? and
+      # Is `.project.paths` non-empty
+      (row.project.paths || []).length > 0 and
       # Does `.project.paths` contain an array (only) of strings
       # NOTE: This one is weird -- how could the state get so corrupted?
       _.all(row.project.paths.map((pn) -> (pn || "").length > 0)) and


activity feed: fix bug where activity deletion response ui was broken

--- client/landing/app/Applications/Activity.kdapplication/views/activityitemchild.coffee
+++ client/landing/app/Applications/Activity.kdapplication/views/activityitemchild.coffee
@@ -60,10 +60,17 @@
         @tags.setData tags
         @tags.render()

+    deleteActivity = (activityItem)->
+      activityItem.slideOut -> activityItem.destroy()
+
+    @on 'ActivityIsDeleted', =>
+      activityItem = @getDelegate()
+      deleteActivity activityItem
+
     data.on 'PostIsDeleted', =>
       activityItem = @getDelegate()
       if KD.whoami().getId() is data.getAt('originId')
-        activityItem.slideOut -> activityItem.destroy()
+        deleteActivity activityItem
       else
         activityItem.putOverlay
           isRemovable : no


Sharper canvas.

Sharper canvas.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -303,18 +303,31 @@
     $('.entries').removeClass('show-saved-search')
     $('.saved-search-wrap').removeClass('open')

+  retinaCanvas: (canvas, context) ->
+    width = $(canvas).attr('width')
+    height = $(canvas).attr('height')
+    $(canvas).attr('width', width * window.devicePixelRatio)
+    $(canvas).attr('height', height * window.devicePixelRatio)
+    $(canvas).css
+      width: width
+      height: height
+    context.scale(window.devicePixelRatio, window.devicePixelRatio)
+    context
+
   drawBarChart: (canvas, values) ->
     if values
       spaceWidth = 1
-      numberOfSpaces = values.length - 1
       barWidth = 5
       if canvas.getContext
         context = canvas.getContext("2d")
+        canvasHeight = $(canvas).attr('height')
+        if 'devicePixelRatio' of window
+          context = feedbin.retinaCanvas(canvas, context)
         context.fillStyle = "#DDDDDD"
         xPosition = 0
         for value in values
-          height = Math.ceil(value * canvas.height)
-          yPosition = canvas.height - height
+          height = Math.ceil(value * canvasHeight)
+          yPosition = canvasHeight - height
           context.fillRect(xPosition, yPosition, barWidth, height)
           xPosition = xPosition + barWidth + spaceWidth


fixed bug in sendWithXhr

fixed bug in sendWithXhr

--- app/assets/javascripts/core_ext.coffee
+++ app/assets/javascripts/core_ext.coffee
@@ -370,11 +370,6 @@
       data : data.buffer
       url : @fallbackUrl
       responseType : 'arraybuffer'
-      (err, buffer) =>
-        if err
-          deferred.reject(err)
-        else
-          deferred.resolve(new @responseBufferType(buffer))
     ).pipe (buffer) => new @responseBufferType(buffer)

 _window = window ? self


cleaning up coffeescript

cleaning up coffeescript

--- app/assets/javascripts/new_trip.coffee
+++ app/assets/javascripts/new_trip.coffee
@@ -1,4 +1,7 @@
 $ ->
+  #
+  # Datetimepicker:
+  #
   window.departure = $('#datetimepicker_departure').datetimepicker({
     pickSeconds: false
   }).on('changeDate', (ev) ->
@@ -10,14 +13,56 @@
   window.arrival = $('#datetimepicker_arrival').datetimepicker({
     pickSeconds: false
   })
+  # Datetimepicker end
+
+
+  if google?
+    window.directionsDisplay = new google.maps.DirectionsRenderer({ draggable: true });
+    window.directionsService = new google.maps.DirectionsService();
+
+    mapOptions = {
+      center: new google.maps.LatLng(52.5234051, 13.411399899999992),
+      zoom: 1,
+      mapTypeId: google.maps.MapTypeId.ROADMAP
+    }
+
+    map = new google.maps.Map($('#map-canvas')[0], mapOptions)
+    directionsDisplay.setMap(map);
+
+    #
+    # When changing Route by dragging, construct new directions hash
+    # with waypoints and store it in trip:
+    #
+    google.maps.event.addListener directionsDisplay, 'directions_changed', () ->
+      if directionsDisplay.directions.status == google.maps.DirectionsStatus.OK
+        # Route with waypoints
+        $("#trip_route").val directions_hash(directionsDisplay)
+        $("#trip_distance").val directionsDisplay.directions.routes[0].legs[0].distance.value
+        # Google Maps duration
+        $("#trip_gmaps_duration").val directionsDisplay.directions.routes[0].legs[0].duration.value
+        # Display Distance
+        $("#trip_distance_display").animate({opacity: 0.25}, 500, -> $("#trip_distance_display").animate({opacity:1}))
+        $("#trip_distance_display").html directionsDisplay.directions.routes[0].legs[0].distance.text if $("#trip_distance_display")
+
+    autocomplete_for( 'from', map )
+    autocomplete_for( 'to', map )
+
+    $("#trip_from_lat").observe_field 1, ->
+      window.from = get_from_coordinates()
+      if window.from && window.to
+        route(window.from, window.to)
+
+    $("#trip_to_lat").observe_field 1, ->
+      window.to = get_to_coordinates()
+      if window.from && window.to
+        route(window.from, window.to)

+from = ''
+to = ''

-window.from = ''
-window.to = ''
-
-get_inputs_for = (direction) ->
+set_inputs_for = (direction) ->
   if place.address_components.length > 0
     for x in [0..place.address_components.length-1]
       type = place.address_components[x].types[0]
@@ -62,7 +107,7 @@
     $("input#trip_#{direction}_lng").val location.lng()
     $("input#trip_#{direction}_formatted_address").val place.formatted_address

-    get_inputs_for( direction )
+    set_inputs_for( direction )
   )

 get_from_coordinates = ->
@@ -89,78 +134,10 @@
       window.directionsDisplay.setDirections(response);
   )

-$ ->
-  $("#modal-btn").click ->
-    $("#from").html $("#trip_from_formatted_address").val()
-    $("#to").html $('#trip_to_formatted_address').val()
-    $("#distance").html $('#trip_distance_display').html()
-    $("#no_of_rides").html $('#trip_hitchhikes').val()
-    $("#departure").html $('#trip_departure').val()
-    $("#arrival").html $('#trip_arrival').val()
-    $("#traveling_with").html(
-      $("#trip_travelling_with option[value='" + ($('#trip_travelling_with').val()) + "']").text();
-    )

-  if google?
-    window.directionsDisplay = new google.maps.DirectionsRenderer({ draggable: true });
-    window.directionsService = new google.maps.DirectionsService();
-
-    mapOptions = {
-      center: new google.maps.LatLng(52.5234051, 13.411399899999992),
-      zoom: 1,
-      mapTypeId: google.maps.MapTypeId.ROADMAP
-    }
-
-    map = new google.maps.Map($('#map-canvas')[0], mapOptions)
-    directionsDisplay.setMap(map);
-
-    #
-    # When changing Route by dragging, construct new directions hash
-    # with waypoints and store it in trip:
-    #
-    google.maps.event.addListener directionsDisplay, 'directions_changed', () ->
-      if directionsDisplay.directions.status == google.maps.DirectionsStatus.OK
-        # Route with waypoints
-        $("#trip_route").val directions_hash(directionsDisplay)
-        $("#trip_distance").val directionsDisplay.directions.routes[0].legs[0].distance.value
-        # Google Maps duration
-        $("#trip_gmaps_duration").val directionsDisplay.directions.routes[0].legs[0].duration.value
-        # Display Distance
-        $("#trip_distance_display").animate({opacity: 0.25}, 500, -> $("#trip_distance_display").animate({opacity:1}))
-        $("#trip_distance_display").html directionsDisplay.directions.routes[0].legs[0].distance.text if $("#trip_distance_display")
-
-    autocomplete_for( 'from', map )
-    autocomplete_for( 'to', map )
-
-    $("#trip_from_lat").observe_field 1, ->
-      window.from = get_from_coordinates()
-      if window.from && window.to
-        route(window.from, window.to)
-
-    $("#trip_to_lat").observe_field 1, ->
-      window.to = get_to_coordinates()
-      if window.from && window.to
-        route(window.from, window.to)

 directions_hash = (directionsDisplay) ->
-  # A sample DirectionsRequest is shown below:
-  #
-  # {
-  #  origin: "Chicago, IL",
-  #  destination: "Los Angeles, CA",
-  #  waypoints: [
-  #    {
-  #      location:"Joplin, MO",
-  #      stopover:false
-  #    },{
-  #      location:"Oklahoma City, OK",
-  #      stopover:true
-  #    }],
-  #  provideRouteAlternatives: false,
-  #  travelMode: TravelMode.DRIVING,
-  #  unitSystem: UnitSystem.IMPERIAL
-  # }
   waypoints = []
   leg = directionsDisplay.directions.routes[0].legs[0]
   directions =


Ensure notes are highlighted properly when they're updated

--- app/assets/javascripts/notes.js.coffee
+++ app/assets/javascripts/notes.js.coffee
@@ -277,13 +277,15 @@

   Updates the current note field.
   ###
-  updateNote: (xhr, note, status) =>
-    note_li = $(".note-row-" + note.id)
-    note_li.replaceWith(note.html)
-    note_li.find('.note-edit-form').hide()
-    note_li.find('.note-body > .note-text').show()
-    note_li.find('js-task-list-container').taskList('enable')
-    @enableTaskList()
+  updateNote: (_xhr, note, _status) =>
+    # Convert returned HTML to a jQuery object so we can modify it further
+    $html = $(note.html)
+    $html.syntaxHighlight()
+    $html.find('.js-task-list-container').taskList('enable')
+
+    # Find the note's `li` element by ID and replace it with the updated HTML
+    $note_li = $("#note_#{note.id}")
+    $note_li.replaceWith($html)

   ###
   Called in response to clicking the edit note link


remove backticks in timeline.coffee

remove backticks in timeline.coffee

--- app/assets/javascripts/timeline.coffee
+++ app/assets/javascripts/timeline.coffee
@@ -1,11 +1,11 @@
 $ ->
-  if gon.run isnt `undefined`
+  if gon.run?
     $.each gon.run.splits, (i, split) ->
       $("#" + gon.run.id + "-split-" + i).each (j, el) ->
         el.style["z-index"] = i
         el.style.width = gon.run.splits[i].duration / gon.scale_to * 100 + "%"

-  if gon.comparison_run isnt `undefined`
+  if gon.comparison_run?
     $.each gon.comparison_run.splits, (i, split) ->
       $("#" + gon.comparison_run.id + "-split-" + i).each (j, el) ->
         el.style["z-index"] = i


Use meta key to open todo in new tab

Use meta key to open todo in new tab

--- app/assets/javascripts/todos.js.coffee
+++ app/assets/javascripts/todos.js.coffee
@@ -57,5 +57,10 @@
     $('.todos-pending .badge, .todos-pending-count').text data.count
     $('.todos-done .badge').text data.done_count

-  goToTodoUrl: ->
-    Turbolinks.visit($(this).data('url'))
+  goToTodoUrl: (e)->
+    todoLink = $(this).data('url')
+    if e.metaKey
+      e.preventDefault()
+      window.open(todoLink,'_blank')
+    else
+      Turbolinks.visit(todoLink)


Optimized autosuggestion closing behaviuor

--- app/assets/javascripts/users.js.coffee
+++ app/assets/javascripts/users.js.coffee
@@ -103,7 +103,7 @@
       open: () ->
         $(@).removeClass("ui-corner-all").addClass("ui-corner-top")
       close: () ->
-        $('#search_address').val("").blur()
+        $('#search_address').val("")
         $(@).removeClass("ui-corner-top").addClass("ui-corner-all")

     # Don't submit the form on enter key for this field


Don't redine `values()` in up.util

Don't redine `values()` in up.util

--- lib/assets/javascripts/unpoly/util.coffee
+++ lib/assets/javascripts/unpoly/util.coffee
@@ -262,7 +262,7 @@
   @return {Array<string>}
   @internal
   ###
-  values = Object.values || valuesPolyfill
+  objectValues = Object.values || valuesPolyfill

   ###**
   Returns a new string with whitespace removed from the beginning
@@ -2064,7 +2064,7 @@
   hasCssTransition: hasCssTransition
   separatedValues : separatedValues
   wrapArray: wrapArray
-  values: values
+  values: objectValues

 )(jQuery)


Don't enable legacy blog in v2

Don't enable legacy blog in v2

--- services/web/app/coffee/router.coffee
+++ services/web/app/coffee/router.coffee
@@ -90,8 +90,9 @@
 		if Settings.enableSubscriptions
 			webRouter.get  '/user/bonus', AuthenticationController.requireLogin(), ReferalController.bonus

-		webRouter.get '/blog', BlogController.getIndexPage
-		webRouter.get '/blog/*', BlogController.getPage
+		if !Settings.overleaf?
+			webRouter.get '/blog', BlogController.getIndexPage
+			webRouter.get '/blog/*', BlogController.getPage

 		webRouter.get '/user/activate', UserPagesController.activateAccountPage
 		AuthenticationController.addEndpointToLoginWhitelist '/user/activate'


logging

logging

--- app/js/controllers/transactions.coffee
+++ app/js/controllers/transactions.coffee
@@ -23,7 +23,6 @@

     $scope.$watchCollection "transactions", () ->
         $scope.account_transactions = Wallet.transactions[$scope.name]
-        console.log('Wallet.transactions[$scope.name]', Wallet.transactions[$scope.name])
         $scope.warning = ""
         if !$scope.account_transactions || $scope.account_transactions.length == 0
             $scope.warning = if $scope.pending_only then "There are no pending transactions!" else "There are no transactions!"


--- src/components/student-dashboard/progress-guide.cjsx
+++ src/components/student-dashboard/progress-guide.cjsx
@@ -10,7 +10,7 @@
 LearningGuideSection = require '../learning-guide/section'
 LearningGuideColorKey = require '../learning-guide/color-key'
 PracticeButton = require '../learning-guide/practice-button'
-WeakerSections = require '../learning-guide/weaker-sections'
+Section = require '../learning-guide/section'

 # Number of sections to display
 NUM_SECTIONS = 4
@@ -31,17 +31,18 @@
   render: ->
     courseId = @props.courseId
     guide = LearningGuide.Student.store.get(courseId)
+    sections = LearningGuide.Helpers.recentSections(
+      LearningGuide.Student.store.getAllSections(courseId)
+    )

Performance Forecast

Recent topics

-        <WeakerSections {...@props}
-          onPractice={@onPractice}
-          sections={LearningGuide.Student.store.getAllSections(courseId)}
-          weakerEmptyMessage="You haven't worked enough problems for Tutor to predict your weakest topics."
-        />
+          {for section, i in sections
+
}

       <LearningGuideColorKey />
@@ -56,10 +57,11 @@
     courseId: React.PropTypes.string.isRequired
     sampleSizeThreshold: React.PropTypes.number.isRequired

+  mixins: [ChapterSectionMixin]
   viewGuide: ->
     @context.router.transitionTo('viewGuide', {courseId: @props.courseId})

-  renderEmpty: ->
+  renderEmpty: (sections) ->

Performance Forecast

@@ -70,10 +72,16 @@

             This area will fill in with topics as you complete your assignments

+

+
{sections.length} sections were returned by the performance forecast

+          { for section in sections
+
{@sectionFormat(section.chapter_section)} section.title
 }
+

   render: ->
+<<<<<<< HEAD
     return @renderEmpty() unless LearningGuide.Helpers.canPractice({
       @props, sections:LearningGuide.Student.store.getAllSections(@props.courseId)
     })
@@ -81,14 +89,29 @@
     sections = LearningGuide.Helpers.weakestSections(
       LearningGuide.Student.store.getAllSections(@props.courseId), @props.sampleSizeThreshold
     )
+=======
+    sections = LearningGuide.Student.store.getAllSections(@props.courseId)
+    recent = LearningGuide.Helpers.recentSections(sections)
+    return @renderEmpty(sections) if _.isEmpty(recent)
+
+    if LearningGuide.Helpers.canPractice({sections})
+      practiceButton = <PracticeButton title='Practice my weakest topics'
+            courseId={@props.courseId} sections={sections} />
+>>>>>>> Show recent sections, regardless of strength

+<<<<<<< HEAD
         <ProgressGuide {...@props} />

         <PracticeButton title='Practice my weakest topics'
           {...@props} sections={sections} />
+=======
+        <ProgressGuide sections={recent} courseId={@props.courseId} />
+
+        {practiceButton}
+>>>>>>> Show recent sections, regardless of strength

         <BS.Button
           onClick={@viewGuide}


Fix up the stress tester

Fix up the stress tester

- bring players out at 10 players/s
- bring back 2-letter words (no numbers)

--- stress_test.coffee
+++ stress_test.coffee
@@ -5,8 +5,14 @@

 users = process.env.USERS || 50

+WORD_CHARS = "abcdefghijklmnopqrstuvwxyz"
+
 randWord = (len) ->
-  Math.random().toString(36).substr(2, len)
+  word = ""
+  for c in [1..len]
+    index = Math.floor(Math.random() * WORD_CHARS.length)
+    word += WORD_CHARS.charAt(index)
+  return word

 startPlayer = (name) ->
   socket = io(url, {multiplex: false})
@@ -22,8 +28,8 @@
         socket.emit 'new word', w
         console.log "#{name} wrote: #{w}"
       , 1000
-    , Math.random()*2000
+    , Math.random()*users*100

-for a in [0..users]
+for a in [1..users]
   startPlayer(process.env.TEST_NAME || "player #{a}")
   # console.log randWord(2)


zilde (⍬) in a separate file

zilde (⍬) in a separate file

--- src/vocabulary.coffee
+++ src/vocabulary.coffee
@@ -69,6 +69,8 @@
 lazy @, '⊖', './vocabulary/rotate'
 lazy @, '⍉', './vocabulary/transpose'
 lazy @, '∈', './vocabulary/epsilon'
+lazy @, 'get_⍬', './vocabulary/zilde'
+lazy @, 'set_⍬', './vocabulary/zilde'

 @[','] = (omega, alpha) ->
   if alpha
@@ -90,7 +92,6 @@
     if alpha then f alpha, omega, axis else f omega, undefined, axis
 (@['⍨'].aplMetaInfo ?= {}).isPostfixAdverb = true

-@['get_⍬'] = -> APLArray.zilde
 @['set_⎕'] = console.info

 do =>


fixed catenation (,); issue #5

fixed catenation (,); issue #5

It didn't work properly with boxed arrays.

--- src/vocabulary.coffee
+++ src/vocabulary.coffee
@@ -757,8 +757,14 @@

 # Helper for verbs `,` and `⍪`
 catenate = (b, a, axis = -1) ->
-  sa = shapeOf a; if sa.length is 0 then sa = [1]; a = [a]
-  sb = shapeOf b; if sb.length is 0 then sb = [1]; b = [b]
+  sa = shapeOf a
+  if sa.length is 0
+    sa = [1]
+    a = if isSimple a then [a] else [a[0]]
+  sb = shapeOf b
+  if sb.length is 0
+    sb = [1]
+    b = if isSimple b then [b] else [b[0]]
   assert sa.length is sb.length,
     'Length error: Cannot catenate arrays of different ranks'
   if axis < 0 then axis += sa.length


v1.1.1 linux test.  Update version in console message.

--- lib/multi-theme-applicator.coffee
+++ lib/multi-theme-applicator.coffee
@@ -10,7 +10,7 @@
   subscriptions: null

   activate: (state) ->
-    console.log "MultiThemeApplicator.activiate: entered v1.1.0"
+    console.log "MultiThemeApplicator.activiate: entered v1.1.1"
     @utils = new Utils()
     @localThemeSelectorView = new LocalThemeSelectorView(
       this, state['fileLookup'], state['FileTypeLookup'], state['ThemeLookup'])


Classroom: Fixed the bug where trying to fetch a non existent course manifest when last course shown.

--- client/app/Applications/Classroom.kdapplication/Views/classroomworkspace.coffee
+++ client/app/Applications/Classroom.kdapplication/Views/classroomworkspace.coffee
@@ -93,7 +93,8 @@
         Next       :
           title    : "Next Chapter"
           cssClass : "modal-clean-green"
-          callback : => @goToNextChapter modal
+          callback : =>
+            @goToNextChapter modal, remainingChapters is 1
     else
       title        = "Well done!"
       content      = "You have completed this course. You can go to courses to start another one."
@@ -123,16 +124,23 @@
           cssClass : "modal-clean-gray"
           callback : => modal.destroy()

-  goToNextChapter: (modal) ->
+  goToNextChapter: (modal, nextChapterIsLastChapter) ->
     {nextChapterConfig} = @getDelegate()
-    nextChapterLayout = nextChapterConfig.panel
+    nextChapterLayout   = nextChapterConfig.panel
     @addDefaultButtons nextChapterLayout
     @getOptions().panels.push nextChapterLayout

     @next()
+    data = @getData()
+    data.config = nextChapterConfig
+    data.courseMeta.index++
+
+    unless nextChapterIsLastChapter
+      delegate = @getDelegate()
+      delegate.currentChapterIndex++
+      delegate.fetchNextCourseConfig()
+
     modal.destroy()
-
-    @getData().config = nextChapterConfig

   goToCoursesView: (modal) ->
     modal.destroy()  if modal
@@ -155,9 +163,3 @@
       KD.getSingleton("windowController").addLayer container
       container.once "ReceivedClickElsewhere", ->
         container.toggleClass "active"
-
-  next: ->
-    super
-    delegate = @getDelegate()
-    delegate.currentChapterIndex++
-    delegate.fetchNextCourseConfig()


:lipstick:

:lipstick:

--- lib/package-generator-view.coffee
+++ lib/package-generator-view.coffee
@@ -16,7 +16,7 @@
       @div class: 'message', outlet: 'message'

   initialize: ->
-    @commandsSubscriptions = atom.commands.add 'atom-workspace',
+    @commandSubscription = atom.commands.add 'atom-workspace',
       'package-generator:generate-package': => @attach('package')
       'package-generator:generate-syntax-theme': => @attach('theme')

@@ -27,7 +27,7 @@

   destroy: ->
     @panel?.destroy()
-    @commandsSubscriptions.dispose()
+    @commandSubscription.dispose()

   attach: (@mode) ->
     @panel ?= atom.workspace.addModalPanel(item: this, visible: false)


Added tracking on giving focus on evidence search bar

--- core/app/backbone/views/fact_relations/auto_complete_fact_relations_view.coffee
+++ core/app/backbone/views/fact_relations/auto_complete_fact_relations_view.coffee
@@ -70,7 +70,10 @@

   setQuery: (text) -> @model.set text: text

-  focus: -> @$el.addClass 'active'
+  focus: ->
+    @$el.addClass 'active'
+
+    mp_track "Evidence: Search focus"

   reset: ->
     @setQuery ''


Do not show pop-ups on touch screen devices

--- frontend/script.coffee
+++ frontend/script.coffee
@@ -1086,8 +1086,12 @@

 SEARCH_WORKER = new Worker "/search.js"

+isTouchDevice = ->
+  return ('ontouchstart' of window) or ('onmsgesturechange' of window)
+
 $(document).ready ->
-  $('[data-toggle=tooltip]').tooltip({delay: { show: 400, hide: 100 }})
+  if not isTouchDevice()
+    $('[data-toggle=tooltip]').tooltip({delay: { show: 400, hide: 100 }})

   audio = new Audio();
   playbackType = null


Minor fix.

Minor fix.

--- src/web-server.coffee
+++ src/web-server.coffee
@@ -69,7 +69,7 @@

     ++@_notYetListening

-    errorHandler = (error) ->
+    errorHandler = (error) =>
       # `address()` can only be used once listening.
       address = if socket?
         socket


:bug: #5 improved path comparison + autoReveal

Resolve the path before comparing to improve filesystem compatability (I
do not have a mac to test this on sorry..) Also added an option to turn
off autoreveal. Also improved auto reveal with unfocus() as suggested in
#5

--- lib/tree-view-git-projects.coffee
+++ lib/tree-view-git-projects.coffee
@@ -5,19 +5,30 @@
   config:
     findByList:
       type: 'string'
-      title: "Find project root directories by:"
+      title: 'Find project root directories by:'
       description: 'List of file paths seperated by "|" \nThe existance of one of these indicates the root of a project.'
       default: '.git|package.json'
+    autoReveal:
+      type: 'boolean'
+      title: 'Automatically Reveal active tab in Tree View'
+      default: true
   activate: ->
     atom.config.observe 'tree-view-git-projects.findByList', (findByList) =>
       @findByList = "#{findByList}".split '|'
+    atom.config.observe 'tree-view-git-projects.autoReveal', (autoReveal) =>
+      @autoReveal = autoReveal
     atom.workspace.observeActivePane (pane) =>
       pane.observeActiveItem (item) =>
         if item?.getPath
           @findProjectRoot item.getPath?(), (directory) =>
-            if directory and directory isnt atom.project.getPaths()[0]
-              atom.project.setPaths [directory]
-            atom.packages.getActivePackage('tree-view')?.mainModule?.treeView?.revealActiveFile?()
+            if directory
+              directory = path.resolve directory
+              if directory isnt path.resolve atom.project.getPaths()[0]
+                atom.project.setPaths [directory]
+            if @autoReveal
+              treeView = atom.packages.getActivePackage('tree-view')?.mainModule?.treeView
+              treeView?.revealActiveFile?()
+              treeView?.unfocus?()
   findProjectRoot: (directory, callback) ->
     directory = "#{directory}".replace /[^\\\/]*[\\\/]*$/, ''
     if directory


remove useless group from regex

remove useless group from regex

--- lib/rules/trim-trailing-whitespace.coffee
+++ lib/rules/trim-trailing-whitespace.coffee
@@ -8,14 +8,14 @@
      ending (which might not be there if `insert_final_newline` is false).
    * @type {Regex}
   ###
-  _trailingWhitespace: /([^\S\r\n]+)(\r\n|\n|\r|)$/
+  _trailingWhitespace: /[^\S\r\n]+(\r\n|\n|\r|)$/
   fixLine: (line) =>
     if @setting
       match = line.match(@_trailingWhitespace)
       # join the line (without trailing space), with the captured line ending
       # (if there is one)
       if match?
-        return line[...-(match[0].length)] + match[2]
+        return line[...-(match[0].length)] + match[1]
     return line

   infer: =>


Point::translate -> Point::add

Point::translate -> Point::add

--- lib/bracket-matcher-view.coffee
+++ lib/bracket-matcher-view.coffee
@@ -104,8 +104,8 @@
         matchPosition = @findMatchingStartPair(position, matchingPair, currentPair)

     if position? and matchPosition?
-      @moveStartView([position, position.translate([0, 1])])
-      @moveEndView([matchPosition, matchPosition.translate([0, 1])])
+      @moveStartView([position, position.add([0, 1])])
+      @moveEndView([matchPosition, matchPosition.add([0, 1])])
       @pairHighlighted = true
     else
       if pair = @tagFinder.findMatchingTags()
@@ -137,7 +137,7 @@
           # if on the same line and the cursor is in front of an end pair
           # offset by one to make up for the missing character
           if position.row is matchPosition.row and endPairMatches.hasOwnProperty(currentPair)
-            position = position.translate([0, -1])
+            position = position.add([0, -1])
           @editor.setCursorBufferPosition(position)
           @editor.delete()
         else
@@ -146,7 +146,7 @@
         @editor.backspace()

   findMatchingEndPair: (startPairPosition, startPair, endPair) ->
-    scanRange = new Range(startPairPosition.translate([0, 1]), @editor.buffer.getEndPosition())
+    scanRange = new Range(startPairPosition.add([0, 1]), @editor.buffer.getEndPosition())
     endPairPosition = null
     unpairedCount = 0
     @editor.scanInBufferRange pairRegexes[startPair], scanRange, ({match, range, stop}) ->
@@ -218,7 +218,7 @@
     position = @editor.getCursorBufferPosition()
     currentPair = @editor.getTextInRange(Range.fromPointWithDelta(position, 0, 1))
     unless matches[currentPair]
-      position = position.translate([0, -1])
+      position = position.add([0, -1])
       currentPair = @editor.getTextInRange(Range.fromPointWithDelta(position, 0, 1))
     if matchingPair = matches[currentPair]
       {position, currentPair, matchingPair}
@@ -238,30 +238,32 @@
       if startRange.compare(endRange) > 0
         [startRange, endRange] = [endRange, startRange]

-      startRange = startRange.translate([0, -1]) # include the <
-      endRange = endRange.translate([0, -2]) # include the </
+      # include the <
+      startRange = new Range(startRange.start.add([0, -1]), endRange.end.add([0, -1]))
+      # include the </
+      endRange = new Range(endRange.start.add([0, -2]), endRange.end.add([0, -2]))

       if position.isLessThan(endRange.start)
         tagCharacterOffset = position.column - startRange.start.column
         tagCharacterOffset++ if tagCharacterOffset > 0
         tagCharacterOffset = Math.min(tagCharacterOffset, tagLength + 2) # include </
-        @editor.setCursorBufferPosition(endRange.start.translate([0, tagCharacterOffset]))
+        @editor.setCursorBufferPosition(endRange.start.add([0, tagCharacterOffset]))
       else
         tagCharacterOffset = position.column - endRange.start.column
         tagCharacterOffset-- if tagCharacterOffset > 1
         tagCharacterOffset = Math.min(tagCharacterOffset, tagLength + 1) # include <
-        @editor.setCursorBufferPosition(startRange.start.translate([0, tagCharacterOffset]))
-    else
-      previousPosition = position.translate([0, -1])
+        @editor.setCursorBufferPosition(startRange.start.add([0, tagCharacterOffset]))
+    else
+      previousPosition = position.add([0, -1])
       startPosition = @startView.bufferPosition
       endPosition = @endView.bufferPosition

       if position.isEqual(startPosition)
-        @editor.setCursorBufferPosition(endPosition.translate([0, 1]))
+        @editor.setCursorBufferPosition(endPosition.add([0, 1]))
       else if previousPosition.isEqual(startPosition)
         @editor.setCursorBufferPosition(endPosition)
       else if position.isEqual(endPosition)
-        @editor.setCursorBufferPosition(startPosition.translate([0, 1]))
+        @editor.setCursorBufferPosition(startPosition.add([0, 1]))
       else if previousPosition.isEqual(endPosition)
         @editor.setCursorBufferPosition(startPosition)

@@ -289,7 +291,7 @@

       if @tagHighlighted
         startPosition = startRange.end
-        endPosition = endRange.start.translate([0, -2]) # Don't select </
+        endPosition = endRange.start.add([0, -2]) # Don't select </
       else
         startPosition = startRange.start
         endPosition = endRange.start
@@ -302,10 +304,10 @@
         if startRange.compare(endRange) > 0
           [startRange, endRange] = [endRange, startRange]
         startPosition = startRange.end
-        endPosition = endRange.start.translate([0, -2]) # Don't select </
+        endPosition = endRange.start.add([0, -2]) # Don't select </

     if startPosition? and endPosition?
-      rangeToSelect = new Range(startPosition, endPosition).translate([0, 1], [0, 0])
+      rangeToSelect = new Range(startPosition.add([0, 1]), endPosition)
       @editor.setSelectedBufferRange(rangeToSelect)

   # Insert at the current cursor position a closing tag if there exists an


Move load semantics into load method

Move load semantics into load method

--- scripts/models/content/module.coffee
+++ scripts/models/content/module.coffee
@@ -12,6 +12,10 @@
   # * `keywords` - an array of keywords (eg `['constant', 'boltzmann constant']`)
   # * `authors` - an `Collection` of `User`s that are attributed as authors
   return BaseModel.extend
+    # This is a "loading" promise (result of 1st fetch)
+    # TODO: use a private instance variable once this class becomes a coffeescript class
+    _loading: null
+
     mediaType: 'application/vnd.org.cnx.module'
     accept: []
     loaded: false
@@ -27,6 +31,14 @@
       # Default language for new content is the browser's language
       language: navigator?.language or navigator?.userLanguage or 'en'

+    load: () ->
+      if not @_loading
+        @_loading = @fetch()
+        @_loading.done () =>
+          @trigger('loaded')
+          @loaded = true
+      @_loading
+
     # Begin editing this medium as soon as it is added
     addAction: () ->
       id = @id or @cid
@@ -39,11 +51,7 @@
         view = new View({model: @})
         callback(view)

-        if not @loaded
-          @fetch
-            success: (model, response, options) =>
-              @loaded = true
-              @trigger('loaded')
+        @load()

     # Change the toolbar view when editing this
     toolbarView: (callback) ->


Library: Fix wrong filename for components from directory

Would fail to start

--- src/library.coffee
+++ src/library.coffee
@@ -71,9 +71,10 @@
         lang = extensionToLanguage[ext]
         component = path.basename(filename, ext)
         debug 'loading component from file', filename, component
+        filepath = path.join directory, filename
         components[component] =
           language: lang
-          command: componentCommandForFile config, filename
+          command: componentCommandForFile config, filepath

       return callback null, components


:bug: pre-release-BUG cursorNode is not properly scoped and fail to dispose style

--- lib/cursor-style-manager.coffee
+++ lib/cursor-style-manager.coffee
@@ -40,16 +40,18 @@
     for cursor in @editor.getCursors()
       cursorIsVisible = cursor in cursorsToShow
       cursor.setVisible(cursorIsVisible)
+      if cursorIsVisible
+        @styleDisposables.add @modifyCursorStyle(cursor, @getCursorStyle(cursor, true))

-      if cursorIsVisible
-        cursorStyle = @getCursorStyle(cursor, cursorIsVisible)
-        # [NOTE] Using non-public API
-        cursorNode = @editorElement.component.linesComponent.cursorsComponent.cursorNodesById[cursor.id]
-        cursorNode.style.setProperty('top', cursorStyle.top)
-        cursorNode.style.setProperty('left', cursorStyle.left)
-        @styleDisposables.add new Disposable ->
-          cursorNode.style.removeProperty('top')
-          cursorNode.style.removeProperty('left')
+  modifyCursorStyle: (cursor, cursorStyle) ->
+    cursorStyle = @getCursorStyle(cursor, true)
+    # [NOTE] Using non-public API
+    cursorNode = @editorElement.component.linesComponent.cursorsComponent.cursorNodesById[cursor.id]
+    cursorNode.style.setProperty('top', cursorStyle.top)
+    cursorNode.style.setProperty('left', cursorStyle.left)
+    new Disposable ->
+      cursorNode.style.removeProperty('top')
+      cursorNode.style.removeProperty('left')

   updateCursorStyleNew: ->
     # We must dispose previous style modification for non-visual-mode


Add missing type to bills created by numericable konnector

--- server/konnectors/numericable.coffee
+++ server/konnectors/numericable.coffee
@@ -186,6 +186,7 @@
             .replace(',', '.')
         )
         pdfurl: baseURL + billLink.attr "href"
+        type: 'internet'
         vendor: 'Numéricable'

     bills.fetched.push bill if bill.date? and bill.amount? and bill.pdfurl?
@@ -205,6 +206,7 @@
                 .replace(',', '.')
             )
             pdfurl: baseURL + billLink.attr 'href'
+            type: 'internet'
             vendor: 'Numéricable'

         bills.fetched.push bill if bill.date? and bill.amount? and bill.pdfurl?


server/embedly: add ext of image to filename, not just md5 of url

--- server/lib/server/image_cache.coffee
+++ server/lib/server/image_cache.coffee
@@ -22,8 +22,14 @@
              "key=#{apiKey}&" +
              "url=#{url}"

-  md5      = crypto.createHash("md5").update(url).digest("hex")
-  filename = "#{imagePath}/#{md5}"
+  # split the ext, md5 just the url and write md5+ext to disk
+  # md5 since some urls are too big and cause 'ENAMETOOLONG' error
+  splitUrl = url.split(".")
+  ext      = splitUrl.pop()
+  noExt    = splitUrl.join(".")
+
+  md5      = crypto.createHash("md5").update(noExt).digest("hex")
+  filename = "#{imagePath}/#{md5}.#{ext}"

   serveFile = (filename, res)->
     fileStream = fs.createReadStream filename


Add some things to Message

Add some things to Message

--- src/message.coffee
+++ src/message.coffee
@@ -1,10 +1,16 @@
+
+# Experimental: This will likely change, do not use.
 module.exports =
 class Message
   constructor: (@type, @message, @options={}) ->

+  getOptions: -> @options
+
   getType: -> @type

   getMessage: -> @message
+
+  getDetail: -> @optons.detail

   isClosable: ->
     !!@options.closable
@@ -31,7 +37,7 @@

     Stack Trace
     ```
-    At #{@options.errorDetail}
+    At #{@options.detail}

     #{@options.stack}
     ```


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color2.coffee
+++ test/color2.coffee
@@ -1,5 +1,9 @@
 module "Color2"

+test "just checking if things work", ->
+  color = Color2()
+
+  ok color.aMethod()

 module()


Fix test.common.await

Fix test.common.await

--- test/common.coffee
+++ test/common.coffee
@@ -38,7 +38,7 @@
 await = (fn, timeout=100) ->
   if fn() == true
     return true
-  setTimeout(fn, timeout)
+  setTimeout((-> await(fn, timeout)), timeout)

 fetchBrowser = () ->
   browser = new Browser()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -189,7 +189,7 @@

   engine.bind "stateEntered", ->
     ok true
-  engine.bi
+  engine.bind ""

   engine.update()


Fixed java.lang.Runtime's gc() method.

Fixed java.lang.Runtime's gc() method.

--- src/natives.coffee
+++ src/natives.coffee
@@ -275,10 +275,11 @@
         ]
       Runtime: [
         o 'availableProcessors()I', () -> 1
-        o 'gc()V', () ->
+        o 'gc()V', (rs) ->
             # No universal way of forcing browser to GC, so we yield in hopes
             # that the browser will use it as an opportunity to GC.
-            throw new util.YieldIOException(cb) -> setTimeout(cb, 0)
+            rs.curr_frame().resume = -> # NOP
+            throw new util.YieldIOException (cb) -> setTimeout(cb, 0)
       ]
       Shutdown: [
         o 'halt0(I)V', (rs) -> throw new util.HaltException(rs.curr_frame().locals[0])


fixing StrictMath.pow, and thus Math.pow on OpenJDK

--- src/natives.coffee
+++ src/natives.coffee
@@ -629,7 +629,7 @@
         o 'cos(D)D', (rs, d_val) -> Math.cos(d_val)
         o 'exp(D)D', (rs, d_val) -> Math.exp(d_val)
         o 'log(D)D', (rs, d_val) -> Math.log(d_val)
-        o 'pow(DD)D', (rs) -> Math.pow(rs.cl(0),rs.cl(2))
+        o 'pow(DD)D', (rs, base, exp) -> Math.pow(base, exp)
         o 'sin(D)D', (rs, d_val) -> Math.sin(d_val)
         o 'sqrt(D)D', (rs, d_val) -> Math.sqrt(d_val)
         o 'tan(D)D', (rs, d_val) -> Math.tan(d_val)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/sprite.coffee
+++ test/sprite.coffee
@@ -8,7 +8,7 @@
   spriteUrl = "http://images.pixie.strd6.com/avatars/1/thumb.png"

   callback = (s) ->
-    sprite2 = Sprite.load spriteUrl
+    sprite2 = Sprite.load spriteUrl, ){->

   sprite = Sprite.load spriteUrl, callback


Add support for platforms and optional dependencies

--- src/npm2nix.coffee
+++ src/npm2nix.coffee
@@ -62,9 +62,11 @@

       stream.write "\n  by-version.\"#{escapeNixString pkg.name}\".\"#{escapeNixString pkg.version}\" = self.buildNodePackage {"
       stream.write "\n    name = \"#{escapeNixString pkg.name}-#{escapeNixString pkg.version}\";"
+      stream.write "\n    version = \"#{escapeNixString pkg.version}\";"
       stream.write "\n    bin = #{if "bin" of pkg then "true" else "false"};"

       stream.write "\n    src = "
+
       if 'tarball' of pkg.dist
         stream.write """
         fetchurl {
@@ -82,15 +84,26 @@
             }
         """

+      seenDeps = {}
+
       stream.write ";\n    deps = {"
-      seenDeps = {}
       for nm, spc of pkg.dependencies or {}
+        unless seenDeps[nm] or nm of (pkg.optionalDependencies or {})
+          spc = spc.version if spc instanceof Object
+          if spc is 'latest' or spc is ''
+            spc = '*'
+          stream.write "\n      \"#{escapeNixString nm}-#{packageSet[nm][spc].version}\" = self.by-version.\"#{escapeNixString nm}\".\"#{packageSet[nm][spc].version}\";"
+          seenDeps[nm] = true
+
+      stream.write "\n    };\n    optionalDependencies = {"
+      for nm, spc of pkg.optionalDependencies or {}
         unless seenDeps[nm]
           spc = spc.version if spc instanceof Object
           if spc is 'latest' or spc is ''
             spc = '*'
           stream.write "\n      \"#{escapeNixString nm}-#{packageSet[nm][spc].version}\" = self.by-version.\"#{escapeNixString nm}\".\"#{packageSet[nm][spc].version}\";"
         seenDeps[nm] = true
+
       stream.write "\n    };\n    peerDependencies = ["
       for nm, spc of pkg.peerDependencies or {}
         unless seenDeps[nm] or cycleDeps[nm]
@@ -99,7 +112,19 @@
             spc = '*'
           stream.write "\n      self.by-version.\"#{escapeNixString nm}\".\"#{packageSet[nm][spc].version}\""
         seenDeps[nm] = true
-      stream.write "];\n  };"
+      stream.write "];\n"
+
+      stream.write "    os = ["
+      for os, i in pkg.os or []
+        stream.write " \"#{os}\""
+      stream.write " ];\n"
+
+      stream.write "    cpu = ["
+      for cpu, i in pkg.cpu or []
+        stream.write " \"#{cpu}\""
+      stream.write " ];\n"
+
+      stream.write "  };"

     if fullNames[name] is spec
       stream.write """


unsubstantial fixes

unsubstantial fixes

--- library/exposure/inventory.coffee
+++ library/exposure/inventory.coffee
@@ -37,6 +37,7 @@
 http = require "http"
 util = require "util"

+{CachingToolkit} = require "./caching"
 {OnlySsl} = require "../membrane/securing"
 {GraniteKernel} = require "../nucleus/kernel"
 {Barebones} = require "../membrane/skeleton"
@@ -50,6 +51,13 @@
 # to this service implementation source code for more information.
 module.exports.ApiInventory = class ApiInventory extends ApiService

+    # These declarations below are implantations of the abstracted
+    # components by the means of the dynamic recomposition system.
+    # Please take a look at the `Composition` class implementation
+    # for all sorts of information on the composition system itself.
+    # Each of these will be dynamicall integrated in class hierarchy.
+    @implanting CachingToolkit
+
     # Impose a conditional limitation on the service. The limiation
     # will be invoked when a router is determining whether a service
     # matches the condition or not. The limitation has to either do
@@ -57,8 +65,6 @@
     # Especially useful for service with the same resource but with
     # different conditions, such as mobile only and desktop only.
     @condition (r, s, decide) -> decide nconf.get "api:inventory"
-
-    @implanting require("./caching").CachingToolkit

     # This block defines a set of documentation directives. All of
     # the directives will be attached to the first API endpoint of


change alert close method

change alert close method

--- JSKit/03_JSAlertView.coffee
+++ JSKit/03_JSAlertView.coffee
@@ -54,8 +54,8 @@
                             @delegate.clickedButtonAtIndex(text, @_self)
                         when "JSAlertViewStyleDefault"
                             @delegate.clickedButtonAtIndex(1, @_self)
-                        if (@delegate? && typeof @delegate.closedDialog == "function")
-                            @delegate.closedDialog(@_self)
+                    if (@delegate? && typeof @delegate.closedDialog == "function")
+                        @delegate.closedDialog(@_self)
                 $(@_viewSelector+"_form").dialog("close")
                 @_self.removeFromSuperview()
         if (@cancel == true)


Delegate methods correctly

Delegate methods correctly

--- src/project.coffee
+++ src/project.coffee
@@ -310,7 +310,7 @@
       @on 'buffer-created', (buffer) -> callback(buffer)

   # Deprecated delegates
-  registerOpener: -> atom.workspaceView.model.registerOpener(arguments)
-  unregisterOpener: -> atom.workspaceView.model.unregisterOpener(arguments)
-  eachEditor: -> atom.workspaceView.model.eachEditor(arguments)
-  getEditors: -> atom.workspaceView.model.getEditors(arguments)
+  registerOpener: -> atom.workspaceView.model.registerOpener(arguments...)
+  unregisterOpener: -> atom.workspaceView.model.unregisterOpener(arguments...)
+  eachEditor: -> atom.workspaceView.model.eachEditor(arguments...)
+  getEditors: -> atom.workspaceView.model.getEditors(arguments...)


Fix grid for 0 on win-loss page

Fix grid for 0 on win-loss page

--- source/javascripts/win-loss.js.coffee
+++ source/javascripts/win-loss.js.coffee
@@ -95,7 +95,7 @@
       if isGrandSlam()
         if d > 1 then d else d * 100
       else
-        if d > 1 then d else 50 + d * 100
+        if d is 0 or d > 1 then d else 50 + d * 100

     y(value)


Pretty sure this will fix the path issue

--- lib/project-manager-view.coffee
+++ lib/project-manager-view.coffee
@@ -62,7 +62,7 @@
       unless error
         projects = []
         for title, project of currentProjects
-          if project.template?
+          if project.template? and currentProjects[project.template]?
             project = _.deepExtend(project, currentProjects[project.template])
           projects.push(project) if project.paths?


cleanup

cleanup

--- lib/quick-highlight-view.coffee
+++ lib/quick-highlight-view.coffee
@@ -14,131 +14,128 @@
 # - Update statusbar count on activeEditor was changed
 # - Clear marker for invisible editor?: Decide to skip to avoid clere/re-render.
 # - Update only keyword added/remove: Achived by diffing.
+module.exports =
+class QuickHighlightView
+  decorationStyle: null

-# https://github.com/atom/text-buffer/pull/192
-# Use markerLayer.clear() in future
-module.exports =
-  class QuickHighlightView
-    decorationStyle: null
+  getConfig: (name) ->
+    atom.config.get("quick-highlight.#{name}")

-    getConfig: (name) ->
-      atom.config.get("quick-highlight.#{name}")
+  constructor: (@editor, {@keywordManager, @statusBarManager, @emitter}) ->
+    @keywordToMarkerLayer = Object.create(null)

-    constructor: (@editor, {@keywordManager, @statusBarManager, @emitter}) ->
-      @keywordToMarkerLayer = Object.create(null)
+    @disposables = new CompositeDisposable

-      @disposables = new CompositeDisposable
+    highlightSelection = null
+    updateHighlightSelection = (delay) =>
+      highlightSelection = _.debounce(@highlightSelection, delay)

-      highlightSelection = null
-      updateHighlightSelection = (delay) =>
-        highlightSelection = _.debounce(@highlightSelection, delay)
+    @disposables.add(
+      atom.config.observe('quick-highlight.highlightSelectionDelay', updateHighlightSelection)
+      atom.config.observe('quick-highlight.decorate', @observeDecorationStyle)
+      @editor.onDidDestroy(@destroy)

-      @disposables.add(
-        atom.config.observe('quick-highlight.highlightSelectionDelay', updateHighlightSelection)
-        atom.config.observe('quick-highlight.decorate', @observeDecorationStyle)
-        @editor.onDidDestroy(@destroy)
+      # Don't pass function directly since we UPDATE highlightSelection on config change
+      @editor.onDidChangeSelectionRange(({selection}) =>
+        if selection.isEmpty()
+          @clearSelectionHighlight()
+        else
+          highlightSelection(selection)
+       )

-        # Don't pass function directly since we UPDATE highlightSelection on config change
-        @editor.onDidChangeSelectionRange(({selection}) =>
-          if selection.isEmpty()
-            @clearSelectionHighlight()
-          else
-            highlightSelection(selection)
-         )
+      atom.workspace.onDidChangeActivePaneItem(@refresh)
+      @keywordManager.onDidChangeKeyword(@refresh)
+      @keywordManager.onDidClearKeyword(@clear)
+      @editor.onDidStopChanging(@reset)
+    )

-        atom.workspace.onDidChangeActivePaneItem(@refresh)
-        @keywordManager.onDidChangeKeyword(@refresh)
-        @keywordManager.onDidClearKeyword(@clear)
-        @editor.onDidStopChanging(@reset)
-      )
+  observeDecorationStyle: (newValue) =>
+    @decorationStyle = newValue
+    @reset()

-    observeDecorationStyle: (newValue) =>
-      @decorationStyle = newValue
-      @reset()
+  needSelectionHighlight: (text) ->
+    editorElement = @editor.element
+    excludeScopes = @getConfig('highlightSelectionExcludeScopes')
+    switch
+      when (not @getConfig('highlightSelection'))
+          , (excludeScopes.some (scope) -> matchScope(editorElement, scope))
+          , /\n/.test(text)
+          , text.length < @getConfig('highlightSelectionMinimumLength')
+          , (not /\S/.test(text))
+        false
+      else
+        true

-    needSelectionHighlight: (text) ->
-      editorElement = @editor.element
-      excludeScopes = @getConfig('highlightSelectionExcludeScopes')
-      switch
-        when (not @getConfig('highlightSelection'))
-            , (excludeScopes.some (scope) -> matchScope(editorElement, scope))
-            , /\n/.test(text)
-            , text.length < @getConfig('highlightSelectionMinimumLength')
-            , (not /\S/.test(text))
-          false
-        else
-          true
+  highlightSelection: (selection) =>
+    @clearSelectionHighlight()
+    keyword = selection.getText()
+    if @needSelectionHighlight(keyword)
+      @markerLayerForSelectionHighlight = @highlight(keyword, 'box-selection')

-    highlightSelection: (selection) =>
-      @clearSelectionHighlight()
-      keyword = selection.getText()
-      if @needSelectionHighlight(keyword)
-        @markerLayerForSelectionHighlight = @highlight(keyword, 'box-selection')
+  clearSelectionHighlight: ->
+    @markerLayerForSelectionHighlight?.destroy()
+    @markerLayerForSelectionHighlight = null

-    clearSelectionHighlight: ->
-      @markerLayerForSelectionHighlight?.destroy()
-      @markerLayerForSelectionHighlight = null
+  highlight: (keyword, color) ->
+    markerLayer = @editor.addMarkerLayer()
+    @editor.decorateMarkerLayer(markerLayer, type: 'highlight', class: "quick-highlight #{color}")
+    @editor.scan ///#{_.escapeRegExp(keyword)}///g, ({range}) ->
+      markerLayer.markBufferRange(range, invalidate: 'inside')
+    if markerLayer.getMarkerCount() > 0
+      markers = markerLayer.getMarkers()
+      @emitter.emit('did-change-highlight', {@editor, markers, color})
+    markerLayer

-    highlight: (keyword, color) ->
-      markerLayer = @editor.addMarkerLayer()
-      @editor.decorateMarkerLayer(markerLayer, type: 'highlight', class: "quick-highlight #{color}")
-      @editor.scan ///#{_.escapeRegExp(keyword)}///g, ({range}) ->
-        markerLayer.markBufferRange(range, invalidate: 'inside')
-      if markerLayer.getMarkerCount() > 0
-        markers = markerLayer.getMarkers()
-        @emitter.emit('did-change-highlight', {@editor, markers, color})
-      markerLayer
+  getDiff: ->
+    masterKeywords = _.keys(@keywordManager.keywordToColor)
+    currentKeywords = _.keys(@keywordToMarkerLayer)
+    newKeywords = _.without(masterKeywords, currentKeywords...)
+    oldKeywords = _.without(currentKeywords, masterKeywords...)
+    if newKeywords.length or oldKeywords.length
+      {newKeywords, oldKeywords}
+    else
+      null

-    getDiff: ->
-      masterKeywords = _.keys(@keywordManager.keywordToColor)
-      currentKeywords = _.keys(@keywordToMarkerLayer)
-      newKeywords = _.without(masterKeywords, currentKeywords...)
-      oldKeywords = _.without(currentKeywords, masterKeywords...)
-      if newKeywords.length or oldKeywords.length
-        {newKeywords, oldKeywords}
-      else
-        null
+  render: ({newKeywords, oldKeywords}) ->
+    # Delete
+    for keyword in oldKeywords
+      @keywordToMarkerLayer[keyword].destroy()
+      delete @keywordToMarkerLayer[keyword]

-    render: ({newKeywords, oldKeywords}) ->
-      # Delete
-      for keyword in oldKeywords
-        @keywordToMarkerLayer[keyword].destroy()
-        delete @keywordToMarkerLayer[keyword]
+    # Add
+    {keywordToColor} = @keywordManager
+    for keyword in newKeywords when color = keywordToColor[keyword]
+      @keywordToMarkerLayer[keyword] = @highlight(keyword, "#{@decorationStyle}-#{color}")

-      # Add
-      {keywordToColor} = @keywordManager
-      for keyword in newKeywords when color = keywordToColor[keyword]
-        @keywordToMarkerLayer[keyword] = @highlight(keyword, "#{@decorationStyle}-#{color}")
+  clear: =>
+    for keyword, markerLayer of @keywordToMarkerLayer
+      markerLayer.destroy()
+    @keywordToMarkerLayer = Object.create(null)

-    clear: =>
-      for keyword, markerLayer of @keywordToMarkerLayer
-        markerLayer.destroy()
-      @keywordToMarkerLayer = Object.create(null)
+  reset: =>
+    @clear()
+    @refresh()

-    reset: =>
-      @clear()
-      @refresh()
+  refresh: =>
+    visibleEditors = atom.workspace.getPanes()
+      .map (pane) -> pane.getActiveEditor()
+      .filter (editor) -> editor?

-    refresh: =>
-      visibleEditors = atom.workspace.getPanes()
-        .map (pane) -> pane.getActiveEditor()
-        .filter (editor) -> editor?
+    return unless @editor in visibleEditors

-      return unless @editor in visibleEditors
+    if diff = @getDiff()
+      @render(diff)
+    @updateStatusBarIfNecesssary()

-      if diff = @getDiff()
-        @render(diff)
-      @updateStatusBarIfNecesssary()
+  updateStatusBarIfNecesssary: ->
+    if @getConfig('displayCountOnStatusBar') and @editor is atom.workspace.getActiveTextEditor()
+      @statusBarManager.clear()
+      keyword = @keywordManager.latestKeyword
+      count = @keywordToMarkerLayer[keyword]?.getMarkerCount() ? 0
+      if count > 0
+        @statusBarManager.update(count)

-    updateStatusBarIfNecesssary: ->
-      if @getConfig('displayCountOnStatusBar') and @editor is atom.workspace.getActiveTextEditor()
-        @statusBarManager.clear()
-        keyword = @keywordManager.latestKeyword
-        count = @keywordToMarkerLayer[keyword]?.getMarkerCount() ? 0
-        if count > 0
-          @statusBarManager.update(count)
-
-    destroy: =>
-      @clear()
-      @clearSelectionHighlight()
-      @disposables.dispose()
+  destroy: =>
+    @clear()
+    @clearSelectionHighlight()
+    @disposables.dispose()


exit with nonzero value if validation fails

--- lib/schematype/validator.coffee
+++ lib/schematype/validator.coffee
@@ -27,6 +27,7 @@
     ajv = new Ajv \
       allErrors: true
     validator = ajv.compile json
+    got_errors = false
     while @next_io()
       out "Validating '#{@input.name}':"
       yaml = @input.read()
@@ -40,10 +41,12 @@
       if valid = validator data
         say " OK!"
       else
+        got_errors = true
         say ''
         for err in validator.errors
           @analyze_error err
       say ''
+      process.exit Number got_errors

   analyze_error: (error)->
     path = error.dataPath


Http changes (#3)

Http changes (#3)

* re-made https changes

* undid taking away https from upload.coffee - epicenter

--- lib/util/simulate/upload.coffee
+++ lib/util/simulate/upload.coffee
@@ -7,11 +7,11 @@
     exec "#{__dirname}/../../../upload.sh #{localPath} #{simPath} #{user} #{password}", callback

 uploadFileAPI = (domain, localPath, simPath, token, callback)->
-    file_url = "#{domain}/simulate/api/file/#{simPath}"
+    file_url = "https://#{domain}/simulate/api/file/#{simPath}"
     exec "curl --silent -L -F token=#{token} -F content=@#{localPath} -F method=PUT #{file_url}", callback

 uploadZip = (domain, localPath, simPath, token, callback)->
-    file_url = "#{domain}/simulate/api/file/#{simPath}"
+    file_url = "https://#{domain}/simulate/api/file/#{simPath}"
     exec "curl -L -F token=#{token} -F content=@#{localPath} -F method=PUT -F unzip=true #{file_url}", callback

 exports.uploadFile = uploadFile


Update scraping class names in DOM tree to follow Youtube's update.

--- src/scraper.coffee
+++ src/scraper.coffee
@@ -27,10 +27,11 @@
   ## Returns
     Integer value.
   ###
-  if value? and value is not NaN
-    parseInt value
-  else
-    0
+  if value?
+    res = parseInt value, 10
+    if not isNaN res
+      return res
+  return 0

 module.exports =
@@ -96,8 +97,9 @@
                 # 1000 msec seems enough to load each page.
                 load_hidden_pages 1000, ->
                   # Load omitted comments.
-                  for read_more in document.getElementsByClassName("read-more")
-                    read_more.firstElementChild.click()
+                  for read_more in document.getElementsByClassName("comment-replies-renderer-expander-down")
+                    # read_more.firstElementChild.click()
+                    read_more.click()

                   document.body.dataset.youtubeCommentScraper = "ready"


Add integration spec for custom params in Brainstem.Collection#fetch

--- spec/brainstem-collection-spec.coffee
+++ spec/brainstem-collection-spec.coffee
@@ -295,7 +295,7 @@
           expect(lastFetchOptions.limit).toEqual(3)

     describe 'integration', ->
-      onDone = options = collection = posts1 = posts2 = null
+      options = collection = posts1 = posts2 = null

       beforeEach ->
         posts1 = [buildPost(), buildPost(), buildPost(), buildPost(), buildPost()]
@@ -303,28 +303,45 @@

         options = { page: 1, perPage: 5 }
         collection = new App.Collections.Posts(null, options)
-
+
+      it 'updates collection with response', ->
         respondWith(server, '/api/posts?per_page=5&page=1', resultsFrom: 'posts', data: { posts: posts1 })
-        collection.fetch().done(onDone = jasmine.createSpy())
-
+
+        collection.fetch()
         server.respond()

-      it 'passes returned models to chained callbacks', ->
-        expect(collection.pluck 'id').toEqual(_.pluck posts1, 'id')
-
-      it 'subsequent fetches return data from storage manager cache', ->
-        collection.fetch()
-
-        expect(collection.pluck 'id').toEqual(_.pluck posts1, 'id')
-        expect(collection.pluck 'id').not.toEqual(_.pluck posts2, 'id')
-
-      it 'subsequent fetch with different options returns different data', ->
-        respondWith(server, '/api/posts?per_page=5&page=2', resultsFrom: 'posts', data: { posts: posts2 })
-        collection.fetch({page: 2})
+        expect(collection.pluck('id')).toEqual(_(posts1).pluck('id'))
+
+      it 'responds to requests with custom params', ->
+        paramsOnDoneSpy = jasmine.createSpy('paramsOnDoneSpy')
+
+        respondWith(server, '/api/posts?my_custom_param=theparam&per_page=5&page=1', resultsFrom: 'posts', data: { posts: posts2 })
+        collection.fetch({params: {my_custom_param: 'theparam'}}).done(paramsOnDoneSpy)
+
         server.respond()

-        expect(collection.pluck 'id').not.toEqual(_.pluck posts1, 'id')
-        expect(collection.pluck 'id').toEqual(_.pluck posts2, 'id')
+        expect(paramsOnDoneSpy).toHaveBeenCalled()
+
+      describe 'subsequent fetches', ->
+        beforeEach ->
+          respondWith(server, '/api/posts?per_page=5&page=1', resultsFrom: 'posts', data: { posts: posts1 })
+
+          collection.fetch()
+          server.respond()
+
+        it 'subsequent fetches return data from storage manager cache', ->
+          collection.fetch()
+
+          expect(collection.pluck 'id').toEqual(_.pluck posts1, 'id')
+          expect(collection.pluck 'id').not.toEqual(_.pluck posts2, 'id')
+
+        it 'subsequent fetch with different options returns different data', ->
+          respondWith(server, '/api/posts?per_page=5&page=2', resultsFrom: 'posts', data: { posts: posts2 })
+          collection.fetch({page: 2})
+          server.respond()
+
+          expect(collection.pluck 'id').not.toEqual(_.pluck posts1, 'id')
+          expect(collection.pluck 'id').toEqual(_.pluck posts2, 'id')

   describe '#refresh', ->
     beforeEach ->


Add unit test. Cannot seem to run locally because of nodegit but will see what happens in CI.

--- spec/context-menu-manager-spec.coffee
+++ spec/context-menu-manager-spec.coffee
@@ -156,3 +156,30 @@
       catch error
         addError = error
       expect(addError.message).toContain('<>')
+
+    it "recursively applies templateForEvent on submenu items", ->
+      created = (event) -> @label = 'D'
+      item = {
+        label: 'A',
+        command: 'B',
+        submenu: [
+          {
+            label: 'C',
+            created,
+          }
+        ]
+      }
+      contextMenu.add('.grandchild': [item])
+
+      dispatchedEvent = {target: grandchild}
+      expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual(
+        [
+          label: 'A',
+          command: 'B',
+          submenu: [
+            {
+              label: 'D',
+              created,
+            }
+          ]
+        ])


Clear deprecations before each spec

Clear deprecations before each spec

--- spec/deprecation-cop-view-spec.coffee
+++ spec/deprecation-cop-view-spec.coffee
@@ -9,7 +9,7 @@
     workspaceElement = atom.views.getView(atom.workspace)
     jasmine.attachToDOM(workspaceElement)

-    expect(Grim.getDeprecationsLength()).toBe 0
+    Grim.clearDeprecations()
     deprecatedMethod = -> Grim.deprecate("A test deprecation. This isn't used")
     deprecatedMethod()


run benchmarks asynchronously

run benchmarks asynchronously

--- benchmark/javascripts/benchmark.coffee
+++ benchmark/javascripts/benchmark.coffee
@@ -6,7 +6,7 @@
        .on('cycle', (event)->
           $('#perf_log').append('<p>' + String(event.target) + '</p>'))
        .on('complete', -> $('#perf_log').append 'all done in here')
-       .run()
+       .run(async: true)

 $ ->
   $('#playout_benchmark').click playoutBenchmark


start breaking things out in smaller methods

--- source/javascripts/master-map.coffee
+++ source/javascripts/master-map.coffee
@@ -1,6 +1,148 @@
-window.callbacks ?= {}
+render = ->
+  width       = $('#master-map').width()
+  height      = 1200
+  projection  = d3.geo.albersUsa().scale(1).translate [ 0, 0 ]
+  path        = d3.geo.path().projection(projection)
+  fill        = d3.scale.log().clamp(true).range ['#111', '#00ff00']
+  starCount   = 'total'
+  centered    = null
+  geometries  = null
+  colors      = ['#a634f4', '#f1f42f', '#bcf020', '#eeb016', '#ec180c']

-callbacks.foo = (d) ->
-  console.log 'foo'
+  # Main objects
+  recruits = null
+  schools  = null
+  counties = null
+  states   = null

+  map = d3.select('#master-map').append('svg')
+    .attr('width', width)
+    .attr('height', height)

+  tip = d3.tip().attr('class', 'd3-tip').offset([-10, 0]).html (d) ->
+    if d.properties?
+      p = d.properties
+      name = if /county/i.test(p.name) then p.name else "#{p.name} County"
+      "
#{name}

#{p[starCount] || 0} athletes.
"
+    else if d.weight?
+      "
#{d.stars} ☆ #{d.name} - #{d.year}

#{d.weight}lb #{d.position} from #{d.school} in #{d.location}"
+    else
+      "
#{d.team}

#{d.stadium} in #{d.city}, #{d.state}
"
+
+  map.call(tip)
+
+  # Generates a LineString GeoJSON object from a player to a school
+  #
+  # player - Object
+  # school - Object
+  #
+  # Returns a GeoJSON LineString object
+  lineStringFromPlayerToSchool = (player, school) ->
+    type: 'LineString'
+    coordinates: [[school.lat, school.lon], [player.lat, player.lon]]
+    properties: player
+
+  # Draw Great Arcs to recruit locations from the school.
+  #
+  # Find all recruits that have committed to the current school
+  # and draw a path from the recruit back to the school.
+  #
+  # school - Object
+  drawRecruitPathsToSchool = (school) ->
+    players = recruits.filter (r) -> r.institution in [school.team, school.alt]
+    features = players.map (player) -> lineStringFromPlayerToSchool(player, school)
+
+    connections = geometries.selectAll('.connection')
+      .data(features, (d) -> "#{d.properties.name}:#{d.properties.school}")
+
+    connections.enter().append('path')
+      .attr('class', 'connection')
+      .attr('d', path)
+      .style('stroke', (d) -> colors[d.properties.stars - 1])
+
+    connections.exit().remove()
+
+    recs = geometries.selectAll('.recruit')
+      .data(players, (d) -> "#{d.name}:#{d.school}")
+
+    recs.enter().append('circle')
+      .attr('cx', (d) -> d.coordinates[0])
+      .attr('cy', (d) -> d.coordinates[1])
+      .attr('r', 2)
+      .style('fill', (d) -> colors[d.stars - 1])
+      .attr('class', 'recruit')
+      .on('mouseover', tip.show)
+      .on('mouseout', tip.hide)
+
+    recs.exit().remove()
+
+  $.when($.ajax('/data/counties.json'),
+         $.ajax('/data/schools.csv'),
+         $.ajax('/data/recruits.csv')).then (r1, r2, r3) ->
+
+    usa      = r1[0]
+    schools = d3.csv.parse(r2[0])
+    recruits = d3.csv.parse(r3[0])
+
+    # Convert to GeoJSON
+    states   = topojson.mesh usa, usa.objects.states, (a, b) -> a.id != b.id
+    counties = topojson.feature usa, usa.objects.counties
+    nation   = topojson.mesh usa, usa.objects.nation
+
+    fill.domain [0.1, d3.max(counties.features, (d) -> d.properties[starCount])]
+
+    # Auto scale map based on bounds
+    projection.scale(1).translate([0, 0])
+    b = path.bounds(nation)
+    s = 1 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height)
+    t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2]
+    projection.scale(s).translate(t)
+
+    geometries = map.append('g')
+
+    # Add counties
+    geometries.append('g')
+      .attr('class', 'counties')
+    .selectAll('path.county')
+      .data(counties.features)
+    .enter().append('path')
+      .attr('class', 'county')
+      .style('fill', (d) -> fill(d.properties[starCount] || 0))
+      .attr('d', path)
+      .on('mouseover', tip.show)
+      .on('mouseout', tip.hide)
+      .style('stroke', (d) ->
+        stars = d.properties[starCount] || 0
+        if stars > 0 then fill(stars || 0) else '#333')
+
+    # Add states mesh
+    geometries.append('path')
+      .datum(states)
+      .attr('class', 'states')
+      .attr('d', path)
+
+    # Add nation mesh
+    geometries.append('path')
+      .datum(nation)
+      .attr('class', 'nation')
+      .attr('d', path)
+
+    for recruit in recruits
+      recruit.coordinates = projection [recruit.lat, recruit.lon]
+
+    for school in schools
+      school.position = projection [school.lat, school.lon]
+
+    geometries.selectAll('.schools')
+      .data(schools)
+    .enter().append('circle')
+      .attr('cx', (d) -> d.position[0])
+      .attr('cy', (d) -> d.position[1])
+      .attr('r', 4)
+      .attr('class', 'school')
+      .classed('gt', (d) -> d.name == 'Georgia Tech')
+      .on('mouseover', tip.show)
+      .on('mouseout', tip.hide)
+      .on('click', drawRecruitPathsToSchool)
+
+$(render)


Explicitly require path module

Explicitly require path module

--- spec/mixed-indent-warning-spec.coffee
+++ spec/mixed-indent-warning-spec.coffee
@@ -1,4 +1,5 @@
 MixedIndentWarning = require '../lib/mixed-indent-warning'
+path = require 'path'

 # Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs.
 #


payment: filter "undefined" from address

we're not really using address2 at the moment.  We should only show address2 if it's defined

--- client/landing/app/Applications/Dashboard.kdapplication/views/billingmethodview.coffee
+++ client/landing/app/Applications/Dashboard.kdapplication/views/billingmethodview.coffee
@@ -22,10 +22,11 @@
     if billingInfo
       { cardFirstName, cardLastName, cardNumber, cardMonth, cardYear
         cardType, address1, address2, city, state, zip } = billingInfo
+      address = [address1, address2].filter(Boolean).join ' '
       """

#{cardFirstName} #{cardLastName}

#{cardNumber} - #{cardMonth}/#{cardYear} (#{cardType})

-
#{address1} #{address2}

+
#{address}

#{city} #{state} #{zip}

       """
     else "Enter billing information"


reload on hash change

reload on hash change

--- httpdd.fsm.html.coffee
+++ httpdd.fsm.html.coffee
@@ -1,6 +1,8 @@
 jsonFsmUrl = window.location.search.substr 1
 jsonFsmUrl = if jsonFsmUrl.length then jsonFsmUrl else 'httpdd.fsm.json'
 $.ajaxSetup cache: false
+window.onhashchange = () ->
+  window.location.reload()

 stmts = []
 vars = {}


added todos for request bugfixing, #78

added todos for request bugfixing, #78

--- both/methods/shift/shiftRequest.coffee
+++ both/methods/shift/shiftRequest.coffee
@@ -42,9 +42,12 @@
 								$pull: 'teams.$.declined': _id: userId
 								$addToSet: 'teams.$.participants': user

+							#TODO: if team.max reached -> close team
+
 							Meteor.call 'sendTeamUpdate', shiftId, teamId, 'participant'
 						else throw new Meteor.Error 500, TAPi18n.__('modal.shift.maximumReached')
 					else if team.pending.length == team.min - 1
+						#TODO: set condition to team.pending.length >= team.min - 1
 						acceptedUsers = [ userId ]
 						hasTeamleader = false
 						chosenId = null
@@ -74,6 +77,7 @@
 								chosenIsTeamleader = false

 						if hasTeamleader
+							#TODO: if team.pending > team.max, make sure to accept only {{team.max}} users
 							Shifts.update _id: shiftId, 'teams._id': teamId,
 								$pull: 'teams.$.declined': _id: userId
 								$addToSet: 'teams.$.participants': user


update spec

update spec

--- spec/package-detail-view-spec.coffee
+++ spec/package-detail-view-spec.coffee
@@ -17,11 +17,13 @@
     # Perhaps there are more things to assert here.
     expect(view.title.text()).toBe('Package With Config')

-  it "Does not call the atom.io api when package metadata is present", ->
-    packageManager.client = jasmine.createSpyObj('client', ['package'])
+  it "Does not call the atom.io api for package metadata when present", ->
+    packageManager.client = jasmine.createSpyObj('client', ['package', 'avatar'])
     view = new PackageDetailView({name: 'package-with-config'}, packageManager)

-    expect(packageManager.client.package).not.toHaveBeenCalled()
+    # PackageCard is a subview, and it calls AtomIoClient::package once to load
+    # metadata from the cache.
+    expect(packageManager.client.package.callCount).toBe(1)

   it "Shows a loading message and calls out to atom.io when package metadata is missing", ->
     packageManager.client = jasmine.createSpyObj('client', ['package'])


Hotfixing Injection API

Hotfixing Injection API

--- src/udefine.coffee
+++ src/udefine.coffee
@@ -32,9 +32,10 @@
           root.udefine.globals[name] = result

         if Object.hasOwnProperty.call root.udefine.inject, name
-          do (injectName = name, origRoot = root) ->
-            {name, root} = root.udefine[injectName]
-            origRoot.udefine.inject(root, name)(result)
+          injectName = root.udefine.inject[name].name
+          injectRoot = root.udefine.inject[name].root
+
+          root.udefine.inject(injectRoot, injectName)(result)
     result

   root.udefine.inject = (obj, name) -> (res) -> obj[name] = res


fix app uri

fix app uri

--- config/stage.coffee
+++ config/stage.coffee
@@ -38,7 +38,7 @@
         auth    : 'https://dev.koding.com/auth'
         vhost   : 'stage'
       apiUri    : 'https://api.koding.com'
-      appsUri   : 'https://apps.koding.com'
+      appsUri   : 'http://dev-app.koding.com'
       env       : 'dev'
   mq            :
     host        : 'localhost'