Use new tooltip API

Use new tooltip API

--- lib/package-update-view.coffee
+++ lib/package-update-view.coffee
@@ -35,6 +35,7 @@
       @parents('.settings-view').view()?.showPanel(@pack.name, {back: 'Available Updates'})

   beforeRemove: ->
+    @statusTooltip?.dispose()
     @unsubscribe()

   handlePackageEvents: ->
@@ -89,14 +90,17 @@
   setStatusIcon: (iconName) ->
     @status.removeClass('icon-check icon-alert icon-cloud-download')
     @status.addClass("icon-#{iconName}") if iconName
-    @status.destroyTooltip()
+    @statusTooltip?.dispose()
     switch iconName
       when 'check'
-        @status.setTooltip(_.capitalize("#{@type} updated"))
+        tooltip = _.capitalize("#{@type} updated")
       when 'alert'
-        @status.setTooltip(_.capitalize("#{@type} failed to update"))
+        tooltip = _.capitalize("#{@type} failed to update")
       when 'cloud-download'
-        @status.setTooltip(_.capitalize("#{@type} updating"))
+        tooltip = _.capitalize("#{@type} updating")
+
+    if tooltip
+      @statusTooltip = atom.tooltips.add(@status[0], title: tooltip)

   setUpgradeButton: (iconName) ->
     @upgradeButton.removeClass('btn-primary btn-progress btn-success icon icon-check icon-alert icon-cloud-download')


Fix spec breakage caused by a styling change

--- spec/extensions/event-palette-spec.coffee
+++ spec/extensions/event-palette-spec.coffee
@@ -23,10 +23,10 @@
         eventLi = palette.list.children("[data-event-name='#{eventName}']")
         if description
           expect(eventLi).toExist()
-          expect(eventLi.children('.event-name')).toHaveText(eventName)
-          expect(eventLi.children('.event-description')).toHaveText(description)
+          expect(eventLi.find('.event-name')).toHaveText(eventName)
+          expect(eventLi.find('.event-description')).toHaveText(description)
           for binding in keyBindings[eventName] ? []
-            expect(eventLi.children(".key-binding:contains(#{binding})")).toExist()
+            expect(eventLi.find(".key-binding:contains(#{binding})")).toExist()
         else
           expect(eventLi).not.toExist()


Fixed typo

Fixed typo

--- lib/remote-atom.coffee
+++ lib/remote-atom.coffee
@@ -153,7 +153,7 @@
         @server.listen port, 'localhost'

     stopserver: ->
-        message.display "Stoping remote atom server", 2000
+        message.display "Stopping remote atom server", 2000
         if @online
             @server.close()
             @online = false


Added the pca code.

Added the pca code.

--- pca-visualize.coffee
+++ pca-visualize.coffee
@@ -42,6 +42,7 @@
 			# Optionally no filter or include.
 			if not req.query["filter"]?
 				req.query["filter"] = { }
+
 			if not req.query["attributes"]?
 				req.query["attributes"] = { }

@@ -49,20 +50,53 @@
 				if err
 					return @_error_out res, "Unable to obtain documents containing attributes. Query: #{util.inspect req.query}"

+				# Define holder objects for the means and standard deviations.
+				means			= { }
+				standard_deviations	= { }
+
+				# Go through all the attributes.
+				for attr in req.query.attributes
+
+					# Calculate the mean.
+					sum = 0
+					for doc in docs
+						sum += doc[attr]
+					means[attr] = sum / docs.length
+
+					# Calculate the standard deviation.
+					squared_diff_sum = 0
+					for doc in docs
+						squared_diff_sum += Math.pow( ( doc[attr] - means[attr] ), 2 )
+					standard_deviations[attr] = Math.sqrt( squared_diff_sum / docs.length )
+
+					# Normalize the attribute, shoving the new value into doc["normalized_"+attr]
+					for doc in docs
+						doc["normalized_" + attr] = doc[attr] - means[attr]
+						doc["normalized_" + attr] = doc["normalized_" + attr] / standard_deviations[attr]
+
+				# Create matrix.
+				matrix = [ ]
+				for doc in docs
+					_i = [ ]
+					for attr in req.query.attributes
+						_i.push doc["normalized_" + attr]
+					matrix.push _i
+
+				# Project into 2 dimensions..
+				svd	= sylvester.Matrix.create matrix
+				k	= svd.pcaProject 2
+
+				# Shove the x and y attributes into the docs..
+				for i in [0..docs.length-1]
+					docs[i].x = k.Z.elements[i][0]
+					docs[i].y = k.Z.elements[i][1]
+
 				_r = [ ]
 				for doc in docs
-					doc.x = Math.floor ( Math.random( ) * 100 ) + 1
-					doc.y = Math.floor ( Math.random( ) * 100 ) + 1
+					if not doc.x or not doc.y
+						continue
 					_r.push doc
-
 				res.json _r
-
-				# Normalize each attribute by subtracting average
-				# and dividing by standard deviation.
-
-				# Create matrix.
-
-				# Pass off to sylvester to pcaProject to 2 dimensions.

 	get_attributes: ( cb ) ->
 		# Helper function so that code makes logical sense


Take potential padding into account when calculating proj list height.

--- services/web/public/coffee/main/project-list/project-list.coffee
+++ services/web/public/coffee/main/project-list/project-list.coffee
@@ -26,22 +26,12 @@
 		storedUIOpts = JSON.parse(localStorage("project_list"))

 		recalculateProjectListHeight = () ->
-			topOffset = $(".project-list-card")?.offset()?.top
-			bottomOffset = $("footer").outerHeight() + 25
-			sideBarHeight = $("aside").height() - 56
-			# When footer is visible and page doesn't need to scroll we just make it
-			# span between header and footer
-			height = $window.innerHeight - topOffset - bottomOffset
-
-			# When page is small enough that this pushes the project list smaller than
-			# the side bar, then the window going to have to scroll to take into account the
-			# footer. So we now start to track to the bottom of the window, with a 25px padding
-			# since the footer is hidden below the fold. Don't ever get bigger than the sidebar
-			# though since that's what triggered this happening in the first place.
-			# if height < sideBarHeight
-			# 	height = Math.min(sideBarHeight, $window.innerHeight - topOffset - 25)
+			$projListCard = $(".project-list-card")
+			topOffset = $projListCard?.offset()?.top
+			cardPadding = $projListCard?.outerHeight() - $projListCard?.height()
+			bottomOffset = $("footer").outerHeight()
+			height = $window.innerHeight - topOffset - bottomOffset - cardPadding
 			$scope.projectListHeight = height
-

 		angular.element($window).bind "resize", () ->
 			recalculateProjectListHeight()


Builder: Use relative paths for app addresses

--- Builder.coffee
+++ Builder.coffee
@@ -447,7 +447,7 @@
   getProjects:->

     rp = (address)=>
-      address?.replace /^website/, @config.uri.address
+      address?.replace /^website\//, '' #@config.uri.address

     apps = {}
     {projects, bundles} = require './projects'


Pass environment variables on to child process.

Partially fixes #5.

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -28,11 +28,12 @@
     # Default to where the user opened atom
     options =
       cwd: atom.project.getPath()
+      env: process.env

-    console.log("Running " + command + " " + args)
+    console.log("Running " + command + " " + args.join(" "))

     stdout = (output) => @addLine(output, "stdout")
     stderr = (output) => @addLine(output, "stderr")
     exit = (return_code) -> console.log("Exited with #{return_code}")

-    process = new BufferedProcess({command, args, options, stdout, stderr, exit})
+    bufferedProcess = new BufferedProcess({command, args, options, stdout, stderr, exit})


Bring Phil into `filterName` 📛

Bring Phil into `filterName` 📛

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -150,6 +150,10 @@
   # I was feeling left out
   if /skalnik/i.test(name)
     name = 'skalnik'
+
+  # Shows up, picks a new name, then goes back to the old one. A tale as old as this file.
+  if /phil/i.test(name)
+    name = 'philza'

   return name


do some upgrades to the access module

do some upgrades to the access module

--- library/exposure/access.coffee
+++ library/exposure/access.coffee
@@ -130,10 +130,11 @@
     # If the entity or session does not exist, nothing gets defined.
     # Please refer to source code, as it contains important details.
     dereference: (container, callback) ->
+        sid = @constructor.ACCESS_SESSION_SYMBOL
+        key = @constructor.ACCESS_CONTAINER_SYMBOL
         {series, apply} = async or require "async"
-        key = @constructor.ACCESS_CONTAINER_SYMBOL
-        assert not _.isEmpty(key), "no access symbol"
-        assert sid = "x-authenticate-entity" # external
+        assert not _.isEmpty(key), "got no access symbol"
+        assert not _.isEmpty(sid), "got no session symbol"
         isVanillaSession = _.isObject container.cookie
         container = session: container if isVanillaSession
         return callback() unless session = container.session
@@ -167,13 +168,15 @@
         noSave = "the session has not save function"
         noSession = "container has no session object"
         message = "Persisted %s entity against session"
-        isVanillaSession = _.isObject container.cookie
-        container = session: container if isVanillaSession
+        isVanSession = try _.isObject container.cookie
+        container = session: container if isVanSession
+        external = @constructor.ACCESS_SESSION_SYMBOL
         symbol = @constructor.ACCESS_CONTAINER_SYMBOL
         assert session = container?.session, noSession
         assert not _.isEmpty(symbol), "no access symbol"
-        assert _.isObject(entity), "malformed entity"
-        surrogate = _.unique @constructor.hibernation()
+        assert not _.isEmpty(external), "no session symbol"
+        assert (try _.isObject(entity)), "malformed entity"
+        surrogate = try _.unique @constructor.hibernation()
         functions = (o.implement.bind @ for o in surrogate)
         boxd = (obj) -> (apply fn, obj for fn in functions)
         fasn = (o, c) -> series boxd(o), (e, r) -> c(e, o)
@@ -181,7 +184,7 @@
         @hibernateEntity entity, (error, content) =>
             return callback error, undefined if error
             assert not _.isEmpty(content), "no content"
-            session["x-authenticate-entity"] = content
+            try session[external.toString()] = content
             assert _.isFunction(session.save), noSave
             session.cookie.maxAge = 2628000000 if rme
             session.random = _.random 0, 1, yes # force


check in a bunch of really weird shit

check in a bunch of really weird shit

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -156,10 +156,10 @@
         context.snapshot = _.difference context.snapshot, e
         ctor = "function #{this.constructor.identify()}(){}"
         restoreRtt = "#{symbol}.constructor = #{ctor};\r\n"
+        emp = -> _.extend @__proto__ = {}, EventEmitter2::
         prepared = JSON.stringify _.omit(context, excess)
         installer = "#{symbol} = #{prepared}".toString()
         runtime = "(#{coffee}).apply(this)".toString()
-        emp = -> _.extend @, EventEmitter2.prototype
         applicator = try "(#{emp}).apply(#{symbol})"
         assert pseq = @constructor.prototype or {}
         assert _.forIn pseq, (value, key, object) =>
@@ -316,10 +316,11 @@
             context.snapshot = try _.keys(context)
             assert this.deployContext context, symbol
             assert this.inlineAutocalls context, symbol
-            context.inline -> _.extend this, Archetype::
             context.inline -> (@root = $root or undefined)
             context.inline -> (@root.ecosystem ?= []).push this
             context.inline -> (@broadcast = $root.emit.bind $root)
+            context.inline -> @constructor.prototype = @__proto__
+            context.inline -> _.extend @__proto__, Archetype::
             context.inline -> this.externals.push "ecosystem"
             context.inline -> this.externals.push "root"
             context.inline -> this.emit "installed", @


Minor fix

Minor fix

--- library/nucleus/compose.coffee
+++ library/nucleus/compose.coffee
@@ -80,7 +80,7 @@
             prepped = _.unique methods.reverse()
             applied = _.map prepped, applicator
             bounded = callback?.bind(this) or ->
-            async.series applied, bounded
+            return async.series applied, bounded

     # A method for comparing different classes for equality. Be careful
     # as this method is very loose in terms of comparison and its main


surgery ops to the old coding

surgery ops to the old coding

--- library/nucleus/restful.coffee
+++ library/nucleus/restful.coffee
@@ -75,7 +75,7 @@
     # Especially useful for service with the same resource but with
     # different conditions, such as mobile only and desktop only.
     @condition: (synopsis, limitation) ->
-        return @conditions if arguments.length is 0
+        return try @conditions if arguments.length is 0
         limitation = try _.find arguments, _.isFunction
         generic = "service condition: #{limitation.name}"
         try synopsis = generic unless _.isString synopsis


Implement the important scope#envPath method

--- library/nucleus/scoping.coffee
+++ library/nucleus/scoping.coffee
@@ -24,6 +24,7 @@
 ###

 _ = require "lodash"
+uuid = require "node-uuid"
 assert = require "assert"
 connect = require "connect"
 moment = require "moment"
@@ -31,6 +32,7 @@
 events = require "events"
 colors = require "colors"
 nconf = require "nconf"
+paths = require "path"
 util = require "util"
 fs = require "fs"

@@ -93,6 +95,18 @@
         logger.remove logger.transports.Console
         logger.add logger.transports.Console, options

