Update cards-es.coffee

Update cards-es.coffee

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -371,6 +371,7 @@
         "Airen Cracken":
             name: "Airen Cracken"
             text: """Después de que realices un ataque, puedes elegir otra nave aliada a alcance 1. Esa nave puede llevar a cabo 1 acción gratuita."""
+            ship: "Z-95 Cazacabezas"
         "Delta Squadron Pilot":
             name: "Piloto del escuadrón Delta"
             ship: "Defensor TIE"
@@ -448,12 +449,18 @@
         # "CR90 Corvette (Crippled Fore)":
         #     name: "CR90 Corvette (Crippled Fore)"
         #     ship: "Corbeta CR90 (Proa)"
+        "Wild Space Fringer":
+            name: "Fronterizo del Espacio Salvaje"
+            ship: "YT-2400"
         "Dash Rendar":
             text: """Puedes ignorar obstáculos durante la fase de Activación y al realizar acciones."""
         '"Leebo"':
             text: """Cuando recibas una carta de Daño boca arriba, roba 1 carta de Daño adicional, resuelve 1 de ellas a tu elección y descarta la otra."""
         "Eaden Vrill":
             text: """Si efectúas un ataque con un armamento principal contra una nave que tenga fichas de Tensión, tiras 1 dado de ataque adicional."""
+        "Patrol Leader":
+            name: "Jefe de Patrulla"
+            ship: "VT-49 Diezmador"
         "Rear Admiral Chiraneau":
             name: "Contralmirante Chiraneau"
             text: """Cuando atacas a alcance 1-2, puedes cambiar 1 de tus resultados de %FOCUS% por un resultado %CRIT%."""
@@ -468,6 +475,7 @@
             ship: "VT-49 Diezmador"
         "Black Sun Enforcer":
             name: "Ejecutor del Sol Negro"
+            ship: "Víbora Estelar"
         "Black Sun Vigo":
             name: "Vigo del Sol Negro"
             ship: "Víbora Estelar"
@@ -494,14 +502,16 @@
             text: """Después de que efectúes un ataque que destruya al defensor, puedes recuperar 1 ficha de Escudos."""
             ship: "Agresor"
         "IG-88B":
-            text: """Una vez por ronda, después de que efectúes un ataque y lo falles, puedes efectuar un ataque con un sistema de armamento secundario %CANON% que tengas equipado."""
+            text: """Una vez por ronda, después de que efectúes un ataque y lo falles, puedes efectuar un ataque con un sistema de armamento secundario %CANNON% que tengas equipado."""
             ship: "Agresor"
         "IG-88C":
-            text: """Después de que realices una acción de impulso, puedes llevar a cabo una acción gratuita de evasión."""
+            text: """Después de que realices una acción de impulso, puedes llevar a cabo una acción gratuita de Evasión."""
             ship: "Agresor"
         "IG-88D":
             text: """Puedes ejecutar la maniobra (%SLOOPLEFT% 3) o (%SLOOPRIGHT% 3) utilizando la plantilla (%TURNLEFT% 3) o (%TURNRIGHT% 3) correspondiente."""
             ship: "Agresor"
+        "Mandalorian Mercenary":
+            name: "Mercenario Mandaloriano"
         "Boba Fett (Scum)":
             text: """Cuando ataques o te defiendas, puedes volver a tirar 1 de tus dados por cada nave enemiga que tengas a alcance 1."""
         "Kath Scarlet (Scum)":
@@ -857,7 +867,7 @@
             text: """Cuando ataques, puedes anular los resultados de todos tus dados. Después puedes añadir 2 resultados %HIT%.

Si decides hacerlo, no podrás volver a modificar tus dados durante este ataque."""
         "Inertial Dampeners":
             name: "Amortiguadores de Inercia"
-            text: """Cuando reveles tu maniobra, puedes descartar esta carta para ejecutar en su lugar una maniobra blanca [0%STOP%]. Despúes recibes 1 ficha de Tensión."""
+            text: """Cuando reveles tu maniobra, puedes descartar esta carta para ejecutar en su lugar una maniobra blanca [0%STOP%]. Después recibes 1 ficha de Tensión."""
         "Flechette Cannon":
             name: "Cañón de Fragmentación"
             text: """Ataque: Ataca a 1 nave.%LINEBREAK%Si este ataque impacta, el defensor sufre 1 de daño y, si no tiene asignada ninguna ficha de Tensión, recibe también 1 ficha de Tensión. Después se anulan todos los resultados de los dados."""
@@ -869,10 +879,10 @@
             text: """Cuando seas destruido, toda nave que tengas a alcance 1 sufre 1 daño."""
         "Feedback Array":
             name: "Transmisor de Sobrecargas"
-            text: """Durante la fase de Combate, en vez de efectuar ataques, puedes recibir 1 ficha de Iones y sufri 1 daño para elegir 1 nave enemiga a alcance 1. Esa nave sufre 1 daño."""
+            text: """Durante la fase de Combate, en vez de efectuar ataques, puedes recibir 1 ficha de Iones y sufrir 1 daño para elegir 1 nave enemiga a alcance 1. Esa nave sufre 1 daño."""
         '"Hot Shot" Blaster':
             name: "Cañón Bláster Desechable"
-            text: """Ataque: Descarta esta carta para atacar a 1 nave (aunque estéfuera de tu arco de fuego)."""
+            text: """Ataque: Descarta esta carta para atacar a 1 nave (aunque esté fuera de tu arco de fuego)."""
         "Greedo":
             text: """%SCUMONLY%

La primera vez que ataques cada ronda y la primera vez que te defiendas cada ronda, la primera carta de Daño inflingida será asignada boca arriba."""
         "Salvaged Astromech":
@@ -887,10 +897,16 @@
         "Unhinged Astromech":
             name: "Droide Astromecánico Desquiciado"
             text: """Puedes ejecutar todas las maniobras de velocidad 3 como maniobras verdes."""
+        "R4 Agromech":
+            name: "Droide Agromecánico R4"
+            text: """Cuando ataques, después de gastar una ficha de Concentración puedes fijar al defensor como blanco."""
+        "R4-B11":
+            text: """Cuando ataques, si tienes al defensor fijado como blanco, puedes gastar la ficha de Blanco Fijado para elegir cualquier o todos sus dados de defensa. El defensor debe volver a tirar los dados elegidos."""
         "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."""
         "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."""
+

     modification_translations =
         "Stealth Device":


Adding more snippets

Adding more snippets


Refactor generic handles to reduce code duplication

--- source/server/infrastructure/router.coffee
+++ source/server/infrastructure/router.coffee
@@ -46,11 +46,11 @@

   onDependenciesReady: ->
     super
-    @_setupInitializingMessage()
-    @_routeEventToEventSourceable(eventType) for eventType in @routeEvents
-    @_routeCommandToEventSourceable(commandType) for commandType in @routeCommands
+    @_setupInitializingHandler()
+    @_setupEventSubscriptions(eventType) for eventType in @routeEvents
+    @_setupCommandHandlers(commandType) for commandType in @routeCommands

-  _setupInitializingMessage: ->
+  _setupInitializingHandler: ->
     if @initializingMessage.isSubclassOf(Space.domain.Event)
       messageBus = @eventBus
       handlerSubscriber = @eventBus.subscribeTo
@@ -71,32 +71,24 @@
       @repository.save(eventSourceable) if eventSourceable?
     )

-  _routeEventToEventSourceable: (eventType) ->
-    @eventBus.subscribeTo eventType, @_genericEventHandler
+  _setupEventSubscriptions: (eventType) ->
+    @eventBus.subscribeTo eventType, @_genericHandler

-  _routeCommandToEventSourceable: (commandType) ->
-    @commandBus.registerHandler commandType, @_genericCommandHandler
+  _setupCommandHandlers: (commandType) ->
+    @commandBus.registerHandler commandType, @_genericHandler

