changed error log message

changed error log message

--- app.coffee
+++ app.coffee
@@ -43,7 +43,12 @@
 			url:req.url
 			key: req.key
 			statusCode: req.statusCode
-		err.domainEmitter.res = "to big to log"
+		err =
+			message: err.message
+			stack: err.stack
+			name: err.name
+			type: err.type
+			arguments: err.arguments
 		logger.err err:err, req:req, res:res, "uncaught exception thrown on request"
 	requestDomain.run next


Init metrics at top of app.coffee

Init metrics at top of app.coffee

--- app.coffee
+++ app.coffee
@@ -1,3 +1,6 @@
+Metrics = require "metrics-sharelatex"
+Metrics.initialize("clsi")
+
 CompileController = require "./app/js/CompileController"
 Settings = require "settings-sharelatex"
 logger = require "logger-sharelatex"
@@ -12,8 +15,7 @@
 Path = require "path"
 fs = require "fs"

-Metrics = require "metrics-sharelatex"
-Metrics.initialize("clsi")
+
 Metrics.open_sockets.monitor(logger)
 Metrics.memory.monitor(logger)


Re-enable secure sockets.

Re-enable secure sockets.

--- app.coffee
+++ app.coffee
@@ -75,22 +75,20 @@

     console.log "=> Node.js cloudsdale-faye started on ws://#{config.faye.host}:#{config.faye.port}#{config.faye.path} (socket)"

-
-    faye.listen 9292
-
   else
     # Start listening to the faye server port.
     faye.listen config.faye.port
     console.log "=> Node.js cloudsdale-faye started on ws://#{config.faye.host}:#{config.faye.port}#{config.faye.path} (port)"

-  # if app_env == "production"
-  #   # Start listening to the secure port using SSL.
-  #   fs.unlink config.faye.socket, ->
-  #     faye.listen config.faye.socketSecure,
-  #       key: config.sslKey
-  #       cert: config.sslCert
+  if app_env == "production"
+    # Start listening to the secure port using SSL.
+    oldmask = process.umask(0o0000)
+    faye.listen config.faye.socketSecure,
+      key: config.sslKey
+      cert: config.sslCert
+    process.umask(oldmask)

-  #   console.log "=> Node.js cloudsdale-faye-ssl started on wss://#{config.faye.host}:#{config.faye.secure_port}#{config.faye.path} (socket)"
+    console.log "=> Node.js cloudsdale-faye-ssl started on wss://#{config.faye.host}:#{config.faye.secure_port}#{config.faye.path} (socket)"

   # Get the faye client
   global.fayeCli = faye.getClient()


request restart on unhandled exceptions to avoid memory leak

--- app.coffee
+++ app.coffee
@@ -37,6 +37,8 @@
 	requestDomain.add req
 	requestDomain.add res
 	requestDomain.on "error", (err)->