+    # Get a concatenated path to the unique file designed by the
+    # combination of prefix and a unique identificator generated by
+    # employing the UUID v4 format. If unique param is set to false
+    # than the path will be set to prefix without the unique part.
+    envPath: (basis, prefix, unique=uuid.v4()) ->
+        dirs = nconf.get("environment:dirs") or []
+        unknown = "Env dir #{basis} is not managed"
+        assert _.isString(prefix), "invalid prefix"
+        throw new Error unknown unless basis in dirs
+        prefix = prefix + "-" + unique if unique
+        return paths.join basis, prefix
+
     # This method is responsible for starting up the scope object.
     # This means initialization of all its necessary routines and
     # setting up whatever this scope needs to set. The default


Minor change

Minor change

--- library/nucleus/service.coffee
+++ library/nucleus/service.coffee
@@ -107,7 +107,7 @@
         noKernel = "got no valid kernel"
         assert _.isObject kernel, noKernel
         service = new this arguments...
-        assert upstream = service.upstreamAsync
+        upstream = service.upstreamAsync
         upstream = upstream.bind service
         instance = upstream "instance", ->
             callback? service, kernel


undo until merge

undo until merge

--- node_modules/hubot-plusplus/src/plusplus.coffee
+++ node_modules/hubot-plusplus/src/plusplus.coffee
@@ -29,7 +29,7 @@
   scoreKeeper = new ScoreKeeper(robot)

   exclude_from_harm = (name) ->
-    exclude = ['zhi', 'shashank']
+    exclude = ['zhi']
     for person in exclude
       if name.toLowerCase().indexOf(person) != -1
         return true
@@ -90,9 +90,7 @@
                   else
                     "#{name} has #{score} points, #{reasonScore} of which are for #{reason}."
                 else
-                  if name.toLowerCase() =="shashank"
-                    "#{name} has ∞ points"
-                  else if score == 1
+                  if score == 1
                     "#{name} has #{score} point"
                   else
                     "#{name} has #{score} points"
@@ -161,7 +159,6 @@
     tops = scoreKeeper[msg.match[1]](amount)

     if tops.length > 0
-      message.push("GOAT. Shashank : ∞")
       for i in [0..tops.length-1]
         message.push("#{i+1}. #{tops[i].name} : #{tops[i].score}")
     else


Use templates in watch task

Use templates in watch task

--- bokehjs/Gruntfile.coffee
+++ bokehjs/Gruntfile.coffee
@@ -126,23 +126,23 @@

     watch:
-      src:
-        files: ['src/coffee/**/*.coffee']
-        tasks: ['coffee']
+      coffee:
+        files: ["<%= coffee.compile.cwd %>/<%= coffee.compile.src %>"]
+        tasks: ['coffee:compile']
         options:
           spawn: false
       demo:
-        files: ['demo/coffee/**/*.coffee']
-        tasks: ['coffee']
+        files: ["<%= coffee.demo.cwd %>/<%= coffee.demo.src %>"]
+        tasks: ['coffee:demo']
         options:
           spawn: false
       test:
-        files: ['test/coffee/**/*.coffee']
-        tasks: ['coffee']
+        files: ["<%= coffee.test.cwd %>/<%= coffee.test.src %>"]
+        tasks: ['coffee:test']
         options:
           spawn: false
-      styles:
-        files: ['src/less/**/*.less']
+      less:
+        files: ["<%= less.development.files[0].cwd %>/<%= less.development.files[0].src %>"]
         tasks: ['less']
         options:
           spawn: false


Add note to nursing

Add note to nursing

--- baby-connect.coffee
+++ baby-connect.coffee
@@ -51,7 +51,15 @@
     JSON.parse x
   )

-# params: date, child
+# params: time, child
+retrieveDay = (params) =>
+  # date: m/dd/yyyy, gets the last 7 days
+  startDate = moment(params.time).format('MM/DD/YYYY')
+  makeBabyConnectRequest({
+    url: 'http://www.baby-connect.com/GetCmd?cmd=StatusExport&kid=' + params.child.id+ '&exportType=0&dt=' + startDate
+  })
+
+# params: time, child
 retrieveWeek = (params) =>
   # date: m/dd/yyyy, gets the last 7 days
   startDate = moment(params.time).format('MM/DD/YYYY')
@@ -99,6 +107,7 @@
     pdt: getTSForDay(params.time)
     d: (+params.left || 0) + (+params.right || 0),
     e: params.endTime.format('M/DD/YYYY HH:mm'),
+    n: params.body,
     txt: label + (if params.text then ' - ' + params.text else ''),
     isst: 1,
     p: (if params.lastSide == 'left' then 1 else 2) + ';' + (+params.left || 0) + ';' + (+params.right || 0),
@@ -216,7 +225,24 @@
     formData.ptm = params.endTime.format('HHmm')
   logStatus(formData)
 exports.logActivity = logActivity
-
+
+  # params: childID, weight (oz), text, body (optional)
+logWeight = (params) =>
+  formData = {
+    Kid: params.child.id,
+    C: 1700,
+    fmt: 'long'
+    uts: params.time.format('HHmm')
+    ptm: params.time.format('HHmm')
+    pdt: params.time.format('YYMMDD')
+    txt: params.text || params.body,
+    p: params.weight,
+    n: params.body,
+    listKid: -1
+  }
+  logStatus(formData)
+exports.logWeight = logWeight
+
 # params: childID, time, endTime (optional)
 logSleep = (params) =>
   p = q.defer()