-  _genericEventHandler: (event) =>
-    # Only route this event if the correlation property exists
-    return unless event.meta? and event.meta[this.eventCorrelationProperty]?
-    correlationId = event.meta[this.eventCorrelationProperty]
-    @log.debug(@_logMsg("Handling event #{event.typeName()} for
-                       #{@eventSourceable}<#{correlationId}>\n"), event)
-    eventSourceable = @repository.find @eventSourceable, correlationId
+  _genericHandler: (message) =>
+    if message instanceof Space.domain.Command
+      aggregateId = message.targetId
+    if message instanceof Space.domain.Event
+      # Only route this event if the correlation property exists
+      return unless aggregateId = message.meta? and message.meta[this.eventCorrelationProperty]
+    @log.debug(@_logMsg("Handling message #{message.typeName()} for
+                       #{@eventSourceable}<#{aggregateId}>\n"), message)
+    eventSourceable = @repository.find @eventSourceable, aggregateId
     @injector.injectInto(eventSourceable)
-    throw Router.ERRORS.cannotHandleMessage(event) if !eventSourceable?
-    eventSourceable = @_handleDomainErrors(-> eventSourceable.handle event)
-    @repository.save(eventSourceable) if eventSourceable?
-
-  _genericCommandHandler: (command) =>
-    if not command? then return
-    @log.debug(@_logMsg("Handling command #{command.typeName()} for
-                       #{@eventSourceable}<#{command.targetId}>"), command)
-    eventSourceable = @repository.find @eventSourceable, command.targetId
-    @injector.injectInto(eventSourceable)
-    throw Router.ERRORS.cannotHandleMessage(command) if !eventSourceable?
-    eventSourceable = @_handleDomainErrors(-> eventSourceable.handle command)
+    throw Router.ERRORS.cannotHandleMessage(message) if !eventSourceable?
+    eventSourceable = @_handleDomainErrors(-> eventSourceable.handle message)
     @repository.save(eventSourceable) if eventSourceable?

   _logMsg: (message) -> "#{@configuration.appId}: #{this}: #{message}"


fix `KDInputView` autogrow calculation procedure

--- components/inputs/KDInputView.coffee
+++ components/inputs/KDInputView.coffee
@@ -331,8 +331,7 @@
           wordBreak     : $input.css 'wordBreak'
           fontSize      : $input.css 'fontSize'
           lineHeight    : $input.css 'lineHeight'
-          whiteSpace    : "pre"
-          wordWrap      : "break-word"
+          whiteSpace    : "pre-line"

     @on "blur", =>
       @_clone.detach()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/collision.coffee
+++ src/collision.coffee
@@ -217,9 +217,10 @@
     rayRectangle: (source, direction, target) ->
       unless target.xw? and target.yw?
         if target.width? and target.height?
-        return Collision.rayRectangle source, direction,
-          xw: target.width/2
-          yw: target.height/2
+          return Collision.rayRectangle source, direction,
+            xw: target.width/2
+            yw: target.height/2
+        els

       xw = target.xw
       yw = target.yw


bodyText instead of rawBody

bodyText instead of rawBody

--- lib/package-snippets-view.coffee
+++ lib/package-snippets-view.coffee
@@ -55,7 +55,7 @@
           @tr =>
             @td class: 'snippet-prefix', prefix
             @td name
-            @td class: 'snippet-body', rawBody
+            @td class: 'snippet-body', bodyText

       if @snippets.children().length > 0
         @show()


Add trigger and accept string for url

Add trigger and accept string for url

--- components/SendRequest.coffee
+++ components/SendRequest.coffee
@@ -8,25 +8,26 @@
   c.icon = 'cog'
   c.description = 'Send request'

+  url = null
+  req = null
   # Add input ports
   c.inPorts.add 'url',
-    datatype: 'object'
+    datatype: 'all'
+    require: yes
+  c.inPorts.add 'start',
+    datatype: 'bang'
     require: yes

   # Add output ports
   c.outPorts.add 'request',
     datatype: 'object'

-  noflo.helpers.WirePattern c,
-    in: 'url'
-    out: 'request'
-    forwardGroups: true
-  , (data, groups, out) ->
-
-    req = coap.request data
-
+  c.inPorts.url.on 'data', (payload) ->
+    url = payload
+  c.inPorts.start.on 'data', ->
+    req = coap.request url
     req.end ->
-      out.send req
+      c.outPorts.request.send req

   # Finally return the component instance
   c


Fix the choice of the redirected URL

Fix the choice of the redirected URL

--- components/ToFullscale.coffee
+++ components/ToFullscale.coffee
@@ -55,8 +55,9 @@

 # Flickr redirects to a photo_unavailable image if the new URL do not exists
 tryRedirect = (original, redirected) ->
-  return redirected unless redirected.indexOf('staticflickr.com') isnt -1
-  return original if redirected.match /photo_unavailable/
+  if (original.indexOf('staticflickr.com') isnt -1) and
+  (redirected.indexOf('photo_unavailable') isnt -1)
+    return original
   return redirected

 exports.getComponent = ->
@@ -105,15 +106,11 @@
     .redirects(1)
     .end (err, res) ->
       return callback err if err
-      console.log 'URL', url
-      console.log 'new URL', newUrl
-      console.log 'status', res.statusCode
-      console.log 'res.redirects', res.redirects
       # If the newUrl exists, send it
       if res and res.statusCode is 200
         # Use redirection URL
-        if res.redirects?
-          newUrl = tryRedirect res.redirects[0], url
+        if res.redirects?.length > 0
+          newUrl = tryRedirect url, res.redirects[0]
         out.send newUrl
       # Otherwise, keep the original one
       else


uniqueness validation should ignore current record itself

--- src/tower/model/validator/uniqueness.coffee
+++ src/tower/model/validator/uniqueness.coffee
@@ -3,6 +3,7 @@
     value       = record.get(attribute)
     conditions  = {}
     conditions[attribute] = value
+    record.persistent && conditions["id"] = {"$ne": record.get("id")}
     record.constructor.where(conditions).exists (error, result) =>
       if result
         return @failure(


Using elapsed time in delayed events

Using elapsed time in delayed events

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -63,7 +63,7 @@

   @returns {Engine} self
   ###
-  delay: (steps, callback) ->
+  delay: (seconds, callback) ->
     delayedEvents.push
       delay: steps
       callback: callback


`Trix.UTF16String.box(value)` stringifies `value` first

--- src/trix/core/utilities/utf16_string.coffee
+++ src/trix/core/utilities/utf16_string.coffee
@@ -3,7 +3,7 @@
     if value instanceof this
       value
     else
-      @fromUCS2String(value)
+      @fromUCS2String(value?.toString())

   @fromUCS2String: (ucs2String) ->
     new this ucs2String, ucs2decode(ucs2String)


fix ticks in level 8

fix ticks in level 8

--- games/tutorial.coffee
+++ games/tutorial.coffee
@@ -163,7 +163,7 @@
       - programs.coffee : prefetch
       - sprites.coffee : collision
       '
-      goal: { clicks: 1, bricks: 2, ticks: 12 }
+      goal: { clicks: 1, bricks: 2, ticks: 11 }
       bricks: 1
       sprites: [
         { kind: 'log', position: [5,0] }


Sped up reki

Sped up reki

--- src/tracker/reki.coffee
+++ src/tracker/reki.coffee
@@ -3,7 +3,7 @@
 url  = require 'url'
 qs   = require 'querystring'
 fs   = require 'fs'
-bencode = require '../../lib/bencode'
+bencode2 = require '../../lib/bencode'

 # defines
 ANNOUNCE_INTERVAL = 60
@@ -12,10 +12,16 @@
 # helper functions
 simple_response = (res, data) ->
   res.setHeader 'Content-Type', 'text/plain'
-  res.end (bencode.bencode data)
-
+  res.end (bencode data)
+
+compact = (ip, port) -> (new Buffer ip.split('.').concat([port >> 8, port & 0xFF])).toString 'base64'
 #compact = (ip, port) -> ip.split('.').concat([port >> 8 & 0xFF, port & 0xFF]).map((c) -> String.fromCharCode c)
-compact = (ip, port) -> new Buffer ip.split('.').concat([port >> 8, port & 0xFF])
+#compact = (buf, ip, port) ->
+#  comp = ip.split '.'
+#  for i in [0..3]
+#    buf[20+i] = parseInt(comp[i])
+#  buf[24] = port >> 8
+#  buf[25] = port & 0xFF

 parse = (qs) ->
   obj = {}
@@ -24,30 +30,48 @@
   obj

 #bencode.bencode = (o) -> new Buffer 1
-#bencode = (o) ->
-#  bstring = (s) -> s.length + ':' + s
-#  bint = (n) -> "i#{n}e"
-#  blist = (l) ->
-#    buf = 'l'
-#    buf += bencode i for i in l
-#    buf + 'e'
-#  bdict = (d) ->
-#    buf = 'd'
-#    buf += bstring(k) + bencode(d[k]) for k in Object.keys(d).sort()
-#    buf + 'e'
-#
-#  switch typeof o
-#    when 'string' then bstring o
-#    when 'number' then bint    o
-#    when 'object'
-#      if o instanceof Array
-#        blist o
-#      else if o?
-#        bdict o
-#      else
-#        throw new Error('cannot encode element null')
-#    else
-#      throw new Error('cannot encode element ' + o)
+bencode_str = (o) ->
+  #return bencode2.bencode o
+  bstring = (s) -> s.length + ':' + s
+  bint = (n) -> "i#{n}e"
+  blist = (l) ->
+    buf = 'l'
+    buf += bencode_str i for i in l
+    buf + 'e'
+  bdict = (d) ->
+    buf = 'd'
+    buf += bstring(k) + bencode_str(d[k]) for k in Object.keys(d).sort()
+    buf + 'e'
+  bbuffer = (b) ->
+    out = b.length + ':'
+    if b.length == 0
+      return out
+    for i in [0..b.length-1]
+      out = out + String.fromCharCode(b[i])
+    return out
+
+  switch typeof o
+    when 'string' then out = bstring o
+    when 'number' then out = bint    o
+    when 'object'
+      if o instanceof Array
+        out = blist o
+      else if o instanceof Buffer
+        out = bbuffer o
+      else if o?
+        out = bdict o
+      else
+        throw new Error('cannot encode element null')
+    else
+      throw new Error('cannot encode element ' + o)
+  return out
+bencode = (o) ->
+  out = bencode_str o
+  out_buf = new Buffer out.length
+  for i in [0..out.length-1]
+    out_buf[i] = out.charCodeAt i
+  return out_buf
+

 decodeURLtoHex = (str) ->
   output = ''
@@ -59,24 +83,11 @@
       output += (str.charCodeAt i).toString 16
   return output.toLowerCase()

-decodeURLtoBinary = (str, len) ->
-  b = new Buffer len
-  j = 0
-  for i in [0..str.length-1]
-    if (str.charCodeAt i)== 37
-      b[j] = parseInt(str[i+1] + str[i+2], 16)
-      i+=2
-    else
-      b[j] = str.charCodeAt i
-    j += 1
-  return b
-
 # Tracker class
 class Tracker
   constructor: (port) ->
     # redis
-    @redis = require('redis').createClient 6379, '127.0.0.1', {'return_buffers' : true}
-    @redis.on 'ready', -> console.log 'connected to redis'
+    @redis = require('redis').createClient 6379, '127.0.0.1' #, {'return_buffers' : true})

     # mongodb
     @torrents = null
@@ -110,56 +121,55 @@
     #   info_hash, peer_id, port, uploaded, downloaded, left,   <--- REQUIRED
     #   compact, no_peer_id, event, ip, numwant, key, trackerid <--- optional
     info_hash = decodeURLtoHex get_vars['info_hash']
-    peer_id = decodeURLtoBinary get_vars['peer_id'], 20

     port = parseInt get_vars['port']
     left = parseInt get_vars['left']
-    if info_hash == '' or peer_id == '' or isNaN(port) or isNaN(left)
+    if info_hash == '' or get_vars['peer_id'] == '' or isNaN(port) or isNaN(left)
       return simple_response res, {'failure reason': 'Invalid Request'}

     key = 'torrent:' + info_hash
+    key_peer = key + ':peers'
+    key_seed = key + ':seeds'
+
+    #get redis etc..
+    redis = @redis

     check_exists = (callback) =>
-      @redis.ZCARD (key + ':seeds'), (err, data)=>
+      #callback()
+      #return
+      redis.ZCARD key_seed, (err, data)=>
         if data != 0
           callback()
         else
-          @redis.ZCARD (key + ':peers'), (err, data)=>
+          redis.ZCARD key_peer, (err, data)=>
             if data != 0
               callback()
             else
+              console.log 'mongo'
               @torrents.findOne {'infohash' : info_hash}, (err, exists) =>
                 if exists
                   callback()
                 else
                   return simple_response res, {'failure reason': 'This torrent does not exist'}
+
     check_exists =>
       event = get_vars['event']
       if event == 'stopped' or event == 'paused'
         return simple_response res, 'Nani?'

       t = Date.now()
-      multi = @redis.multi()
-        .ZREMRANGEBYSCORE(key + ':seeds', 0, t - ANNOUNCE_INTERVAL * 6000)
-        .ZREMRANGEBYSCORE(key + ':peers', 0, t - ANNOUNCE_INTERVAL * 6000)
+      multi = redis.multi()
+        .ZREMRANGEBYSCORE(key_seed, 0, t - ANNOUNCE_INTERVAL * 6000)
+        .ZREMRANGEBYSCORE(key_peer, 0, t - ANNOUNCE_INTERVAL * 6000)

       ip = get_vars['ip'] #REALLY SHOULD CHECK THIS IS A VALID IP
       ip = req.client.remoteAddress if !ip?
       #ip = process.env['HTTP_X_REAL_IP'] if ip is '127.0.0.1'
-
-      peer_entry = new Buffer 46
-      peer_id.copy peer_entry, 0, 0, 20
-      compact(ip, port).copy peer_entry, 20, 0, 6
-      if ip.length > 15
-        console.log "You have major problems. Please seek help"
-        return
-      for i in [26..45]
-        peer_entry[i] = 0
-      peer_entry.write ip, 26
-      peer_entry.write port.toString(), 41
+
+      peer_entry = { 'ip' : ip, 'port' : port, 'peer_id' : get_vars['peer_id'], 'compacted' : compact(ip, port) }

       suffix = if (left == 0) then ":seeds" else ":peers"
-      multi.ZADD key + suffix, t, peer_entry
+      multi.ZADD key + suffix, t, JSON.stringify(peer_entry)

       if event == 'completed' #increment snatch
         @torrents.update {'infohash': info_hash}, {$inc: {'snatches': 1}}
@@ -170,30 +180,29 @@

       numwant = parseInt get_vars['numwant']
       numwant = 50 if isNaN(numwant) or numwant < 0 or numwant > 50
-
+
       multi
-        .ZCARD(key + ':seeds')
-        .ZCARD(key + ':peers')
-        .ZRANGE(key + ':seeds', 0, numwant)
-        .ZRANGE(key + ':peers', 0, numwant)
+        .ZCARD(key_seed)
+        .ZCARD(key_peer)
+        .ZRANGE(key_seed, 0, numwant)
+        .ZRANGE(key_peer, 0, numwant)
         .exec (err, replies) =>
-          #res.end 'hi'
-          #return
+
           if err? then return simple_response res, {'failure reason': 'wat'}
           doCompact = get_vars['compact'] is '1'

           peerlist = replies[5].concat(replies[6])
             .slice(0, numwant)
             .map (p) ->
+              peer_entry = JSON.parse p
               if doCompact
-                return p.slice 20, 26
+                return peer_entry['compacted']
               else
-                return {'peer id': p.slice(0, 20), 'ip' : p.slice(26,41) , 'port' : p.slice(41,46)}
+                delete peer_entry['compacted']
+                return peer_entry
           if doCompact
-            out = new Buffer (peerlist.length*6)
-            for i in [0..peerlist.length-1]
-              peerlist[i].copy out, i*6, 0
-            peerlist = out
+            peerlist = peerlist.join ''
+            peerlist = new Buffer peerlist, 'base64'

           return simple_response res,
             'interval'     : ANNOUNCE_INTERVAL
@@ -202,5 +211,6 @@
             'min interval' : MIN_INTERVAL
             'peers'        : peerlist

-console.log 'Starting tracker on 127.0.0.1:6969...'
-new Tracker 6969
+port = parseInt(process.argv[2]) || 6969
+console.log ('Starting tracker on 127.0.0.1:' + port + '...')
+new Tracker port


Cleanup

Cleanup

--- src/transparency.coffee
+++ src/transparency.coffee
@@ -96,10 +96,9 @@
     #throw new Error "Transparency: Directives should be two-dimensional objects, e.g., directive[element][attribute] = function(){}"

     for element in matchingElements instance, key
+      for attribute, directive of attributes when typeof directive == 'function'

-      for attribute, directive of attributes when typeof directive == 'function'
         value = directive.call (if typeof model == 'object' then model else value: model), element, index
-
         setAttribute element, attribute, value if value

 renderChildren = (instance, model, directives) ->
@@ -170,8 +169,7 @@
 # jQuery.clone: https://github.com/jquery/jquery/blob/master/src/manipulation.js#L594
 # jQuery.support.html5Clone: https://github.com/jquery/jquery/blob/master/src/support.js#L83
 html5Clone = () -> document.createElement("nav").cloneNode( true ).outerHTML != "<:nav></:nav>"
-
-cloneNode = if not html5Clone() then (node) -> jQuery(node).clone()[0] else (node) -> node.cloneNode true
+cloneNode  = if not html5Clone() then (node) -> jQuery(node).clone()[0] else (node) -> node.cloneNode true

 Array.isArray  ?= (obj) -> jQuery.isArray obj
 Array::indexOf ?= (obj) -> jQuery.inArray obj, this


Fix typo.

Fix typo.

--- src/transparency.coffee
+++ src/transparency.coffee
@@ -41,7 +41,7 @@
   models = [models] unless isArray models

   # Rendering is a lot faster when the `context` is detached from the DOM, as
-  # noreflow calculations are triggered. However, we need to save reference
+  # reflow calculations are not triggered. However, we need to save reference
   # to `parent` and `nextSibling` in order to attach the `context` back once we're done.
   parent = context.parentNode
   if parent
@@ -182,7 +182,6 @@
     #       <img class="trusted" src="http://trusted.com/funny.gif" />
     #     </div>
     #
-    #
     # Directives are executed after the default rendering, so that they can be used for overriding default rendering.
     renderDirectives instance, model, index, directives


fix a couple UX issues with clicking the add new proposal shortcut in list header

--- @client/homepage.coffee
+++ @client/homepage.coffee
@@ -700,7 +700,7 @@
                   display: if @local.hover_label or is_collapsed then 'inline-block' else 'none'
                   outline: 'none'

-            if customization('list_show_new_button', cluster_key) && !is_collapsed
+            if customization('list_show_new_button', cluster_key) && !is_collapsed && @props.proposals_count > 4
               permitted = permit('create proposal')
               if permitted > 0 || permitted == Permission.NOT_LOGGED_IN
                 A
@@ -711,10 +711,11 @@
                     fontFamily: customization('font')
                     fontStyle: 'normal'
                     marginLeft: 14
-                  onClick: =>
+                  onClick: (e) =>
                     show_all = fetch('show_all_proposals')
                     show_all.show_all = true
                     save show_all
+                    e.stopPropagation()

                     setTimeout =>
                       $("[name='add_new_#{cluster.name}']").ensureInView()


EnvironmentMachineStateModal: show different message when a vm terminated in aws but not in Koding [completes #104409880]

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -420,10 +420,13 @@
       @showRandomMarketingSnippet()  if @state is Starting
     else if @state is Terminated
       @label.destroy?()
-      @createStateLabel "
+      @createStateLabel if isKoding() then "
         The VM #{@machineName or ''} was
         successfully deleted. Please select a new VM to operate on from
         the VMs list or create a new one.
+      " else "
+        The VM #{@machineName or ''} was terminated.
+        Please re-initalize your stack to rebuild the VM again.
       "
       @createStateButton()
     else if @state is Running
@@ -547,10 +550,13 @@
     return  if @isManaged

     if @state in [Terminated, 'NotFound']
-      title    = 'Create a new VM'
       callback = 'requestNewMachine'

-      if not isKoding()
+      if isKoding()
+        title  = 'Create a new VM'
+      else
+        title  = 'Show Stacks'
+
         { groupsController } = kd.singletons
         return  unless groupsController.currentGroupHasStack()


activity: improve tag transformation procedure to look(before|ahead)

--- client/Social/Activity/views/activitylistitem.coffee
+++ client/Social/Activity/views/activitylistitem.coffee
@@ -95,8 +95,12 @@
     {slug}   = KD.getGroup()
     prefix   = "#{origin}#{if slug is 'koding' then '' else '/' + slug}"

-    return text.replace /#(\w+)/g, (match, tag) ->
-      tag = Encoder.XSSEncode tag
+    return text.replace /#(\w+)/g, (match, tag, offset) ->
+      pre  = text[offset - 1]
+      post = text[offset + match.length]
+      return match  if pre  isnt ' ' and offset isnt 0
+      return match  if post isnt ' ' and (offset + match.length) isnt text.length
+      return match  if pre  is '&'
       return "[##{tag}](#{prefix}/Activity/Topic/#{tag})"


Close modal when subscription is done.

Close modal when subscription is done.

--- client/landing/app/CommonViews/paymentwidgets.coffee
+++ client/landing/app/CommonViews/paymentwidgets.coffee
@@ -108,4 +108,6 @@
           Yes         :
             title     : "Yes, subscribe"
             cssClass  : "modal-clean-green"
-            callback  : callback
+            callback  : =>
+              modal.destroy()
+              callback()


Check if app exists before calling ints quit function, Chris can u check?

--- client/landing/app/MainApp/ApplicationManager.coffee
+++ client/landing/app/MainApp/ApplicationManager.coffee
@@ -30,7 +30,7 @@

   quitApplication:(path)->
     app = @getAppInstance path
-    if typeof app.quit is "function"
+    if app and typeof app.quit is "function"
       app.quit? ->
         @removeAppInstance path
       setTimeout ->


Rework get group name from location function

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- client/landing/site.landing/coffee/core/utils.coffee
+++ client/landing/site.landing/coffee/core/utils.coffee
@@ -60,31 +60,40 @@

   getGroupNameFromLocation: (hostname) ->

-    { hostname }     = location  unless hostname
-    ipV4Pattern      = '(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.xip\.io$'
-    subDomainPattern = '[A-Za-z0-9_]{1}[A-Za-z0-9_-]+'
-    kodingDomains    = '(?:dev|sandbox|latest|prod)'
-
-    # Base Domain may include port information which we don't need ~GG
-    baseDomain       = (kd.config.domains.main.split ':')[0]
-    baseDomain       = "#{baseDomain}".replace /\./g, '\\.'
-
-    # e.g. [teamName.]dev|sandbox|latest|prod.koding.com
-    teamPattern = if ///#{kodingDomains}\.#{baseDomain}$///.test hostname
-    then ///(?:^(#{subDomainPattern})\.)?#{kodingDomains}\.#{baseDomain}$///
-    # e.g. [teamName.]koding.com
-    else if ///#{baseDomain}$///.test hostname
-    then ///(?:^(#{subDomainPattern})\.)?#{baseDomain}$///
-    # e.g. [teamName.]<teamMember>.koding.team
-    else if /koding\.team$/.test hostname
-    then ///(?:^(#{subDomainPattern})\.)?(?:#{subDomainPattern}\.)koding\.team$///
-    # e.g. [teamName.]<vm-ip>.xip.io
-    else ///(?:^(#{subDomainPattern})\.)?#{ipV4Pattern}///
-
-    matches  = hostname.match teamPattern
-    teamName = matches?[1] or 'koding'
-
-    return teamName
+    { hostname } = location  unless hostname
+    basePattern  = (kd.config.domains.main.split ':')[0].replace /\./g, '\\.'
+
+    predefinedPatterns = [
+      # <team-domain>.<(dev|sandbox|latest|prod)>.koding.com
+      { predict: "(dev|sandbox|latest|prod)\.#{basePattern}" }
+
+      # <team-domain>.koding.com
+      # <team-domain>.<username>.koding.team
+      { predict: "#{basePattern}" }
+
+      # <team-domain>.<IPv4>.xip.io
+      # IPV4: A.B.C.D
+      {
+        predict: 'xip\.io'
+        labelFn: (hostname) ->
+          labels = hostname.split('.')
+          labels.splice -6, 6 # 'A.B.C.D.xip.io'.length
+          return labels.pop()
+      }
+    ]
+
+    for pattern in predefinedPatterns
+
+      { predict, labelFn } = pattern
+
+      unless labelFn
+        labelFn = do (predict) -> (hostname) ->
+          hostname.replace(new RegExp("\.?#{predict}$"), '').split('.').pop()
+
+      if hostname.match new RegExp "#{predict}$"
+        return labelFn(hostname) or 'koding'
+
+    return 'koding'

   checkIfGroupExists: (groupName, callback) ->


Rename misleading mock data in indicator selector view tests

--- client/test/src/views/indicator_selector_view.coffee
+++ client/test/src/views/indicator_selector_view.coffee
@@ -86,7 +86,7 @@
     _id: Factory.findNextFreeId('Section')
   )

-  indicators = [{_id: 1, name: "Such Theme"}]
+  indicators = [{_id: 1, name: "Such Indicator"}]

   @stub(Backbone.Collections.IndicatorCollection::, 'fetch', ->
     Helpers.promisify(=>


Removed unused User#channels

Removed unused User#channels

--- core/app/backbone/models/user.coffee
+++ core/app/backbone/models/user.coffee
@@ -1,11 +1,10 @@
 class window.User extends Backbone.Model
   initialize: ->
-    @channels = []
     @following = new Following([], user: @)

     @following.on 'all', => @trigger 'follow_action'

-  setChannels: (channels) -> @channels = channels
+  setChannels: (channels) ->

   url: ->
     # We do this because we cannot (yet) set the idAttribute to "username"


fix iterator errors in lru-map

fix iterator errors in lru-map

--- src/util/lru-map.coffee
+++ src/util/lru-map.coffee
@@ -95,7 +95,7 @@
 		iter = @_map.values()

 		return {
-			next: -> iter.next().value?.value
+			next: -> {value: iter.next().value?.value}
 		}

 	entries: ->
@@ -106,9 +106,9 @@
 				entry = iter.next().value

 				if entry?
-					return [entry[0], entry[1].value]
+					return {value: [entry[0], entry[1].value]}
 				else
-					return undefined
+					return {}
 		}

 	forEach: (callback, thisArg) ->


Removed debug outputs.

Removed debug outputs.


calculation for frames

calculation for frames


Performance optimization

Performance optimization

--- fetching.coffee
+++ fetching.coffee
@@ -68,6 +68,7 @@
     _qsByRequester: {} # computationId: querySpecString: true
     _requestersByQs: {} # querySpecString: {computationId: computation}
     _requestsChanged: false
+    _changesByModelQs: {} # modelName: querySpecString: netNumComputationsAdded

     # Snapshot of what the cursors will be after the next _updateDataQueries returns
     _nextUnmergedQsSet: {} # querySpecString: true
@@ -79,6 +80,14 @@

 _.extend J.fetching,
+    _incChanges: (modelName, qss, inc) ->
+        @_changesByModelQs[modelName] ?= {}
+        newValue = (@_changesByModelQs[modelName][qss] ? 0) + inc
+        if newValue is 0
+            delete @_changesByModelQs[modelName][qss]
+        else
+            @_changesByModelQs[modelName][qss] = newValue
+
     _addComputationQsRequest: (computation, qs) ->
         qsString = @stringifyQs qs

@@ -106,6 +115,7 @@
         for addedQss in qsStringsDiff.added
             if addedQss not of @_requestersByQs
                 @_requestersByQs[addedQss] = {}
+                @_incChanges @parseQs(addedQss).modelName, addedQss, 1
                 requestsChanged = true
             @_requestersByQs[addedQss][computation._id] = computation

@@ -113,6 +123,7 @@
             delete @_requestersByQs[deletedQss][computation._id]
             if _.isEmpty @_requestersByQs[deletedQss]
                 delete @_requestersByQs[deletedQss]
+                @_incChanges @parseQs(deletedQss).modelName, deletedQss, -1
                 requestsChanged = true

         if requestsChanged and not @_requestsChanged
@@ -134,6 +145,7 @@
             delete @_requestersByQs[deletedQss][computation._id]
             if _.isEmpty @_requestersByQs[deletedQss]
                 delete @_requestersByQs[deletedQss]
+                @_incChanges @parseQs(deletedQss).modelName, deletedQss, -1
                 requestsChanged = true

         delete @_qsByRequester[computation._id]
@@ -625,7 +637,13 @@
         newUnmergedQuerySpecsByModelName = J.util.groupByKey newUnmergedQuerySpecs, 'modelName'
         newMergedQuerySpecs = []
         for modelName, newUnmergedModelQuerySpecs of newUnmergedQuerySpecsByModelName
-            newMergedQuerySpecs.push.apply newMergedQuerySpecs, @getMerged newUnmergedModelQuerySpecs
+            if _.isEmpty(@_changesByModelQs[modelName] ? {})
+                # console.log "Skipping #{modelName} which has #{newUnmergedModelQuerySpecs.length} querySpecs"
+                newMergedQuerySpecs.push.apply newMergedQuerySpecs, newUnmergedModelQuerySpecs
+            else
+                # console.log "Merging #{modelName} which has #{newUnmergedModelQuerySpecs.length} querySpecs"
+                newMergedQuerySpecs.push.apply newMergedQuerySpecs, @getMerged newUnmergedModelQuerySpecs
+        @_changesByModelQs = {}

         newMergedQsStrings = (@stringifyQs querySpec for querySpec in newMergedQuerySpecs)
         @_nextMergedQsSet = {}


Issue #31: add the _ function to the Atom plugin

--- tools/atom-plugin/language-micro/grammars/micro.cson
+++ tools/atom-plugin/language-micro/grammars/micro.cson
@@ -47,7 +47,7 @@
 	}
 	{
 		'comment': 'Built-in functions (it should be described before functions)'
-		'match': '\\b(nil|false|true|num|type|arity|apply|floor|ceil|trunc|round|sin|cos|tn|arcsin|arccos|arctn|angle|pow|sqrt|exp|ln|lg|abs|random|head|tail|if|str|strb|strl|env|time|in|out|outln|err|errln|exit)\\b'
+		'match': '\\b(nil|false|true|num|type|arity|apply|_|floor|ceil|trunc|round|sin|cos|tn|arcsin|arccos|arctn|angle|pow|sqrt|exp|ln|lg|abs|random|head|tail|if|str|strb|strl|env|time|in|out|outln|err|errln|exit)\\b'
 		'name': 'variable.function.micro'
 	}
 	{


ibid for async loaded blocks

ibid for async loaded blocks

--- components/block_collection/client/block_view.coffee
+++ components/block_collection/client/block_view.coffee
@@ -4,6 +4,7 @@
 mediator = require '../../../lib/mediator.coffee'
 ConnectView = require '../../connect/client/connect_view.coffee'
 LightboxView = require '../../lightbox/client/lightbox_view.coffee'
+IconicJS = require '../../../components/iconic/client/iconic.min.js'
 User = require '../../../models/user.coffee'

 blockTemplate = -> require('../templates/block.jade') arguments...
@@ -51,6 +52,9 @@
     $("##{model.id}").replaceWith blockTemplate(block: model, user: @current_user)
     @$el = $("##{model.id}")
     @model = model
+
+    IconicJS().inject 'img.iconic'
+
     @delegateEvents()

   removeActiveClass: ->


generateRunFile: error handling for resource creation

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -461,41 +461,48 @@
       export CHANGE_MINIKUBE_NONE_USER=true
       sudo -E minikube start --vm-driver=none

-      sleep 25
-
-      export HOSTNAME=$(hostname)
+      sleep 60
+
+      export NAMESPACE_DIR="${KONFIG_PROJECTROOT}/deployment/kubernetes/namespace.yaml"
+      export BACKEND_DIR="${KONFIG_PROJECTROOT}/deployment/kubernetes/backend-pod/containers.yaml"
+      export FRONTEND_DIR="${KONFIG_PROJECTROOT}/deployment/kubernetes/frontend-pod/client-containers.yaml"
+
       envsubst < deployment/kubernetes/backend-pod/containers.yaml.template > deployment/kubernetes/backend-pod/containers.yaml
       envsubst < deployment/kubernetes/frontend-pod/client-containers.yaml.template > deployment/kubernetes/frontend-pod/client-containers.yaml
-      kubectl create -f $KONFIG_PROJECTROOT/deployment/kubernetes/namespace.yaml
-      kubectl create -f $KONFIG_PROJECTROOT/deployment/kubernetes/backend-pod/containers.yaml
-      kubectl create -f $KONFIG_PROJECTROOT/deployment/kubernetes/frontend-pod/client-containers.yaml
+
+      cp $BACKEND_DIR ${KONFIG_PROJECTROOT}/deployment/generated_files
+      cp $FRONTEND_DIR ${KONFIG_PROJECTROOT}/deployment/generated_files
+
+      kubectl apply -f $NAMESPACE_DIR || kubectl create -f $NAMESPACE_DIR || exit 1
+      kubectl apply -f $BACKEND_DIR || kubectl create -f $BACKEND_DIR || exit 1
+      kubectl apply -f $FRONTEND_DIR || kubectl create -f $FRONTEND_DIR || exit 1

       sleep 5
       echo "checking if backend pod is ready"
       check_pod_ready
-      export BACKEND_POD_NAME="$(kubectl get pods --namespace koding -o jsonpath="{.items[0].metadata.name}")"
-      export FRONTEND_POD_NAME="$(kubectl get pods --namespace koding -o jsonpath="{.items[1].metadata.name}")"
+      export BACKEND_POD_NAME="$(kubectl get pods --namespace koding -o jsonpath="{.items[0].metadata.name}" || exit 1)"
+      export FRONTEND_POD_NAME="$(kubectl get pods --namespace koding -o jsonpath="{.items[1].metadata.name}" || exit 1)"
       sleep 10

-      k8s_health_check $BACKEND_POD_NAME koding 5 120 backend-services
+      k8s_health_check $BACKEND_POD_NAME koding 5 120 backend
       k8s_health_check $FRONTEND_POD_NAME koding 5 120 landing

       # guest user can only connect via localhost, a test user equivalent to guest will be created to check connectivity via POD IP.
       # more info on guest user update: https://www.rabbitmq.com/blog/2014/04/02/breaking-things-with-rabbitmq-3-3/
       k8s_health_check $BACKEND_POD_NAME koding 5 120 rabbitmq
       sleep 25
-      kubectl exec -n koding $BACKEND_POD_NAME -c rabbitmq -- bash -c "rabbitmqctl add_user test test && rabbitmqctl set_user_tags test administrator && rabbitmqctl set_permissions -p / test '.*' '.*' '.*'"
-
-      k8s_connectivity_check $BACKEND_POD_NAME koding backend-services
+      kubectl exec -n koding $BACKEND_POD_NAME -c rabbitmq -- bash -c "rabbitmqctl add_user test test && rabbitmqctl set_user_tags test administrator && rabbitmqctl set_permissions -p / test '.*' '.*' '.*'" || exit 1
+
+      k8s_connectivity_check $BACKEND_POD_NAME koding backend

       echo "all services are ready"
     }

     function k8s_connectivity_check () {
       # args: $1: POD NAME, $2: NAMESPACE, $3: container name
-      kubectl exec -n $2 $1 -c $3 -- bash -c "./run is_ready"
-    }
-
+      kubectl exec -n $2 $1 -c $3 -- bash -c "./run is_ready" || exit 1
+    }
+
     function k8s_health_check () {
       # args: $1: POD NAME, $2: NAMESPACE, $3: health check interval (recommended 5 seconds), $4: timeout duration
       declare interval=$3
@@ -504,7 +511,7 @@

       sleep $interval

-      declare response_code=$(kubectl exec -n $2 $1 -c $5 -- echo 'i am alive')
+      declare response_code=$(kubectl exec -n $2 $1 -c $5 -- echo 'i am alive' || exit 1)

       echo -n $5 : 'health-check : '

@@ -519,7 +526,7 @@
         sleep $interval
         duration=$((duration + interval))

-        response_code=$(kubectl exec -n $2 $1 -c $5 -- echo 'i am alive')
+        response_code=$(kubectl exec -n $2 $1 -c $5 -- echo 'i am alive' || exit 1)
       done

       echo ' succeeded!'
@@ -529,7 +536,7 @@
       remaining_time=600

       until eval "check_pod_response"; do
-        sleep 1
+        sleep 30
         let remaining_time--
         if [ $remaining_time == 0 ]; then
           echo "time out!"
@@ -542,7 +549,7 @@
     }

     function check_pod_response () {
-      declare response_code=$(kubectl get pods --namespace koding -o jsonpath="{.items[0].status.phase}")
+      declare response_code=$(kubectl get pods --namespace koding -o jsonpath="{.items[0].status.phase}" || exit 1)

       if [[ $response_code == *"Pending"* ]]; then
         return 1


runFile: add gotest func for run file

runFile: add gotest func for run file

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -386,7 +386,17 @@
     }

     function gotest() {
-        echo "mehmetalisavas"
+        # action="sh -c"
+        action="echo"
+        COMPILE_FLAGS=$KONFIG_SOCIALAPI_CONFIGFILEPATH
+        echo $COMPILE_FLAGS
+        echo $1
+        echo $2
+
+        go list -f '{{if len .TestGoFiles}}"go test -v -cover -o={{.Dir}}/{{.Name}}.test -c {{.ImportPath}} "{{end}}' $1 | grep -v vendor | xargs -L 1 -I{} $action {}$COMPILE_FLAGS
+        # pushd is required for test folders/paths
+        go list -f '{{if len .TestGoFiles}}"pushd {{.Dir}} && ./{{.Name}}.test -test.coverprofile={{.Dir}}/coverage.txt "{{end}}' $1 | xargs -L 1 -I{} $action {}$RUN_FLAGS
+        go list -f '{{if len .TestGoFiles}}"rm {{.Dir}}/{{.Name}}.test "{{end}}' $1 | xargs -L 1 $action
     }

     function runMongoDocker () {


style(cli.coffee): remove superfluous parentheses

--- src/cli.coffee
+++ src/cli.coffee
@@ -44,35 +44,41 @@

 module.exports = ->
   outputPath = cli.argv.output
-  outputPath = path.resolve(outputPath) if outputPath
+  outputPath = path.resolve outputPath if outputPath

-  textlex = new Textlex(includePath: cli.argv.include)
+  textlex = new Textlex includePath: cli.argv.include

   stringify = (tokens, tab) ->
     JSON.stringify tokens, null, tab

   output = (outputPath, tokens, tab) ->
+    tab = ' '.repeat tab if tab is String parseInt tab
+    tokensString = stringify tokens, tab
     if outputPath
-      fs.writeFileSync(outputPath, stringify tokens, tab)
+      fs.writeFileSync outputPath, tokensString
     else
-      console.log(stringify tokens, tab)
+      console.log tokensString

   if cli.argv._.length
     for filePath in cli.argv._
-      filePath = path.resolve(filePath)
-      unless fs.isFileSync(filePath)
-        console.error("Specified path is not a file: #{filePath}")
-        process.exit(1)
+      filePath = path.resolve filePath
+      unless fs.isFileSync filePath
+        console.error "Specified path is not a file: #{filePath}"
+        process.exit 1
         return

-      tokens = textlex.lexSync({filePath, scopeName: cli.argv.scope})
+      tokens = textlex.lexSync {filePath, scopeName: cli.argv.scope}
       output outputPath, tokens, cli.argv.tab
   else
     filePath = cli.argv.f
     process.stdin.resume()
-    process.stdin.setEncoding('utf8')
+    process.stdin.setEncoding 'utf8'
     fileContents = ''
     process.stdin.on 'data', (chunk) -> fileContents += chunk.toString()
     process.stdin.on 'end', ->
-      tokens = textlex.lexSync({filePath, fileContents, scopeName: cli.argv.scope})
+      tokens = textlex.lexSync {
+        filePath,
+        fileContents,
+        scopeName: cli.argv.scope
+      }
       output outputPath, tokens, cli.argv.tab


Revert previous "fix".

Revert previous "fix".

--- src/cli.coffee
+++ src/cli.coffee
@@ -11,7 +11,7 @@
     mod = false

   # Try to require Die app
-  {Die} = require('./die')
+  Die = require('./die')
   if mod
     app = require mod
     # If we actually have a Die app instance, use it


Nodejs Web Server Tests: add _csrf param to test request body

--- servers/testhelper/handler/recoverhelper.coffee
+++ servers/testhelper/handler/recoverhelper.coffee
@@ -4,7 +4,8 @@
   deepObjectExtend
   generateRandomEmail
   generateRandomString
-  generateDefaultRequestParams } = require '../index'
+  generateRequestParamsEncodeBody } = require '../index'
+

 defaultExpiryPeriod = 5 * 60 * 1000 # 5 minutes
@@ -14,6 +15,7 @@

   defaultBodyObject =
     email : ''
+    _csrf : generateRandomString()

   deepObjectExtend defaultBodyObject, opts

@@ -23,17 +25,14 @@
 generateRecoverRequestParams = (opts = {}) ->

   email = opts?.body?.email or generateRandomEmail()
+  body  = generateRecoverRequestBody()

-  url  = generateUrl
-    route : "#{encodeURIComponent email}/Recover"
+  params =
+    url        : generateUrl { route : "#{encodeURIComponent email}/Recover" }
+    body       : body
+    csrfCookie : body._csrf

-  body = generateRecoverRequestBody()
-
-  params               = { url, body }
-  defaultRequestParams = generateDefaultRequestParams params
-  requestParams        = deepObjectExtend defaultRequestParams, opts
-  # after deep extending object, encodes body param to a query string
-  requestParams.body   = querystring.stringify requestParams.body
+  requestParams = generateRequestParamsEncodeBody params, opts

   return requestParams


Nodejs Web Server Tests: import querystring from testhelper/index

--- servers/testhelper/handler/recoverhelper.coffee
+++ servers/testhelper/handler/recoverhelper.coffee
@@ -1,11 +1,9 @@
-querystring = require 'querystring'
-
-{ generateUrl
+{ queryString
+  generateUrl
   deepObjectExtend
   generateRandomEmail
   generateRandomString
   generateRequestParamsEncodeBody } = require '../index'
-

 defaultExpiryPeriod = 5 * 60 * 1000 # 5 minutes


GameObject.Effect

GameObject.Effect

--- src/game_object_effect.coffee
+++ src/game_object_effect.coffee
@@ -1,6 +1,5 @@
 ###*
-`Effect` module provides a method to fade a sprite to transparent.
-You may also provide a callback function that is executed when the sprite has finished fading out.
+H

 @name Effect
 @fieldOf GameObject


Test: tweak e2e video test for travis

Test: tweak e2e video test for travis

--- test/e2e/app/work/work.projects.spec.coffee
+++ test/e2e/app/work/work.projects.spec.coffee
@@ -70,9 +70,12 @@
               when 'video'
                 item.$('video').getAttribute('poster').then (poster) ->
                   expect(poster).toMatch(RegExp(dataItem.poster))
+                  checkDone()
+                  ### phantom can't seem to test this?
                   item.$('video').getAttribute('src').then (src) ->
                     expect(src).toMatch(RegExp("#{dataItem.mp4}|#{dataItem.ogg}|#{dataItem.webm}"))
                     checkDone()
+                  ###

       it 'should optionally have: description, press, team', (done) ->
         doneIdx = 0


using utils

using utils

--- lib/atom-butterfly.coffee
+++ lib/atom-butterfly.coffee
@@ -74,13 +74,14 @@
     rimraf targetZipFile, =>

       Downloader.download(butterflyURL, targetZipFile)
-      .on 'progress', ->
+      .on 'progress', (progress)->
         pv.setProgress(progress)
-      .on 'finish', =>
-        rimraf targetFolder, =>
+      .on 'finish', ->
+        rimraf targetFolder, ->

           pv.setTitle("Unzip...")
-          Unzip.unzip targetZipFile, installToPath, =>
+          Unzip.unzip(targetZipFile, installToPath)
+          .on 'finish', ->

             fs.unlink targetZipFile, ->
               pv.destroy()


db migration debug

db migration debug

--- migration/database/v0.8.0.coffee
+++ migration/database/v0.8.0.coffee
@@ -20,7 +20,7 @@
       async.forever (callback) ->
         cAccount.findOne
           signup_at:
-            $exist: true
+            $exists: true
         , (err, account) ->
           throw err if err

@@ -37,8 +37,8 @@
             account.attribute.plugin.shadowsocks.method = 'aes-256-cfb'

           account.attribute.plugin.bitcoin =
-            bitcoin_deposit_address: account.attribure.bitcoin_deposit_address
-            bitcoin_secret: account.attribure.bitcoin_secret
+            bitcoin_deposit_address: account.attribute.bitcoin_deposit_address
+            bitcoin_secret: account.attribute.bitcoin_secret

           for token in account.tokens
             token.type = 'full_access'
@@ -58,7 +58,7 @@
               preferences: account.setting
               pluggable: account.attribute.plugin
               resources_limit: account.attribute.resources_limit
-              tokens: tokens
+              tokens: account.tokens
               billing:
                 services: account.attribute.services
                 plans: account.attribute.plans
@@ -67,16 +67,18 @@
                 last_billing_at: last_billing_at

             $unset:
+              attribute: true
               setting: true
               signup_at: true
               group: true

           , (err) ->
             throw err if err
-            console.log "[accounts] updated #{account._id}(#{account.username})"
-            callback()
-
-      , callback
+            console.log "[accounts] updated #{account.username} (#{account._id})"
+            callback()
+
+      , ->
+        callback()

     (callback) ->
       console.log '[financials] beginning'
@@ -99,7 +101,7 @@
             payload: balance_log.attribute
           , (err) ->
             throw err if err
-            console.log "[financials] created #{balance_log._id}}"
+            console.log "[financials] created #{balance_log._id}"
             callback()

       , ->
@@ -124,7 +126,7 @@
             apply_log: coupon.log
           , (err) ->
             throw err if err
-            console.log "[couponcodes] created #{coupon._id}}"
+            console.log "[couponcodes] created #{coupon._id}"
             callback()

       , ->
@@ -142,6 +144,21 @@

           if security_log.type == 'update_setting'
             security_log.type = 'update_preferences'
+
+            security_log.payload =
+              original_preferences: {}
+              preferences: {}
+
+            security_log.payload.original_preferences[security_log.attribute.name] = security_log.attribute.old_value
+            security_log.payload.preferences[security_log.attribute.name] = security_log.attribute.value
+
+          else if security_log.type == 'update_email'
+            security_log.payload =
+              original_email: security_log.attribute.old_email
+              email: security_log.attribute.email
+
+          else
+            security_log.payload = _.omit security_log.attribute, 'token'

           token = security_log.attribute.token
           token.type ?= 'full_access'
@@ -153,10 +170,10 @@
             type: security_log.type
             created_at: security_log.created_at
             token: token
-            payload: _.omit security_log.attribute, 'token'
-          , (err) ->
-            throw err if err
-            console.log "[securitylogs] created #{security_log._id}}"
+            payload: security_log.payload
+          , (err) ->
+            throw err if err
+            console.log "[securitylogs] created #{security_log._id}"
             callback()

       , ->
@@ -168,7 +185,7 @@
       async.forever (callback) ->
         cTicket.findOne
           replys:
-            $exist: true
+            $exists: true
         , (err, ticket) ->
           throw err if err

@@ -193,6 +210,7 @@
             console.log "[tickets] updated #{ticket._id}"
             callback()

-      , callback
+      , ->
+        callback()

   ], callback


mixin returns base object for chaining

mixin returns base object for chaining

--- src/dou.coffee
+++ src/dou.coffee
@@ -45,6 +45,7 @@

     mixin = (target, withs) ->
         compose.mixin (if typeof target is 'function' then target.prototype else target), withs
+        target

     {
         define: define


fix platform ssh test

fix platform ssh test

--- test/integration/ssh_platform_detect.coffee
+++ test/integration/ssh_platform_detect.coffee
@@ -1,5 +1,12 @@
 should = require 'should'
 {wd40, browser, login_url, home_url, prepIntegration} = require './helper'
+
+clickSSHButton = (done) ->
+  browser.elementByPartialLinkText 'Tools', (err, link) =>
+    link.click ->
+      browser.elementByPartialLinkText 'Test app', (err, link) ->
+        link.elementByCss '.ssh-in', (err, sshLink) ->
+          sshLink.click done

 describe 'Platform-specific SSH instructions', ->
   prepIntegration()
@@ -12,14 +19,17 @@
   context 'when I use a Windows PC to view SSH instructions', ->
     before (done) ->
       browser.refresh ->
-        browser.eval "window.navigator = {platform: 'Win32'}", ->
-          wd40.click '.dataset-actions .git-ssh', ->
-            browser.waitForVisibleByCss '.modal', 4000, done
+        browser.eval "window.navigator = {platform: 'Win32'}", done
+
+    before clickSSHButton
+
+    before (done) ->
+      browser.waitForVisibleByCss '.modal', 4000, done

     before (done) =>
-        wd40.getText '.modal', (err, text) =>
-          @modalTextContent = text.toLowerCase()
-          done()
+      wd40.getText '.modal', (err, text) =>
+        @modalTextContent = text.toLowerCase()
+        done()

     it 'the modal window tells me to use Git Bash', =>
       @modalTextContent.should.include 'git bash'
@@ -30,14 +40,17 @@
   context 'when I use a Mac to view SSH instructions', ->
     before (done) ->
       browser.refresh ->
-        browser.eval "window.navigator = {platform: 'MacIntel'}", ->
-          wd40.click '.dataset-actions .git-ssh', ->
-            browser.waitForVisibleByCss '.modal', 4000, done
+        browser.eval "window.navigator = {platform: 'MacIntel'}", done
+
+    before clickSSHButton
+
+    before (done) ->
+      browser.waitForVisibleByCss '.modal', 4000, done

     before (done) =>
-        wd40.getText '.modal', (err, text) =>
-          @modalTextContent = text.toLowerCase()
-          done()
+      wd40.getText '.modal', (err, text) =>
+        @modalTextContent = text.toLowerCase()
+        done()

     it 'the modal window tells me to use the Terminal', =>
       @modalTextContent.should.include 'terminal'
@@ -48,14 +61,17 @@
   context 'when I use a Linux computer to view SSH instructions', ->
     before (done) ->
       browser.refresh ->
-        browser.eval "window.navigator = {platform: 'Linux i686'}", ->
-          wd40.click '.dataset-actions .git-ssh', ->
-            browser.waitForVisibleByCss '.modal', 4000, done
+        browser.eval "window.navigator = {platform: 'Linux i686'}", done
+
+    before clickSSHButton
+
+    before (done) ->
+      browser.waitForVisibleByCss '.modal', 4000, done

     before (done) =>
-        wd40.getText '.modal', (err, text) =>
-          @modalTextContent = text.toLowerCase()
-          done()
+      wd40.getText '.modal', (err, text) =>
+        @modalTextContent = text.toLowerCase()
+        done()

     it 'the modal window tells me to use the Terminal', =>
       @modalTextContent.should.include 'terminal'


S88525: Remove the ability to edit name and dates on Iteration and Release lists

--- test/spec/timeboxes/TimeboxesAppSpec.coffee
+++ test/spec/timeboxes/TimeboxesAppSpec.coffee
@@ -72,6 +72,9 @@
     it 'should have an xml export button', ->
       expect(@app.xmlExportEnabled()).toBe true

+    it 'should not allow editing of the Name field', ->
+      expect(_.find(@app.gridboard.getGridOrBoard().columns, (column) -> column.dataIndex =='Name').tdCls).not.toContain 'editable'
+
   describe 'releases', ->
     beforeEach ->
       @createApp 'release'
@@ -87,6 +90,9 @@

     it 'should have an xml export button', ->
       expect(@app.xmlExportEnabled()).toBe true
+
+    it 'should not allow editing of the Name field', ->
+      expect(_.find(@app.gridboard.getGridOrBoard().columns, (column) -> column.dataIndex =='Name').tdCls).not.toContain 'editable'

   describe 'on type change', ->
     beforeEach ->


Fixed occasional code clear on format

Fixed occasional code clear on format

Signed-off-by: WebFreak001 <b0a51d41aaf8d5df1e850f1ef2ec58fb1c30319f@web.de>

--- lib/formatter-dfmt.coffee
+++ lib/formatter-dfmt.coffee
@@ -8,8 +8,11 @@
 			env: process.env
 		})
 		formatted = ""
+		attachedCallback = false
 		formatter.stdout.on "data", (data) ->
 			formatted += data.toString("utf8")
-		formatter.once "exit", (code) ->
-			callback formatted
+			if not attachedCallback
+				attachedCallback = true
+				formatter.once "exit", (code) ->
+					callback formatted
 		formatter.stdin.end text


display notification whenever chatroom gets updated

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -3768,15 +3768,17 @@
     ext = misc.filename_extension(path)
     action = 'edit'
     {head, tail} = misc.path_split(path)
-    if ext == "sage-chat" and tail?[0] == '.'  # hidden sage-chat associated to a regular file.
-        action = 'chat'
-        path = path.slice(0, path.length-'.sage-chat'.length)
-        {head, tail} = misc.path_split(path)
-        tail = tail.slice(1) # get rid of .
-        if head
-            path = head + '/' + tail
-        else
-            path = tail
+    if ext == "sage-chat"
+        action = 'chat'  # editing sage-chat gets the extra important chat action (instead of just edit)
+        if tail?[0] == '.'
+            # hidden sage-chat associated to a regular file, so notify about the regular file
+            path = path.slice(0, path.length-'.sage-chat'.length)
+            {head, tail} = misc.path_split(path)
+            tail = tail.slice(1) # get rid of .
+            if head
+                path = head + '/' + tail
+            else
+                path = tail
     else if ext.slice(0,7) == 'syncdoc'   # for IPython, and possibly other things later
         path = path.slice(0, path.length - ext.length - 1)
         {head, tail} = misc.path_split(path)


Update goal.coffee

Update goal.coffee

--- scripts/goal.coffee
+++ scripts/goal.coffee
@@ -11,7 +11,6 @@
 moment = require "moment"
 _ = require "underscore"
 Path = require "path"
-Dialog = require Path.join __dirname, "..", "src", "dialog"

 module.exports = (robot) ->
@@ -52,30 +51,32 @@
   ]

   setGoals = (msg) ->
-    dialog = new Dialog msg, (msg) ->
-      text = msg.envelope.message.text
+    user = msg.envelope.user.name
+    room = msg.envelope.user.room
+    robot.emit "dialogue:start", msg.envelope.user, (message) ->
+      text = message.text
       if /^(help|ヘルプ|わからん)$/i.test text
-        msg.send "何かおっしゃってください。目標として設定します。" +
+        @respond "何かおっしゃってください。目標として設定します。" +
           "「もうない」や「終わり」と言えば目標設定モードを終了します。"
       else if /^((もう)?ない|(終|お)わり|おしまい|none|nothing|done|finish|end)$/i.test text
         _goals = @.get("goals")
-        robot.brain.data.goals[@.user] = robot.brain.data.goals[@.user] or {
+        robot.brain.data.goals[@username] = robot.brain.data.goals[@username] or {
           goals: []
           attempted: 0
           achieved: 0
         }
-        goals = robot.brain.data.goals[@.user].goals.concat _goals
+        goals = robot.brain.data.goals[@username].goals.concat _goals
         goals = _.uniq goals
         expiration = moment()
           .set("date", moment().get("date") + 1)
           .startOf("day").set("hour", 3)

-        _.extend robot.brain.data.goals[@.user], {
+        _.extend robot.brain.data.goals[@username], {
           goals: goals
-          attempted: robot.brain.data.goals[@.user].attempted + _goals.length
+          attempted: robot.brain.data.goals[@username].attempted + _goals.length
           expiration: expiration.format()
         }
-        msg.send "ありがとうございました。現在設定中の目標：\n" +
+        @respond "ありがとうございました。現在設定中の目標：\n" +
           goalsToString(goals) + "\n\n" +
           "期限は明日午前3時までです。" + msg.random cheers
         @.end()
@@ -83,7 +84,7 @@
         goals = @.get("goals") or []
         goals.push text
         @.set "goals", goals
-        msg.send "「#{text}」を目標として設定しました。現在設定中の目標：\n" +
+        @respond "「#{text}」を目標として設定しました。現在設定中の目標：\n" +
           goalsToString(goals) + "\n" +
           "もうなければ、「ない」とか「終わり」とか言ってくださいね。"

@@ -94,6 +95,7 @@
         goals: []
         achieved_goals: []
         attempted: 0
+        achieved: 0
         expiration: 0
       }
     unless data.goals.length > 0 and
@@ -102,12 +104,9 @@
       robot.brain.data.goals[user].achieved_goals = []

     goals = data.goals or []
-    dialog.set "goals", goals
+    robot.emit "dialogue:set", msg.envelope.user, "goals", goals
     msg.send "#{msg.envelope.user.name}さんの今日の目標を教えてください！\n" +
       "なければ、「ない」とか「終わり」とか言ってくださいね。"
-
-    #Dialog.addDialog dialog
-    Dialog.listen robot

   setAGoal = (msg) ->
     goal = msg.match[1]
@@ -141,8 +140,9 @@

     unless data and data.goals.length > 0 and
         moment(data.expiration).isAfter moment()
+      achieved_goals = data?.achieved_goals or []
       msg.send "#{user}さんの今日の目標は設定されておりません。\n" +
-        goalsToString [], data.achieved_goals
+        goalsToString [], achieved_goals
       return

     goals = data.goals or []
@@ -153,6 +153,7 @@

   achieveGoals = (msg) ->
     user = msg.envelope.user.name
+    room = msg.envelope.user.room
     data = robot.brain.data.goals[user] or null

     unless data and data.goals.length > 0 and
@@ -170,15 +171,15 @@
         msg.random congraturations
       return

-    # achieving dialog
+    # achieving dialogue
     goals = data.goals
     msg.send "どの目標を達成されましたか？番号を教えてください。 [番号/ない]\n" +
       goalsToString goals

-    dialog = new Dialog msg, (msg) ->
-      text = msg.envelope.message.text
+    robot.emit "dialogue:start", msg.envelope.user, (message) ->
+      text = message.text
       if text.length is 0 or /^(help|ヘルプ|わからん)$/i.test text
-        msg.send "1 や 2、など達成した目標の番号をおっしゃってください。" +
+        @respond "1 や 2、など達成した目標の番号をおっしゃってください。" +
           "「ない」や「終わり」と言えば目標達成モードを終了します。\n" +
           goalsToString goals
       else if text.match /\d+|\*/i
@@ -186,7 +187,7 @@
         # nums: 1, 2, 3
         # span: 1-3
         # all: *
-        goals = @robot.brain.data.goals[@.user].goals
+        goals = robot.brain.data.goals[@username].goals
         indexes = []
         if text.match /\*/i
           indexes = _.range 0, goals.length
@@ -208,23 +209,23 @@
         res = ""
         for goal in achieved_goals
           res += "「#{goal}」を達成したのですね！\n"
-          robot.brain.data.goals[@.user].achieved += 1
-
-          robot.brain.data.goals[@.user].goals =
-            _.without robot.brain.data.goals[@.user].goals, goal
-          robot.brain.data.goals[@.user].achieved_goals.push goal
-
-        msg.send res
-
-        if robot.brain.data.goals[@.user].goals.length > 0
-          msg.send "続けて達成した目標はありますか？ [番号/ない]\n" +
-            goalsToString robot.brain.data.goals[@.user].goals
+          robot.brain.data.goals[@username].achieved += 1
+
+          robot.brain.data.goals[@username].goals =
+            _.without robot.brain.data.goals[@username].goals, goal
+          robot.brain.data.goals[@username].achieved_goals.push goal
+
+        @respond res
+
+        if robot.brain.data.goals[@username].goals.length > 0
+          @respond "続けて達成した目標はありますか？ [番号/ない]\n" +
+            goalsToString robot.brain.data.goals[@username].goals
         else
-          msg.send "全ての目標を達成したようです！\n" +
+          @respond "全ての目標を達成したようです！\n" +
             msg.random congraturations
           @.end()
       else if /^((もう)?ない|(終|お)わり|おしまい|none|nothing|done|finish|end)$/i.test text
-        msg.send "お疲れ様でした。"
+        @respond "お疲れ様でした。"
         @.end()

     Dialog.listen robot
@@ -263,6 +264,7 @@

   resetData = (msg) ->
     user = msg.envelope.user.name
+    room = msg.envelope.user.room
     data = robot.brain.data.goals[user] or null
     embed_user = user

@@ -272,13 +274,12 @@
       return

     msg.send "#{user}さんのデータをリセットしてもよろしいですか？[y/N]"
-    dialog = new Dialog msg, (msg) ->
-      text = msg.envelope.message.text
+    robot.emit "dialogue:start", msg.envelope.user, (message) ->
+      text = message.text
       if /^(y|はい|よし|よろし)$/i.test text
         delete robot.brain.data.goals[user]
-        msg.send "完全にリセットしました。"
+        @respond "完全にリセットしました。"
       @end
-    Dialog.listen robot

   # English
   robot.respond /goals/i, (msg) ->


Made jerk script send responses in 2 messages. This will allow for less clicking and therefore a significantly improved user experience PogChamp

--- scripts/jerk.coffee
+++ scripts/jerk.coffee
@@ -8,6 +8,8 @@
         results = JSON.parse(body)
         post = msg.random results.data.children
         if (post.data.domain == "self.circlejerk")
-          msg.send post.data.title, post.data.selftext
+          msg.send post.data.title
+          msg.send post.data.selftext
         else
-          msg.send post.data.title, post.data.url+          msg.send post.data.title
+          msg.send post.data.url


Fix Deprecation

Fix Deprecation

- Fixes #350

--- lib/fuzzy-provider.coffee
+++ lib/fuzzy-provider.coffee
@@ -105,7 +105,7 @@
     @tokenList.clear()

     if atom.config.get('autocomplete-plus.includeCompletionsFromAllBuffers')
-      editors = atom.workspace.getEditors()
+      editors = atom.workspace.getTextEditors()
     else
       editors = [@editor]


fixes #3

fixes #3

--- lib/geekywalletlib.coffee
+++ lib/geekywalletlib.coffee
@@ -59,9 +59,9 @@
       # given
       line.payers.map (payer) ->
         line.computed.given[payer.name] = payer.amount
+        line.computed.spent[payer.name] ?= 0
       # compute balance
-      line.beneficiaries.map (ben) ->
-        line.computed.balance[ben.name] = line.computed.given[ben.name] - line.computed.spent[ben.name]
+      line.computed.balance[person] = line.computed.given[person] - line.computed.spent[person] for own person, val of line.computed.spent

       # return line object
       line


Splitting long line so that the build passes

--- lib/highlight-line.coffee
+++ lib/highlight-line.coffee
@@ -33,7 +33,8 @@
     @subscriptions.add atom.commands.add "atom-workspace",
         'highlight-line:toggle-background': => @toggleHighlight()
     @subscriptions.add atom.commands.add "atom-workspace",
-        'highlight-line:toggle-hide-highlight-on-select': => @toggleHideHighlightOnSelect()
+        'highlight-line:toggle-hide-highlight-on-select': \
+        => @toggleHideHighlightOnSelect()
     @subscriptions.add atom.commands.add "atom-workspace",
         'highlight-line:toggle-underline': => @toggleUnderline()
     @subscriptions.add atom.commands.add "atom-workspace",


CodeManager: Don't move the cursor down

CodeManager: Don't move the cursor down

if there is no following code to execute.

--- lib/code-manager.coffee
+++ lib/code-manager.coffee
@@ -182,15 +182,13 @@
     moveDown: (row) ->
         lastRow = @editor.getLastBufferRow()

-        if row >= lastRow
-            @editor.moveToBottom()
-            @editor.insertNewline()
-            return
-
         while row < lastRow
             row++
             break if not @isBlank(row)

+        unless row < lastRow
+            return
+
         @editor.setCursorBufferPosition
             row: row
             column: 0


[+] lazy loading of submodules

[+] lazy loading of submodules

--- src/git.coffee
+++ src/git.coffee
@@ -156,6 +156,9 @@
 Repository::submoduleForPath = (path) ->
   path = @relativize(path)
   return null unless path
+
+  if @submodules instanceof Function
+    @submodules()

   for submodulePath, submoduleRepo of @submodules
     if path is submodulePath
@@ -228,5 +231,6 @@

 exports.open = (repositoryPath) ->
   repository = openRepository(repositoryPath)
-  openSubmodules(repository) if repository?
+  if repository?
+    repository.submodules = -> openSubmodules(repository)
   repository


Fall back to root repo for submodule check

--- src/git.coffee
+++ src/git.coffee
@@ -223,7 +223,7 @@
   # Returns a {Boolean}.
   isSubmodule: (path) ->
     repo = @getRepo(path)
-    repo.isSubmodule(repo.relativize(path))
+    repo.isSubmodule(repo.relativize(path)) or @getRepo().isSubmodule(@relativize(path))

   # Public: Get the status of a directory in the repository's working directory.
   #


Fixing formatting in datetime.coffee

Fixing formatting in datetime.coffee

--- js/lib/mustache/calendars/datetime.coffee
+++ js/lib/mustache/calendars/datetime.coffee
@@ -37,16 +37,16 @@
 		toks = this.get_tokens(obj, options)

 		for i in [0..toks.length - 1]
-      switch toks[i].type
-        when "pattern"
-          result += this.result_for_token(toks[i], i, obj)
-        else
-          if toks[i].value.length > 0 && toks[i].value[0] == "'" && toks[i].value[toks[i].value.length - 1] == "'"
-            result += toks[i].value.substring(1, toks[i].value.length - 1)
-          else
-            result += toks[i].value
-
-	  return result
+			switch toks[i].type
+				when "pattern"
+					result += this.result_for_token(toks[i], i, obj)
+				else
+					if toks[i].value.length > 0 && toks[i].value[0] == "'" && toks[i].value[toks[i].value.length - 1] == "'"
+						result += toks[i].value.substring(1, toks[i].value.length - 1)
+					else
+						result += toks[i].value
+
+		return result

 	get_tokens: (obj, options) ->
 		return @tokens[options.type || "default"]


Issue #121: Added transaction indexes. Issue #122: Updated the status enum.

--- src/model/transactions.coffee
+++ src/model/transactions.coffee
@@ -19,27 +19,29 @@

 # Route Schema
 RouteMetadataSchema = new Schema
-    "name" :{ type: String, required: true }
-    "request": RequestDef
-    "response": ResponseDef
+	"name" :{ type: String, required: true }
+	"request": RequestDef
+	"response": ResponseDef

 # OrchestrationMetadata Schema
 OrchestrationMetadataSchema = new Schema
-    "name" :{ type: String, required: true }
-    "request": RequestDef
-    "response": ResponseDef
+	"name" :{ type: String, required: true }
+	"request": RequestDef
+	"response": ResponseDef

 # Trasnaction schema
-TransactionSchema = new Schema
-    "clientID": { type: String, required: true }
-    "parentID": { type: String, required: false }
-    "channelID": { type: String, required: false }
-    "request": RequestDef
-    "response": ResponseDef
-    "routes": [ RouteMetadataSchema ]
-    "orchestrations": [ OrchestrationMetadataSchema ]
-    "properties": { type: Object }
-    "status": { type: String, required:true, enum: ["Processing","Failed","Completed"] }
+TransactionSchema = new Schema
+	"clientID": { type: String, required: true }
+	"parentID": { type: String, required: false }
+	"channelID": { type: String, required: false, index: true }
+	"request": RequestDef
+	"response": ResponseDef
+	"routes": [ RouteMetadataSchema ]
+	"orchestrations": [ OrchestrationMetadataSchema ]
+	"properties": { type: Object }
+	"status": { type: String, required: true, index: true, enum: ['Processing', 'Failed', 'Completed', 'Successful', 'Completed with error(s)'] }
+
+TransactionSchema.index "request.timestamp"

 #compile schema into Model
 exports.RouteMetadata = mongoose.model 'RouteMetadata', RouteMetadataSchema


Break Out If Statements

Break Out If Statements

--- lib/goexecutable.coffee
+++ lib/goexecutable.coffee
@@ -38,7 +38,9 @@
     switch os.platform()
       when 'darwin', 'freebsd', 'linux', 'sunos'
         # Configuration
-        executables.push path.normalize(goinstallation) if goinstallation? and goinstallation.trim() isnt '' and goinstallation.lastIndexOf(path.sep + 'go') is goinstallation.length - 3 or goinstallation.lastIndexOf(path.sep + 'goapp') is goinstallation.length - 6
+        if goinstallation? and goinstallation.trim() isnt ''
+          if goinstallation.lastIndexOf(path.sep + 'go') is goinstallation.length - 3 or goinstallation.lastIndexOf(path.sep + 'goapp') is goinstallation.length - 6
+            executables.push path.normalize(goinstallation)

         # PATH
         if @env.PATH?
@@ -52,7 +54,9 @@
         executables.push path.normalize(path.join('/usr', 'local', 'bin', 'go', ))
       when 'win32'
         # Configuration
-        executables.push path.normalize(goinstallation) if goinstallation? and goinstallation.trim() isnt '' and goinstallation.lastIndexOf(path.sep + 'go.exe') is goinstallation.length - 7 or goinstallation.lastIndexOf(path.sep + 'goapp.bat') is goinstallation.length - 10
+        if goinstallation? and goinstallation.trim() isnt ''
+          if goinstallation.lastIndexOf(path.sep + 'go.exe') is goinstallation.length - 7 or goinstallation.lastIndexOf(path.sep + 'goapp.bat') is goinstallation.length - 10
+            executables.push path.normalize(goinstallation)

         # PATH
         if @env.Path?


Add more hover text to search result list

--- src/opac/search_result.coffee
+++ src/opac/search_result.coffee
@@ -25,8 +25,8 @@

-
-
+
+

 				 of  available.


Add default settings on focus

Add default settings on focus

--- lib/settings-panel.coffee
+++ lib/settings-panel.coffee
@@ -106,7 +106,9 @@
   isDefault: (name) ->
     params = {sources: [atom.config.getUserConfigPath()]}
     params.scope = [@options.scopeName] if @options.scopeName?
-    not atom.config.get(name, params)?
+    defaultValue = @getDefault(name)
+    value = atom.config.get(name, params)
+    not value or defaultValue is value

   getDefault: (name) ->
     params = {excludeSources: [atom.config.getUserConfigPath()]}
@@ -136,11 +138,20 @@
   bindEditors: ->
     @find('atom-text-editor[id]').views().forEach (editorView) =>
       editor = editorView.getModel()
+      editorElement = $(editorView.element)
       name = editorView.attr('id')
       type = editorView.attr('type')

       if defaultValue = @valueToString(@getDefault(name))
         editor.setPlaceholderText("Default: #{defaultValue}")
+
+      editorElement.on 'focus', =>
+        if @isDefault(name)
+          editorView.setText(@valueToString(@getDefault(name)) ? '')
+
+      editorElement.on 'blur', =>
+        if @isDefault(name)
+          editorView.setText('')

       @observe name, (value) =>
         if @isDefault(name)


:art: Notify all editor linters of new messages

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -93,9 +93,9 @@
     @bottomContainer.setCount(@count)

   renderPanelMarkers: ({added, removed}) ->
-    activeEditor = @linter.getActiveEditorLinter()
-    return unless activeEditor
-    activeEditor.updateMarkers({added, removed})
+    @linter.eachEditorLinter((editorLinter) =>
+        editorLinter.updateMarkers({added, removed})
+    )

   attachBottom: (statusBar) ->
     @subscriptions.add atom.config.observe('linter.statusIconPosition', (statusIconPosition) =>


add resetProperties option

add resetProperties option

--- lib/mode-manager.coffee
+++ lib/mode-manager.coffee
@@ -158,12 +158,17 @@
           bs.restoreCharacterwise()
         @vimState.clearBlockwiseSelections()

-  normalizeSelections: ({preservePreviousSelection}={}) ->
+  normalizeSelections: ({preservePreviousSelection, resetProperties}={}) ->
+    preservePreviousSelection ?= false
+    resetProperties ?= true
+
     if preservePreviousSelection
       range = @editor.getLastSelection().getBufferRange()
       @vimState.mark.setRange('<', '>', range)
     @selectCharacterwise()
-    swrap.resetProperties(@editor)
+
+    swrap.resetProperties(@editor) if resetProperties
+
     if preservePreviousSelection and not @editor.getLastSelection().isEmpty()
       @preservePreviousSelection(@editor.getLastSelection())


Fix specs for version display

Fix specs for version display

--- lib/package-card.coffee
+++ lib/package-card.coffee
@@ -88,6 +88,7 @@

     @updateButtonGroup.hide()

+    @hasCompatibleVersion = true
     @updateForUninstalledCommunityPackage() unless @isInstalled()
     @updateInterfaceState()

@@ -119,6 +120,7 @@

           @installablePack = pack
         else
+          @hasCompatibleVersion = false
           @installButtonGroup.hide()
           @versionValue.addClass('text-error')
           @packageMessage.addClass('text-error')
@@ -178,7 +180,7 @@
       @downloadCount.text data.downloads?.toLocaleString()

   updateInterfaceState: ->
-    @versionValue.text(@pack.version)
+    @versionValue.text(@installablePack?.version ? @pack.version)
     @updateInstalledState()
     @updateDisabledState()
     @updateDeprecatedState()
@@ -230,7 +232,10 @@
     @uninstallButton.show()

   displayNotInstalledState: ->
-    if @newVersion
+    if not @hasCompatibleVersion
+      @installButtonGroup.hide()
+      @updateButtonGroup.hide()
+    else if @newVersion
       @updateButtonGroup.show()
       @installButtonGroup.hide()
     else


Determine if package is core from Atom's package.json

--- lib/user-utilities.coffee
+++ lib/user-utilities.coffee
@@ -172,14 +172,13 @@
       installedVersion = @getPackageVersion(packageName)
       upToDate = installedVersion? and semver.gte(installedVersion, latestPackageData.releases.latest)
       latestVersion = latestPackageData.releases.latest
-      isCore = latestPackageData.repository.url.startsWith('https://github.com/atom/')
+      versionShippedWithAtom = @getPackageVersionShippedWithAtom(packageName)

-      if isCore
+      if isCore = versionShippedWithAtom?
         # A core package is out of date if the version which is being used
         # is lower than the version which normally ships with the version
         # of Atom which is running. This will happen when there's a locally
         # installed version of the package with a lower version than Atom's.
-        versionShippedWithAtom = @getPackageVersionShippedWithAtom(packageName)
         upToDate = installedVersion? and semver.gte(installedVersion, versionShippedWithAtom)

       {isCore, upToDate, latestVersion, installedVersion, versionShippedWithAtom}


Minor fix

Minor fix

--- lib/util/form-view.coffee
+++ lib/util/form-view.coffee
@@ -56,7 +56,7 @@
   # {value: 123, text: "XXXX"}
   createField: (id, type, cssClass, options) ->
     td = document.createElement("td")
-    field = document.createElement("input") if type != "select"
+    field = document.createElement("input") if type != "select" || type != "progress"
     field = document.createElement("select") if type == "select"
     field = document.createElement("progress") if type == "progress"
     field.id = id


Update full text block

Update full text block

--- lib/vertical-align.coffee
+++ lib/vertical-align.coffee
@@ -4,16 +4,17 @@
 align = (editor) ->
   if !editor.hasMultipleCursors()
     # Get cursor row and text
-    row  = editor.getCursorBufferPosition().row
-    text = editor.lineForBufferRow row
+    origRow = editor.getCursorBufferPosition().row
+    text    = editor.lineForBufferRow origRow

     # Get alignment character
     character = helper.getAlignCharacter text

     if character
-      indentRange = helper.getSameIndentationRange editor, row, character
+      indentRange = helper.getSameIndentationRange editor, origRow, character
       config      = operatorConfig[character]
       regex       = helper.getOffsetRegex character
+      textBlock   = ""

       for row in [indentRange.start..indentRange.end]
         lineText = editor.lineForBufferRow row
@@ -40,7 +41,14 @@
         newText += lineText.substr(
           currentOffset + spaceBefore + operator.length + spaceAfter
         )
-        editor.setTextInBufferRange([[row, 0], [row, lineText.length]], newText)
+        textBlock += "#{newText}\n"
+
+      # Replace the whole block
+      editor.setTextInBufferRange([[indentRange.start, 0], [indentRange.end + 1, 0]], textBlock)
+
+      # Update the cursor to the end of the original line
+      editor.setCursorBufferPosition [origRow, editor.lineForBufferRow(origRow).length]
+

 module.exports =
   align:    align


checking open in app and clear priority

checking open in app and clear priority

--- lib/views/terminal.coffee
+++ lib/views/terminal.coffee
@@ -105,17 +105,18 @@

   sendClear: ->
     console.log 'm at sendClear'
-    setTimeout (=>
-      if !@isOpenLabActive
-        @terminal.send('clear \r')
-    ), 1000
+    if !@isOpenLabActive
+      @terminal.send('clear \r')
+

   openLab: (path = @openPath)->
     console.log 'm at openLab'
     console.log path
     if path
       @isOpenLabActive = true
-      @terminal.send('clive open ' + path.toString() + '\r')
+      setTimeout (=>
+        @terminal.send('clive open ' + path.toString() + '\r')
+      ), 2000
       @openPath = null

   onBlur: (e) ->


Fix API host in dev

Fix API host in dev

--- app/index.coffee
+++ app/index.coffee
@@ -51,7 +51,7 @@
 host = if +window.location.port < 1024
   'http://api.zooniverse.org'
 else
-  'http://localhost:3000'
+  "#{window.location.protocol}//#{window.location.hostname}:3000"

 Api.init {host}


Pass self up, too

Pass self up, too

--- scripts/rc.coffee
+++ scripts/rc.coffee
@@ -11,9 +11,9 @@
     who = msg.message.user.name.toLowerCase()
     score = msg.match[1]

-    new RCScore(who).set score, (err, res) ->
+    new RCScore(who).set score, (err, res, self) ->
       msg.reply "TODO(sshirokov): rc: #{who} => #{score}"
-      msg.reply "Err: #{err} Res: #{util.inspect res}"
+      msg.reply "Err: #{err} Res: #{util.inspect res} -- #{self}"

 # Bases and utilities
@@ -37,19 +37,19 @@
 class RCScore extends RCBase
   constructor: (@who=null, @options={}) -> super()

-  set: (score, cb) ->
-    cb(new RCError("Setting score of nobody")) unless @who
+  set: (score, cb) =>
+    cb(new RCError("Setting score of nobody"), null, this) unless @who
     now = Date.now()

     @storage.multi([
       ["ZADD", @key("latest"), now, @who],
       ["ZADD", @key("#{@who}:scores"), score, now],
     ]).exec (err, replies) =>
-      return cb(err) if err
+      return cb(err, null, this) if err
       # Update the internal state if we succeed
       # So that .get is free
       @info = {score: score, when: now}
-      cb(false, @info)
+      cb(false, @info, this)

   score: -> @info?.score
   timestamp: -> @info?.timestamp


another tiny indentation fix

another tiny indentation fix

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -47,8 +47,8 @@
         .pipe sourcemaps.init()
         .pipe coffee()
         .on 'error', (e) ->
-                console.log e.toString()
-                @emit 'end'
+            console.log e.toString()
+            @emit 'end'
         .pipe sourcemaps.write()
 #        .pipe changed outapp
         .pipe gulp.dest outapp


obsolete rollup-plugin-inject

obsolete rollup-plugin-inject

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -190,16 +190,6 @@
             utils.require 'rollup-plugin-commonjs', {
                 sourceMap: false
             }
-
-            utils.require 'rollup-plugin-inject', do ->
-                config = exclude: 'node_modules/**', modules: {}
-
-                map_w_key = _.mapValues.convert 'cap': false
-                make = map_w_key (value, key) -> ['tslib', key]
-
-                modules = make require 'tslib'
-
-                return _.assign config, {modules}
         ]
     }


Fix build dependencies in gulpfile

Fix build dependencies in gulpfile

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -25,8 +25,8 @@
 ################################################################################
 # Gulp tasks

-gulp.task "watch", ["webpack:debug"], ->
-	gulp.watch(["./*.coffee", "framer/**", "test/tests/**", "!Version.coffee"], ["webpack:debug","test"])
+gulp.task "watch", ["test"], ->
+	gulp.watch(["./*.coffee", "framer/**", "test/tests/**", "!Version.coffee"], ["test"])

 gulp.task "test", ["webpack:tests"], ->
 	return gulp
@@ -91,7 +91,7 @@

 	webpackDev("webpack:release", config, callback)

-gulp.task "webpack:tests", (callback) ->
+gulp.task "webpack:tests", ["webpack:debug"],(callback) ->

 	config = _.extend WEBPACK,
 		entry: "./test/tests.coffee"


refactor

refactor

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -20,7 +20,7 @@
 watch = require 'gulp-watch'
 prerender = require './prerender'

-loadEntries = (src) ->
+loadEntriesV3 = (src) ->
   myjekyll = require 'myjekyll'
   moment = require 'moment'
   marked = require 'marked'
@@ -44,10 +44,6 @@
       if titleKey is 'diary' then null else dir + '/diary/index.html'
       dir + '/index.html'
     ].filter (i) -> i
-  .concat [
-    paths: ['index.html']
-    entry: null
-  ]

 ignoreError = (stream) ->
   stream.on 'error', (e) ->
@@ -81,15 +77,17 @@

 gulp.task 'build-html', ->
   baseDir = './dist/'
-  loadEntries('./data/**/*.md').forEach ({ paths, entry }) ->
+  loadEntriesV3('./data/**/*.md').forEach ({ paths, entry }) ->
     paths.forEach (path) ->
       prerender(baseDir + path, entry)
+  prerender(baseDir + 'index.html', null)

 gulp.task 'build-html-dev', ->
   baseDir = './dist/'
-  loadEntries('./data/nofile.md').forEach ({ paths, entry }) ->
+  loadEntriesV3('./data/nofile.md').forEach ({ paths, entry }) ->
     paths.forEach (path) ->
       prerender(baseDir + path, entry)
+  prerender(baseDir + 'index.html', null)

 gulp.task 'build-images', ->
   gulp.src './src/images/*'


Add target folder for App Engine dev server

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -14,7 +14,9 @@
   less:   'src/main/less/**/*'
   static: 'src/main/static/**/*'

-target = 'src/main/webapp/'
+targets =
+  gulp: 'src/main/webapp/'
+  appengineDevServer: 'target/webapp/'

 gulp.task 'bower', ->
   bower.commands.install().on 'end', (installed) ->
@@ -24,11 +26,13 @@
         'bower_components/ngstorage/ngStorage.min.js'
       ])
       .pipe(concat('lib.js'))
-      .pipe gulp.dest(target)
+      .pipe(gulp.dest(targets.gulp))
+      .pipe(gulp.dest(targets.appengineDevServer))
     gulp.src([
         'bower_components/bootstrap/dist/**/*'
       ])
-      .pipe gulp.dest(target)
+      .pipe(gulp.dest(targets.gulp))
+      .pipe(gulp.dest(targets.appengineDevServer))

 gulp.task 'coffee', ->
   gulp.src(sources.coffee)
@@ -36,17 +40,20 @@
     .pipe(ngmin())
     .pipe(uglify())
     .pipe(concat('app.js'))
-    .pipe gulp.dest(target)
+    .pipe(gulp.dest(targets.gulp))
+    .pipe(gulp.dest(targets.appengineDevServer))

 gulp.task 'less', ->
   gulp.src(sources.less)
     .pipe(less())
     .pipe(concat('app.css'))
-    .pipe gulp.dest(target)
+    .pipe(gulp.dest(targets.gulp))
+    .pipe(gulp.dest(targets.appengineDevServer))

 gulp.task 'static', ->
   gulp.src(sources.static)
-    .pipe gulp.dest(target)
+    .pipe(gulp.dest(targets.gulp))
+    .pipe(gulp.dest(targets.appengineDevServer))

 gulp.task 'default', ['clean'], ->
   gulp.start 'bower', 'coffee', 'less', 'static'
@@ -59,8 +66,8 @@

 gulp.task 'server', ['watch'], ->
   connect.server
-    root: target
+    root: targets.gulp
     port: 8888
     livereload: true

-gulp.task 'clean', (cb) -> del target, cb
+gulp.task 'clean', (cb) -> del targets.gulp, cb


[grunt] use `this` whenever possible in Sass task

--- tasks/grunt-sass.coffee
+++ tasks/grunt-sass.coffee
@@ -8,18 +8,20 @@
 { castToSass } = require('node-sass-utils')(sass)
 { get } = require('lodash')

-module.exports = (grunt) ->
+module.exports = ->
   @config 'sass',
     build:
       options:
         outputStyle: 'nested'
         sourceMap: true
         functions:
-          'kotsu-path($query)': (query) ->
+
+          'kotsu-path($query)': (query) =>
             query = query.getValue()
-            baseLocale = grunt.config('i18n.baseLocale')
-            paths = grunt.config.process(grunt.config('data')(baseLocale).path)
+            baseLocale = @config('i18n.baseLocale')
+            paths = @config.process(@config('data')(baseLocale).path)
             return castToSass(get(paths, query))
+
       files: [
         expand: true
         cwd: '<%= path.source.styles %>'


stop dev to add test

stop dev to add test

--- coffee/app.coffee
+++ coffee/app.coffee
@@ -10,26 +10,28 @@
     ($location, $rootScope) ->
         return {
             parse: () ->
-                serial =
-                    npid: ''
+                # default setting
+                npid = ''
+                zoom = 10
+                latitude = 35.680795
+                longitude = 139.76721
+
+                elements = $location.path().split('/').filter (e) -> e isnt ''
+
+                if elements.length > 2
+                    _longitude = parseFloat elements.pop()
+                    _latitude = parseFloat elements.pop()
+                    _zoom = parseInt elements.pop()
+                    console.log _zoom,_latitude,_longitude
+                if elements.length > 0
+                    npid = elements.pop()
+
+                $rootScope.serial =
+                    npid: npid
                     mapPosition:
-                        zoom: 10
-                        latitude: 35.680795
-                        longitude: 139.76721
-                elements = $location.path().split('/').filter (e) -> e isnt ''
-                if elements.length > 0
-                    serial.npid = elements[0]
-                if elements.length > 1
-                    zoom = parseInt elements[1]
-                    if zoom?
-                        serial.mapPosition.zoom = zoom
-                if elements.length > 3
-                    latitude = parseInt elements[2]
-                    longitude = parseInt elements[3]
-                    if latitude? and longitude?
-                        serial.mapPosition.latitude = elements[2]
-                        serial.mapPosition.longitude = elements[3]
-                $rootScope.serial = serial
+                        zoom: zoom
+                        latitude: latitude
+                        longitude: longitude
         }
 ]


Remove unused require

Remove unused require

--- src/index.cjsx
+++ src/index.cjsx
@@ -1,6 +1,5 @@
 React = require 'react'
 cx = require 'classnames'
-objectAssign = require 'react/lib/Object.assign'

 module.exports = React.createClass
   displayName: "SpinKit"


fix for empty files list

fix for empty files list

--- src/smc-hub/postgres-server-queries.coffee
+++ src/smc-hub/postgres-server-queries.coffee
@@ -1560,8 +1560,8 @@
                     # some files in the listing might not be public, since the containing path isn't public, so we filter
                     # WARNING: this is kind of stupid since misc.path_is_in_public_paths is badly implemented, especially
                     # for this sort of iteration.  TODO: make this faster.  This could matter since is done on server.
-                    if not misc.is_array(listing?.files?)  # There is no telling what listing actually is, so be careful.
-                        listing.files = {files:[]}
+                    if not listing?.files?[0]?  # There is no telling what listing actually is, so be careful.
+                        listing.files = []
                     else
                         listing.files = (x for x in listing.files when \
                             misc.path_is_in_public_paths(misc.path_to_file(opts.path, x.name), public_paths))


Actually do filter safe paths, but allow any other domain

--- server_setup.coffee
+++ server_setup.coffee
@@ -63,6 +63,8 @@
       domainPrefix = req.host.match(domainRegex)?[1] or ''
       if _.any(unsafePaths, (pathRegex) -> pathRegex.test(req.path)) and (req.host isnt domainPrefix + config.unsafeContentHostname)
         res.redirect('http://' + domainPrefix + config.unsafeContentHostname + req.path)
+      else if not _.any(unsafePaths, (pathRegex) -> pathRegex.test(req.path)) and req.host is domainPrefix + config.unsafeContentHostname
+        res.redirect('http://' + domainPrefix + config.mainHostname + req.path)
       else
         next()


Removed unused `ejs` module.

Removed unused `ejs` module.

--- server/app.coffee
+++ server/app.coffee
@@ -5,7 +5,6 @@
 #
 require 'coffee-script'

-ejs = require 'ejs'
 path = require 'path'
 require 'express-resource'
 express = require 'express'


Don't cache assets in development

Don't cache assets in development

--- server/app.coffee
+++ server/app.coffee
@@ -40,9 +40,12 @@

   app.use express.compress()

-  oneWeekInMilliseconds = (60 * 60 * 24 * 7) * 1000
-  oneYearInMilliseconds = oneWeekInMilliseconds * 52
-  app.use express.static(path.join(__dirname, "public"), maxAge: oneYearInMilliseconds)
+  maxAge = -1
+  if app.get('env') is 'production'
+    oneWeekInMilliseconds = (60 * 60 * 24 * 7) * 1000
+    oneYearInMilliseconds = oneWeekInMilliseconds * 52
+    maxAge = oneYearInMilliseconds
+  app.use express.static(path.join(__dirname, "public"), maxAge: maxAge)

   app.engine "hbs", hbs.express3(
     partialsDir: __dirname + '/views/partials'


make args.title a string to avoid error on toLowerCase

--- server/cli.coffee
+++ server/cli.coffee
@@ -25,6 +25,8 @@
         .options('title', alias: 't', demand: true, describe: 'Site title')
         .parse(argv)

+      args.title = "" + args.title
+
       siteService.setup().then ->
         albumService.newAlbum(null, title: args.title)
       .then ->
@@ -41,6 +43,8 @@
             .options('description', alias: 'd', default: '', describe: 'Album description')
             .options('parent', alias: 'p', describe: 'Path of the parent album')
             .parse(argv)
+
+          args.title = "" + args.title

           albumService.newAlbum(args.parent, _.pick(args, 'title', 'description')).then ->
             process.exit()


support objects in getValue

support objects in getValue

--- hexaflip.coffee
+++ hexaflip.coffee
@@ -246,13 +246,16 @@
           offset = setLength + offset

       offset %= setLength if offset >= setLength
-      set[offset]
+      if typeof set[offset] is 'object'
+        set[offset].value
+      else
+        set[offset]

   flip: (back) ->
     delta = if back then -90 else 90
     for set, cube of @cubes
-      continue if @cubes[key].touchStarted
+      continue if cube.touchStarted
       cube.yDelta = cube.yLast += delta
       @_setSides cube


Improve docs for up:click event

Improve docs for up:click event

--- lib/assets/javascripts/unpoly/link.coffee
+++ lib/assets/javascripts/unpoly/link.coffee
@@ -585,12 +585,12 @@
   \#\#\# Cancelation is forwarded

   If the user cancels an `up:click` event using `event.preventDefault()`,
-  the underlying `click` or `mousedown` will also be canceled.
+  the underlying `click` or `mousedown` event will also be canceled.

   \#\#\# Accessibility

   If the user activates an element using their keyboard, the `up:click` event will be emitted
-  on `click`, even if the element has an `[up-instant]` attribute.
+  when the key is pressed even if the element has an `[up-instant]` attribute.

   @event up:click
   @param {Element} event.target


trying to support outputElement of code chunk in html export

--- lib/markdown-preview-enhanced-view.coffee
+++ lib/markdown-preview-enhanced-view.coffee
@@ -639,6 +639,15 @@
         running = @codeChunksData[id]?.running or false
         codeChunk.classList.add('running') if running

+        # remove output-div and output-element
+        children = codeChunk.children
+        i = children.length - 1
+        while i >= 0
+          child = children[i]
+          if child.classList.contains('output-div') or child.classList.contains('output-element')
+            child.remove()
+          i -= 1
+
         outputDiv = @codeChunksData[id]?.outputDiv
         outputElement = @codeChunksData[id]?.outputElement

@@ -735,13 +744,11 @@

     cmd =  options.cmd or codeChunk.getAttribute('data-lang')

-    # check id and save outputDiv to @codeChunksData
-    idMatch = dataArgs.match(/\s*id\s*:\s*\"([^\"]*)\"/)
-
-    if !idMatch
+    # check id and save outputDiv & outputElement to @codeChunksData
+    id = options.id
+
+    if !id
       return atom.notifications.addError('Code chunk error', detail: 'id is not found or just updated.')
-    else
-      id = idMatch[1]

     codeChunk.classList.add('running')
     if @codeChunksData[id]
@@ -1033,20 +1040,37 @@
     # insert outputDiv and outputElement accordingly
     $ = cheerio.load(htmlContent, {decodeEntities: true})
     codeChunks = $('.code-chunk')
+    jsCode = ''
     for codeChunk in codeChunks
       $codeChunk = $(codeChunk)
       dataArgs = $codeChunk.attr('data-args')
-      idMatch = dataArgs.match(/\s*id\s*:\s*\"([^\"]*)\"/)
-
-      continue if !idMatch or !idMatch[1]
-      id = idMatch[1]
+
+      options = null
+      try
+        allowUnsafeEval ->
+          options = eval("({#{dataArgs}})")
+      catch e
+        continue
+
+      id = options.id
+      continue if !id
+
+      cmd = options.cmd or $codeChunk.attr('data-lang')
+
       outputDiv = @codeChunksData[id]?.outputDiv
       outputElement = @codeChunksData[id]?.outputElement

       if outputDiv # append outputDiv result
         $codeChunk.append("
#{outputDiv.innerHTML}
")

-    $.html()
+      if outputElement
+        $codeChunk.append("
#{outputElement.innerHTML}
")
+
+      if cmd == 'javascript'
+        code = $codeChunk.attr('data-code')
+        jsCode += code + '\n'
+
+    $.html() + ""

   getHTMLContent: ({isForPrint, offline, useRelativeImagePath, phantomjsType})->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -31,8 +31,8 @@
     alpha: 0.4

   equals enemy.I.flickerAlpha, 0.4
-  equals enemy.I.flickerFrequency, 3
-  equals enemy.I.flickerDuration, 30
+  equals enemy.I.flickerFrequency, 0.1
+  equals enemy.I.flickerDuration, 2

   boss = GameObject()