+		# request a shutdown to prevent memory leaks
+		appIsOk = false
 		setTimeout(->
 			if !res.headerSent
 				res.send(500)


redis

redis

--- app.coffee
+++ app.coffee
@@ -12,10 +12,8 @@
 ejs = require 'ejs'
 CONFIG = require('./config').CONFIG
 server = require('http').Server(app)
-Redis = require('ioredis')
-redis = new Redis(process.env.REDIS_URL)
 app.use session {
-  store: new RedisStore client: redis
+  store: new RedisStore client: client
   prefix: CONFIG?.DB?.REDIS?.PREFIX + 'sess:'
   key: CONFIG.EXPRESS.SESSION.KEY
   secret: CONFIG.EXPRESS.SESSION.SECRET


get right of console.log

get right of console.log

--- coach/src/exercise/index.cjsx
+++ coach/src/exercise/index.cjsx
@@ -45,7 +45,7 @@
     if not sections?.length then return

     section = _.first(sections)
-    console.info(section)
+
     <div key='task-help-links' className='task-help-links'>
       Comes from {@sectionFormat(section.chapter_section)} - {section.title}
     </div>


fixed paths to work on windows

fixed paths to work on windows

--- lib/nap.coffee
+++ lib/nap.coffee
@@ -27,13 +27,17 @@
   @assets = options.assets
   unless @assets?
     throw new Error "You must specify an 'assets' obj with keys 'js', 'css', or 'jst'"
+  appDir = process.cwd().replace(/\\/g, "\/")
   for key, obj of @assets
-    for pkg, files of @assets[key]
-      files = (glob.sync("#{process.cwd()}/#{file}") for file in files)
-      files = _.uniq _.flatten files
-      files = (file.replace(process.cwd(), '').replace(/^\//, '') for file in files)
-      @assets[key][pkg] = files
-
+    for pkg, patterns of @assets[key]
+      matches = []
+      for pattern in patterns
+        fnd = glob.sync path.resolve("#{appDir}/#{pattern}").replace(/\\/g, "\/")
+        matches = matches.concat(fnd)
+      matches = _.uniq _.flatten matches
+      matches = (file.replace(appDir, '').replace(/^\//, '') for file in matches)
+      @assets[key][pkg] = matches
+
   # Config defaults
   @publicDir = options.publicDir ? '/public'
   @mode = options.mode ? do ->
@@ -172,36 +176,38 @@
     return

   @usingMiddleware = true
-
-  if path.extname(req.url) is '.css'
-    res.setHeader?("Content-Type", "text/css")
-    for pkg, filenames of @assets.css
-      for filename in filenames
-        if req.url.replace(/^\/assets\/|.(?!.*\.).*/g, '') is filename.replace(/.(?!.*\.).*/, '')
-          contents = fs.readFileSync(process.cwd() + '/' + filename).toString()
-          contents = preprocess contents, filename
-          res.end contents
-          return
-
-  if path.extname(req.url) is '.js'
-    res.setHeader?("Content-Type", "application/javascript")
-
-    if req.url.match /\.jst\.js$/
-      pkg = path.basename req.url, '.jst.js'
-      res.end generateJSTs pkg
-      return
-
-    if req.url.match /nap-templates-prefix\.js$/
-      res.end @_tmplPrefix
-      return
-
-    for pkg, filenames of @assets.js
-      for filename in filenames
-        if req.url.replace(/^\/assets\/|.(?!.*\.).*/g, '') is filename.replace(/.(?!.*\.).*/, '')
-          contents = fs.readFileSync(process.cwd() + '/' + filename).toString()
-          contents = preprocess contents, filename
-          res.end contents
-          return
+
+  switch path.extname req.url
+
+    when '.css'
+      res.setHeader?("Content-Type", "text/css")
+      for pkg, filenames of @assets.css
+        for filename in filenames
+          if req.url.replace(/^\/assets\/|.(?!.*\.).*/g, '') is filename.replace(/.(?!.*\.).*/, '')
+            contents = fs.readFileSync(path.resolve process.cwd() + '/' + filename).toString()
+            contents = preprocess contents, filename
+            res.end contents
+            return
+
+    when '.js'
+      res.setHeader?("Content-Type", "application/javascript")
+
+      if req.url.match /\.jst\.js$/
+        pkg = path.basename req.url, '.jst.js'
+        res.end generateJSTs pkg
+        return
+
+      if req.url.match /nap-templates-prefix\.js$/
+        res.end @_tmplPrefix
+        return
+
+      for pkg, filenames of @assets.js
+        for filename in filenames
+          if req.url.replace(/^\/assets\/|.(?!.*\.).*/g, '') is filename.replace(/.(?!.*\.).*/, '')
+            contents = fs.readFileSync(path.resolve process.cwd() + '/' + filename).toString()
+            contents = preprocess contents, filename
+            res.end contents
+            return

   next()

@@ -242,7 +248,7 @@
   for filename in @assets.jst[pkg]

     # Read the file and compile it into a javascript function string
-    contents = fs.readFileSync(process.cwd() + '/' + filename).toString()
+    contents = fs.readFileSync(path.resolve process.cwd() + '/' + filename).toString()
     ext = path.extname filename
     contents = if templateParsers[ext]? then templateParsers[ext](contents, filename) else contents

@@ -277,7 +283,7 @@
   obj = {}

   for filename in @assets[type][pkg]
-    contents = fs.readFileSync(process.cwd() + '/' + filename).toString()
+    contents = fs.readFileSync(path.resolve process.cwd() + '/' + filename).toString()
     contents = preprocess contents, filename

     outputFilename = filename.replace /\.[^.]*$/, '' + '.' + type
@@ -359,7 +365,7 @@

     if mime?
       if path.existsSync filename
-        base64Str = fs.readFileSync(filename).toString('base64')
+        base64Str = fs.readFileSync(path.resolve filename).toString('base64')

         newUrl = "data:#{mime};base64,#{base64Str}"
         contents = _.splice(contents, start, end - start, newUrl)
@@ -383,7 +389,7 @@
   file = "#{process.cwd() + @_outputDir + '/'}#{filename}"
   ext = if _.endsWith filename, '.js' then '.jgz' else '.cgz'
   exec "gzip #{file}", (err, stdout, stderr) ->
-    console.log stderr if stderr?
+    console.log stderr if stderr
     fs.renameSync file + '.gz', file + ext
     writeFile filename, contents
     callback()


Update cards-es.coffee

Update cards-es.coffee

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -1022,7 +1022,7 @@
             name: "Especialista de Sienar"
         'Viktor Hel':
             ship: "Caza Kihraxz"
-            text: '''After defending, if you did not roll exactly 2 defense dice, the attacker receives 1 stress token.'''
+            text: '''Después de que te defiendas, si la tirada que realizaste no consistió exactamente en 2 dados de defensa, el atacante recibe 1 ficha de Tensión.'''
         'Lowhhrick':
             ship: "Cañonera Auzituck"
             text: '''Cuando otra nave aliada que tengas a alcance 1 se esté defendiendo, puedes gastar 1 ficha de Refuerzo. Si lo haces, el defensor añade 1 resultado %EVADE%.'''
@@ -1054,27 +1054,40 @@
             text: '''Cuando sueltes una bomba, puedes utilizar la plantilla de maniobra (%TURNLEFT% 1) o (%TURNRIGHT% 1) en vez de la plantilla de (%STRAIGHT% 1).'''
         'Dalan Oberos':
             ship: 'Víbora Estelar'
-            text: '''If you are not stressed, when you reveal a turn, bank, or Segnor's Loop maneuver, you may instead treat it as a red Tallon Roll maneuver of the same direction (left or right) using the template of the original revealed maneuver.'''
+            text: '''Si no estás bajo tensión, cuando reveles una maniobra de giro, inclinación o giro de Segnor, puedes ejecutarla como si fuera una maniobra roja de giro Tallon con la misma dirección (izquierda o derecha) utilizando la plantilla de maniobra revelada originalmente.'''
         'Thweek':
             ship: 'Víbora Estelar'
-            text: '''During setup, before the "Place Forces" step, you may choose 1 enemy ship and assign the "Shadowed" or "Mimicked" Condition card to it.'''
+            text: '''Durante la preparación de la partida, antes del paso "Desplegar fuerzas", puedes elegir 1 nave enemiga y asignarle la carta de Estado "Vigilado" o "Imitado".'''
         'Captain Jostero':
+            name: "Capitán Jostero"
             ship: "Caza Kihraxz"
-            text: '''Once per round, after an enemy ship that is not defending against an attack suffers damage or critical damage, you may perform an attack against that ship.'''
+            text: '''Una vez por ronda, después de que una nave enemiga que no se está defendiendo contra un ataque sufra daño normal o daño crítico, puedes efectuar un ataque contra esa nave.'''
         'Major Vynder':
-            text: '''When defending, if you have a weapons disabled token, roll 1 additional defense die.'''
-        'Karthakk Pirate':
-            name: "Pirata de Karthakk"
-            ship: "Bombardero Scurrg H-6"
-        'Karthakk Pirate':
-            name: "Pirata de Karthakk"
-            ship: "Bombardero Scurrg H-6"
+            ship: "Ala Estelar clase Alfa"
+            name: "Mayor Vynder"
+            text: '''Cuando te defiendas, si tienes asignada una ficha de Armas bloqueadas, tira 1 dado de defensa adicional.'''
+        'Nu Squadron Pilot':
+            name: "Piloto del Escuadrón Nu"
+            ship: "Ala Estelar clase Alfa"
+        'Rho Squadron Veteran':
+            name: "Veterano del Escuadrón Rho"
+            ship: "Ala Estelar clase Alfa"
         'Lieutenant Karsabi':
-            text: '''When you receive a weapons disabled token, if you are not stressed, you may receive 1 stress token to remove it.'''
+            ship: "Ala Estelar clase Alfa"
+            name: "Teniente Karsabi"
+            text: '''Cuando recibas una ficha de Armas bloqueadas, si no estás bajo tensión, puedes recibir 1 ficha de Tensión para retirar esa ficha de Armas bloqueadas.'''
+        'Cartel Brute':
+            name: "Secuaz del Cártel"
+            ship: "Caza M12-L Kimogila"
+        'Cartel Executioner':
+            name: "Verdugo del Cártel"
+            ship: "Caza M12-L Kimogila"
         'Torani Kulda':
-            text: '''After you perform an attack, each enemy ship inside your bullseye firing arc at Range 1-3 must choose to suffer 1 damage or remove all of its focus and evade tokens.'''
+            ship: "Caza M12-L Kimogila"
+            text: '''Después de que efectúes un ataque, toda nave enemiga que tengas a alcance 1-3 y esté situada dentro de tu arco de fuego centrado debe elegir entre sufrir 1 daño o retirar todas sus fichas de Concrentración y Evasión.'''
         'Dalan Oberos (Kimogila)':
-            text: '''At the start of the Combat phase, you may acquire a target lock on an enemy ship inside your bullseye firing arc at Range 1-3 .'''
+            ship: "Caza M12-L Kimogila"
+            text: '''Al comienzo de la fase de Combate, puedes fijar como blanco una nave enemiga que tengas a alcance 1-3 y esté situada dentro de tu arco de fuego centrado.'''
         'Fenn Rau (Sheathipede)':
             ship: "Lanzadera clase Sheathipede"
             text: '''Cuando una nave enemiga que tengas a alcance 1-3 y esté situada dentro de tu arco de fuego pase a ser la nave activa durante la fase de Combate, si no estás bajo tensión, puedes recibir 1 ficha de Tensión. Si lo haces, esa nave no podrá gastar fichas para modificar sus dados cuando ataque en esta ronda.'''
@@ -1087,7 +1100,7 @@
         'AP-5':
             ship: "Lanzadera clase Sheathipede"
             text: '''Cuando realices la acción de coordinación, después de que elijas a una nave aliada y antes de que esa nave realice su acción gratuita, puedes recibir 2 fichas de Tensión para quitarle 1 ficha de Tensión a esa nave.'''
-        'Crimson Squadron Pilot ':
+        'Crimson Squadron Pilot':
             name: "Piloto del Escuadrón Carmesí"
             ship: "Bombardero B/SF-17"
         '"Crimson Leader"':
@@ -1102,10 +1115,19 @@
             name: '"Jefe Cobalto"'
             ship: "Bombardero B/SF-17"
             text: '''Cuando ataques, si el defensor está situado a alcance 1 de una ficha de Bomba, el defensor tira 1 dado de defensa menos (hasta un mínimo de 0).'''
+        'Sienar-Jaemus Analyst':
+            name: "Analista de Sienar-Jaemus"
+            ship: "Silenciador TIE"
+        'First Order Test Pilot':
+            name: "Piloto de pruebas de la Primera Orden"
+            ship: "Silenciador TIE"
         'Kylo Ren (TIE Silencer)':
-            text: '''The first time you are hit by an attack each round, deal the "I'll Show You the Dark Side" Condition card to the attacker.'''
+            name: "Kylo Ren (Silenciador TIE)"
+            ship: "Silenciador TIE"
+            text: '''La primera vez que seas impactado por un ataque en cada ronda, asigna la carta de Estado "Yo te mostraré el Lado Oscuro" al atacante.'''
         'Test Pilot "Blackout"':
-            text: '''When attacking, if the attack is obstructed, the defender rolls 2 fewer defense dice (to a minimum of 0).'''
+            name: 'Piloto de pruebas "Apagón"'
+            text: '''Cuando ataques, si el ataque está obstruido, el defensor tira 2 dados de defensa menos (hasta un mínimo de 0).'''

     upgrade_translations =
         "Ion Cannon Turret":
@@ -1779,9 +1801,11 @@
             name: "Misiles de crucero"
             text: '''Ataque (Blanco Fijado): Descarta esta carta para efectuar este ataque. %LINEBREAK%Puedes tirar tantos dados de ataque adicionales como la velocidad de la maniobra que has ejecutado en esta ronda, hasta un máximo de 4 dados adicionales.'''
         'Ion Dischargers':
-            text: '''After you receive an ion token, you may choose an enemy ship at Range 1.  If you do, remove that ion token. Then that ship may choose to receive 1 ion token. If it does, discard this card.'''
+            name: "Descargadores de iones"
+            text: '''Después de que recibas una ficha de Iones, puedes elegir una nave enemiga que tengas a alcance 1. Si lo haces retira esa ficha de Iones. A continuación, esa nave puede elegir recibir 1 ficha de Iones. Si lo hace, descarta esta carta.'''
         'Harpoon Missiles':
-            text: '''Attack (target lock): Discard this card to perform this attack.%LINEBREAK%If this attack hits, assign the "Harpooned!" Condition to the defender.'''
+            name: "Misiles arpón"
+            text: '''Ataque (Blanco fijado): Descarta esta carta para efcetuar este ataque.%LINEBREAK%Si este ataque impacta, después de resolver el ataque, asigna el estado "¡Arponeado!" al defensor.'''
         'Ordnance Silos':
             name: "Silos de munición"
             ship: "Bombardero B/SF-17"
@@ -1790,13 +1814,17 @@
             name: "Simulador de trayectorias"
             text: '''Puedes lanzar bombas utilizando la plantilla de maniobra (%STRAIGHT% 5) en vez de soltarlas. No puedes lanzar de esta manera bombas que tengan el encabezado "Acción:".'''
         'Jamming Beam':
-            text: '''Attack: Attack 1 ship.%LINEBREAK%If this attack hits, assign the defender 1 jam token.  Then cancel all dice results.'''
+            name: "Haz de interferencias"
+            text: '''Ataque: Ataca a 1 nave.%LINEBREAK%Si este ataque impacta, asígnale al defensor 1 ficha de Interferencia. Luego se anulan todos los resultados de los dados.'''
         'Linked Battery':
-            text: '''%SMALLSHIPONLY%%LINEBREAK%When attacking with a primary or %CANNON% secondary weapon, you may reroll 1 attack die.'''
+            name: "Batería enlazada"
+            text: '''%SMALLSHIPONLY%%LINEBREAK%Cuando ataques con tu armament principal o con un armamento secundario %CANNON%, puedes volver a tirar 1 dado de ataque.'''
         'Saturation Salvo':
-            text: '''After you perform an attack with a %TORPEDO% or %MISSILE% secondary weapon that does not hit, each ship at Range 1 of the defender with an agility value lower than the squad point cost of the %TORPEDO% or %MISSILE% Upgrade card must roll 1 attack die and suffer any damage (%HIT%) or critical damage (%CRIT%) rolled.'''
+            name: "Andanada de saturación"
+            text: '''Después de que efectúes un ataque con un armamento secundario %TORPEDO% o %MISSILE% que no impacte, toda nave que esté situada a alcance 1 del defensor y que tenga una puntuación de Agilidad inferior al coste en puntos de escuadrón de la carta de Meora %TORPEDO% o %MISSILE% debe tirar 1 dado de ataque y sufrir cualquier daño normal (%HIT%) o daño crítico (%CRIT%) obtenido.'''
         'Contraband Cybernetics':
-            text: '''When you become the active ship during the Activation phase, you may discard this card and receive 1 stress token. If you do, until the end of the round, you may perform actions and red maneuvers even while you are stressed.'''
+            name: "Ciberimplantes ilícitos"
+            text: '''Cuando pases a ser la nave activa durante la fase de Activación, puedes descartar esta carta y recibir 1 ficha de Tensión. Si lo haces, hasta el final de la ornda, puedes llevar a cabo acciones y maniobras rojas incluso aunque estés bajo tensión.'''
         'Maul':
             text: '''%SCUMONLY% Ignora esta restricción si tu escuadrón contiene a "Ezra Bridger."%LINEBREAK%Cuando ataques, si no estás bajo tensión, puedes recibir cualquier cantidad de fichas de Tensión para volver a tirar esa misma cantidad de dados de ataque.%LINEBREAK% Después de que realices un ataque que impacte, puedes retirar 1 de tus fichas de Tensión.'''
         'Courier Droid':
@@ -1808,15 +1836,19 @@
             name: "Droide astromecánico de ayuda al pilotaje"
             text: '''No puedes efectuar ataques ataques contra naves que estén situadas fuera de tu arco de fuego.%LINEBREAK%Después de que ejecutes una maniobra, si no te has solapado con una nave ni con un obstáculo, y no tienes ninguna nave enemiga a alcance 1-3 situada dentro de tu arco de fuego, puedes realizar una acción gratuita de impulso o tonel volado.'''
         'Advanced Optics':
-            text: '''You cannot have more than 1 focus token.%LINEBREAK%During the End phase, do not remove an unused focus token from your ship.'''
+            name: "Sensores ópticos avanzados"
+            text: '''No puedes tener más de 1 ficha de Concentración.%LINEBREAK%Durante la fase Final, no retires de tu nave las fichas de Concentración que no hayas usado.'''
         'Scrambler Missiles':
-            text: '''Attack (target lock): Discard this card to perform this attack.%LINEBREAK%If this attack hits, the defender and each other ship at Range 1 receives 1 jam token.  Then cancel all results.'''
+            name: "Misiles interferidores"
+            text: '''Ataque (Blanco fijado): Descarta esta carta para efectuar este ataque.%LINEBREAK%Si este ataque impacta, el defensor y toda otra nave que tenga a alcance 1 recibe 1 ficha de Interferencia. Luego se anulan todos los resultados de dados.'''
         'R5-TK':
-            text: '''You can acquire target locks on friendly ships.%LINEBREAK%You can attack friendly ships.'''
+            text: '''Puedes fijar como blanco naves aliadas.%LINEBREAK%Puedes efectuar ataques contra naves aliadas.'''
         'Threat Tracker':
-            text: '''%SMALLSHIPONLY%%LINEBREAK%When an enemy ship inside your firing arc at Range 1-2 becomes thee active ship during the Combat phase, you may spend your target lock on that ship to perform a free boost or barrell roll action if that action is on your action bar.'''
+            name: "Rastreador de amenazas"
+            text: '''%SMALLSHIPONLY%%LINEBREAK%Cuando una nave enemiga que tengas a alcance 1-2 y dentro de tu arco de fuego se convierta en la nave activa durante la fase de Combate, puedes gastar la ficha de Blanco fijado que tengas sobre esa nave para realizar una acción gratuita de impulso o tonel volado, si esa acción figura en tu barra de acciones.'''
         'Debris Gambit':
-            text: '''%SMALLSHIPONLY%%LINEBREAK%Action: Assign 1 evade token to your ship for each obstacle at Range 1, to a maximum of 2 evade tokens.'''
+            name: "Treta de los desechos"
+            text: '''%SMALLSHIPONLY%%LINEBREAK%Acción: Asigna 1 ficha de Evasión a tu nave por cada obstáculo que tengas a alcance 1, hast aun máximo de 2 fichas de Evasión.'''

     modification_translations =
         "Stealth Device":
@@ -2113,14 +2145,19 @@
             text: '''Sólo Bombarder Scurrg H-6.%LINEBREAK%Tu barra de mejoras gana los iconos %SYSTEM% y %SALVAGEDASTROMECH% y pierde el icono %CREW%.%LINEBREAK%No puedes equiparte con cartas de mejora %SALVAGEDASTROMECH% que no sean únicas.'''
         'Vaksai':
             ship: "Caza Kihraxz"
-            text: '''Kihraxz Fighter only.%LINEBREAK%The squad point cost of each of your equipped upgrades is reduced by 1 (to a minimum of 0).%LINEBREAK%You may equip up to 3 different Modification upgrades.'''
+            text: '''Sólo Caza Kihraxz.%LINEBREAK%El coste en puntos de escuadrón de cada una de tus mejoras equipadas se reduce en 1 (hast aun mínimo de 0).%LINEBREAK%Puedes equipar un máximo de 3 mejoras distintas de Modificación.'''
         'StarViper Mk. II':
+            name: "Víbora Estelar modelo II"
             ship: "Víbora Estelar"
-            text: '''StarViper only.%LINEBREAK%You may equip up to 2 different title Upgrades.%LINEBREAK%When performing a barrel roll action, you must use the (%BANKLEFT% 1) or (%BANKRIGHT% 1) template instead of the (%STRAIGHT% 1) template.'''
+            text: '''Sólo Víbora Estelar.%LINEBREAK%Puedes equipar un máximo de 2 mejoras distintas de Título.%LINEBREAK%Cuando realices una acción de tonel volado debes utilizar la plantilla (%BANKLEFT% 1) o (%BANKRIGHT% 1) en vez de la plantilla (%STRAIGHT% 1).'''
         'XG-1 Assault Configuration':
-            text: '''Alpha-class Star Wing only.%LINEBREAK%Your upgrade bar gains 2 %CANNON% icons.%LINEBREAK%You may perform attacks with %CANNON% secondary weapons that cost 2 or fewer points even while you have a weapons disabled token.'''
+            ship: "Ala Estelar clase Alfa"
+            name: "Configuración de asalto Xg-1
+            text: '''Sólo Ala Estelar clase Alfa.%LINEBREAK%Tu barra de mejoras gana 2 iconos %CANNON%.%LINEBREAK%Puedes efectuar ataques con sistemas de armamento secundario %CANNON% con un coste igual o inferior a 2 puntos de escuadrón incluso aunque tengas asignada una ficha de Armas bloqueadas.'''
         'Enforcer':
-            text: '''M12-L Kimogila Fighter only.%LINEBREAK%After defending, if the attacker is inside your bullseye firing arc, the attacker receives 1 stress token.'''
+            name: "Brazo Ejecutor"
+            ship: "Caza M12-L Kimogila"
+            text: '''Sólo Caza M12-L Kimogila.%LINEBREAK%Después de que te defiendas, si el atacante está situado dentro de tu arco de fuego centrado, recibe 1 fciha de Tensión.'''
         'Ghost (Phantom II)':
             name: "Espíritu (Fantasma II)"
             text: '''Sólo VCX-100.%LINEBREAK%Equipa la carta de Título Fantasma II a una Lanzadera clase Sheathipede aliada y acóplala a esta nave.%LINEBREAK%Después de que ejecutes una maniobra, puedes desplegar la nave que tienes acoplada desde los salientes de la parte trasera de tu peana.'''
@@ -2129,9 +2166,13 @@
             ship: "Lanzadera clase Sheathipede"
             text: '''Mientras estés acoplado, el Espíritu puede efectuar ataques con su armamento principal desde su arco de fuego especial.%LINEBREAK%Mientras estés acoplado, al final de la fase de Activación, el Espíritu puede efectuar una acción gratuita de coordinación.'''
         'First Order Vanguard':
-            text: '''TIE Silencer only.%LINEBREAK%When attacking, if the defender is the only ship in your firing arc at Range 1-3, you may reroll 1 attack die.%LINEBREAK%When defending, you may discard this card to reroll all of your defense dice.'''
+            name: "Vanguardia de la Primera Orden"
+            ship: "Silenciador TIE"
+            text: '''Sólo Silenciador TIE.%LINEBREAK%Cuando ataques, si el defensor es la única nave que tienes dentro de tu arco de fuego a alcance 1-3, puedes volver a tirar 1 dado de ataque.%LINEBREAK%Cuando te defiendas, puedes descartar esta carta para volver a tirar todos tus dados de defensa.'''
         'Os-1 Arsenal Loadout':
-            text: '''Alpha-class Star Wing only.%LINEBREAK%Your upgrade bar gains the %TORPEDO% and %MISSILE% icons.%LINEBREAK%You may perform attacks with %TORPEDO% and %MISSILE% secondary weapons against ships you have locked even while you have a weapons disabled token.'''
+            name: "Configuración de Arsenal Os-1"
+            ship: "Ala Estelar clase Alfa"
+            text: '''Sólo Ala Estelar clase Alfa.%LINEBREAK%Tu barra de mejoras gana los iconos %TORPEDO% y %MISSILE%.%LINEBREAK%Puedes efectuar ataques con sistemas de armamento secundario %TORPEDO% y %MISSILE% contra naves que hayas fijado como blanco incluso aunque tengas asignada una ficha de Armas bloqueadas.'''
         'Crossfire Formation':
             name: "Formación de fuego cruzado"
             ship: "Bombardero B/SF-17"
@@ -2151,11 +2192,14 @@
             name: "Una deuda por saldar"
             text: '''Cuando ataques a una nave que tiene la carta de Mejora "Una cuenta pendiente", puedes cambiar 1 resultado %FOCUS% por un resultado %CRIT%.'''
         'Shadowed':
-            text: '''"Thweek" is treated as having the pilot skill value you had after setup.%LINEBREAK%The pilot skill value of "Thweek" does not change if your pilot skill value changes or you are destroyed.'''
+            name: "Vigilado"
+            text: '''Se considera que "Thweek" tiene el mismo valor de Habilidad que tu piloto tenía después de la preparación de la partida.%LINEBREAK%El valor de Habilidad de "Thweek" no cambia si el valor de Habilidad de tu piloto se modifica o eres destruido.'''
         'Mimicked':
-            text: '''"Thweek" is treated as having your pilot ability.%LINEBREAK%"Thweek" cannot apply a Condition card by using your pilot ability.%LINEBREAK%"Thweek" does not lose your pilot ability if you are destroyed.'''
+            name: "Imitado"
+            text: '''Se considera que "Thweek" tiene tu misma capacidad especial de piloto.%LINEBREAK%"Thweek" no puede utilizar tu capacidad especial de piloto para asignar una carta de Estado.%LINEBREAK%"Thweek" no pierde tu capacidad especial de piloto si eres destruido.'''
         'Harpooned!':
-            text: '''When you are hit by an attack, if there is at least 1 uncanceled %CRIT% result, each other ship at Range 1 suffers 1 damage.  Then discard this card and receive 1 facedown Damage card.%LINEBREAK%When you are destroyed, each ship at Range 1 suffers 1 damage.%LINEBREAK%Action: Discard this card.  Then roll 1 attack die.  On a %HIT% or %CRIT% result, suffer 1 damage.'''
+            name: "¡Arponeado!
+            text: '''Cuando seas impactado por un ataque, si hay al menos 1 resultad %CRIT% sin anular, toda otra nave que tengas a alcance 1 sufre 1 punto de daño. Luego descarta esta carta y recibe 1 carta de Daño boca abajo.%LINEBREAK%Cuando seas destruido, toda nave que tengas a alcance 1 sufre 1 punto de daño.%LINEBREAK%Acción: Descarta esta carta. Luego tira 1 dado de ataque. Si sacas %HIT% o %CRIT%, sufres 1 punto de daño.'''
         'Rattled':
             name: "Estremecido"
             text: '''Cuando sufras daño normal o daño crítico causado por una bomba, sufres 1 punto adicional de daño crítico. Luego, retira esta carta%LINEBREAK%Acción: Tira 1 dado de ataque. Si sacas %FOCUS% o %HIT%, retira esta carta.'''


Update cards-es.coffee

Update cards-es.coffee

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -61,6 +61,10 @@
         "IG-2000 Expansion Pack": "Pack de Expansión IG-2000"
         "Most Wanted Expansion Pack": "Pack de Expansión Los Más Buscados"
         "Imperial Raider Expansion Pack": "Pack de Expansión Incursor Imperial"
+        "K-Wing Expansion Pack": "Pack de Expansión Ala-K"
+        "TIE Punisher Expansion Pack": "Pack de Expansión Castigador TIE"
+        "Khiraxz Fighter Expansion Pack": "Pack de Expansión Caza Khiraxz"
+        "Hound's Tooth  Expansion Pack": "Pack de Expansión Diente de Perro"
     ui:
         shipSelectorPlaceholder: "Selecciona una nave"
         pilotSelectorPlaceholder: "Selecciona un piloto"
@@ -154,12 +158,14 @@
     exportObj.renameShip 'TIE Interceptor', 'Interceptor TIE'
     exportObj.renameShip 'TIE Phantom', 'TIE Fantasma'
     exportObj.renameShip 'TIE Defender', 'Defensor TIE'
+    exportObj.renameShip 'TIE Punisher', 'Castigador TIE'
     exportObj.renameShip 'VT-49 Decimator', 'VT-49 Diezmador'
     exportObj.renameShip 'A-Wing', 'Ala-A'
     exportObj.renameShip 'B-Wing', 'Ala-B'
     exportObj.renameShip 'E-Wing', 'Ala-E'
     exportObj.renameShip 'X-Wing', 'Ala-X'
     exportObj.renameShip 'Y-Wing', 'Ala-Y'
+    exportObj.renameShip 'K-Wing', 'Ala-K'
     exportObj.renameShip 'Z-95 Headhunter', 'Z-95 Cazacabezas'
     exportObj.renameShip 'CR90 Corvette (Aft)', 'Corbeta CR90 (Popa)'
     exportObj.renameShip 'CR90 Corvette (Fore)', 'Corbeta CR90 (Proa)'
@@ -167,6 +173,7 @@
     exportObj.renameShip 'M3-A Interceptor', 'Interceptor M3-A'
     exportObj.renameShip 'StarViper', 'Víbora Estelar'
     exportObj.renameShip 'Aggressor', 'Agresor'
+    exportObj.renameShip 'Khiraxz Fighter', 'Caza Khiraxz'

     pilot_translations =
         "Wedge Antilles":
@@ -553,16 +560,16 @@
             ship: "Z-95 Cazacabezas"
         "Commander Alozen":
             ship: "TIE Avanzado"
-            text: """At the start of the Combat phase, you may acquire a target lock on an enemy ship at Range 1."""
+            text: """Al comienzo de la fase de Combate, puedes fijar como blanco una nave enemiga que tengas a alcance 1."""
         "Juno Eclipse":
             ship: "TIE Avanzado"
-            text: """When you reveal your maneuver, you may increase or decrease its speed by 1 (to a minimum of 1)."""
+            text: """Cuando reveles tu maniobra, puedes incrementar o reducir en 1 la velocidad de la maniobra (hasta un mínimo de 1)."""
         "Zertik Strom":
             ship: "TIE Avanzado"
-            text: """Enemy ships at Range 1 cannot add their range combat bonus when attacking."""
+            text: """Las naves enemigas que tengas a alcance 1 no pueden aplicar su modificador al combate por alcance cuando ataquen."""
         "Lieutenant Colzet":
             ship: "TIE Avanzado"
-            text: """At the start of the End phase, you may spend a target lock you have on an enemy ship to flip 1 random facedown Damage card assigned to it faceup."""
+            text: """Al comienzo de la fase Final, puedes gastar una de tus fichas de Blanco fijado asignadas a una nave enemiga para seleccionar al azar y poner boca arriba 1 carta de Daño que esa nave tenga asignada boca abajo."""
         "Latts Razzi":
             text: """When a friendly ship declares an attack, you may spend a target lock you have on the defender to reduce its agility by 1 for that attack."""
         "Graz the Hunter":
@@ -944,11 +951,15 @@
             name: "Torreta de Bláster Automático"
             text: """Ataque: Ataca a 1 nave (aunque esté fuera de tu arco de fuego).

Tus resultados %HIT% no pueden ser anulados por los dados de defensa.

El defensor puede anular tus resultados %CRIT% antes que los %HIT%."""
         "Advanced Targeting Computer":
-            text: """TIE Advanced only.%LINEBREAK%When attacking with your primary weapon, if you have a target lock on the defender, you may add 1 %CRIT% result to your roll.  If you do, you cannot spend target locks during this attack."""
+            ship: "TIE Avanzado"
+            name: "Computadora de Selección de Blancos Avanzada"
+            text: """Solo TIE Avanzado.%LINEBREAK%Cuando ataques con tu armamento principal, si tienes al defensor fijado como blanco, puedes añadir 1 %CRIT% al resultado de tu tirada. Si decides hacerlo, no podrás gastar fichas de Blanco Fijado durante este ataque."""
         "Ion Cannon Battery":
-            text: """Attack (energy): Spend 2 energy from this card to perform this attack.  If this attack hits, the defender suffers 1 critical damage and receives 1 ion token.  Then cancel all dice results."""
+            name: "Batería de Cañones de Iones"
+            text: """Ataque (Energía): Gasta 2 de Energía de esta carta para efectuar este ataque. Si este ataque impacta, el defensor sufre 1 de daño crítico y recibe 1 ficha de Iones. Después se anulan todos los resultados de los dados."""
         "Emperor Palpatine":
-            text: """%IMPERIALONLY%%LINEBREAK%Once per round, you may change a friendly ship's die result to any other die result.  That die result cannot be modified again."""
+            name: "Emperador Palpatine"
+            text: """%IMPERIALONLY%%LINEBREAK%Una vez por ornda, puedes cambiar el resultado de una tirada de dado efectuada por otra nave aliada por el de cualquier otro resultado posible para ese dado. El resultado de ese dado no podrá volver a ser modificado."""
         "Bossk":
             text: """%SCUMONLY%%LINEBREAK%After you perform an attack that does not hit, if you are not stressed, you must receive 1 stress token. Then assign 1 focus token to your ship and acquire a target lock on the defender."""
         "Lightning Reflexes":
@@ -978,8 +989,23 @@
         "Cloaking Device":
             text: """%SMALLSHIPONLY%%LINEBREAK%Action: Perform a free cloak action.%LINEBREAK%At the end of each round, if you are cloaked, roll 1 attack die.  On a %FOCUS% result, discard this card, then decloak or discard your cloak token."""
         "Shield Technician":
+            name: "Técnico de Escudos"
+            text: """%HUGESHIPONLY%%LINEBREAK%Cuando lleves a cabo una acción de recuperación, en vez de retirar todas tus fichas de Energía, puedes elegir qué cantidad de fichas de Energía deseas retirar."""
+        "Gran Moff Tarkin":
+            text: """%HUGESHIPONLY%%IMPERIALONLY%%LINEBREAK%Al comienzo de la fase de Combate, puedes elegir otra nave que tengas a alcance 1-4. Escoge entre retirar 1 ficha de Concentración de la nave elegida o asignarle 1 ficha de Concentración a esa nave."""
+        "Captain Needa":
+            name: "Capitán Needa"
+            text: """%HUGESHIPONLY%%IMPERIALONLY%%LINEBREAK%Si durante la fase de Activación te solapas con un obstáculo, en vez de recibir 1 carta de Daño boca arriba, tira 1 dado de ataque. Si sacas %HIT% o %CRIT%, sufres 1 de daño."""
+        "Admiral Ozzel":
+            name: "Almirante Ozzel"
+            text: """%HUGESHIPONLY%%IMPERIALONLY%%LINEBREAK%ENERGÍA: Puedes descartar hasta 3 fichas de Escudos de tu nave. Por cada ficha de Escudos descartada, obtienes 1 de Energía."""
+        "Shield Technician":
             text: """%HUGESHIPONLY%%LINEBREAK%When you perform a recover action, instead of spending all of your energy, you can choose any amount of energy to spend."""
+        "Shield Technician":
+            text: """%HUGESHIPONLY%%LINEBREAK%When you perform a recover action, instead of spending all of your energy, you can choose any amount of energy to spend."""
+

+
     modification_translations =
         "Stealth Device":
             name: "Dispositivo de Sigilo"
@@ -1087,7 +1113,7 @@
             text: """Solo Firespray-31.

Tu barra de mejoras gana 2 iconos %BOMB% adicionales."""
         "TIE/x1":
             ship: "TIE Avanzado"
-            text: """TIE Advanced only.%LINEBREAK%Your upgrade bar gains the %SYSTEM% upgrade icon.%LINEBREAK%If you equip a %SYSTEM% upgrade, its squad point cost is reduced by 4 (to a minimum of 0)."""
+            text: """Solo TIE Avanzado.%LINEBREAK%Tu barra de mejoras gana el icono %SYSTEM%.%LINEBREAK%Si te equipas con una mejora %SYSTEM%, su coste en puntos de escuadrón se reduce en 4 (hasta un mínimo de 0)."""
         "BTL-A4 Y-Wing":
             name: "BTL-A4 Ala-Y"
             text: """Solo Ala-Y.

No puedes atacar naves que estén fuera de tu arco de fuego. Después de que efectúes un ataque con tu armamento principal, puedes realizar inmediatamente un ataque con arma secundaria %TURRET%."""
@@ -1127,10 +1153,13 @@
         "Punishing One":
             text: """JumpMaster 5000 only.%LINEBREAK%Increase your primary weapon value by 1."""
         "Assailer":
-            text: """Raider-class corvette aft section only.%LINEBREAK%When defending, if the targeted section has a reinforce token, you may change 1 %FOCUS% result to a %EVADE% result."""
+            name: "Acometedor"
+            text: """Sólo sección de popa de corbeta clase Incursor.%LINEBREAK%Cuando te defiendas, si la sección atacada tiene asginada una ficha de Refuerzo, puedes cambiar 1 resultado de %FOCUS% por 1 resultado %EVADE%."""
         "Instigator":
-            text: """Raider-class corvette aft section only.%LINEBREAK%After you perform a recover action, recover 1 additional shield."""
+            name: "Instigador"
+            text: """Sólo sección de popa de corbeta clase Incursor.%LINEBREAK%ADespués de que hayas llevado a cabo una acción de recuperación, recuperas 1 de Escudos adicional."""
         "Impetuous":
-            text: """Raider-class corvette aft section only.%LINEBREAK%After you perform an attack that destroys an enemy ship, you may acquire a target lock."""
+            name: "Impetuoso"
+            text: """Sólo sección de popa de corbeta clase Incursor.%LINEBREAK%Después de que hayas efectuado un ataque que destruya una nave enemiga, puedes fijar un blanco."""

     exportObj.setupCardData basic_cards, pilot_translations, upgrade_translations, modification_translations, title_translations


Fix FR translation

Fix FR translation

--- coffeescripts/cards-fr.coffee
+++ coffeescripts/cards-fr.coffee
@@ -1143,10 +1143,12 @@
             text: '''%REBELONLY%%LINEBREAK%Action: Choose 1 friendly ship at Range 1-2. Assign 1 focus token to that ship for each enemy ship inside your firing arc at Range 1-3.  You cannot assign more than 3 focus tokens in this way.'''
         'Cassian Andor':
             text: '''%REBELONLY%%LINEBREAK%At the end of the Planning phase, you may choose an enemy ship at Range 1-2.  Guess aloud that ship's bearing and speed, then look at its dial.  If you are correct, you may rotate your dial to another maneuver.'''
-        'Han Solo (TFA)':
-            text: '''When you are placed during setup, you can be placed anywhere in the play area beyond Range 3 of enemy ships.'''
-        'Chewbacca (TFA)':
-            text: '''After another friendly ship at Range 1-3 is destroyed (but has not fled the battlefield), you may perform an attack.'''
+        'Finn':
+            text: '''%REBELONLY%%LINEBREAK%When attacking with a primary weapon or defending, if the enemy ship is inside your firing arc, you may add 1 blank result to your roll.'''
+        'Rey':
+            text: '''%REBELONLY%%LINEBREAK%At the start of the End phase, you may place 1 of your ship's focus tokens on this card.  At the start of the Combat phase, you may assign 1 of those tokens to your ship.'''
+        'Burnout SLAM':
+            text: '''%LARGESHIPONLY%%LINEBREAK%Your action bar gains the %SLAM% action icon.%LINEBREAK%After you perform a SLAM action, discard this card.'''

     modification_translations =
         "Shield Upgrade":


No need for return

No need for return

--- components/CreateImage.coffee
+++ components/CreateImage.coffee
@@ -41,4 +41,4 @@
       err.url = url
       return callback err
     image.src = url
-  c
+    return


simplify tests

simplify tests

--- test/basic-test.coffee
+++ test/basic-test.coffee
@@ -5,145 +5,55 @@

 aspm = require '../lib'

+platform = process.platform()
+
+testBuild = (module, opts) ->
+	opts.quiet = yes
+	msg = 'should build "time@0.11.0"'
+	msg += " for Atom-Shell@#{opts.target} on #{platform} @#{opts.arch}" if opts.target and opts.arch
+	it msg, (done) ->
+		aspm.installModule 'time@0.11.0', opts, (err) ->
+			done err
+
+
 describe 'build', ->
 	@timeout 1000*60*30

 	describe 'js-only modules', ->
-
-		it 'should install "async"', (done) ->
-			aspm.installModule 'async',
-				quiet: yes
-			, (err) ->
-				done(err)
+		testBuild 'async'

 	describe 'native modules', ->
-
 		# time
-
-		it 'should build "time@0.11.0" for as-0.17.2 ia32', (done) ->
-			aspm.installModule 'time@0.11.0',
-				quiet: yes
-				target: '0.17.2'
-				arch: 'ia32'
-			, (err) ->
-				done(err)
-
-		it 'should build "time@0.11.0" for as-0.17.2 x64', (done) ->
-			aspm.installModule 'time@0.11.0',
-				quiet: yes
-				target: '0.17.2'
-				arch: 'x64'
-			, (err) ->
-				done(err)
-
-		it 'should build "time@0.11.0" for as-0.19.5 ia32', (done) ->
-			aspm.installModule 'time@0.11.0',
-				quiet: yes
-				target: '0.19.5'
-				arch: 'ia32'
-			, (err) ->
-				done(err)
-
-		it 'should build "time@0.11.0" for as-0.19.5 x64', (done) ->
-			aspm.installModule 'time@0.11.0',
-				quiet: yes
-				target: '0.19.5'
-				arch: 'x64'
-			, (err) ->
-				done(err)
-
+		testBuild 'time@0.11.0', target: '0.17.2', arch: 'ia32'
+		testBuild 'time@0.11.0', target: '0.17.2', arch: 'x64'
+		testBuild 'time@0.11.0', target: '0.19.5', arch: 'ia32'
+		testBuild 'time@0.11.0', target: '0.19.5', arch: 'x64'
 		# leveldown
-
-		it 'should build "leveldown@1.0.0" for as-0.17.2 ia32', (done) ->
-			aspm.installModule 'leveldown@1.0.0',
-				quiet: yes
-				target: '0.17.2'
-				arch: 'ia32'
-			, (err) ->
-				done(err)
-
-		it 'should build "leveldown@1.0.0" for as-0.17.2 x64', (done) ->
-			aspm.installModule 'leveldown@1.0.0',
-				quiet: yes
-				target: '0.17.2'
-				arch: 'x64'
-			, (err) ->
-				done(err)
-
-		it 'should build "leveldown@1.0.0" for as-0.19.5 ia32', (done) ->
-			aspm.installModule 'leveldown@1.0.0',
-				quiet: yes
-				target: '0.19.5'
-				arch: 'ia32'
-			, (err) ->
-				done(err)
-
-		it 'should build "leveldown@1.0.0" for as-0.19.5 x64', (done) ->
-			aspm.installModule 'leveldown@1.0.0',
-				quiet: yes
-				target: '0.19.5'
-				arch: 'x64'
-			, (err) ->
-				done(err)
-
+		testBuild 'leveldown@1.0.0', target: '0.17.2', arch: 'ia32'
+		testBuild 'leveldown@1.0.0', target: '0.17.2', arch: 'x64'
+		testBuild 'leveldown@1.0.0', target: '0.19.5', arch: 'ia32'
+		testBuild 'leveldown@1.0.0', target: '0.19.5', arch: 'x64'
 		###
 		# node-sass
-
-		it 'should build "node-sass@1.2.3" for as-0.17.2 ia32', (done) ->
-			aspm.installModule 'node-sass@1.2.3',
-				quiet: yes
-				target: '0.17.2'
-				arch: 'ia32'
-			, (err) ->
-				done(err)
+		testBuild 'node-sass@1.2.3', target: '0.17.2', arch: 'ia32'
+		testBuild 'node-sass@1.2.3', target: '0.17.2', arch: 'x64'
+		testBuild 'node-sass@1.2.3', target: '0.19.5', arch: 'ia32'
+		testBuild 'node-sass@1.2.3', target: '0.19.5', arch: 'x64'
 		###

 	describe 'native modules that use node-pre-gyp', ->

 		###
 		# nodegit
-
-		it 'should build "nodegit@0.2.4" for as-0.17.2 ia32', (done) ->
-			aspm.installModule 'nodegit@0.2.4',
-				quiet: yes
-				target: '0.17.2'
-				arch: 'ia32'
-			, (err) ->
-				done(err)
+		testBuild 'nodegit@0.2.4', target: '0.17.2', arch: 'ia32'
+		testBuild 'nodegit@0.2.4', target: '0.17.2', arch: 'x64'
+		testBuild 'nodegit@0.2.4', target: '0.19.5', arch: 'ia32'
+		testBuild 'nodegit@0.2.4', target: '0.19.5', arch: 'x64'
 		###

 		# sqlite3
-
-		it 'should build "sqlite3@3.0.4" for as-0.17.2 ia32', (done) ->
-			aspm.installModule 'sqlite3@3.0.4',
-				quiet: yes
-				target: '0.17.2'
-				arch: 'ia32'
-			, (err) ->
-				done(err)
-
-		it 'should build "sqlite3@3.0.4" for as-0.17.2 x64', (done) ->
-			aspm.installModule 'sqlite3@3.0.4',
-				quiet: yes
-				target: '0.17.2'
-				arch: 'x64'
-			, (err) ->
-				done(err)
-
-		it 'should build "sqlite3@master" for as-0.19.5 ia32', (done) ->
-			aspm.installModule 'sqlite3',
-				quiet: yes
-				target: '0.19.5'
-				arch: 'ia32'
-				tarball: 'https://github.com/mapbox/node-sqlite3/archive/master.tar.gz'
-			, (err) ->
-				done(err)
-
-		it 'should build "sqlite3@master" for as-0.19.5 x64', (done) ->
-			aspm.installModule 'sqlite3',
-				quiet: yes
-				target: '0.19.5'
-				arch: 'x64'
-				tarball: 'https://github.com/mapbox/node-sqlite3/archive/master.tar.gz'
-			, (err) ->
-				done(err)
+		testBuild 'nodegit@0.2.4', target: '0.17.2', arch: 'ia32'
+		testBuild 'nodegit@0.2.4', target: '0.17.2', arch: 'x64'
+		testBuild 'nodegit@0.2.4', target: '0.19.5', arch: 'ia32', tarball: 'https://github.com/mapbox/node-sqlite3/archive/master.tar.gz'
+		testBuild 'nodegit@0.2.4', target: '0.19.5', arch: 'x64', tarball: 'https://github.com/mapbox/node-sqlite3/archive/master.tar.gz'
+


alright, it's working....

alright, it's working....

--- test/browser/main.iced
+++ test/browser/main.iced
@@ -3,6 +3,7 @@
   sha512 : require '../files/sha512.iced'
   wordarray : require '../files/wordarray.iced'
   hmac : require '../files/hmac.iced'
+  aes : require '../files/aes.iced'

 {BrowserRunner} = require('iced-test')


Fix class splitting

Fix class splitting

--- test/style.spec.coffee
+++ test/style.spec.coffee
@@ -37,7 +37,7 @@
         assert.isFalse el.hasClass('c')

     getClass = (el) ->
-        el.get(0).className.trim().split(/\s/)
+        el.get(0).className.trim().split(/\s+/)

     test 'addClass', ->
         el = $('.a')


[grunt] place CSS-related tasks in proper order

--- tasks/styles.coffee
+++ tasks/styles.coffee
@@ -61,24 +61,6 @@
       ]

   ###
-  CSSO
-  https://github.com/t32k/grunt-csso
-  Minify CSS files with CSSO
-  ###
-
-  @config 'csso',
-    build:
-      options:
-        report: 'min'
-      files: [
-        expand: true
-        cwd: '<%= path.build.styles %>'
-        src: '{,**/}*.tidy.css'
-        dest: '<%= path.build.styles %>'
-        ext: '.min.css'
-      ]
-
-  ###
   Uncss
   https://github.com/addyosmani/grunt-uncss
   Remove unused CSS
@@ -102,6 +84,24 @@
       files: [
         src: '<%= path.build.root %>/{,**/}*.html'
         dest: '<%= file.build.style.tidy %>'
+      ]
+
+  ###
+  CSSO
+  https://github.com/t32k/grunt-csso
+  Minify CSS files with CSSO
+  ###
+
+  @config 'csso',
+    build:
+      options:
+        report: 'min'
+      files: [
+        expand: true
+        cwd: '<%= path.build.styles %>'
+        src: '{,**/}*.tidy.css'
+        dest: '<%= path.build.styles %>'
+        ext: '.min.css'
       ]

   ###


Fixed fuzzy-finder's file regex

Fixed fuzzy-finder's file regex

--- src/fuzzy-finder.coffee
+++ src/fuzzy-finder.coffee
@@ -6,7 +6,7 @@

 module.exports =
 class FuzzyFinder extends SelectList
-  filenameRegex: /([^\w\.\-\/\\])/
+  filenameRegex: /[\w\.\-\/\\]+/

   @activate: (rootView) ->
     @instance = new FuzzyFinder(rootView)


Parse json on request

Parse json on request

--- command-savepause.coffee
+++ command-savepause.coffee
@@ -21,6 +21,7 @@

     url = "https://flow-deploy.octoblu.com/flows/#{config.uuid}/instance/save-pause/#{uuid.v1()}"
     requestOptions =
+      json: true
       auth:
         user: config.uuid
         pass: config.token


Cancelling the image loading when setting the image to null

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -662,7 +662,17 @@
 			@_setPropertyValue("image", value)

 			if value in [null, ""]
+				if @imageLoader?
+					@imageLoader.onload = null
+					@imageLoader.onerror = null
+					@imageLoader.src = null
+
 				@style["background-image"] = null
+
+				if @imageLoader?
+					@emit Events.ImageLoadCancelled, @imageLoader
+					@imageLoader = null
+
 				return

 			# Show placeholder image on any browser that doesn't support inline pdf
@@ -681,17 +691,19 @@
 			# As an optimization, we will only use a loader
 			# if something is explicitly listening to the load event

-				loader = new Image()
-				loader.name = imageUrl
-				loader.src = imageUrl
 			if @listeners(Events.ImageLoaded, true) or @listeners(Events.ImageLoadError, true) or @listeners(Events.ImageLoadCancelled, true)
-
-				loader.onload = =>
+				@imageLoader = new Image()
+				@imageLoader.name = imageUrl
+				@imageLoader.src = imageUrl
+
+				@imageLoader.onload = =>
 					@style["background-image"] = "url('#{imageUrl}')"
-					@emit Events.ImageLoaded, loader
-
-				loader.onerror = =>
-					@emit Events.ImageLoadError, loader
+					@emit Events.ImageLoaded, @imageLoader
+					@imageLoader = null
+
+				@imageLoader.onerror = =>
+					@emit Events.ImageLoadError, @imageLoader
+					@imageLoader = null

 			else
 				@style["background-image"] = "url('#{imageUrl}')"


Print function improvements

Print function improvements

--- framer/Print.coffee
+++ framer/Print.coffee
@@ -12,34 +12,39 @@

 exports.print = (args...) ->

-	printLayer = Session.printLayer
+	printLayer = Framer.Session.printLayer

 	if not printLayer

 		printLayer = new Layer
 		printLayer.scrollVertical = true
+		printLayer.ignoreEvents = false
 		printLayer.html = ""
 		printLayer.style =
 			"font": "12px/1.35em Menlo"
 			"color": "rgba(0,0,0,.7)"
 			"padding": "8px"
 			"padding-bottom": "30px"
-			"zIndex": 9999
-			"border-top": "1px solid #ccc"
-			"backgroundColor": "#fff"
+			"border-top": "1px solid #d9d9d9"

-		update = ->
-			printLayer.width = window.innerWidth
-			printLayer.height = 160
-			printLayer.maxY = window.innerHeight
-
-		update()
-
-		Screen.on "resize", update
+		printLayer.opacity = 0.9
+		printLayer.style.zIndex = 999 # Always stay on top
+		printLayer.visible = true
+		printLayer.backgroundColor = "white"
+		# printLayer.bringToFront()
+
+		printLayer.width = window.innerWidth
+		printLayer.height = 160
+		printLayer.maxY = window.innerHeight

-	printLayer.visible = true
+	printNode = document.createElement("div")
+	printNode.innerHTML = "» " + args.map(Utils.stringify).join(", ") + "
"
+	printNode.style["-webkit-user-select"] = "text"
+	printNode.style["cursor"] = "auto"
+
+	printLayer._element.appendChild(printNode)

-	printLayer.html += "» #{args.map(Utils.stringify).join ', '}
"
-	printLayer._element.scrollTop = printLayer._element.scrollHeight
-
-	Session.printLayer = printLayer+	Framer.Session.printLayer = printLayer
+
+	Utils.delay 0, ->
+		printLayer._element.scrollTop = printLayer._element.scrollHeight


Share twitter message from title

Share twitter message from title


Rename constructor option in test

Rename constructor option in test

--- examples/collection.coffee
+++ examples/collection.coffee
@@ -24,7 +24,7 @@

 class Post extends Serenade.Model
   @property 'commentCount', dependsOn: 'comments', get: -> @comments.length
-  @hasMany 'comments', constructor: (-> Comment), serialize: true
+  @hasMany 'comments', as: (-> Comment), serialize: true
   @localStorage: true

 class Comment extends Serenade.Model


one more underscore

one more underscore

--- src/natives/main.coffee
+++ src/natives/main.coffee
@@ -15,7 +15,7 @@
 "use strict"

 # pull in external modules
-_ = require '../vendor/_.js'
+_ = require '../vendor/underscore/underscore.js'
 gLong = require '../vendor/gLong.js'
 util = require './util'
 runtime = require './runtime'


two boxes for video navigation

two boxes for video navigation

--- client/landing/app/Applications/Activity.kdapplication/ContentDisplays/tutorial.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ContentDisplays/tutorial.coffee
@@ -198,12 +198,10 @@
           item.hide()
           item.destroy()

-    # @listInfo = new KDView
-
-    @listAnchor = new KDView
+    @listAnchorNext = new KDView
+    @listAnchorPrevious = new KDView

     KD.remote.api.JTutorialList.fetchForTutorialId @getData().getId(), (listData)=>
-      log "list",listData
       if listData
         # @listInfo.updatePartial "In list '#{listData.title}'"
         for tutorial,i in listData.tutorials
@@ -211,13 +209,12 @@
             @position = i
             @before = listData.tutorials[0...i]
             @after = listData.tutorials[i+1..]
-        log "at",@position,"before",@before,"after",@after
         if @after.length >0
-          @listAnchor.addSubView new TutorialListSwitchBox
+          @listAnchorNext.addSubView new TutorialListSwitchBox
             direction:"next"
           , @after[0]
         if @before.length >0
-          @listAnchor.addSubView new TutorialListSwitchBox
+          @listAnchorPrevious.addSubView new TutorialListSwitchBox
             direction:"previous"
           , @before[0]

@@ -294,7 +291,6 @@
             AUTHOR

-            {{> @listAnchor}}

{{@utils.expandUsernames @utils.applyMarkdown #(title)}}

@@ -306,7 +302,9 @@

             {{> @editDiscussionLink}}
             {{> @deleteDiscussionLink}}
+            {{> @listAnchorPrevious}}
             {{> @embedBox}}
+            {{> @listAnchorNext}}

{{@utils.expandUsernames(@utils.applyMarkdown(#(body)),"pre")}}

@@ -333,13 +331,11 @@

     @outgoingContainer = new KDView
     if data.link?
-      log data.link
       image = new KDCustomHTMLView
         cssClass : "image-preview"
         tagName : "img"
         attributes :
           src : @utils.proxifyUrl data.link.link_embed.images[0].url
-      log image
       @outgoingContainer.addSubView image

     @outgoingLink = new KDButtonView


Avoid race conditions when handling events

--- gitcommander.coffee
+++ gitcommander.coffee
@@ -58,17 +58,6 @@
   timer = setInterval timerCallback, TIMEOUT
   do timer.unref

-eventTeller =
-  "PushEvent": (channel, event) ->
-    numerus = if event.payload.size > 1 then "commits" else "commit"
-    ircClient.say channel, "[#{event.created_at}] #{event.actor.login} pushed
-      #{event.payload.size} #{numerus} to
-      #{event.repo.name} (#{event.repo.weburl})"
-  "ReleaseEvent": (channel, event) ->
-    ircClient.say channel, "[#{event.created_at}] #{event.actor.login} published
-      a new release (#{event.payload.release.tag_name}) of
-      #{event.repo.name} (#{event.repo.weburl})"
-
 events = []
 handleEvents = (err, res) ->
   if err? or not res?
@@ -86,21 +75,35 @@
     if age >= RECENCY or event.id in events
       continue
     events.push event.id
-    nofEvents++
     if event.type of eventTeller
-      weburl = "https://github.com/#{event.repo.name}"
-      gitio weburl, (gitioerr, gitiores) ->
-        if gitioerr? or not gitiores?
-          console.log "! Error when shortening URL #{weburl}."
-          event.repo.weburl = weburl
-        else
-          event.repo.weburl = gitiores
-
-        for channel in CHANNELS
-          eventTeller[event.type] channel, event
+      nofEvents++
+      announceEvent event

   if nofEvents
     console.log "* Seen #{nofEvents} new events (#{events.length} total)."
+
+announceEvent = (event) ->
+  weburl = "https://github.com/#{event.repo.name}"
+  gitio weburl, (err, res) ->
+    if err? or not res?
+      console.log "! Error when shortening URL #{weburl}."
+      event.repo.weburl = weburl
+    else
+      event.repo.weburl = res
+
+    for channel in CHANNELS
+      eventTeller[event.type] channel, event
+
+eventTeller =
+  "PushEvent": (channel, event) ->
+    numerus = if event.payload.size > 1 then "commits" else "commit"
+    ircClient.say channel, "[#{event.created_at}] #{event.actor.login} pushed
+      #{event.payload.size} #{numerus} to
+      #{event.repo.name} (#{event.repo.weburl})"
+  "ReleaseEvent": (channel, event) ->
+    ircClient.say channel, "[#{event.created_at}] #{event.actor.login} published
+      a new release (#{event.payload.release.tag_name}) of
+      #{event.repo.name} (#{event.repo.weburl})"

 timerCallback = ->
   for orgEntry in GIT_OPTS.orgs


:art: welcome message, callbacks, latlng normalization

--- src/Mapscii.coffee
+++ src/Mapscii.coffee
@@ -55,6 +55,7 @@

     .then =>
       @_draw()
+      .then => @notify("Welcome to MapSCII! Use your cursors to navigate, a/z to zoom, q to quit.")

   _initTileSource: ->
     @tileSource = new TileSource()
@@ -62,7 +63,7 @@

   _initKeyboard: ->
     keypress config.input
-    config.input.setRawMode true
+    config.input.setRawMode true if config.input.setRawMode
     config.input.resume()

     config.input.on 'keypress', (ch, key) => @_onKey key
@@ -108,9 +109,11 @@

     z = utils.baseZoom @zoom
     center = utils.ll2tile @center.lon, @center.lat, z
-    @mousePosition = utils.tile2ll center.x+(dx/size), center.y+(dy/size), z
+
+    @mousePosition = utils.normalize utils.tile2ll center.x+(dx/size), center.y+(dy/size), z

   _onClick: (event) ->
+    return if event.x < 0 or event.x > @width/2 or event.y < 0 or event.y > @height/4
     @_updateMousePosition event

     if @mouseDragging and event.button is "left"
@@ -127,6 +130,9 @@
     @_draw()

   _onMouseMove: (event) ->
+    return if event.x < 0 or event.x > @width/2 or event.y < 0 or event.y > @height/4
+    return if config.mouseCallback and not config.mouseCallback event
+
     # start dragging
     if event.button is "left"
       if @mouseDragging
@@ -153,16 +159,20 @@
     @notify @_getFooter()

   _onKey: (key) ->
+    if config.keyCallback and not config.keyCallback key
+      return
+
     # check if the pressed key is configured
     draw = switch key?.name
       when "q"
-        process.exit 0
-
-      when "w" then @zoomy = 1
-      when "s" then @zoomy = -1
+        if config.quitCallback
+          config.quitCallback()
+        else
+          process.exit 0

       when "a" then @zoomBy config.zoomStep
-      when "z" then @zoomBy -config.zoomStep
+      when "z", "y"
+        @zoomBy -config.zoomStep

       when "left" then @moveBy 0, -8/Math.pow(2, @zoom)
       when "right" then @moveBy 0, 8/Math.pow(2, @zoom)
@@ -174,9 +184,6 @@

     if draw isnt null
       @_draw()
-    else
-      # display debug info for unhandled keys
-      @notify JSON.stringify key

   _draw: ->
     @renderer
@@ -186,13 +193,6 @@
       @notify @_getFooter()
     .catch =>
       @notify "renderer is busy"
-    .then =>
-      if @zoomy
-        if (@zoomy > 0 and @zoom < config.maxZoom) or (@zoomy < 0 and @zoom > @minZoom)
-          @zoom += @zoomy * config.zoomStep
-        else
-          @zoomy *= -1
-        setImmediate => @_draw()

   _getFooter: ->
     # tile = utils.ll2tile @center.lon, @center.lat, @zoom
@@ -203,6 +203,7 @@
     "mouse: #{utils.digits @mousePosition.lat, 3}, #{utils.digits @mousePosition.lon, 3} "

   notify: (text) ->
+    config.onUpdate() if config.onUpdate
     @_write "\r\x1B[K"+text unless config.headless

   _write: (output) ->
@@ -218,11 +219,4 @@
     @setCenter @center.lat+lat, @center.lon+lon

   setCenter: (lat, lon) ->
-    lon += 360 if lon < -180
-    lon -= 360 if lon > 180
-
-    lat = 85.0511 if lat > 85.0511
-    lat = -85.0511 if lat < -85.0511
-
-    @center.lat = lat
-    @center.lon = lon
+    @center = utils.normalize lon: lon, lat: lat


Tweak shell physics again

Tweak shell physics again

--- src/script/Shell.coffee
+++ src/script/Shell.coffee
@@ -1,6 +1,6 @@
 class Shell

-  gravity: 500
+  gravity: 400
   lifetime: 1 #seconds

   constructor: (x, y) ->
@@ -8,10 +8,10 @@
     effectsManager.shells.add(@ref)
     game.physics.arcade.enable(@ref)
     @ref.body.gravity.y  = @gravity
-    @ref.body.bounce.y   = 0.3
-    if player.facing is 'right' then sign = 1 else sign = -1
-    @ref.body.velocity.x = sign * Math.floor(Math.random()*100 - 200)
-    @ref.body.velocity.y = -Math.floor(Math.random()*100 + 50)
+    @ref.body.bounce.y   = 0.5
+    if player.facing is 'right' then sign = -1 else sign = 1
+    @ref.body.velocity.x = sign * Math.floor(Math.random()*50 + 150)
+    @ref.body.velocity.y = -Math.floor(Math.random()*50 + 75)
     game.time.events.add(Phaser.Timer.SECOND * @lifetime, @removeShell, this)

     rotationMagnitude = Math.random() * 0.1


boots

boots

--- mqtt-gw.coffee
+++ mqtt-gw.coffee
@@ -1,4 +1,4 @@
-console.log "node-mqtt-gw:"
+console.log "node-mqtt-gw:::"

 plist={}
 plistp={}


Tokenize embedded lexers

Tokenize embedded lexers

--- grammars/jison.cson
+++ grammars/jison.cson
@@ -52,6 +52,59 @@
     patterns: [
       {include: "#comments"}

+      # Jison declarations sections can have lexers embedded between %lex and
+      # /lex.
+      {
+        begin: "^\\s*(%lex)\\s*$"
+        end:   "^\\s*(/lex)\\b"
+        # There may be a better choice of scope than “entity.name.tag”, but
+        # using a tag scope makes the start and end matches stand out. Also, the
+        # end match “/lex” is somewhat like an end tag.
+        beginCaptures: 1: name: "entity.name.tag.lexer.begin.jison"
+        endCaptures:   1: name: "entity.name.tag.lexer.end.jison"
+        # Simply setting the patterns object to {include: "source.jisonlex"}
+        # here doesn’t work for several reasons:
+        # - The Jison Lex grammar’s rules section treats /lex as a valid regex
+        #   pattern; /lex is the same as (?=lex).
+        # - The user-code section treats /lex as a division operator followed by
+        #   the identifier “lex”.
+        # - The user-code section also does not end.
+        # This is the Jison Lex grammar with end patterns that circumvent these
+        # issues.
+        patterns: [
+          {
+            begin: "%%"
+            end:   "(?=/lex)"
+            beginCaptures: 0: name: "meta.separator.section.jisonlex"
+            patterns: [
+              {
+                begin: "^%%"
+                end:   "(?=/lex)"
+                beginCaptures: 0: name: "meta.separator.section.jisonlex"
+                patterns: [
+                  {
+                    name:  "meta.section.user-code.jisonlex"
+                    begin: "(?!%%)"
+                    end:   "(?=/lex)"
+                    patterns: [include: "source.jisonlex#user_code_section"]
+                  }
+                ]
+              },{
+                name:  "meta.section.rules.jisonlex"
+                begin: "(?!%%)"
+                end:   "(?=^(?:%%|/lex))"
+                patterns: [include: "source.jisonlex#rules_section"]
+              }
+            ]
+          },{
+            name:  "meta.section.definitions.jisonlex"
+            begin: "(?!%%)"
+            end:   "(?=%%|/lex)"
+            patterns: [include: "source.jisonlex#definitions_section"]
+          }
+        ]
+      }
+
       {
         name:  "meta.section.prologue.jison"
         begin: "(?=%\\{)"


Fix typo in pypi.coffee

Fix typo in pypi.coffee

--- src/scripts/pypi.coffee
+++ src/scripts/pypi.coffee
@@ -9,7 +9,7 @@
 #
 # Commands:
 #   hubot show latest from pypi for <package> - Shows latest version of Python package registered at PyPI
-#   hubot show total downloads from pypi for <package> - Shows total number of downloads accross all versions of Python package registered at PyPI
+#   hubot show total downloads from pypi for <package> - Shows total number of downloads across all versions of Python package registered at PyPI
 #
 # Author:
 #   lukaszb


Allow for SHA specification when retrieving contents

--- octokit.coffee
+++ octokit.coffee
@@ -597,8 +597,11 @@

           # Get contents (file/dir)
           # -------
-          @getContents = (path) ->
-            _request('GET', "#{_repoPath}/contents/#{path}", null, {raw:true})
+          @getContents = (path, sha=null) ->
+            queryString = ''
+            if sha != null
+              queryString = toQueryString({ref:sha})
+            _request('GET', "#{_repoPath}/contents/#{path}#{queryString}", null, {raw:true})
             .then (contents) =>
               return contents
             # Return the promise
@@ -779,9 +782,11 @@
           @contents = (path) ->
             _getRef()
             .then (branch) =>
-              _git.getContents(path)
-              .then (contents) =>
-                return contents
+              _git.getSha(branch, '')
+              .then (sha) =>
+                _git.getContents(path, sha)
+                .then (contents) =>
+                  return contents
             # Return the promise
             .promise()


use global ga for impression tracking

use global ga for impression tracking

--- lib/analytics.coffee
+++ lib/analytics.coffee
@@ -83,8 +83,7 @@
   (_.map ids, (id) -> createHash('md5').update(id).digest('hex').substr(0, 8)).join("-")

 module.exports.trackMulti = (description, data) =>
-  # Send google analytics event
-  @ga? 'send', 'event', categories['multi'], description, data
+  ga? 'send', 'event', categories['multi'], description, data

 module.exports.multi = (description, modelName, ids) ->
   return unless ids?.length > 0
@@ -124,4 +123,4 @@
     hash = {}
     hash[key] = enabledDisabled
     module.exports.setProperty hash
-    enabledDisabled == 'enabled'+    enabledDisabled == 'enabled'


Correct destination path for build to reflect docker name

--- options.coffee
+++ options.coffee
@@ -17,7 +17,7 @@
 source.fontDir = "#{source.baseDir}/font"

 target = {}
-target.baseDir = 'docker/var/www'
+target.baseDir = 'docker-web/var/www'

 alpha = {}
 alpha.baseDir = "#{target.baseDir}/alpha"


metric styling overrides

metric styling overrides

--- oridomi.coffee
+++ oridomi.coffee
@@ -454,7 +454,9 @@
       percent = 100 / count

       mask = cloneEl maskProto, true, 'mask' + classSuffix
-      mask.children[0].style[metric] = count * 100 + '%'
+      content = mask.children[0]
+      content.style.width   = content.style.height = '100%'
+      content.style[metric] = content.style['max' + capitalize metric] = count * 100 + '%'
       if @_shading
         mask.appendChild shaderProtos[anchor] for anchor in anchorSet


call reset at end of constructor

call reset at end of constructor

--- oridomi.coffee
+++ oridomi.coffee
@@ -569,7 +569,7 @@
     @$el = $ @el if $
     # An effect method is called since touch events rely on using the last
     # method called.
-    @accordion 0
+    @reset 0
     # The ripple setting is converted to a number to allow boolean settings.
     @_settings.ripple = Number @_settings.ripple
     @_setTrans @_settings.speed, @_settings.ripple if @_settings.ripple


handle placeholders

handle placeholders

--- src/browser.coffee
+++ src/browser.coffee
@@ -105,6 +105,12 @@
     make: -> # create a dom object
         throw new Error "Adapter::make not defined."

+    createPlaceholder: -> # create a dom object
+        throw new Error "Adapter::createPlaceholder not defined."
+
+    removePlaceholder: -> # create a dom object
+        throw new Error "Adapter::removePlaceholder not defined."
+
     # flow control : eventlisteners

     onadd: (parent, el) ->
@@ -178,9 +184,15 @@
     # ready callbacks

     insert_callback: (el) ->
+        if el is el.builder and el.isempty
+            el._browser.wrapped = yes
+            @createPlaceholder(el)
         @fn.add(el.parent, el)
+        if el.parent._browser.wrapped
+            el.parent._browser.wrapped = no
+            @removePlaceholder(el.parent)
         el._browser.ready?(el)
-        el._browser.ready = yes
+        el._browser.ready  = yes
         el._browser.insert = yes

     parent_done_callback: (el) ->
@@ -197,6 +209,9 @@
         el._browser.parent_done = yes

     replace_callback: (oldtag, newtag) ->
+        if newtag is newtag.builder and newtag.isempty
+            newtag._browser.wrapped = yes
+            @createPlaceholder(newtag)
         @fn.replace(oldtag, newtag)
         newtag._browser.replace = null


Added mob cookies and location history

Added mob cookies and location history

--- lib/model/mob.coffee
+++ lib/model/mob.coffee
@@ -23,6 +23,8 @@
         female: 2       # Female sexual mechanic
         intersex: 3     # Both male and female mechanics

+    @maxLocationHistory: 50 # Maximum number of rooms to keep in history cookie
+
     defaults: ->
         name: 'unnamed'
         shortDescription: 'an unnamed mob'
@@ -34,13 +36,23 @@
         sex: Mob.sex.none                  # Determines sexual mechanics
         height: 150                     # Height in cm
         weight: 80                      # Weight in kg
-        tattoos: []                     # Invisible IDs assigned by other commands
+        tattoos: {}                     # Invisible IDs assigned by other commands

         currentLocation: null

     initialize: ->
         @sessions = []
+        @cookies = {}
         @resolver = new MobAttributeResolver @
+
+        # Keep track of the last fiew locations
+        @on 'change:currentLocation', (model, value, options) =>
+            return if model isnt @
+            history = @getCookie("mob_location_history") ? []
+            history.push value
+            while history.length > Mob.maxLocationHistory
+                history.shift()
+            @setCookie "mob_location_history", history

     hasPermission: (acl, permission) -> @resolver.hasPermission acl, permission
     mustHavePermission: (acl, permission) ->
@@ -167,21 +179,35 @@

         targets

+    # Perminant saved tracking information (achievements, crimes, etc)
     hasTattoo: (tattoo) ->
         tattoos = @get 'tattoos'
-        tattoos ?= []
-        tattoo in tattoos
-
-    setTattoo: (tattoo) ->
-        tattoos = @get 'tattoos'
-        tattoos ?= []
-        tattoos.push tattoo
-        @set 'tattoos', _.uniq tattoos
+        tattoos ?= {}
+        tattoos[tattoo] != undefined
+
+    getTattoo: (tattoo) ->
+        tattoos = @get 'tattoos'
+        tattoos ?= {}
+        tattoos[tattoo]
+
+    setTattoo: (tattoo, value=true) ->
+        tattoos = @get 'tattoos'
+        tattoos ?= {}
+        tattoos[tattoo] = value
+        @set 'tattoos', tattoos

     unsetTattoo: (tattoo) ->
         tattoos = @get 'tattoos'
-        tattoos ?= []
-        @set 'tattoos', _.without tattoos, tattoo
+        tattoos ?= {}
+        if tattoos[tattoo] != undefined
+            delete tattoos[tattoo]
+        @set 'tattoos', tattoos
+
+    # Temporary tracking information (seen rooms, room extras, etc)
+    hasCookie: (cookie) -> @cookies[cookie] != undefined
+    getCookie: (cookie) -> @cookies[cookie]
+    setCookie: (cookie, value=true) -> @cookies[cookie] = value
+    unsetCookie: (cookie) -> delete @cookies[cookie] if @hasCookie cookie


:fire: slim up

:fire: slim up

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -203,21 +203,17 @@
   @extend()
   complete: true
   step: 1
-
-  constructor: ->
-    super
-    @numberRegex = new RegExp(settings.get('numberRegex'), 'g')
-
-  execute: ->
+
+  execute: ->
+    pattern = new RegExp(settings.get('numberRegex'), 'g')
     @editor.transact =>
-      results = (@increaseNumber(cursor) for cursor in @editor.getCursors())
-      unless _.any(results)
+      unless _.any(@increaseNumber(c, pattern) for c in @editor.getCursors())
         atom.beep()

-  increaseNumber: (cursor) ->
+  increaseNumber: (cursor, pattern) ->
     success = null
     scanRange = cursor.getCurrentLineBufferRange()
-    @editor.scanInBufferRange @numberRegex, scanRange, ({matchText, range, stop, replace}) =>
+    @editor.scanInBufferRange pattern, scanRange, ({matchText, range, stop, replace}) =>
       unless range.end.isGreaterThan cursor.getBufferPosition()
         return
       number = parseInt(matchText, 10) + @step * @getCount(1)


add setting to disable status bar buttons

--- lib/php-debug.coffee
+++ lib/php-debug.coffee
@@ -36,6 +36,11 @@
   subscriptions: null

   config:
+    EnableStatusbarButtons:
+      title: "Enable buttons on status bar"
+      type: 'boolean'
+      default: true
+      description: "Enable buttons on status bar"
     GutterBreakpointToggle:
       title: "Enable breakpoint markers in the gutter"
       type: 'boolean'
@@ -124,6 +129,7 @@
     @subscriptions.add atom.commands.add 'atom-workspace', 'php-debug:toggleBreakpoint': => @toggleBreakpoint()
     @subscriptions.add atom.commands.add 'atom-workspace', 'php-debug:breakpointSettings': => @breakpointSettings()
     @subscriptions.add atom.commands.add 'atom-workspace', 'php-debug:toggleDebugging': => @toggleDebugging()
+    @subscriptions.add atom.commands.add 'atom-workspace', 'php-debug:toggleConsole': => @toggleConsole()
     @subscriptions.add atom.commands.add 'atom-workspace', 'php-debug:addWatch': => @addWatch()
     @subscriptions.add atom.commands.add 'atom-workspace', 'php-debug:run': => @run()
     @subscriptions.add atom.commands.add 'atom-workspace', 'php-debug:stepOver': => @stepOver()
@@ -225,8 +231,15 @@
       @getUnifiedView().setConnected(true)

   consumeStatusBar: (statusBar) ->
-    @debugView = new PhpDebugDebugView(statusBar, this)
-    @consoleStatusView = new PhpDebugConsoleStatusView(statusBar, this)
+    atom.config.observe "php-debug.EnableStatusbarButtons", (enable) =>
+      if enable
+        @debugView = new PhpDebugDebugView(statusBar, this)
+        @consoleStatusView = new PhpDebugConsoleStatusView(statusBar, this)
+      else
+        @consoleStatusView?.destroy?()
+        @consoleStatusView = null
+        @debugView?.destroy?()
+        @debugView = null

   getUnifiedView: ->


Returned time from timeEnd().

Returned time from timeEnd().

--- src/Log.coffee
+++ src/Log.coffee
@@ -25,7 +25,7 @@
     currentLevel ?= @_level
     # Only logs if the level is less than or equal to the current level
     code = @_levels[level]
-    return typeof code != 'undefined' && code <= @_levels[currentLevel]
+    return code? and code <= @_levels[currentLevel]

   # Enables the Log functions if they have been disabled.
   on: ->
@@ -73,23 +73,20 @@
     console.log(stack)

   time: (name, level) ->
-    if level != undefined
-      @_timers[name] =
-        date: Date.now()
-        level: level
-    else if @shouldLog('time')
-      console.time(name)
+    @_timers[name] =
+      date: Date.now()
+      level: level
+    if !level? and @shouldLog('time') then console.time(name)

   timeEnd: (name) ->
     timer = @_timers[name]
-    if timer
-      if @shouldLog(timer.level)
-        time = Date.now() - timer.date
-        Log[timer.level](name + ': ' + time + 'ms')
-      delete @_timers[name]
-      return time
+    time = Date.now() - timer.date
+    if @shouldLog(timer.level)
+      Log[timer.level](name + ': ' + time + 'ms')
     else if @shouldLog('time')
       console.timeEnd(name)
+    delete @_timers[name]
+    return time

   # Tracking events for analytics.
   track: -> @msg('TRACK', arguments, console.debug)


adding in api routes for deleting and loading task plan

--- src/api.coffee
+++ src/api.coffee
@@ -92,6 +92,11 @@

   apiHelper TaskPlanActions, TaskPlanActions.removeTopic, TaskPlanActions.saved, 'PATCH', saveHelper

+  apiHelper TaskPlanActions, TaskPlanActions.delete, TaskPlanActions.deleted, 'DELETE', saveHelper
+
+  apiHelper TaskPlanActions, TaskPlanActions.load , TaskPlanActions.loaded, 'GET', (id) ->
+    url: "/api/courses/1/plans/#{id}"
+
   apiHelper TocActions, TocActions.load, TocActions.loaded, 'GET', () ->
     url: '/api/courses/1/readings'


renamed test

renamed test

--- test/messageFormats.coffee
+++ test/messageFormats.coffee
@@ -11,7 +11,7 @@
     errors = streamInPolicy mongoWatchPolicy()
     errors.should.be.empty

-describe 'JSV', ->
+describe 'JSON validator', ->

   it 'should validate a function', ->
     errors = jsv.validate {fn: ->}, {


Iterate over entries when selecting continuous entries

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -625,13 +625,14 @@
   #         a new given entry. This is shift+click functionality
   #
   # Returns array of selected elements
-  selectContinuousEntries: (entry)->
+  selectContinuousEntries: (entry) ->
     currentSelectedEntry = @selectedEntry()
     parentContainer = $(entry).parent()
     if $.contains(parentContainer[0], currentSelectedEntry)
-      entryIndex = parentContainer.indexOf(entry)
-      selectedIndex = parentContainer.indexOf(currentSelectedEntry)
-      elements = (parentContainer[0].children[i] for i in [entryIndex..selectedIndex])
+      entries = parentContainer.find('.entry').toArray()
+      entryIndex = entries.indexOf(entry)
+      selectedIndex = entries.indexOf(currentSelectedEntry)
+      elements = (entries[i] for i in [entryIndex..selectedIndex])

       @deselect()
       element.classList.add('selected') for element in elements
@@ -642,7 +643,7 @@
   #         items. This is cmd+click functionality
   #
   # Returns given entry
-  selectMultipleEntries: (entry)->
+  selectMultipleEntries: (entry) ->
     entry?.classList.add('selected')
     entry


Fix toggleSide to use atom.config.toggle

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -343,8 +343,7 @@
     @scrollTop(0)

   toggleSide: ->
-    newValue = !atom.config.get('tree-view.showOnRightSide')
-    atom.config.set('tree-view.showOnRightSide', newValue)
+    atom.config.toggle('tree-view.showOnRightSide')

   onSideToggled: (newValue) ->
     @detach()


fixes the lack of detail in test desc names and it blocks

--- test/transform-spec.coffee
+++ test/transform-spec.coffee
@@ -2,167 +2,171 @@

 ts = require '../lib/transform'

-describe 'Transform', ->
-  describe 'object parent property', ->
-    it 'should alias `parent*` to `__proto__`', ->
-      ts.parentProperty('parent*').should.eq '__proto__'
-      ts.parentProperty('parent*: foo,').should.eq '__proto__: foo,'
-      ts.parentProperty('bar.parent* = foo').should.eq 'bar.__proto__ = foo'
-      # Regex test cases - http://regexr.com?342i2
+describe 'method parentProperty', ->
+  it 'should transform `parent*` to `__proto__`', ->
+    ts.parentProperty('parent*').should.eq '__proto__'
+  it 'should work next to an object literal', ->
+    ts.parentProperty('parent*: foo,').should.eq '__proto__: foo,'
+  it 'should work when assigning to a method with dot opperator', ->
+    ts.parentProperty('bar.parent* = foo').should.eq 'bar.__proto__ = foo'
+    # Regex test cases - http://regexr.com?342i2

-  describe '#objLiteral', ->
-    it 'should transform `<` to `__proto__', ->
-      line = 'object bar < foo'
-      ts.objLiteral(line).should.eq 'var bar = { __proto__: foo,'
-      line = 'object _$bar < _f$oo'
-      ts.objLiteral(line).should.eq 'var _$bar = { __proto__: _f$oo,'
-      # Regex test cases - bit.ly/ZHcSKt
+describe 'method objLiteral', ->
+  it 'should transform `<` to `__proto__', ->
+    line = 'object bar < foo'
+    ts.objLiteral(line).should.eq 'var bar = { __proto__: foo,'
+    line = 'object _$bar < _f$oo'
+    ts.objLiteral(line).should.eq 'var _$bar = { __proto__: _f$oo,'
+    # Regex test cases - bit.ly/ZHcSKt

-  describe '#insertVars', ->
-    it 'should insert `var` when needed', ->
-      line = 'foo = true'
-      ts.insertVars(line, []).should.eq 'var foo = true'
-    it 'should work with multiple declarations', ->
-      line = 'foo = true; bar = false'
-      ts.insertVars(line, []).should.eq 'var foo = true; var bar = false'
-    it 'should work if one var is decalred', ->
-      line = 'foo = true; var bar = 20'
-      ts.insertVars(line, []).should.eq 'var foo = true; var bar = 20'
-    it 'should skip over declarations with var', ->
-      line = 'var baz = 10'
-      ts.insertVars(line, []).should.eq 'var baz = 10'
-    it 'should only assign first in mult assignment', ->
-      line = 'foo = baz = 10'
-      ts.insertVars(line, []).should.eq 'var foo = baz = 10'
-    it 'shouldnt match `==` or `===`', ->
-      line = 'baz == 10'
-      ts.insertVars(line, []).should.eq 'baz == 10'
-      line = 'baz === 10'
-      ts.insertVars(line, []).should.eq 'baz === 10'
-    it 'shouldnt insert var on propertys or instance vars', ->
-      line = '@foo = 20'
-      ts.insertVars(line, []).should.eq '@foo = 20'
-      line = 'foo.bar = 20'
-      ts.insertVars(line, []).should.eq 'foo.bar = 20'
-    it 'shouldnt insert a var if its already declared', ->
-      vstate = []
-      line1 = 'foo = 12'
-      line2 = 'foo = 20'
-      ts.insertVars(line1, vstate).should.eq 'var foo = 12'
-      ts.insertVars(line2, vstate).should.eq 'foo = 20'
-      # Regex test cases - http://regexr.com?342j9
+describe 'method insertVars', ->
+  it 'should insert `var` when needed', ->
+    line = 'foo = true'
+    ts.insertVars(line, []).should.eq 'var foo = true'
+  it 'should work with multiple declarations', ->
+    line = 'foo = true; bar = false'
+    ts.insertVars(line, []).should.eq 'var foo = true; var bar = false'
+  it 'should work if one var is decalred', ->
+    line = 'foo = true; var bar = 20'
+    ts.insertVars(line, []).should.eq 'var foo = true; var bar = 20'
+  it 'should skip over declarations with var', ->
+    line = 'var baz = 10'
+    ts.insertVars(line, []).should.eq 'var baz = 10'
+  it 'should only assign first in mult assignment', ->
+    line = 'foo = baz = 10'
+    ts.insertVars(line, []).should.eq 'var foo = baz = 10'
+  it 'shouldnt match `==` or `===`', ->
+    line = 'baz == 10'
+    ts.insertVars(line, []).should.eq 'baz == 10'
+    line = 'baz === 10'
+    ts.insertVars(line, []).should.eq 'baz === 10'
+  it 'shouldnt insert var on propertys or instance vars', ->
+    line = '@foo = 20'
+    ts.insertVars(line, []).should.eq '@foo = 20'
+    line = 'foo.bar = 20'
+    ts.insertVars(line, []).should.eq 'foo.bar = 20'
+  it 'shouldnt insert a var if its already declared', ->
+    vstate = []
+    line1 = 'foo = 12'
+    line2 = 'foo = 20'
+    ts.insertVars(line1, vstate).should.eq 'var foo = 12'
+    ts.insertVars(line2, vstate).should.eq 'foo = 20'
+    # Regex test cases - http://regexr.com?342j9

-  describe '#classes', ->
-    it 'should transform a single class', ->
-      line = 'class Foo'
-      ts.classes(line).should.eq 'var Foo = Class.extend({'
-    it 'should transform inheriting classes', ->
-      line = 'class Bar < Foo'
-      ts.classes(line).should.eq 'var Bar = Foo.extend({'
-    it 'should transform a class using dot notation', ->
-      line = 'class App.Model < Backbone.Model'
-      ts.classes(line).should.eq 'App.Model = Backbone.Model.extend({'
-      # Regex test cases - http://regexr.com?342po
+describe 'method classes', ->
+  it 'should transform a single class', ->
+    line = 'class Foo'
+    ts.classes(line).should.eq 'var Foo = Class.extend({'
+  it 'should transform inheriting classes', ->
+    line = 'class Bar < Foo'
+    ts.classes(line).should.eq 'var Bar = Foo.extend({'
+  it 'should transform a class using dot notation', ->
+    line = 'class App.Model < Backbone.Model'
+    ts.classes(line).should.eq 'App.Model = Backbone.Model.extend({'
+    # Regex test cases - http://regexr.com?342po

-  describe '#_super', ->
-    it 'should transform super to this._super', ->
-      line = 'super f00_$'
-      ts.callSuper(line).should.eq 'this._super(f00_$);'
-    it 'should transform without args', ->
-      line = 'super'
-      ts.callSuper(line).should.eq 'this._super();'
-    it 'should transform with multiple args', ->
-      line = 'super foo, bar, baz'
-      ts.callSuper(line).should.eq 'this._super(foo, bar, baz);'
-    it 'should not transform inside a string or comment', ->
-      line = ' "this is super cool" '
-      ts.callSuper(line).should.eq ' "this is super cool" '
-      # Regex test cases - http://regexr.com?342qs
+describe 'method _super', ->
+  it 'should transform super to this._super', ->
+    line = 'super f00_$'
+    ts.callSuper(line).should.eq 'this._super(f00_$);'
+  it 'should transform without args', ->
+    line = 'super'
+    ts.callSuper(line).should.eq 'this._super();'
+  it 'should transform with multiple args', ->
+    line = 'super foo, bar, baz'
+    ts.callSuper(line).should.eq 'this._super(foo, bar, baz);'
+  it 'should not transform inside a string or comment', ->
+    line = ' "this is super cool" '
+    ts.callSuper(line).should.eq ' "this is super cool" '
+    # Regex test cases - http://regexr.com?342qs

-  describe 'while loop', ->
-    it 'should transform correctly', ->
-      line = 'while foo < 200'
-      ts.whileLoop(line).should.eq 'while (foo < 200) {'
-      line = 'while (foo < 2) {moo}'
-      ts.whileLoop(line).should.eq 'while (foo < 2) {moo}'
-      line = 'while (x - 2) / 2'
-      ts.whileLoop(line).should.eq 'while ((x - 2) / 2) {'
-      # Regex test cases - http://bit.ly/X0vXdg
+describe 'method whileLoop', ->
+  it 'should transform correctly', ->
+    line = 'while foo < 200'
+    ts.whileLoop(line).should.eq 'while (foo < 200) {'
+  it 'should not compile a while loop with brackets', ->
+    line = 'while (foo < 2) {moo}'
+    ts.whileLoop(line).should.eq 'while (foo < 2) {moo}'
+  it 'should accept parens as its parameter', ->
+    line = 'while (x - 2) / 2'
+    ts.whileLoop(line).should.eq 'while ((x - 2) / 2) {'
+    # Regex test cases - http://bit.ly/X0vXdg

-  describe 'until loop', ->
-    it 'should transform correctly', ->
-      line = 'until foo === 5'
-      ts.untilLoop(line).should.eq 'while (!( foo === 5 )) { //until'
-      line = 'until (x - 2) / 2'
-      ts.untilLoop(line).should.eq 'while (!( (x - 2) / 2 )) { //until'
-      # Regex test cases - http://bit.ly/106gt3K
+describe 'method untilLoop', ->
+  it 'should transform into a while loop with comment', ->
+    line = 'until foo === 5'
+    ts.untilLoop(line).should.eq 'while (!( foo === 5 )) { //until'
+  it 'should accept parens as a parameter', ->
+    line = 'until (x - 2) / 2'
+    ts.untilLoop(line).should.eq 'while (!( (x - 2) / 2 )) { //until'
+    # Regex test cases - http://bit.ly/106gt3K

-  describe 'for in loop using range operator', ->
-    it 'should transform with two dots', ->
-      line = 'for i in 0..5'
-      ts.forLoopRange(line).should.eq 'for (var i=0; i < 5; i++) {'
-    it 'should transform with three dots', ->
-      line = 'for i in 0...5'
-      ts.forLoopRange(line).should.eq 'for (var i=0; i <= 5; i++) {'
-    it 'should handle variables', ->
-      line = 'for ct in b..e'
-      ts.forLoopRange(line).should.eq 'for (var ct=b; ct < e; ct++) {'
-      # Regex test cases - bit.ly/16Ofgn2
+describe 'method forLoopRang', ->
+  it 'should transform for in with range to for loop', ->
+    line = 'for i in 0..5'
+    ts.forLoopRange(line).should.eq 'for (var i=0; i < 5; i++) {'
+  it 'should transform with three dots', ->
+    line = 'for i in 0...5'
+    ts.forLoopRange(line).should.eq 'for (var i=0; i <= 5; i++) {'
+  it 'should handle variables', ->
+    line = 'for ct in b..e'
+    ts.forLoopRange(line).should.eq 'for (var ct=b; ct < e; ct++) {'
+    # Regex test cases - bit.ly/16Ofgn2

-  describe 'normal for in', ->
-    it 'should transform correctly', ->
-      line = 'for key in obj'
-      ts.forIn(line).should.eq 'for (var key in obj) {'
-    it 'should transform with an obj literal', ->
-      line = 'for key in {one: 1}'
-      ts.forIn(line).should.eq 'for (var key in {one: 1}) {'
-      # Regex test cases - bit.ly/ZkxXHv
+describe 'method forIn', ->
+  it 'should transform correctly', ->
+    line = 'for key in obj'
+    ts.forIn(line).should.eq 'for (var key in obj) {'
+  it 'should transform with an obj literal', ->
+    line = 'for key in {one: 1}'
+    ts.forIn(line).should.eq 'for (var key in {one: 1}) {'
+    # Regex test cases - bit.ly/ZkxXHv

-  describe 'for in array', ->
-    it 'should transform correctly', ->
-      line = 'for fruit inArr basket'
-      ts.forInArr(line).should.eq 'for (var i_=0, len=basket.length; i_ < len; i_++) { var fruit = basket[i_];'
-    it 'should itterate index for multiple for `inArr` statements'
-      # Regex test cases - bit.ly/WPApt4
+describe 'method forInArr', ->
+  it 'should transform into a for loop', ->
+    line = 'for fruit inArr basket'
+    ts.forInArr(line).should.eq 'for (var i_=0, len=basket.length; i_ < len; i_++) { var fruit = basket[i_];'
+  it 'should itterate index for multiple for `inArr` statements'
+    # Regex test cases - bit.ly/WPApt4

-  describe 'for key,val in array', ->
-    it 'should transform correctly', ->
-      line = 'for key,val in obj'
-      ts.forKeyVal(line).should.eq 'for (var key in obj) { var val = obj[key];'
-      line = 'for   $k ,   _v in users'
-      ts.forKeyVal(line).should.eq 'for (var $k in users) { var _v = users[$k];'
-      # Regex test cases - bit.ly/13r7y3b
+describe 'method forKeyVal', ->
+  it 'should transform key and value correctly', ->
+    line = 'for key,val in obj'
+    ts.forKeyVal(line).should.eq 'for (var key in obj) { var val = obj[key];'
+    line = 'for   $k ,   _v in users'
+    ts.forKeyVal(line).should.eq 'for (var $k in users) { var _v = users[$k];'
+    # Regex test cases - bit.ly/13r7y3b

-  describe 'func shorthand', ->
-    it 'should transfrom with name and params', ->
-      line = 'func foo (a, b)'
-      ts.func(line).should.eq 'var foo = function(a, b) {'
-    it 'should pass with name & empty parens', ->
-      line = 'func foo()'
-      ts.func(line).should.eq 'var foo = function() {'
-    it 'should transfrom with name & without params', ->
-      line = 'func bar'
-      ts.func(line).should.eq 'var bar = function() {'
-    it 'should transfrom without name & with params', ->
-      line = 'func(a, b)'
-      ts.func(line).should.eq 'function(a, b) {'
-    it 'it should pass without name and empty parens', ->
-      line = 'func()'
-      ts.func(line).should.eq 'function() {'
-    it 'should transfrom without name & without params', ->
-      line = 'func bar'
-      ts.func(line).should.eq 'var bar = function() {'
-    it 'should transform with just func keyword', ->
-      line = 'func'
-      ts.func(line).should.eq 'function() {'
-    it 'should pass using parens', ->
-      ts.func('func foo()').should.eq 'var foo = function() {'
-      ts.func('func $bar(foo)').should.eq 'var $bar = function(foo) {'
-      ts.func('func $bar(foo, bar)').should.eq 'var $bar = function(foo, bar) {'
-    it 'should have optional parens', ->
-      ts.func('func bar').should.eq 'var bar = function() {'
-      ts.func('func $_bar').should.eq 'var $_bar = function() {'
-      # Regex test cases - bit.ly/YBld3l
+describe 'method func', ->
+  it 'should transform with name and params', ->
+    line = 'func foo (a, b)'
+    ts.func(line).should.eq 'var foo = function(a, b) {'
+  it 'should pass with name & empty parens', ->
+    line = 'func foo()'
+    ts.func(line).should.eq 'var foo = function() {'
+  it 'should transfrom with name & without params', ->
+    line = 'func bar'
+    ts.func(line).should.eq 'var bar = function() {'
+  it 'should transfrom without name & with params', ->
+    line = 'func(a, b)'
+    ts.func(line).should.eq 'function(a, b) {'
+  it 'it should pass without name and empty parens', ->
+    line = 'func()'
+    ts.func(line).should.eq 'function() {'
+  it 'should transfrom without name & without params', ->
+    line = 'func bar'
+    ts.func(line).should.eq 'var bar = function() {'
+  it 'should transform with just func keyword', ->
+    line = 'func'
+    ts.func(line).should.eq 'function() {'
+  it 'should pass using parens', ->
+    ts.func('func foo()').should.eq 'var foo = function() {'
+    ts.func('func $bar(foo)').should.eq 'var $bar = function(foo) {'
+    ts.func('func $bar(foo, bar)').should.eq 'var $bar = function(foo, bar) {'
+  it 'should have optional parens', ->
+    ts.func('func bar').should.eq 'var bar = function() {'
+    ts.func('func $_bar').should.eq 'var $_bar = function() {'
+    # Regex test cases - bit.ly/YBld3l

 describe 'method comments', ->
   it 'should transform # comments into // comments', ->


Consolidate class initialization into a new @initialize() method.

--- src/control.coffee
+++ src/control.coffee
@@ -139,12 +139,14 @@
       # Property value implicitly handed to content() property.
       properties = content: properties

+    @initialize()
+
     $controls
       # Save a reference to the controls' class.
-      .controlClass( this )
+      .controlClass( @ )
       # Apply all class names in the class hierarchy as style names.
       # This lets the element pick up styles defined by those classes.
-      .addClass( cssClasses this )
+      .addClass( @::classes )
       # Render controls as DOM elements.
       .render()
       # Pass in the target's old contents ( if any ).
@@ -153,7 +155,7 @@
       .properties properties

     # Let each control initialize itself.
-    initialize this, $control for $control in $controls.segments()
+    initialize @, $control for $control in $controls.segments()

     # Return the new controls
     $controls
@@ -211,6 +213,26 @@
   ###
   initialize: ->

+
+  ###
+  A *class* method to force initialization of the data associated with a class.
+  A class will be implicitly initialized if an attempt is made to instantiate
+  it. However, if a tool wants to statically inspect a class without
+  instantiating it, this method can be called to ensure the class is properly
+  set up. Invoking this method more than once does no harm.
+
+  The class members set up in this method are:
+  .className: the name of the class
+  .classes: all CSS class names applied to new instances of that control class
+  ###
+  @initialize: ->
+    unless @::hasOwnProperty "className"
+      # Get class name from function in modern browser, otherwise parse constructor.
+      @::className = @name ? /function\s+([^\( ]*)/.exec( @toString() )[1]
+    unless @::hasOwnProperty "classes"
+      superclass = @superclass()
+      superclass.initialize() # Initialize superclass first.
+      @::classes = @::className + " " + superclass::classes

   ###
   Rendering a control lets each class in the control class' hierarchy,
@@ -344,21 +366,6 @@

 ###
-Return a class' "classes" member, which reflects the CSS classes that should be
-applied to new instances of that control class. If a class doesn't yet define
-this member for itself, a default value is calculated which includes the
-control class' own name, followed by the "classes" member of its superclass.
-###
-cssClasses = ( classFn ) ->
-  unless classFn::hasOwnProperty "className"
-    # Get class name from function in modern browser, otherwise parse constructor.
-    classFn::className = classFn.name ? /function\s+([^\( ]*)/.exec( classFn.toString() )[1]
-  unless classFn::hasOwnProperty "classes"
-    classFn::classes = classFn::className + " " + cssClasses classFn.superclass()
-  classFn::classes
-
-
-###
 Invoke the initialize() method of each class in the control's class hierarchy,
 starting with the base class and working down.
 ###
@@ -405,4 +412,3 @@
 # Explicitly define Control as a window member if we have a window.
 # The check is in case we're running in a headless state, e.g., Node.
 window?.Control = Control
-


Fix errors handling message with un-handled subtypes that may or may not include a user field.

--- src/bot.coffee
+++ src/bot.coffee
@@ -1,4 +1,4 @@
-{Adapter, TextMessage, EnterMessage, LeaveMessage, TopicMessage, Message} = require.main.require 'hubot'
+{Adapter, TextMessage, EnterMessage, LeaveMessage, TopicMessage, Message, CatchAllMessage} = require.main.require 'hubot'

 SlackClient = require './client'

@@ -105,9 +105,14 @@
   message: (message) =>
     {text, user, channel, subtype, topic, bot} = message

+    subtype = subtype || 'message'
+
     # Hubot expects this format for TextMessage Listener
     bot.room = channel.id if bot
     user.room = channel.id if user
+    user = {
+      room: channel.id
+    } if !user && !bot

     # Direct messages
     if channel.id[0] is 'D'
@@ -117,6 +122,10 @@

     # Send to Hubot based on message type
     switch subtype
+
+      when 'message'
+        @robot.logger.debug "Received message: '#{text}' in channel: #{channel.name}, from: #{user.name}"
+        @receive new TextMessage(user, text, message.ts)

       when 'bot_message'
         @robot.logger.debug "#{bot.name} has joined #{channel.name}"
@@ -135,8 +144,9 @@
         @receive new TopicMessage user, message.topic, message.ts

       else
-        @robot.logger.debug "Received message: '#{text}' in channel: #{channel.name}, from: #{user.name}"
-        @receive new TextMessage(user, text, message.ts)
+        @robot.logger.debug "Received message: '#{text}' in channel: #{channel.name}, subtype: #{subtype}"
+        message.user = user
+        @receive new CatchAllMessage(message)


bit more robust against conversations without messages

--- core/app/backbone/views/conversations/conversation_item_view.coffee
+++ core/app/backbone/views/conversations/conversation_item_view.coffee
@@ -11,7 +11,8 @@
   templateHelpers: =>
     first_recipient: @otherRecipients[0].toJSON()
     recipients_comma: @otherRecipients.map((user) -> user.get('name')).join(', ')
-    reply: @model.get('last_message').sender.id != currentUser.id
+    reply: @model.get('last_message')? and
+           @model.get('last_message').sender.id != currentUser.id

   wholeElementClick: (e) ->
     url = @model.url()