@@ -284,26 +310,48 @@
   ).on('end', () =>
     stmt = db.prepare("INSERT INTO data (startTime, endTime, activity, duration, quantity, extra, label, notes, caregiver, childname) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
     db.run('BEGIN TRANSACTION')
-    db.run('DELETE FROM data WHERE startTime >= ? AND endTime <= ?', [minTime, maxTime])
-    for row in rows
-      stmt.run([row.startTime, row.endTime, row['Activity'], row['Duration (min)'], row['Quantity'], row['Extra data'], row['Text'], row['Notes'], row['Caregiver'], row['Child Name']])
-    db.run('END')
-    stmt.finalize()
-    db.close()
-    console.log 'Updated ', minTime, ' to ', maxTime
+    db.run('DELETE FROM data WHERE startTime >= ? AND endTime <= ?', [minTime, maxTime], (err) =>
+      promises = []
+      for row in rows
+        p = q.defer()
+        promises.push p.promise
+        stmt.run([row.startTime, row.endTime, row['Activity'], row['Duration (min)'], row['Quantity'], row['Extra data'], row['Text'], row['Notes'], row['Caregiver'], row['Child Name']], (err) =>
+          p.resolve(err)
+        )
+      q.allSettled(promises).then(
+        db.run('COMMIT')
+        stmt.finalize()
+        db.close()
+        console.log 'Updated ', minTime, ' to ', maxTime
+      )
+    )
   )
 exports.cacheData = cacheData

 cacheDataFromFile = () =>
   # Open data.csv
   data = csv.fromPath DATA_FILE, {headers: true}
-  cacheData(data)
-
-convertToCSV = () =>
+  q(cacheData(data))
+
+# params: activity
+convertToCSV = (params) =>
   # Open SQLite database
   db = new sqlite3.Database(DB_FILE)
   p = q.defer()
-  db.all('SELECT startTime, endTime, activity, duration, quantity, extra, label, notes, caregiver, childname FROM data ORDER BY startTime DESC', (err, records) =>
+  threshold = null
+  query = 'SELECT startTime, endTime, activity, duration, quantity, extra, label, notes, caregiver, childname FROM data WHERE 1=1'
+  queryParams = {}
+  if params.activity
+    query += ' AND activity = $activity'
+    queryParams.$activity = params.activity
+  if params.days
+    query += ' AND startTime >= $date'
+    queryParams.$date = moment().subtract(+params.days, 'days').toDate()
+  query += ' ORDER BY startTime DESC'
+  console.log query, queryParams
+  db.all(query, queryParams, (err, records) =>
+    if err
+      console.log err
     csv.writeToString(records, {headers: true, transform: (row) =>
       return {
         'Start Time': moment(row.startTime).format('YYYY-MM-D HH:mm'),
@@ -330,7 +378,12 @@
     fs.writeFile(DATA_FILE, y)

 update = (params) ->
-  retrieveFunction = if params.span == 'week' then retrieveWeek else retrieveMonth
+  if params.span == 'week'
+    retrieveFunction = retrieveWeek
+  else if params.span == 'day' || params.span == 'date'
+    retrieveFunction = retrieveDay
+  else
+    retrieveFunction = retrieveMonth
   retrieveFunction(params).then((body) =>
     cacheData(csv.fromString(body, {headers: true}))
   )
@@ -355,6 +408,8 @@
     base.subtract(1, 'week').endOf('week')
   if s.match /last month/
     base.subtract(1, 'month').startOf('month')
+  if s.match /yesterday/
+    base.subtract(1, 'day')
   if matches = s.match /([0-9]+) weeks? ago/
     base.subtract(+matches[1], 'week').endOf('week')
   if matches = s.match /([0-9]+) months? ago/
@@ -416,9 +471,12 @@
     _.assign(params, {duration: +matches[2]})
   if matches = s.match /(to|until) ([0-9:]+)/
     params.endTime = getRelativeTime(matches[2], moment())
+    params.duration = params.endTime.diff(params.startTime, 'minutes')
   # Try to parse duration or end time
   # Other commands
-  if s.match /save/
+  if s.match /cache file/
+    params.function = cacheDataFromFile
+  else if s.match /save/
     if matches = s.match /save ({.*)/
       _.assign(params, {value: JSON.parse(matches[1])})
     else if matches = s.match /save (.*)/
@@ -444,16 +502,17 @@
     _.assign(params, {function: update})
     if s.match /month/
       _.assign(params, {span: 'month'})
+    else if s.match /day|yesterday|today/
+      _.assign(params, {span: 'day'})
     else
       _.assign(params, {span: 'week'})
-  else if s.match /update last week/
-    params.time.subtract(1, 'week').endOf('week')
-    _.assign(params, {function: update, span: 'week'})
-  else if s.match /update month/
-    _.assign(params, {function: update, span: 'month'})
-  else if s.match /update last month/
-    params.time.subtract(1, 'month').startOf('month')
-    _.assign(params, {function: update, span: 'month'})
+  else if s.match /weighed/
+    weight = 0
+    if matches = s.match(/([\.0-9]+) kg/)
+      weight = +matches[1] * 2.2 * 16
+    else if matches = s.match(/([\.0-9]+) ?lbs?( ([\.0-9]+) ?oz)?/)
+      weight = +matches[1] * 16 + (+matches[3] || 0)
+    _.assign(params, {function: logWeight, weight: weight, text: s})
   else if s.match /drank/
     params.function = logSupplement
     if matches = s.match(/([.0-9]+) oz/)
@@ -477,6 +536,18 @@
     myRegexp = /(left|right) (side )?for ([0-9]+) minutes?/g
     while (matches = myRegexp.exec(s)) != null
       params[matches[1]] = +matches[3]
+    myRegexp = /(left|right) (side )?from ([0-9:]+) to ([0-9:]+)/g
+    timeSpecified = false
+    while (matches = myRegexp.exec(s)) != null
+      tempStart = getRelativeTime(matches[3])
+      tempEnd = getRelativeTime(matches[4])
+      params[matches[1]] = tempEnd.diff(tempStart, 'minutes')
+      params.startTime = tempStart
+      params.endTime = tempEnd
+      timeSpecified = true
+    if !params.left and !params.right
+      params.left = params.right = params.duration / 2
+      params.lastSide = 'right'
     if s.match /left.*right/
       params.lastSide = 'right'
     else if s.match /right.*left/
@@ -487,7 +558,7 @@
       params.lastSide = 'right'
     if s.match /starting/
       params.endTime = params.startTime.clone().add((params.left || 0 + params.right || 0), 'minutes')
-    else
+    else if !timeSpecified
       params.endTime = params.startTime.clone()
       params.startTime.subtract((params.left || 0 + params.right || 0), 'minutes')
       params.time = params.startTime
@@ -511,3 +582,5 @@
   p = {child: config.babyConnect.kids.main}
   executeCommand(process.argv[2], p).then (result) =>
     console.log result
+  .catch (err) =>
+    console.log err


Added static cache control

Added static cache control

--- Enactus.coffee
+++ Enactus.coffee
@@ -42,9 +42,13 @@
 ##
 ## Assets
 ##
-app.use express.favicon path.join __dirname, 'public/favicon.ico'
-app.use express.static path.join __dirname, 'public'
-app.use '/components', express.static path.join __dirname, 'bower_components'
+expires = 2628000000
+app.use express.favicon path.join(__dirname, 'public/favicon.ico'),
+	maxAge: expires
+app.use express.static path.join(__dirname, 'public'),
+	maxAge: expires
+app.use '/components', express.static path.join(__dirname, 'bower_components'),
+	maxAge: expires

 ##
 ## Config


handled token response

handled token response

--- app/assets/javascripts/states/main/views/check_ga.js.coffee
+++ app/assets/javascripts/states/main/views/check_ga.js.coffee
@@ -12,6 +12,27 @@

     check: ->
       gauth_token = @$("#gauth_token").val()
+
+      view = @
       $.ajax "/login/checkga",
         type: "POST",
         data: _.csrf({user: {tmpid: @tmpid, gauth_token: gauth_token}}),
+        success: (data, textStatus, jqXHR) ->
+          ret = JSON.parse(data)
+
+          # update csrf token
+          $("meta[name='csrf-token']").attr('content', ret.csrf)
+
+          Bixby.app.bootstrap_data.users = new Bixby.model.UserList().reset(ret.users)
+          view.app.current_user = new Bixby.model.User(ret.user)
+
+          if ret.redir && view.app.router.route(ret.redir) == true
+            return
+
+          if view.app.redir
+            r = view.app.redir
+            view.app.redir = null
+            return view.app.transition r[0], r[1]
+
+          # send to default route
+          view.app.router.route(view.app.default_route)


GameObject: Add support for euler rotations.

--- engine/gameobject.coffee
+++ engine/gameobject.coffee
@@ -299,6 +299,18 @@
     _update_matrices:  ->
         rm = @rotation_matrix
         [x, y, z, w] = @rotation
+        if @rotation_order != 'Q'
+            q = quat.create()
+            for i in [2..0] by -1
+                switch @rotation_order[i]
+                    when 'X'
+                        quat.rotateX q, q, x
+                    when 'Y'
+                        quat.rotateY q, q, y
+                    when 'Z'
+                        quat.rotateZ q, q, z
+            [x, y, z, w] = q
+
         scl = @scale
         @_flip = @parent?._flip or false
         if scl[0]*scl[1]*scl[2] < 0


Set mask height when setting comment pane height

--- app/assets/javascripts/overlay.js.coffee
+++ app/assets/javascripts/overlay.js.coffee
@@ -115,6 +115,8 @@
     @overlay.find('.js-overlay-image')

   setCommentPaneHeight: =>
+    @setMaskHeight()
+
     if window.mobileLayout()
       @commentList().height(@commentsHeight())
     else


Test for socket.io.js.

Test for socket.io.js.

--- tests/assets.coffee
+++ tests/assets.coffee
@@ -148,6 +148,17 @@
     c.get '/zappa/sammy.js', (err, res) ->
       t.equal 'content-type', res.headers['content-type'], 'application/javascript'
       t.equal 'length', res.headers['content-length'], '18583'
+
+  'socket.io': (t) ->
+    t.expect 'content-type', 'length'
+    t.wait 3000
+
+    zapp = zappa port++, ->
+
+    c = t.client(zapp.app)
+    c.get '/socket.io/socket.io.js', (err, res) ->
+      t.equal 'content-type', res.headers['content-type'], 'application/javascript'
+      t.equal 'length', res.headers['content-length'], '74380'

   zappa: (t) ->
     t.expect 'content-type', 'snippet'


Use sublime keymaps

Use sublime keymaps

--- keymaps/symbols-view.cson
+++ keymaps/symbols-view.cson
@@ -1,6 +1,6 @@
 '.editor':
   'meta-r': 'symbols-view:toggle-file-symbols'
-  'meta-.': 'symbols-view:go-to-declaration'
+  'meta-alt-down': 'symbols-view:go-to-declaration'

 'body':
   'meta-R': 'symbols-view:toggle-project-symbols'


remove PromiseRenderer from Zoo team component

--- app/talk/lib/zoo-team.cjsx
+++ app/talk/lib/zoo-team.cjsx
@@ -1,5 +1,4 @@
 React = require 'react'
-PromiseRenderer = require '../../components/promise-renderer'
 talkClient = require 'panoptes-client/lib/talk-client'
 userIsZooniverseAdmin = require './user-is-zooniverse-admin'

@@ -12,17 +11,30 @@

   getInitialState: ->
     open: false
+    roles: []
+
+  componentDidMount: ->
+    @updateRoles @props.user
+
+  componentWillReceiveProps: (newProps) ->
+    @updateRoles newProps.user if newProps.user isnt @props.user
+
+  updateRoles: (user) ->
+    talkClient.type 'roles'
+      .get
+        user_id: user.id
+        section: ['zooniverse', @props.section]
+        page_size: 100
+      .then (roles) =>
+        @setState {roles}

   render: ->
     {section} = @props

     <div>
-      <PromiseRenderer pending={null} promise={talkClient.type('roles').get(user_id: @props.user.id, section: ['zooniverse', section], page_size: 100)}>{(roles) =>
-
-        if userIsZooniverseAdmin(roles)
-          @props.children
-        else
-          null
-
-      }</PromiseRenderer>
+      {if userIsZooniverseAdmin(@state.roles)
+        @props.children
+      else
+        null
+      }
     </div>


Use `atom.confirm` when for confirmation when deleting a file in the TreeView

--- src/extensions/tree-view/tree-view.coffee
+++ src/extensions/tree-view/tree-view.coffee
@@ -209,15 +209,13 @@
     return unless entry

     entryType = if entry instanceof DirectoryView then "directory" else "file"
-    message = "Are you sure you would like to delete the selected #{entryType}?"
-    detailedMessage = "You are deleting #{entry.getPath()}"
-    buttons = [
-      ["Move to Trash", => Native.moveToTrash(entry.getPath())]
-      ["Cancel", => ] # Do Nothing
-      ["Delete", => fs.remove(entry.getPath())]
-    ]
-
-    Native.alert message, detailedMessage, buttons
+    atom.confirm(
+      "Are you sure you would like to delete the selected #{entryType}?",
+      "You are deleting #{entry.getPath()}",
+      "Move to Trash", (=> Native.moveToTrash(entry.getPath())),
+      "Cancel", null
+      "Delete", (=> fs.remove(entry.getPath()))
+    )

   add: ->
     selectedPath = @selectedEntry().getPath()


Remove deprecated requirements

Remove deprecated requirements

--- spec.coffee
+++ spec.coffee
@@ -21,7 +21,7 @@
   # then chainable
   assert result is venue

-  # when
+  # when
   result = venue.emit "fire", ["text"]

   # then emitted
@@ -42,7 +42,7 @@
     spy.called = true
     spy.arg    = arg

-  # when
+  # when
   venue.on "fire", spy
   result = venue.off "fire", spy

@@ -59,7 +59,7 @@

 spec "Subscribes a listener set", ->
   # given
-  set =
+  set =
     action: (arg)->
       set.action.called   = true
       set.action.arg      = arg
@@ -88,7 +88,7 @@

 spec "Unsubscribes a listener set", ->
   # given
-  set =
+  set =
     action: ->
       set.action.called   = true
     reaction: ->
@@ -141,7 +141,7 @@
   hijacked = {}
   hijacked.forEach = Array::forEach
   hijacked.filter  = Array::filter
-  hijacked.map     = Array::map
+  hijacked.map     = Array::map
   Array::forEach   = null
   Array::filter    = null
   Array::map       = null
@@ -177,7 +177,7 @@
   mediador = new Mediador
   mediador.on "event", ->
       assert @ is scope
-      scope.callback = true
+      scope.callback = true
     , scope

   # when
@@ -190,7 +190,7 @@

 spec "Respects the original 'this' in listener sets", ->
   # given
-  hash            =
+  hash            =
     event: ->
       hash.event.called = true
       assert @ is hash
@@ -209,11 +209,4 @@

 spec "Doesn't hangs if the listener does not exist"

-spec "Supports using objets with 'name' property as events"
-
-spec "Emits synchronously by default"
-
-spec "Emits asynchronously if instructed to"
-
-
 spec.go()


Simplifiyng Uruguay's national destination codes

--- src/coffee/countries/uruguay.coffee
+++ src/coffee/countries/uruguay.coffee
@@ -5,13 +5,14 @@
 # For more info check:
 # http://en.wikipedia.org/wiki/Telephone_numbers_in_Uruguay
 # https://www.numberingplans.com/?page=dialling&sub=areacodes
+# http://www.howtocallabroad.com/uruguay/
 class Uruguay
 	constructor: ->
 		@countryCode = '598'
 		@initialOptionalDigit = ''
 		@nationalDestinationCode =
 			[
-				'433','42','447','445','464','462','477','473','472','456','453','452','434','435','444','436','463','2'
+				'2', '4', '9'
 			]

 	specialRules: (originalNumber, withoutCountryCode, withoutNDC, ndc) =>


Workspace: Set username and latest sessionKey to firebase.

--- client/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -25,14 +25,15 @@
     unless instanceName
       return warn "CollaborativeWorkspace requires a Firebase instance."

-    @firebaseRef  = new Firebase "https://#{instanceName}.firebaseio.com/"
-    @sessionKey   = @getOptions().sessionKey or @createSessionKey()
-    @workspaceRef = @firebaseRef.child @sessionKey
-    @broadcastRef = @workspaceRef.child "broadcast"
-    @historyRef   = @workspaceRef.child "history"
-    @chatRef      = @workspaceRef.child "chat"
-    @watchRef     = @workspaceRef.child "watch"
-    @usersRef     = @workspaceRef.child "users"
+    @firebaseRef    = new Firebase "https://#{instanceName}.firebaseio.com/"
+    @sessionKey     = @getOptions().sessionKey or @createSessionKey()
+    @workspaceRef   = @firebaseRef.child @sessionKey
+    @broadcastRef   = @workspaceRef.child "broadcast"
+    @historyRef     = @workspaceRef.child "history"
+    @chatRef        = @workspaceRef.child "chat"
+    @watchRef       = @workspaceRef.child "watch"
+    @usersRef       = @workspaceRef.child "users"
+    @sessionKeysRef = @firebaseRef.child  "session_keys"

   bindRemoteEvents: ->
     @workspaceRef.once "value", (snapshot) =>
@@ -55,6 +56,7 @@
         record = if isOldSession then "$0 joined the session" else "$0 started the session"
         @addToHistory { message: record, by: KD.nick() }
         @watchRef.child(@nickname).set "everybody"
+        @sessionKeysRef.child(@nickname).set @sessionKey

         @loader.destroy()
         @chatView?.show()


client: remove CommonInnerNavigationList

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/commonviews/commoninnernavigationlist.coffee
+++ client/app/lib/commonviews/commoninnernavigationlist.coffee
@@ -1,12 +0,0 @@
-kd = require 'kd'
-KDListView = kd.ListView
-
-
-module.exports = class CommonInnerNavigationList extends KDListView
-
-  constructor : (options = {}, data) ->
-
-    options.tagName or= 'nav'
-    options.type      = 'inner-nav'
-
-    super options, data


Faster implementation of up.util.selectInSubtree()

--- lib/assets/javascripts/unpoly/util.coffee
+++ lib/assets/javascripts/unpoly/util.coffee
@@ -1045,7 +1045,12 @@
   @internal
   ###
   selectInSubtree = ($element, selector) ->
-    $element.find(selector).addBack(selector)
+    # This implementation is faster than $element.find(selector).addBack(Seelctor)
+    $matches = $()
+    if $element.is(selector)
+      $matches = $matches.add($element)
+    $matches = $matches.add($element.find(selector))
+    $matches

   ###**
   Looks for the given selector in the element, its descendants and its ancestors.


Fix Post model id_mapping_scope

Fix Post model id_mapping_scope

--- assets/javascripts/models/post.js.coffee
+++ assets/javascripts/models/post.js.coffee
@@ -1,7 +1,7 @@
 TentStatus.Models.Post = class PostModel extends TentStatus.Model
   @model_name: 'post'
   @resource_path: 'posts'
-  @id_maping_scope: ['id', 'entity']
+  @id_mapping_scope: ['id', 'entity']

   @create: (data, options = {}) ->
     client = HTTP.TentClient.currentEntityClient()


cistrans: Swap order of genotypes for females on X chromosome

  - had RR / BR, but everything else has B first

--- cistrans/cistrans.coffee
+++ cistrans/cistrans.coffee
@@ -143,6 +143,19 @@
   pxgXscaleX = d3.scale.ordinal()
                  .domain(d3.range(4))
                  .rangePoints([left[2], right[2]], 1)
+
+  # swap genotypes in females on X chromsome
+  for m in data.markers
+    continue unless data.pmark[m].chr == "X"
+    for g,i in data.geno[m]
+      if data.sex[i] == 0
+        newg = 0
+        switch g
+          when -1 then newg = -2
+          when -2 then newg = -1
+          when +1 then newg = +2
+          when +2 then newg = +1
+        data.geno[m][i] = newg

   # create SVG
   svg = d3.select("div#cistrans").append("svg")
@@ -572,7 +585,7 @@
         means = [0,0,0,0]
         n = [0,0,0,0]
         male = [0,0,1,1]
-        genotypes = ["RR", "BR", "BY", "RY"]
+        genotypes = ["BR", "RR", "BY", "RY"]
         sexcenter = [(pxgXscaleX(0) + pxgXscaleX(1))/2,
                      (pxgXscaleX(2) + pxgXscaleX(3))/2]
       else
@@ -636,7 +649,7 @@
         means = [0,0,0,0]
         n = [0,0,0,0]
         male = [0,0,1,1]
-        genotypes = ["RR", "BR", "BY", "RY"]
+        genotypes = ["BR", "RR", "BY", "RY"]
         sexcenter = [(pxgXscaleX(0) + pxgXscaleX(1))/2,
                      (pxgXscaleX(2) + pxgXscaleX(3))/2]
       else


Fix a bug in js driver.

Fix a bug in js driver.

rql-tests now all pass.

--- drivers/javascript/ast.coffee
+++ drivers/javascript/ast.coffee
@@ -216,7 +216,7 @@
         if opts?
             new IndexCreate opts, @, name, funcWrap(defun_or_opts)
         else if defun_or_opts?
-            if (defun_or_opts instanceof Object) and not (defun_or_opts instanceof Function)
+            if (defun_or_opts instanceof Object) and not (defun_or_opts instanceof Function) and not (defun_or_opts instanceof TermBase)
                 new IndexCreate defun_or_opts, @, name
             else
                 new IndexCreate {}, @, name, funcWrap(defun_or_opts)


Explain capital letters in keystrokes

Explain capital letters in keystrokes

--- dot-atom/keymap.cson
+++ dot-atom/keymap.cson
@@ -16,3 +16,4 @@
 #   'ctrl-P': 'core:move-up'
 #   'ctrl-p': 'core:move-down'
 #
+# In the example above, 'ctrl-P' is equivalent to 'ctrl-shift-p'.


#4807 Fix hubot-help path

#4807 Fix hubot-help path

--- packages/rocketchat-internal-hubot/hubot.coffee
+++ packages/rocketchat-internal-hubot/hubot.coffee
@@ -154,7 +154,7 @@
 		for modulePath in modulesToLoad
 			try
 				Npm.require(modulePath)(robot)
-				robot.parseHelp __meteor_bootstrap__.serverDir+'/npm/rocketchat_internal-hubot/node_modules/'+modulePath
+				robot.parseHelp __meteor_bootstrap__.serverDir+'/npm/node_modules/meteor/rocketchat_internal-hubot/node_modules/'+modulePath
 				console.log "Loaded #{modulePath}".green
 			catch e
 				console.log "can't load #{modulePath}".red


Allow multi-line title on oembed

Allow multi-line title on oembed

--- packages/rocketchat-oembed/server/server.coffee
+++ packages/rocketchat-oembed/server/server.coffee
@@ -103,7 +103,7 @@

 	if content?.body?
 		metas = {}
-		content.body.replace /<title>(.+)<\/title>/gmi, (meta, title) ->
+		content.body.replace /<title>((.|\n)+)<\/title>/gmi, (meta, title) ->
 			metas.pageTitle = title

 		content.body.replace /<meta[^>]*(?:name|property)=[']([^']*)['][^>]*content=[']([^']*)['][^>]*>/gmi, (meta, name, value) ->


Add spec for enumerated value port

Add spec for enumerated value port

--- spec/InPort.coffee
+++ spec/InPort.coffee
@@ -129,3 +129,24 @@
         chai.expect(data).toEqual 'data'
         chai.expect(data).toEqual _data
       s.send 'data'
+
+  describe 'with accepted enumerated values', (done) ->
+    it 'should accept certain values', ->
+      p = new inport
+        values: 'noflo is awesome'.split ''
+      s = new socket
+      p.attach s
+      p.on 'data', (data) ->
+        chai.expect(data).toEqual 'noflo is awesome'
+        done()
+      s.send 'awesome'
+
+    it 'should send to error port if value is not accepted', ->
+      p = new inport
+        values: 'noflo is awesome'.split ''
+      s = new socket
+      p.attach s
+      cb = jasmine.createSpy()
+      p.on 'data', cb
+      s.send 'terrific'
+      chai.expect(cb).not.toHaveBeenCalled()


spec: x = a(#box[width]) b(1);

spec: x = a(#box[width]) b(1);


more robust virtual hoisting tests

more robust virtual hoisting tests

--- spec/scoper.coffee
+++ spec/scoper.coffee
@@ -3,7 +3,7 @@
 else
   chai = require 'chai' unless chai
   parser = require '../lib/compiler'
-
+
 scope = parser.scope

 {expect, assert} = chai
@@ -12,27 +12,27 @@
   sources = [arguments...]
   title = sources.splice(0,1)[0]
   results = []
-  describe title + " ok", ->
+  describe title + " ok", ->
     it "sources ok ✓", ->
       for source, i in sources
-        results.push parser.parse source
+        results.push parser.parse source
         assert results[results.length-1].commands?, "source #{i} is ok"
-  describe title, ->
+  describe title, ->
     for source, i in sources
       if i isnt 0
         it "source #{i} == source #{i - 1}  ✓", ->
           expect(results[1]).to.eql results.splice(0,1)[0]

-
+
 describe "Scoper", ->
-
-
+
+
   # var hoisting raw commands
   # ====================================================================
-
+
   describe "var hoisting raw commands", ->
-
-    it 'hoist 1 level root before', ->
+
+    it 'hoist 1 level root before', ->
       ast =
         commands:
           [
@@ -42,8 +42,8 @@
                 ['==',['get','foo'],100]
               ]
             ]
-          ]
-      expect(scope(ast)).to.eql commands:
+          ]
+      expect(scope(ast)).to.eql commands:
         [
           ['==',['get','foo'],100]
           ['rule',['.','box'],
@@ -52,12 +52,12 @@
             ]
           ]
         ]
-
-
-    it 'hoist 1 level root after', ->
-      ast =
-        commands:
-          [
+
+
+    it 'hoist 1 level root after', ->
+      ast =
+        commands:
+          [
             ['rule',['.','box'],
               [
                 ['==',['get','foo'],100]
@@ -65,8 +65,8 @@
             ]
             ['==',['get','foo'],100]
           ]
-      expect(scope(ast)).to.eql commands:
-        [
+      expect(scope(ast)).to.eql commands:
+        [
           ['rule',['.','box'],
             [
               ['==',['get',['^'],'foo'],100]
@@ -74,7 +74,7 @@
           ]
           ['==',['get','foo'],100]
         ]
-
+
     it 'hoist 2 level before', ->
       ast =
         commands:
@@ -89,9 +89,9 @@
                   ]
                 ]
               ]
-            ]
-          ]
-      expect(scope(ast)).to.eql commands:
+            ]
+          ]
+      expect(scope(ast)).to.eql commands:
         [
           ['==',['get','foo'],0]
           ['rule',['.','box'],
@@ -103,14 +103,14 @@
                 ]
               ]
             ]
-          ]
-        ]
-
-
+          ]
+        ]
+
+
     it 'hoist 2 level root after', ->
       ast =
         commands:
-          [
+          [
             ['rule',['.','box'],
               [
                 ['rule',['.','box'],
@@ -123,8 +123,8 @@
             ]
             ['==',['get','foo'],0]
           ]
-      expect(scope(ast)).to.eql commands:
-        [
+      expect(scope(ast)).to.eql commands:
+        [
           ['rule',['.','box'],
             [
               ['rule',['.','box'],
@@ -137,12 +137,12 @@
           ]
           ['==',['get','foo'],0]
         ]
-
-
+
+
     it 'DONT already hoisted 2 level root after', ->
       ast =
         commands:
-          [
+          [
             ['rule',['.','box'],
               [
                 ['rule',['.','box'],
@@ -155,8 +155,8 @@
             ]
             ['==',['get','foo'],0]
           ]
-      expect(scope(ast)).to.eql commands:
-        [
+      expect(scope(ast)).to.eql commands:
+        [
           ['rule',['.','box'],
             [
               ['rule',['.','box'],
@@ -169,9 +169,9 @@
           ]
           ['==',['get','foo'],0]
         ]
-
-
-    it 'conditionals', ->
+
+
+    it 'conditionals', ->
       ast =
         commands:
           [
@@ -202,11 +202,11 @@
                       ]
                     ]
                   ]
-                ]
-              ]
-            ]
-          ]
-      expect(scope(ast)).to.eql commands:
+                ]
+              ]
+            ]
+          ]
+      expect(scope(ast)).to.eql commands:
         [
           ['==',['get','foo'],0]
           ['rule',['.','box'],
@@ -235,19 +235,19 @@
                     ]
                   ]
                 ]
-              ]
-            ]
-          ]
-        ]
-
-
+              ]
+            ]
+          ]
+        ]
+
+
   # virtual hoisting raw commands
   # ====================================================================
-
+
   describe "virtual hoisting raw commands", ->
-
+
     it 'hoist 1 level root before', ->
-      ast =
+      input =
         commands:
           [
             ['==',['get',['virtual','zone'],'foo'],100]
@@ -257,27 +257,52 @@
               ]
             ]
           ]
-      expect(scope(ast)).to.eql commands:
-        [
+      output =
+        commands:
+          [
+            ['==',['get',['virtual','zone'],'foo'],100]
+            ['rule',['.','box'],
+              [
+                ['==',['get',['virtual',['^'],'zone'],'foo'],100]
+              ]
+            ]
+          ]
+      expect(scope(input)).to.eql output
+      expect(scope(output)).to.eql JSON.parse JSON.stringify output
+
+    it 'hoist 1 level root after', ->
+      ast =
+        commands:
+          [
+            ['rule',['.','box'],
+              [
+                ['==',['get',['virtual','zone'],'foo'],100]
+              ]
+            ]
+            ['==',['get',['virtual','zone'],'foo'],100]
+          ]
+      expect(scope(ast)).to.eql commands:
+        [
+          ['rule',['.','box'],
+            [
+              ['==',['get',['virtual',['^'],'zone'],'foo'],100]
+            ]
+          ]
           ['==',['get',['virtual','zone'],'foo'],100]
-          ['rule',['.','box'],
-            [
-              ['==',['get',['virtual',['^'],'zone'],'foo'],100]
-            ]
-          ]
-        ]
-
+        ]
+
+
     it 'Dont hoist root', ->
       ast =
         commands:
           [
             ['==',['get',['virtual','zone'],'foo'],100]
           ]
-      expect(scope(ast)).to.eql commands:
+      expect(scope(ast)).to.eql commands:
         [
           ['==',['get',['virtual','zone'],'foo'],100]
         ]
-
+
     it 'Dont consider ruleset selector child scope', ->
       ast =
         commands:
@@ -285,26 +310,76 @@
             ['==',['get',['virtual','zone'],'foo'],100]
             ['rule', ['virtual','zone']
               [
-
-              ]
-            ]
-          ]
-      expect(scope(ast)).to.eql commands:
+
+              ]
+            ]
+          ]
+      expect(scope(ast)).to.eql commands:
         [
           ['==',['get',['virtual','zone'],'foo'],100]
           ['rule', ['virtual','zone']
             [
-
-            ]
-          ]
-        ]
-
-
+
+            ]
+          ]
+        ]
+
+    it '4 level', ->
+      ast =
+        commands:
+          [
+            ['rule', ['.','ready']
+              [
+                ['==',['get',['virtual','zone'],'foo'],100]
+                ['rule', ['virtual','zone']
+                  [
+                    ['==',['get',['virtual',['.','box'],'zone'],'foo'],100]
+                    ['rule', ['virtual','zone']
+                      [
+                        ['==',['get',['virtual',['.','box'],'zone'],'foo'],100]
+                        ['rule', ['virtual','zone']
+                          [
+                            ['==',['get',['virtual','zone'],'foo'],100]
+                          ]
+                        ]
+                      ]
+                    ]
+                  ]
+                ]
+              ]
+            ]
+          ]
+      expect(scope(ast)).to.eql commands:
+        [
+          ['rule', ['.','ready']
+            [
+              ['==',['get',['virtual','zone'],'foo'],100]
+              ['rule', ['virtual','zone']
+                [
+                  ['==',['get',['virtual',['.','box'],'zone'],'foo'],100]
+                  ['rule', ['virtual',['^'],'zone']
+                    [
+                      ['==',['get',['virtual',['.','box'],'zone'],'foo'],100]
+                      ['rule', ['virtual',['^',2],'zone']
+                        [
+                          ['==',['get',['virtual',['^',3],'zone'],'foo'],100]
+                        ]
+                      ]
+                    ]
+                  ]
+                ]
+              ]
+            ]
+          ]
+        ]
+
+
+
   # manual & auto hoisting source equivalence
   # ====================================================================
-
+
   describe "manual & auto hoisting source equivalence", ->
-
+
     equivalent "1 level basic",
       """
         foo == bar;
@@ -318,14 +393,14 @@
           ^foo == ^bar;
         }
       """
-
+
     equivalent "3 level moderate",
       """
-
+
         @if foo > 20 {
-
+
         .outer {
-
+
           .box {
             20 * foo + 100 == bye - bar / 10;
             .inner {
@@ -333,20 +408,20 @@
               20 * foo + 100 == bye - bar / 10;
             }
           }
-
+
           20 * foo + 100 == hey - bar / 10;
-
-        }
-
-        }
-
+
+        }
+
+        }
+
       """,
       """
-
+
         @if foo > 20 {
-
+
         .outer {
-
+
           .box {
             20 * ^^foo + 100 == bye - ^bar / 10;
             .inner {
@@ -354,15 +429,14 @@
               20 * ^^^foo + 100 == ^bye - ^^bar / 10;
             }
           }
-
+
           20 * ^foo + 100 == hey - bar / 10;
-
-        }
-
-        }
-
-      """
-
-
-
-    +
+        }
+
+        }
+
+      """
+
+
+


Use `now` as the reference point for command timing.

This will tell us the amount of time that passed between the
last command run and the error.

--- lib/command-logger.coffee
+++ lib/command-logger.coffee
@@ -48,7 +48,7 @@
   # Returns a {String} of the Markdown for the report.
   getText: (externalData) ->
     lines = []
-    lastTime = @calculateLastEventTime(externalData)
+    lastTime = Date.now()

     @eachEvent (event) =>
       return if event.time > lastTime


Fix some bugs

Fix some bugs

--- src/6.timer.coffee
+++ src/6.timer.coffee
@@ -15,12 +15,14 @@
     @_endOfTime: new Date("9999-12-30T00:00:00Z")
     @_precision: null
     @enable: (precision = 30) ->
+        if Date.Timer.getEnabled() then return
         Date.Timer._precision = precision
         @_internalTimer = setInterval(=>
             Date.Timer._onCheck.fire()
         , precision)
         undefined
     @disable: ->
+        if not Date.Timer.getEnabled() then return
         clearInterval(@_internalTimer)
         Date.Timer._precision = null
         undefined
@@ -36,7 +38,7 @@
         @_running = false
         @onArrive = eventField()
     run: ->
-        if @_running then return
+        if @_running then return @
         @_checker = =>
             nowTime = new Date()
             if (if @allowsEqual then nowTime >= @targetTime else nowTime > @targetTime)
@@ -52,7 +54,7 @@
         @_running = true
         @
     stop: ->
-        if not @_running then return
+        if not @_running then return @
         Date.Timer._onCheck.unbind(@_checker)
         @_running = false
         @


log failed callbacks in Atom console

log failed callbacks in Atom console

--- lib/connection/ipc.coffee
+++ lib/connection/ipc.coffee
@@ -64,7 +64,9 @@
       if callback
         result
           .then (result) => @msg 'cb', callback, result
-          .catch (err) => @msg 'cancelCallback', callback, @errJson err
+          .catch (err) =>
+            console.error(err)
+            @msg 'cancelCallback', callback, @errJson err
     else
       console.log "julia-client: unrecognised message #{type}", args


Remove extraneous comment pointed out in code review

--- src/code/stores/graph-store.coffee
+++ src/code/stores/graph-store.coffee
@@ -44,7 +44,6 @@
     @updateListeners()

   _trimSimulation: ->
-    #settings = SimulationStore.store.serialize()
     for node in @getNodes()
       # leaving some excess data reduces flicker during rapid changes
       excessFrames = node.frames.length - 2 * SimulationStore.store.simulationDuration()


Safeguard @productData no notifyMe

Safeguard @productData no notifyMe

--- src/coffee/portal-notify-me.coffee
+++ src/coffee/portal-notify-me.coffee
@@ -31,7 +31,7 @@
 		@bindEvents()

 	render: =>
-		unless @productData.displayMode == 'lista'
+		unless @productData and @productData.displayMode == 'lista'
 			dust.render 'notify-me', @options, (err, out) =>
 				throw new Error("Notify Me Dust error: #{err}") if err
 				@element.html out


clean useless code

clean useless code

--- helpers/db_feed_helper.coffee
+++ helpers/db_feed_helper.coffee
@@ -60,7 +60,4 @@
         @db.get change.id, (err, doc) =>
             console.log err if err?
             doctype = doc?.docType?.toLowerCase()
-            @_publish "#{doctype}.#{operation}", doc._id if doctype
-
-    usage: (app) ->
-        @_publish "usage.application", app
+            @_publish "#{doctype}.#{operation}", doc._id if doctype


The resolver method of mixins' effective methods made to respect concerns

--- lib/entities/mixin.coffee
+++ lib/entities/mixin.coffee
@@ -81,6 +81,7 @@
     methods

   effectiveMethods: ->
+    return @effectiveConcernMethods() if @concern
     @_effectiveMethods ||= @aggregateEffectiveMethods()

   effectiveInclusionMethods: ->


Filter by word length

Filter by word length

--- lib/fuzzy-provider.coffee
+++ lib/fuzzy-provider.coffee
@@ -117,6 +117,12 @@
     # Flatten the matches, make it an unique array
     wordList = _.flatten matches
     wordList = Utils.unique wordList
+
+    # Filter words by length
+    minimumWordLength = atom.config.get("autocomplete-plus.minimumWordLength")
+    if minimumWordLength
+      wordList = wordList.filter (word) -> word?.length >= minimumWordLength
+
     @wordList = wordList

     p.stop()


Suppress useless logs

Suppress useless logs


fix random problem

fix random problem

--- lib/pg-hoff-dialog.coffee
+++ lib/pg-hoff-dialog.coffee
@@ -45,7 +45,7 @@
             input.placeholder = text
             modal = atom.workspace.addModalPanel(item: element, visible: true)
             input.focus()
-            input.onkeyup = (e) ->
+            input.onkeydown = (e) ->
                 if e.which == 27
                     modal.hide()
                     reject('escape')
@@ -64,7 +64,7 @@
             input.placeholder = text
             modal = atom.workspace.addModalPanel(item: element, visible: true)
             input.focus()
-            input.onkeyup = (e) ->
+            input.onkeydown = (e) ->
                 if e.which == 27
                     modal.hide()
                     reject('escape')


Show default in tooltip when present in settings - Resolves #268

--- lib/settings-panel.coffee
+++ lib/settings-panel.coffee
@@ -39,6 +39,7 @@
     @bindInputFields()
     @bindSelectFields()
     @bindEditors()
+    @bindTooltips()
     @handleEvents()

   dispose: ->
@@ -173,6 +174,15 @@

       editor.onDidStopChanging =>
         @set(name, @parseValue(type, editor.getText()))
+
+  bindTooltips: ->
+    @find('input[id], select[id], atom-text-editor[id]').views().forEach (view) =>
+      if defaultValue = @valueToString(@getDefault(view.attr('id')))
+        @disposables.add atom.tooltips.add view,
+          title: 'Default: ' + defaultValue
+          delay:
+            show: 100
+          placement: 'auto left'

   valueToString: (value) ->
     if _.isArray(value)


Throw proper error when CoffeeScript compilation fails

--- lib/tintan/compile.coffee
+++ lib/tintan/compile.coffee
@@ -59,15 +59,19 @@
         c.stderr.on 'data', (data)-> process.stderr.write data
     true

-   compile: (source, target, cb)->
-     jake.mkdirP path.dirname(target)
-     c = fs.readFileSync source, 'utf-8'
-     j = coffee.compile c
-     fs.writeFileSync target, j, 'utf-8'
-     cb()
+  compile: (source, target, cb)->
+    jake.mkdirP path.dirname(target)
+    c = fs.readFileSync source, 'utf-8'
+    try
+      j = coffee.compile c
+      fs.writeFileSync target, j, 'utf-8'
+    catch err
+      process.stderr.write "Error compiling #{source}\n"
+      process.stderr.write err.toString() + "\n"
+    cb()

-   invokeTask: ->
-     jake.Task[@options.name].invoke()
+  invokeTask: ->
+    jake.Task[@options.name].invoke()

    invokeClean: ->
      jake.Task[@options.name + ':clean'].invoke()


Add recognition of POD commands

Add recognition of POD commands

--- grammars/perl.cson
+++ grammars/perl.cson
@@ -15,12 +15,21 @@
     'include': '#line_comment'
   }
   {
-    'begin': '^='
-    'captures':
-      '0':
-        'name': 'punctuation.definition.comment.perl'
-    'end': '^=cut'
+    'begin': '^(?==)'
+    'end': '^(=cut\\b.*$)'
+    'endCaptures':
+      '1':
+        'patterns': [
+          {
+            'include': '#pod'
+          }
+        ]
     'name': 'comment.block.documentation.perl'
+    'patterns': [
+      {
+        'include': '#pod'
+      }
+    ]
   }
   {
     'include': '#variable'
@@ -746,6 +755,11 @@
         'name': 'constant.language.perl'
     'contentName': 'comment.block.documentation.perl'
     'end': '(?=N)A'
+    'patterns': [
+      {
+        'include': '#pod'
+      }
+    ]
   }
   {
     'match': '(?<!->)\\b(continue|default|die|do|else|elsif|exit|for|foreach|given|goto|if|last|next|redo|return|select|unless|until|wait|when|while|switch|case|require|use|eval)\\b'
@@ -1923,6 +1937,98 @@
         'include': '#nested_parens_interpolated'
       }
     ]
+  'pod':
+    'patterns': [
+      {
+        # Niladic POD commandss
+        'match': '^=(pod|back|cut)\\b'
+        'name': 'storage.type.class.pod.perl'
+      }
+      {
+        # Embedded text/data
+        'begin': '^(=begin)\\s+(html)\\s*$'
+        'end': '^(=end)\\s+(html)|^(?==cut)'
+        'name': 'meta.embedded.pod.perl'
+        'beginCaptures':
+          '1':
+            'name': 'storage.type.class.pod.perl'
+          '2':
+            'name': 'variable.other.pod.perl'
+        'endCaptures':
+          '1':
+            'name': 'storage.type.class.pod.perl'
+          '2':
+            'name': 'variable.other.pod.perl'
+        'contentName': 'text.embedded.html.basic'
+        'patterns': [
+          {
+            'include': 'text.html.basic'
+          }
+        ]
+      }
+      {
+        # POD commands with highlighted arguments
+        'match': '^(=(?:head[1-4]|item|over|encoding|begin|end|for))\\b\\s*(.*)'
+        'captures':
+          '1':
+            'name': 'storage.type.class.pod.perl'
+          '2':
+            'name': 'variable.other.pod.perl'
+            'patterns': [
+              {
+                'include': '#pod-formatting'
+              }
+            ]
+      }
+      {
+        'include': '#pod-formatting'
+      }
+    ]
+  'pod-formatting':
+    'patterns': [
+      {
+        # Italic text
+        'match': 'I(?:<([^<>]+)>|<+(\\s+(?:(?<!\\s)>|[^>])+\\s+)>+)'
+        'name': 'entity.name.type.instance.pod.perl'
+        'captures':
+          '1':
+            'name': 'markup.italic.pod.perl'
+          '2':
+            'name': 'markup.italic.pod.perl'
+      }
+      {
+        # Bold text
+        'match': 'B(?:<([^<>]+)>|<+(\\s+(?:(?<!\\s)>|[^>])+\\s+)>+)'
+        'name': 'entity.name.type.instance.pod.perl'
+        'captures':
+          '1':
+            'name': 'markup.bold.pod.perl'
+          '2':
+            'name': 'markup.bold.pod.perl'
+      }
+      {
+        # Constant-width/"preformatted" text
+        'match': 'C(?:<([^<>]+)>|<+(\\s+(?:(?<!\\s)>|[^>])+\\s+)>+)'
+        'name': 'entity.name.type.instance.pod.perl'
+        'captures':
+          '1':
+            'name': 'markup.raw.pod.perl'
+          '2':
+            'name': 'markup.raw.pod.perl'
+      }
+      {
+        # Link
+        'match': 'L<([^>]+)>'
+        'name': 'entity.name.type.instance.pod.perl'
+        'captures':
+          '1':
+            'name': 'markup.underline.link.hyperlink.pod.perl'
+      }
+      {
+        'match': '[EFSXZ]<[^>]*>'
+        'name': 'entity.name.type.instance.pod.perl'
+      }
+    ]
   'variable':
     'patterns': [
       {


KD.utils.sendDataDogEvent: just send events only when logs disabled or hostname is koding.com

--- client/Main/utils.coffee
+++ client/Main/utils.coffee
@@ -823,16 +823,31 @@

   sendDataDogEvent: (eventName)->

-    KD.utils.s3upload
-      name    : "logs_#{new Date().toISOString()}.txt"
-      content : KD.parseLogs()
-    , (err, publicUrl)->
-
-      logs = if err? and not publicUrl
-      then KD.parseLogs()
-      else publicUrl
-
+    sendEvent = (logs)->
       KD.remote.api.DataDog.sendEvent { eventName, logs }
+
+    kdlogs = KD.parseLogs()
+
+    # If there is enough log to send, no more checks required
+    # just send them away, first to s3 then datadog
+    if kdlogs.length > 100
+
+      KD.utils.s3upload
+        name    : "logs_#{new Date().toISOString()}.txt"
+        content : kdlogs
+      , (err, publicUrl)->
+
+        logs = if err? and not publicUrl
+        then KD.parseLogs()
+        else publicUrl
+
+        sendEvent publicUrl
+
+    else
+
+      # Send only events when hostname is koding.com
+      # and user enabled logs somehow
+      sendEvent()  if location.hostname is "koding.com"

   s3upload: (options, callback = noop)->


fixed invalid package paths referencing ui

--- coffee/jplayer-ui.coffee
+++ coffee/jplayer-ui.coffee
@@ -43,8 +43,8 @@
       # @player.load m.get "mp3_file_path"
       # @hud.setModel m
   subviews:
-    "#trackHUD": ApiHeroUI.widgets.jPlayer.ui.components.PlayerDisplay
-    ".ctrl_buttons": ApiHeroUI.widgets.jPlayer.ui.components.PlayerControls
+    "#trackHUD": ApiHeroUI.widgets.jPlayer.components.PlayerDisplay
+    ".ctrl_buttons": ApiHeroUI.widgets.jPlayer.components.PlayerControls
   init:(o)->
     if (@model)
       @model.fetch
@@ -56,9 +56,6 @@
       @$el.find(".time").text "00:00"
       @hud.setModel ApiHeroUI.widgets.jPlayer.queue.getCurrentTrack()
     # @discovery = new global.Tunstr.Discovery
-
-
-
     @[".ctrl_buttons"].on "play", @play, @
     @[".ctrl_buttons"].on "pause", @pause, @
     @[".ctrl_buttons"].on "fadv", =>
@@ -80,9 +77,7 @@
       @$el.find(".time").text tArr.join ':'
     )
     ApiHeroUI.widgets.jPlayer.player.on("trackEnded", (evt)=>
-      if ApiHeroUI.widgets.jPlayer.queue._discoveryMode
-        ApiHeroUI.widgets.jPlayer.queue.discover()
-      else if @play_mode == "playlist"
+      if @play_mode == "playlist"
         @fadv()
       else
         @pause()


Options

Options

- Added hint that form.right is obsolete in "ng" design.

--- src/elements/Options/Options.coffee
+++ src/elements/Options/Options.coffee
@@ -317,6 +317,9 @@
 				if @__maxChars < chars
 					@__maxChars = chars

+				if CUI.__ng__ and opt.form?.right
+					console.error("Options.render: form.right is obsolete. 'right' part will not appear.", @, opt)
+
 				if @_sortable
 					if not opt.form
 						opt.form = {}


fix settings

fix settings

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -52,13 +52,22 @@
 		constructor: ->
 			window.addEventListener "message", @messageListener, false

+			@initSettings()
+
+			# Get the service URL
+			chrome.extension.sendMessage {action: 'url'}, (url) =>
+				@url = url
+				@checkVersion()
+				return
+
+		initSettings: ->
 			# Get the settings
 			chrome.storage.local.get null, (items) =>
 				_.each items, (value, key) =>
 					switch key
 						when M.STORE.DEBUG
 							@debug = value
-							log 'MailFred debugging is enabled'
+							log 'MailFred debugging is enabled' if @debug
 						when M.STORE.EMAIL
 							@settingEmail = value
 					return
@@ -70,15 +79,27 @@
 				return

 			# Get the last used presets
-			M.getLastUsed (lastUsed) =>
-				@lastUsed = lastUsed
-				return
-
-			# Get the service URL
-			chrome.extension.sendMessage {action: 'url'}, (url) =>
-				@url = url
-				@checkVersion()
-				return
+			chrome.storage.sync.get M.STORE.LASTUSED, (items) =>
+				@lastUsed = items[M.STORE.LASTUSED] ? []
+				return
+
+			# Listen to changes
+			chrome.storage.onChanged.addListener (changes, namespace) =>
+				switch namespace
+					when 'sync'
+						if M.STORE.LASTUSED of changes
+							@lastUsed = changes[M.STORE.LASTUSED].newValue
+						if M.STORE.BOX_SETTING of changes
+							@settingProps = changes[M.STORE.BOX_SETTING].newValue
+					when 'local'
+						if M.STORE.DEBUG of changes
+							@debug = changes[M.STORE.DEBUG].newValue
+							log 'MailFred debugging is enabled' if @debug
+						if M.STORE.EMAIL of changes
+							@settingEmail = changes[M.STORE.EMAIL].newValue
+				return
+
+			return

 		@storeLastVersion: (version) ->
 			store = {}
@@ -227,7 +248,7 @@
 				(__msg x)
 			]

-		composeButton: (type) =>
+		composeButton: =>

 			props =
 				noanswer: 	false
@@ -269,11 +290,12 @@
 				errorSection.toggle !valid
 				return

-			propStoreFn = (checkbox, propName) ->
-				checkbox.addOnChange (e, checked) ->
+			propStoreFn = (checkbox, propName) =>
+				checkbox.addOnChange (e, checked) =>
 					props[propName] = checked
 					toStore = {}
 					toStore[M.STORE.BOX_SETTING] = props
+					@settingProps = props
 					chrome.storage.sync.set toStore, ->
 						log 'Storing properties finished'
 						return
@@ -371,7 +393,7 @@
 					button = lastUsedSection.append new GMailUI.Button label, title
 					timeFn = @generateTimeFn key
 					button.on 'click', (e) =>
-						M.storeLastUsed key, time
+						@storeLastUsed key, time
 						wen = timeFn time
 						log "schedule: #{time}, #{key}: #{wen}"
 						schedule wen
@@ -395,7 +417,7 @@
 						item = new GMailUI.PopupMenuItem presetItem, label, title, '', false
 						onChange = (e, checked) =>
 							if checked
-								M.storeLastUsed key, time
+								@storeLastUsed key, time
 								wen = timeFn time
 								log "schedule: #{time}, #{key}: #{wen}"
 								schedule wen
@@ -414,26 +436,19 @@

 			bar.getElement()

-		@storeLastUsed: (key, time) ->
-			@getLastUsed (lastUsed) =>
-				entry =
-					key: key
-					time: time
-
-				lastUsed = _.reject lastUsed, (item) -> _.isEqual item, entry
-
-				lastUsed.unshift entry
-				store = {}
-				lastUsed = store[@STORE.LASTUSED] = (_.first lastUsed, 3)
-				chrome.storage.sync.set store, ->
-					log 'Last used items saved', lastUsed
-					return
-				return
-			return
-
-		@getLastUsed: (resp) ->
-			chrome.storage.sync.get @STORE.LASTUSED, (items) =>
-				resp (items[@STORE.LASTUSED] ? [])
+		storeLastUsed: (key, time) =>
+			entry =
+				key: key
+				time: time
+
+			lastUsed = _.reject @lastUsed, (item) -> _.isEqual item, entry
+
+			lastUsed.unshift entry
+			store = {}
+			store[M.STORE.LASTUSED] = (_.first lastUsed, 3)
+
+			chrome.storage.sync.set store, ->
+				log 'Last used items saved', store
 				return
 			return


fix(vdi.set): properly get resource set when resizing (#585)

--- src/api/vdi.coffee
+++ src/api/vdi.coffee
@@ -44,7 +44,7 @@
     vbds = vdi.$VBDs
     if (
       vbds.length == 1 &&
-      (resourceSetId = xapi.xo.getData(@getObject(vbds[0], 'VBD'), 'resourceSet')) != undefined
+      (resourceSetId = xapi.xo.getData(@getObject(vbds[0], 'VBD').VM, 'resourceSet')) != undefined
     )
       if @user.permission != 'admin'
         yield @checkResourceSetConstraints(resourceSetId, @user.id)


fix it in the right place

fix it in the right place

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -17,10 +17,10 @@
       try
         for event in value
           reply = sink(event = toEvent(event))
-          unbind() if reply == Bacon.noMore or event.isEnd?()
+          unbind() if reply == Bacon.noMore or event.isEnd()
       catch e
         # make sure the stream ends even if an exception is thrown
-        unbind() if _.last(value).isEnd()
+        unbind() if _.last(value).isEnd?()
         throw e

 # eventTransformer - defaults to returning the first argument to handler


add delete_account to c.coffee

add delete_account to c.coffee

--- src/c.coffee
+++ src/c.coffee
@@ -6,6 +6,8 @@
 The functiosns below in some cases return things, and in some cases set global variables!  Read docs.

 ###
+
+db_hosts = [process.env.SMC_DB_HOSTS ? 'db0']

 start_time = new Date()
 global.start = ->
@@ -23,7 +25,7 @@
         cb(undefined, db)  # HACK -- might not really be initialized yet!
         return db
     else
-        db = require('./smc-hub/rethink').rethinkdb(hosts:['db0'], pool:1, cb:cb)
+        db = require('./smc-hub/rethink').rethinkdb(hosts:db_hosts, pool:1, cb:cb)
         return db

 # get a connection to the db
@@ -40,7 +42,7 @@
 # make the global variable s be the compute server
 global.compute_server = () ->
     return require('smc-hub/compute-client').compute_server
-        db_hosts:['db0']
+        db_hosts:db_hosts
         cb:(e,s)->
             global.s=s
 console.log("compute_server() -- sets global variable s to compute server")
@@ -48,7 +50,7 @@
 # make the global variable p be the project with given id and the global variable s be the compute server
 global.proj = global.project = (id) ->
     require('smc-hub/compute-client').compute_server
-        db_hosts:['db0']
+        db_hosts: db_hosts
         cb:(e,s)->
             global.s=s
             s.project
@@ -67,3 +69,20 @@
     require('smc-hub/storage').activity(opts)

 console.log("activity()  -- makes activity the activity monitor object")
+
+global.delete_account = (email) ->
+    require('./smc-hub/rethink').rethinkdb
+        hosts:db_hosts
+        pool:1
+        cb: (err, db) ->
+            if err
+                done("FAIL -- #{err}")
+                return
+            db.mark_account_deleted
+                email_address: email
+                cb           : (err) ->
+                    if err
+                        done("FAIL -- #{err}")
+                    else
+                        done("SUCCESS!")
+console.log("delete_account 'email@foo.bar'  -- marks an account deleted")


Change scripts block to allow custom attributes

--- src/lib/collections/scripts.coffee
+++ src/lib/collections/scripts.coffee
@@ -13,12 +13,12 @@
 		# Prepare
 		opts or= {}
 		opts.defer ?= true
+		opts.attrs or= ''
 		values = [values]  unless _.isArray(values)
-		attrs = ''

 		# Build attrs
 		if opts.defer
-			attrs += """defer="defer" """
+			opts.attrs += """defer="defer" """

 		# Convert urls into script element html
 		for value,key in values
@@ -28,12 +28,12 @@
 				else if value.indexOf(' ') is -1
 					# we are a url
 					values[key] = """
-						<script #{attrs} src="#{value}"></script>
+						<script #{opts.attrs} src="#{value}"></script>
 						"""
 				else
 					# we are javascript not in a script element
 					values[key] = """
-						<script #{attrs}>#{value}</script>
+						<script #{opts.attrs}>#{value}</script>
 						"""

 		# Call the super with our values


Use 33 formula as default LOS formula

Use 33 formula as default LOS formula

--- views/components/ship-parts/topalert.cjsx
+++ views/components/ship-parts/topalert.cjsx
@@ -374,7 +374,7 @@

           Lv. {@messages.totalLv}
           {__ 'Fighter Power'}: {@messages.tyku.max}
-          {__ 'LOS'}: {@messages.saku25a.total}
+          {__ 'LOS'}: {@messages.saku33.total}

       else
         <Alert style={getFontStyle window.theme}>
@@ -397,7 +397,7 @@

33: {@messages.saku33.total}

                 </Tooltip>
               }>
-                {__ 'LOS'}: {@messages.saku25a.total}
+                {__ 'LOS'}: {@messages.saku33.total}
               </OverlayTrigger>

             {@getState()}: {resolveTime @maxCountdown}


Added ability to change listening port based on command line switch (-p)

--- start.coffee
+++ start.coffee
@@ -3,7 +3,11 @@
 join = require("path").join
 Vein = require "vein"

-port = process.env.PORT or 8080
+try
+  if process.argv[2] is "-p" then port = parseInt(process.argv[3]) else port = process.env.PORT or 8080
+catch e
+  console.error "Listening port was invalid"
+  port = process.env.PORT or 8080

 # Web server
 app = express()


test of assignments being right-associative


homoiconicity tests

homoiconicity tests

--- tests.coffee
+++ tests.coffee
@@ -3257,6 +3257,49 @@
   """
   "the end."

+# ---------------------------------------------------------------------------
+# homoiconicity
+
+  """
+  codeToBeRun ='
+  ﹍console print 1+2
+  codeToBeRun eval
+  """
+  "3"
+
+  """
+  codeToBeRun ='
+  ﹍console print 1+2
+  console print codeToBeRun[3]
+  """
+  "+"
+
+  """
+  codeToBeRun ='
+  ﹍console print 1+2
+  codeToBeRun[3] = '-
+  codeToBeRun eval
+  """
+  "-1"
+
+  """
+  codeToBeRun ='
+  ﹍console print 1+2
+
+  MyClass=Class new
+  MyClass.counter = nil
+  MyClass answer:
+  ﹍printOperation
+  by:
+  ﹍codeToBeRun eval
+  myObject=MyClass new
+  myObject printOperation
+  codeToBeRun[3] = '-
+  myObject printOperation
+  """
+
+  "3-1"
+

 ]


arguments has become a reserved keyword - renamed to args

--- bin/precious.coffee
+++ bin/precious.coffee
@@ -69,23 +69,23 @@
   stream.stdout.pipe process.stdout

-# Processing of command-line arguments.  Minimal on purpose.
+# Processing of command-line args.  Minimal on purpose.
 # Expects `<json>` string or `-i <file>`.
 # Does help with man pages for precious, precious-json.
-arguments = process.argv.splice(2)
-if arguments.length > 0
-  if arguments[0] is '-i'
-    unless arguments[1]?
+args = process.argv.splice(2)
+if args.length > 0
+  if args[0] is '-i'
+    unless args[1]?
       console.error "Usage: precious -i <file>"
       process.exit(1)
-    require('fs').readFile "./#{arguments[1]}", "utf8", (err, data) ->
+    require('fs').readFile "./#{args[1]}", "utf8", (err, data) ->
       if err
         console.error "
 An error has ocurred.  Please double-check the file & path."
         console.error err
         process.exit(1)
       else fetch data
-  else if arguments[0] is "json" then man "precious-json"
-  else fetch arguments[0]
+  else if args[0] is "json" then man "precious-json"
+  else fetch args[0]
 else man "precious"


update approximate to take some indication from user.dateTimePref

--- vue/src/shared/helpers/format_time.coffee
+++ vue/src/shared/helpers/format_time.coffee
@@ -11,15 +11,26 @@
   throw {"invalid date", date} unless isValid(date)
   format(utcToZonedTime(date, zone), 'EEE', {timeZone: zone, locale: i18n.dateLocale})

-export approximate = (date, zone) ->
+export approximate = (date, zone = AppConfig.timeZone, dateTimePref = Session.user().dateTimePref) ->
   throw {"invalid date", date} unless isValid(date)
+
+  localFormat = (pattern) ->
+    format(utcToZonedTime(date, zone), pattern, {timeZone: zone, locale: i18n.dateLocale})
+
   now = new Date
   if differenceInHours(now, date) < 24
     formatDistanceStrict(date, new Date(), {addSuffix: true, locale: i18n.dateLocale})
   else if isSameYear(date, now)
     format(date, "MMMM d", {locale: i18n.dateLocale})
   else
-    format(date, "yyyy-MM-dd")
+    switch dateTimePref
+      when 'day_iso', 'iso'
+        localFormat('yyyy-MM-dd')
+      when 'abbr', 'day_abbr'
+        localFormat("d LLL yyyy")
+      else
+        console.error('unknown date pref')
+

 export exact = (date, zone = AppConfig.timeZone, dateTimePref = Session.user().dateTimePref) ->
   throw {"invalid date", date} unless isValid(date)


viewer opens files with https now

viewer opens files with https now

--- client/landing/app/Applications/Viewer.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Viewer.kdapplication/AppController.coffee
@@ -15,7 +15,7 @@
         return cb true  unless path
         path = FSHelper.plainPath path
         publicPath = path.replace \
-          ////home/(.*)/Web/(.*)///, "http://$1.#{KD.config.userSitesDomain}/$2"
+          ////home/(.*)/Web/(.*)///, "https://$1.#{KD.config.userSitesDomain}/$2"
         cb publicPath isnt path, {path: publicPath}

       failure    : (options, cb)->


landing: check for payment token instead of card info

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/landing/site.landing/coffee/team/forms/stripepaymenttabform.coffee
+++ client/landing/site.landing/coffee/team/forms/stripepaymenttabform.coffee
@@ -42,7 +42,7 @@

     kd.singletons.router.on 'RouteInfoHandled', =>

-      return  unless card = utils.getPayment()?.card
+      return  unless token = utils.getPayment().token

       @resetFormLink.show()
       @forEachInputView (input) -> input.hide()


in TextLayer, only generate a <br/> if the whole line is empty

--- framer/StyledText.coffee
+++ framer/StyledText.coffee
@@ -61,11 +61,11 @@
 		endIndex: @endIndex
 		css: _.clone(@css)

-	createElement: ->
+	createElement: (maybeLineBreak) ->
 		span = document.createElement "span"
 		for prop, value of @css
 			span.style[prop] = value
-		if @text is ""
+		if @text is "" and maybeLineBreak
 			span.innerHTML = "<br/>"
 		else
 			span.textContent = @text
@@ -152,8 +152,9 @@
 	createElement: ->
 		div = document.createElement "div"
 		div.style.fontSize = "1px"
+		maybeLineBreak = @inlineStyles.length is 1
 		for style in @inlineStyles
-			span = style.createElement()
+			span = style.createElement(maybeLineBreak)
 			style.element = span
 			div.appendChild span
 		return div


Add unrefined() method to refinement library; gets unrefined object

--- javascripts/lib.coffee
+++ javascripts/lib.coffee
@@ -67,7 +67,7 @@
       refinement_class = ->
       refinement_class.prototype = subject
       ret = new refinement_class
-      _.extend(ret, @methods)
+      _.extend(ret, {unrefined: -> subject }, @methods)
       ret
   class_mixer RefinementDeclaration

@@ -76,8 +76,7 @@

     isApplicable: (subject)->
       subject instanceof @type
+  class_mixer RefinementByType

-  class_mixer RefinementByType
-
   return Refinement


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -40,8 +40,7 @@
     height: 8
     collisionMargin: Point(0, 0)

-  self.include Distance
-
+
   ###*
   The position of this game object. By default it is the top left point.
   Redefining the center method will change the relative position.


bodyWrapper setting

bodyWrapper setting

--- app/app.coffee
+++ app/app.coffee
@@ -39,6 +39,10 @@
 AppComponent = React.createClass
   displayName: 'AppComponent'
   render: ->
+    if @props.bodyWrapper
+      body = @props.bodyWrapper null, @props.activeRouteHandler()
+    else
+      body = @props.activeRouteHandler()
     React.DOM.div {className: 'lead'},
       React.DOM.div {className: 'nav-bar'},
         Router.Link {to: 'notebook', className: 'title'}, 'lead'
@@ -46,7 +50,7 @@
           Router.Link {to: 'help-index'}, React.DOM.i {className: 'fa fa-question-circle'}
           Router.Link {to: 'settings'}, React.DOM.i {className: 'fa fa-cog'}
       React.DOM.div {className: 'body'},
-        this.props.activeRouteHandler()
+        body

 HelpPathComponent = React.createClass
   displayName: 'HelpPathComponent'
@@ -172,6 +176,8 @@
   if publicUrl?
     `__webpack_public_path__ = publicUrl`

+  bodyWrapper = Settings.get 'app', 'bodyWrapper'
+
   raw_cell_value = null
   if location.search isnt ''
     uri = URI location.href
@@ -181,8 +187,8 @@

   null_route = (fn) -> React.createClass render: -> fn.call(@); null

-  routes = Routes null,
-    Route {handler: AppComponent},
+  routesComponent = Routes null,
+    Route {handler: AppComponent, bodyWrapper},
       Route {path: '/', name: 'default', handler: null_route ->
         if raw_cell_value?
           Router.replaceWith '/notebook/raw/' + raw_cell_value
@@ -200,7 +206,7 @@

   # TODO handler errors, timeouts
   Modules.init_modules(module_names).finally ->
-    React.renderComponent routes, target
+    React.renderComponent routesComponent, target

 exports.raw_cell_url = (value) ->
   URI(makeHref 'raw_notebook', splat: btoa value).absoluteTo(location.href).toString()


Add explanatory comment.

Add explanatory comment.

To some weird code whose rationale probably isn't obvious.

--- app/assets/javascripts/all_aboard/models/slide.js.coffee
+++ app/assets/javascripts/all_aboard/models/slide.js.coffee
@@ -3,6 +3,9 @@
   layout: DS.belongsTo("App.Layout")
   panes: DS.hasMany("App.Pane")

+  # There won't be database records for unassigned panes, but we do want to
+  # render a placeholder so that interface looks right.  There has to be a
+  # better solution.
   possiblePanes: Em.computed ->
     return [] unless @get("layout.paneCount")


Applied coffeelint.

Applied coffeelint.

--- app/assets/javascripts/app/controllers/widget/tag.coffee
+++ app/assets/javascripts/app/controllers/widget/tag.coffee
@@ -68,7 +68,7 @@
     e.preventDefault()
     @newTagLabel.addClass('hide')
     @newTagInput.removeClass('hide').focus()
-    @newTagInput.trigger(jQuery.Event("keydown"))
+    @newTagInput.trigger(jQuery.Event('keydown'))
     @editMode = true

   hideAndAddInput: =>


color fix for task-overview

color fix for task-overview

--- app/assets/javascripts/admin/views/task/task_overview_view.coffee
+++ app/assets/javascripts/admin/views/task/task_overview_view.coffee
@@ -314,8 +314,7 @@
       if @doDrawUser(user)
         userName = user.firstName + " " + user.lastName
         workingHours = user.workingHours
-        minMaxHours = @getMinMaxHours()
-        color = @colorJet(128 - (workingHours - minMaxHours.min) * (128 / minMaxHours.max - minMaxHours.min) + 128)
+        color = @colorJet(128 - (workingHours - @chosenMinHours) * (128 / @chosenMaxHours - @chosenMinHours) + 128)

         @quoted(userName) + " [id="+ @quoted(user.id) + ", shape=box, fillcolor=" + @quoted(color) + "]"
     )).join(";")


Re-implement popup passthrough more robustly

Commit 35f48d24bf5c broke popup passthrough a bit, unfortunately. It
works for the context menu, but not for the menu bar.

It is time to ditch the unreliable onpopupshowing/onpopuphiding and
checking properties on root windows for determining if popups are open.

Now we simply just loop through all menupopups and panels and check if
any of them are open. If so, don’t do anything. Otherwise, proceed as
normal.

--- extension/packages/events.coffee
+++ extension/packages/events.coffee
@@ -18,15 +18,6 @@
     return keyStr

   return null
-
-# Passthrough mode is activated when VimFx should temporarily stop processing keyboard input, for
-# example when a menu is shown.
-popupPassthrough = false
-checkPassthrough = (event) ->
-  if event.target.nodeName in ['menupopup', 'panel']
-    popupPassthrough = switch event.type
-      when 'popupshown'  then true
-      when 'popuphidden' then false

 suppress = false
 suppressEvent = (event) ->
@@ -49,9 +40,12 @@
       # No matter what, always reset the `suppress` flag, so we don't suppress more than intended.
       suppress = false

-      # Suppress popup passthrough mode if there is no passthrough mode on the root document
-      return if popupPassthrough and !!utils.getEventRootWindow(event).document.popupNode
       return if getPref('disabled')
+
+      return unless rootWindow = utils.getEventRootWindow(event)
+      popups = rootWindow.document.querySelectorAll('menupopup, panel')
+      for popup in popups
+        return if popup.state == 'open'

       return unless window = utils.getEventCurrentTabWindow(event)
       return unless vim = vimBucket.get(window)
@@ -74,9 +68,6 @@
   # highest priority.
   keypress: suppressEvent
   keyup:    suppressEvent
-
-  popupshown:  checkPassthrough
-  popuphidden: checkPassthrough

   # When the top level window closes we should release all Vims that were
   # associated with tabs in this window


Add Storage comment.

Add Storage comment.

--- app/client/js/storage.coffee
+++ app/client/js/storage.coffee
@@ -8,6 +8,8 @@
 class app.Storage extends este.labs.Storage

   ###*
+    Storage manages stores state. Store itself should not load nor save anything
+    directly since it's used both on client and server side.
     @param {app.LocalStorage} localStorage
     @param {app.Firebase} firebase
     @param {app.Routes} routes


More borken build images

More borken build images

--- scripts/brokethebuild.coffee
+++ scripts/brokethebuild.coffee
@@ -4,7 +4,11 @@
 images = [
   "http://i.qkme.me/1rei.jpg",
   "http://troll.me/images/success-kid/broke-the-build-someone-else-fixed-it.jpg",
-  "http://www.ucalgary.ca/internphoto/files/internphoto/images/BrooksRileyHat_0.jpg"
+  "http://www.ucalgary.ca/internphoto/files/internphoto/images/BrooksRileyHat_0.jpg",
+  "http://www.finalbuilder.com/Portals/0/ArticleImages/BlogImages/penance-of-doughnuts.jpg",
+  "http://troll.me/images/the-most-interesting-man-in-the-world/doh-you-broke-the-build-my-friend.jpg",
+  "http://troll.me/images/y-u-no/the-build-why-you-broke-it.jpg",
+  "http://cdn02.cdn.thesuperficial.com/wp-content/uploads/2011/09/0907-kate-gosselin-broke-17-400x470.jpg"
 ]

 module.exports = (robot) ->


tests: fix test to not take a callback

tests: fix test to not take a callback

--- it/pingpong.coffee
+++ it/pingpong.coffee
@@ -35,7 +35,7 @@
   throw new Error('SYMPHONY_SESSIONAUTH_HOST undefined') unless process.env.SYMPHONY_SESSIONAUTH_HOST
   throw new Error('SYMPHONY_AGENT_HOST undefined') unless process.env.SYMPHONY_AGENT_HOST

-  it 'should send a message from user account and receive it on bot account', (done) ->
+  it 'should send a message from user account and receive it on bot account', () ->
     this.timeout(10000)

     # create two separate connections so we can send a message from one account to another
@@ -86,7 +86,4 @@
           .then (response) ->
             botConnection.readDatafeed(datafeedId)
       .then (response) ->
-        logger.info "Received '#{msg.message}'" for msg in response when msg.v2messageType = 'V2Message'
-        done()
-      .fail (err) ->
-        done new Error "Ping integration test failure: #{err}"
+        assert.equal('<messageML>ping</messageML>', msg.message) for msg in response when msg.v2messageType = 'V2Message'


Added axes.

Added axes.

Setting the x-axis as a "time" axis with the unit years didn't work, so I jut stuck to numbers for now.

--- src/lib/rickshaw_strip_chart.coffee
+++ src/lib/rickshaw_strip_chart.coffee
@@ -6,7 +6,6 @@
   numYears: 100
   runNumber: 0
   chart: null
-  hoverDetail: null

   constructor: ->
     @values = []
@@ -22,7 +21,13 @@
       series: @values
       min: 'auto'
     })
-    @hoverDetail = new Rickshaw.Graph.HoverDetail({
+    xAxis = new Rickshaw.Graph.Axis.X({
+      graph: @chart
+    })
+    yAxis = new Rickshaw.Graph.Axis.Y({
+      graph: @chart
+    })
+    hoverDetail = new Rickshaw.Graph.HoverDetail({
       graph: @chart
       xFormatter: (year) -> "Year #{year}"
       yFormatter: (numCranes) -> "#{Math.round(numCranes)} cranes"
@@ -33,7 +38,7 @@

   extendData: ->
     years = [@year...(@year+@numYears)]
-    offsets = years.map (x) -> Math.round(20*(Math.random()-0.65))
+    offsets = years.map (x) -> Math.round(20*(Math.random()-0.5))
     start = @numCranes
     newVals = offsets.reduce ((l, r) -> l.concat([Math.max(0, l[l.length-1]+r)])), [start]
     firstZero = newVals.indexOf(0)
@@ -50,6 +55,6 @@
     @drawChart()
     @runNumber = @runNumber + 1
     console.log("Run number #{@runNumber}")
-    setTimeout(@tick, @tickLength) if @runNumber < 250
+    setTimeout(@tick, @tickLength) if @runNumber < 100

 window.RickshawStripChart = RickshawStripChart


implement (fix) loading specific subject set

--- app/assets/javascripts/lib/fetch-subject-sets-mixin.cjsx
+++ app/assets/javascripts/lib/fetch-subject-sets-mixin.cjsx
@@ -6,16 +6,14 @@
     console.log 'PROPS.QUERY: ', @props
     # console.log "Fetch Subjects Mixin: ", @
     if @props.query.subject_set_id
-      console.log 'BLAH'
-      @fetchSubjectSet @props.params.subject_set_id, @props.workflow.id
+      console.log 'FETCHING SUBJECT SET WITH ID: ', @props.query.subject_set_id
+      @fetchSubjectSet @props.query.subject_set_id, @props.workflow.id
     else
       @fetchSubjectSets @props.workflow.id, @props.workflow.subject_fetch_limit

   fetchSubjectSet: (subject_set_id, workflow_id)->
     console.log 'fetchSubjectSet()'
-    request = API.type("subject_sets").get(subject_set_id, workflow_id: workflow_id)
-
-    console.log 'REQUEST: ', request
+    request = API.type("subject_sets").get(subject_set_id: subject_set_id, workflow_id: workflow_id)

     @setState
       subjectSet: []
@@ -23,9 +21,9 @@

     request.then (subject_set)=>
       @setState
-        subjectSets: [subject_set]
+        subjectSets: subject_set
         subject_set_index: 0
-        subject_index: 0
+        # subject_index: 0
         # currentSubjectSet: subject_set

   fetchSubjectSets: (workflow_id, limit) ->


Adds Order.all()

Adds Order.all()

--- app/assets/javascripts/sprangular/services/orders.coffee
+++ app/assets/javascripts/sprangular/services/orders.coffee
@@ -6,4 +6,9 @@
         .then (response) ->
           Sprangular.extend(response.data, Sprangular.Order)

+    all: ->
+      $http.get("/api/orders")
+        .then (response) ->
+          Sprangular.extend(response.data, Sprangular.Order)
+
   service


fix new list creation

fix new list creation

--- app/controllers/lists.coffee
+++ app/controllers/lists.coffee
@@ -49,20 +49,20 @@
       $("header .search input").val("").trigger "keyup"

   new: (e) ->
-    val = @input.val()

     # Handles correct box
     if $(e.currentTarget).hasClass("searcher")
-      $("header .search input").val(val).trigger "keyup"
+      $("header .search input").val($(e.currentTarget).val()).trigger "keyup"

       # Overides the other function thingy
       if val.length is 0
         $(e.currentTarget).focus()
     else
-      if e.which is Keys.ENTER and val
-        list = List.create name: val
+      if e.which is Keys.ENTER and $(e.currentTarget).val()
+        console.log " helloo"
+        list = List.create name: $(e.currentTarget).val()
         List.trigger "changeList", list
-        @input.val ""
+        $(e.currentTarget).val ""

   addOne: (list) =>
     return if list.id is "inbox"


flux/teams: add getter to add pending invitations to teammates

--- client/app/lib/flux/teams/getters.coffee
+++ client/app/lib/flux/teams/getters.coffee
@@ -32,13 +32,30 @@
 isValidMemberValue = (member, value) ->
   re = new RegExp(value, 'i')

-  re.test(member.get('profile').get('email')) or \
-  re.test(member.get('profile').get('firstname')) or \
-  re.test(member.get('profile').get('lastname'))
+  if member.get('status') is 'pending'
+    re.test(member.get('email')) or \
+    re.test(member.get('firstname')) or \
+    re.test(member.get('lastname'))
+  else
+    re.test(member.get('profile').get('email')) or \
+    re.test(member.get('profile').get('firstname')) or \
+    re.test(member.get('profile').get('lastname'))
+
+
+membersWithPendingInvitations = [
+  membersWithRole
+  pendingInvitations
+  (members, pendingMembers) ->
+
+    pendingMembers.toArray().forEach (invitation) ->
+      members = members.set invitation.get('_id'), invitation
+
+    return members
+]

 filteredMembersWithRole = [
-  membersWithRole
+  membersWithPendingInvitations
   searchInputValue
   (members, value) ->
     return members  if value is ''


addes some todos

addes some todos

--- test.coffee
+++ test.coffee
@@ -6,6 +6,7 @@
 # yields
 # properly formatted return statement
 # validate that expectation is an array (root and on_value)
+# Better error message on unexpected args (list possibles)

 describe 'create_mock', ->


moved graphs to bottom row

moved graphs to bottom row

--- client/components/gateblu-dashboard.cjsx
+++ client/components/gateblu-dashboard.cjsx
@@ -59,14 +59,14 @@
         successPercentage={@state.successPercentage}
         total={@state.total} />

+      <AvgElapsedTimeGauge
+        title="Gateblu Add Device Average Time"
+        avgElapsedTime={@state.avgElapsedTime} />
+
       <OverTimeGauge
         title="Gateblu Add Device Success Over Time"
         suffix="%"
         elapsedTimeChartData={@state.gatebluAddDeviceSuccessOverTime} />
-
-      <AvgElapsedTimeGauge
-        title="Gateblu Add Device Average Time"
-        avgElapsedTime={@state.avgElapsedTime} />

       <OverTimeGauge
         title="Gateblu Add Device Average Time Over Time"


Remove runp from the suggestions

Remove runp from the suggestions

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -94,7 +94,7 @@
             for group in data['sections']
                 for command in group['commands']
                     tag = command['langs']['js']['name']
-                    if tag is '()'
+                    if tag is '()' or tag is 'runp'
                         continue
                     if tag is 'row' # We want r.row and r.row(
                         new_command = DataUtils.deep_copy command


Fix comment for performance

Fix comment for performance

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -609,7 +609,7 @@
             multiple_line_comment: /^(\s)*\/\*[^]*\*\//
             get_first_non_white_char: /\s*(\S+)/
             last_char_is_white: /.*(\s+)$/
-        stop_char: # Just for performance and cleaner code
+        stop_char: # Just for performance (we look for a stop_char in constant time - which is better than having 3 and conditions) and cleaner code
             opening:
                 '(': true
                 '{': true


Hook up IA biblio

Hook up IA biblio

--- js/book-aligner.coffee
+++ js/book-aligner.coffee
@@ -73,6 +73,18 @@
   ht_biblio_query(ht_id)
   console.log ht_id

+ia_biblio_query = (ia_id) ->
+  $.ajax "https://archive.org/metadata/#{ia_id}",
+    type: 'GET'
+    cache: true
+    dataType: 'json'
+    crossDomain: true
+    error: (jqXHR, textStatus, errorThrown) ->
+      console.log "AJAX Error: #{textStatus}"
+    success: (data) ->
+      console.log data
+      $("##{html_id(ia_id)}").append($('<span/>').text(" - #{data.metadata.title}, #{data.metadata.year}, v.#{data.metadata.volume}"))
+
 process_ia = (identifier_string) ->
   console.log 'process_ia'
   match = identifier_string.match(IA_REGEX)
@@ -91,6 +103,7 @@
 process_ia_id = (ia_id) ->
   ia_link = $('<a/>',{href: ia_url(ia_id),target: '_blank'}).text(ia_id)
   $('#results_list').append($('<li/>', {id: html_id(ia_id)}).append(ia_link))
+  ia_biblio_query(ia_id)
   console.log ia_id

 process_gb = (identifier_string) ->


disabled non working listener until we find the underlying problem

--- client/app/MainApp/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/app/MainApp/avatararea/avatarareagroupswitcherpopup.coffee
@@ -21,7 +21,7 @@
     @_popupListPending.on 'PendingCountDecreased', @bound 'decreasePendingCount'
     @_popupListPending.on 'UpdateGroupList',       @bound 'populateGroups'
     # does not work
-    KD.whoami().on        'NewPendingInvitation',  @bound 'populatePendingGroups'
+    # KD.whoami().on        'NewPendingInvitation',  @bound 'populatePendingGroups'

     @listControllerPending = new KDListViewController
       view                : @_popupListPending


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -41,7 +41,7 @@

     parsedColor = null

-    parsedColor = parseHex(color) || parseRGB(color)
+    parsedColor =  parseRGB(color) || parseHex(color)

     alpha = parsedColor[3]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -16,7 +16,7 @@
     hexString = hexString.replace(/#/, '')

     switch hexString.length
-      when 3, 4 then function() {}
+      when 3, 4 then
       else
         return [
           parseInt(hexString.substr(0, 2), 16)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -43,6 +43,8 @@
     if arguments.length == 2
       if Object.prototype.toString.call(arguments[0]) == '[object Array]'
         parsedColor = [parseInt(arguments[0][0]), parseInt(arguments[0][1]), parseInt(arguments[0][2]), parseFloat(arguments[1])]
+      else if Object.prototype.toString.call(arguments[0]) != '[object Array]'
+        parsedColor = [parseHex(arguments[0]) || parseRGB(arguments[0], arguments[1]]
     else if arguments.length > 2
       alpha = if arguments[3]? then arguments[3] else 1
       parsedColor = [parseInt(arguments[0]), parseInt(arguments[1]), parseInt(arguments[2]), parseFloat(alpha)]


removed unused helper functions

removed unused helper functions

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -12,29 +12,6 @@
 @extend = extend = (x, extraProperties) ->
   for k, v of extraProperties then x[k] = v
   x
-
-# Helpers for the APL data model
-@prototypeOf = prototypeOf = (x) ->
-  if typeof x is 'number' then 0
-  else if typeof x is 'string' then ' '
-  else if x.aplPrototype? then x.aplPrototype
-  else if isSimple(x) or not x.length then 0
-  else if isSimple x[0] then prototypeOf x[0]
-  else
-    p = prototypeOf x[0]
-    withShape shapeOf(x[0]), (for [0...x[0].length] then p)
-
-@withPrototype = withPrototype = (p, x) ->
-  if (x instanceof Array) and (not x.length) and (p isnt 0)
-    x.aplPrototype = p
-  x
-
-@withPrototypeCopiedFrom = (y, x) ->
-  if x instanceof Array and not x.length
-    withPrototype prototypeOf(y), x
-  x
-
-

 # Sum and product;  I wish JavaScript had a _reduce_ operator :)
 @sum = (xs) -> r = 0; (for x in xs then r += x); r


relocate login notification

relocate login notification

--- app/assets/javascripts/blabbr.js.coffee
+++ app/assets/javascripts/blabbr.js.coffee
@@ -134,11 +134,15 @@
       console.log 'TODO'

   Blabbr.run = () ->
-    $.getJSON '/users/current.json', (data) ->
-      Blabbr.current_user = data
+    $.getJSON '/users/current.json', (user) ->
+      Blabbr.current_user = user
       app.run()

   $(->
+    notice = $('#flash_notice')
+    $.blabbrNotify 'success', notice.text() if notice.text()
+    notice.remove()
+
     $('html').mouseover ->
       Blabbr.is_active = true


Fix conflicto con alt + cursor

Fix conflicto con alt + cursor

--- app/assets/javascripts/cartas.js.coffee
+++ app/assets/javascripts/cartas.js.coffee
@@ -17,10 +17,11 @@
 $(document)
   .on 'keydown', (tecla) ->
     Turbolinks.visit((
-      switch tecla.which
-        when 37 then $('#anterior')
-        when 39 then $('#siguiente')
-      ).attr('href'))
+      unless tecla.altKey
+        switch tecla.which
+          when 37 then $('#anterior')
+          when 39 then $('#siguiente')
+        ).attr('href'))

 jQuery ->
   bindearTodo()